未验证 提交 72bf4547 编写于 作者: A Asher 提交者: Kyle Carberry

Getting the client to run (#12)

* Clean up workbench and integrate initialization data

* Uncomment Electron fill

* Run server & client together

* Clean up Electron fill & patch

* Bind fs methods

This makes them usable with the promise form:
`promisify(access)(...)`.

* Add space between tag and title to browser logger

* Add typescript dep to server and default __dirname for path

* Serve web files from server

* Adjust some dev options

* Rework workbench a bit to use a class and catch unexpected errors

* No mkdirs for now, fix util fill, use bash with exec

* More fills, make general client abstract

* More fills

* Fix cp.exec

* Fix require calls in fs fill being aliased

* Create data and storage dir

* Implement fs.watch

Using exec for now.

* Implement storage database fill

* Fix os export and homedir

* Add comment to use navigator.sendBeacon

* Fix fs callbacks (some args are optional)

* Make sure data directory exists when passing it back

* Update patch

* Target es5

* More fills

* Add APIs required for bootstrap-fork to function (#15)

* Add bootstrap-fork execution

* Add createConnection

* Bundle bootstrap-fork into cli

* Remove .node directory created from spdlog

* Fix npm start

* Remove unnecessary comment

* Add webpack-hot-middleware if CLI env is not set

* Add restarting to shared process

* Fix starting with yarn
上级 05899b5e
lib/vscode
lib/vscode*
lib/VSCode*
node_modules
dist
......@@ -9,9 +9,9 @@
"vscode:clone": "mkdir -p ./lib && test -d ./lib/vscode || git clone https://github.com/Microsoft/vscode/ ./lib/vscode",
"vscode:install": "cd ./lib/vscode && git checkout tags/1.30.1 && yarn",
"vscode": "npm-run-all vscode:*",
"packages:install": "cd ./packages && yarn && ts-node ../scripts/install-packages.ts",
"packages:install": "cd ./packages && yarn",
"postinstall": "npm-run-all --parallel vscode packages:install build:rules",
"start": "webpack-dev-server --hot --config ./webpack.config.app.js",
"start": "cd ./packages/server && yarn start",
"test": "cd ./packages && yarn test"
},
"devDependencies": {
......@@ -26,9 +26,9 @@
"mini-css-extract-plugin": "^0.5.0",
"node-sass": "^4.11.0",
"npm-run-all": "^4.1.5",
"os-browserify": "^0.3.0",
"preload-webpack-plugin": "^3.0.0-beta.2",
"sass-loader": "^7.1.0",
"string-replace-loader": "^2.1.1",
"style-loader": "^0.23.1",
"ts-loader": "^5.3.3",
"ts-node": "^7.0.1",
......@@ -39,7 +39,9 @@
"webpack": "^4.28.4",
"webpack-bundle-analyzer": "^3.0.3",
"webpack-cli": "^3.2.1",
"webpack-dev-middleware": "^3.5.0",
"webpack-dev-server": "^3.1.14",
"webpack-hot-middleware": "^2.24.3",
"write-file-webpack-plugin": "^4.5.0"
},
"dependencies": {
......
{
"name": "@coder/ide",
"description": "Browser-based IDE client abstraction.",
"main": "src/index.ts"
"main": "src/index.ts"
}
import { exec } from "child_process";
import { promisify } from "util";
import { field, logger, time, Time } from "@coder/logger";
import { escapePath } from "@coder/protocol";
import { retry } from "./retry";
import { InitData } from "@coder/protocol";
import { retry, Retry } from "./retry";
import { client } from "./fill/client";
import { Clipboard, clipboard } from "./fill/clipboard";
export interface IURI {
readonly path: string;
readonly fsPath: string;
readonly scheme: string;
}
export interface IURIFactory {
/**
* Convert the object to an instance of a real URI.
*/
create<T extends IURI>(uri: IURI): T;
file(path: string): IURI;
parse(raw: string): IURI;
export interface IClientOptions {
mkDirs?: string[];
}
/**
......@@ -14,33 +29,81 @@ export interface IClientOptions {
* Everything the client provides is asynchronous so you can wait on what
* you need from it without blocking anything else.
*
* It also provides task management to help asynchronously load and time
* external code.
* It also provides task management to help asynchronously load and time code.
*/
export class Client {
export abstract class Client {
public readonly mkDirs: Promise<void>;
public readonly retry: Retry = retry;
public readonly clipboard: Clipboard = clipboard;
public readonly uriFactory: IURIFactory;
private start: Time | undefined;
private readonly progressElement: HTMLElement | undefined;
private tasks: string[];
private finishedTaskCount: number;
private tasks: string[] = [];
private finishedTaskCount = 0;
private readonly loadTime: Time;
public constructor() {
logger.info("Loading IDE");
this.loadTime = time(2500);
const overlay = document.getElementById("overlay");
const logo = document.getElementById("logo");
const msgElement = overlay
? overlay.querySelector(".message") as HTMLElement
: undefined;
if (overlay && logo) {
overlay.addEventListener("mousemove", (event) => {
const xPos = ((event.clientX - logo.offsetLeft) / 24).toFixed(2);
const yPos = ((logo.offsetTop - event.clientY) / 24).toFixed(2);
logo.style.transform = `perspective(200px) rotateX(${yPos}deg) rotateY(${xPos}deg)`;
});
}
public constructor(options: IClientOptions) {
this.tasks = [];
this.finishedTaskCount = 0;
this.progressElement = typeof document !== "undefined"
? document.querySelector("#fill") as HTMLElement
: undefined;
this.mkDirs = this.wrapTask("Creating directories", 100, async () => {
if (options.mkDirs && options.mkDirs.length > 0) {
await promisify(exec)(`mkdir -p ${options.mkDirs.map(escapePath).join(" ")}`);
}
require("path").posix = require("path");
window.addEventListener("contextmenu", (event) => {
event.preventDefault();
});
// Prevent Firefox from trying to reconnect when the page unloads.
window.addEventListener("unload", () => {
retry.block();
this.retry.block();
logger.info("Unloaded");
});
this.uriFactory = this.createUriFactory();
this.initialize().then(() => {
if (overlay) {
overlay.style.opacity = "0";
overlay.addEventListener("transitionend", () => {
overlay.remove();
});
}
logger.info("Load completed", field("duration", this.loadTime));
}).catch((error) => {
logger.error(error.message);
if (overlay) {
overlay.classList.add("error");
}
if (msgElement) {
const button = document.createElement("div");
button.className = "reload-button";
button.innerText = "Reload";
button.addEventListener("click", () => {
location.reload();
});
msgElement.innerText = `Failed to load: ${error.message}.`;
msgElement.parentElement!.appendChild(button);
}
logger.warn("Load completed with errors", field("duration", this.loadTime));
});
}
......@@ -48,14 +111,14 @@ export class Client {
* Wrap a task in some logging, timing, and progress updates. Can optionally
* wait on other tasks which won't count towards this task's time.
*/
public async wrapTask<T>(description: string, duration: number, task: () => Promise<T>): Promise<T>;
public async wrapTask<T, V>(description: string, duration: number, task: (v: V) => Promise<T>, t: Promise<V>): Promise<T>;
public async wrapTask<T, V1, V2>(description: string, duration: number, task: (v1: V1, v2: V2) => Promise<T>, t1: Promise<V1>, t2: Promise<V2>): Promise<T>;
public async wrapTask<T, V1, V2, V3>(description: string, duration: number, task: (v1: V1, v2: V2, v3: V3) => Promise<T>, t1: Promise<V1>, t2: Promise<V2>, t3: Promise<V3>): Promise<T>;
public async wrapTask<T, V1, V2, V3, V4>(description: string, duration: number, task: (v1: V1, v2: V2, v3: V3, v4: V4) => Promise<T>, t1: Promise<V1>, t2: Promise<V2>, t3: Promise<V3>, t4: Promise<V4>): Promise<T>;
public async wrapTask<T, V1, V2, V3, V4, V5>(description: string, duration: number, task: (v1: V1, v2: V2, v3: V3, v4: V4, v5: V5) => Promise<T>, t1: Promise<V1>, t2: Promise<V2>, t3: Promise<V3>, t4: Promise<V4>, t5: Promise<V5>): Promise<T>;
public async wrapTask<T, V1, V2, V3, V4, V5, V6>(description: string, duration: number, task: (v1: V1, v2: V2, v3: V3, v4: V4, v5: V5, v6: V6) => Promise<T>, t1: Promise<V1>, t2: Promise<V2>, t3: Promise<V3>, t4: Promise<V4>, t5: Promise<V5>, t6: Promise<V6>): Promise<T>;
public async wrapTask<T>(
public async task<T>(description: string, duration: number, task: () => Promise<T>): Promise<T>;
public async task<T, V>(description: string, duration: number, task: (v: V) => Promise<T>, t: Promise<V>): Promise<T>;
public async task<T, V1, V2>(description: string, duration: number, task: (v1: V1, v2: V2) => Promise<T>, t1: Promise<V1>, t2: Promise<V2>): Promise<T>;
public async task<T, V1, V2, V3>(description: string, duration: number, task: (v1: V1, v2: V2, v3: V3) => Promise<T>, t1: Promise<V1>, t2: Promise<V2>, t3: Promise<V3>): Promise<T>;
public async task<T, V1, V2, V3, V4>(description: string, duration: number, task: (v1: V1, v2: V2, v3: V3, v4: V4) => Promise<T>, t1: Promise<V1>, t2: Promise<V2>, t3: Promise<V3>, t4: Promise<V4>): Promise<T>;
public async task<T, V1, V2, V3, V4, V5>(description: string, duration: number, task: (v1: V1, v2: V2, v3: V3, v4: V4, v5: V5) => Promise<T>, t1: Promise<V1>, t2: Promise<V2>, t3: Promise<V3>, t4: Promise<V4>, t5: Promise<V5>): Promise<T>;
public async task<T, V1, V2, V3, V4, V5, V6>(description: string, duration: number, task: (v1: V1, v2: V2, v3: V3, v4: V4, v5: V5, v6: V6) => Promise<T>, t1: Promise<V1>, t2: Promise<V2>, t3: Promise<V3>, t4: Promise<V4>, t5: Promise<V5>, t6: Promise<V6>): Promise<T>;
public async task<T>(
description: string, duration: number = 100, task: (...args: any[]) => Promise<T>, ...after: Array<Promise<any>> // tslint:disable-line no-any
): Promise<T> {
this.tasks.push(description);
......@@ -97,4 +160,21 @@ export class Client {
}
}
/**
* A promise that resolves with initialization data.
*/
public get initData(): Promise<InitData> {
return client.initData;
}
/**
* Initialize the IDE.
*/
protected abstract initialize(): Promise<void>;
/**
* Create URI factory.
*/
protected abstract createUriFactory(): IURIFactory;
}
import { Emitter } from "@coder/events";
import { logger, field } from "@coder/logger";
import { field, logger } from "@coder/logger";
import { Client, ReadWriteConnection } from "@coder/protocol";
import { retry } from "../retry";
......@@ -10,27 +10,20 @@ import { retry } from "../retry";
class Connection implements ReadWriteConnection {
private activeSocket: WebSocket | undefined;
private readonly messageEmitter: Emitter<Uint8Array>;
private readonly closeEmitter: Emitter<void>;
private readonly upEmitter: Emitter<void>;
private readonly downEmitter: Emitter<void>;
private readonly messageBuffer: Uint8Array[];
private readonly messageEmitter: Emitter<Uint8Array> = new Emitter();
private readonly closeEmitter: Emitter<void> = new Emitter();
private readonly upEmitter: Emitter<void> = new Emitter();
private readonly downEmitter: Emitter<void> = new Emitter();
private readonly messageBuffer: Uint8Array[] = [];
private socketTimeoutDelay = 60 * 1000;
private retryName = "Web socket";
private isUp: boolean | undefined;
private closed: boolean | undefined;
private isUp: boolean = false;
private closed: boolean = false;
public constructor() {
this.messageEmitter = new Emitter();
this.closeEmitter = new Emitter();
this.upEmitter = new Emitter();
this.downEmitter = new Emitter();
this.messageBuffer = [];
retry.register(this.retryName, () => this.connect());
this.connect().catch(() => {
retry.block(this.retryName);
retry.run(this.retryName);
});
retry.block(this.retryName);
retry.run(this.retryName);
}
/**
......@@ -72,7 +65,7 @@ class Connection implements ReadWriteConnection {
this.closeEmitter.emit();
}
/**
/**
* Connect to the server.
*/
private async connect(): Promise<void> {
......@@ -116,7 +109,7 @@ class Connection implements ReadWriteConnection {
private async openSocket(): Promise<WebSocket> {
this.dispose();
const socket = new WebSocket(
`${location.protocol === "https" ? "wss" : "ws"}://${location.host}/websocket`,
`${location.protocol === "https" ? "wss" : "ws"}://${location.host}`,
);
socket.binaryType = "arraybuffer";
this.activeSocket = socket;
......@@ -153,7 +146,5 @@ class Connection implements ReadWriteConnection {
}
/**
* A client for proxying Node APIs based on web sockets.
*/
// Global instance so all fills can use the same client.
export const client = new Client(new Connection());
import { IDisposable } from "@coder/disposable";
import { Emitter } from "@coder/events";
/**
* Native clipboard.
*/
export class Clipboard {
private readonly enableEmitter: Emitter<boolean> = new Emitter();
private _isEnabled: boolean = false;
/**
* Ask for permission to use the clipboard.
*/
public initialize(): void {
// tslint:disable no-any
const navigatorClip = (navigator as any).clipboard;
const navigatorPerms = (navigator as any).permissions;
// tslint:enable no-any
if (navigatorClip && navigatorPerms) {
navigatorPerms.query({
name: "clipboard-read",
}).then((permissionStatus: {
onchange: () => void,
state: "denied" | "granted" | "prompt",
}) => {
const updateStatus = (): void => {
this._isEnabled = permissionStatus.state !== "denied";
this.enableEmitter.emit(this.isEnabled);
};
updateStatus();
permissionStatus.onchange = (): void => {
updateStatus();
};
});
}
}
/**
* Return true if the native clipboard is supported.
*/
public get isSupported(): boolean {
// tslint:disable no-any
return typeof navigator !== "undefined"
&& typeof (navigator as any).clipboard !== "undefined"
&& typeof (navigator as any).clipboard.readText !== "undefined";
// tslint:enable no-any
}
/**
* Register a function to be called when the native clipboard is
* enabled/disabled.
*/
public onPermissionChange(cb: (enabled: boolean) => void): IDisposable {
return this.enableEmitter.event(cb);
}
/**
* Read text from the clipboard.
*/
public readText(): Promise<string> {
return this.instance ? this.instance.readText() : Promise.resolve("");
}
/**
* Write text to the clipboard.
*/
public writeText(value: string): Promise<void> {
return this.instance
? this.instance.writeText(value)
: this.writeTextFallback(value);
}
/**
* Return true if the clipboard is currently enabled.
*/
public get isEnabled(): boolean {
return !!this._isEnabled;
}
/**
* Return clipboard instance if there is one.
*/
private get instance(): ({
readText(): Promise<string>;
writeText(value: string): Promise<void>;
}) | undefined {
// tslint:disable-next-line no-any
return this.isSupported ? (navigator as any).clipboard : undefined;
}
/**
* Fallback for writing text to the clipboard.
* Taken from https://hackernoon.com/copying-text-to-clipboard-with-javascript-df4d4988697f
*/
private writeTextFallback(value: string): Promise<void> {
// Note the current focus and selection.
const active = document.activeElement as HTMLElement;
const selection = document.getSelection();
const selected = selection && selection.rangeCount > 0
? selection.getRangeAt(0)
: false;
// Insert a hidden textarea to put the text to copy in.
const el = document.createElement("textarea");
el.value = value;
el.setAttribute("readonly", "");
el.style.position = "absolute";
el.style.left = "-9999px";
document.body.appendChild(el);
// Select the textarea and execute a copy (this will only work as part of a
// user interaction).
el.select();
document.execCommand("copy");
// Remove the textarea and put focus and selection back to where it was
// previously.
document.body.removeChild(el);
active.focus();
if (selected && selection) {
selection.removeAllRanges();
selection.addRange(selected);
}
return Promise.resolve();
}
}
// Global clipboard instance since it's used in the Electron fill.
export const clipboard = new Clipboard();
......@@ -131,7 +131,7 @@ export class Dialog {
/**
* Display or remove an error.
*/
public set error(error: string) {
public set error(error: string | undefined) {
while (this.errors.lastChild) {
this.errors.removeChild(this.errors.lastChild);
}
......
此差异已折叠。
import { InitData } from "@coder/protocol";
import { client } from "./client";
class OS {
private _homedir: string | undefined;
private _tmpdir: string | undefined;
public constructor() {
client.initData.then((data) => {
this.initialize(data);
});
}
public homedir(): string {
if (typeof this._homedir === "undefined") {
throw new Error("not initialized");
}
return this._homedir;
}
public tmpdir(): string {
if (typeof this._tmpdir === "undefined") {
throw new Error("not initialized");
}
return this._tmpdir;
}
public initialize(data: InitData): void {
this._homedir = data.homeDirectory;
this._tmpdir = data.tmpDirectory;
}
}
export = new OS();
import { implementation as promisify } from "util.promisify";
export * from "../../../../node_modules/util";
import { implementation } from "util.promisify";
export {
promisify,
}
export const promisify = implementation;
export * from "./client";
export * from "./retry";
export * from "./upload";
export * from "./uri";
......@@ -209,7 +209,7 @@ export class Retry {
const item = this.items.get(name)!;
if (typeof item.timeout === "undefined" && !item.running && typeof item.count !== "undefined") {
logger.info(`Recovered connection to ${name.toLowerCase()}`);
logger.info(`Connected to ${name.toLowerCase()}`);
item.delay = undefined;
item.count = undefined;
}
......@@ -228,7 +228,7 @@ export class Retry {
const retryCountText = item.count <= this.maxImmediateRetries
? `[${item.count}/${this.maxImmediateRetries}]`
: `[${item.count}]`;
logger.info(`Retrying ${name.toLowerCase()} ${retryCountText}...`);
logger.info(`Trying ${name.toLowerCase()} ${retryCountText}...`);
const endItem = (): void => {
this.stopItem(item);
......@@ -341,4 +341,6 @@ export class Retry {
}
// Global instance so we can block other retries when retrying the main
// connection.
export const retry = new Retry();
export interface IURI {
readonly path: string;
readonly fsPath: string;
readonly scheme: string;
}
export interface IURIFactory {
/**
* Convert the object to an instance of a real URI.
*/
create<T extends IURI>(uri: IURI): T;
file(path: string): IURI;
parse(raw: string): IURI;
}
let activeUriFactory: IURIFactory;
/**
* Get the active URI factory
*/
export const getFactory = (): IURIFactory => {
if (!activeUriFactory) {
throw new Error("default uri factory not set");
}
return activeUriFactory;
};
/**
* Update the active URI factory.
*/
export const setUriFactory = (factory: IURIFactory): void => {
activeUriFactory = factory;
};
export interface IUriSwitcher {
strip(uri: IURI): IURI;
prepend(uri: IURI): IURI;
}
......@@ -158,6 +158,8 @@ export class BrowserFormatter extends Formatter {
+ " padding-bottom: 1px; font-size: 12px; font-weight: bold; color: white;"
+ (name.length === 4 ? "padding-left: 3px; padding-right: 4px;" : ""),
);
// A space to separate the tag from the title.
this.push(" ");
}
public push(arg: any, color: string = "inherit", weight: string = "normal"): void { // tslint:disable-line no-any
......
{
"scripts": {
"postinstall": "../node_modules/.bin/ts-node ../scripts/install-packages.ts",
"test": "jest"
},
"devDependencies": {
......
import { ReadWriteConnection, InitData, OperatingSystem } from "../common/connection";
import { NewEvalMessage, ServerMessage, EvalDoneMessage, EvalFailedMessage, TypedValue, ClientMessage, NewSessionMessage, TTYDimensions, SessionOutputMessage, CloseSessionInputMessage, InitMessage } from "../proto";
import { Emitter, Event } from "@coder/events";
import { NewEvalMessage, ServerMessage, EvalDoneMessage, EvalFailedMessage, TypedValue, ClientMessage, NewSessionMessage, TTYDimensions, SessionOutputMessage, CloseSessionInputMessage, WorkingInitMessage, NewConnectionMessage } from "../proto";
import { Emitter } from "@coder/events";
import { logger, field } from "@coder/logger";
import { ChildProcess, SpawnOptions, ServerProcess } from "./command";
import { ChildProcess, SpawnOptions, ServerProcess, ServerSocket, Socket } from "./command";
/**
* Client accepts an arbitrary connection intended to communicate with the Server.
......@@ -13,10 +13,14 @@ export class Client {
private evalFailedEmitter: Emitter<EvalFailedMessage> = new Emitter();
private sessionId: number = 0;
private sessions: Map<number, ServerProcess> = new Map();
private readonly sessions: Map<number, ServerProcess> = new Map();
private connectionId: number = 0;
private readonly connections: Map<number, ServerSocket> = new Map();
private _initData: InitData | undefined;
private initDataEmitter: Emitter<InitData> = new Emitter();
private initDataPromise: Promise<InitData>;
/**
* @param connection Established connection to the server
......@@ -33,14 +37,14 @@ export class Client {
logger.error("Failed to handle server message", field("length", data.byteLength), field("exception", ex));
}
});
}
public get onInitData(): Event<InitData> {
return this.initDataEmitter.event;
this.initDataPromise = new Promise((resolve): void => {
this.initDataEmitter.event(resolve);
});
}
public get initData(): InitData | undefined {
return this._initData;
public get initData(): Promise<InitData> {
return this.initDataPromise;
}
public evaluate<R>(func: () => R | Promise<R>): Promise<R>;
......@@ -62,7 +66,7 @@ export class Client {
* @param func Function to evaluate
* @returns Promise rejected or resolved from the evaluated function
*/
public evaluate<R, T1, T2, T3, T4, T5, T6>(func: (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> {
public evaluate<R, T1, T2, T3, T4, T5, T6>(func: (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> {
const newEval = new NewEvalMessage();
const id = this.evalId++;
newEval.setId(id);
......@@ -151,6 +155,27 @@ export class Client {
return this.doSpawn(modulePath, args, options, true);
}
public createConnection(path: string, callback?: () => void): Socket;
public createConnection(port: number, callback?: () => void): Socket;
public createConnection(target: string | number, callback?: () => void): Socket {
const id = this.connectionId++;
const newCon = new NewConnectionMessage();
newCon.setId(id);
if (typeof target === "string") {
newCon.setPath(target);
} else {
newCon.setPort(target);
}
const clientMsg = new ClientMessage();
clientMsg.setNewConnection(newCon);
this.connection.send(clientMsg.serializeBinary());
const socket = new ServerSocket(this.connection, id, callback);
this.connections.set(id, socket);
return socket;
}
private doSpawn(command: string, args: string[] = [], options?: SpawnOptions, isFork: boolean = false): ChildProcess {
const id = this.sessionId++;
const newSess = new NewSessionMessage();
......@@ -200,13 +225,13 @@ export class Client {
const init = message.getInit()!;
let opSys: OperatingSystem;
switch (init.getOperatingSystem()) {
case InitMessage.OperatingSystem.WINDOWS:
case WorkingInitMessage.OperatingSystem.WINDOWS:
opSys = OperatingSystem.Windows;
break;
case InitMessage.OperatingSystem.LINUX:
case WorkingInitMessage.OperatingSystem.LINUX:
opSys = OperatingSystem.Linux;
break;
case InitMessage.OperatingSystem.MAC:
case WorkingInitMessage.OperatingSystem.MAC:
opSys = OperatingSystem.Mac;
break;
default:
......@@ -253,6 +278,33 @@ export class Client {
return;
}
s.pid = message.getIdentifySession()!.getPid();
} 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());
}
}
}
import * as events from "events";
import * as stream from "stream";
import { SendableConnection } from "../common/connection";
import { ShutdownSessionMessage, ClientMessage, SessionOutputMessage, WriteToSessionMessage, ResizeSessionTTYMessage, TTYDimensions as ProtoTTYDimensions } from "../proto";
import { ShutdownSessionMessage, ClientMessage, WriteToSessionMessage, ResizeSessionTTYMessage, TTYDimensions as ProtoTTYDimensions, ConnectionOutputMessage, ConnectionCloseMessage } from "../proto";
export interface TTYDimensions {
readonly columns: number;
......@@ -33,8 +33,8 @@ export interface ChildProcess {
export class ServerProcess extends events.EventEmitter implements ChildProcess {
public readonly stdin = new stream.Writable();
public readonly stdout = new stream.Readable({ read: () => true });
public readonly stderr = new stream.Readable({ read: () => true });
public readonly stdout = new stream.Readable({ read: (): boolean => true });
public readonly stderr = new stream.Readable({ read: (): boolean => true });
public pid: number | undefined;
private _killed: boolean = false;
......@@ -42,7 +42,7 @@ export class ServerProcess extends events.EventEmitter implements ChildProcess {
public constructor(
private readonly connection: SendableConnection,
private readonly id: number,
private readonly hasTty: boolean = false,
private readonly hasTty: boolean = false,
) {
super();
......@@ -77,7 +77,7 @@ export class ServerProcess extends events.EventEmitter implements ChildProcess {
this.connection.send(client.serializeBinary());
}
public resize(dimensions: TTYDimensions) {
public resize(dimensions: TTYDimensions): void {
const resize = new ResizeSessionTTYMessage();
resize.setId(this.id);
const tty = new ProtoTTYDimensions();
......@@ -89,3 +89,129 @@ export class ServerProcess extends events.EventEmitter implements ChildProcess {
this.connection.send(client.serializeBinary());
}
}
export interface Socket {
readonly destroyed: boolean;
readonly connecting: boolean;
write(buffer: Buffer): void;
end(): 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 = true;
public constructor(
private readonly connection: SendableConnection,
private readonly id: number,
connectCallback?: () => void,
) {
super();
if (connectCallback) {
this.once("connect", () => {
this._connecting = false;
connectCallback();
});
}
}
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.");
}
}
\ No newline at end of file
import * as cp from "child_process";
import { Client } from "../client";
import { useBuffer } from "./util";
import { useBuffer } from "../../common/util";
export class CP {
......@@ -13,19 +13,21 @@ export class CP {
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),
): cp.ChildProcess => {
const process = this.client.spawn(command);
// 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 = "";
process.stdout.on("data", (data) => {
childProcess.stdout.on("data", (data) => {
stdout += data.toString();
});
let stderr = "";
process.stderr.on("data", (data) => {
childProcess.stderr.on("data", (data) => {
stderr += data.toString();
});
process.on("exit", (exitCode) => {
childProcess.on("exit", (exitCode) => {
const error = exitCode !== 0 ? new Error(stderr) : null;
if (typeof options === "function") {
callback = options;
......@@ -39,15 +41,15 @@ export class CP {
});
// @ts-ignore
return process;
return childProcess;
}
public fork(modulePath: string): cp.ChildProcess {
public fork = (modulePath: string, args?: ReadonlyArray<string> | cp.ForkOptions, options?: cp.ForkOptions): cp.ChildProcess => {
//@ts-ignore
return this.client.fork(modulePath);
return this.client.fork(modulePath, args, options);
}
public spawn(command: string, args?: ReadonlyArray<string> | cp.SpawnOptions, _options?: cp.SpawnOptions): cp.ChildProcess {
public spawn = (command: string, args?: ReadonlyArray<string> | cp.SpawnOptions, options?: cp.SpawnOptions): cp.ChildProcess => {
// TODO: fix this ignore. Should check for args or options here
//@ts-ignore
return this.client.spawn(command, args, options);
......
import * as net from "net";
import { Client } from '../client';
type NodeNet = typeof net;
......@@ -7,6 +8,10 @@ type NodeNet = typeof net;
*/
export class Net implements NodeNet {
public constructor(
private readonly client: Client,
) {}
public get Socket(): typeof net.Socket {
throw new Error("not implemented");
}
......@@ -19,8 +24,9 @@ export class Net implements NodeNet {
throw new Error("not implemented");
}
public createConnection(): net.Socket {
throw new Error("not implemented");
public createConnection(...args: any[]): net.Socket {
//@ts-ignore
return this.client.createConnection(...args) as net.Socket;
}
public isIP(_input: string): number {
......
/**
* Return true if the options specify to use a Buffer instead of string.
*/
export const useBuffer = (options: { encoding?: string | null } | string | undefined | null | Function): boolean => {
return options === "buffer"
|| (!!options && typeof options !== "string" && typeof options !== "function"
&& (options.encoding === "buffer" || options.encoding === null));
};
......@@ -12,3 +12,23 @@ export const isBrowserEnvironment = (): boolean => {
export const escapePath = (path: string): string => {
return `'${path.replace(/'/g, "'\\''")}'`;
};
export type IEncodingOptions = {
encoding?: string | null;
flag?: string;
mode?: string;
persistent?: boolean;
recursive?: boolean;
} | string | undefined | null;
// tslint:disable-next-line no-any
export type IEncodingOptionsCallback = IEncodingOptions | ((err: NodeJS.ErrnoException, ...args: any[]) => void);
/**
* Return true if the options specify to use a Buffer instead of string.
*/
export const useBuffer = (options: IEncodingOptionsCallback): boolean => {
return options === "buffer"
|| (!!options && typeof options !== "string" && typeof options !== "function"
&& (options.encoding === "buffer" || options.encoding === null));
};
import * as cp from "child_process";
import * as net from "net";
import * as nodePty from "node-pty";
import * as stream from "stream";
import { TextEncoder } from "text-encoding";
import { NewSessionMessage, ServerMessage, SessionDoneMessage, SessionOutputMessage, ShutdownSessionMessage, IdentifySessionMessage, ClientMessage } from "../proto";
import { NewSessionMessage, ServerMessage, SessionDoneMessage, SessionOutputMessage, ShutdownSessionMessage, IdentifySessionMessage, ClientMessage, NewConnectionMessage, ConnectionEstablishedMessage, NewConnectionFailureMessage, ConnectionCloseMessage, ConnectionOutputMessage } from "../proto";
import { SendableConnection } from "../common/connection";
export interface Process {
......@@ -59,7 +60,7 @@ export const handleNewSession = (connection: SendableConnection, newSession: New
};
}
const sendOutput = (fd: SessionOutputMessage.FD, msg: string | Uint8Array): void => {
const sendOutput = (_fd: SessionOutputMessage.FD, msg: string | Uint8Array): void => {
const serverMsg = new ServerMessage();
const d = new SessionOutputMessage();
d.setId(newSession.getId());
......@@ -90,7 +91,7 @@ export const handleNewSession = (connection: SendableConnection, newSession: New
sm.setIdentifySession(id);
connection.send(sm.serializeBinary());
process.on("exit", (code, signal) => {
process.on("exit", (code) => {
const serverMsg = new ServerMessage();
const exit = new SessionDoneMessage();
exit.setId(newSession.getId());
......@@ -103,3 +104,61 @@ export const handleNewSession = (connection: SendableConnection, newSession: New
return process;
};
export const handleNewConnection = (connection: SendableConnection, newConnection: NewConnectionMessage, onExit: () => void): net.Socket => {
const id = newConnection.getId();
let socket: net.Socket;
let didConnect = false;
const connectCallback = () => {
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) => {
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", () => {
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) => {
const dataMsg = new ConnectionOutputMessage();
dataMsg.setId(id);
dataMsg.setData(data);
const servMsg = new ServerMessage();
servMsg.setConnectionOutput(dataMsg);
connection.send(servMsg.serializeBinary());
});
return socket;
}
\ No newline at end of file
......@@ -29,8 +29,7 @@ export const evaluate = async (connection: SendableConnection, message: NewEvalM
t = TypedValue.Type.NUMBER;
break;
default:
sendErr(EvalFailedMessage.Reason.EXCEPTION, `unsupported response type ${tof}`);
return;
return sendErr(EvalFailedMessage.Reason.EXCEPTION, `unsupported response type ${tof}`);
}
tv.setValue(tof === "string" ? resp : JSON.stringify(resp));
tv.setType(t);
......@@ -52,11 +51,17 @@ export const evaluate = async (connection: SendableConnection, message: NewEvalM
connection.send(serverMsg.serializeBinary());
};
try {
const value = vm.runInNewContext(`(${message.getFunction()})(${argStr.join(",")})`, { Buffer, require: typeof __non_webpack_require__ !== "undefined" ? __non_webpack_require__ : require, setTimeout }, {
const value = vm.runInNewContext(`(${message.getFunction()})(${argStr.join(",")})`, {
Buffer,
require: typeof __non_webpack_require__ !== "undefined" ? __non_webpack_require__ : require,
_require: typeof __non_webpack_require__ !== "undefined" ? __non_webpack_require__ : require,
tslib_1: require("tslib"), // TODO: is there a better way to do this?
setTimeout,
}, {
timeout: message.getTimeout() || 30000,
});
sendResp(await value);
} catch (ex) {
sendErr(EvalFailedMessage.Reason.EXCEPTION, ex.toString());
}
};
\ No newline at end of file
};
import { logger, field } from "@coder/logger";
import * as os from "os";
import * as path from "path";
import { mkdir } from "fs";
import { promisify } from "util";
import { TextDecoder } from "text-encoding";
import { ClientMessage, InitMessage, ServerMessage } from "../proto";
import { logger, field } from "@coder/logger";
import { ClientMessage, WorkingInitMessage, ServerMessage } from "../proto";
import { evaluate } from "./evaluate";
import { ReadWriteConnection } from "../common/connection";
import { Process, handleNewSession } from "./command";
import { Process, handleNewSession, handleNewConnection } from "./command";
import * as net from "net";
export interface ServerOptions {
readonly workingDirectory: string;
......@@ -13,14 +17,13 @@ export interface ServerOptions {
export class Server {
private readonly sessions: Map<number, Process>;
private readonly sessions: Map<number, Process> = new Map();
private readonly connections: Map<number, net.Socket> = new Map();
public constructor(
private readonly connection: ReadWriteConnection,
options?: ServerOptions,
) {
this.sessions = new Map();
connection.onMessage((data) => {
try {
this.handleMessage(ClientMessage.deserializeBinary(data));
......@@ -35,22 +38,43 @@ export class Server {
return;
}
const initMsg = new InitMessage();
// Ensure the data directory exists.
const mkdirP = async (path: string): Promise<void> => {
const split = path.replace(/^\/*|\/*$/g, "").split("/");
let dir = "";
while (split.length > 0) {
dir += "/" + split.shift();
try {
await promisify(mkdir)(dir);
} catch (error) {
if (error.code !== "EEXIST") {
throw error;
}
}
}
};
Promise.all([ mkdirP(path.join(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.setHomeDirectory(os.homedir());
initMsg.setTmpDirectory(os.tmpdir());
const platform = os.platform();
let operatingSystem: InitMessage.OperatingSystem;
let operatingSystem: WorkingInitMessage.OperatingSystem;
switch (platform) {
case "win32":
operatingSystem = InitMessage.OperatingSystem.WINDOWS;
operatingSystem = WorkingInitMessage.OperatingSystem.WINDOWS;
break;
case "linux":
operatingSystem = InitMessage.OperatingSystem.LINUX;
operatingSystem = WorkingInitMessage.OperatingSystem.LINUX;
break;
case "darwin":
operatingSystem = InitMessage.OperatingSystem.MAC;
operatingSystem = WorkingInitMessage.OperatingSystem.MAC;
break;
default:
throw new Error(`unrecognized platform "${platform}"`);
......@@ -66,9 +90,8 @@ export class Server {
evaluate(this.connection, message.getNewEval()!);
} else if (message.hasNewSession()) {
const session = handleNewSession(this.connection, message.getNewSession()!, () => {
this.sessions.delete(message.getNewSession()!.getId());
this.sessions.delete(message.getNewSession()!.getId());
});
this.sessions.set(message.getNewSession()!.getId(), session);
} else if (message.hasCloseSessionInput()) {
const s = this.getSession(message.getCloseSessionInput()!.getId());
......@@ -95,9 +118,30 @@ export class Server {
return;
}
s.write(new TextDecoder().decode(message.getWriteToSession()!.getData_asU8()));
} else if (message.hasNewConnection()) {
const socket = handleNewConnection(this.connection, message.getNewConnection()!, () => {
this.connections.delete(message.getNewConnection()!.getId());
});
this.connections.set(message.getNewConnection()!.getId(), socket);
} else if (message.hasConnectionOutput()) {
const c = this.getConnection(message.getConnectionOutput()!.getId());
if (!c) {
return;
}
c.write(Buffer.from(message.getConnectionOutput()!.getData_asU8()));
} else if (message.hasConnectionClose()) {
const c = this.getConnection(message.getConnectionClose()!.getId());
if (!c) {
return;
}
c.end();
}
}
private getConnection(id: number): net.Socket | undefined {
return this.connections.get(id);
}
private getSession(id: number): Process | undefined {
return this.sessions.get(id);
}
......
syntax = "proto3";
import "command.proto";
import "node.proto";
import "vscode.proto";
message ClientMessage {
oneof msg {
......@@ -10,9 +11,14 @@ message ClientMessage {
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;
// node.proto
NewEvalMessage new_eval = 6;
NewEvalMessage new_eval = 9;
SharedProcessInitMessage shared_process_init = 10;
}
}
......@@ -23,16 +29,20 @@ message ServerMessage {
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;
// node.proto
EvalFailedMessage eval_failed = 5;
EvalDoneMessage eval_done = 6;
EvalFailedMessage eval_failed = 9;
EvalDoneMessage eval_done = 10;
InitMessage init = 7;
WorkingInitMessage init = 11;
}
}
message InitMessage {
message WorkingInitMessage {
string home_directory = 1;
string tmp_directory = 2;
string data_directory = 3;
......@@ -43,4 +53,4 @@ message InitMessage {
Mac = 2;
}
OperatingSystem operating_system = 5;
}
\ No newline at end of file
}
......@@ -4,6 +4,7 @@
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;
......@@ -31,11 +32,31 @@ export class ClientMessage extends jspb.Message {
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;
hasNewEval(): boolean;
clearNewEval(): void;
getNewEval(): node_pb.NewEvalMessage | undefined;
setNewEval(value?: node_pb.NewEvalMessage): void;
hasSharedProcessInit(): boolean;
clearSharedProcessInit(): void;
getSharedProcessInit(): vscode_pb.SharedProcessInitMessage | undefined;
setSharedProcessInit(value?: vscode_pb.SharedProcessInitMessage): void;
getMsgCase(): ClientMessage.MsgCase;
serializeBinary(): Uint8Array;
toObject(includeInstance?: boolean): ClientMessage.AsObject;
......@@ -54,7 +75,11 @@ export namespace ClientMessage {
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,
newEval?: node_pb.NewEvalMessage.AsObject,
sharedProcessInit?: vscode_pb.SharedProcessInitMessage.AsObject,
}
export enum MsgCase {
......@@ -64,7 +89,11 @@ export namespace ClientMessage {
WRITE_TO_SESSION = 3,
CLOSE_SESSION_INPUT = 4,
RESIZE_SESSION_TTY = 5,
NEW_EVAL = 6,
NEW_CONNECTION = 6,
CONNECTION_OUTPUT = 7,
CONNECTION_CLOSE = 8,
NEW_EVAL = 9,
SHARED_PROCESS_INIT = 10,
}
}
......@@ -89,6 +118,26 @@ export class ServerMessage extends jspb.Message {
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;
hasEvalFailed(): boolean;
clearEvalFailed(): void;
getEvalFailed(): node_pb.EvalFailedMessage | undefined;
......@@ -101,8 +150,8 @@ export class ServerMessage extends jspb.Message {
hasInit(): boolean;
clearInit(): void;
getInit(): InitMessage | undefined;
setInit(value?: InitMessage): void;
getInit(): WorkingInitMessage | undefined;
setInit(value?: WorkingInitMessage): void;
getMsgCase(): ServerMessage.MsgCase;
serializeBinary(): Uint8Array;
......@@ -121,9 +170,13 @@ export namespace ServerMessage {
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,
evalFailed?: node_pb.EvalFailedMessage.AsObject,
evalDone?: node_pb.EvalDoneMessage.AsObject,
init?: InitMessage.AsObject,
init?: WorkingInitMessage.AsObject,
}
export enum MsgCase {
......@@ -132,13 +185,17 @@ export namespace ServerMessage {
SESSION_DONE = 2,
SESSION_OUTPUT = 3,
IDENTIFY_SESSION = 4,
EVAL_FAILED = 5,
EVAL_DONE = 6,
INIT = 7,
CONNECTION_FAILURE = 5,
CONNECTION_OUTPUT = 6,
CONNECTION_CLOSE = 7,
CONNECTION_ESTABLISHED = 8,
EVAL_FAILED = 9,
EVAL_DONE = 10,
INIT = 11,
}
}
export class InitMessage extends jspb.Message {
export class WorkingInitMessage extends jspb.Message {
getHomeDirectory(): string;
setHomeDirectory(value: string): void;
......@@ -151,26 +208,26 @@ export class InitMessage extends jspb.Message {
getWorkingDirectory(): string;
setWorkingDirectory(value: string): void;
getOperatingSystem(): InitMessage.OperatingSystem;
setOperatingSystem(value: InitMessage.OperatingSystem): void;
getOperatingSystem(): WorkingInitMessage.OperatingSystem;
setOperatingSystem(value: WorkingInitMessage.OperatingSystem): void;
serializeBinary(): Uint8Array;
toObject(includeInstance?: boolean): InitMessage.AsObject;
static toObject(includeInstance: boolean, msg: InitMessage): InitMessage.AsObject;
toObject(includeInstance?: boolean): WorkingInitMessage.AsObject;
static toObject(includeInstance: boolean, msg: WorkingInitMessage): WorkingInitMessage.AsObject;
static extensions: {[key: number]: jspb.ExtensionFieldInfo<jspb.Message>};
static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo<jspb.Message>};
static serializeBinaryToWriter(message: InitMessage, writer: jspb.BinaryWriter): void;
static deserializeBinary(bytes: Uint8Array): InitMessage;
static deserializeBinaryFromReader(message: InitMessage, reader: jspb.BinaryReader): InitMessage;
static serializeBinaryToWriter(message: WorkingInitMessage, writer: jspb.BinaryWriter): void;
static deserializeBinary(bytes: Uint8Array): WorkingInitMessage;
static deserializeBinaryFromReader(message: WorkingInitMessage, reader: jspb.BinaryReader): WorkingInitMessage;
}
export namespace InitMessage {
export namespace WorkingInitMessage {
export type AsObject = {
homeDirectory: string,
tmpDirectory: string,
dataDirectory: string,
workingDirectory: string,
operatingSystem: InitMessage.OperatingSystem,
operatingSystem: WorkingInitMessage.OperatingSystem,
}
export enum OperatingSystem {
......
......@@ -76,3 +76,32 @@ 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;
}
......@@ -286,3 +286,121 @@ export namespace TTYDimensions {
}
}
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 * from "./client_pb";
export * from "./command_pb";
export * from "./node_pb";
export * from "./vscode_pb";
syntax = "proto3";
message SharedProcessInitMessage {
uint64 window_id = 1;
string log_directory = 2;
// Maps to `"vs/platform/log/common/log".LogLevel`
uint32 log_level = 3;
}
\ No newline at end of file
// package:
// file: vscode.proto
import * as jspb from "google-protobuf";
export class SharedProcessInitMessage extends jspb.Message {
getWindowId(): number;
setWindowId(value: number): void;
getLogDirectory(): string;
setLogDirectory(value: string): void;
getLogLevel(): number;
setLogLevel(value: number): void;
serializeBinary(): Uint8Array;
toObject(includeInstance?: boolean): SharedProcessInitMessage.AsObject;
static toObject(includeInstance: boolean, msg: SharedProcessInitMessage): SharedProcessInitMessage.AsObject;
static extensions: {[key: number]: jspb.ExtensionFieldInfo<jspb.Message>};
static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo<jspb.Message>};
static serializeBinaryToWriter(message: SharedProcessInitMessage, writer: jspb.BinaryWriter): void;
static deserializeBinary(bytes: Uint8Array): SharedProcessInitMessage;
static deserializeBinaryFromReader(message: SharedProcessInitMessage, reader: jspb.BinaryReader): SharedProcessInitMessage;
}
export namespace SharedProcessInitMessage {
export type AsObject = {
windowId: number,
logDirectory: string,
logLevel: number,
}
}
此差异已折叠。
......@@ -9,10 +9,10 @@ describe("Server", () => {
});
it("should get init msg", (done) => {
client.onInitData((data) => {
client.initData.then((data) => {
expect(data.dataDirectory).toEqual(dataDirectory);
expect(data.workingDirectory).toEqual(workingDirectory);
done();
});
});
});
\ No newline at end of file
});
out
cli*
build
# This file is generated when the binary is created.
# We want to use the parent tsconfig so we can ignore it.
......
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
bin
\ No newline at end of file
此差异已折叠。
此差异已折叠。
此差异已折叠。
export const gracefulify = (): void => undefined;
......@@ -70,4 +70,4 @@ const ptyType: nodePtyType = {
};
module.exports = ptyType;
exports = ptyType;
// TODO: obtain this in a reasonable way.
export default { name: "vscode", version: "1.31.1" };
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册