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
This commit is contained in:
Asher 2019-01-18 15:46:40 -06:00 committed by Kyle Carberry
parent 05899b5edf
commit 72bf4547d4
No known key found for this signature in database
GPG Key ID: A0409BDB6B0B3EDB
80 changed files with 5183 additions and 9697 deletions

3
.gitignore vendored
View File

@ -1,3 +1,4 @@
lib/vscode
lib/vscode*
lib/VSCode*
node_modules
dist

View File

@ -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": {

View File

@ -1,11 +1,26 @@
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;
}

View File

@ -1,5 +1,5 @@
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);
});
}
/**
@ -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());

View File

@ -0,0 +1,132 @@
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();

View File

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

View File

@ -1,352 +1,388 @@
// import * as electron from "electron";
// import { EventEmitter } from "events";
// import * as fs from "fs";
// import { getFetchUrl } from "../src/coder/api";
// import { escapePath } from "../src/coder/common";
// import { wush } from "../src/coder/server";
// import { IKey, Dialog } from "./dialog";
/// <reference path="../../../../lib/vscode/src/typings/electron.d.ts" />
import { exec } from "child_process";
import { EventEmitter } from "events";
import * as fs from "fs";
import { promisify } from "util";
import { logger, field } from "@coder/logger";
import { escapePath } from "@coder/protocol";
import { IKey, Dialog as DialogBox } from "./dialog";
import { clipboard } from "./clipboard";
// (global as any).getOpenUrls = () => {
// return [];
// };
// tslint:disable-next-line no-any
(global as any).getOpenUrls = (): string[] => {
return [];
};
// const oldCreateElement = document.createElement;
if (typeof document === "undefined") {
(<any>global).document = {} as any;
}
// document.createElement = (tagName: string) => {
// const createElement = (tagName: string) => {
// return oldCreateElement.call(document, tagName);
// };
const oldCreateElement: <K extends keyof HTMLElementTagNameMap>(
tagName: K, options?: ElementCreationOptions,
) => HTMLElementTagNameMap[K] = document.createElement;
// if (tagName === "webview") {
// const view = createElement("iframe") as HTMLIFrameElement;
// view.style.border = "0px";
// const frameID = Math.random().toString();
// view.addEventListener("error", (event) => {
// console.log("Got iframe error", event.error, event.message);
// });
// window.addEventListener("message", (event) => {
// if (!event.data || !event.data.id) {
// return;
// }
// if (event.data.id !== frameID) {
// return;
// }
// const e = new CustomEvent("ipc-message");
// (e as any).channel = event.data.channel;
// (e as any).args = event.data.data;
// view.dispatchEvent(e);
// });
// view.sandbox.add("allow-same-origin", "allow-scripts", "allow-popups", "allow-forms");
// Object.defineProperty(view, "preload", {
// set: (url: string) => {
// view.onload = () => {
// view.contentDocument.body.id = frameID;
// view.contentDocument.body.parentElement.style.overflow = "hidden";
// const script = document.createElement("script");
// script.src = url;
// view.contentDocument.head.appendChild(script);
// };
// },
// });
// (view as any).getWebContents = () => undefined;
// (view as any).send = (channel: string, ...args) => {
// if (args[0] && typeof args[0] === "object" && args[0].contents) {
const newCreateElement = <K extends keyof HTMLElementTagNameMap>(tagName: K): HTMLElementTagNameMap[K] => {
const createElement = <K extends keyof HTMLElementTagNameMap>(tagName: K): HTMLElementTagNameMap[K] => {
return oldCreateElement.call(document, tagName);
};
if (tagName === "webview") {
const view = createElement("iframe") as HTMLIFrameElement;
view.style.border = "0px";
const frameID = Math.random().toString();
view.addEventListener("error", (event) => {
logger.error("iframe error", field("event", event));
});
window.addEventListener("message", (event) => {
if (!event.data || !event.data.id) {
return;
}
if (event.data.id !== frameID) {
return;
}
const e = new CustomEvent("ipc-message");
(e as any).channel = event.data.channel; // tslint:disable-line no-any
(e as any).args = event.data.data; // tslint:disable-line no-any
view.dispatchEvent(e);
});
view.sandbox.add("allow-same-origin", "allow-scripts", "allow-popups", "allow-forms");
Object.defineProperty(view, "preload", {
set: (url: string): void => {
view.onload = (): void => {
if (view.contentDocument) {
view.contentDocument.body.id = frameID;
view.contentDocument.body.parentElement!.style.overflow = "hidden";
const script = document.createElement("script");
script.src = url;
view.contentDocument.head.appendChild(script);
}
};
},
});
(view as any).getWebContents = (): void => undefined; // tslint:disable-line no-any
(view as any).send = (channel: string, ...args: any[]): void => { // tslint:disable-line no-any
if (args[0] && typeof args[0] === "object" && args[0].contents) {
// TODO
// args[0].contents = (args[0].contents as string).replace(/"(file:\/\/[^"]*)"/g, (m) => `"${getFetchUrl(m)}"`);
// args[0].contents = (args[0].contents as string).replace(/"vscode-resource:([^"]*)"/g, (m) => `"${getFetchUrl(m)}"`);
// }
// view.contentWindow.postMessage({
// channel,
// data: args,
// id: frameID,
// }, "*");
// };
// return view;
// }
}
if (view.contentWindow) {
view.contentWindow.postMessage({
channel,
data: args,
id: frameID,
}, "*");
}
};
// return createElement(tagName);
// };
return view;
}
// const rendererToMainEmitter = new EventEmitter();
// const mainToRendererEmitter = new EventEmitter();
return createElement(tagName);
};
// module.exports = {
// clipboard: {
// has: () => {
// return false;
// },
// writeText: (value: string) => {
// // Taken from https://hackernoon.com/copying-text-to-clipboard-with-javascript-df4d4988697f
// const active = document.activeElement as HTMLElement;
// const el = document.createElement('textarea'); // Create a <textarea> element
// el.value = value; // Set its value to the string that you want copied
// el.setAttribute('readonly', ''); // Make it readonly to be tamper-proof
// el.style.position = 'absolute';
// el.style.left = '-9999px'; // Move outside the screen to make it invisible
// document.body.appendChild(el); // Append the <textarea> element to the HTML document
// const selected =
// document.getSelection().rangeCount > 0 // Check if there is any content selected previously
// ? document.getSelection().getRangeAt(0) // Store selection if found
// : false; // Mark as false to know no selection existed before
// el.select(); // Select the <textarea> content
// document.execCommand('copy'); // Copy - only works as a result of a user action (e.g. click events)
// document.body.removeChild(el); // Remove the <textarea> element
// if (selected) { // If a selection existed before copying
// document.getSelection().removeAllRanges(); // Unselect everything on the HTML document
// document.getSelection().addRange(selected); // Restore the original selection
// }
// active.focus();
// },
// },
// dialog: {
// showSaveDialog: (_: void, options: Electron.SaveDialogOptions, callback: (filename: string) => void): void => {
// const defaultPath = options.defaultPath || "/untitled";
// const fileIndex = defaultPath.lastIndexOf("/");
// const extensionIndex = defaultPath.lastIndexOf(".");
// const saveDialogOptions = {
// buttons: ["Cancel", "Save"],
// detail: "Enter a path for this file",
// input: {
// value: defaultPath,
// selection: {
// start: fileIndex === -1 ? 0 : fileIndex + 1,
// end: extensionIndex === -1 ? defaultPath.length : extensionIndex,
// },
// },
// message: "Save file",
// };
document.createElement = newCreateElement;
// const dialog = new Dialog(saveDialogOptions);
// dialog.onAction((action) => {
// if (action.key !== IKey.Enter && action.buttonIndex !== 1) {
// dialog.hide();
// return callback(undefined);
// }
class Clipboard {
// const filePath = dialog.inputValue.replace(/\/+$/, "");
// const split = filePath.split("/");
// const fileName = split.pop();
// const parentName = split.pop() || "/";
// if (fileName === "") {
// dialog.error = "You must enter a file name.";
// return;
// }
public has(): boolean {
return false;
}
// fs.stat(filePath, (error, stats) => {
// if (error && error.code === "ENOENT") {
// dialog.hide();
// callback(filePath);
// } else if (error) {
// dialog.error = error.message;
// } else if (stats.isDirectory()) {
// dialog.error = `A directory named "${fileName}" already exists.`;
// } else {
// dialog.error = undefined;
public writeText(value: string): Promise<void> {
return clipboard.writeText(value);
}
// const confirmDialog = new Dialog({
// message: `A file named "${fileName}" already exists. Do you want to replace it?`,
// detail: `The file already exists in "${parentName}". Replacing it will overwrite its contents.`,
// buttons: ["Cancel", "Replace"],
// });
}
// confirmDialog.onAction((action) => {
// if (action.buttonIndex === 1) {
// confirmDialog.hide();
// return callback(filePath);
// }
class Shell {
// confirmDialog.hide();
// dialog.show();
// });
public async moveItemToTrash(path: string): Promise<void> {
await promisify(exec)(
`trash-put --trash-dir ${escapePath("~/.Trash")} ${escapePath(path)}`,
);
}
// dialog.hide();
// confirmDialog.show();
// }
// });
// });
// dialog.show();
// },
// showOpenDialog: () => {
// console.log("Trying to show the open dialog");
// },
// showMessageBox: (_: void, options: Electron.MessageBoxOptions, callback: (button: number, checked: boolean) => void): void => {
// const dialog = new Dialog(options);
// dialog.onAction((action) => {
// dialog.hide();
// callback(action.buttonIndex, false);
// });
// dialog.show();
// },
// },
// remote: {
// dialog: {
// showOpenDialog: () => {
// console.log("Trying to remotely open");
// },
// },
// },
// webFrame: {
// getZoomFactor: () => {
// return 1;
// },
// getZoomLevel: () => {
// return 1;
// },
// setZoomLevel: () => {
// return;
// },
// },
// screen: {
// getAllDisplays: () => {
// return [{
// bounds: {
// x: 1000,
// y: 1000,
// },
// }];
// },
// },
// app: {
// isAccessibilitySupportEnabled: () => {
// return false;
// },
// setAsDefaultProtocolClient: () => {
}
// },
// send: (str) => {
// console.log("APP Trying to send", str);
// //
// },
// on: () => {
// //
// },
// once: () => {
// //
// },
// },
// // ipcRenderer communicates with ipcMain
// ipcRenderer: {
// send: (str, ...args) => {
// rendererToMainEmitter.emit(str, {
// sender: module.exports.ipcMain,
// }, ...args);
// },
// on: (str, listener) => {
// mainToRendererEmitter.on(str, listener);
// },
// once: (str, listener) => {
// mainToRendererEmitter.once(str, listener);
// },
// removeListener: (str, listener) => {
// mainToRendererEmitter.removeListener(str, listener);
// },
// },
// ipcMain: {
// send: (str, ...args) => {
// mainToRendererEmitter.emit(str, {
// sender: module.exports.ipcRenderer,
// }, ...args);
// },
// on: (str, listener) => {
// rendererToMainEmitter.on(str, listener);
// },
// once: (str, listener) => {
// rendererToMainEmitter.once(str, listener);
// },
// },
// shell: {
// moveItemToTrash: async (path) => {
// const response = await wush.execute({
// command: `trash-put --trash-dir ${escapePath("~/.Trash")} ${escapePath(path)}`,
// }).done();
// return response.wasSuccessful();
// },
// },
// BrowserWindow: class {
class App extends EventEmitter {
// public webContents = {
// on: () => {
public isAccessibilitySupportEnabled(): boolean {
return false;
}
// },
// session: {
// webRequest: {
// onBeforeRequest: () => {
public setAsDefaultProtocolClient(): void {
throw new Error("not implemented");
}
// },
}
// onBeforeSendHeaders: () => {
class Dialog {
// },
public showSaveDialog(_: void, options: Electron.SaveDialogOptions, callback: (filename: string | undefined) => void): void {
const defaultPath = options.defaultPath || "/untitled";
const fileIndex = defaultPath.lastIndexOf("/");
const extensionIndex = defaultPath.lastIndexOf(".");
const saveDialogOptions = {
buttons: ["Cancel", "Save"],
detail: "Enter a path for this file",
input: {
value: defaultPath,
selection: {
start: fileIndex === -1 ? 0 : fileIndex + 1,
end: extensionIndex === -1 ? defaultPath.length : extensionIndex,
},
},
message: "Save file",
};
// onHeadersReceived: () => {
const dialog = new DialogBox(saveDialogOptions);
dialog.onAction((action) => {
if (action.key !== IKey.Enter && action.buttonIndex !== 1) {
dialog.hide();
// },
// }
// },
// removeAllListeners: () => {
return callback(undefined);
}
// },
// }
const inputValue = dialog.inputValue || "";
const filePath = inputValue.replace(/\/+$/, "");
const split = filePath.split("/");
const fileName = split.pop();
const parentName = split.pop() || "/";
if (fileName === "") {
dialog.error = "You must enter a file name.";
// public static getFocusedWindow() {
// return undefined;
// }
return;
}
// public isMaximized() {
// return false;
// }
fs.stat(filePath, (error, stats) => {
if (error && error.code === "ENOENT") {
dialog.hide();
callback(filePath);
} else if (error) {
dialog.error = error.message;
} else if (stats.isDirectory()) {
dialog.error = `A directory named "${fileName}" already exists.`;
} else {
dialog.error = undefined;
// public isFullScreen() {
// return false;
// }
const confirmDialog = new DialogBox({
message: `A file named "${fileName}" already exists. Do you want to replace it?`,
detail: `The file already exists in "${parentName}". Replacing it will overwrite its contents.`,
buttons: ["Cancel", "Replace"],
});
// public setMenuBarVisibility(visibility) {
// console.log("We are setting the menu bar to ", visibility);
// }
confirmDialog.onAction((action) => {
if (action.buttonIndex === 1) {
confirmDialog.hide();
// public setAutoHideMenuBar() {
return callback(filePath);
}
// }
confirmDialog.hide();
dialog.show();
});
// public on() {
dialog.hide();
confirmDialog.show();
}
});
});
dialog.show();
}
// }
public showOpenDialog(): void {
throw new Error("not implemented");
}
// public setTitle(value: string): void {
// document.title = value;
// }
// },
// toggleFullScreen: () => {
// const doc = document as any;
// const isInFullScreen = doc.fullscreenElement
// || doc.webkitFullscreenElement
// || doc.mozFullScreenElement
// || doc.msFullscreenElement;
public showMessageBox(_: void, options: Electron.MessageBoxOptions, callback: (button: number | undefined, checked: boolean) => void): void {
const dialog = new DialogBox(options);
dialog.onAction((action) => {
dialog.hide();
callback(action.buttonIndex, false);
});
dialog.show();
}
// const body = doc.body;
// if (!isInFullScreen) {
// if (body.requestFullscreen) {
// body.requestFullscreen();
// } else if (body.mozRequestFullScreen) {
// body.mozRequestFullScreen();
// } else if (body.webkitRequestFullScreen) {
// body.webkitRequestFullScreen();
// } else if (body.msRequestFullscreen) {
// body.msRequestFullscreen();
// }
// } else {
// if (doc.exitFullscreen) {
// doc.exitFullscreen();
// } else if (doc.webkitExitFullscreen) {
// doc.webkitExitFullscreen();
// } else if (doc.mozCancelFullScreen) {
// doc.mozCancelFullScreen();
// } else if (doc.msExitFullscreen) {
// doc.msExitFullscreen();
// }
// }
// },
// focusWindow: () => {
// console.log("focusing window");
// window.focus();
// },
// };
}
class WebFrame {
public getZoomFactor(): number {
return 1;
}
public getZoomLevel(): number {
return 1;
}
public setZoomLevel(): void {
// Nothing.
}
}
class Screen {
public getAllDisplays(): [] {
return [];
}
}
class WebRequest extends EventEmitter {
public onBeforeRequest(): void {
throw new Error("not implemented");
}
public onBeforeSendHeaders(): void {
throw new Error("not implemented");
}
public onHeadersReceived(): void {
throw new Error("not implemented");
}
}
class Session extends EventEmitter {
public webRequest = new WebRequest();
public resolveProxy(url: string, callback: (proxy: string) => void): void {
// TODO: not sure what this actually does.
callback(url);
}
}
class WebContents extends EventEmitter {
public session = new Session();
}
class BrowserWindow extends EventEmitter {
public webContents = new WebContents();
private representedFilename: string = "";
public static getFocusedWindow(): undefined {
return undefined;
}
public focus(): void {
window.focus();
}
public show(): void {
window.focus();
}
public reload(): void {
location.reload();
}
public isMaximized(): boolean {
return false;
}
public setFullScreen(fullscreen: boolean): void {
if (fullscreen) {
document.documentElement.requestFullscreen();
} else {
document.exitFullscreen();
}
}
public isFullScreen(): boolean {
return document.fullscreenEnabled;
}
public isFocused(): boolean {
return document.hasFocus();
}
public setMenuBarVisibility(): void {
throw new Error("not implemented");
}
public setAutoHideMenuBar(): void {
throw new Error("not implemented");
}
public setRepresentedFilename(filename: string): void {
this.representedFilename = filename;
}
public getRepresentedFilename(): string {
return this.representedFilename;
}
public setTitle(value: string): void {
document.title = value;
}
}
/**
* We won't be able to do a 1 to 1 fill because things like moveItemToTrash for
* example returns a boolean while we need a promise.
*/
class ElectronFill {
public readonly shell = new Shell();
public readonly clipboard = new Clipboard();
public readonly app = new App();
public readonly dialog = new Dialog();
public readonly webFrame = new WebFrame();
public readonly screen = new Screen();
private readonly rendererToMainEmitter = new EventEmitter();
private readonly mainToRendererEmitter = new EventEmitter();
public get BrowserWindow(): typeof BrowserWindow {
return BrowserWindow;
}
// tslint:disable no-any
public get ipcRenderer(): object {
return {
send: (str: string, ...args: any[]): void => {
this.rendererToMainEmitter.emit(str, {
sender: module.exports.ipcMain,
}, ...args);
},
on: (str: string, listener: (...args: any[]) => void): void => {
this.mainToRendererEmitter.on(str, listener);
},
once: (str: string, listener: (...args: any[]) => void): void => {
this.mainToRendererEmitter.once(str, listener);
},
removeListener: (str: string, listener: (...args: any[]) => void): void => {
this.mainToRendererEmitter.removeListener(str, listener);
},
};
}
public get ipcMain(): object {
return {
send: (str: string, ...args: any[]): void => {
this.mainToRendererEmitter.emit(str, {
sender: module.exports.ipcRenderer,
}, ...args);
},
on: (str: string, listener: (...args: any[]) => void): void => {
this.rendererToMainEmitter.on(str, listener);
},
once: (str: string, listener: (...args: any[]) => void): void => {
this.rendererToMainEmitter.once(str, listener);
},
};
}
// tslint:enable no-any
}
module.exports = new ElectronFill();

View File

@ -1 +1 @@
module.exports = {};
export = {};

View File

@ -0,0 +1,38 @@
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();

View File

@ -1,5 +1,4 @@
import { implementation as promisify } from "util.promisify";
export * from "../../../../node_modules/util";
import { implementation } from "util.promisify";
export {
promisify,
}
export const promisify = implementation;

View File

@ -1,4 +1,2 @@
export * from "./client";
export * from "./retry";
export * from "./upload";
export * from "./uri";

View File

@ -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();

View File

@ -1,45 +0,0 @@
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;
}

View File

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

View File

@ -1,5 +1,6 @@
{
"scripts": {
"postinstall": "../node_modules/.bin/ts-node ../scripts/install-packages.ts",
"test": "jest"
},
"devDependencies": {

View File

@ -1,8 +1,8 @@
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));
}
});
this.initDataPromise = new Promise((resolve): void => {
this.initDataEmitter.event(resolve);
});
}
public get onInitData(): Event<InitData> {
return this.initDataEmitter.event;
}
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>;
@ -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());
}
}
}

View File

@ -1,7 +1,7 @@
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;
@ -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.");
}
}

View File

@ -1,6 +1,6 @@
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);

View File

