|
| 1 | +import { logger } from "@coder/logger" |
1 | 2 | import * as express from "express"
|
2 |
| -import { DefaultedArgs } from "../cli" |
| 3 | +import { WebsocketRequest } from "../../../typings/pluginapi" |
| 4 | +import { logError } from "../../common/util" |
| 5 | +import { isDevMode } from "../constants" |
3 | 6 | import { ensureAuthenticated, authenticated, redirect } from "../http"
|
4 | 7 | import { loadAMDModule } from "../util"
|
5 |
| -import { Router as WsRouter, WebsocketRouter } from "../wsRouter" |
| 8 | +import { Router as WsRouter } from "../wsRouter" |
6 | 9 | import { errorHandler } from "./errors"
|
7 | 10 |
|
8 |
| -export interface VSServerResult { |
9 |
| - router: express.Router |
10 |
| - wsRouter: WebsocketRouter |
11 |
| - codeServerMain: CodeServerLib.IServerAPI |
12 |
| -} |
13 |
| - |
14 |
| -export const createVSServerRouter = async (args: DefaultedArgs): Promise<VSServerResult> => { |
15 |
| - // See ../../../vendor/modules/code-oss-dev/src/vs/server/main.js. |
16 |
| - const createVSServer = await loadAMDModule<CodeServerLib.CreateServer>( |
17 |
| - "vs/server/remoteExtensionHostAgent", |
18 |
| - "createServer", |
19 |
| - ) |
| 11 | +export class CodeServerRouteWrapper { |
| 12 | + /** Assigned in `ensureCodeServerLoaded` */ |
| 13 | + private _codeServerMain!: CodeServerLib.IServerAPI |
| 14 | + private _wsRouterWrapper = WsRouter() |
| 15 | + public router = express.Router() |
20 | 16 |
|
21 |
| - const codeServerMain = await createVSServer(null, { |
22 |
| - connectionToken: "0000", |
23 |
| - ...args, |
24 |
| - // For some reason VS Code takes the port as a string. |
25 |
| - port: typeof args.port !== "undefined" ? args.port.toString() : undefined, |
26 |
| - }) |
| 17 | + public get wsRouter() { |
| 18 | + return this._wsRouterWrapper.router |
| 19 | + } |
27 | 20 |
|
28 |
| - const router = express.Router() |
29 |
| - const wsRouter = WsRouter() |
| 21 | + //#region Route Handlers |
30 | 22 |
|
31 |
| - router.get("/", async (req, res, next) => { |
| 23 | + private $root: express.Handler = async (req, res, next) => { |
32 | 24 | const isAuthenticated = await authenticated(req)
|
| 25 | + |
33 | 26 | if (!isAuthenticated) {
|
34 | 27 | return redirect(req, res, "login", {
|
35 | 28 | // req.baseUrl can be blank if already at the root.
|
36 | 29 | to: req.baseUrl && req.baseUrl !== "/" ? req.baseUrl : undefined,
|
37 | 30 | })
|
38 | 31 | }
|
| 32 | + |
39 | 33 | next()
|
40 |
| - }) |
| 34 | + } |
41 | 35 |
|
42 |
| - router.all("*", ensureAuthenticated, (req, res, next) => { |
43 |
| - req.on("error", (error: any) => { |
| 36 | + private $proxyRequest: express.Handler = async (req, res, next) => { |
| 37 | + // We allow certain errors to propagate so that other routers may handle requests |
| 38 | + // outside VS Code |
| 39 | + const requestErrorHandler = (error: any) => { |
44 | 40 | if (error instanceof Error && ["EntryNotFound", "FileNotFound", "HttpError"].includes(error.message)) {
|
45 | 41 | next()
|
46 | 42 | }
|
47 | 43 |
|
48 | 44 | errorHandler(error, req, res, next)
|
49 |
| - }) |
| 45 | + } |
| 46 | + |
| 47 | + req.once("error", requestErrorHandler) |
50 | 48 |
|
51 |
| - codeServerMain.handleRequest(req, res) |
52 |
| - }) |
| 49 | + this._codeServerMain.handleRequest(req, res) |
| 50 | + } |
53 | 51 |
|
54 |
| - wsRouter.ws("/", ensureAuthenticated, (req) => { |
55 |
| - codeServerMain.handleUpgrade(req, req.socket) |
| 52 | + private $proxyWebsocket = async (req: WebsocketRequest) => { |
| 53 | + this._codeServerMain.handleUpgrade(req, req.socket) |
56 | 54 |
|
57 | 55 | req.socket.resume()
|
58 |
| - }) |
| 56 | + } |
| 57 | + |
| 58 | + //#endregion |
| 59 | + |
| 60 | + /** |
| 61 | + * Fetches a code server instance asynchronously to avoid an initial memory overhead. |
| 62 | + */ |
| 63 | + private ensureCodeServerLoaded: express.Handler = async (req, _res, next) => { |
| 64 | + if (this._codeServerMain) { |
| 65 | + return next() |
| 66 | + } |
| 67 | + |
| 68 | + const { args } = req |
| 69 | + |
| 70 | + /** |
| 71 | + * @file ../../../vendor/modules/code-oss-dev/src/vs/server/main.js |
| 72 | + */ |
| 73 | + const createVSServer = await loadAMDModule<CodeServerLib.CreateServer>( |
| 74 | + "vs/server/remoteExtensionHostAgent", |
| 75 | + "createServer", |
| 76 | + ) |
| 77 | + |
| 78 | + try { |
| 79 | + this._codeServerMain = await createVSServer(null, { |
| 80 | + connectionToken: "0000", |
| 81 | + ...args, |
| 82 | + // For some reason VS Code takes the port as a string. |
| 83 | + port: args.port?.toString(), |
| 84 | + }) |
| 85 | + } catch (createServerError) { |
| 86 | + logError(logger, "CodeServerRouteWrapper", createServerError) |
| 87 | + |
| 88 | + const loggedError = isDevMode ? new Error("VS Code may still be compiling...") : createServerError |
| 89 | + |
| 90 | + return next(loggedError) |
| 91 | + } |
| 92 | + |
| 93 | + return next() |
| 94 | + } |
| 95 | + |
| 96 | + constructor() { |
| 97 | + this.router.get("/", this.ensureCodeServerLoaded, this.$root) |
| 98 | + this.router.all("*", ensureAuthenticated, this.ensureCodeServerLoaded, this.$proxyRequest) |
| 99 | + this._wsRouterWrapper.ws("/", ensureAuthenticated, this.ensureCodeServerLoaded, this.$proxyWebsocket) |
| 100 | + } |
59 | 101 |
|
60 |
| - return { |
61 |
| - router, |
62 |
| - wsRouter, |
63 |
| - codeServerMain, |
| 102 | + dispose() { |
| 103 | + this._codeServerMain?.dispose() |
64 | 104 | }
|
65 | 105 | }
|
0 commit comments