Skip to content

Commit b865d62

Browse files
code-asherkylecarbs
authored andcommitted
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
1 parent 85b627c commit b865d62

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

80 files changed

+5217
-9731
lines changed

.gitignore

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1-
lib/vscode
1+
lib/vscode*
2+
lib/VSCode*
23
node_modules
34
dist

package.json

+5-3
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@
99
"vscode:clone": "mkdir -p ./lib && test -d ./lib/vscode || git clone https://github.com/Microsoft/vscode/ ./lib/vscode",
1010
"vscode:install": "cd ./lib/vscode && git checkout tags/1.30.1 && yarn",
1111
"vscode": "npm-run-all vscode:*",
12-
"packages:install": "cd ./packages && yarn && ts-node ../scripts/install-packages.ts",
12+
"packages:install": "cd ./packages && yarn",
1313
"postinstall": "npm-run-all --parallel vscode packages:install build:rules",
14-
"start": "webpack-dev-server --hot --config ./webpack.config.app.js",
14+
"start": "cd ./packages/server && yarn start",
1515
"test": "cd ./packages && yarn test"
1616
},
1717
"devDependencies": {
@@ -26,9 +26,9 @@
2626
"mini-css-extract-plugin": "^0.5.0",
2727
"node-sass": "^4.11.0",
2828
"npm-run-all": "^4.1.5",
29-
"os-browserify": "^0.3.0",
3029
"preload-webpack-plugin": "^3.0.0-beta.2",
3130
"sass-loader": "^7.1.0",
31+
"string-replace-loader": "^2.1.1",
3232
"style-loader": "^0.23.1",
3333
"ts-loader": "^5.3.3",
3434
"ts-node": "^7.0.1",
@@ -39,7 +39,9 @@
3939
"webpack": "^4.28.4",
4040
"webpack-bundle-analyzer": "^3.0.3",
4141
"webpack-cli": "^3.2.1",
42+
"webpack-dev-middleware": "^3.5.0",
4243
"webpack-dev-server": "^3.1.14",
44+
"webpack-hot-middleware": "^2.24.3",
4345
"write-file-webpack-plugin": "^4.5.0"
4446
},
4547
"dependencies": {

packages/ide/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
22
"name": "@coder/ide",
33
"description": "Browser-based IDE client abstraction.",
4-
"main": "src/index.ts"
4+
"main": "src/index.ts"
55
}

packages/ide/src/client.ts

