-
Notifications
You must be signed in to change notification settings - Fork 5.9k
Spawn vscode on demand #4499
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Spawn vscode on demand #4499
Changes from all commits
ad7da73
e56d970
4e81353
22231f4
0de57fe
f2b3156
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 const createVSServerRouter = async (args: DefaultedArgs): Promise<VSServerResult> => { | ||
// See ../../../vendor/modules/code-oss-dev/src/vs/server/main.js. | ||
const createVSServer = await loadAMDModule<CodeServerLib.CreateServer>( | ||
"vs/server/remoteExtensionHostAgent", | ||
"createServer", | ||
) | ||
export class CodeServerRouteWrapper { | ||
/** Assigned in `ensureCodeServerLoaded` */ | ||
private _codeServerMain!: CodeServerLib.IServerAPI | ||
private _wsRouterWrapper = WsRouter() | ||
public router = express.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, | ||
}) | ||
public get wsRouter() { | ||
return this._wsRouterWrapper.router | ||
} | ||
|
||
const router = express.Router() | ||
const wsRouter = WsRouter() | ||
//#region Route Handlers | ||
|
||
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) => { | ||
private $proxyRequest: express.Handler = async (req, res, next) => { | ||
// We allow certain errors to propagate so that other routers may handle requests | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👏 ❓ why start the name with There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It’s something of a convention I’ve seen help with readability, similarly to prefixing private methods with |
||
// outside VS Code | ||
const requestErrorHandler = (error: any) => { | ||
if (error instanceof Error && ["EntryNotFound", "FileNotFound", "HttpError"].includes(error.message)) { | ||
next() | ||
} | ||
|
||
errorHandler(error, req, res, next) | ||
}) | ||
} | ||
|
||
req.once("error", requestErrorHandler) | ||
|
||
codeServerMain.handleRequest(req, res) | ||
}) | ||
this._codeServerMain.handleRequest(req, res) | ||
} | ||
|
||
wsRouter.ws("/", ensureAuthenticated, (req) => { | ||
codeServerMain.handleUpgrade(req, req.socket) | ||
private $proxyWebsocket = async (req: WebsocketRequest) => { | ||
this._codeServerMain.handleUpgrade(req, req.socket) | ||
|
||
req.socket.resume() | ||
}) | ||
} | ||
|
||
//#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<CodeServerLib.CreateServer>( | ||
"vs/server/remoteExtensionHostAgent", | ||
"createServer", | ||
) | ||
|
||
try { | ||
this._codeServerMain = await createVSServer(null, { | ||
connectionToken: "0000", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ❓ What's the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Connection token is used by VS Code as a basic form of client-side security. At the moment we’ve disabled its usage, but I think it’s worth looking at again as upstream matures the feature. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Related: #4479 |
||
...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) | ||
} | ||
|
||
return { | ||
router, | ||
wsRouter, | ||
codeServerMain, | ||
dispose() { | ||
this._codeServerMain?.dispose() | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
so much cleaner 👏