@ -1,9 +1,20 @@
import { exec, ChildProcess } from "child_process";
import { EventEmitter } from "events";
import * as fs from "fs";
import { IEncodingOptions, IEncodingOptionsCallback, escapePath, useBuffer } from "../../common/util";
import { Client } from "../client";
// Use this to get around Webpack inserting our fills.
// TODO: is there a better way?
declare var _require: typeof require;
/**
* Implements the native fs module
* Doesn't use `implements typeof import("fs")` to remove need for __promisify__ impls
*
* TODO: For now we can't use async in the evaluate calls because they get
* transpiled to TypeScript's helpers. tslib is included but we also need to set
* _this somehow which the __awaiter helper uses.
*/
export class FS {
@ -11,34 +22,46 @@ export class FS {
private readonly client: Client,
) { }
public access(path: fs.PathLike, mode: number | undefined, callback: (err: NodeJS.ErrnoException) => void): void {
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;
}
this.client.evaluate((path, mode) => {
const fs = require("fs") as typeof import("fs");
const util = require("util") as typeof import("util");
const fs = _require("fs") as typeof import("fs");
const util = _require("util") as typeof import("util");
return util.promisify(fs.access)(path, mode);
}, path, mode).then(() => {
callback(undefined!);
callback!(undefined!);
}).catch((ex) => {
callback(ex);
callback!(ex);
});
}
public appendFile(file: fs.PathLike | number, data: any, options: { encoding?: string | null, mode?: string | number, flag?: string } | string | undefined | null, callback: (err: NodeJS.ErrnoException) => void): void {
// tslint:disable-next-line no-any
public appendFile = (file: fs.PathLike | number, data: any, options: IEncodingOptionsCallback, callback?: (err: NodeJS.ErrnoException) => void): void => {
if (typeof options === "function") {
callback = options;
options = undefined;
}
this.client.evaluate((path, data, options) => {
const fs = require("fs") as typeof import("fs");
const util = require("util") as typeof import("util");
const fs = _require("fs") as typeof import("fs");
const util = _require("util") as typeof import("util");
return util.promisify(fs.appendFile)(path, data, options);
}, file, data, options).then(() => {
callback(undefined!);
callback!(undefined!);
}).catch((ex) => {
callback(ex);
callback!(ex);
});
}
public chmod(path: fs.PathLike, mode: string | number, callback: (err: NodeJS.ErrnoException) => void): void {
public chmod = (path: fs.PathLike, mode: string | number, callback: (err: NodeJS.ErrnoException) => void): void => {
this.client.evaluate((path, mode) => {
const fs = require("fs") as typeof import("fs");
const util = require("util") as typeof import("util");
const fs = _require("fs") as typeof import("fs");
const util = _require("util") as typeof import("util");
return util.promisify(fs.chmod)(path, mode);
}, path, mode).then(() => {
callback(undefined!);
@ -47,10 +70,11 @@ export class FS {
});
}
public chown(path: fs.PathLike, uid: number, gid: number, callback: (err: NodeJS.ErrnoException) => void): void {
public chown = (path: fs.PathLike, uid: number, gid: number, callback: (err: NodeJS.ErrnoException) => void): void => {
this.client.evaluate((path, uid, gid) => {
const fs = require("fs") as typeof import("fs");
const util = require("util") as typeof import("util");
const fs = _require("fs") as typeof import("fs");
const util = _require("util") as typeof import("util");
return util.promisify(fs.chown)(path, uid, gid);
}, path, uid, gid).then(() => {
callback(undefined!);
@ -59,10 +83,11 @@ export class FS {
});
}
public close(fd: number, callback: (err: NodeJS.ErrnoException) => void): void {
public close = (fd: number, callback: (err: NodeJS.ErrnoException) => void): void => {
this.client.evaluate((fd) => {
const fs = require("fs") as typeof import("fs");
const util = require("util") as typeof import("util");
const fs = _require("fs") as typeof import("fs");
const util = _require("util") as typeof import("util");
return util.promisify(fs.close)(fd);
}, fd).then(() => {
callback(undefined!);
@ -71,26 +96,31 @@ export class FS {
});
}
public copyFile(src: fs.PathLike, dest: fs.PathLike, callback: (err: NodeJS.ErrnoException) => void): void {
this.client.evaluate((src, dest) => {
const fs = require("fs") as typeof import("fs");
const util = require("util") as typeof import("util");
return util.promisify(fs.copyFile)(src, dest);
}, src, dest).then(() => {
callback(undefined!);
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;
}
this.client.evaluate((src, dest, flags) => {
const fs = _require("fs") as typeof import("fs");
const util = _require("util") as typeof import("util");
return util.promisify(fs.copyFile)(src, dest, flags);
}, src, dest, typeof flags !== "function" ? flags : undefined).then(() => {
callback!(undefined!);
}).catch((ex) => {
callback(ex);
callback!(ex);
});
}
public createWriteStream(): void {
public createWriteStream = (): void => {
throw new Error("not implemented");
}
public exists(path: fs.PathLike, callback: (exists: boolean) => void): void {
public exists = (path: fs.PathLike, callback: (exists: boolean) => void): void => {
this.client.evaluate((path) => {
const fs = require("fs") as typeof import("fs");
const util = require("util") as typeof import("util");
const fs = _require("fs") as typeof import("fs");
const util = _require("util") as typeof import("util");
return util.promisify(fs.exists)(path);
}, path).then((r) => {
callback(r);
@ -99,10 +129,11 @@ export class FS {
});
}
public fchmod(fd: number, mode: string | number, callback: (err: NodeJS.ErrnoException) => void): void {
public fchmod = (fd: number, mode: string | number, callback: (err: NodeJS.ErrnoException) => void): void => {
this.client.evaluate((fd, mode) => {
const fs = require("fs") as typeof import("fs");
const util = require("util") as typeof import("util");
const fs = _require("fs") as typeof import("fs");
const util = _require("util") as typeof import("util");
return util.promisify(fs.fchmod)(fd, mode);
}, fd, mode).then(() => {
callback(undefined!);
@ -111,10 +142,11 @@ export class FS {
});
}
public fchown(fd: number, uid: number, gid: number, callback: (err: NodeJS.ErrnoException) => void): void {
public fchown = (fd: number, uid: number, gid: number, callback: (err: NodeJS.ErrnoException) => void): void => {
this.client.evaluate((fd, uid, gid) => {
const fs = require("fs") as typeof import("fs");
const util = require("util") as typeof import("util");
const fs = _require("fs") as typeof import("fs");
const util = _require("util") as typeof import("util");
return util.promisify(fs.fchown)(fd, uid, gid);
}, fd, uid, gid).then(() => {
callback(undefined!);
@ -123,10 +155,11 @@ export class FS {
});
}
public fdatasync(fd: number, callback: (err: NodeJS.ErrnoException) => void): void {
public fdatasync = (fd: number, callback: (err: NodeJS.ErrnoException) => void): void => {
this.client.evaluate((fd) => {
const fs = require("fs") as typeof import("fs");
const util = require("util") as typeof import("util");
const fs = _require("fs") as typeof import("fs");
const util = _require("util") as typeof import("util");
return util.promisify(fs.fdatasync)(fd);
}, fd).then(() => {
callback(undefined!);
@ -135,11 +168,12 @@ export class FS {
});
}
public fstat(fd: number, callback: (err: NodeJS.ErrnoException, stats: fs.Stats) => void): void {
this.client.evaluate(async (fd) => {
const fs = require("fs") as typeof import("fs");
const util = require("util") as typeof import("util");
const stats = await util.promisify(fs.fstat)(fd);
public fstat = (fd: number, callback: (err: NodeJS.ErrnoException, stats: fs.Stats) => void): void => {
this.client.evaluate((fd) => {
const fs = _require("fs") as typeof import("fs");
const util = _require("util") as typeof import("util");
return util.promisify(fs.fstat)(fd).then((stats) => {
return {
...stats,
_isBlockDevice: stats.isBlockDevice(),
@ -150,6 +184,7 @@ export class FS {
_isSocket: stats.isSocket(),
_isSymbolicLink: stats.isSymbolicLink(),
};
});
}, fd).then((stats) => {
callback(undefined!, Stats.fromObject(stats));
}).catch((ex) => {
@ -157,10 +192,11 @@ export class FS {
});
}
public fsync(fd: number, callback: (err: NodeJS.ErrnoException) => void): void {
public fsync = (fd: number, callback: (err: NodeJS.ErrnoException) => void): void => {
this.client.evaluate((fd) => {
const fs = require("fs") as typeof import("fs");
const util = require("util") as typeof import("util");
const fs = _require("fs") as typeof import("fs");
const util = _require("util") as typeof import("util");
return util.promisify(fs.fsync)(fd);
}, fd).then(() => {
callback(undefined!);
@ -169,22 +205,28 @@ export class FS {
});
}
public ftruncate(fd: number, len: number | undefined | null, callback: (err: NodeJS.ErrnoException) => void): void {
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;
}
this.client.evaluate((fd, len) => {
const fs = require("fs") as typeof import("fs");
const util = require("util") as typeof import("util");
const fs = _require("fs") as typeof import("fs");
const util = _require("util") as typeof import("util");
return util.promisify(fs.ftruncate)(fd, len);
}, fd, len).then(() => {
callback(undefined!);
callback!(undefined!);
}).catch((ex) => {
callback(ex);
callback!(ex);
});
}
public futimes(fd: number, atime: string | number | Date, mtime: string | number | Date, callback: (err: NodeJS.ErrnoException) => void): void {
public futimes = (fd: number, atime: string | number | Date, mtime: string | number | Date, callback: (err: NodeJS.ErrnoException) => void): void => {
this.client.evaluate((fd, atime, mtime) => {
const fs = require("fs") as typeof import("fs");
const util = require("util") as typeof import("util");
const fs = _require("fs") as typeof import("fs");
const util = _require("util") as typeof import("util");
return util.promisify(fs.futimes)(fd, atime, mtime);
}, fd, atime, mtime).then(() => {
callback(undefined!);
@ -193,10 +235,11 @@ export class FS {
});
}
public lchmod(path: fs.PathLike, mode: string | number, callback: (err: NodeJS.ErrnoException) => void): void {
public lchmod = (path: fs.PathLike, mode: string | number, callback: (err: NodeJS.ErrnoException) => void): void => {
this.client.evaluate((path, mode) => {
const fs = require("fs") as typeof import("fs");
const util = require("util") as typeof import("util");
const fs = _require("fs") as typeof import("fs");
const util = _require("util") as typeof import("util");
return util.promisify(fs.lchmod)(path, mode);
}, path, mode).then(() => {
callback(undefined!);
@ -205,10 +248,11 @@ export class FS {
});
}
public lchown(path: fs.PathLike, uid: number, gid: number, callback: (err: NodeJS.ErrnoException) => void): void {
public lchown = (path: fs.PathLike, uid: number, gid: number, callback: (err: NodeJS.ErrnoException) => void): void => {
this.client.evaluate((path, uid, gid) => {
const fs = require("fs") as typeof import("fs");
const util = require("util") as typeof import("util");
const fs = _require("fs") as typeof import("fs");
const util = _require("util") as typeof import("util");
return util.promisify(fs.lchown)(path, uid, gid);
}, path, uid, gid).then(() => {
callback(undefined!);
@ -217,10 +261,11 @@ export class FS {
});
}
public link(existingPath: fs.PathLike, newPath: fs.PathLike, callback: (err: NodeJS.ErrnoException) => void): void {
public link = (existingPath: fs.PathLike, newPath: fs.PathLike, callback: (err: NodeJS.ErrnoException) => void): void => {
this.client.evaluate((existingPath, newPath) => {
const fs = require("fs") as typeof import("fs");
const util = require("util") as typeof import("util");
const fs = _require("fs") as typeof import("fs");
const util = _require("util") as typeof import("util");
return util.promisify(fs.link)(existingPath, newPath);
}, existingPath, newPath).then(() => {
callback(undefined!);
@ -229,11 +274,12 @@ export class FS {
});
}
public lstat(path: fs.PathLike, callback: (err: NodeJS.ErrnoException, stats: fs.Stats) => void): void {
this.client.evaluate(async (path) => {
const fs = require("fs") as typeof import("fs");
const util = require("util") as typeof import("util");
const stats = await util.promisify(fs.lstat)(path);
public lstat = (path: fs.PathLike, callback: (err: NodeJS.ErrnoException, stats: fs.Stats) => void): void => {
this.client.evaluate((path) => {
const fs = _require("fs") as typeof import("fs");
const util = _require("util") as typeof import("util");
return util.promisify(fs.lstat)(path).then((stats) => {
return {
...stats,
_isBlockDevice: stats.isBlockDevice(),
@ -244,6 +290,7 @@ export class FS {
_isSocket: stats.isSocket(),
_isSymbolicLink: stats.isSymbolicLink(),
};
});
}, path).then((stats) => {
callback(undefined!, Stats.fromObject(stats));
}).catch((ex) => {
@ -251,54 +298,70 @@ export class FS {
});
}
public mkdir(path: fs.PathLike, mode: number | string | undefined | null, callback: (err: NodeJS.ErrnoException) => void): void {
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;
}
this.client.evaluate((path, mode) => {
const fs = require("fs") as typeof import("fs");
const util = require("util") as typeof import("util");
const fs = _require("fs") as typeof import("fs");
const util = _require("util") as typeof import("util");
return util.promisify(fs.mkdir)(path, mode);
}, path, mode).then(() => {
callback(undefined!);
callback!(undefined!);
}).catch((ex) => {
callback(ex);
callback!(ex);
});
}
public mkdtemp(prefix: string, options: { encoding?: BufferEncoding | null } | BufferEncoding | undefined | null, callback: (err: NodeJS.ErrnoException, folder: string) => void): void {
public mkdtemp = (prefix: string, options: IEncodingOptionsCallback, callback?: (err: NodeJS.ErrnoException, folder: string | Buffer) => void): void => {
if (typeof options === "function") {
callback = options;
options = undefined;
}
this.client.evaluate((prefix, options) => {
const fs = require("fs") as typeof import("fs");
const util = require("util") as typeof import("util");
const fs = _require("fs") as typeof import("fs");
const util = _require("util") as typeof import("util");
return util.promisify(fs.mkdtemp)(prefix, options);
}, prefix, options).then((folder) => {
callback(undefined!, folder);
callback!(undefined!, folder);
}).catch((ex) => {
callback(ex, undefined!);
callback!(ex, undefined!);
});
}
public open(path: fs.PathLike, flags: string | number, mode: string | number | undefined | null, callback: (err: NodeJS.ErrnoException, fd: number) => void): void {
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;
}
this.client.evaluate((path, flags, mode) => {
const fs = require("fs") as typeof import("fs");
const util = require("util") as typeof import("util");
const fs = _require("fs") as typeof import("fs");
const util = _require("util") as typeof import("util");
return util.promisify(fs.open)(path, flags, mode);
}, path, flags, mode).then((fd) => {
callback(undefined!, fd);
callback!(undefined!, fd);
}).catch((ex) => {
callback(ex, undefined!);
callback!(ex, undefined!);
});
}
public read<TBuffer extends Buffer | Uint8Array>(fd: number, buffer: TBuffer, offset: number, length: number, position: number | null, callback: (err: NodeJS.ErrnoException, bytesRead: number, buffer: TBuffer) => void): void {
this.client.evaluate(async (fd, bufferLength, length, position) => {
const fs = require("fs") as typeof import("fs");
const util = require("util") as typeof import("util");
public read = <TBuffer extends Buffer | Uint8Array>(fd: number, buffer: TBuffer, offset: number, length: number, position: number | null, callback: (err: NodeJS.ErrnoException, bytesRead: number, buffer: TBuffer) => void): void => {
this.client.evaluate((fd, length, position) => {
const fs = _require("fs") as typeof import("fs");
const util = _require("util") as typeof import("util");
const buffer = new Buffer(length);
const resp = await util.promisify(fs.read)(fd, buffer, 0, length, position);
return util.promisify(fs.read)(fd, buffer, 0, length, position).then((resp) => {
return {
bytesRead: resp.bytesRead,
content: buffer.toString("utf8"),
};
}, fd, buffer.byteLength, length, position).then((resp) => {
}):
}, fd, length, position).then((resp) => {
const newBuf = Buffer.from(resp.content, "utf8");
buffer.set(newBuf, offset);
callback(undefined!, resp.bytesRead, newBuf as TBuffer);
@ -307,60 +370,80 @@ export class FS {
});
}
public readFile(path: fs.PathLike | number, options: string | { encoding?: string | null | undefined; flag?: string | undefined; } | null | undefined, callback: (err: NodeJS.ErrnoException, data: string | Buffer) => void): void {
this.client.evaluate(async (path, options) => {
const fs = require("fs") as typeof import("fs");
const util = require("util") as typeof import("util");
const value = await util.promisify(fs.readFile)(path, options);
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;
}
this.client.evaluate((path, options) => {
const fs = _require("fs") as typeof import("fs");
const util = _require("util") as typeof import("util");
return value.toString();
return util.promisify(fs.readFile)(path, options).then((value) => value.toString());
}, path, options).then((buffer) => {
callback(undefined!, buffer);
callback!(undefined!, buffer);
}).catch((ex) => {
callback(ex, undefined!);
callback!(ex, undefined!);
});
}
public readdir(path: fs.PathLike, options: { encoding: BufferEncoding | null } | BufferEncoding | undefined | null, callback: (err: NodeJS.ErrnoException, files: string[]) => void): void {
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;
}
// TODO: options can also take `withFileTypes` but the types aren't working.
this.client.evaluate((path, options) => {
const fs = require("fs") as typeof import("fs");
const util = require("util") as typeof import("util");
const fs = _require("fs") as typeof import("fs");
const util = _require("util") as typeof import("util");
return util.promisify(fs.readdir)(path, options);
}, path, options).then((files) => {
callback(undefined!, files);
callback!(undefined!, files);
}).catch((ex) => {
callback(ex, undefined!);
callback!(ex, undefined!);
});
}
public readlink(path: fs.PathLike, options: { encoding?: BufferEncoding | null } | BufferEncoding | undefined | null, callback: (err: NodeJS.ErrnoException, linkString: string) => void): void {
public readlink = (path: fs.PathLike, options: IEncodingOptionsCallback, callback?: (err: NodeJS.ErrnoException, linkString: string | Buffer) => void): void => {
if (typeof options === "function") {
callback = options;
options = undefined;
}
this.client.evaluate((path, options) => {
const fs = require("fs") as typeof import("fs");
const util = require("util") as typeof import("util");
const fs = _require("fs") as typeof import("fs");
const util = _require("util") as typeof import("util");
return util.promisify(fs.readlink)(path, options);
}, path, options).then((linkString) => {
callback(undefined!, linkString);
callback!(undefined!, linkString);
}).catch((ex) => {
callback(ex, undefined!);
callback!(ex, undefined!);
});
}
public realpath(path: fs.PathLike, options: { encoding?: BufferEncoding | null } | BufferEncoding | undefined | null, callback: (err: NodeJS.ErrnoException, resolvedPath: string) => void): void {
public realpath = (path: fs.PathLike, options: IEncodingOptionsCallback, callback?: (err: NodeJS.ErrnoException, resolvedPath: string | Buffer) => void): void => {
if (typeof options === "function") {
callback = options;
options = undefined;
}
this.client.evaluate((path, options) => {
const fs = require("fs") as typeof import("fs");
const util = require("util") as typeof import("util");
const fs = _require("fs") as typeof import("fs");
const util = _require("util") as typeof import("util");
return util.promisify(fs.realpath)(path, options);
}, path, options).then((resolvedPath) => {
callback(undefined!, resolvedPath);
callback!(undefined!, resolvedPath);
}).catch((ex) => {
callback(ex, undefined!);
callback!(ex, undefined!);
});
}
public rename(oldPath: fs.PathLike, newPath: fs.PathLike, callback: (err: NodeJS.ErrnoException) => void): void {
public rename = (oldPath: fs.PathLike, newPath: fs.PathLike, callback: (err: NodeJS.ErrnoException) => void): void => {
this.client.evaluate((oldPath, newPath) => {
const fs = require("fs") as typeof import("fs");
const util = require("util") as typeof import("util");
const fs = _require("fs") as typeof import("fs");
const util = _require("util") as typeof import("util");
return util.promisify(fs.rename)(oldPath, newPath);
}, oldPath, newPath).then(() => {
callback(undefined!);
@ -369,10 +452,11 @@ export class FS {
});
}
public rmdir(path: fs.PathLike, callback: (err: NodeJS.ErrnoException) => void): void {
public rmdir = (path: fs.PathLike, callback: (err: NodeJS.ErrnoException) => void): void => {
this.client.evaluate((path) => {
const fs = require("fs") as typeof import("fs");
const util = require("util") as typeof import("util");
const fs = _require("fs") as typeof import("fs");
const util = _require("util") as typeof import("util");
return util.promisify(fs.rmdir)(path);
}, path).then(() => {
callback(undefined!);
@ -381,11 +465,12 @@ export class FS {
});
}
public stat(path: fs.PathLike, callback: (err: NodeJS.ErrnoException, stats: fs.Stats) => void): void {
this.client.evaluate(async (path) => {
const fs = require("fs") as typeof import("fs");
const util = require("util") as typeof import("util");
const stats = await util.promisify(fs.stat)(path);
public stat = (path: fs.PathLike, callback: (err: NodeJS.ErrnoException, stats: fs.Stats) => void): void => {
this.client.evaluate((path) => {
const fs = _require("fs") as typeof import("fs");
const util = _require("util") as typeof import("util");
return util.promisify(fs.stat)(path).then((stats) => {
return {
...stats,
_isBlockDevice: stats.isBlockDevice(),
@ -396,6 +481,7 @@ export class FS {
_isSocket: stats.isSocket(),
_isSymbolicLink: stats.isSymbolicLink(),
};
});
}, path).then((stats) => {
callback(undefined!, Stats.fromObject(stats));
}).catch((ex) => {
@ -403,34 +489,45 @@ export class FS {
});
}
public symlink(target: fs.PathLike, path: fs.PathLike, type: fs.symlink.Type | undefined | null, callback: (err: NodeJS.ErrnoException) => void): void {
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;
}
this.client.evaluate((target, path, type) => {
const fs = require("fs") as typeof import("fs");
const util = require("util") as typeof import("util");
const fs = _require("fs") as typeof import("fs");
const util = _require("util") as typeof import("util");
return util.promisify(fs.symlink)(target, path, type);
}, target, path, type).then(() => {
callback(undefined!);
callback!(undefined!);
}).catch((ex) => {
callback(ex);
callback!(ex);
});
}
public truncate(path: fs.PathLike, len: number | undefined | null, callback: (err: NodeJS.ErrnoException) => void): void {
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;
}
this.client.evaluate((path, len) => {
const fs = require("fs") as typeof import("fs");
const util = require("util") as typeof import("util");
const fs = _require("fs") as typeof import("fs");
const util = _require("util") as typeof import("util");
return util.promisify(fs.truncate)(path, len);
}, path, len).then(() => {
callback(undefined!);
callback!(undefined!);
}).catch((ex) => {
callback(ex);
callback!(ex);
});
}
public unlink(path: fs.PathLike, callback: (err: NodeJS.ErrnoException) => void): void {
public unlink = (path: fs.PathLike, callback: (err: NodeJS.ErrnoException) => void): void => {
this.client.evaluate((path) => {
const fs = require("fs") as typeof import("fs");
const util = require("util") as typeof import("util");
const fs = _require("fs") as typeof import("fs");
const util = _require("util") as typeof import("util");
return util.promisify(fs.unlink)(path);
}, path).then(() => {
callback(undefined!);
@ -439,10 +536,11 @@ export class FS {
});
}
public utimes(path: fs.PathLike, atime: string | number | Date, mtime: string | number | Date, callback: (err: NodeJS.ErrnoException) => void): void {
public utimes = (path: fs.PathLike, atime: string | number | Date, mtime: string | number | Date, callback: (err: NodeJS.ErrnoException) => void): void => {
this.client.evaluate((path, atime, mtime) => {
const fs = require("fs") as typeof import("fs");
const util = require("util") as typeof import("util");
const fs = _require("fs") as typeof import("fs");
const util = _require("util") as typeof import("util");
return util.promisify(fs.utimes)(path, atime, mtime);
}, path, atime, mtime).then(() => {
callback(undefined!);
@ -451,38 +549,112 @@ export class FS {
});
}
public write<TBuffer extends Buffer | Uint8Array>(fd: number, buffer: TBuffer, offset: number | undefined, length: number | undefined, position: number | undefined, callback: (err: NodeJS.ErrnoException, written: number, buffer: TBuffer) => void): void {
this.client.evaluate(async (fd, buffer, offset, length, position) => {
const fs = require("fs") as typeof import("fs");
const util = require("util") as typeof import("util");
const resp = await util.promisify(fs.write)(fd, Buffer.from(buffer, "utf8"), offset, length, position);
public write = <TBuffer extends Buffer | Uint8Array>(fd: number, buffer: TBuffer, offset: number | undefined, length: number | undefined, position: number | undefined | ((err: NodeJS.ErrnoException, written: number, buffer: TBuffer) => void), callback?: (err: NodeJS.ErrnoException, written: number, buffer: TBuffer) => void): void => {
if (typeof position === "function") {
callback = position;
position = undefined;
}
this.client.evaluate((fd, buffer, offset, length, position) => {
const fs = _require("fs") as typeof import("fs");
const util = _require("util") as typeof import("util");
return util.promisify(fs.write)(fd, Buffer.from(buffer, "utf8"), offset, length, position).then((resp) => {
return {
bytesWritten: resp.bytesWritten,
content: resp.buffer.toString("utf8"),
}
};
});
}, fd, buffer.toString(), offset, length, position).then((r) => {
callback(undefined!, r.bytesWritten, Buffer.from(r.content, "utf8") as TBuffer);
callback!(undefined!, r.bytesWritten, Buffer.from(r.content, "utf8") as TBuffer);
}).catch((ex) => {
callback(ex, undefined!, undefined!);
callback!(ex, undefined!, undefined!);
});
}
public writeFile(path: fs.PathLike | number, data: any, options: { encoding?: string | null; mode?: number | string; flag?: string; } | string | undefined | null, callback: (err: NodeJS.ErrnoException) => void): void {
// tslint:disable-next-line no-any
public writeFile = (path: fs.PathLike | number, data: any, options: IEncodingOptionsCallback, callback?: (err: NodeJS.ErrnoException) => void): void => {
if (typeof options === "function") {
callback = options;
options = undefined;
}
this.client.evaluate((path, data, options) => {
const fs = require("fs") as typeof import("fs");
const util = require("util") as typeof import("util");
const fs = _require("fs") as typeof import("fs");
const util = _require("util") as typeof import("util");
return util.promisify(fs.writeFile)(path, data, options);
}, path, data, options).then(() => {
callback(undefined!);
callback!(undefined!);
}).catch((ex) => {
callback(ex);
callback!(ex);
});
}
public watch = (filename: fs.PathLike, options: IEncodingOptions, listener?: ((event: string, filename: string) => void) | ((event: string, filename: Buffer) => void)): fs.FSWatcher => {
// TODO: can we modify `evaluate` for long-running processes like watch?
// Especially since inotifywait might not be available.
const buffer = new NewlineInputBuffer((msg): void => {
msg = msg.trim();
const index = msg.lastIndexOf(":");
const events = msg.substring(index + 1).split(",");
const baseFilename = msg.substring(0, index).split("/").pop();
events.forEach((event) => {
switch (event) {
// Rename is emitted when a file appears or disappears in the directory.
case "CREATE":
case "DELETE":
case "MOVED_FROM":
case "MOVED_TO":
watcher.emit("rename", baseFilename);
break;
case "CLOSE_WRITE":
watcher.emit("change", baseFilename);
break;
}
});
});
// TODO: `exec` is undefined for some reason.
const process = exec(`inotifywait ${escapePath(filename.toString())} -m --format "%w%f:%e"`);
process.on("exit", (exitCode) => {
watcher.emit("error", new Error(`process terminated unexpectedly with code ${exitCode}`));
});
process.stdout.on("data", (data) => {
buffer.push(data);
});
const watcher = new Watcher(process);
if (listener) {
const l = listener;
watcher.on("change", (filename) => {
// @ts-ignore not sure how to make this work.
l("change", useBuffer(options) ? Buffer.from(filename) : filename);
});
watcher.on("rename", (filename) => {
// @ts-ignore not sure how to make this work.
l("rename", useBuffer(options) ? Buffer.from(filename) : filename);
});
}
return watcher;
}
}
class Watcher extends EventEmitter implements fs.FSWatcher {
public constructor(private readonly process: ChildProcess) {
super();
}
public close(): void {
this.process.kill();
}
}
class Stats implements fs.Stats {
// tslint:disable-next-line no-any
public static fromObject(object: any): Stats {
return new Stats(object);
}
@ -575,3 +747,39 @@ class Stats implements fs.Stats {
}
}
/**
* Class for safely taking input and turning it into separate messages.
* Assumes that messages are split by newlines.
*/
export class NewlineInputBuffer {
private callback: (msg: string) => void;
private buffer: string | undefined;
public constructor(callback: (msg: string) => void) {
this.callback = callback;
}
/**
* Add data to be buffered.
*/
public push(data: string | Uint8Array): void {
let input = typeof data === "string" ? data : data.toString();
if (this.buffer) {
input = this.buffer + input;
this.buffer = undefined;
}
const lines = input.split("\n");
const length = lines.length - 1;
const lastLine = lines[length];
if (lastLine.length > 0) {
this.buffer = lastLine;
}
lines.pop(); // This is either the line we buffered or an empty string.
for (let i = 0; i < length; ++i) {
this.callback(lines[i]);
}
}
}

View File

@ -1,4 +1,5 @@
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 {

View File

@ -1,8 +0,0 @@
/**
* 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));
};

View File

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

View File

@ -1,8 +1,9 @@
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;
}

View 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,7 +51,13 @@ 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);

View File

@ -1,10 +1,14 @@
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}"`);
@ -68,7 +92,6 @@ export class Server {
const session = handleNewSession(this.connection, message.getNewSession()!, () => {
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,7 +118,28 @@ 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 {

View File

@ -1,6 +1,7 @@
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;

View 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 {

View File

@ -11,10 +11,11 @@ var global = Function('return this')();
var command_pb = require('./command_pb.js');
var node_pb = require('./node_pb.js');
var vscode_pb = require('./vscode_pb.js');
goog.exportSymbol('proto.ClientMessage', null, global);
goog.exportSymbol('proto.InitMessage', null, global);
goog.exportSymbol('proto.InitMessage.OperatingSystem', null, global);
goog.exportSymbol('proto.ServerMessage', null, global);
goog.exportSymbol('proto.WorkingInitMessage', null, global);
goog.exportSymbol('proto.WorkingInitMessage.OperatingSystem', null, global);
/**
* Generated by JsPbCodeGenerator.
@ -41,7 +42,7 @@ if (goog.DEBUG && !COMPILED) {
* @private {!Array<!Array<number>>}
* @const
*/
proto.ClientMessage.oneofGroups_ = [[1,2,3,4,5,6]];
proto.ClientMessage.oneofGroups_ = [[1,2,3,4,5,6,7,8,9,10]];
/**
* @enum {number}
@ -53,7 +54,11 @@ proto.ClientMessage.MsgCase = {
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
};
/**
@ -96,7 +101,11 @@ proto.ClientMessage.toObject = function(includeInstance, msg) {
writeToSession: (f = msg.getWriteToSession()) && command_pb.WriteToSessionMessage.toObject(includeInstance, f),
closeSessionInput: (f = msg.getCloseSessionInput()) && command_pb.CloseSessionInputMessage.toObject(includeInstance, f),
resizeSessionTty: (f = msg.getResizeSessionTty()) && command_pb.ResizeSessionTTYMessage.toObject(includeInstance, f),
newEval: (f = msg.getNewEval()) && node_pb.NewEvalMessage.toObject(includeInstance, f)
newConnection: (f = msg.getNewConnection()) && command_pb.NewConnectionMessage.toObject(includeInstance, f),
connectionOutput: (f = msg.getConnectionOutput()) && command_pb.ConnectionOutputMessage.toObject(includeInstance, f),
connectionClose: (f = msg.getConnectionClose()) && command_pb.ConnectionCloseMessage.toObject(includeInstance, f),
newEval: (f = msg.getNewEval()) && node_pb.NewEvalMessage.toObject(includeInstance, f),
sharedProcessInit: (f = msg.getSharedProcessInit()) && vscode_pb.SharedProcessInitMessage.toObject(includeInstance, f)
};
if (includeInstance) {
@ -159,10 +168,30 @@ proto.ClientMessage.deserializeBinaryFromReader = function(msg, reader) {
msg.setResizeSessionTty(value);
break;
case 6:
var value = new command_pb.NewConnectionMessage;
reader.readMessage(value,command_pb.NewConnectionMessage.deserializeBinaryFromReader);
msg.setNewConnection(value);
break;
case 7:
var value = new command_pb.ConnectionOutputMessage;
reader.readMessage(value,command_pb.ConnectionOutputMessage.deserializeBinaryFromReader);
msg.setConnectionOutput(value);
break;
case 8:
var value = new command_pb.ConnectionCloseMessage;
reader.readMessage(value,command_pb.ConnectionCloseMessage.deserializeBinaryFromReader);
msg.setConnectionClose(value);
break;
case 9:
var value = new node_pb.NewEvalMessage;
reader.readMessage(value,node_pb.NewEvalMessage.deserializeBinaryFromReader);
msg.setNewEval(value);
break;
case 10:
var value = new vscode_pb.SharedProcessInitMessage;
reader.readMessage(value,vscode_pb.SharedProcessInitMessage.deserializeBinaryFromReader);
msg.setSharedProcessInit(value);
break;
default:
reader.skipField();
break;
@ -241,14 +270,46 @@ proto.ClientMessage.prototype.serializeBinaryToWriter = function (writer) {
command_pb.ResizeSessionTTYMessage.serializeBinaryToWriter
);
}
f = this.getNewEval();
f = this.getNewConnection();
if (f != null) {
writer.writeMessage(
6,
f,
command_pb.NewConnectionMessage.serializeBinaryToWriter
);
}
f = this.getConnectionOutput();
if (f != null) {
writer.writeMessage(
7,
f,
command_pb.ConnectionOutputMessage.serializeBinaryToWriter
);
}
f = this.getConnectionClose();
if (f != null) {
writer.writeMessage(
8,
f,
command_pb.ConnectionCloseMessage.serializeBinaryToWriter
);
}
f = this.getNewEval();
if (f != null) {
writer.writeMessage(
9,
f,
node_pb.NewEvalMessage.serializeBinaryToWriter
);
}
f = this.getSharedProcessInit();
if (f != null) {
writer.writeMessage(
10,
f,
vscode_pb.SharedProcessInitMessage.serializeBinaryToWriter
);
}
};
@ -412,18 +473,108 @@ proto.ClientMessage.prototype.hasResizeSessionTty = function() {
/**
* optional NewEvalMessage new_eval = 6;
* optional NewConnectionMessage new_connection = 6;
* @return {proto.NewConnectionMessage}
*/
proto.ClientMessage.prototype.getNewConnection = function() {
return /** @type{proto.NewConnectionMessage} */ (
jspb.Message.getWrapperField(this, command_pb.NewConnectionMessage, 6));
};
/** @param {proto.NewConnectionMessage|undefined} value */
proto.ClientMessage.prototype.setNewConnection = function(value) {
jspb.Message.setOneofWrapperField(this, 6, proto.ClientMessage.oneofGroups_[0], value);
};
proto.ClientMessage.prototype.clearNewConnection = function() {
this.setNewConnection(undefined);
};
/**
* Returns whether this field is set.
* @return{!boolean}
*/
proto.ClientMessage.prototype.hasNewConnection = function() {
return jspb.Message.getField(this, 6) != null;
};
/**
* optional ConnectionOutputMessage connection_output = 7;
* @return {proto.ConnectionOutputMessage}
*/
proto.ClientMessage.prototype.getConnectionOutput = function() {
return /** @type{proto.ConnectionOutputMessage} */ (
jspb.Message.getWrapperField(this, command_pb.ConnectionOutputMessage, 7));
};
/** @param {proto.ConnectionOutputMessage|undefined} value */
proto.ClientMessage.prototype.setConnectionOutput = function(value) {
jspb.Message.setOneofWrapperField(this, 7, proto.ClientMessage.oneofGroups_[0], value);
};
proto.ClientMessage.prototype.clearConnectionOutput = function() {
this.setConnectionOutput(undefined);
};
/**
* Returns whether this field is set.
* @return{!boolean}
*/
proto.ClientMessage.prototype.hasConnectionOutput = function() {
return jspb.Message.getField(this, 7) != null;
};
/**
* optional ConnectionCloseMessage connection_close = 8;
* @return {proto.ConnectionCloseMessage}
*/
proto.ClientMessage.prototype.getConnectionClose = function() {
return /** @type{proto.ConnectionCloseMessage} */ (
jspb.Message.getWrapperField(this, command_pb.ConnectionCloseMessage, 8));
};
/** @param {proto.ConnectionCloseMessage|undefined} value */
proto.ClientMessage.prototype.setConnectionClose = function(value) {
jspb.Message.setOneofWrapperField(this, 8, proto.ClientMessage.oneofGroups_[0], value);
};
proto.ClientMessage.prototype.clearConnectionClose = function() {
this.setConnectionClose(undefined);
};
/**
* Returns whether this field is set.
* @return{!boolean}
*/
proto.ClientMessage.prototype.hasConnectionClose = function() {
return jspb.Message.getField(this, 8) != null;
};
/**
* optional NewEvalMessage new_eval = 9;
* @return {proto.NewEvalMessage}
*/
proto.ClientMessage.prototype.getNewEval = function() {
return /** @type{proto.NewEvalMessage} */ (
jspb.Message.getWrapperField(this, node_pb.NewEvalMessage, 6));
jspb.Message.getWrapperField(this, node_pb.NewEvalMessage, 9));
};
/** @param {proto.NewEvalMessage|undefined} value */
proto.ClientMessage.prototype.setNewEval = function(value) {
jspb.Message.setOneofWrapperField(this, 6, proto.ClientMessage.oneofGroups_[0], value);
jspb.Message.setOneofWrapperField(this, 9, proto.ClientMessage.oneofGroups_[0], value);
};
@ -437,7 +588,37 @@ proto.ClientMessage.prototype.clearNewEval = function() {
* @return{!boolean}
*/
proto.ClientMessage.prototype.hasNewEval = function() {
return jspb.Message.getField(this, 6) != null;
return jspb.Message.getField(this, 9) != null;
};
/**
* optional SharedProcessInitMessage shared_process_init = 10;
* @return {proto.SharedProcessInitMessage}
*/
proto.ClientMessage.prototype.getSharedProcessInit = function() {
return /** @type{proto.SharedProcessInitMessage} */ (
jspb.Message.getWrapperField(this, vscode_pb.SharedProcessInitMessage, 10));
};
/** @param {proto.SharedProcessInitMessage|undefined} value */
proto.ClientMessage.prototype.setSharedProcessInit = function(value) {
jspb.Message.setOneofWrapperField(this, 10, proto.ClientMessage.oneofGroups_[0], value);
};
proto.ClientMessage.prototype.clearSharedProcessInit = function() {
this.setSharedProcessInit(undefined);
};
/**
* Returns whether this field is set.
* @return{!boolean}
*/
proto.ClientMessage.prototype.hasSharedProcessInit = function() {
return jspb.Message.getField(this, 10) != null;
};
@ -467,7 +648,7 @@ if (goog.DEBUG && !COMPILED) {
* @private {!Array<!Array<number>>}
* @const
*/
proto.ServerMessage.oneofGroups_ = [[1,2,3,4,5,6,7]];
proto.ServerMessage.oneofGroups_ = [[1,2,3,4,5,6,7,8,9,10,11]];
/**
* @enum {number}
@ -478,9 +659,13 @@ proto.ServerMessage.MsgCase = {
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
};
/**
@ -522,9 +707,13 @@ proto.ServerMessage.toObject = function(includeInstance, msg) {
sessionDone: (f = msg.getSessionDone()) && command_pb.SessionDoneMessage.toObject(includeInstance, f),
sessionOutput: (f = msg.getSessionOutput()) && command_pb.SessionOutputMessage.toObject(includeInstance, f),
identifySession: (f = msg.getIdentifySession()) && command_pb.IdentifySessionMessage.toObject(includeInstance, f),
connectionFailure: (f = msg.getConnectionFailure()) && command_pb.NewConnectionFailureMessage.toObject(includeInstance, f),
connectionOutput: (f = msg.getConnectionOutput()) && command_pb.ConnectionOutputMessage.toObject(includeInstance, f),
connectionClose: (f = msg.getConnectionClose()) && command_pb.ConnectionCloseMessage.toObject(includeInstance, f),
connectionEstablished: (f = msg.getConnectionEstablished()) && command_pb.ConnectionEstablishedMessage.toObject(includeInstance, f),
evalFailed: (f = msg.getEvalFailed()) && node_pb.EvalFailedMessage.toObject(includeInstance, f),
evalDone: (f = msg.getEvalDone()) && node_pb.EvalDoneMessage.toObject(includeInstance, f),
init: (f = msg.getInit()) && proto.InitMessage.toObject(includeInstance, f)
init: (f = msg.getInit()) && proto.WorkingInitMessage.toObject(includeInstance, f)
};
if (includeInstance) {
@ -582,18 +771,38 @@ proto.ServerMessage.deserializeBinaryFromReader = function(msg, reader) {
msg.setIdentifySession(value);
break;
case 5:
var value = new command_pb.NewConnectionFailureMessage;
reader.readMessage(value,command_pb.NewConnectionFailureMessage.deserializeBinaryFromReader);
msg.setConnectionFailure(value);
break;
case 6:
var value = new command_pb.ConnectionOutputMessage;
reader.readMessage(value,command_pb.ConnectionOutputMessage.deserializeBinaryFromReader);
msg.setConnectionOutput(value);
break;
case 7:
var value = new command_pb.ConnectionCloseMessage;
reader.readMessage(value,command_pb.ConnectionCloseMessage.deserializeBinaryFromReader);
msg.setConnectionClose(value);
break;
case 8:
var value = new command_pb.ConnectionEstablishedMessage;
reader.readMessage(value,command_pb.ConnectionEstablishedMessage.deserializeBinaryFromReader);
msg.setConnectionEstablished(value);
break;
case 9:
var value = new node_pb.EvalFailedMessage;
reader.readMessage(value,node_pb.EvalFailedMessage.deserializeBinaryFromReader);
msg.setEvalFailed(value);
break;
case 6:
case 10:
var value = new node_pb.EvalDoneMessage;
reader.readMessage(value,node_pb.EvalDoneMessage.deserializeBinaryFromReader);
msg.setEvalDone(value);
break;
case 7:
var value = new proto.InitMessage;
reader.readMessage(value,proto.InitMessage.deserializeBinaryFromReader);
case 11:
var value = new proto.WorkingInitMessage;
reader.readMessage(value,proto.WorkingInitMessage.deserializeBinaryFromReader);
msg.setInit(value);
break;
default:
@ -666,18 +875,50 @@ proto.ServerMessage.prototype.serializeBinaryToWriter = function (writer) {
command_pb.IdentifySessionMessage.serializeBinaryToWriter
);
}
f = this.getEvalFailed();
f = this.getConnectionFailure();
if (f != null) {
writer.writeMessage(
5,
f,
command_pb.NewConnectionFailureMessage.serializeBinaryToWriter
);
}
f = this.getConnectionOutput();
if (f != null) {
writer.writeMessage(
6,
f,
command_pb.ConnectionOutputMessage.serializeBinaryToWriter
);
}
f = this.getConnectionClose();
if (f != null) {
writer.writeMessage(
7,
f,
command_pb.ConnectionCloseMessage.serializeBinaryToWriter
);
}
f = this.getConnectionEstablished();
if (f != null) {
writer.writeMessage(
8,
f,
command_pb.ConnectionEstablishedMessage.serializeBinaryToWriter
);
}
f = this.getEvalFailed();
if (f != null) {
writer.writeMessage(
9,
f,
node_pb.EvalFailedMessage.serializeBinaryToWriter
);
}
f = this.getEvalDone();
if (f != null) {
writer.writeMessage(
6,
10,
f,
node_pb.EvalDoneMessage.serializeBinaryToWriter
);
@ -685,9 +926,9 @@ proto.ServerMessage.prototype.serializeBinaryToWriter = function (writer) {
f = this.getInit();
if (f != null) {
writer.writeMessage(
7,
11,
f,
proto.InitMessage.serializeBinaryToWriter
proto.WorkingInitMessage.serializeBinaryToWriter
);
}
};
@ -823,18 +1064,138 @@ proto.ServerMessage.prototype.hasIdentifySession = function() {
/**
* optional EvalFailedMessage eval_failed = 5;
* optional NewConnectionFailureMessage connection_failure = 5;
* @return {proto.NewConnectionFailureMessage}
*/
proto.ServerMessage.prototype.getConnectionFailure = function() {
return /** @type{proto.NewConnectionFailureMessage} */ (
jspb.Message.getWrapperField(this, command_pb.NewConnectionFailureMessage, 5));
};
/** @param {proto.NewConnectionFailureMessage|undefined} value */
proto.ServerMessage.prototype.setConnectionFailure = function(value) {
jspb.Message.setOneofWrapperField(this, 5, proto.ServerMessage.oneofGroups_[0], value);
};
proto.ServerMessage.prototype.clearConnectionFailure = function() {
this.setConnectionFailure(undefined);
};
/**
* Returns whether this field is set.
* @return{!boolean}
*/
proto.ServerMessage.prototype.hasConnectionFailure = function() {
return jspb.Message.getField(this, 5) != null;
};
/**
* optional ConnectionOutputMessage connection_output = 6;
* @return {proto.ConnectionOutputMessage}
*/
proto.ServerMessage.prototype.getConnectionOutput = function() {
return /** @type{proto.ConnectionOutputMessage} */ (
jspb.Message.getWrapperField(this, command_pb.ConnectionOutputMessage, 6));
};
/** @param {proto.ConnectionOutputMessage|undefined} value */
proto.ServerMessage.prototype.setConnectionOutput = function(value) {
jspb.Message.setOneofWrapperField(this, 6, proto.ServerMessage.oneofGroups_[0], value);
};
proto.ServerMessage.prototype.clearConnectionOutput = function() {
this.setConnectionOutput(undefined);
};
/**
* Returns whether this field is set.
* @return{!boolean}
*/
proto.ServerMessage.prototype.hasConnectionOutput = function() {
return jspb.Message.getField(this, 6) != null;
};
/**
* optional ConnectionCloseMessage connection_close = 7;
* @return {proto.ConnectionCloseMessage}
*/
proto.ServerMessage.prototype.getConnectionClose = function() {
return /** @type{proto.ConnectionCloseMessage} */ (
jspb.Message.getWrapperField(this, command_pb.ConnectionCloseMessage, 7));
};
/** @param {proto.ConnectionCloseMessage|undefined} value */
proto.ServerMessage.prototype.setConnectionClose = function(value) {
jspb.Message.setOneofWrapperField(this, 7, proto.ServerMessage.oneofGroups_[0], value);
};
proto.ServerMessage.prototype.clearConnectionClose = function() {
this.setConnectionClose(undefined);
};
/**
* Returns whether this field is set.
* @return{!boolean}
*/
proto.ServerMessage.prototype.hasConnectionClose = function() {
return jspb.Message.getField(this, 7) != null;
};
/**
* optional ConnectionEstablishedMessage connection_established = 8;
* @return {proto.ConnectionEstablishedMessage}
*/
proto.ServerMessage.prototype.getConnectionEstablished = function() {
return /** @type{proto.ConnectionEstablishedMessage} */ (
jspb.Message.getWrapperField(this, command_pb.ConnectionEstablishedMessage, 8));
};
/** @param {proto.ConnectionEstablishedMessage|undefined} value */
proto.ServerMessage.prototype.setConnectionEstablished = function(value) {
jspb.Message.setOneofWrapperField(this, 8, proto.ServerMessage.oneofGroups_[0], value);
};
proto.ServerMessage.prototype.clearConnectionEstablished = function() {
this.setConnectionEstablished(undefined);
};
/**
* Returns whether this field is set.
* @return{!boolean}
*/
proto.ServerMessage.prototype.hasConnectionEstablished = function() {
return jspb.Message.getField(this, 8) != null;
};
/**
* optional EvalFailedMessage eval_failed = 9;
* @return {proto.EvalFailedMessage}
*/
proto.ServerMessage.prototype.getEvalFailed = function() {
return /** @type{proto.EvalFailedMessage} */ (
jspb.Message.getWrapperField(this, node_pb.EvalFailedMessage, 5));
jspb.Message.getWrapperField(this, node_pb.EvalFailedMessage, 9));
};
/** @param {proto.EvalFailedMessage|undefined} value */
proto.ServerMessage.prototype.setEvalFailed = function(value) {
jspb.Message.setOneofWrapperField(this, 5, proto.ServerMessage.oneofGroups_[0], value);
jspb.Message.setOneofWrapperField(this, 9, proto.ServerMessage.oneofGroups_[0], value);
};
@ -848,23 +1209,23 @@ proto.ServerMessage.prototype.clearEvalFailed = function() {
* @return{!boolean}
*/
proto.ServerMessage.prototype.hasEvalFailed = function() {
return jspb.Message.getField(this, 5) != null;
return jspb.Message.getField(this, 9) != null;
};
/**
* optional EvalDoneMessage eval_done = 6;
* optional EvalDoneMessage eval_done = 10;
* @return {proto.EvalDoneMessage}
*/
proto.ServerMessage.prototype.getEvalDone = function() {
return /** @type{proto.EvalDoneMessage} */ (
jspb.Message.getWrapperField(this, node_pb.EvalDoneMessage, 6));
jspb.Message.getWrapperField(this, node_pb.EvalDoneMessage, 10));
};
/** @param {proto.EvalDoneMessage|undefined} value */
proto.ServerMessage.prototype.setEvalDone = function(value) {
jspb.Message.setOneofWrapperField(this, 6, proto.ServerMessage.oneofGroups_[0], value);
jspb.Message.setOneofWrapperField(this, 10, proto.ServerMessage.oneofGroups_[0], value);
};
@ -878,23 +1239,23 @@ proto.ServerMessage.prototype.clearEvalDone = function() {
* @return{!boolean}
*/
proto.ServerMessage.prototype.hasEvalDone = function() {
return jspb.Message.getField(this, 6) != null;
return jspb.Message.getField(this, 10) != null;
};
/**
* optional InitMessage init = 7;
* @return {proto.InitMessage}
* optional WorkingInitMessage init = 11;
* @return {proto.WorkingInitMessage}
*/
proto.ServerMessage.prototype.getInit = function() {
return /** @type{proto.InitMessage} */ (
jspb.Message.getWrapperField(this, proto.InitMessage, 7));
return /** @type{proto.WorkingInitMessage} */ (
jspb.Message.getWrapperField(this, proto.WorkingInitMessage, 11));
};
/** @param {proto.InitMessage|undefined} value */
/** @param {proto.WorkingInitMessage|undefined} value */
proto.ServerMessage.prototype.setInit = function(value) {
jspb.Message.setOneofWrapperField(this, 7, proto.ServerMessage.oneofGroups_[0], value);
jspb.Message.setOneofWrapperField(this, 11, proto.ServerMessage.oneofGroups_[0], value);
};
@ -908,7 +1269,7 @@ proto.ServerMessage.prototype.clearInit = function() {
* @return{!boolean}
*/
proto.ServerMessage.prototype.hasInit = function() {
return jspb.Message.getField(this, 7) != null;
return jspb.Message.getField(this, 11) != null;
};
@ -923,12 +1284,12 @@ proto.ServerMessage.prototype.hasInit = function() {
* @extends {jspb.Message}
* @constructor
*/
proto.InitMessage = function(opt_data) {
proto.WorkingInitMessage = function(opt_data) {
jspb.Message.initialize(this, opt_data, 0, -1, null, null);
};
goog.inherits(proto.InitMessage, jspb.Message);
goog.inherits(proto.WorkingInitMessage, jspb.Message);
if (goog.DEBUG && !COMPILED) {
proto.InitMessage.displayName = 'proto.InitMessage';
proto.WorkingInitMessage.displayName = 'proto.WorkingInitMessage';
}
@ -943,8 +1304,8 @@ if (jspb.Message.GENERATE_TO_OBJECT) {
* for transitional soy proto support: http://goto/soy-param-migration
* @return {!Object}
*/
proto.InitMessage.prototype.toObject = function(opt_includeInstance) {
return proto.InitMessage.toObject(opt_includeInstance, this);
proto.WorkingInitMessage.prototype.toObject = function(opt_includeInstance) {
return proto.WorkingInitMessage.toObject(opt_includeInstance, this);
};
@ -953,10 +1314,10 @@ proto.InitMessage.prototype.toObject = function(opt_includeInstance) {
* @param {boolean|undefined} includeInstance Whether to include the JSPB
* instance for transitional soy proto support:
* http://goto/soy-param-migration
* @param {!proto.InitMessage} msg The msg instance to transform.
* @param {!proto.WorkingInitMessage} msg The msg instance to transform.
* @return {!Object}
*/
proto.InitMessage.toObject = function(includeInstance, msg) {
proto.WorkingInitMessage.toObject = function(includeInstance, msg) {
var f, obj = {
homeDirectory: msg.getHomeDirectory(),
tmpDirectory: msg.getTmpDirectory(),
@ -976,23 +1337,23 @@ proto.InitMessage.toObject = function(includeInstance, msg) {
/**
* Deserializes binary data (in protobuf wire format).
* @param {jspb.ByteSource} bytes The bytes to deserialize.
* @return {!proto.InitMessage}
* @return {!proto.WorkingInitMessage}
*/
proto.InitMessage.deserializeBinary = function(bytes) {
proto.WorkingInitMessage.deserializeBinary = function(bytes) {
var reader = new jspb.BinaryReader(bytes);
var msg = new proto.InitMessage;
return proto.InitMessage.deserializeBinaryFromReader(msg, reader);
var msg = new proto.WorkingInitMessage;
return proto.WorkingInitMessage.deserializeBinaryFromReader(msg, reader);
};
/**
* Deserializes binary data (in protobuf wire format) from the
* given reader into the given message object.
* @param {!proto.InitMessage} msg The message object to deserialize into.
* @param {!proto.WorkingInitMessage} msg The message object to deserialize into.
* @param {!jspb.BinaryReader} reader The BinaryReader to use.
* @return {!proto.InitMessage}
* @return {!proto.WorkingInitMessage}
*/
proto.InitMessage.deserializeBinaryFromReader = function(msg, reader) {
proto.WorkingInitMessage.deserializeBinaryFromReader = function(msg, reader) {
while (reader.nextField()) {
if (reader.isEndGroup()) {
break;
@ -1016,7 +1377,7 @@ proto.InitMessage.deserializeBinaryFromReader = function(msg, reader) {
msg.setWorkingDirectory(value);
break;
case 5:
var value = /** @type {!proto.InitMessage.OperatingSystem} */ (reader.readEnum());
var value = /** @type {!proto.WorkingInitMessage.OperatingSystem} */ (reader.readEnum());
msg.setOperatingSystem(value);
break;
default:
@ -1031,10 +1392,10 @@ proto.InitMessage.deserializeBinaryFromReader = function(msg, reader) {
/**
* Class method variant: serializes the given message to binary data
* (in protobuf wire format), writing to the given BinaryWriter.
* @param {!proto.InitMessage} message
* @param {!proto.WorkingInitMessage} message
* @param {!jspb.BinaryWriter} writer
*/
proto.InitMessage.serializeBinaryToWriter = function(message, writer) {
proto.WorkingInitMessage.serializeBinaryToWriter = function(message, writer) {
message.serializeBinaryToWriter(writer);
};
@ -1043,7 +1404,7 @@ proto.InitMessage.serializeBinaryToWriter = function(message, writer) {
* Serializes the message to binary data (in protobuf wire format).
* @return {!Uint8Array}
*/
proto.InitMessage.prototype.serializeBinary = function() {
proto.WorkingInitMessage.prototype.serializeBinary = function() {
var writer = new jspb.BinaryWriter();
this.serializeBinaryToWriter(writer);
return writer.getResultBuffer();
@ -1055,7 +1416,7 @@ proto.InitMessage.prototype.serializeBinary = function() {
* writing to the given BinaryWriter.
* @param {!jspb.BinaryWriter} writer
*/
proto.InitMessage.prototype.serializeBinaryToWriter = function (writer) {
proto.WorkingInitMessage.prototype.serializeBinaryToWriter = function (writer) {
var f = undefined;
f = this.getHomeDirectory();
if (f.length > 0) {
@ -1097,10 +1458,10 @@ proto.InitMessage.prototype.serializeBinaryToWriter = function (writer) {
/**
* Creates a deep clone of this proto. No data is shared with the original.
* @return {!proto.InitMessage} The clone.
* @return {!proto.WorkingInitMessage} The clone.
*/
proto.InitMessage.prototype.cloneMessage = function() {
return /** @type {!proto.InitMessage} */ (jspb.Message.cloneMessage(this));
proto.WorkingInitMessage.prototype.cloneMessage = function() {
return /** @type {!proto.WorkingInitMessage} */ (jspb.Message.cloneMessage(this));
};
@ -1108,13 +1469,13 @@ proto.InitMessage.prototype.cloneMessage = function() {
* optional string home_directory = 1;
* @return {string}
*/
proto.InitMessage.prototype.getHomeDirectory = function() {
proto.WorkingInitMessage.prototype.getHomeDirectory = function() {
return /** @type {string} */ (jspb.Message.getFieldProto3(this, 1, ""));
};
/** @param {string} value */
proto.InitMessage.prototype.setHomeDirectory = function(value) {
proto.WorkingInitMessage.prototype.setHomeDirectory = function(value) {
jspb.Message.setField(this, 1, value);
};
@ -1123,13 +1484,13 @@ proto.InitMessage.prototype.setHomeDirectory = function(value) {
* optional string tmp_directory = 2;
* @return {string}
*/
proto.InitMessage.prototype.getTmpDirectory = function() {
proto.WorkingInitMessage.prototype.getTmpDirectory = function() {
return /** @type {string} */ (jspb.Message.getFieldProto3(this, 2, ""));
};
/** @param {string} value */
proto.InitMessage.prototype.setTmpDirectory = function(value) {
proto.WorkingInitMessage.prototype.setTmpDirectory = function(value) {
jspb.Message.setField(this, 2, value);
};
@ -1138,13 +1499,13 @@ proto.InitMessage.prototype.setTmpDirectory = function(value) {
* optional string data_directory = 3;
* @return {string}
*/
proto.InitMessage.prototype.getDataDirectory = function() {
proto.WorkingInitMessage.prototype.getDataDirectory = function() {
return /** @type {string} */ (jspb.Message.getFieldProto3(this, 3, ""));
};
/** @param {string} value */
proto.InitMessage.prototype.setDataDirectory = function(value) {
proto.WorkingInitMessage.prototype.setDataDirectory = function(value) {
jspb.Message.setField(this, 3, value);
};
@ -1153,28 +1514,28 @@ proto.InitMessage.prototype.setDataDirectory = function(value) {
* optional string working_directory = 4;
* @return {string}
*/
proto.InitMessage.prototype.getWorkingDirectory = function() {
proto.WorkingInitMessage.prototype.getWorkingDirectory = function() {
return /** @type {string} */ (jspb.Message.getFieldProto3(this, 4, ""));
};
/** @param {string} value */
proto.InitMessage.prototype.setWorkingDirectory = function(value) {
proto.WorkingInitMessage.prototype.setWorkingDirectory = function(value) {
jspb.Message.setField(this, 4, value);
};
/**
* optional OperatingSystem operating_system = 5;
* @return {!proto.InitMessage.OperatingSystem}
* @return {!proto.WorkingInitMessage.OperatingSystem}
*/
proto.InitMessage.prototype.getOperatingSystem = function() {
return /** @type {!proto.InitMessage.OperatingSystem} */ (jspb.Message.getFieldProto3(this, 5, 0));
proto.WorkingInitMessage.prototype.getOperatingSystem = function() {
return /** @type {!proto.WorkingInitMessage.OperatingSystem} */ (jspb.Message.getFieldProto3(this, 5, 0));
};
/** @param {!proto.InitMessage.OperatingSystem} value */
proto.InitMessage.prototype.setOperatingSystem = function(value) {
/** @param {!proto.WorkingInitMessage.OperatingSystem} value */
proto.WorkingInitMessage.prototype.setOperatingSystem = function(value) {
jspb.Message.setField(this, 5, value);
};
@ -1182,7 +1543,7 @@ proto.InitMessage.prototype.setOperatingSystem = function(value) {
/**
* @enum {number}
*/
proto.InitMessage.OperatingSystem = {
proto.WorkingInitMessage.OperatingSystem = {
WINDOWS: 0,
LINUX: 1,
MAC: 2

View File

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

View File

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

View File

@ -10,7 +10,12 @@ var goog = jspb;
var global = Function('return this')();
goog.exportSymbol('proto.CloseSessionInputMessage', null, global);
goog.exportSymbol('proto.ConnectionCloseMessage', null, global);
goog.exportSymbol('proto.ConnectionEstablishedMessage', null, global);
goog.exportSymbol('proto.ConnectionOutputMessage', null, global);
goog.exportSymbol('proto.IdentifySessionMessage', null, global);
goog.exportSymbol('proto.NewConnectionFailureMessage', null, global);
goog.exportSymbol('proto.NewConnectionMessage', null, global);
goog.exportSymbol('proto.NewSessionFailureMessage', null, global);
goog.exportSymbol('proto.NewSessionFailureMessage.Reason', null, global);
goog.exportSymbol('proto.NewSessionMessage', null, global);
@ -2155,4 +2160,931 @@ proto.TTYDimensions.prototype.setWidth = function(value) {
};
/**
* Generated by JsPbCodeGenerator.
* @param {Array=} opt_data Optional initial data array, typically from a
* server response, or constructed directly in Javascript. The array is used
* in place and becomes part of the constructed object. It is not cloned.
* If no data is provided, the constructed object will be empty, but still
* valid.
* @extends {jspb.Message}
* @constructor
*/
proto.NewConnectionMessage = function(opt_data) {
jspb.Message.initialize(this, opt_data, 0, -1, null, null);
};
goog.inherits(proto.NewConnectionMessage, jspb.Message);
if (goog.DEBUG && !COMPILED) {
proto.NewConnectionMessage.displayName = 'proto.NewConnectionMessage';
}
if (jspb.Message.GENERATE_TO_OBJECT) {
/**
* Creates an object representation of this proto suitable for use in Soy templates.
* Field names that are reserved in JavaScript and will be renamed to pb_name.
* To access a reserved field use, foo.pb_<name>, eg, foo.pb_default.
* For the list of reserved names please see:
* com.google.apps.jspb.JsClassTemplate.JS_RESERVED_WORDS.
* @param {boolean=} opt_includeInstance Whether to include the JSPB instance
* for transitional soy proto support: http://goto/soy-param-migration
* @return {!Object}
*/
proto.NewConnectionMessage.prototype.toObject = function(opt_includeInstance) {
return proto.NewConnectionMessage.toObject(opt_includeInstance, this);
};
/**
* Static version of the {@see toObject} method.
* @param {boolean|undefined} includeInstance Whether to include the JSPB
* instance for transitional soy proto support:
* http://goto/soy-param-migration
* @param {!proto.NewConnectionMessage} msg The msg instance to transform.
* @return {!Object}
*/
proto.NewConnectionMessage.toObject = function(includeInstance, msg) {
var f, obj = {
id: msg.getId(),
port: msg.getPort(),
path: msg.getPath()
};
if (includeInstance) {
obj.$jspbMessageInstance = msg;
}
return obj;
};
}
/**
* Deserializes binary data (in protobuf wire format).
* @param {jspb.ByteSource} bytes The bytes to deserialize.
* @return {!proto.NewConnectionMessage}
*/
proto.NewConnectionMessage.deserializeBinary = function(bytes) {
var reader = new jspb.BinaryReader(bytes);
var msg = new proto.NewConnectionMessage;
return proto.NewConnectionMessage.deserializeBinaryFromReader(msg, reader);
};
/**
* Deserializes binary data (in protobuf wire format) from the
* given reader into the given message object.
* @param {!proto.NewConnectionMessage} msg The message object to deserialize into.
* @param {!jspb.BinaryReader} reader The BinaryReader to use.
* @return {!proto.NewConnectionMessage}
*/
proto.NewConnectionMessage.deserializeBinaryFromReader = function(msg, reader) {
while (reader.nextField()) {
if (reader.isEndGroup()) {
break;
}
var field = reader.getFieldNumber();
switch (field) {
case 1:
var value = /** @type {number} */ (reader.readUint64());
msg.setId(value);
break;
case 2:
var value = /** @type {number} */ (reader.readUint64());
msg.setPort(value);
break;
case 3:
var value = /** @type {string} */ (reader.readString());
msg.setPath(value);
break;
default:
reader.skipField();
break;
}
}
return msg;
};
/**
* Class method variant: serializes the given message to binary data
* (in protobuf wire format), writing to the given BinaryWriter.
* @param {!proto.NewConnectionMessage} message
* @param {!jspb.BinaryWriter} writer
*/
proto.NewConnectionMessage.serializeBinaryToWriter = function(message, writer) {
message.serializeBinaryToWriter(writer);
};
/**
* Serializes the message to binary data (in protobuf wire format).
* @return {!Uint8Array}
*/
proto.NewConnectionMessage.prototype.serializeBinary = function() {
var writer = new jspb.BinaryWriter();
this.serializeBinaryToWriter(writer);
return writer.getResultBuffer();
};
/**
* Serializes the message to binary data (in protobuf wire format),
* writing to the given BinaryWriter.
* @param {!jspb.BinaryWriter} writer
*/
proto.NewConnectionMessage.prototype.serializeBinaryToWriter = function (writer) {
var f = undefined;
f = this.getId();
if (f !== 0) {
writer.writeUint64(
1,
f
);
}
f = this.getPort();
if (f !== 0) {
writer.writeUint64(
2,
f
);
}
f = this.getPath();
if (f.length > 0) {
writer.writeString(
3,
f
);
}
};
/**
* Creates a deep clone of this proto. No data is shared with the original.
* @return {!proto.NewConnectionMessage} The clone.
*/
proto.NewConnectionMessage.prototype.cloneMessage = function() {
return /** @type {!proto.NewConnectionMessage} */ (jspb.Message.cloneMessage(this));
};
/**
* optional uint64 id = 1;
* @return {number}
*/
proto.NewConnectionMessage.prototype.getId = function() {
return /** @type {number} */ (jspb.Message.getFieldProto3(this, 1, 0));
};
/** @param {number} value */
proto.NewConnectionMessage.prototype.setId = function(value) {
jspb.Message.setField(this, 1, value);
};
/**
* optional uint64 port = 2;
* @return {number}
*/
proto.NewConnectionMessage.prototype.getPort = function() {
return /** @type {number} */ (jspb.Message.getFieldProto3(this, 2, 0));
};
/** @param {number} value */
proto.NewConnectionMessage.prototype.setPort = function(value) {
jspb.Message.setField(this, 2, value);
};
/**
* optional string path = 3;
* @return {string}
*/
proto.NewConnectionMessage.prototype.getPath = function() {
return /** @type {string} */ (jspb.Message.getFieldProto3(this, 3, ""));
};
/** @param {string} value */
proto.NewConnectionMessage.prototype.setPath = function(value) {
jspb.Message.setField(this, 3, value);
};
/**
* Generated by JsPbCodeGenerator.
* @param {Array=} opt_data Optional initial data array, typically from a
* server response, or constructed directly in Javascript. The array is used
* in place and becomes part of the constructed object. It is not cloned.
* If no data is provided, the constructed object will be empty, but still
* valid.
* @extends {jspb.Message}
* @constructor
*/
proto.ConnectionEstablishedMessage = function(opt_data) {
jspb.Message.initialize(this, opt_data, 0, -1, null, null);
};
goog.inherits(proto.ConnectionEstablishedMessage, jspb.Message);
if (goog.DEBUG && !COMPILED) {
proto.ConnectionEstablishedMessage.displayName = 'proto.ConnectionEstablishedMessage';
}
if (jspb.Message.GENERATE_TO_OBJECT) {
/**
* Creates an object representation of this proto suitable for use in Soy templates.
* Field names that are reserved in JavaScript and will be renamed to pb_name.
* To access a reserved field use, foo.pb_<name>, eg, foo.pb_default.
* For the list of reserved names please see:
* com.google.apps.jspb.JsClassTemplate.JS_RESERVED_WORDS.
* @param {boolean=} opt_includeInstance Whether to include the JSPB instance
* for transitional soy proto support: http://goto/soy-param-migration
* @return {!Object}
*/
proto.ConnectionEstablishedMessage.prototype.toObject = function(opt_includeInstance) {
return proto.ConnectionEstablishedMessage.toObject(opt_includeInstance, this);
};
/**
* Static version of the {@see toObject} method.
* @param {boolean|undefined} includeInstance Whether to include the JSPB
* instance for transitional soy proto support:
* http://goto/soy-param-migration
* @param {!proto.ConnectionEstablishedMessage} msg The msg instance to transform.
* @return {!Object}
*/
proto.ConnectionEstablishedMessage.toObject = function(includeInstance, msg) {
var f, obj = {
id: msg.getId()
};
if (includeInstance) {
obj.$jspbMessageInstance = msg;
}
return obj;
};
}
/**
* Deserializes binary data (in protobuf wire format).
* @param {jspb.ByteSource} bytes The bytes to deserialize.
* @return {!proto.ConnectionEstablishedMessage}
*/
proto.ConnectionEstablishedMessage.deserializeBinary = function(bytes) {
var reader = new jspb.BinaryReader(bytes);
var msg = new proto.ConnectionEstablishedMessage;
return proto.ConnectionEstablishedMessage.deserializeBinaryFromReader(msg, reader);
};
/**
* Deserializes binary data (in protobuf wire format) from the
* given reader into the given message object.
* @param {!proto.ConnectionEstablishedMessage} msg The message object to deserialize into.
* @param {!jspb.BinaryReader} reader The BinaryReader to use.
* @return {!proto.ConnectionEstablishedMessage}
*/
proto.ConnectionEstablishedMessage.deserializeBinaryFromReader = function(msg, reader) {
while (reader.nextField()) {
if (reader.isEndGroup()) {
break;
}
var field = reader.getFieldNumber();
switch (field) {
case 1:
var value = /** @type {number} */ (reader.readUint64());
msg.setId(value);
break;
default:
reader.skipField();
break;
}
}
return msg;
};
/**
* Class method variant: serializes the given message to binary data
* (in protobuf wire format), writing to the given BinaryWriter.
* @param {!proto.ConnectionEstablishedMessage} message
* @param {!jspb.BinaryWriter} writer
*/
proto.ConnectionEstablishedMessage.serializeBinaryToWriter = function(message, writer) {
message.serializeBinaryToWriter(writer);
};
/**
* Serializes the message to binary data (in protobuf wire format).
* @return {!Uint8Array}
*/
proto.ConnectionEstablishedMessage.prototype.serializeBinary = function() {
var writer = new jspb.BinaryWriter();
this.serializeBinaryToWriter(writer);
return writer.getResultBuffer();
};
/**
* Serializes the message to binary data (in protobuf wire format),
* writing to the given BinaryWriter.
* @param {!jspb.BinaryWriter} writer
*/
proto.ConnectionEstablishedMessage.prototype.serializeBinaryToWriter = function (writer) {
var f = undefined;
f = this.getId();
if (f !== 0) {
writer.writeUint64(
1,
f
);
}
};
/**
* Creates a deep clone of this proto. No data is shared with the original.
* @return {!proto.ConnectionEstablishedMessage} The clone.
*/
proto.ConnectionEstablishedMessage.prototype.cloneMessage = function() {
return /** @type {!proto.ConnectionEstablishedMessage} */ (jspb.Message.cloneMessage(this));
};
/**
* optional uint64 id = 1;
* @return {number}
*/
proto.ConnectionEstablishedMessage.prototype.getId = function() {
return /** @type {number} */ (jspb.Message.getFieldProto3(this, 1, 0));
};
/** @param {number} value */
proto.ConnectionEstablishedMessage.prototype.setId = function(value) {
jspb.Message.setField(this, 1, value);
};
/**
* Generated by JsPbCodeGenerator.
* @param {Array=} opt_data Optional initial data array, typically from a
* server response, or constructed directly in Javascript. The array is used
* in place and becomes part of the constructed object. It is not cloned.
* If no data is provided, the constructed object will be empty, but still
* valid.
* @extends {jspb.Message}
* @constructor
*/
proto.NewConnectionFailureMessage = function(opt_data) {
jspb.Message.initialize(this, opt_data, 0, -1, null, null);
};
goog.inherits(proto.NewConnectionFailureMessage, jspb.Message);
if (goog.DEBUG && !COMPILED) {
proto.NewConnectionFailureMessage.displayName = 'proto.NewConnectionFailureMessage';
}
if (jspb.Message.GENERATE_TO_OBJECT) {
/**
* Creates an object representation of this proto suitable for use in Soy templates.
* Field names that are reserved in JavaScript and will be renamed to pb_name.
* To access a reserved field use, foo.pb_<name>, eg, foo.pb_default.
* For the list of reserved names please see:
* com.google.apps.jspb.JsClassTemplate.JS_RESERVED_WORDS.
* @param {boolean=} opt_includeInstance Whether to include the JSPB instance
* for transitional soy proto support: http://goto/soy-param-migration
* @return {!Object}
*/
proto.NewConnectionFailureMessage.prototype.toObject = function(opt_includeInstance) {
return proto.NewConnectionFailureMessage.toObject(opt_includeInstance, this);
};
/**
* Static version of the {@see toObject} method.
* @param {boolean|undefined} includeInstance Whether to include the JSPB
* instance for transitional soy proto support:
* http://goto/soy-param-migration
* @param {!proto.NewConnectionFailureMessage} msg The msg instance to transform.
* @return {!Object}
*/
proto.NewConnectionFailureMessage.toObject = function(includeInstance, msg) {
var f, obj = {
id: msg.getId(),
message: msg.getMessage()
};
if (includeInstance) {
obj.$jspbMessageInstance = msg;
}
return obj;
};
}
/**
* Deserializes binary data (in protobuf wire format).
* @param {jspb.ByteSource} bytes The bytes to deserialize.
* @return {!proto.NewConnectionFailureMessage}
*/
proto.NewConnectionFailureMessage.deserializeBinary = function(bytes) {
var reader = new jspb.BinaryReader(bytes);
var msg = new proto.NewConnectionFailureMessage;
return proto.NewConnectionFailureMessage.deserializeBinaryFromReader(msg, reader);
};
/**
* Deserializes binary data (in protobuf wire format) from the
* given reader into the given message object.
* @param {!proto.NewConnectionFailureMessage} msg The message object to deserialize into.
* @param {!jspb.BinaryReader} reader The BinaryReader to use.
* @return {!proto.NewConnectionFailureMessage}
*/
proto.NewConnectionFailureMessage.deserializeBinaryFromReader = function(msg, reader) {
while (reader.nextField()) {
if (reader.isEndGroup()) {
break;
}
var field = reader.getFieldNumber();
switch (field) {
case 1:
var value = /** @type {number} */ (reader.readUint64());
msg.setId(value);
break;
case 2:
var value = /** @type {string} */ (reader.readString());
msg.setMessage(value);
break;
default:
reader.skipField();
break;
}
}
return msg;
};
/**
* Class method variant: serializes the given message to binary data
* (in protobuf wire format), writing to the given BinaryWriter.
* @param {!proto.NewConnectionFailureMessage} message
* @param {!jspb.BinaryWriter} writer
*/
proto.NewConnectionFailureMessage.serializeBinaryToWriter = function(message, writer) {
message.serializeBinaryToWriter(writer);
};
/**
* Serializes the message to binary data (in protobuf wire format).
* @return {!Uint8Array}
*/
proto.NewConnectionFailureMessage.prototype.serializeBinary = function() {
var writer = new jspb.BinaryWriter();
this.serializeBinaryToWriter(writer);
return writer.getResultBuffer();
};
/**
* Serializes the message to binary data (in protobuf wire format),
* writing to the given BinaryWriter.
* @param {!jspb.BinaryWriter} writer
*/
proto.NewConnectionFailureMessage.prototype.serializeBinaryToWriter = function (writer) {
var f = undefined;
f = this.getId();
if (f !== 0) {
writer.writeUint64(
1,
f
);
}
f = this.getMessage();
if (f.length > 0) {
writer.writeString(
2,
f
);
}
};
/**
* Creates a deep clone of this proto. No data is shared with the original.
* @return {!proto.NewConnectionFailureMessage} The clone.
*/
proto.NewConnectionFailureMessage.prototype.cloneMessage = function() {
return /** @type {!proto.NewConnectionFailureMessage} */ (jspb.Message.cloneMessage(this));
};
/**
* optional uint64 id = 1;
* @return {number}
*/
proto.NewConnectionFailureMessage.prototype.getId = function() {
return /** @type {number} */ (jspb.Message.getFieldProto3(this, 1, 0));
};
/** @param {number} value */
proto.NewConnectionFailureMessage.prototype.setId = function(value) {
jspb.Message.setField(this, 1, value);
};
/**
* optional string message = 2;
* @return {string}
*/
proto.NewConnectionFailureMessage.prototype.getMessage = function() {
return /** @type {string} */ (jspb.Message.getFieldProto3(this, 2, ""));
};
/** @param {string} value */
proto.NewConnectionFailureMessage.prototype.setMessage = function(value) {
jspb.Message.setField(this, 2, value);
};
/**
* Generated by JsPbCodeGenerator.
* @param {Array=} opt_data Optional initial data array, typically from a
* server response, or constructed directly in Javascript. The array is used
* in place and becomes part of the constructed object. It is not cloned.
* If no data is provided, the constructed object will be empty, but still
* valid.
* @extends {jspb.Message}
* @constructor
*/
proto.ConnectionOutputMessage = function(opt_data) {
jspb.Message.initialize(this, opt_data, 0, -1, null, null);
};
goog.inherits(proto.ConnectionOutputMessage, jspb.Message);
if (goog.DEBUG && !COMPILED) {
proto.ConnectionOutputMessage.displayName = 'proto.ConnectionOutputMessage';
}
if (jspb.Message.GENERATE_TO_OBJECT) {
/**
* Creates an object representation of this proto suitable for use in Soy templates.
* Field names that are reserved in JavaScript and will be renamed to pb_name.
* To access a reserved field use, foo.pb_<name>, eg, foo.pb_default.
* For the list of reserved names please see:
* com.google.apps.jspb.JsClassTemplate.JS_RESERVED_WORDS.
* @param {boolean=} opt_includeInstance Whether to include the JSPB instance
* for transitional soy proto support: http://goto/soy-param-migration
* @return {!Object}
*/
proto.ConnectionOutputMessage.prototype.toObject = function(opt_includeInstance) {
return proto.ConnectionOutputMessage.toObject(opt_includeInstance, this);
};
/**
* Static version of the {@see toObject} method.
* @param {boolean|undefined} includeInstance Whether to include the JSPB
* instance for transitional soy proto support:
* http://goto/soy-param-migration
* @param {!proto.ConnectionOutputMessage} msg The msg instance to transform.
* @return {!Object}
*/
proto.ConnectionOutputMessage.toObject = function(includeInstance, msg) {
var f, obj = {
id: msg.getId(),
data: msg.getData_asB64()
};
if (includeInstance) {
obj.$jspbMessageInstance = msg;
}
return obj;
};
}
/**
* Deserializes binary data (in protobuf wire format).
* @param {jspb.ByteSource} bytes The bytes to deserialize.
* @return {!proto.ConnectionOutputMessage}
*/
proto.ConnectionOutputMessage.deserializeBinary = function(bytes) {
var reader = new jspb.BinaryReader(bytes);
var msg = new proto.ConnectionOutputMessage;
return proto.ConnectionOutputMessage.deserializeBinaryFromReader(msg, reader);
};
/**
* Deserializes binary data (in protobuf wire format) from the
* given reader into the given message object.
* @param {!proto.ConnectionOutputMessage} msg The message object to deserialize into.
* @param {!jspb.BinaryReader} reader The BinaryReader to use.
* @return {!proto.ConnectionOutputMessage}
*/
proto.ConnectionOutputMessage.deserializeBinaryFromReader = function(msg, reader) {
while (reader.nextField()) {
if (reader.isEndGroup()) {
break;
}
var field = reader.getFieldNumber();
switch (field) {
case 1:
var value = /** @type {number} */ (reader.readUint64());
msg.setId(value);
break;
case 2:
var value = /** @type {!Uint8Array} */ (reader.readBytes());
msg.setData(value);
break;
default:
reader.skipField();
break;
}
}
return msg;
};
/**
* Class method variant: serializes the given message to binary data
* (in protobuf wire format), writing to the given BinaryWriter.
* @param {!proto.ConnectionOutputMessage} message
* @param {!jspb.BinaryWriter} writer
*/
proto.ConnectionOutputMessage.serializeBinaryToWriter = function(message, writer) {
message.serializeBinaryToWriter(writer);
};
/**
* Serializes the message to binary data (in protobuf wire format).
* @return {!Uint8Array}
*/
proto.ConnectionOutputMessage.prototype.serializeBinary = function() {
var writer = new jspb.BinaryWriter();
this.serializeBinaryToWriter(writer);
return writer.getResultBuffer();
};
/**
* Serializes the message to binary data (in protobuf wire format),
* writing to the given BinaryWriter.
* @param {!jspb.BinaryWriter} writer
*/
proto.ConnectionOutputMessage.prototype.serializeBinaryToWriter = function (writer) {
var f = undefined;
f = this.getId();
if (f !== 0) {
writer.writeUint64(
1,
f
);
}
f = this.getData_asU8();
if (f.length > 0) {
writer.writeBytes(
2,
f
);
}
};
/**
* Creates a deep clone of this proto. No data is shared with the original.
* @return {!proto.ConnectionOutputMessage} The clone.
*/
proto.ConnectionOutputMessage.prototype.cloneMessage = function() {
return /** @type {!proto.ConnectionOutputMessage} */ (jspb.Message.cloneMessage(this));
};
/**
* optional uint64 id = 1;
* @return {number}
*/
proto.ConnectionOutputMessage.prototype.getId = function() {
return /** @type {number} */ (jspb.Message.getFieldProto3(this, 1, 0));
};
/** @param {number} value */
proto.ConnectionOutputMessage.prototype.setId = function(value) {
jspb.Message.setField(this, 1, value);
};
/**
* optional bytes data = 2;
* @return {!(string|Uint8Array)}
*/
proto.ConnectionOutputMessage.prototype.getData = function() {
return /** @type {!(string|Uint8Array)} */ (jspb.Message.getFieldProto3(this, 2, ""));
};
/**
* optional bytes data = 2;
* This is a type-conversion wrapper around `getData()`
* @return {string}
*/
proto.ConnectionOutputMessage.prototype.getData_asB64 = function() {
return /** @type {string} */ (jspb.Message.bytesAsB64(
this.getData()));
};
/**
* optional bytes data = 2;
* Note that Uint8Array is not supported on all browsers.
* @see http://caniuse.com/Uint8Array
* This is a type-conversion wrapper around `getData()`
* @return {!Uint8Array}
*/
proto.ConnectionOutputMessage.prototype.getData_asU8 = function() {
return /** @type {!Uint8Array} */ (jspb.Message.bytesAsU8(
this.getData()));
};
/** @param {!(string|Uint8Array)} value */
proto.ConnectionOutputMessage.prototype.setData = function(value) {
jspb.Message.setField(this, 2, value);
};
/**
* Generated by JsPbCodeGenerator.
* @param {Array=} opt_data Optional initial data array, typically from a
* server response, or constructed directly in Javascript. The array is used
* in place and becomes part of the constructed object. It is not cloned.
* If no data is provided, the constructed object will be empty, but still
* valid.
* @extends {jspb.Message}
* @constructor
*/
proto.ConnectionCloseMessage = function(opt_data) {
jspb.Message.initialize(this, opt_data, 0, -1, null, null);
};
goog.inherits(proto.ConnectionCloseMessage, jspb.Message);
if (goog.DEBUG && !COMPILED) {
proto.ConnectionCloseMessage.displayName = 'proto.ConnectionCloseMessage';
}
if (jspb.Message.GENERATE_TO_OBJECT) {
/**
* Creates an object representation of this proto suitable for use in Soy templates.
* Field names that are reserved in JavaScript and will be renamed to pb_name.
* To access a reserved field use, foo.pb_<name>, eg, foo.pb_default.
* For the list of reserved names please see:
* com.google.apps.jspb.JsClassTemplate.JS_RESERVED_WORDS.
* @param {boolean=} opt_includeInstance Whether to include the JSPB instance
* for transitional soy proto support: http://goto/soy-param-migration
* @return {!Object}
*/
proto.ConnectionCloseMessage.prototype.toObject = function(opt_includeInstance) {
return proto.ConnectionCloseMessage.toObject(opt_includeInstance, this);
};
/**
* Static version of the {@see toObject} method.
* @param {boolean|undefined} includeInstance Whether to include the JSPB
* instance for transitional soy proto support:
* http://goto/soy-param-migration
* @param {!proto.ConnectionCloseMessage} msg The msg instance to transform.
* @return {!Object}
*/
proto.ConnectionCloseMessage.toObject = function(includeInstance, msg) {
var f, obj = {
id: msg.getId()
};
if (includeInstance) {
obj.$jspbMessageInstance = msg;
}
return obj;
};
}
/**
* Deserializes binary data (in protobuf wire format).
* @param {jspb.ByteSource} bytes The bytes to deserialize.
* @return {!proto.ConnectionCloseMessage}
*/
proto.ConnectionCloseMessage.deserializeBinary = function(bytes) {
var reader = new jspb.BinaryReader(bytes);
var msg = new proto.ConnectionCloseMessage;
return proto.ConnectionCloseMessage.deserializeBinaryFromReader(msg, reader);
};
/**
* Deserializes binary data (in protobuf wire format) from the
* given reader into the given message object.
* @param {!proto.ConnectionCloseMessage} msg The message object to deserialize into.
* @param {!jspb.BinaryReader} reader The BinaryReader to use.
* @return {!proto.ConnectionCloseMessage}
*/
proto.ConnectionCloseMessage.deserializeBinaryFromReader = function(msg, reader) {
while (reader.nextField()) {
if (reader.isEndGroup()) {
break;
}
var field = reader.getFieldNumber();
switch (field) {
case 1:
var value = /** @type {number} */ (reader.readUint64());
msg.setId(value);
break;
default:
reader.skipField();
break;
}
}
return msg;
};
/**
* Class method variant: serializes the given message to binary data
* (in protobuf wire format), writing to the given BinaryWriter.
* @param {!proto.ConnectionCloseMessage} message
* @param {!jspb.BinaryWriter} writer
*/
proto.ConnectionCloseMessage.serializeBinaryToWriter = function(message, writer) {
message.serializeBinaryToWriter(writer);
};
/**
* Serializes the message to binary data (in protobuf wire format).
* @return {!Uint8Array}
*/
proto.ConnectionCloseMessage.prototype.serializeBinary = function() {
var writer = new jspb.BinaryWriter();
this.serializeBinaryToWriter(writer);
return writer.getResultBuffer();
};
/**
* Serializes the message to binary data (in protobuf wire format),
* writing to the given BinaryWriter.
* @param {!jspb.BinaryWriter} writer
*/
proto.ConnectionCloseMessage.prototype.serializeBinaryToWriter = function (writer) {
var f = undefined;
f = this.getId();
if (f !== 0) {
writer.writeUint64(
1,
f
);
}
};
/**
* Creates a deep clone of this proto. No data is shared with the original.
* @return {!proto.ConnectionCloseMessage} The clone.
*/
proto.ConnectionCloseMessage.prototype.cloneMessage = function() {
return /** @type {!proto.ConnectionCloseMessage} */ (jspb.Message.cloneMessage(this));
};
/**
* optional uint64 id = 1;
* @return {number}
*/
proto.ConnectionCloseMessage.prototype.getId = function() {
return /** @type {number} */ (jspb.Message.getFieldProto3(this, 1, 0));
};
/** @param {number} value */
proto.ConnectionCloseMessage.prototype.setId = function(value) {
jspb.Message.setField(this, 1, value);
};
goog.object.extend(exports, proto);

View File

@ -1,3 +1,4 @@
export * from "./client_pb";
export * from "./command_pb";
export * from "./node_pb";
export * from "./vscode_pb";

View File

@ -0,0 +1,9 @@
syntax = "proto3";
message SharedProcessInitMessage {
uint64 window_id = 1;
string log_directory = 2;
// Maps to `"vs/platform/log/common/log".LogLevel`
uint32 log_level = 3;
}

View File

@ -0,0 +1,33 @@
// 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,
}
}

View File

@ -0,0 +1,226 @@
/**
* @fileoverview
* @enhanceable
* @public
*/
// GENERATED CODE -- DO NOT EDIT!
var jspb = require('google-protobuf');
var goog = jspb;
var global = Function('return this')();
goog.exportSymbol('proto.SharedProcessInitMessage', null, global);
/**
* Generated by JsPbCodeGenerator.
* @param {Array=} opt_data Optional initial data array, typically from a
* server response, or constructed directly in Javascript. The array is used
* in place and becomes part of the constructed object. It is not cloned.
* If no data is provided, the constructed object will be empty, but still
* valid.
* @extends {jspb.Message}
* @constructor
*/
proto.SharedProcessInitMessage = function(opt_data) {
jspb.Message.initialize(this, opt_data, 0, -1, null, null);
};
goog.inherits(proto.SharedProcessInitMessage, jspb.Message);
if (goog.DEBUG && !COMPILED) {
proto.SharedProcessInitMessage.displayName = 'proto.SharedProcessInitMessage';
}
if (jspb.Message.GENERATE_TO_OBJECT) {
/**
* Creates an object representation of this proto suitable for use in Soy templates.
* Field names that are reserved in JavaScript and will be renamed to pb_name.
* To access a reserved field use, foo.pb_<name>, eg, foo.pb_default.
* For the list of reserved names please see:
* com.google.apps.jspb.JsClassTemplate.JS_RESERVED_WORDS.
* @param {boolean=} opt_includeInstance Whether to include the JSPB instance
* for transitional soy proto support: http://goto/soy-param-migration
* @return {!Object}
*/
proto.SharedProcessInitMessage.prototype.toObject = function(opt_includeInstance) {
return proto.SharedProcessInitMessage.toObject(opt_includeInstance, this);
};
/**
* Static version of the {@see toObject} method.
* @param {boolean|undefined} includeInstance Whether to include the JSPB
* instance for transitional soy proto support:
* http://goto/soy-param-migration
* @param {!proto.SharedProcessInitMessage} msg The msg instance to transform.
* @return {!Object}
*/
proto.SharedProcessInitMessage.toObject = function(includeInstance, msg) {
var f, obj = {
windowId: msg.getWindowId(),
logDirectory: msg.getLogDirectory(),
logLevel: msg.getLogLevel()
};
if (includeInstance) {
obj.$jspbMessageInstance = msg;
}
return obj;
};
}
/**
* Deserializes binary data (in protobuf wire format).
* @param {jspb.ByteSource} bytes The bytes to deserialize.
* @return {!proto.SharedProcessInitMessage}
*/
proto.SharedProcessInitMessage.deserializeBinary = function(bytes) {
var reader = new jspb.BinaryReader(bytes);
var msg = new proto.SharedProcessInitMessage;
return proto.SharedProcessInitMessage.deserializeBinaryFromReader(msg, reader);
};
/**
* Deserializes binary data (in protobuf wire format) from the
* given reader into the given message object.
* @param {!proto.SharedProcessInitMessage} msg The message object to deserialize into.
* @param {!jspb.BinaryReader} reader The BinaryReader to use.
* @return {!proto.SharedProcessInitMessage}
*/
proto.SharedProcessInitMessage.deserializeBinaryFromReader = function(msg, reader) {
while (reader.nextField()) {
if (reader.isEndGroup()) {
break;
}
var field = reader.getFieldNumber();
switch (field) {
case 1:
var value = /** @type {number} */ (reader.readUint64());
msg.setWindowId(value);
break;
case 2:
var value = /** @type {string} */ (reader.readString());
msg.setLogDirectory(value);
break;
case 3:
var value = /** @type {number} */ (reader.readUint32());
msg.setLogLevel(value);
break;
default:
reader.skipField();
break;
}
}
return msg;
};
/**
* Class method variant: serializes the given message to binary data
* (in protobuf wire format), writing to the given BinaryWriter.
* @param {!proto.SharedProcessInitMessage} message
* @param {!jspb.BinaryWriter} writer
*/
proto.SharedProcessInitMessage.serializeBinaryToWriter = function(message, writer) {
message.serializeBinaryToWriter(writer);
};
/**
* Serializes the message to binary data (in protobuf wire format).
* @return {!Uint8Array}
*/
proto.SharedProcessInitMessage.prototype.serializeBinary = function() {
var writer = new jspb.BinaryWriter();
this.serializeBinaryToWriter(writer);
return writer.getResultBuffer();
};
/**
* Serializes the message to binary data (in protobuf wire format),
* writing to the given BinaryWriter.
* @param {!jspb.BinaryWriter} writer
*/
proto.SharedProcessInitMessage.prototype.serializeBinaryToWriter = function (writer) {
var f = undefined;
f = this.getWindowId();
if (f !== 0) {
writer.writeUint64(
1,
f
);
}
f = this.getLogDirectory();
if (f.length > 0) {
writer.writeString(
2,
f
);
}
f = this.getLogLevel();
if (f !== 0) {
writer.writeUint32(
3,
f
);
}
};
/**
* Creates a deep clone of this proto. No data is shared with the original.
* @return {!proto.SharedProcessInitMessage} The clone.
*/
proto.SharedProcessInitMessage.prototype.cloneMessage = function() {
return /** @type {!proto.SharedProcessInitMessage} */ (jspb.Message.cloneMessage(this));
};
/**
* optional uint64 window_id = 1;
* @return {number}
*/
proto.SharedProcessInitMessage.prototype.getWindowId = function() {
return /** @type {number} */ (jspb.Message.getFieldProto3(this, 1, 0));
};
/** @param {number} value */
proto.SharedProcessInitMessage.prototype.setWindowId = function(value) {
jspb.Message.setField(this, 1, value);
};
/**
* optional string log_directory = 2;
* @return {string}
*/
proto.SharedProcessInitMessage.prototype.getLogDirectory = function() {
return /** @type {string} */ (jspb.Message.getFieldProto3(this, 2, ""));
};
/** @param {string} value */
proto.SharedProcessInitMessage.prototype.setLogDirectory = function(value) {
jspb.Message.setField(this, 2, value);
};
/**
* optional uint32 log_level = 3;
* @return {number}
*/
proto.SharedProcessInitMessage.prototype.getLogLevel = function() {
return /** @type {number} */ (jspb.Message.getFieldProto3(this, 3, 0));
};
/** @param {number} value */
proto.SharedProcessInitMessage.prototype.setLogLevel = function(value) {
jspb.Message.setField(this, 3, value);
};
goog.object.extend(exports, proto);

View File

@ -1,3 +1,5 @@
import * as net from "net";
import * as os from "os";
import * as path from "path";
import { TextEncoder, TextDecoder } from "text-encoding";
import { createClient } from "./helpers";
@ -5,7 +7,7 @@ import { createClient } from "./helpers";
(<any>global).TextDecoder = TextDecoder;
(<any>global).TextEncoder = TextEncoder;
describe("Command", () => {
describe("spawn", () => {
const client = createClient();
it("should execute command and return output", (done) => {
@ -130,3 +132,59 @@ describe("Command", () => {
proc.on("exit", () => done());
});
});
describe("createConnection", () => {
const client = createClient();
const tmpPath = path.join(os.tmpdir(), Math.random().toString());
let server: net.Server;
beforeAll(async () => {
await new Promise((r) => {
server = net.createServer().listen(tmpPath, () => {
r();
});
});
});
afterAll(() => {
server.close();
});
it("should connect to socket", (done) => {
const socket = client.createConnection(tmpPath, () => {
socket.end();
socket.addListener("close", () => {
done();
});
});
});
it("should get data from server", (done) => {
server.once("connection", (socket: net.Socket) => {
socket.write("hi how r u");
});
const socket = client.createConnection(tmpPath);
socket.addListener("data", (data) => {
expect(data.toString()).toEqual("hi how r u");
socket.end();
socket.addListener("close", () => {
done();
});
});
});
it("should send data to server", (done) => {
const clientSocket = client.createConnection(tmpPath);
clientSocket.write(Buffer.from("bananas"));
server.once("connection", (socket: net.Socket) => {
socket.addListener("data", (data) => {
expect(data.toString()).toEqual("bananas");
socket.end();
clientSocket.addListener("end", () => {
done();
});
});
});
});
});

View File

@ -9,7 +9,7 @@ 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();

View File

@ -1,5 +1,6 @@
out
cli*
build
# This file is generated when the binary is created.
# We want to use the parent tsconfig so we can ignore it.

View File

@ -4,10 +4,12 @@
"bin": "./out/cli.js",
"files": [],
"scripts": {
"start": "ts-node -r tsconfig-paths/register src/cli.ts",
"build:webpack": "rm -rf ./out && ../../node_modules/.bin/webpack --config ./webpack.config.js",
"start": "NODE_ENV=development ts-node -r tsconfig-paths/register src/cli.ts",
"build:webpack": "rm -rf ./out && export CLI=true && ../../node_modules/.bin/webpack --config ./webpack.config.js",
"build:nexe": "node scripts/nexe.js",
"build": "npm run build:webpack && npm run build:nexe"
"build:bootstrap-fork": "cd ../vscode && npm run build:bootstrap-fork; cp ./bin/bootstrap-fork.js ../server/build/bootstrap-fork.js",
"build:default-extensions": "cd ../../lib/vscode && npx gulp vscode-linux-arm && cd ../.. && cp -r ./lib/VSCode-linux-arm/resources/app/extensions/* ./packages/server/build/extensions/",
"build": "npm run build:bootstrap-fork && npm run build:webpack && npm run build:nexe"
},
"dependencies": {
"@oclif/config": "^1.10.4",
@ -16,6 +18,7 @@
"express": "^4.16.4",
"nexe": "^2.0.0-rc.34",
"node-pty": "^0.8.0",
"spdlog": "^0.7.2",
"ws": "^6.1.2"
},
"devDependencies": {
@ -23,6 +26,7 @@
"@types/ws": "^6.0.1",
"string-replace-webpack-plugin": "^0.1.3",
"ts-node": "^7.0.1",
"tsconfig-paths": "^3.7.0"
"tsconfig-paths": "^3.7.0",
"typescript": "^3.2.2"
}
}

View File

@ -28,14 +28,22 @@ nexe.compile({
additionalFiles: [
'./node_modules/node-pty/build/Release/pty',
],
}
},
"spdlog": {
additionalFiles: [
'spdlog.node',
],
},
},
targets: ["linux"],
/**
* To include native extensions, do NOT install node_modules for each one. They
* are not required as each extension is built using webpack.
*/
resources: [path.join(__dirname, "../package.json")],
resources: [
path.join(__dirname, "../package.json"),
path.join(__dirname, "../build/**"),
],
});
/**

View File

@ -1,9 +1,12 @@
import { SharedProcessInitMessage } from "@coder/protocol/src/proto";
import { Command, flags } from "@oclif/command";
import { logger, field } from "@coder/logger";
import * as fs from "fs";
import * as os from "os";
import * as path from "path";
import { createApp } from './server';
import { requireModule } from "./vscode/bootstrapFork";
import { createApp } from "./server";
import { SharedProcess } from './vscode/sharedProcess';
export class Entry extends Command {
@ -17,23 +20,65 @@ export class Entry extends Command {
open: flags.boolean({ char: "o", description: "Open in browser on startup" }),
port: flags.integer({ char: "p", default: 8080, description: "Port to bind on" }),
version: flags.version({ char: "v" }),
// Dev flags
"bootstrap-fork": flags.string({ hidden: true }),
};
public static args = [{
name: "workdir",
description: "Specify working dir",
default: () => process.cwd(),
default: (): string => process.cwd(),
}];
public async run(): Promise<void> {
try {
/**
* Suuuper janky
* Comes from - https://github.com/nexe/nexe/issues/524
* Seems to cleanup by removing this path immediately
* If any native module is added its assumed this pathname
* will change.
*/
require("spdlog");
const nodePath = path.join(process.cwd(), "e91a410b");
fs.unlinkSync(path.join(nodePath, "spdlog.node"));
fs.rmdirSync(nodePath);
} catch (ex) {
logger.warn("Failed to remove extracted dependency.", field("dependency", "spdlog"), field("error", ex.message));
}
const { args, flags } = this.parse(Entry);
const dataDir = flags["data-dir"] || path.join(os.homedir(), `.vscode-online`);
if (flags["bootstrap-fork"]) {
const modulePath = flags["bootstrap-fork"];
if (!modulePath) {
logger.error("No module path specified to fork!");
process.exit(1);
}
requireModule(modulePath);
return;
}
const dataDir = flags["data-dir"] || path.join(os.homedir(), ".vscode-online");
const workingDir = args["workdir"];
logger.info("\u001B[1mvscode-remote v1.0.0");
// TODO: fill in appropriate doc url
logger.info("Additional documentation: https://coder.com/docs");
logger.info("Initializing", field("data-dir", dataDir), field("working-dir", workingDir));
const sharedProcess = new SharedProcess(dataDir);
logger.info("Starting shared process...", field("socket", sharedProcess.socketPath));
sharedProcess.onWillRestart(() => {
logger.info("Restarting shared process...");
sharedProcess.ready.then(() => {
logger.info("Shared process has restarted!");
});
});
sharedProcess.ready.then(() => {
logger.info("Shared process has started up!");
});
const app = createApp((app) => {
app.use((req, res, next) => {
@ -43,17 +88,33 @@ export class Entry extends Command {
next();
});
if (process.env.CLI === "false" || !process.env.CLI) {
const webpackConfig = require(path.join(__dirname, "..", "..", "web", "webpack.dev.config.js"));
const compiler = require("webpack")(webpackConfig);
app.use(require("webpack-dev-middleware")(compiler, {
logger,
publicPath: webpackConfig.output.publicPath,
stats: webpackConfig.stats,
}));
app.use(require("webpack-hot-middleware")(compiler));
}
}, {
dataDirectory: dataDir,
workingDirectory: workingDir,
});
logger.info("Starting webserver...", field("host", flags.host), field("port", flags.port))
logger.info("Starting webserver...", field("host", flags.host), field("port", flags.port));
app.server.listen(flags.port, flags.host);
let clientId = 1;
app.wss.on("connection", (ws, req) => {
const id = clientId++;
logger.info(`WebSocket opened \u001B[0m${req.url}`, field("client", id), field("ip", req.socket.remoteAddress));
const spm = (<any>req).sharedProcessInit as SharedProcessInitMessage;
if (!spm) {
logger.warn("Received a socket without init data. Not sure how this happened.");
return;
}
logger.info(`WebSocket opened \u001B[0m${req.url}`, field("client", id), field("ip", req.socket.remoteAddress), field("window_id", spm.getWindowId()), field("log_directory", spm.getLogDirectory()));
ws.on("close", (code) => {
logger.info(`WebSocket closed \u001B[0m${req.url}`, field("client", id), field("code", code));
@ -73,9 +134,10 @@ export class Entry extends Command {
logger.info(`http://localhost:${flags.port}/`);
logger.info(" ");
}
}
Entry.run(undefined, {
root: process.env.BUILD_DIR as string,
root: process.env.BUILD_DIR as string || __dirname,
//@ts-ignore
}).catch(require("@oclif/errors/handle"));

View File

@ -0,0 +1,70 @@
import { EventEmitter } from "events";
import { ChildProcess } from "child_process";
export interface IpcMessage {
readonly event: string;
readonly args: any[];
}
export class StdioIpcHandler extends EventEmitter {
private isListening: boolean = false;
public constructor(
private readonly childProcess?: ChildProcess,
) {
super();
}
public on(event: string, cb: (...args: any[]) => void): this {
this.listen();
return super.on(event, cb);
}
public once(event: string, cb: (...args: any[]) => void): this {
this.listen();
return super.once(event, cb);
}
public addListener(event: string, cb: (...args: any[]) => void): this {
this.listen();
return super.addListener(event, cb);
}
public send(event: string, ...args: any[]): void {
const msg: IpcMessage = {
event,
args,
};
const d = JSON.stringify(msg);
if (this.childProcess) {
this.childProcess.stdin.write(d + "\n");
} else {
process.stdout.write(d);
}
}
private listen(): void {
if (this.isListening) {
return;
}
const onData = (data: any) => {
try {
const d = JSON.parse(data.toString()) as IpcMessage;
this.emit(d.event, ...d.args);
} catch (ex) {
if (!this.childProcess) {
process.stderr.write(`Failed to parse incoming data: ${ex.message}`);
}
}
};
if (this.childProcess) {
this.childProcess.stdout.resume();
this.childProcess.stdout.on("data", onData);
} else {
process.stdin.resume();
process.stdin.on("data", onData);
}
}
}

View File

@ -3,6 +3,8 @@ import { Server, ServerOptions } from "@coder/protocol/src/node/server";
import * as express from "express";
import * as http from "http";
import * as ws from "ws";
import * as url from "url";
import { ClientMessage, SharedProcessInitMessage } from '@coder/protocol/src/proto';
export const createApp = (registerMiddleware?: (app: express.Application) => void, options?: ServerOptions): {
readonly express: express.Application;
@ -16,21 +18,52 @@ export const createApp = (registerMiddleware?: (app: express.Application) => voi
const server = http.createServer(app);
const wss = new ws.Server({ server });
wss.on("connection", (ws: WebSocket) => {
wss.shouldHandle = (req): boolean => {
if (typeof req.url === "undefined") {
return false;
}
const parsedUrl = url.parse(req.url, true);
const sharedProcessInit = parsedUrl.query["shared_process_init"];
if (typeof sharedProcessInit === "undefined" || Array.isArray(sharedProcessInit)) {
return false;
}
try {
const msg = ClientMessage.deserializeBinary(Buffer.from(sharedProcessInit, "base64"));
if (!msg.hasSharedProcessInit()) {
return false;
}
const spm = msg.getSharedProcessInit()!;
(<any>req).sharedProcessInit = spm;
} catch (ex) {
return false;
}
return true;
};
wss.on("connection", (ws: WebSocket, req) => {
const spm = (<any>req).sharedProcessInit as SharedProcessInitMessage;
if (!spm) {
ws.close();
return;
}
const connection: ReadWriteConnection = {
onMessage: (cb) => {
onMessage: (cb): void => {
ws.addEventListener("message", (event) => cb(event.data));
},
close: () => ws.close(),
send: (data) => ws.send(data),
onClose: (cb) => ws.addEventListener("close", () => cb()),
close: (): void => ws.close(),
send: (data): void => ws.send(data),
onClose: (cb): void => ws.addEventListener("close", () => cb()),
};
const server = new Server(connection, options);
});
/**
* We should static-serve the `web` package at this point
* We should static-serve the `web` package at this point.
*/
app.get("/", (req, res, next) => {
res.write("Example! :)");

View File

@ -0,0 +1,29 @@
import * as cp from "child_process";
import * as fs from "fs";
import * as path from "path";
declare var __non_webpack_require__: typeof require;
export const requireModule = (modulePath: string): void => {
process.env.AMD_ENTRYPOINT = modulePath;
process.env.VSCODE_ALLOW_IO = "true";
const content = fs.readFileSync(path.join(process.env.BUILD_DIR as string || path.join(__dirname, "../.."), "./build/bootstrap-fork.js"));
eval(content.toString());
};
/**
* Uses the internal bootstrap-fork.js to load a module
* @example
* const cp = forkModule("vs/code/electron-browser/sharedProcess/sharedProcessMain");
* cp.stdout.on("data", (data) => console.log(data.toString("utf8")));
* cp.stderr.on("data", (data) => console.log(data.toString("utf8")));
* @param modulePath
*/
export const forkModule = (modulePath: string): cp.ChildProcess => {
const args = ["--bootstrap-fork", modulePath];
if (process.env.CLI === "true") {
return cp.spawn(process.execPath, args);
} else {
return cp.spawn("npm", ["start", "--scripts-prepend-node-path", "--", ...args]);
}
};

View File

@ -0,0 +1,106 @@
import { ChildProcess } from "child_process";
import * as fs from "fs";
import * as os from "os";
import * as path from "path";
import { forkModule } from "./bootstrapFork";
import { StdioIpcHandler } from "../ipc";
import { logger, field } from "@coder/logger/src";
import { ParsedArgs } from "vs/platform/environment/common/environment";
import { LogLevel } from "vs/platform/log/common/log";
import { Emitter, Event } from '@coder/events/src';
export class SharedProcess {
public readonly socketPath: string = path.join(os.tmpdir(), `.vscode-online${Math.random().toString()}`);
private _ready: Promise<void> | undefined;
private activeProcess: ChildProcess | undefined;
private ipcHandler: StdioIpcHandler | undefined;
private readonly willRestartEmitter: Emitter<void>;
public constructor(
private readonly userDataDir: string,
) {
this.willRestartEmitter = new Emitter();
this.restart();
}
public get onWillRestart(): Event<void> {
return this.willRestartEmitter.event;
}
public get ready(): Promise<void> {
return this._ready!;
}
public restart(): void {
if (this.activeProcess) {
this.willRestartEmitter.emit();
}
if (this.activeProcess && !this.activeProcess.killed) {
this.activeProcess.kill();
}
let resolve: () => void;
let reject: (err: Error) => void;
const extensionsDir = path.join(this.userDataDir, "extensions");
const mkdir = (dir: string): void => {
try {
fs.mkdirSync(dir);
} catch (ex) {
if (ex.code !== "EEXIST") {
throw ex;
}
}
};
mkdir(this.userDataDir);
mkdir(extensionsDir);
this._ready = new Promise<void>((res, rej) => {
resolve = res;
reject = rej;
});
let resolved: boolean = false;
this.activeProcess = forkModule("vs/code/electron-browser/sharedProcess/sharedProcessMain");
this.activeProcess.on("exit", () => {
this.restart();
});
this.ipcHandler = new StdioIpcHandler(this.activeProcess);
this.ipcHandler.once("handshake:hello", () => {
const data: {
sharedIPCHandle: string;
args: ParsedArgs;
logLevel: LogLevel;
} = {
args: {
"builtin-extensions-dir": path.join(process.env.BUILD_DIR || path.join(__dirname, "../.."), "build/extensions"),
"user-data-dir": this.userDataDir,
"extensions-dir": extensionsDir,
} as any,
logLevel: 0,
sharedIPCHandle: this.socketPath,
};
this.ipcHandler!.send("handshake:hey there", "", data);
});
this.ipcHandler.once("handshake:im ready", () => {
resolved = true;
resolve();
});
this.activeProcess.stderr.on("data", (data) => {
if (!resolved) {
reject(data.toString());
} else {
logger.named("SHRD PROC").debug("stderr", field("message", data.toString()));
}
});
}
public dispose(): void {
if (this.ipcHandler) {
this.ipcHandler.send("handshake:goodbye");
}
this.ipcHandler = undefined;
}
}

View File

@ -2,9 +2,10 @@ const path = require("path");
const webpack = require("webpack");
const merge = require("webpack-merge");
const StringReplacePlugin = require("string-replace-webpack-plugin");
const HappyPack = require("happypack");
module.exports = merge({
devtool: 'none',
devtool: "none",
module: {
rules: [
{
@ -35,14 +36,15 @@ module.exports = merge({
__dirname: false,
setImmediate: false
},
externals: ["node-pty"],
externals: ["node-pty", "spdlog"],
entry: "./packages/server/src/cli.ts",
target: "node",
plugins: [
new webpack.DefinePlugin({
"process.env.BUILD_DIR": `"${__dirname}"`,
"process.env.CLI": `"${process.env.CLI ? "true" : "false"}"`,
}),
],
}, require("../../scripts/webpack.general.config"), {
}, require("../../scripts/webpack.general.config")(), {
mode: "development",
});

View File

@ -418,6 +418,11 @@ binary-extensions@^1.0.0:
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.12.0.tgz#c2d780f53d45bba8317a8902d4ceeaf3a6385b14"
integrity sha512-DYWGk01lDcxeS/K9IHPGWfT8PsJmbXRtRd2Sx72Tnb8pcYZQFF1oSDb8hJtS1vhp212q1Rzi5dUf9+nq0o9UIg==
bindings@^1.3.0:
version "1.3.1"
resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.3.1.tgz#21fc7c6d67c18516ec5aaa2815b145ff77b26ea5"
integrity sha512-i47mqjF9UbjxJhxGf+pZ6kSxrnI3wBLlnGI2ArWJ4r0VrvDS7ZYXkprq/pLaBWYq4GM0r4zdHY+NNRqEMU7uew==
bl@^1.0.0:
version "1.2.2"
resolved "https://registry.yarnpkg.com/bl/-/bl-1.2.2.tgz#a160911717103c07410cef63ef51b397c025af9c"
@ -1386,9 +1391,9 @@ fs.realpath@^1.0.0:
integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8=
fsevents@^1.0.0:
version "1.2.4"
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.4.tgz#f41dcb1af2582af3692da36fc55cbd8e1041c426"
integrity sha512-z8H8/diyk76B7q5wg+Ud0+CqzcAF3mBBI/bA5ne5zrRUUIvNkJY//D3BqyH571KuAC4Nr7Rw7CjWX4r0y9DvNg==
version "1.2.7"
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.7.tgz#4851b664a3783e52003b3c66eb0eee1074933aa4"
integrity sha512-Pxm6sI2MeBD7RdD12RYsqaP0nMiwx8eZBXCa6z2L+mRHm2DYrOYwihmhjpkdjUHwQhslWQjRpEgNq4XvBmaAuw==
dependencies:
nan "^2.9.2"
node-pre-gyp "^0.10.0"
@ -2152,9 +2157,9 @@ map-visit@^1.0.0:
object-visit "^1.0.0"
math-random@^1.0.1:
version "1.0.3"
resolved "https://registry.yarnpkg.com/math-random/-/math-random-1.0.3.tgz#5843d8307f8d2fd83de240701eeb2dc7bc77a104"
integrity sha512-hULdrPg17lCyaJOrDwS4RSGQcc/MFyv1aujidohCsBq2zotkhIns8mMDQ7B8VnKG23xcpa+haU5MLDNyNzCesQ==
version "1.0.4"
resolved "https://registry.yarnpkg.com/math-random/-/math-random-1.0.4.tgz#5dd6943c938548267016d4e34f057583080c514c"
integrity sha512-rUxjysqif/BZQH2yhd5Aaq7vXMSx9NdEsQcyA07uEzIvxgI7zIr33gGsh+RU0/XjmQpCW7RsVof1vlkvQVCK5A==
media-typer@0.3.0:
version "0.3.0"
@ -2313,7 +2318,7 @@ nan@2.10.0:
resolved "https://registry.yarnpkg.com/nan/-/nan-2.10.0.tgz#96d0cd610ebd58d4b4de9cc0c6828cda99c7548f"
integrity sha512-bAdJv7fBLhWC+/Bls0Oza+mvTaNQtP+1RyhhhvD95pgUJz6XM5IzgmxOkItJ9tkoCiplvAnXI1tNmmUD/eScyA==
nan@^2.9.2:
nan@^2.8.0, nan@^2.9.2:
version "2.12.1"
resolved "https://registry.yarnpkg.com/nan/-/nan-2.12.1.tgz#7b1aa193e9aa86057e3c7bbd0ac448e770925552"
integrity sha512-JY7V6lRkStKcKTvHO5NVSQRv+RV+FIL5pvDoLiAtSL9pKlC5x9PKQcZDsq7m4FO4d57mkhC6Z+QhAh3Jdk5JFw==
@ -3169,6 +3174,15 @@ source-map@~0.1.38:
dependencies:
amdefine ">=0.0.4"
spdlog@^0.7.2:
version "0.7.2"
resolved "https://registry.yarnpkg.com/spdlog/-/spdlog-0.7.2.tgz#9298753d7694b9ee9bbfd7e01ea1e4c6ace1e64d"
integrity sha512-rHfWCaWMD4NindDnql6rc6kn7Bs8JR92jhiUpCl3D6v+jYcQ6GozMLig0RliOOR8st5mU+IHLZnr15fBys5x/Q==
dependencies:
bindings "^1.3.0"
mkdirp "^0.5.1"
nan "^2.8.0"
split-string@^3.0.1, split-string@^3.0.2:
version "3.1.0"
resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2"
@ -3464,6 +3478,11 @@ typescript@2.5.3:
resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.5.3.tgz#df3dcdc38f3beb800d4bc322646b04a3f6ca7f0d"
integrity sha512-ptLSQs2S4QuS6/OD1eAKG+S5G8QQtrU5RT32JULdZQtM1L3WTi34Wsu48Yndzi8xsObRAB9RPt/KhA9wlpEF6w==
typescript@^3.2.2:
version "3.2.2"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.2.2.tgz#fe8101c46aa123f8353523ebdcf5730c2ae493e5"
integrity sha512-VCj5UiSyHBjwfYacmDuc/NOk4QQixbE+Wn7MFJuS0nRuPQbof132Pw4u53dm264O8LPc2MVsc7RJNml5szurkg==
uglify-es@^3.3.9:
version "3.3.9"
resolved "https://registry.yarnpkg.com/uglify-es/-/uglify-es-3.3.9.tgz#0c1c4f0700bed8dbc124cdb304d2592ca203e677"

1
packages/vscode/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
bin

View File

@ -1,5 +1,12 @@
{
"name": "@coder/vscode",
"description": "VS Code implementation of the browser-based IDE client.",
"main": "src/index.ts"
"main": "src/index.ts",
"scripts": {
"build:bootstrap-fork": "../../node_modules/.bin/webpack --config ./webpack.config.bootstrap.js"
},
"dependencies": {
"spdlog": "^0.7.2",
"string-replace-loader": "^2.1.1"
}
}

View File

@ -0,0 +1,133 @@
import "./fill/require";
import "./fill/storageDatabase";
import "./fill/windowsService";
import { fork } from "child_process";
import { Client as IDEClient, IURI, IURIFactory } from "@coder/ide";
import { logger } from "@coder/logger";
import { registerContextMenuListener } from "vs/base/parts/contextmenu/electron-main/contextmenu";
import { LogLevel } from "vs/platform/log/common/log";
import { toLocalISOString } from "vs/base/common/date";
// import { RawContextKey, IContextKeyService } from "vs/platform/contextkey/common/contextkey";
import { URI } from "vs/base/common/uri";
import { Protocol, ISharedProcessInitData } from "./protocol";
import * as paths from "./fill/paths";
import "./firefox";
export class Client extends IDEClient {
private readonly sharedProcessLogger = logger.named("shr proc");
private readonly windowId = parseInt(toLocalISOString(new Date()).replace(/[-:.TZ]/g, ""), 10);
private readonly version = "hello"; // TODO: pull from package.json probably
private readonly bootstrapForkLocation = "/bootstrap"; // TODO: location.
public readonly protocolPromise: Promise<Protocol>;
private protoResolve: ((protocol: Protocol) => void) | undefined;
public constructor() {
super();
process.env.VSCODE_LOGS = "/tmp/vscode-online/logs"; // TODO: use tmpdir or get log directory from init data.
this.protocolPromise = new Promise((resolve): void => {
this.protoResolve = resolve;
});
}
protected initialize(): Promise<void> {
this.task("Start shared process", 5, async () => {
const protocol = await this.forkSharedProcess();
this.protoResolve!(protocol);
}).catch(() => undefined);
registerContextMenuListener();
return this.task("Start workbench", 1000, async (initData) => {
const { startup } = require("./startup");
await startup({
machineId: "1",
windowId: this.windowId,
logLevel: LogLevel.Info,
mainPid: 1,
appRoot: initData.dataDirectory,
execPath: initData.tmpDirectory,
userEnv: {},
nodeCachedDataDir: initData.tmpDirectory,
perfEntries: [],
_: [],
folderUri: URI.file(initData.dataDirectory),
});
// TODO: Set up clipboard context.
// const workbench = workbenchShell.workbench;
// const contextKeys = workbench.workbenchParams.serviceCollection.get(IContextKeyService) as IContextKeyService;
// const clipboardContextKey = new RawContextKey("nativeClipboard", this.clipboard.isSupported);
// const bounded = clipboardContextKey.bindTo(contextKeys);
// this.clipboard.onPermissionChange((enabled) => {
// bounded.set(enabled);
// });
this.clipboard.initialize();
}, this.initData);
}
public async forkSharedProcess(): Promise<Protocol> {
const childProcess = fork(this.bootstrapForkLocation, ["--shared"], {
env: {
"VSCODE_ALLOW_IO": "true",
"AMD_ENTRYPOINT": "vs/code/electron-browser/sharedProcess/sharedProcessClient",
},
});
childProcess.stderr.on("data", (data) => {
this.sharedProcessLogger.error("stderr: " + data);
});
const protocol = Protocol.fromProcess(childProcess);
await new Promise((resolve, reject): void => {
protocol.onClose(() => {
reject(new Error("unable to establish connection to shared process"));
});
const listener = protocol.onMessage((message) => {
const messageStr = message.toString();
this.sharedProcessLogger.debug(messageStr);
switch (messageStr) {
case "handshake:hello":
protocol.send(Buffer.from(JSON.stringify({
// Using the version so if we get a new mount, it spins up a new
// shared process.
socketPath: `/tmp/vscode-online/shared-${this.version}.sock`,
serviceUrl: "", // TODO
logsDir: process.env.VSCODE_LOGS,
windowId: this.windowId,
logLevel: LogLevel.Info,
} as ISharedProcessInitData)));
break;
case "handshake:ready":
listener.dispose();
resolve();
break;
}
});
});
return protocol;
}
protected createUriFactory(): IURIFactory {
return {
// TODO: not sure why this is an error.
// tslint:disable-next-line no-any
create: <URI>(uri: IURI): URI => URI.from(uri) as any,
file: (path: string): IURI => URI.file(path),
parse: (raw: string): IURI => URI.parse(raw),
};
}
}
export const client = new Client();
client.initData.then((initData) => {
paths.appData = initData.dataDirectory;
paths.defaultUserData = initData.dataDirectory;
});

View File

@ -0,0 +1,11 @@
import { URI } from "vs/base/common/uri";
export const getPathFromAmdModule = (_: typeof require, relativePath: string): string => {
if (process.mainModule && process.mainModule.filename) {
const index = process.mainModule.filename.lastIndexOf("/");
return process.mainModule.filename.slice(0, index);
}
return relativePath ? URI.parse(require.toUrl(relativePath)).fsPath : "";
};

View File

@ -0,0 +1 @@
export const gracefulify = (): void => undefined;

View File

@ -1,8 +1,13 @@
module.exports = {
getCurrentKeyboardLayout: (): null => {
class NativeKeymap {
public getCurrentKeyboardLayout(): null {
return null;
},
getKeyMap: (): undefined[] => {
}
public getKeyMap(): undefined[] {
return [];
},
};
}
}
export = new NativeKeymap();

View File

@ -70,4 +70,4 @@ const ptyType: nodePtyType = {
};
module.exports = ptyType;
exports = ptyType;

View File

@ -0,0 +1,2 @@
// TODO: obtain this in a reasonable way.
export default { name: "vscode", version: "1.31.1" };

View File

@ -0,0 +1,7 @@
const paths = {
appData: "/tmp",
defaultUserData: "/tmp",
};
export let getAppDataPath = (): string => paths.appData;
export let getDefaultUserDataPath = (): string => paths.defaultUserData;

View File

@ -0,0 +1,24 @@
import { IProductConfiguration } from "vs/platform/node/product";
const product = {
nameShort: "VSCode",
nameLong: "vscode online",
dataFolderName: ".vscode-online",
extensionsGallery: {
serviceUrl: "",
},
extensionExecutionEnvironments: {
"wayou.vscode-todo-highlight": "worker",
"vscodevim.vim": "worker",
"coenraads.bracket-pair-colorizer": "worker",
},
fetchUrl: "",
} as IProductConfiguration;
if (process.env['VSCODE_DEV']) {
product.nameShort += ' Dev';
product.nameLong += ' Dev';
product.dataFolderName += '-dev';
}
export default product;

View File

@ -0,0 +1,3 @@
// TODO: ?
// tslint:disable-next-line no-any
(global as any).requireToUrl = (path: string): string => `${location.protocol}//{location.host}/${path}`;

View File

@ -0,0 +1,186 @@
import { exec } from "child_process";
import { promisify } from "util";
import { appendFile, stat, readdir } from "fs";
import { RotatingLogger as NodeRotatingLogger } from "spdlog";
import { logger, field } from "@coder/logger";
import { escapePath } from "@coder/protocol";
// TODO: It would be better to spawn an actual spdlog instance on the server and
// use that for the logging. Or maybe create an instance when the server starts,
// and just always use that one (make it part of the protocol).
export class RotatingLogger implements NodeRotatingLogger {
private format = true;
private buffer = "";
private flushPromise: Promise<void> | undefined;
private name: string;
private logDirectory: string;
private escapedLogDirectory: string;
private fullFilePath: string;
private fileName: string;
private fileExt: string | undefined;
private escapedFilePath: string;
private filesize: number;
private filecount: number;
public constructor(name: string, filePath: string, filesize: number, filecount: number) {
this.name = name;
this.filesize = filesize;
this.filecount = filecount;
this.fullFilePath = filePath;
const slashIndex = filePath.lastIndexOf("/");
const dotIndex = filePath.lastIndexOf(".");
this.logDirectory = slashIndex !== -1 ? filePath.substring(0, slashIndex) : "/";
this.fileName = filePath.substring(slashIndex + 1, dotIndex !== -1 ? dotIndex : undefined);
this.fileExt = dotIndex !== -1 ? filePath.substring(dotIndex + 1) : undefined;
this.escapedLogDirectory = escapePath(this.logDirectory);
this.escapedFilePath = escapePath(filePath);
this.flushPromise = new Promise((resolve): void => {
exec(`mkdir -p ${this.escapedLogDirectory}; touch ${this.escapedFilePath}`, async (error) => {
if (!error) {
try {
await this.doFlush();
} catch (e) {
error = e;
}
}
if (error) {
logger.error(error.message, field("error", error));
}
this.flushPromise = undefined;
resolve();
});
});
}
public trace(message: string): void {
this.write("trace", message);
}
public debug(message: string): void {
this.write("debug", message);
}
public info(message: string): void {
this.write("info", message);
}
public warn(message: string): void {
this.write("warn", message);
}
public error(message: string): void {
this.write("error", message);
}
public critical(message: string): void {
this.write("critical", message);
}
public setLevel(): void {
// Should output everything.
}
public clearFormatters(): void {
this.format = false;
}
/**
* Flushes the buffer. Only one process runs at a time to prevent race
* conditions.
*/
public flush(): Promise<void> {
if (!this.flushPromise) {
this.flushPromise = this.doFlush().then(() => {
this.flushPromise = undefined;
}).catch((error) => {
this.flushPromise = undefined;
logger.error(error.message, field("error", error));
});
}
return this.flushPromise;
}
public drop(): void {
this.buffer = "";
}
private pad(num: number, length: number = 2, prefix: string = "0"): string {
const str = num.toString();
return (length > str.length ? prefix.repeat(length - str.length) : "") + str;
}
private write(severity: string, message: string): void {
if (this.format) {
const date = new Date();
const dateStr = `${date.getFullYear()}-${this.pad(date.getMonth() + 1)}-${this.pad(date.getDate())}`
+ ` ${this.pad(date.getHours())}:${this.pad(date.getMinutes())}:${this.pad(date.getSeconds())}.${this.pad(date.getMilliseconds(), 3)}`;
this.buffer += `[${dateStr}] [${this.name}] [${severity}] `;
}
this.buffer += message;
if (this.format) {
this.buffer += "\n";
}
this.flush();
}
private async rotate(): Promise<void> {
const stats = await promisify(stat)(this.fullFilePath);
if (stats.size < this.filesize) {
return;
}
const reExt = typeof this.fileExt !== "undefined" ? `\\.${this.fileExt}` : "";
const re = new RegExp(`^${this.fileName}(?:\\.(\\d+))?${reExt}$`);
const orderedFiles: string[] = [];
(await promisify(readdir)(this.logDirectory)).forEach((file) => {
const match = re.exec(file);
if (match) {
orderedFiles[typeof match[1] !== "undefined" ? parseInt(match[1], 10) : 0] = file;
}
});
// Rename in reverse so we don't overwrite before renaming something.
let count = 0;
const command = orderedFiles.map((file) => {
const fileExt = typeof this.fileExt !== "undefined" ? `.${this.fileExt}` : "";
const newFile = `${this.logDirectory}/${this.fileName}.${++count}${fileExt}`;
return count >= this.filecount
? `rm ${escapePath(this.logDirectory + "/" + file)}`
: `mv ${escapePath(this.logDirectory + "/" + file)} ${escapePath(newFile)}`;
}).reverse().concat([
`touch ${escapePath(this.fullFilePath)}`,
]).join(";");
await promisify(exec)(command);
}
/**
* Flushes the entire buffer, including anything added in the meantime, and
* rotates the log if necessary.
*/
private async doFlush(): Promise<void> {
const writeBuffer = async (): Promise<void> => {
const toWrite = this.buffer;
this.buffer = "";
await promisify(appendFile)(this.fullFilePath, toWrite);
};
while (this.buffer.length > 0) {
await writeBuffer();
await this.rotate();
}
}
}
export const setAsyncMode = (): void => {
// Nothing to do.
};

View File

@ -0,0 +1,22 @@
import { StdioIpcHandler } from "@coder/server/src/ipc";
import { IpcRenderer } from "electron";
export * from "@coder/ide/src/fill/electron";
class StdioIpcRenderer extends StdioIpcHandler implements IpcRenderer {
public sendTo(windowId: number, channel: string, ...args: any[]): void {
throw new Error("Method not implemented.");
}
public sendToHost(channel: string, ...args: any[]): void {
throw new Error("Method not implemented.");
}
public eventNames(): string[] {
return super.eventNames() as string[];
}
}
export const ipcRenderer = new StdioIpcRenderer();

View File

@ -0,0 +1,78 @@
import { readFile, writeFile } from "fs";
import { promisify } from "util";
import { Event } from "vs/base/common/event";
import * as storage from "vs/base/node/storage";
export class StorageDatabase implements storage.IStorageDatabase {
public readonly onDidChangeItemsExternal = Event.None;
private items = new Map<string, string>();
private fetched: boolean = false;
public constructor(private readonly path: string) {
window.addEventListener("unload", () => {
if (!navigator.sendBeacon) {
throw new Error("cannot save state");
}
// TODO: Need to use navigator.sendBeacon instead of the web socket, or we
// need to save when there is a change. Should we save as a sqlite3
// database instead of JSON? Could send to the server the way the global
// storage works. Or maybe fill `vscode-sqlite3` to do that.
this.save();
});
}
public async getItems(): Promise<Map<string, string>> {
if (this.fetched) {
return this.items;
}
try {
const contents = await promisify(readFile)(this.path, "utf8");
const json = JSON.parse(contents);
Object.keys(json).forEach((key) => {
this.items.set(key, json[key]);
});
} catch (error) {
if (error.code && error.code !== "ENOENT") {
throw error;
}
}
this.fetched = true;
return this.items;
}
public updateItems(request: storage.IUpdateRequest): Promise<void> {
if (request.insert) {
request.insert.forEach((value, key) => this.items.set(key, value));
}
if (request.delete) {
request.delete.forEach(key => this.items.delete(key));
}
return Promise.resolve();
}
public close(): Promise<void> {
return Promise.resolve();
}
public checkIntegrity(): Promise<string> {
return Promise.resolve("ok");
}
private save(): Promise<void> {
const json: { [key: string]: string } = {};
this.items.forEach((value, key) => {
json[key] = value;
});
return promisify(writeFile)(this.path, JSON.stringify(json));
}
}
// @ts-ignore
storage.SQLiteStorageDatabase = StorageDatabase;

View File

@ -0,0 +1,274 @@
import * as electron from "electron";
import { Emitter } from "@coder/events";
import * as windowsIpc from "vs/platform/windows/node/windowsIpc";
import { IWindowsService, INativeOpenDialogOptions, MessageBoxOptions, SaveDialogOptions, OpenDialogOptions, IMessageBoxResult, IDevToolsOptions, IEnterWorkspaceResult, CrashReporterStartOptions, INewWindowOptions } from "vs/platform/windows/common/windows";
import { ParsedArgs } from "vs/platform/environment/common/environment";
import { IWorkspaceIdentifier, IWorkspaceFolderCreationData, ISingleFolderWorkspaceIdentifier } from "vs/platform/workspaces/common/workspaces";
import { URI } from "vs/base/common/uri";
import { IRecentlyOpened } from "vs/platform/history/common/history";
import { ISerializableCommandAction } from "vs/platform/actions/common/actions";
// TODO: Might make sense to hook these straight in if we can.
// import { WindowsService as VSWindowsService } from "vs/platform/windows/electron-main/windowsService";
// import { WindowsManager } from "vs/code/electron-main/windows";
/**
* Instead of going to the shared process, we'll directly run these methods on
* the client. This setup means we can only control the current window.
*/
class WindowsService implements IWindowsService {
// tslint:disable-next-line no-any
public _serviceBrand: any;
private openEmitter = new Emitter<number>();
private focusEmitter = new Emitter<number>();
private blurEmitter = new Emitter<number>();
private maximizeEmitter = new Emitter<number>();
private unmaximizeEmitter = new Emitter<number>();
private recentlyOpenedChangeEmitter = new Emitter<void>();
public onWindowOpen = this.openEmitter.event;
public onWindowFocus = this.focusEmitter.event;
public onWindowBlur = this.blurEmitter.event;
public onWindowMaximize = this.maximizeEmitter.event;
public onWindowUnmaximize = this.unmaximizeEmitter.event;
public onRecentlyOpenedChange = this.recentlyOpenedChangeEmitter.event;
private window = new electron.BrowserWindow();
// Dialogs
public pickFileFolderAndOpen(_options: INativeOpenDialogOptions): Promise<void> {
throw new Error("not implemented");
}
public pickFileAndOpen(_options: INativeOpenDialogOptions): Promise<void> {
throw new Error("not implemented");
}
public pickFolderAndOpen(_options: INativeOpenDialogOptions): Promise<void> {
throw new Error("not implemented");
}
public pickWorkspaceAndOpen(_options: INativeOpenDialogOptions): Promise<void> {
throw new Error("not implemented");
}
public showMessageBox(_windowId: number, _options: MessageBoxOptions): Promise<IMessageBoxResult> {
throw new Error("not implemented");
}
public showSaveDialog(_windowId: number, _options: SaveDialogOptions): Promise<string> {
throw new Error("not implemented");
}
public showOpenDialog(_windowId: number, _options: OpenDialogOptions): Promise<string[]> {
throw new Error("not implemented");
}
public reloadWindow(windowId: number, _args?: ParsedArgs): Promise<void> {
return Promise.resolve(this.getWindowById(windowId).reload());
}
public openDevTools(_windowId: number, _options?: IDevToolsOptions): Promise<void> {
throw new Error("not implemented");
}
public toggleDevTools(_windowId: number): Promise<void> {
throw new Error("not implemented");
}
public closeWorkspace(_windowId: number): Promise<void> {
throw new Error("not implemented");
}
public enterWorkspace(_windowId: number, _path: string): Promise<IEnterWorkspaceResult> {
throw new Error("not implemented");
}
public createAndEnterWorkspace(_windowId: number, _folders?: IWorkspaceFolderCreationData[], _path?: string): Promise<IEnterWorkspaceResult> {
throw new Error("not implemented");
}
public saveAndEnterWorkspace(_windowId: number, _path: string): Promise<IEnterWorkspaceResult> {
throw new Error("not implemented");
}
public toggleFullScreen(windowId: number): Promise<void> {
const win = this.getWindowById(windowId);
return Promise.resolve(win.setFullScreen(!win.isFullScreen()));
}
public setRepresentedFilename(windowId: number, fileName: string): Promise<void> {
return Promise.resolve(this.getWindowById(windowId).setRepresentedFilename(fileName));
}
public addRecentlyOpened(_files: URI[]): Promise<void> {
throw new Error("not implemented");
}
public removeFromRecentlyOpened(_paths: (IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | URI | string)[]): Promise<void> {
throw new Error("not implemented");
}
public clearRecentlyOpened(): Promise<void> {
throw new Error("not implemented");
}
public getRecentlyOpened(_windowId: number): Promise<IRecentlyOpened> {
// TODO: properly implement.
return Promise.resolve({
workspaces: [],
files: [],
});
}
public focusWindow(windowId: number): Promise<void> {
return Promise.resolve(this.getWindowById(windowId).focus());
}
public closeWindow(_windowId: number): Promise<void> {
throw new Error("not implemented");
}
public isFocused(windowId: number): Promise<boolean> {
return Promise.resolve(this.getWindowById(windowId).isFocused());
}
public isMaximized(_windowId: number): Promise<boolean> {
throw new Error("not implemented");
}
public maximizeWindow(_windowId: number): Promise<void> {
throw new Error("not implemented");
}
public unmaximizeWindow(_windowId: number): Promise<void> {
throw new Error("not implemented");
}
public minimizeWindow(_windowId: number): Promise<void> {
throw new Error("not implemented");
}
public onWindowTitleDoubleClick(_windowId: number): Promise<void> {
throw new Error("not implemented");
}
public setDocumentEdited(_windowId: number, _flag: boolean): Promise<void> {
throw new Error("not implemented");
}
public quit(): Promise<void> {
throw new Error("not implemented");
}
public relaunch(_options: { addArgs?: string[], removeArgs?: string[] }): Promise<void> {
throw new Error("not implemented");
}
// macOS Native Tabs
public newWindowTab(): Promise<void> {
throw new Error("not implemented");
}
public showPreviousWindowTab(): Promise<void> {
throw new Error("not implemented");
}
public showNextWindowTab(): Promise<void> {
throw new Error("not implemented");
}
public moveWindowTabToNewWindow(): Promise<void> {
throw new Error("not implemented");
}
public mergeAllWindowTabs(): Promise<void> {
throw new Error("not implemented");
}
public toggleWindowTabsBar(): Promise<void> {
throw new Error("not implemented");
}
// macOS TouchBar
public updateTouchBar(_windowId: number, _items: ISerializableCommandAction[][]): Promise<void> {
throw new Error("not implemented");
}
// Shared process
public whenSharedProcessReady(): Promise<void> {
// TODO: Update once shared process is tied in.
return Promise.resolve();
}
public toggleSharedProcess(): Promise<void> {
throw new Error("not implemented");
}
// Global methods
public openWindow(_windowId: number, _paths: URI[], _options?: { forceNewWindow?: boolean, forceReuseWindow?: boolean, forceOpenWorkspaceAsFile?: boolean, args?: ParsedArgs }): Promise<void> {
throw new Error("not implemented");
}
public openNewWindow(_options?: INewWindowOptions): Promise<void> {
throw new Error("not implemented");
}
public showWindow(windowId: number): Promise<void> {
return Promise.resolve(this.getWindowById(windowId).show());
}
public getWindows(): Promise<{ id: number; workspace?: IWorkspaceIdentifier; folderUri?: ISingleFolderWorkspaceIdentifier; title: string; filename?: string; }[]> {
throw new Error("not implemented");
}
public getWindowCount(): Promise<number> {
return Promise.resolve(1);
}
public log(_severity: string, ..._messages: string[]): Promise<void> {
throw new Error("not implemented");
}
public showItemInFolder(_path: string): Promise<void> {
throw new Error("not implemented");
}
public getActiveWindowId(): Promise<number | undefined> {
return Promise.resolve(1);
}
public openExternal(_url: string): Promise<boolean> {
throw new Error("not implemented");
}
public startCrashReporter(_config: CrashReporterStartOptions): Promise<void> {
throw new Error("not implemented");
}
public openAboutDialog(): Promise<void> {
throw new Error("not implemented");
}
public resolveProxy(windowId: number, url: string): Promise<string | undefined> {
return new Promise((resolve): void => {
this.getWindowById(windowId).webContents.session.resolveProxy(url, (proxy) => {
resolve(proxy);
});
});
}
/**
* Get window by ID. For now this is always the current window.
*/
private getWindowById(_windowId: number): electron.BrowserWindow {
return this.window;
}
}
// @ts-ignore
windowsIpc.WindowsChannelClient = WindowsService;

View File

@ -1,38 +1 @@
import { field, logger, time } from "@coder/logger";
import { Client, IURI, setUriFactory } from "@coder/ide";
import { URI } from "vs/base/common/uri";
import "./firefox";
const load = (): Promise<void> => {
return new Promise((resolve, reject): void => {
setUriFactory({
// TODO: not sure why this is an error.
// tslint:disable-next-line no-any
create: <URI>(uri: IURI): URI => URI.from(uri) as any,
file: (path: string): IURI => URI.file(path),
parse: (raw: string): IURI => URI.parse(raw),
});
const client = new Client({
mkDirs: [
"~/vscode/extensions",
"~/.config/User",
],
});
const importTime = time(1500);
import(/* webpackPrefetch: true */ "./workbench").then((module) => {
logger.info("Loaded workbench bundle", field("duration", importTime));
const initTime = time(1500);
return module.initialize(client).then(() => {
logger.info("Initialized workbench", field("duration", initTime));
resolve();
});
}).catch((error) => {
reject(error);
});
});
};
export { load };
export * from "./client";

View File

@ -0,0 +1,129 @@
import { ChildProcess } from "child_process";
import { EventEmitter } from "events";
import { Protocol as VSProtocol } from "vs/base/parts/ipc/node/ipc.net";
import { LogLevel } from "vs/platform/log/common/log";
export interface ISharedProcessInitData {
socketPath: string;
serviceUrl: string;
logsDir: string;
windowId: number;
logLevel: LogLevel;
}
export interface IStdio {
onMessage: (cb: (data: string | Buffer) => void) => void;
sendMessage: (data: string | Buffer) => void;
onExit?: (cb: () => void) => void;
}
/**
* An implementation of net.Socket that uses stdio streams.
*/
class Socket {
private readonly emitter: EventEmitter;
public constructor(private readonly stdio: IStdio, ignoreFirst: boolean = false) {
this.emitter = new EventEmitter();
let first = true;
stdio.onMessage((data) => {
if (ignoreFirst && first) {
first = false;
return;
}
this.emitter.emit("data", Buffer.from(data.toString()));
});
if (stdio.onExit) {
stdio.onExit(() => {
this.emitter.emit("close");
});
}
}
public removeListener(event: string, listener: () => void): void {
this.emitter.removeListener(event, listener);
}
public once(event: string, listener: () => void): void {
this.emitter.once(event, listener);
}
public on(event: string, listener: () => void): void {
this.emitter.on(event, listener);
}
public end(): void {
// TODO: figure it out
}
public get destroyed(): boolean {
return false;
}
public write(data: string | Buffer): void {
this.stdio.sendMessage(data);
}
}
/**
* A protocol around a process, stream, or worker.
*/
export class Protocol extends VSProtocol {
public static fromProcess(childProcess: ChildProcess): Protocol {
return Protocol.fromStdio({
onMessage: (cb): void => {
childProcess.stdout.on("data", (data: string | Buffer) => {
cb(data);
});
},
sendMessage: (data): void => {
childProcess.stdin.write(data);
},
onExit: (cb): void => {
childProcess.on("exit", cb);
},
});
}
public static fromStream(
inStream: { on: (event: "data", cb: (b: string | Buffer) => void) => void },
outStream: { write: (b: string | Buffer) => void },
): Protocol {
return Protocol.fromStdio({
onMessage: (cb): void => {
inStream.on("data", (data) => {
cb(data);
});
},
sendMessage: (data): void => {
outStream.write(data);
},
});
}
public static fromWorker(worker: {
onmessage: (event: MessageEvent) => void;
postMessage: (data: string, origin?: string | string[]) => void;
}, ignoreFirst: boolean = false): Protocol {
return Protocol.fromStdio({
onMessage: (cb): void => {
worker.onmessage = (event: MessageEvent): void => {
cb(event.data);
};
},
sendMessage: (data): void => {
worker.postMessage(data.toString());
},
}, ignoreFirst);
}
public static fromStdio(stdio: IStdio, ignoreFirst?: boolean): Protocol {
return new Protocol(new Socket(stdio, ignoreFirst));
}
}

View File

@ -0,0 +1,151 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import "vs/loader";
// Base
import "vs/base/common/strings";
import "vs/base/common/errors";
// Configuration
import "vs/workbench/services/configuration/common/configurationExtensionPoint";
// Editor
import "vs/editor/editor.all";
// Platform
import "vs/platform/widget/browser/contextScopedHistoryWidget";
import "vs/platform/label/electron-browser/label.contribution";
// Menus/Actions
import "vs/workbench/services/actions/electron-browser/menusExtensionPoint";
// Views
import "vs/workbench/api/browser/viewsContainersExtensionPoint";
import "vs/workbench/api/browser/viewsExtensionPoint";
// Localizations
import "vs/workbench/parts/localizations/electron-browser/localizations.contribution";
// Workbench
import "vs/workbench/browser/actions/toggleActivityBarVisibility";
import "vs/workbench/browser/actions/toggleStatusbarVisibility";
import "vs/workbench/browser/actions/toggleSidebarVisibility";
import "vs/workbench/browser/actions/toggleSidebarPosition";
import "vs/workbench/browser/actions/toggleEditorLayout";
import "vs/workbench/browser/actions/toggleZenMode";
import "vs/workbench/browser/actions/toggleCenteredLayout";
import "vs/workbench/browser/actions/toggleTabsVisibility";
import "vs/workbench/parts/preferences/electron-browser/preferences.contribution";
import "vs/workbench/parts/preferences/browser/keybindingsEditorContribution";
import "vs/workbench/parts/logs/electron-browser/logs.contribution";
import "vs/workbench/browser/parts/quickopen/quickopen.contribution";
import "vs/workbench/parts/quickopen/browser/quickopen.contribution";
import "vs/workbench/browser/parts/editor/editorPicker";
import "vs/workbench/browser/parts/quickinput/quickInput.contribution";
import "vs/workbench/parts/files/electron-browser/explorerViewlet";
import "vs/workbench/parts/files/electron-browser/fileActions.contribution";
import "vs/workbench/parts/files/electron-browser/files.contribution";
import "vs/workbench/parts/backup/common/backup.contribution";
import "vs/workbench/parts/stats/node/stats.contribution";
import "vs/workbench/parts/splash/electron-browser/partsSplash.contribution";
import "vs/workbench/parts/search/electron-browser/search.contribution";
import "vs/workbench/parts/search/browser/searchView";
import "vs/workbench/parts/search/browser/openAnythingHandler";
import "vs/workbench/parts/scm/electron-browser/scm.contribution";
import "vs/workbench/parts/scm/electron-browser/scmViewlet";
import "vs/workbench/parts/debug/electron-browser/debug.contribution";
// import "vs/workbench/parts/debug/browser/debugQuickOpen";
// import "vs/workbench/parts/debug/electron-browser/repl";
// import "vs/workbench/parts/debug/browser/debugViewlet";
import "vs/workbench/parts/markers/electron-browser/markers.contribution";
import "vs/workbench/parts/comments/electron-browser/comments.contribution";
import "vs/workbench/parts/html/electron-browser/html.contribution";
import "vs/workbench/parts/url/electron-browser/url.contribution";
import "vs/workbench/parts/webview/electron-browser/webview.contribution";
import "vs/workbench/parts/welcome/walkThrough/electron-browser/walkThrough.contribution";
import "vs/workbench/parts/extensions/electron-browser/extensions.contribution";
import "vs/workbench/parts/extensions/browser/extensionsQuickOpen";
import "vs/workbench/parts/extensions/electron-browser/extensionsViewlet";
import "vs/workbench/parts/welcome/page/electron-browser/welcomePage.contribution";
import "vs/workbench/parts/output/electron-browser/output.contribution";
import "vs/workbench/parts/output/browser/outputPanel";
import "vs/workbench/parts/terminal/electron-browser/terminal.contribution";
import "vs/workbench/parts/terminal/browser/terminalQuickOpen";
import "vs/workbench/parts/terminal/electron-browser/terminalPanel";
import "vs/workbench/electron-browser/workbench";
// import "vs/workbench/parts/relauncher/electron-browser/relauncher.contribution";
import "vs/workbench/parts/tasks/electron-browser/task.contribution";
import "vs/workbench/parts/emmet/browser/emmet.browser.contribution";
import "vs/workbench/parts/emmet/electron-browser/emmet.contribution";
import "vs/workbench/parts/codeEditor/codeEditor.contribution";
import "vs/workbench/parts/execution/electron-browser/execution.contribution";
import "vs/workbench/parts/snippets/electron-browser/snippets.contribution";
import "vs/workbench/parts/snippets/electron-browser/snippetsService";
import "vs/workbench/parts/snippets/electron-browser/insertSnippet";
import "vs/workbench/parts/snippets/electron-browser/configureSnippets";
import "vs/workbench/parts/snippets/electron-browser/tabCompletion";
import "vs/workbench/parts/themes/electron-browser/themes.contribution";
// import "vs/workbench/parts/feedback/electron-browser/feedback.contribution";
import "vs/workbench/parts/welcome/gettingStarted/electron-browser/gettingStarted.contribution";
import "vs/workbench/parts/update/electron-browser/update.contribution";
// import "vs/workbench/parts/surveys/electron-browser/nps.contribution";
// import "vs/workbench/parts/surveys/electron-browser/languageSurveys.contribution";
import "vs/workbench/parts/performance/electron-browser/performance.contribution";
// import "vs/workbench/parts/cli/electron-browser/cli.contribution";
import "vs/workbench/api/electron-browser/extensionHost.contribution";
import "vs/workbench/electron-browser/main.contribution";
import { startup } from "vs/workbench/electron-browser/main";
// import "vs/workbench/parts/themes/test/electron-browser/themes.test.contribution";
import "vs/workbench/parts/watermark/electron-browser/watermark";
import "vs/workbench/parts/welcome/overlay/browser/welcomeOverlay";
import "vs/workbench/parts/outline/electron-browser/outline.contribution";
import "vs/workbench/services/bulkEdit/electron-browser/bulkEditService";
import "vs/workbench/parts/experiments/electron-browser/experiments.contribution";
import { URI } from "vs/base/common/uri";
export {
URI,
startup,
};

View File

@ -1,59 +0,0 @@
import { IStorageService, StorageScope } from "vs/platform/storage/common/storage";
export class StorageService implements IStorageService {
public _serviceBrand: any;
private _globalObject: { [key: string]: any };
private _workspaceObject: { [ key: string]: any };
public constructor(globalState: object, workspaceState: object) {
this._globalObject = globalState;
this._workspaceObject = workspaceState;
}
public get globalObject() {
return this._globalObject;
}
public get workspaceObject() {
return this._workspaceObject;
}
public store(key: string, value: any, scope?: StorageScope): void {
this.getObject(scope)[key] = value;
}
public remove(key: string, scope?: StorageScope): void {
delete this.getObject(scope)[key];
}
public get(key: string, scope?: StorageScope, defaultValue?: string): string {
return this.getObject(scope)[key] || defaultValue;
}
public getInteger(key: string, scope?: StorageScope, defaultValue?: number): number {
return parseInt(this.get(key, scope), 10) || defaultValue;
}
public getBoolean(key: string, scope?: StorageScope, defaultValue?: boolean): boolean {
const v = this.get(key, scope);
if (typeof v !== "undefined") {
return v === "true";
}
return defaultValue;
}
private getObject(scope = StorageScope.GLOBAL): { [key: string]: any } {
switch (scope) {
case StorageScope.GLOBAL:
return this._globalObject;
case StorageScope.WORKSPACE:
return this._workspaceObject;
default:
throw new Error("unsupported storage scope");
}
}
}

View File

@ -1,224 +0,0 @@
import { Client } from "@coder/ide";
import { Emitter } from "@coder/events";
import { logger } from "@coder/logger";
import { Protocol } from "vs/base/parts/ipc/node/ipc.net";
import { IModelService } from "vs/editor/common/services/modelService";
import { ICodeEditorService } from "vs/editor/browser/services/codeEditorService";
import { registerContextMenuListener } from "vs/base/parts/contextmenu/electron-main/contextmenu";
import { Workbench } from "vs/workbench/electron-browser/workbench";
import { IDecorationsService } from "vs/workbench/services/decorations/browser/decorations";
import { LogLevel } from "vs/platform/log/common/log";
import { INotificationService, Severity } from "vs/platform/notification/common/notification";
import { toLocalISOString } from "vs/base/common/date";
import { RawContextKey, IContextKeyService } from "vs/platform/contextkey/common/contextkey";
import { StorageService } from "./storageService";
let protoResolve: (protocol: Protocol) => void;
export const protocolPromise = new Promise<Protocol>((res) => {
protoResolve = res;
});
let storageResolve: (storageService: StorageService) => void;
export const getStorageService = new Promise<StorageService>((res) => {
storageResolve = res;
});
export let systemExtensionsLocation: string;
export let forkedBinLocation: string;
const hasNativeClipboard = typeof navigator !== "undefined" && typeof (navigator as any).clipboard !== "undefined" && typeof (navigator as any).clipboard.readText !== "undefined";
let isEnabled: boolean = false;
const clipboardEnabledEmitter = new Emitter<boolean>();
export const nativeClipboard: {
readonly contextKey: RawContextKey<boolean>;
readonly instance: {
readText(): Promise<string>;
writeText(value: string): Promise<void>;
};
readonly onChange: Event<boolean>;
readonly isEnabled: boolean;
} = {
contextKey: new RawContextKey('nativeClipboard', hasNativeClipboard),
instance: hasNativeClipboard ? (navigator as any).clipboard : undefined,
get onChange(): Event<boolean> {
return clipboardEnabledEmitter.event;
},
get isEnabled(): boolean {
return isEnabled;
}
};
let workbench: Workbench;
function getModelService(): IModelService {
return workbench.workbenchParams.serviceCollection.get<IModelService>(IModelService) as IModelService;
}
function getCodeEditorService(): ICodeEditorService {
return workbench.workbenchParams.serviceCollection.get(ICodeEditorService) as ICodeEditorService;
}
function getNotificationService(): INotificationService {
return workbench.workbenchParams.serviceCollection.get(INotificationService) as INotificationService;
}
export const initialize = async (client: Client): Promise<void> => {
window.addEventListener("contextmenu", (event) => {
event.preventDefault();
});
// TODO: Fetch configuration.
const storageServicePromise = client.wrapTask("Set configurations", 5, async (state) => {
const storageService = new StorageService(state.global, state.workspace);
storageResolve(storageService);
return storageService;
}, client.state);
// Set up window ID for logging. We'll also use a static logging directory
// otherwise we'd have to get the log directory back from the currently
// running shared process. This allows us to not wait for that. Each window
// will still have its own logging within that directory.
const windowId = parseInt(toLocalISOString(new Date()).replace(/[-:.TZ]/g, ""), 10);
process.env.VSCODE_LOGS = "/tmp/vscode-logs";
client.wrapTask("Start shared process", 5, async (api, wush, mountPath) => {
const session = wush.execute({
command: "bash -c 'VSCODE_ALLOW_IO=true"
+ " AMD_ENTRYPOINT=vs/code/electron-browser/sharedProcess/sharedProcessClient"
+ ` nice -n -17 ${nodePath} ${bootstrapForkLocation} --client'`,
});
const sharedProcessLogger = logger.named("shr proc");
session.onStderr((data) => {
sharedProcessLogger.error("stderr: " + data);
});
session.onDone(() => {
workbenchPromise.then(() => {
getNotificationService().prompt(
Severity.Error,
"Shared process terminated unexpectedly.",
[{
label: "Reload IDE",
run: (): void => {
window.location.reload();
},
}],
);
});
});
const protocol = Protocol.fromStdio({
onMessage: (cb) => {
session.onStdout((data) => {
cb(Buffer.from(data as any));
}, true);
},
sendMessage: (data) => {
session.sendStdin(data);
},
});
await new Promise((resolve) => {
const listener = protocol.onMessage((message) => {
const messageStr = message.toString();
sharedProcessLogger.debug(messageStr);
switch (messageStr) {
case "handshake:hello":
protocol.send(Buffer.from(JSON.stringify({
// Using the mount path so if we get a new mount, it spins up a new shared
// process since it or the builtin extensions could contain changes.
sharedIPCHandle: `/tmp/vscode-shared${mountPath.replace(/\//g, "-")}.sock`,
serviceUrl: api.environment.appURL("extensions-api"),
logsDir: process.env.VSCODE_LOGS,
nodePath,
bootstrapForkLocation,
args: {},
windowId,
logLevel: LogLevel.Info,
} as ISharedProcessInitData)));
break;
case "handshake:ready":
listener.dispose();
resolve();
break;
}
});
});
protoResolve(protocol);
}, client.api, client.wush, mountPromise);
const { startup, URI } = require('vs/workbench/workbench.main');
require("os").homedir = () => {
// TODO: update this as well as folderURL
return "/root";
};
require("path").posix = require("path");
registerContextMenuListener();
const workbenchPromise = client.wrapTask("Start workbench", 1000, async (workspace, mountPath) => {
const workbenchShellPromise = startup({
machineId: "1",
windowId,
logLevel: LogLevel.Info,
mainPid: 1,
appRoot: mountPath,
execPath: "/tmp",
userEnv: {},
nodeCachedDataDir: "/tmp",
perfEntries: [],
_: undefined,
folderUri: URI.file(workspace.mountUri.path),
});
const workbenchShell = await workbenchShellPromise;
workbench = workbenchShell.workbench;
const contextKeys = workbench.workbenchParams.serviceCollection.get(IContextKeyService) as IContextKeyService;
const bounded = nativeClipboard.contextKey.bindTo(contextKeys);
const navigatorClip = (navigator as any).clipboard;
const navigatorPerms = (navigator as any).permissions;
if (navigatorClip && navigatorPerms) {
navigatorPerms.query({
name: "clipboard-read",
}).then((permissionStatus) => {
const updateStatus = () => {
if (permissionStatus.state === "denied") {
isEnabled = false;
clipboardEnabledEmitter.emit(false);
bounded.set(false);
} else {
isEnabled = true;
clipboardEnabledEmitter.emit(true);
bounded.set(true);
}
};
updateStatus();
permissionStatus.onchange = () => {
updateStatus();
};
});
}
const decorations = workbench.workbenchParams.serviceCollection.get(IDecorationsService) as IDecorationsService;
await registerCollaboratorDecorations(client, decorations);
return workbenchShell;
}, client.mkDirs);
client.wrapTask("Set up saving state", 5, async () => {
if (!navigator.sendBeacon) {
throw new Error("cannot save state");
}
// TODO: save storageSevice.globalObject and storageService.workspaceObject
});
await workbenchPromise;
};

View File

@ -0,0 +1,123 @@
const path = require("path");
const webpack = require("webpack");
const root = path.resolve(__dirname, "..", "..");
const fills = path.join(root, "packages", "ide", "src", "fill");
const vscodeFills = path.join(root, "packages", "vscode", "src", "fill");
const merge = require("webpack-merge");
module.exports = (env) => {
const afterCompileCommand = env && env.afterCompileCommand;
return merge(require(path.join(root, "scripts", "webpack.general.config.js"))({
typescriptCompilerOptions: {
target: "es5",
},
}), {
entry: path.join(root, "lib/vscode/src/bootstrap-fork.js"),
mode: "development",
target: "node",
externals: ["node-pty", "spdlog"],
output: {
chunkFilename: "[name].bundle.js",
path: path.resolve(__dirname, "./bin"),
publicPath: "/",
filename: "bootstrap-fork.js",
libraryTarget: "commonjs",
globalObject: "this",
},
module: {
rules: [{
loader: "string-replace-loader",
test: /\.(js|ts)$/,
options: {
multiple: [
{
search: "require\\.toUrl\\(",
replace: "requireToUrl(",
flags: "g",
},
{
search: "require\\.__\\$__nodeRequire",
replace: "require",
flags: "g",
},
],
},
}, {
loader: "string-replace-loader",
test: /vs\/loader\.js/,
options: {
multiple: [
{
search: "var recorder = moduleManager.getRecorder\\(\\);",
replace: `
var recorder = moduleManager.getRecorder();
const context = require.context("../", true, /.*/);
if (scriptSrc.indexOf("file:///") !== -1) {
const vsSrc = scriptSrc.split("file:///")[1].split(".js")[0];
if (vsSrc && vsSrc.startsWith("vs/")) {
scriptSrc = \`node|./\${vsSrc}\`;
}
}
`,
flags: "g",
},
{
search: "nodeRequire\\(",
replace: "require(",
flags: "g",
},
{
search: "moduleExports_1 = require\\(",
replace: "moduleExports_1 = context(",
flags: "g",
},
],
},
}, {
test: /\.wasm$/,
type: "javascript/auto",
}, {
// Ignore a bunch of file types we don't have loaders for. Also ignore
// test directories, some files with invalid JSON, and files we don't
// actually require but throw warnings or errors. This all seems to be a
// case of dynamic loading including things we won't require.
// This also results in the bundle being significantly smaller which
// makes uglify much faster.
test: /(\/vs\/code\/electron-main\/)|(\/test\/)|(OSSREADME\.json$)|(\.(test\.ts|test\.js|d\.ts|qwoff|node|html|txt|exe|wuff|md|sh|scpt|less)$)/,
use: ["ignore-loader"]
}],
},
resolve: {
alias: {
"gc-signals": path.join(fills, "empty.ts"),
"native-keymap": path.join(fills, "native-keymap.ts"),
"windows-process-tree": path.resolve(fills, "empty.ts"),
"electron": path.join(vscodeFills, "stdioElectron.ts"),
"vs/platform/node/product": path.resolve(vscodeFills, "product.ts"),
"vs/platform/node/package": path.resolve(vscodeFills, "package.ts"),
"vs/base/node/paths": path.resolve(vscodeFills, "paths.ts"),
"vs/base/common/amd": path.resolve(vscodeFills, "amd.ts"),
"vs": path.resolve(root, "lib/vscode/src/vs"),
},
},
resolveLoader: {
alias: {
"vs/css": path.resolve(vscodeFills, "css.js"),
},
},
plugins: [
new webpack.ProgressPlugin((percentage, msg) => {
if (percentage === 1) {
if (afterCompileCommand) {
require("child_process").execSync(afterCompileCommand, {
stdio: "inherit"
});
}
}
}),
],
});
};

View File

@ -2,3 +2,122 @@
# yarn lockfile v1
ajv-keywords@^3.1.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.2.0.tgz#e86b819c602cf8821ad637413698f1dec021847a"
integrity sha1-6GuBnGAs+IIa1jdBNpjx3sAhhHo=
ajv@^6.1.0:
version "6.7.0"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.7.0.tgz#e3ce7bb372d6577bb1839f1dfdfcbf5ad2948d96"
integrity sha512-RZXPviBTtfmtka9n9sy1N5M5b82CbxWIR6HIis4s3WQTXDJamc/0gpCWNGz6EWdWp4DOfjzJfhz/AS9zVPjjWg==
dependencies:
fast-deep-equal "^2.0.1"
fast-json-stable-stringify "^2.0.0"
json-schema-traverse "^0.4.1"
uri-js "^4.2.2"
big.js@^5.2.2:
version "5.2.2"
resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328"
integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==
bindings@^1.3.0:
version "1.3.1"
resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.3.1.tgz#21fc7c6d67c18516ec5aaa2815b145ff77b26ea5"
integrity sha512-i47mqjF9UbjxJhxGf+pZ6kSxrnI3wBLlnGI2ArWJ4r0VrvDS7ZYXkprq/pLaBWYq4GM0r4zdHY+NNRqEMU7uew==
emojis-list@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-2.1.0.tgz#4daa4d9db00f9819880c79fa457ae5b09a1fd389"
integrity sha1-TapNnbAPmBmIDHn6RXrlsJof04k=
fast-deep-equal@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49"
integrity sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=
fast-json-stable-stringify@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2"
integrity sha1-1RQsDK7msRifh9OnYREGT4bIu/I=
json-schema-traverse@^0.4.1:
version "0.4.1"
resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660"
integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==
json5@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe"
integrity sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==
dependencies:
minimist "^1.2.0"
loader-utils@^1.1.0:
version "1.2.3"
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.2.3.tgz#1ff5dc6911c9f0a062531a4c04b609406108c2c7"
integrity sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA==
dependencies:
big.js "^5.2.2"
emojis-list "^2.0.0"
json5 "^1.0.1"
minimist@0.0.8:
version "0.0.8"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=
minimist@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284"
integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=
mkdirp@^0.5.1:
version "0.5.1"
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903"
integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=
dependencies:
minimist "0.0.8"
nan@^2.8.0:
version "2.12.1"
resolved "https://registry.yarnpkg.com/nan/-/nan-2.12.1.tgz#7b1aa193e9aa86057e3c7bbd0ac448e770925552"
integrity sha512-JY7V6lRkStKcKTvHO5NVSQRv+RV+FIL5pvDoLiAtSL9pKlC5x9PKQcZDsq7m4FO4d57mkhC6Z+QhAh3Jdk5JFw==
punycode@^2.1.0:
version "2.1.1"
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
schema-utils@^0.4.5:
version "0.4.7"
resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-0.4.7.tgz#ba74f597d2be2ea880131746ee17d0a093c68187"
integrity sha512-v/iwU6wvwGK8HbU9yi3/nhGzP0yGSuhQMzL6ySiec1FSrZZDkhm4noOSWzrNFo/jEc+SJY6jRTwuwbSXJPDUnQ==
dependencies:
ajv "^6.1.0"
ajv-keywords "^3.1.0"
spdlog@^0.7.2:
version "0.7.2"
resolved "https://registry.yarnpkg.com/spdlog/-/spdlog-0.7.2.tgz#9298753d7694b9ee9bbfd7e01ea1e4c6ace1e64d"
integrity sha512-rHfWCaWMD4NindDnql6rc6kn7Bs8JR92jhiUpCl3D6v+jYcQ6GozMLig0RliOOR8st5mU+IHLZnr15fBys5x/Q==
dependencies:
bindings "^1.3.0"
mkdirp "^0.5.1"
nan "^2.8.0"
string-replace-loader@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/string-replace-loader/-/string-replace-loader-2.1.1.tgz#b72e7b57b6ef04efe615aff0ad989b5c14ca63d1"
integrity sha512-0Nvw1LDclF45AFNuYPcD2Jvkv0mwb/dQSnJZMvhqGrT+zzmrpG3OJFD600qfQfNUd5aqfp7fCm2mQMfF7zLbyQ==
dependencies:
loader-utils "^1.1.0"
schema-utils "^0.4.5"
uri-js@^4.2.2:
version "4.2.2"
resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0"
integrity sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==
dependencies:
punycode "^2.1.0"

View File

@ -1,47 +1,2 @@
import { logger, field, time } from "@coder/logger";
import { load } from "@coder/vscode";
import "./index.scss";
const loadTime = time(2500);
logger.info("Loading IDE");
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)`;
});
}
load().then(() => {
if (overlay) {
overlay.style.opacity = "0";
overlay.addEventListener("transitionend", () => {
overlay.remove();
});
}
}).catch((error: 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);
}
}).finally(() => {
logger.info("Load completed", field("duration", loadTime));
});
import "@coder/vscode";

View File

@ -0,0 +1,8 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"target": "es5",
"lib": ["dom", "esnext"],
"importHelpers": true
}
}

View File

@ -0,0 +1,103 @@
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const { BundleAnalyzerPlugin } = require("webpack-bundle-analyzer");
const PreloadWebpackPlugin = require("preload-webpack-plugin");
const HappyPack = require("happypack");
const root = path.resolve(__dirname, "..", "..");
const fills = path.join(root, "packages", "ide", "src", "fill");
const vsFills = path.join(root, "packages", "vscode", "src", "fill");
const merge = require("webpack-merge");
module.exports = merge({
devtool: "source-map",
entry: "./packages/web/src/index.ts",
output: {
chunkFilename: "[name]-[hash:6].bundle.js",
path: path.join(root, "dist"),
filename: "[hash:6].bundle.js",
},
module: {
rules: [{
loader: "string-replace-loader",
test: /\.(j|t)s/,
options: {
multiple: [{
search: "require\\.toUrl\\(",
replace: "requireToUrl(",
flags: "g",
}, {
search: "require\\.__\\$__nodeRequire",
replace: "require",
flags: "g",
}, {
search: "\\.attributes\\[([^\\]]+)\\] = ([^;]+)",
replace: ".setAttribute($1, $2)",
flags: "g",
}],
},
}],
},
resolve: {
alias: {
"gc-signals": path.join(fills, "empty.ts"),
"selenium-webdriver": path.join(fills, "empty.ts"),
"vscode": path.join(fills, "empty.ts"),
"vscode-fsevents": path.join(fills, "empty.ts"),
"vsda": path.join(fills, "empty.ts"),
"windows-foreground-love": path.join(fills, "empty.ts"),
"windows-mutex": path.join(fills, "empty.ts"),
"windows-process-tree": path.join(fills, "empty.ts"),
"vscode-sqlite3": path.join(fills, "empty.ts"),
"tls": path.join(fills, "empty.ts"),
"native-is-elevated": path.join(fills, "empty.ts"),
"crypto": "crypto-browserify",
"http": "http-browserify",
"child_process": path.join(fills, "child_process.ts"),
"os": path.join(fills, "os.ts"),
"fs": path.join(fills, "fs.ts"),
"net": path.join(fills, "net.ts"),
"util": path.join(fills, "util.ts"),
"electron": path.join(fills, "electron.ts"),
"native-keymap": path.join(vsFills, "native-keymap.ts"),
"node-pty": path.join(vsFills, "node-pty.ts"),
"graceful-fs": path.join(vsFills, "graceful-fs.ts"),
"spdlog": path.join(vsFills, "spdlog.ts"),
"vs/base/node/paths": path.join(vsFills, "paths.ts"),
"vs/base/common/amd": path.join(vsFills, "amd.ts"),
"vs/platform/node/product": path.join(vsFills, "product.ts"),
"vs/platform/node/package": path.join(vsFills, "package.ts"),
"vs": path.join(root, "lib", "vscode", "src", "vs"),
},
},
resolveLoader: {
alias: {
"vs/css": path.join(vsFills, "css.js"),
},
},
plugins: [
new HtmlWebpackPlugin({
template: "packages/web/src/index.html",
}),
new PreloadWebpackPlugin({
rel: "preload",
as: "script",
}),
new HappyPack({
id: "ts",
threads: 2,
loaders: [{
path: "ts-loader",
query: {
happyPackMode: true,
configFile: path.join(__dirname, "tsconfig.json"),
},
}],
}),
],
target: "web",
}, require(path.join(root, "scripts", "webpack.general.config.js"))());

View File

@ -0,0 +1,13 @@
const webpack = require("webpack");
const merge = require("webpack-merge");
module.exports = merge(require("./webpack.common.config.js"), {
devtool: "cheap-module-eval-source-map",
entry: [
"webpack-hot-middleware/client?reload=true&quiet=true",
"./packages/web/src/index.ts"
],
plugins: [
new webpack.HotModuleReplacementPlugin(),
]
});

View File

@ -1,178 +0,0 @@
diff --git a/src/vs/base/browser/ui/iconLabel/iconlabel.css b/src/vs/base/browser/ui/iconLabel/iconlabel.css
index 651843fcc9..aa31b52cb9 100644
--- a/src/vs/base/browser/ui/iconLabel/iconlabel.css
+++ b/src/vs/base/browser/ui/iconLabel/iconlabel.css
@@ -32,6 +32,7 @@
.monaco-icon-label > .monaco-icon-label-description-container {
overflow: hidden; /* this causes the label/description to shrink first if decorations are enabled */
text-overflow: ellipsis;
+ margin-right: auto;
}
.monaco-icon-label > .monaco-icon-label-description-container > .label-name {
@@ -39,6 +40,12 @@
white-space: pre; /* enable to show labels that include multiple whitespaces */
}
+.monaco-icon-label > .decorations-wrapper {
+ display: flex;
+ flex-direction: row;
+ padding-right: 12px;
+}
+
.monaco-icon-label > .monaco-icon-label-description-container > .label-description {
opacity: .7;
margin-left: 0.5em;
@@ -56,6 +63,5 @@
font-size: 90%;
font-weight: 600;
padding: 0 12px 0 5px;
- margin-left: auto;
text-align: center;
}
diff --git a/src/vs/workbench/browser/parts/activitybar/media/activitybarpart.css b/src/vs/workbench/browser/parts/activitybar/media/activitybarpart.css
index 5a92b2e1f5..1d3c735e75 100644
--- a/src/vs/workbench/browser/parts/activitybar/media/activitybarpart.css
+++ b/src/vs/workbench/browser/parts/activitybar/media/activitybarpart.css
@@ -4,25 +4,130 @@
*--------------------------------------------------------------------------------------------*/
.monaco-workbench > .part.activitybar {
- width: 50px;
+ width: 50px;
}
.monaco-workbench > .activitybar > .content {
- height: 100%;
- display: flex;
- flex-direction: column;
- justify-content: space-between;
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+ justify-content: space-between;
}
.monaco-workbench > .activitybar > .content .monaco-action-bar {
- text-align: left;
- background-color: inherit;
+ text-align: left;
+ background-color: inherit;
}
.monaco-workbench > .activitybar .action-item:focus {
- outline: 0 !important; /* activity bar indicates focus custom */
+ outline: 0 !important; /* activity bar indicates focus custom */
}
.monaco-workbench .activitybar > .content > .composite-bar > .monaco-action-bar .action-label.toggle-more {
- -webkit-mask: url('ellipsis-global.svg') no-repeat 50% 50%;
-}
\ No newline at end of file
+ -webkit-mask: url("ellipsis-global.svg") no-repeat 50% 50%;
+}
+
+.monaco-workbench .activitybar > .content > .extras-bar {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ overflow: visible;
+}
+
+.monaco-workbench .activitybar > .content > .extras-bar .toggle-terminal {
+ transition: 500ms color ease;
+ opacity: 0.65;
+ filter: brightness(115%);
+ padding-top: 10px;
+ padding-bottom: 10px;
+}
+
+.monaco-workbench .activitybar > .content > .extras-bar .toggle-terminal:hover {
+ opacity: 1;
+}
+
+.monaco-workbench .activitybar > .content > .extras-bar .toggle-terminal.disabled {
+ cursor: disabled;
+ opacity: 0.45 !important;
+}
+
+.monaco-workbench .activitybar > .content > .extras-bar .toggle-terminal > .icon {
+ text-align: center;
+ display: block;
+}
+
+.monaco-workbench .activitybar > .content > .extras-bar .toggle-terminal > .icon > svg {
+ width: 29px;
+ fill: currentColor;
+}
+
+.monaco-workbench .activitybar > .content > .extras-bar .fasttime {
+ transition: 500ms color ease;
+ opacity: 0.65;
+ filter: brightness(115%);
+}
+
+.monaco-workbench .activitybar > .content > .extras-bar .fasttime:hover {
+ opacity: 1;
+}
+
+.monaco-workbench .activitybar > .content > .extras-bar .fasttime.disabled {
+ cursor: disabled;
+ opacity: 0.45 !important;
+}
+
+.monaco-workbench .activitybar > .content > .extras-bar .fasttime > .icon {
+ text-align: center;
+ display: block;
+}
+
+.monaco-workbench .activitybar > .content > .extras-bar .fasttime > .icon > svg {
+ width: 22px;
+ fill: currentColor;
+}
+
+.monaco-workbench .activitybar > .content > .extras-bar .fasttime > .text {
+ font-size: 12px;
+ text-align: center;
+}
+
+.monaco-workbench .activitybar > .content > .extras-bar .fasttime > .text.unknown {
+ font-size: 8px;
+ opacity: 0;
+}
+
+.monaco-workbench .activitybar > .content > .extras-bar > .feedback {
+ transition: 500ms color ease;
+ padding-top: 10px;
+ padding-bottom: 10px;
+ margin-left: 0px;
+ margin-top: auto;
+ flex: 0;
+ cursor: default;
+}
+
+.monaco-workbench .activitybar > .content > .extras-bar .feedback > .icon {
+ text-align: center;
+ display: block;
+ opacity: 0.65;
+ filter: brightness(115%);
+ cursor: pointer;
+}
+
+.monaco-workbench .activitybar > .content > .extras-bar .feedback .feedback {
+ position: initial;
+ margin-left: 0px;
+}
+
+.monaco-workbench .activitybar > .content > .extras-bar .feedback .feedback-dropdown {
+ bottom: -63px;
+}
+
+.monaco-workbench .activitybar > .content > .extras-bar .feedback:hover > .icon {
+ opacity: 1;
+}
+
+.monaco-workbench .activitybar > .content > .extras-bar .feedback > .icon > svg {
+ width: 29px;
+ fill: currentColor;
+}

File diff suppressed because it is too large Load Diff

View File

@ -7,7 +7,7 @@ const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const root = path.join(__dirname, "..");
module.exports = {
module.exports = (options = {}) => ({
context: root,
devtool: "source-map",
// entry: "./packages/app/src/index.ts",
@ -67,15 +67,6 @@ module.exports = {
path.join(root, "node_modules"),
],
},
devServer: {
hot: true,
port: 3000,
stats: {
all: false, // Fallback for options not defined.
errors: true,
warnings: true,
},
},
plugins: [
new HappyPack({
id: "ts",
@ -84,6 +75,7 @@ module.exports = {
path: "ts-loader",
query: {
happyPackMode: true,
compilerOptions: options.typescriptCompilerOptions,
},
}],
}),
@ -101,4 +93,4 @@ module.exports = {
errors: true,
warnings: true,
},
};
});

View File

@ -8,7 +8,6 @@
"declaration": true,
"sourceMap": true,
"strict": true,
"importHelpers": true,
"experimentalDecorators": true,
"plugins": [
{

View File

@ -1,66 +0,0 @@
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const { BundleAnalyzerPlugin } = require("webpack-bundle-analyzer");
const WriteFilePlugin = require("write-file-webpack-plugin");
const PreloadWebpackPlugin = require("preload-webpack-plugin");
const root = __dirname;
const fills = path.join(root, "packages", "ide", "src", "fill");
const vscodeFills = path.join(root, "packages", "vscode", "src", "fill");
const merge = require("webpack-merge");
module.exports = merge({
devtool: "eval",
entry: "./packages/web/src/index.ts",
output: {
chunkFilename: "[name]-[hash:6].bundle.js",
path: path.join(root, "dist"),
filename: "[hash:6].bundle.js",
},
resolve: {
alias: {
"native-keymap": path.join(vscodeFills, "native-keymap.ts"),
"node-pty": path.join(vscodeFills, "node-pty.ts"),
"gc-signals": path.join(fills, "empty.ts"),
"selenium-webdriver": path.join(fills, "empty.ts"),
"vscode": path.join(fills, "empty.ts"),
"vscode-fsevents": path.join(fills, "empty.ts"),
"vsda": path.join(fills, "empty.ts"),
"windows-foreground-love": path.join(fills, "empty.ts"),
"windows-mutex": path.join(fills, "empty.ts"),
"windows-process-tree": path.join(fills, "empty.ts"),
"crypto": "crypto-browserify",
"http": "http-browserify",
"os": "os-browserify",
"child_process": path.join(fills, "child_process.ts"),
"fs": path.join(fills, "fs.ts"),
"net": path.join(fills, "net.ts"),
"util": path.join(fills, "util.ts"),
"electron": path.join(fills, "electron.ts"),
"vs": path.join(root, "lib", "vscode", "src", "vs"),
},
},
resolveLoader: {
alias: {
"vs/css": path.join(vscodeFills, "css.js"),
},
},
plugins: [
new HtmlWebpackPlugin({
template: "packages/web/src/index.html",
}),
new PreloadWebpackPlugin({
rel: "preload",
as: "script",
}),
new WriteFilePlugin({
exitOnErrors: false,
}),
],
target: "web",
}, require("./scripts/webpack.general.config.js"));

View File

@ -3971,7 +3971,7 @@ querystring-es3@^0.2.0:
resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73"
integrity sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=
querystring@0.2.0:
querystring@0.2.0, querystring@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620"
integrity sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=
@ -4312,7 +4312,7 @@ sax@^1.2.4:
resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==
schema-utils@^0.4.4:
schema-utils@^0.4.4, schema-utils@^0.4.5:
version "0.4.7"
resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-0.4.7.tgz#ba74f597d2be2ea880131746ee17d0a093c68187"
integrity sha512-v/iwU6wvwGK8HbU9yi3/nhGzP0yGSuhQMzL6ySiec1FSrZZDkhm4noOSWzrNFo/jEc+SJY6jRTwuwbSXJPDUnQ==
@ -4726,6 +4726,14 @@ stream-shift@^1.0.0:
resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.0.tgz#d5c752825e5367e786f78e18e445ea223a155952"
integrity sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=
string-replace-loader@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/string-replace-loader/-/string-replace-loader-2.1.1.tgz#b72e7b57b6ef04efe615aff0ad989b5c14ca63d1"
integrity sha512-0Nvw1LDclF45AFNuYPcD2Jvkv0mwb/dQSnJZMvhqGrT+zzmrpG3OJFD600qfQfNUd5aqfp7fCm2mQMfF7zLbyQ==
dependencies:
loader-utils "^1.1.0"
schema-utils "^0.4.5"
string-width@^1.0.1, string-width@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3"
@ -5342,6 +5350,16 @@ webpack-dev-middleware@3.4.0:
range-parser "^1.0.3"
webpack-log "^2.0.0"
webpack-dev-middleware@^3.5.0:
version "3.5.0"
resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-3.5.0.tgz#fff0a07b0461314fb6ca82df3642c2423f768429"
integrity sha512-1Zie7+dMr4Vv3nGyhr8mxGQkzTQK1PTS8K3yJ4yB1mfRGwO1DzQibgmNfUqbEfQY6eEtEEUzC+o7vhpm/Sfn5w==
dependencies:
memory-fs "~0.4.1"
mime "^2.3.1"
range-parser "^1.0.3"
webpack-log "^2.0.0"
webpack-dev-server@^3.1.14:
version "3.1.14"
resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-3.1.14.tgz#60fb229b997fc5a0a1fc6237421030180959d469"
@ -5378,6 +5396,16 @@ webpack-dev-server@^3.1.14:
webpack-log "^2.0.0"
yargs "12.0.2"
webpack-hot-middleware@^2.24.3:
version "2.24.3"
resolved "https://registry.yarnpkg.com/webpack-hot-middleware/-/webpack-hot-middleware-2.24.3.tgz#5bb76259a8fc0d97463ab517640ba91d3382d4a6"
integrity sha512-pPlmcdoR2Fn6UhYjAhp1g/IJy1Yc9hD+T6O9mjRcWV2pFbBjIFoJXhP0CoD0xPOhWJuWXuZXGBga9ybbOdzXpg==
dependencies:
ansi-html "0.0.7"
html-entities "^1.2.0"
querystring "^0.2.0"
strip-ansi "^3.0.0"
webpack-log@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/webpack-log/-/webpack-log-2.0.0.tgz#5b7928e0637593f119d32f6227c1e0ac31e1b47f"