+108-28
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,26 @@
1-
import { exec } from "child_process";
2-
import { promisify } from "util";
31
import { field, logger, time, Time } from "@coder/logger";
4-
import { escapePath } from "@coder/protocol";
5-
import { retry } from "./retry";
2+
import { InitData } from "@coder/protocol";
3+
import { retry, Retry } from "./retry";
4+
import { client } from "./fill/client";
5+
import { Clipboard, clipboard } from "./fill/clipboard";
6+
7+
export interface IURI {
8+
9+
readonly path: string;
10+
readonly fsPath: string;
11+
readonly scheme: string;
12+
13+
}
14+
15+
export interface IURIFactory {
16+
17+
/**
18+
* Convert the object to an instance of a real URI.
19+
*/
20+
create<T extends IURI>(uri: IURI): T;
21+
file(path: string): IURI;
22+
parse(raw: string): IURI;
623

7-
export interface IClientOptions {
8-
mkDirs?: string[];
924
}
1025

1126
/**
@@ -14,48 +29,96 @@ export interface IClientOptions {
1429
* Everything the client provides is asynchronous so you can wait on what
1530
* you need from it without blocking anything else.
1631
*
17-
* It also provides task management to help asynchronously load and time
18-
* external code.
32+
* It also provides task management to help asynchronously load and time code.
1933
*/
20-
export class Client {
34+
export abstract class Client {
2135

22-
public readonly mkDirs: Promise<void>;
36+
public readonly retry: Retry = retry;
37+
public readonly clipboard: Clipboard = clipboard;
38+
public readonly uriFactory: IURIFactory;
2339
private start: Time | undefined;
2440
private readonly progressElement: HTMLElement | undefined;
25-
private tasks: string[];
26-
private finishedTaskCount: number;
41+
private tasks: string[] = [];
42+
private finishedTaskCount = 0;
43+
private readonly loadTime: Time;
44+
45+
public constructor() {
46+
logger.info("Loading IDE");
47+
48+
this.loadTime = time(2500);
49+
50+
const overlay = document.getElementById("overlay");
51+
const logo = document.getElementById("logo");
52+
const msgElement = overlay
53+
? overlay.querySelector(".message") as HTMLElement
54+
: undefined;
55+
56+
if (overlay && logo) {
57+
overlay.addEventListener("mousemove", (event) => {
58+
const xPos = ((event.clientX - logo.offsetLeft) / 24).toFixed(2);
59+
const yPos = ((logo.offsetTop - event.clientY) / 24).toFixed(2);
60+
61+
logo.style.transform = `perspective(200px) rotateX(${yPos}deg) rotateY(${xPos}deg)`;
62+
});
63+
}
2764

28-
public constructor(options: IClientOptions) {
29-
this.tasks = [];
30-
this.finishedTaskCount = 0;
3165
this.progressElement = typeof document !== "undefined"
3266
? document.querySelector("#fill") as HTMLElement
3367
: undefined;
3468

35-
this.mkDirs = this.wrapTask("Creating directories", 100, async () => {
36-
if (options.mkDirs && options.mkDirs.length > 0) {
37-
await promisify(exec)(`mkdir -p ${options.mkDirs.map(escapePath).join(" ")}`);
38-
}
69+
require("path").posix = require("path");
70+
71+
window.addEventListener("contextmenu", (event) => {
72+
event.preventDefault();
3973
});
4074

4175
// Prevent Firefox from trying to reconnect when the page unloads.
4276
window.addEventListener("unload", () => {
43-
retry.block();
77+
this.retry.block();
78+
logger.info("Unloaded");
79+
});
80+
81+
this.uriFactory = this.createUriFactory();
82+
83+
this.initialize().then(() => {
84+
if (overlay) {
85+
overlay.style.opacity = "0";
86+
overlay.addEventListener("transitionend", () => {
87+
overlay.remove();
88+
});
89+
}
90+
logger.info("Load completed", field("duration", this.loadTime));
91+
}).catch((error) => {
92+
logger.error(error.message);
93+
if (overlay) {
94+
overlay.classList.add("error");
95+
}
96+
if (msgElement) {
97+
const button = document.createElement("div");
98+
button.className = "reload-button";
99+
button.innerText = "Reload";
100+
button.addEventListener("click", () => {
101+
location.reload();
102+
});
103+
msgElement.innerText = `Failed to load: ${error.message}.`;
104+
msgElement.parentElement!.appendChild(button);
105+
}
106+
logger.warn("Load completed with errors", field("duration", this.loadTime));
44107
});
45108
}
46109

47110
/**
48111
* Wrap a task in some logging, timing, and progress updates. Can optionally
49112
* wait on other tasks which won't count towards this task's time.
50113
*/
51-
public async wrapTask<T>(description: string, duration: number, task: () => Promise<T>): Promise<T>;
52-
public async wrapTask<T, V>(description: string, duration: number, task: (v: V) => Promise<T>, t: Promise<V>): Promise<T>;
53-
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>;
54-
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>;
55-
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>;
56-
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>;
57-
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>;
58-
public async wrapTask<T>(
114+
public async task<T>(description: string, duration: number, task: () => Promise<T>): Promise<T>;
115+
public async task<T, V>(description: string, duration: number, task: (v: V) => Promise<T>, t: Promise<V>): Promise<T>;
116+
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>;
117+
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>;
118+
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>;
119+
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>;
120+
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>;
121+
public async task<T>(
59122
description: string, duration: number = 100, task: (...args: any[]) => Promise<T>, ...after: Array<Promise<any>> // tslint:disable-line no-any
60123
): Promise<T> {
61124
this.tasks.push(description);
@@ -97,4 +160,21 @@ export class Client {
97160
}
98161
}
99162

163+
/**
164+
* A promise that resolves with initialization data.
165+
*/
166+
public get initData(): Promise<InitData> {
167+
return client.initData;
168+
}
169+
170+
/**
171+
* Initialize the IDE.
172+
*/
173+
protected abstract initialize(): Promise<void>;
174+
175+
/**
176+
* Create URI factory.
177+
*/
178+
protected abstract createUriFactory(): IURIFactory;
179+
100180
}

packages/ide/src/fill/client.ts

+13-22
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Emitter } from "@coder/events";
2-
import { logger, field } from "@coder/logger";
2+
import { field, logger } from "@coder/logger";
33
import { Client, ReadWriteConnection } from "@coder/protocol";
44
import { retry } from "../retry";
55

@@ -10,27 +10,20 @@ import { retry } from "../retry";
1010
class Connection implements ReadWriteConnection {
1111

1212
private activeSocket: WebSocket | undefined;
13-
private readonly messageEmitter: Emitter<Uint8Array>;
14-
private readonly closeEmitter: Emitter<void>;
15-
private readonly upEmitter: Emitter<void>;
16-
private readonly downEmitter: Emitter<void>;
17-
private readonly messageBuffer: Uint8Array[];
13+
private readonly messageEmitter: Emitter<Uint8Array> = new Emitter();
14+
private readonly closeEmitter: Emitter<void> = new Emitter();
15+
private readonly upEmitter: Emitter<void> = new Emitter();
16+
private readonly downEmitter: Emitter<void> = new Emitter();
17+
private readonly messageBuffer: Uint8Array[] = [];
1818
private socketTimeoutDelay = 60 * 1000;
1919
private retryName = "Web socket";
20-
private isUp: boolean | undefined;
21-
private closed: boolean | undefined;
20+
private isUp: boolean = false;
21+
private closed: boolean = false;
2222

2323
public constructor() {
24-
this.messageEmitter = new Emitter();
25-
this.closeEmitter = new Emitter();
26-
this.upEmitter = new Emitter();
27-
this.downEmitter = new Emitter();
28-
this.messageBuffer = [];
2924
retry.register(this.retryName, () => this.connect());
30-
this.connect().catch(() => {
31-
retry.block(this.retryName);
32-
retry.run(this.retryName);
33-
});
25+
retry.block(this.retryName);
26+
retry.run(this.retryName);
3427
}
3528

3629
/**
@@ -72,7 +65,7 @@ class Connection implements ReadWriteConnection {
7265
this.closeEmitter.emit();
7366
}
7467

75-
/**
68+
/**
7669
* Connect to the server.
7770
*/
7871
private async connect(): Promise<void> {
@@ -116,7 +109,7 @@ class Connection implements ReadWriteConnection {
116109
private async openSocket(): Promise<WebSocket> {
117110
this.dispose();
118111
const socket = new WebSocket(
119-
`${location.protocol === "https" ? "wss" : "ws"}://${location.host}/websocket`,
112+
`${location.protocol === "https" ? "wss" : "ws"}://${location.host}`,
120113
);
121114
socket.binaryType = "arraybuffer";
122115
this.activeSocket = socket;
@@ -153,7 +146,5 @@ class Connection implements ReadWriteConnection {
153146

154147
}
155148

156-
/**
157-
* A client for proxying Node APIs based on web sockets.
158-
*/
149+
// Global instance so all fills can use the same client.
159150
export const client = new Client(new Connection());

0 commit comments

Comments
 (0)