From 2819fd51e2318a6324b8d43a1f18096435e4f916 Mon Sep 17 00:00:00 2001 From: Asher Date: Wed, 22 Jul 2020 15:28:33 -0500 Subject: [PATCH] Remove unused endpoints - dashboard - app api --- ci/build/build-code-server.sh | 1 - ci/dev/watch.ts | 6 +- src/browser/pages/app.html | 28 --- src/browser/pages/app.ts | 37 ---- src/browser/pages/error.html | 2 +- src/browser/pages/home.css | 51 ------ src/browser/pages/home.html | 59 ------- src/browser/pages/login.html | 2 +- src/browser/pages/update.html | 2 +- src/browser/register.ts | 6 +- src/common/api.ts | 60 ------- src/common/http.ts | 8 - src/node/app/api.ts | 312 ---------------------------------- src/node/app/bin.ts | 30 ---- src/node/app/dashboard.ts | 147 ---------------- src/node/entry.ts | 8 +- 16 files changed, 11 insertions(+), 748 deletions(-) delete mode 100644 src/browser/pages/app.html delete mode 100644 src/browser/pages/app.ts delete mode 100644 src/browser/pages/home.css delete mode 100644 src/browser/pages/home.html delete mode 100644 src/common/api.ts delete mode 100644 src/node/app/api.ts delete mode 100644 src/node/app/bin.ts delete mode 100644 src/node/app/dashboard.ts diff --git a/ci/build/build-code-server.sh b/ci/build/build-code-server.sh index a48ab0e0..df528874 100755 --- a/ci/build/build-code-server.sh +++ b/ci/build/build-code-server.sh @@ -21,7 +21,6 @@ main() { --public-url "/static/$(git rev-parse HEAD)/dist" \ --out-dir dist \ $([[ $MINIFY ]] || echo --no-minify) \ - src/browser/pages/app.ts \ src/browser/register.ts \ src/browser/serviceWorker.ts } diff --git a/ci/dev/watch.ts b/ci/dev/watch.ts index 03ce2e42..fd144653 100644 --- a/ci/dev/watch.ts +++ b/ci/dev/watch.ts @@ -144,11 +144,7 @@ class Watcher { private createBundler(out = "dist"): Bundler { return new Bundler( - [ - path.join(this.rootPath, "src/browser/pages/app.ts"), - path.join(this.rootPath, "src/browser/register.ts"), - path.join(this.rootPath, "src/browser/serviceWorker.ts"), - ], + [path.join(this.rootPath, "src/browser/register.ts"), path.join(this.rootPath, "src/browser/serviceWorker.ts")], { outDir: path.join(this.rootPath, out), cacheDir: path.join(this.rootPath, ".cache"), diff --git a/src/browser/pages/app.html b/src/browser/pages/app.html deleted file mode 100644 index 551471a1..00000000 --- a/src/browser/pages/app.html +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - code-server - - - - - - - - - - - diff --git a/src/browser/pages/app.ts b/src/browser/pages/app.ts deleted file mode 100644 index f7162947..00000000 --- a/src/browser/pages/app.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { getOptions, normalize } from "../../common/util" -import { ApiEndpoint } from "../../common/http" - -import "./error.css" -import "./global.css" -import "./home.css" -import "./login.css" -import "./update.css" - -const options = getOptions() - -const isInput = (el: Element): el is HTMLInputElement => { - return !!(el as HTMLInputElement).name -} - -document.querySelectorAll("form").forEach((form) => { - if (!form.classList.contains("-x11")) { - return - } - form.addEventListener("submit", (event) => { - event.preventDefault() - const values: { [key: string]: string } = {} - Array.from(form.elements).forEach((element) => { - if (isInput(element)) { - values[element.name] = element.value - } - }) - fetch(normalize(`${options.base}/api/${ApiEndpoint.process}`), { - method: "POST", - body: JSON.stringify(values), - }) - }) -}) - -// TEMP: Until we can get the real ready event. -const event = new CustomEvent("ide-ready") -window.dispatchEvent(event) diff --git a/src/browser/pages/error.html b/src/browser/pages/error.html index 0ae7bb2b..12d6efe2 100644 --- a/src/browser/pages/error.html +++ b/src/browser/pages/error.html @@ -18,7 +18,7 @@ crossorigin="use-credentials" /> - + diff --git a/src/browser/pages/home.css b/src/browser/pages/home.css deleted file mode 100644 index d77d2640..00000000 --- a/src/browser/pages/home.css +++ /dev/null @@ -1,51 +0,0 @@ -.block-row { - display: flex; -} - -.block-row > .item { - flex: 1; - margin: 2px 0; -} - -.block-row > button.item { - background: none; - border: none; - cursor: pointer; - text-align: left; -} - -.block-row > .item > .sub { - font-size: 0.95em; -} - -.block-row .-link { - color: rgb(87, 114, 245); - display: block; - text-decoration: none; -} - -.block-row .-link:hover { - text-decoration: underline; -} - -.block-row > .item > .icon { - height: 1rem; - margin-right: 5px; - vertical-align: top; - width: 1rem; -} - -.block-row > .item > .icon.-missing { - background-color: rgba(87, 114, 245, 0.2); - display: inline-block; - text-align: center; -} - -.kill-form { - display: inline-block; -} - -.kill-form > .kill { - border-radius: 3px; - padding: 2px 5px; -} diff --git a/src/browser/pages/home.html b/src/browser/pages/home.html deleted file mode 100644 index 4fbe8fbd..00000000 --- a/src/browser/pages/home.html +++ /dev/null @@ -1,59 +0,0 @@ - - - - - - - code-server - - - - - - - -
-
-
-

Editors

-
Choose an editor to launch below.
-
-
- {{APP_LIST:EDITORS}} -
-
- -
-
-

Other

-
Choose an application to launch below.
-
-
- {{APP_LIST:OTHER}} -
-
- -
-
-

Version

-
Version information and updates.
-
-
- {{UPDATE:NAME}} -
-
-
- - - - diff --git a/src/browser/pages/login.html b/src/browser/pages/login.html index 37d51f20..788055d6 100644 --- a/src/browser/pages/login.html +++ b/src/browser/pages/login.html @@ -18,7 +18,7 @@ crossorigin="use-credentials" /> - + diff --git a/src/browser/pages/update.html b/src/browser/pages/update.html index e1506f0f..954d3010 100644 --- a/src/browser/pages/update.html +++ b/src/browser/pages/update.html @@ -18,7 +18,7 @@ crossorigin="use-credentials" /> - + diff --git a/src/browser/register.ts b/src/browser/register.ts index 9fb29c8e..3bc810e5 100644 --- a/src/browser/register.ts +++ b/src/browser/register.ts @@ -2,13 +2,17 @@ import { getOptions, normalize } from "../common/util" const options = getOptions() +import "./pages/error.css" +import "./pages/global.css" +import "./pages/login.css" + if ("serviceWorker" in navigator) { const path = normalize(`${options.base}/static/${options.commit}/dist/serviceWorker.js`) navigator.serviceWorker .register(path, { scope: options.base || "/", }) - .then(function () { + .then(() => { console.log("[Service Worker] registered") }) } diff --git a/src/common/api.ts b/src/common/api.ts deleted file mode 100644 index 2a2b14ea..00000000 --- a/src/common/api.ts +++ /dev/null @@ -1,60 +0,0 @@ -export interface Application { - readonly categories?: string[] - readonly comment?: string - readonly directory?: string - readonly exec?: string - readonly genericName?: string - readonly icon?: string - readonly installed?: boolean - readonly name: string - /** - * Path if this is a browser app (like VS Code). - */ - readonly path?: string - /** - * PID if this is a process. - */ - readonly pid?: number - readonly version?: string -} - -export interface ApplicationsResponse { - readonly applications: ReadonlyArray -} - -export enum SessionError { - FailedToStart = 4000, - Starting = 4001, - InvalidState = 4002, - Unknown = 4003, -} - -export interface SessionResponse { - /** - * Whether the process was spawned or an existing one was returned. - */ - created: boolean - pid: number -} - -export interface RecentResponse { - readonly paths: string[] - readonly workspaces: string[] -} - -export interface HealthRequest { - readonly event: "health" -} - -export type ClientMessage = HealthRequest - -export interface HealthResponse { - readonly event: "health" - readonly connections: number -} - -export type ServerMessage = HealthResponse - -export interface ReadyMessage { - protocol: string -} diff --git a/src/common/http.ts b/src/common/http.ts index a90cee37..8ecbaa34 100644 --- a/src/common/http.ts +++ b/src/common/http.ts @@ -14,11 +14,3 @@ export class HttpError extends Error { this.name = this.constructor.name } } - -export enum ApiEndpoint { - applications = "/applications", - process = "/process", - recent = "/recent", - run = "/run", - status = "/status", -} diff --git a/src/node/app/api.ts b/src/node/app/api.ts deleted file mode 100644 index 88519ee3..00000000 --- a/src/node/app/api.ts +++ /dev/null @@ -1,312 +0,0 @@ -import { field, logger } from "@coder/logger" -import * as cp from "child_process" -import * as fs from "fs-extra" -import * as http from "http" -import * as net from "net" -import * as path from "path" -import * as url from "url" -import * as WebSocket from "ws" -import { - Application, - ApplicationsResponse, - ClientMessage, - RecentResponse, - ServerMessage, - SessionError, - SessionResponse, -} from "../../common/api" -import { ApiEndpoint, HttpCode, HttpError } from "../../common/http" -import { HttpProvider, HttpProviderOptions, HttpResponse, HttpServer, Route } from "../http" -import { findApplications, findWhitelistedApplications, Vscode } from "./bin" -import { VscodeHttpProvider } from "./vscode" - -interface VsRecents { - [key: string]: (string | { configURIPath: string })[] -} - -type VsSettings = [string, string][] - -/** - * API HTTP provider. - */ -export class ApiHttpProvider extends HttpProvider { - private readonly ws = new WebSocket.Server({ noServer: true }) - - public constructor( - options: HttpProviderOptions, - private readonly server: HttpServer, - private readonly vscode: VscodeHttpProvider, - private readonly dataDir?: string, - ) { - super(options) - } - - public async handleRequest(route: Route, request: http.IncomingMessage): Promise { - this.ensureAuthenticated(request) - if (!this.isRoot(route)) { - throw new HttpError("Not found", HttpCode.NotFound) - } - - switch (route.base) { - case ApiEndpoint.applications: - this.ensureMethod(request) - return { - mime: "application/json", - content: { - applications: await this.applications(), - }, - } as HttpResponse - case ApiEndpoint.process: - return this.process(request) - case ApiEndpoint.recent: - this.ensureMethod(request) - return { - mime: "application/json", - content: await this.recent(), - } as HttpResponse - } - - throw new HttpError("Not found", HttpCode.NotFound) - } - - public async handleWebSocket( - route: Route, - request: http.IncomingMessage, - socket: net.Socket, - head: Buffer, - ): Promise { - if (!this.authenticated(request)) { - throw new Error("not authenticated") - } - switch (route.base) { - case ApiEndpoint.status: - return this.handleStatusSocket(request, socket, head) - case ApiEndpoint.run: - return this.handleRunSocket(route, request, socket, head) - } - - throw new HttpError("Not found", HttpCode.NotFound) - } - - private async handleStatusSocket(request: http.IncomingMessage, socket: net.Socket, head: Buffer): Promise { - const getMessageResponse = async (event: "health"): Promise => { - switch (event) { - case "health": - return { event, connections: await this.server.getConnections() } - default: - throw new Error("unexpected message") - } - } - - await new Promise((resolve) => { - this.ws.handleUpgrade(request, socket, head, (ws) => { - const send = (event: ServerMessage): void => { - ws.send(JSON.stringify(event)) - } - ws.on("message", (data) => { - logger.trace("got message", field("message", data)) - try { - const message: ClientMessage = JSON.parse(data.toString()) - getMessageResponse(message.event).then(send) - } catch (error) { - logger.error(error.message, field("message", data)) - } - }) - resolve() - }) - }) - } - - /** - * A socket that connects to the process. - */ - private async handleRunSocket( - _route: Route, - request: http.IncomingMessage, - socket: net.Socket, - head: Buffer, - ): Promise { - logger.debug("connecting to process") - const ws = await new Promise((resolve, reject) => { - this.ws.handleUpgrade(request, socket, head, (socket) => { - socket.binaryType = "arraybuffer" - - socket.on("error", (error) => { - socket.close(SessionError.FailedToStart) - logger.error("got error while connecting socket", field("error", error)) - reject(error) - }) - - resolve(socket as WebSocket) - }) - }) - - logger.debug("connected to process") - - // Send ready message. - ws.send( - Buffer.from( - JSON.stringify({ - protocol: "TODO", - }), - ), - ) - } - - /** - * Return whitelisted applications. - */ - public async applications(): Promise> { - return findWhitelistedApplications() - } - - /** - * Return installed applications. - */ - public async installedApplications(): Promise> { - return findApplications() - } - - /** - * Handle /process endpoint. - */ - private async process(request: http.IncomingMessage): Promise { - this.ensureMethod(request, ["DELETE", "POST"]) - - const data = await this.getData(request) - if (!data) { - throw new HttpError("No data was provided", HttpCode.BadRequest) - } - - const parsed: Application = JSON.parse(data) - - switch (request.method) { - case "DELETE": - if (parsed.pid) { - await this.killProcess(parsed.pid) - } else if (parsed.path) { - await this.killProcess(parsed.path) - } else { - throw new Error("No pid or path was provided") - } - return { - mime: "application/json", - code: HttpCode.Ok, - } - case "POST": { - if (!parsed.exec) { - throw new Error("No exec was provided") - } - return { - mime: "application/json", - content: { - created: true, - pid: await this.spawnProcess(parsed.exec), - }, - } as HttpResponse - } - } - - throw new HttpError("Not found", HttpCode.NotFound) - } - - /** - * Kill a process identified by pid or path if a web app. - */ - public async killProcess(pid: number | string): Promise { - if (typeof pid === "string") { - switch (pid) { - case Vscode.path: - await this.vscode.dispose() - break - default: - throw new Error(`Process "${pid}" does not exist`) - } - } else { - process.kill(pid) - } - } - - /** - * Spawn a process and return the pid. - */ - public async spawnProcess(exec: string): Promise { - const proc = cp.spawn(exec, { - shell: process.env.SHELL || true, - env: { - ...process.env, - }, - }) - - proc.on("error", (error) => logger.error("process errored", field("pid", proc.pid), field("error", error))) - proc.on("exit", () => logger.debug("process exited", field("pid", proc.pid))) - - logger.debug("started process", field("pid", proc.pid)) - - return proc.pid - } - - /** - * Return VS Code's recent paths. - */ - public async recent(): Promise { - try { - if (!this.dataDir) { - throw new Error("data directory is not set") - } - - const state: VsSettings = JSON.parse(await fs.readFile(path.join(this.dataDir, "User/state/global.json"), "utf8")) - const setting = Array.isArray(state) && state.find((item) => item[0] === "recently.opened") - if (!setting) { - return { paths: [], workspaces: [] } - } - - const pathPromises: { [key: string]: Promise } = {} - const workspacePromises: { [key: string]: Promise } = {} - Object.values(JSON.parse(setting[1]) as VsRecents).forEach((recents) => { - recents.forEach((recent) => { - try { - const target = typeof recent === "string" ? pathPromises : workspacePromises - const pathname = url.parse(typeof recent === "string" ? recent : recent.configURIPath).pathname - if (pathname && !target[pathname]) { - target[pathname] = new Promise((resolve) => { - fs.stat(pathname) - .then(() => resolve(pathname)) - .catch(() => resolve()) - }) - } - } catch (error) { - logger.debug("invalid path", field("path", recent)) - } - }) - }) - - const [paths, workspaces] = await Promise.all([ - Promise.all(Object.values(pathPromises)), - Promise.all(Object.values(workspacePromises)), - ]) - - return { - paths: paths.filter((p) => !!p), - workspaces: workspaces.filter((p) => !!p), - } - } catch (error) { - if (error.code !== "ENOENT") { - throw error - } - } - - return { paths: [], workspaces: [] } - } - - /** - * For these, just return the error message since they'll be requested as - * JSON. - */ - public async getErrorRoot(_route: Route, _title: string, _header: string, error: string): Promise { - return { - mime: "application/json", - content: JSON.stringify({ error }), - } - } -} diff --git a/src/node/app/bin.ts b/src/node/app/bin.ts deleted file mode 100644 index f12ce3a2..00000000 --- a/src/node/app/bin.ts +++ /dev/null @@ -1,30 +0,0 @@ -import * as fs from "fs" -import * as path from "path" -import { Application } from "../../common/api" - -const getVscodeVersion = (): string => { - try { - return require(path.resolve(__dirname, "../../../lib/vscode/package.json")).version - } catch (error) { - return "unknown" - } -} - -export const Vscode: Application = { - categories: ["Editor"], - icon: fs.readFileSync(path.resolve(__dirname, "../../../lib/vscode/resources/linux/code.png")).toString("base64"), - installed: true, - name: "VS Code", - path: "/", - version: getVscodeVersion(), -} - -export const findApplications = async (): Promise> => { - const apps: Application[] = [Vscode] - - return apps.sort((a, b): number => a.name.localeCompare(b.name)) -} - -export const findWhitelistedApplications = async (): Promise> => { - return [Vscode] -} diff --git a/src/node/app/dashboard.ts b/src/node/app/dashboard.ts deleted file mode 100644 index 261e93c5..00000000 --- a/src/node/app/dashboard.ts +++ /dev/null @@ -1,147 +0,0 @@ -import * as http from "http" -import * as querystring from "querystring" -import { Application } from "../../common/api" -import { HttpCode, HttpError } from "../../common/http" -import { normalize } from "../../common/util" -import { HttpProvider, HttpProviderOptions, HttpResponse, Route } from "../http" -import { ApiHttpProvider } from "./api" -import { UpdateHttpProvider } from "./update" - -/** - * Dashboard HTTP provider. - */ -export class DashboardHttpProvider extends HttpProvider { - public constructor( - options: HttpProviderOptions, - private readonly api: ApiHttpProvider, - private readonly update: UpdateHttpProvider, - ) { - super(options) - } - - public async handleRequest(route: Route, request: http.IncomingMessage): Promise { - if (!this.isRoot(route)) { - throw new HttpError("Not found", HttpCode.NotFound) - } - - switch (route.base) { - case "/spawn": { - this.ensureAuthenticated(request) - this.ensureMethod(request, "POST") - const data = await this.getData(request) - const app = data ? querystring.parse(data) : {} - if (app.path) { - return { redirect: Array.isArray(app.path) ? app.path[0] : app.path } - } - if (!app.exec) { - throw new Error("No exec was provided") - } - this.api.spawnProcess(Array.isArray(app.exec) ? app.exec[0] : app.exec) - return { redirect: this.options.base } - } - case "/app": - case "/": { - this.ensureMethod(request) - if (!this.authenticated(request)) { - return { redirect: "/login", query: { to: this.options.base } } - } - return route.base === "/" ? this.getRoot(route) : this.getAppRoot(route) - } - } - - throw new HttpError("Not found", HttpCode.NotFound) - } - - public async getRoot(route: Route): Promise { - const base = this.base(route) - const apps = await this.api.installedApplications() - const response = await this.getUtf8Resource(this.rootPath, "src/browser/pages/home.html") - response.content = response.content - .replace(/{{UPDATE:NAME}}/, await this.getUpdate(base)) - .replace( - /{{APP_LIST:EDITORS}}/, - this.getAppRows( - base, - apps.filter((app) => app.categories && app.categories.includes("Editor")), - ), - ) - .replace( - /{{APP_LIST:OTHER}}/, - this.getAppRows( - base, - apps.filter((app) => !app.categories || !app.categories.includes("Editor")), - ), - ) - return this.replaceTemplates(route, response) - } - - public async getAppRoot(route: Route): Promise { - const response = await this.getUtf8Resource(this.rootPath, "src/browser/pages/app.html") - return this.replaceTemplates(route, response) - } - - private getAppRows(base: string, apps: ReadonlyArray): string { - return apps.length > 0 - ? apps.map((app) => this.getAppRow(base, app)).join("\n") - : `
No applications found.
` - } - - private getAppRow(base: string, app: Application): string { - return `
- -
` - } - - private async getUpdate(base: string): Promise { - if (!this.update.enabled) { - return `
Updates are disabled
` - } - - const humanize = (time: number): string => { - const d = new Date(time) - const pad = (t: number): string => (t < 10 ? "0" : "") + t - return ( - `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}` + - ` ${pad(d.getHours())}:${pad(d.getMinutes())}` - ) - } - - const update = await this.update.getUpdate() - if (this.update.isLatestVersion(update)) { - return `
-
- Latest: ${update.version} -
Up to date
-
-
- ${humanize(update.checked)} - Check now -
-
Current: ${this.update.currentVersion}
-
` - } - - return `
-
- Latest: ${update.version} -
Out of date
-
-
- ${humanize(update.checked)} - Update now -
-
Current: ${this.update.currentVersion}
-
` - } -} diff --git a/src/node/entry.ts b/src/node/entry.ts index a7d8663d..dea47d9f 100644 --- a/src/node/entry.ts +++ b/src/node/entry.ts @@ -2,8 +2,6 @@ import { field, logger } from "@coder/logger" import * as cp from "child_process" import * as path from "path" import { CliMessage } from "../../lib/vscode/src/vs/server/ipc" -import { ApiHttpProvider } from "./app/api" -import { DashboardHttpProvider } from "./app/dashboard" import { LoginHttpProvider } from "./app/login" import { ProxyHttpProvider } from "./app/proxy" import { StaticHttpProvider } from "./app/static" @@ -73,13 +71,11 @@ const main = async (args: Args, cliArgs: Args, configArgs: Args): Promise } const httpServer = new HttpServer(options) - const vscode = httpServer.registerHttpProvider("/", VscodeHttpProvider, args) - const api = httpServer.registerHttpProvider("/api", ApiHttpProvider, httpServer, vscode, args["user-data-dir"]) - const update = httpServer.registerHttpProvider("/update", UpdateHttpProvider, false) + httpServer.registerHttpProvider("/", VscodeHttpProvider, args) + httpServer.registerHttpProvider("/update", UpdateHttpProvider, false) httpServer.registerHttpProvider("/proxy", ProxyHttpProvider) httpServer.registerHttpProvider("/login", LoginHttpProvider, args.config!, envPassword) httpServer.registerHttpProvider("/static", StaticHttpProvider) - httpServer.registerHttpProvider("/dashboard", DashboardHttpProvider, api, update) ipcMain().onDispose(() => httpServer.dispose())