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
-
-
-
-
-
-
-
-
-
-
-
- {{APP_LIST:EDITORS}}
-
-
-
-
-
-
- {{APP_LIST:OTHER}}
-
-
-
-
-
-
- {{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 ``
- }
-
- 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
-
-
-
Current: ${this.update.currentVersion}
-
`
- }
-
- return `
-
- Latest: ${update.version}
-
Out of date
-
-
-
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())