diff --git a/src/node/routes/index.ts b/src/node/routes/index.ts index 7ca79fa7..0328ce22 100644 --- a/src/node/routes/index.ts +++ b/src/node/routes/index.ts @@ -10,7 +10,7 @@ import { HttpCode, HttpError } from "../../common/http" import { plural } from "../../common/util" import { App } from "../app" import { AuthType, DefaultedArgs } from "../cli" -import { commit, isDevMode, rootPath } from "../constants" +import { commit, rootPath } from "../constants" import { Heart } from "../heart" import { ensureAuthenticated, redirect } from "../http" import { PluginAPI } from "../plugin" @@ -23,7 +23,7 @@ import * as login from "./login" import * as logout from "./logout" import * as pathProxy from "./pathProxy" import * as update from "./update" -import { createVSServerRouter, VSServerResult } from "./vscode" +import { CodeServerRouteWrapper } from "./vscode" /** * Register all routes and middleware. @@ -138,20 +138,12 @@ export const register = async (app: App, args: DefaultedArgs): Promise { @@ -164,6 +156,6 @@ export const register = async (app: App, args: DefaultedArgs): Promise { heart.dispose() pluginApi?.dispose() - vscode?.codeServerMain.dispose() + vsServerRouteHandler.dispose() } } diff --git a/src/node/routes/vscode.ts b/src/node/routes/vscode.ts index 76efb934..55fb308e 100644 --- a/src/node/routes/vscode.ts +++ b/src/node/routes/vscode.ts @@ -1,65 +1,105 @@ +import { logger } from "@coder/logger" import * as express from "express" -import { DefaultedArgs } from "../cli" +import { WebsocketRequest } from "../../../typings/pluginapi" +import { logError } from "../../common/util" +import { isDevMode } from "../constants" import { ensureAuthenticated, authenticated, redirect } from "../http" import { loadAMDModule } from "../util" -import { Router as WsRouter, WebsocketRouter } from "../wsRouter" +import { Router as WsRouter } from "../wsRouter" import { errorHandler } from "./errors" -export interface VSServerResult { - router: express.Router - wsRouter: WebsocketRouter - codeServerMain: CodeServerLib.IServerAPI -} +export class CodeServerRouteWrapper { + /** Assigned in `ensureCodeServerLoaded` */ + private _codeServerMain!: CodeServerLib.IServerAPI + private _wsRouterWrapper = WsRouter() + public router = express.Router() -export const createVSServerRouter = async (args: DefaultedArgs): Promise => { - // See ../../../vendor/modules/code-oss-dev/src/vs/server/main.js. - const createVSServer = await loadAMDModule( - "vs/server/remoteExtensionHostAgent", - "createServer", - ) + public get wsRouter() { + return this._wsRouterWrapper.router + } - const codeServerMain = await createVSServer(null, { - connectionToken: "0000", - ...args, - // For some reason VS Code takes the port as a string. - port: typeof args.port !== "undefined" ? args.port.toString() : undefined, - }) + //#region Route Handlers - const router = express.Router() - const wsRouter = WsRouter() - - router.get("/", async (req, res, next) => { + private $root: express.Handler = async (req, res, next) => { const isAuthenticated = await authenticated(req) + if (!isAuthenticated) { return redirect(req, res, "login", { // req.baseUrl can be blank if already at the root. to: req.baseUrl && req.baseUrl !== "/" ? req.baseUrl : undefined, }) } - next() - }) - router.all("*", ensureAuthenticated, (req, res, next) => { - req.on("error", (error: any) => { + next() + } + + private $proxyRequest: express.Handler = async (req, res, next) => { + // We allow certain errors to propagate so that other routers may handle requests + // outside VS Code + const requestErrorHandler = (error: any) => { if (error instanceof Error && ["EntryNotFound", "FileNotFound", "HttpError"].includes(error.message)) { next() } errorHandler(error, req, res, next) - }) + } - codeServerMain.handleRequest(req, res) - }) + req.once("error", requestErrorHandler) - wsRouter.ws("/", ensureAuthenticated, (req) => { - codeServerMain.handleUpgrade(req, req.socket) + this._codeServerMain.handleRequest(req, res) + } + + private $proxyWebsocket = async (req: WebsocketRequest) => { + this._codeServerMain.handleUpgrade(req, req.socket) req.socket.resume() - }) + } - return { - router, - wsRouter, - codeServerMain, + //#endregion + + /** + * Fetches a code server instance asynchronously to avoid an initial memory overhead. + */ + private ensureCodeServerLoaded: express.Handler = async (req, _res, next) => { + if (this._codeServerMain) { + return next() + } + + const { args } = req + + /** + * @file ../../../vendor/modules/code-oss-dev/src/vs/server/main.js + */ + const createVSServer = await loadAMDModule( + "vs/server/remoteExtensionHostAgent", + "createServer", + ) + + try { + this._codeServerMain = await createVSServer(null, { + connectionToken: "0000", + ...args, + // For some reason VS Code takes the port as a string. + port: args.port?.toString(), + }) + } catch (createServerError) { + logError(logger, "CodeServerRouteWrapper", createServerError) + + const loggedError = isDevMode ? new Error("VS Code may still be compiling...") : createServerError + + return next(loggedError) + } + + return next() + } + + constructor() { + this.router.get("/", this.ensureCodeServerLoaded, this.$root) + this.router.all("*", ensureAuthenticated, this.ensureCodeServerLoaded, this.$proxyRequest) + this._wsRouterWrapper.ws("/", ensureAuthenticated, this.ensureCodeServerLoaded, this.$proxyWebsocket) + } + + dispose() { + this._codeServerMain?.dispose() } } diff --git a/vendor/package.json b/vendor/package.json index bdf69c89..e98b3e3a 100644 --- a/vendor/package.json +++ b/vendor/package.json @@ -7,6 +7,6 @@ "postinstall": "./postinstall.sh" }, "devDependencies": { - "code-oss-dev": "cdr/vscode#d62e8db202f80db7a42233cd56d04e6806109fb1" + "code-oss-dev": "cdr/vscode#8db6c9bb0bc065bdb905dc076f4d4234f126aff7" } } diff --git a/vendor/yarn.lock b/vendor/yarn.lock index 10acfc65..17f03ea4 100644 --- a/vendor/yarn.lock +++ b/vendor/yarn.lock @@ -296,9 +296,9 @@ clone-response@^1.0.2: dependencies: mimic-response "^1.0.0" -code-oss-dev@cdr/vscode#d62e8db202f80db7a42233cd56d04e6806109fb1: +code-oss-dev@cdr/vscode#8db6c9bb0bc065bdb905dc076f4d4234f126aff7: version "1.61.1" - resolved "https://codeload.github.com/cdr/vscode/tar.gz/d62e8db202f80db7a42233cd56d04e6806109fb1" + resolved "https://codeload.github.com/cdr/vscode/tar.gz/8db6c9bb0bc065bdb905dc076f4d4234f126aff7" dependencies: "@microsoft/applicationinsights-web" "^2.6.4" "@vscode/sqlite3" "4.0.12"