Make routing base path agnostic

This commit is contained in:
Asher 2020-02-05 17:30:09 -06:00
parent a149c5fc60
commit 4cc181cedc
No known key found for this signature in database
GPG Key ID: D63C1EF81242354A
13 changed files with 198 additions and 221 deletions

View File

@ -1,3 +1,4 @@
import { getBasepath } from "hookrouter"
import { Application, ApplicationsResponse, CreateSessionResponse, FilesResponse, RecentResponse } from "../common/api"
import { ApiEndpoint, HttpCode, HttpError } from "../common/http"
@ -18,7 +19,7 @@ export function setAuthed(authed: boolean): void {
* Also set authed to false if the request returns unauthorized.
*/
const tryRequest = async (endpoint: string, options?: RequestInit): Promise<Response> => {
const response = await fetch("/api" + endpoint + "/", options)
const response = await fetch(getBasepath() + "/api" + endpoint + "/", options)
if (response.status === HttpCode.Unauthorized) {
setAuthed(false)
}
@ -33,14 +34,9 @@ const tryRequest = async (endpoint: string, options?: RequestInit): Promise<Resp
* Try authenticating.
*/
export const authenticate = async (body?: AuthBody): Promise<void> => {
let formBody: URLSearchParams | undefined
if (body) {
formBody = new URLSearchParams()
formBody.append("password", body.password)
}
const response = await tryRequest(ApiEndpoint.login, {
method: "POST",
body: formBody,
body: JSON.stringify({ ...body, basePath: getBasepath() }),
headers: {
"Content-Type": "application/x-www-form-urlencoded; charset=utf-8",
},

View File

@ -1,4 +1,4 @@
import { getBasepath, navigate } from "hookrouter"
import { getBasepath, navigate, setBasepath } from "hookrouter"
import * as React from "react"
import { Application, isExecutableApplication } from "../common/api"
import { HttpError } from "../common/http"
@ -11,25 +11,36 @@ export interface AppProps {
}
const App: React.FunctionComponent<AppProps> = (props) => {
const [authed, setAuthed] = React.useState<boolean>(!!props.options.authed)
const [authed, setAuthed] = React.useState<boolean>(props.options.authed)
const [app, setApp] = React.useState<Application | undefined>(props.options.app)
const [error, setError] = React.useState<HttpError | Error | string>()
if (typeof window !== "undefined") {
const url = new URL(window.location.origin + window.location.pathname + props.options.basePath)
setBasepath(normalize(url.pathname))
// eslint-disable-next-line @typescript-eslint/no-explicit-any
;(window as any).setAuthed = (a: boolean): void => {
if (authed !== a) {
setAuthed(a)
// TEMP: Remove when no longer auto-loading VS Code.
if (a && !app) {
setApp({
name: "VS Code",
path: "/",
embedPath: "/vscode-embed",
})
}
}
}
}
React.useEffect(() => {
if (app && !isExecutableApplication(app)) {
navigate(normalize(`${getBasepath()}/${app.path}/`, true))
}
}, [app])
if (typeof window !== "undefined") {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
;(window as any).setAuthed = (a: boolean): void => {
if (authed !== a) {
setAuthed(a)
}
}
}
return (
<>
{!app || !app.loaded ? (
@ -41,7 +52,7 @@ const App: React.FunctionComponent<AppProps> = (props) => {
)}
<Modal app={app} setApp={setApp} authed={authed} error={error} setError={setError} />
{authed && app && app.embedPath ? (
<iframe id="iframe" src={normalize(`${getBasepath()}/${app.embedPath}/`, true)}></iframe>
<iframe id="iframe" src={normalize(`./${app.embedPath}/`, true)}></iframe>
) : (
undefined
)}

View File

@ -128,7 +128,6 @@ export const Modal: React.FunctionComponent<ModalProps> = (props) => {
<aside className="sidebar-nav">
<nav className="links">
{props.authed ? (
// TEMP: Remove once we don't auto-load vscode.
<>
<button className="link" onClick={(): void => setSection(Section.Recent)}>
Recent

View File

@ -3,17 +3,17 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no">
<!-- <meta http-equiv="Content-Security-Policy" content="font-src 'self'; connect-src 'self'; default-src ws: wss:; style-src 'self'; script-src 'self' 'unsafe-inline'; manifest-src 'self'; img-src 'self' data:;"> -->
<meta http-equiv="Content-Security-Policy" content="font-src 'self' fonts.gstatic.com; connect-src 'self'; default-src ws: wss: 'self'; style-src 'self' fonts.googleapis.com; script-src 'self' 'unsafe-inline'; manifest-src 'self'; img-src 'self' data:;">
<title>code-server</title>
<link rel="icon" href="/static-{{COMMIT}}/src/browser/media/favicon.ico" type="image/x-icon" />
<link rel="manifest" href="/static-{{COMMIT}}/src/browser/media/manifest.json" crossorigin="use-credentials">
<link rel="apple-touch-icon" href="/static-{{COMMIT}}/src/browser/media/code-server.png" />
<link rel="icon" href="{{BASE}}/static-{{COMMIT}}/src/browser/media/favicon.ico" type="image/x-icon" />
<link rel="manifest" href="{{BASE}}/static-{{COMMIT}}/src/browser/media/manifest.json" crossorigin="use-credentials">
<link rel="apple-touch-icon" href="{{BASE}}/static-{{COMMIT}}/src/browser/media/code-server.png" />
<link href="https://fonts.googleapis.com/css?family=IBM+Plex+Sans&display=swap" rel="stylesheet" />
<link href="/static-{{COMMIT}}/dist/index.css" rel="stylesheet">
<link href="{{BASE}}/static-{{COMMIT}}/dist/index.css" rel="stylesheet">
<meta id="coder-options" data-settings="{{OPTIONS}}">
</head>
<body>
<div id="root">{{COMPONENT}}</div>
<script src="/static-{{COMMIT}}/dist/index.js"></script>
<script src="{{BASE}}/static-{{COMMIT}}/dist/index.js"></script>
</body>
</html>

View File

@ -22,6 +22,11 @@ export enum SessionError {
Unknown,
}
export interface LoginRequest {
password: string
basePath: string
}
export interface LoginResponse {
success: boolean
}

View File

@ -3,8 +3,9 @@ import { Application } from "../common/api"
export interface Options {
app?: Application
authed?: boolean
logLevel?: number
authed: boolean
basePath: string
logLevel: number
}
/**

View File

@ -1,17 +1,20 @@
import { field, logger } from "@coder/logger"
import * as http from "http"
import * as net from "net"
import * as querystring from "querystring"
import * as ws from "ws"
import { ApplicationsResponse, ClientMessage, FilesResponse, LoginResponse, ServerMessage } from "../../common/api"
import {
ApplicationsResponse,
ClientMessage,
FilesResponse,
LoginRequest,
LoginResponse,
ServerMessage,
} from "../../common/api"
import { ApiEndpoint, HttpCode } from "../../common/http"
import { HttpProvider, HttpProviderOptions, HttpResponse, HttpServer, PostData } from "../http"
import { normalize } from "../../common/util"
import { HttpProvider, HttpProviderOptions, HttpResponse, HttpServer, Route } from "../http"
import { hash } from "../util"
interface LoginPayload extends PostData {
password?: string | string[]
}
/**
* API HTTP provider.
*/
@ -22,13 +25,8 @@ export class ApiHttpProvider extends HttpProvider {
super(options)
}
public async handleRequest(
base: string,
_requestPath: string,
_query: querystring.ParsedUrlQuery,
request: http.IncomingMessage
): Promise<HttpResponse | undefined> {
switch (base) {
public async handleRequest(route: Route, request: http.IncomingMessage): Promise<HttpResponse | undefined> {
switch (route.base) {
case ApiEndpoint.login:
if (request.method === "POST") {
return this.login(request)
@ -38,7 +36,7 @@ export class ApiHttpProvider extends HttpProvider {
if (!this.authenticated(request)) {
return { code: HttpCode.Unauthorized }
}
switch (base) {
switch (route.base) {
case ApiEndpoint.applications:
return this.applications()
case ApiEndpoint.files:
@ -49,9 +47,7 @@ export class ApiHttpProvider extends HttpProvider {
}
public async handleWebSocket(
_base: string,
_requestPath: string,
_query: querystring.ParsedUrlQuery,
_route: Route,
request: http.IncomingMessage,
socket: net.Socket,
head: Buffer
@ -93,30 +89,35 @@ export class ApiHttpProvider extends HttpProvider {
* unauthorized.
*/
private async login(request: http.IncomingMessage): Promise<HttpResponse<LoginResponse>> {
const ok = (password: string | true): HttpResponse<LoginResponse> => {
return {
content: {
success: true,
},
cookie: typeof password === "string" ? { key: "key", value: password } : undefined,
}
}
// Already authenticated via cookies?
const providedPassword = this.authenticated(request)
if (providedPassword) {
return ok(providedPassword)
return { code: HttpCode.Ok }
}
const data = await this.getData(request)
const payload: LoginPayload = data ? querystring.parse(data) : {}
const payload: LoginRequest = data ? JSON.parse(data) : {}
const password = this.authenticated(request, {
key: typeof payload.password === "string" ? [hash(payload.password)] : undefined,
})
if (password) {
return ok(password)
return {
content: {
success: true,
},
cookie:
typeof password === "string"
? {
key: "key",
value: password,
path: normalize(payload.basePath),
}
: undefined,
}
}
// Only log if it was an actual login attempt.
if (payload && payload.password) {
console.error(
"Failed login attempt",
JSON.stringify({
@ -126,6 +127,7 @@ export class ApiHttpProvider extends HttpProvider {
timestamp: Math.floor(new Date().getTime() / 1000),
})
)
}
return { code: HttpCode.Unauthorized }
}

View File

@ -1,51 +1,54 @@
import { logger } from "@coder/logger"
import * as http from "http"
import * as querystring from "querystring"
import * as React from "react"
import * as ReactDOMServer from "react-dom/server"
import App from "../../browser/app"
import { Options } from "../../common/util"
import { HttpProvider, HttpResponse } from "../http"
import { HttpProvider, HttpResponse, Route } from "../http"
/**
* Top-level and fallback HTTP provider.
*/
export class MainHttpProvider extends HttpProvider {
public async handleRequest(
base: string,
requestPath: string,
_query: querystring.ParsedUrlQuery,
request: http.IncomingMessage
): Promise<HttpResponse | undefined> {
if (base === "/static") {
const response = await this.getResource(this.rootPath, requestPath)
public async handleRequest(route: Route, request: http.IncomingMessage): Promise<HttpResponse | undefined> {
switch (route.base) {
case "/static": {
const response = await this.getResource(this.rootPath, route.requestPath)
if (!this.isDev) {
response.cache = true
}
return response
}
case "/": {
const options: Options = {
authed: !!this.authenticated(request),
basePath: this.base(route),
logLevel: logger.level,
}
if (options.authed) {
// TEMP: Auto-load VS Code for now. In future versions we'll need to check
// the URL for the appropriate application to load, if any.
const app = {
options.app = {
name: "VS Code",
path: "/",
embedPath: "/vscode-embed",
}
const options: Options = {
app,
authed: !!this.authenticated(request),
logLevel: logger.level,
}
const response = await this.getUtf8Resource(this.rootPath, "src/browser/index.html")
response.content = response.content
.replace(/{{COMMIT}}/g, this.options.commit)
.replace(/{{BASE}}/g, this.base(route))
.replace(/"{{OPTIONS}}"/g, `'${JSON.stringify(options)}'`)
.replace(/{{COMPONENT}}/g, ReactDOMServer.renderToString(<App options={options} />))
return response
}
}
return undefined
}
public async handleWebSocket(): Promise<undefined> {
return undefined

View File

@ -47,8 +47,9 @@ export interface HttpResponse<T = string | Buffer | object> {
content?: T
/**
* Cookie to write with the response.
* NOTE: Cookie paths must be absolute. The default is /.
*/
cookie?: { key: string; value: string }
cookie?: { key: string; value: string; path?: string }
/**
* Used to automatically determine the appropriate mime type.
*/
@ -64,7 +65,7 @@ export interface HttpResponse<T = string | Buffer | object> {
/**
* Redirect to this path. Will rewrite against the base path but NOT the
* provider endpoint so you must include it. This allows redirecting outside
* of your endpoint. Use `withBase()` to redirect within your endpoint.
* of your endpoint.
*/
redirect?: string
/**
@ -87,9 +88,12 @@ export interface HttpStringFileResponse extends HttpResponse {
filePath: string
}
export interface RedirectResponse extends HttpResponse {
redirect: string
}
export interface HttpServerOptions {
readonly auth?: AuthType
readonly basePath?: string
readonly cert?: string
readonly certKey?: string
readonly commit?: string
@ -99,15 +103,18 @@ export interface HttpServerOptions {
readonly socket?: string
}
interface ProviderRoute {
export interface Route {
base: string
requestPath: string
query: querystring.ParsedUrlQuery
provider: HttpProvider
fullPath: string
originalPath: string
}
interface ProviderRoute extends Route {
provider: HttpProvider
}
export interface HttpProviderOptions {
readonly base: string
readonly auth: AuthType
@ -132,9 +139,7 @@ export abstract class HttpProvider {
* Handle web sockets on the registered endpoint.
*/
public abstract handleWebSocket(
base: string,
requestPath: string,
query: querystring.ParsedUrlQuery,
route: Route,
request: http.IncomingMessage,
socket: net.Socket,
head: Buffer
@ -143,24 +148,20 @@ export abstract class HttpProvider {
/**
* Handle requests to the registered endpoint.
*/
public abstract handleRequest(
base: string,
requestPath: string,
query: querystring.ParsedUrlQuery,
request: http.IncomingMessage
): Promise<HttpResponse | undefined>
public abstract handleRequest(route: Route, request: http.IncomingMessage): Promise<HttpResponse | undefined>
/**
* Get the base relative to the provided route.
*/
public base(route: Route): string {
const depth = route.fullPath ? (route.fullPath.match(/\//g) || []).length : 1
return normalize("./" + (depth > 1 ? "../".repeat(depth - 1) : ""))
}
protected get isDev(): boolean {
return this.options.commit === "development"
}
/**
* Return the specified path with the base path prepended.
*/
protected withBase(path: string): string {
return normalize(`${this.options.base}/${path}`)
}
/**
* Get a file resource.
* TODO: Would a stream be faster, at least for large files?
@ -346,19 +347,14 @@ export class HttpServer {
private listenPromise: Promise<string | null> | undefined
public readonly protocol: "http" | "https"
private readonly providers = new Map<string, HttpProvider>()
private readonly options: HttpServerOptions
private readonly heart: Heart
public constructor(options: HttpServerOptions) {
public constructor(private readonly options: HttpServerOptions) {
this.heart = new Heart(path.join(xdgLocalDir, "heartbeat"), async () => {
const connections = await this.getConnections()
logger.trace(`${connections} active connection${plural(connections)}`)
return connections !== 0
})
this.options = {
...options,
basePath: options.basePath ? options.basePath.replace(/\/+$/, "") : "",
}
this.protocol = this.options.cert ? "https" : "http"
if (this.protocol === "https") {
this.server = httpolyglot.createServer(
@ -452,30 +448,19 @@ export class HttpServer {
try {
this.heart.beat()
const route = this.parseUrl(request)
const payload =
this.maybeRedirect(request, route) ||
(await route.provider.handleRequest(route.base, route.requestPath, route.query, request))
const payload = this.maybeRedirect(request, route) || (await route.provider.handleRequest(route, request))
if (!payload) {
throw new HttpError("Not found", HttpCode.NotFound)
}
const basePath = this.options.basePath || "/"
response.writeHead(payload.redirect ? HttpCode.Redirect : payload.code || HttpCode.Ok, {
"Content-Type": payload.mime || getMediaMime(payload.filePath),
...(payload.redirect
? {
Location: this.constructRedirect(
request.headers.host as string,
route.fullPath,
normalize(`${basePath}/${payload.redirect}`) + "/",
{ ...route.query, ...(payload.query || {}) }
),
}
: {}),
...(request.headers["service-worker"] ? { "Service-Worker-Allowed": basePath } : {}),
...(payload.redirect ? { Location: payload.redirect } : {}),
...(request.headers["service-worker"] ? { "Service-Worker-Allowed": route.provider.base(route) } : {}),
...(payload.cache ? { "Cache-Control": "public, max-age=31536000" } : {}),
...(payload.cookie
? {
"Set-Cookie": `${payload.cookie.key}=${payload.cookie.value}; Path=${basePath}; HttpOnly; SameSite=strict`,
"Set-Cookie": `${payload.cookie.key}=${payload.cookie.value}; Path=${payload.cookie.path ||
"/"}; HttpOnly; SameSite=strict`,
}
: {}),
...payload.headers,
@ -497,9 +482,8 @@ export class HttpServer {
let e = error
if (error.code === "ENOENT" || error.code === "EISDIR") {
e = new HttpError("Not found", HttpCode.NotFound)
} else {
logger.error(error.stack)
}
logger.debug(error.stack)
response.writeHead(typeof e.code === "number" ? e.code : HttpCode.ServerError)
response.end(error.message)
}
@ -509,14 +493,29 @@ export class HttpServer {
* Return any necessary redirection before delegating to a provider.
*/
private maybeRedirect(request: http.IncomingMessage, route: ProviderRoute): HttpResponse | undefined {
// Redirect to HTTPS.
if (this.options.cert && !(request.connection as tls.TLSSocket).encrypted) {
return { redirect: route.fullPath }
const redirect = (path: string): string => {
Object.keys(route.query).forEach((key) => {
if (typeof route.query[key] === "undefined") {
delete route.query[key]
}
// Redirect indexes to a trailing slash so relative paths will operate
// against the provider.
if (route.requestPath === "/index.html" && !route.originalPath.endsWith("/")) {
return { redirect: route.fullPath } // Redirect always includes a trailing slash.
})
// If we're handling TLS ensure all requests are redirected to HTTPS.
return this.options.cert
? `${this.protocol}://${request.headers.host}`
: "" +
normalize(`${route.provider.base(route)}/${path}`, true) +
(Object.keys(route.query).length > 0 ? `?${querystring.stringify(route.query)}` : "")
}
// Redirect to HTTPS if we're handling the TLS.
if (this.options.cert && !(request.connection as tls.TLSSocket).encrypted) {
return { redirect: redirect(route.fullPath) }
}
// Redirect our indexes to a trailing slash so relative paths in the served
// HTML will operate against the base path properly.
if (route.requestPath === "/index.html" && !route.originalPath.endsWith("/") && this.providers.has(route.base)) {
return { redirect: redirect(route.fullPath + "/") }
}
return undefined
}
@ -534,12 +533,12 @@ export class HttpServer {
throw new HttpError("HTTP/1.1 400 Bad Request", HttpCode.BadRequest)
}
const { base, requestPath, query, provider } = this.parseUrl(request)
if (!provider) {
const route = this.parseUrl(request)
if (!route.provider) {
throw new HttpError("Not found", HttpCode.NotFound)
}
if (!(await provider.handleWebSocket(base, requestPath, query, request, socket, head))) {
if (!(await route.provider.handleWebSocket(route, request, socket, head))) {
throw new HttpError("Not found", HttpCode.NotFound)
}
} catch (error) {
@ -593,21 +592,4 @@ export class HttpServer {
}
return { base, fullPath, requestPath, query: parsedUrl.query, provider, originalPath }
}
/**
* Return the request URL with the specified base and new path.
*/
private constructRedirect(host: string, oldPath: string, newPath: string, query: Query): string {
if (oldPath && oldPath !== "/" && !query.to && /\/login(\/|$)/.test(newPath) && !/\/login(\/|$)/.test(oldPath)) {
query.to = oldPath
}
Object.keys(query).forEach((key) => {
if (typeof query[key] === "undefined") {
delete query[key]
}
})
return (
`${this.protocol}://${host}${newPath}` + (Object.keys(query).length > 0 ? `?${querystring.stringify(query)}` : "")
)
}
}

View File

@ -3,13 +3,8 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no">
<!-- <meta http-equiv="Content-Security-Policy" content="font-src 'self'; connect-src 'self'; default-src ws: wss:; style-src 'self'; script-src 'self' 'unsafe-inline'; manifest-src 'self'; img-src 'self' data:;"> -->
<meta http-equiv="Content-Security-Policy" content="font-src 'self' fonts.gstatic.com; connect-src 'self'; default-src ws: wss: 'self'; style-src 'self' 'unsafe-inline' fonts.googleapis.com; script-src 'self' 'unsafe-inline'; manifest-src 'self'; img-src 'self' data:;">
<title>code-server</title>
<link rel="icon" href="./static-{{COMMIT}}/src/browser/media/favicon.ico" type="image/x-icon" />
<link rel="manifest" href="./static-{{COMMIT}}/src/browser/media/manifest.json" crossorigin="use-credentials">
<link rel="apple-touch-icon" href="./static-{{COMMIT}}/src/browser/media/code-server.png" />
<link href="https://fonts.googleapis.com/css?family=IBM+Plex+Sans&display=swap" rel="stylesheet" />
<meta id="coder-options" data-settings="{{OPTIONS}}">
</head>
<body>
<div id="root" style="color:#f4f4f4;padding:20px;max-width:700px;">

View File

@ -4,7 +4,6 @@ import * as crypto from "crypto"
import * as http from "http"
import * as net from "net"
import * as path from "path"
import * as querystring from "querystring"
import {
CodeServerMessage,
Settings,
@ -13,7 +12,7 @@ import {
WorkbenchOptions,
} from "../../../lib/vscode/src/vs/server/ipc"
import { generateUuid } from "../../common/util"
import { HttpProvider, HttpProviderOptions, HttpResponse } from "../http"
import { HttpProvider, HttpProviderOptions, HttpResponse, Route } from "../http"
import { SettingsProvider } from "../settings"
import { xdgLocalDir } from "../util"
@ -76,13 +75,7 @@ export class VscodeHttpProvider extends HttpProvider {
return this._vscode
}
public async handleWebSocket(
_base: string,
_requestPath: string,
query: querystring.ParsedUrlQuery,
request: http.IncomingMessage,
socket: net.Socket
): Promise<true> {
public async handleWebSocket(route: Route, request: http.IncomingMessage, socket: net.Socket): Promise<true> {
if (!this.authenticated(request)) {
throw new Error("not authenticated")
}
@ -105,7 +98,7 @@ export class VscodeHttpProvider extends HttpProvider {
)
const vscode = await this._vscode
this.send({ type: "socket", query }, vscode, socket)
this.send({ type: "socket", query: route.query }, vscode, socket)
return true
}
@ -116,27 +109,20 @@ export class VscodeHttpProvider extends HttpProvider {
vscode.send(message, socket)
}
public async handleRequest(
base: string,
requestPath: string,
query: querystring.ParsedUrlQuery,
request: http.IncomingMessage
): Promise<HttpResponse | undefined> {
public async handleRequest(route: Route, request: http.IncomingMessage): Promise<HttpResponse | undefined> {
this.ensureGet(request)
switch (base) {
this.ensureAuthenticated(request)
switch (route.base) {
case "/":
if (!this.authenticated(request)) {
return { redirect: "/login" }
}
try {
return await this.getRoot(request, query)
return await this.getRoot(request, route)
} catch (error) {
return this.getErrorRoot(error)
}
case "/static": {
switch (requestPath) {
switch (route.requestPath) {
case "/out/vs/workbench/services/extensions/worker/extensionHostWorkerMain.js": {
const response = await this.getUtf8Resource(this.vsRootPath, requestPath)
const response = await this.getUtf8Resource(this.vsRootPath, route.requestPath)
response.content = response.content.replace(
/{{COMMIT}}/g,
this.workbenchOptions ? this.workbenchOptions.commit : ""
@ -145,40 +131,37 @@ export class VscodeHttpProvider extends HttpProvider {
return response
}
}
const response = await this.getResource(this.vsRootPath, requestPath)
const response = await this.getResource(this.vsRootPath, route.requestPath)
response.cache = true
return response
}
case "/resource":
case "/vscode-remote-resource":
this.ensureAuthenticated(request)
if (typeof query.path === "string") {
return this.getResource(query.path)
if (typeof route.query.path === "string") {
return this.getResource(route.query.path)
}
break
case "/tar":
this.ensureAuthenticated(request)
if (typeof query.path === "string") {
return this.getTarredResource(query.path)
if (typeof route.query.path === "string") {
return this.getTarredResource(route.query.path)
}
break
case "/webview":
this.ensureAuthenticated(request)
if (/^\/vscode-resource/.test(requestPath)) {
return this.getResource(requestPath.replace(/^\/vscode-resource(\/file)?/, ""))
if (/^\/vscode-resource/.test(route.requestPath)) {
return this.getResource(route.requestPath.replace(/^\/vscode-resource(\/file)?/, ""))
}
return this.getResource(this.vsRootPath, "out/vs/workbench/contrib/webview/browser/pre", requestPath)
return this.getResource(this.vsRootPath, "out/vs/workbench/contrib/webview/browser/pre", route.requestPath)
}
return undefined
}
private async getRoot(request: http.IncomingMessage, query: querystring.ParsedUrlQuery): Promise<HttpResponse> {
private async getRoot(request: http.IncomingMessage, route: Route): Promise<HttpResponse> {
const settings = await this.settings.read()
const [response, options] = await Promise.all([
await this.getUtf8Resource(this.rootPath, `src/node/vscode/workbench${!this.isDev ? "-build" : ""}.html`),
this.initialize({
args: this.args,
query,
query: route.query,
remoteAuthority: request.headers.host as string,
settings,
}),

View File

@ -19,10 +19,10 @@
<meta id="vscode-remote-nls-configuration" data-settings="{{NLS_CONFIGURATION}}">
<!-- Workbench Icon/Manifest/CSS -->
<link rel="icon" href="./static-{{COMMIT}}/src/browser/media/favicon.ico" type="image/x-icon" />
<link rel="manifest" href="./static-{{COMMIT}}/src/browser/media/manifest.json" crossorigin="use-credentials">
<link data-name="vs/workbench/workbench.web.api" rel="stylesheet" href="./static-{{COMMIT}}/out/vs/workbench/workbench.web.api.css">
<link rel="apple-touch-icon" href="./static-{{COMMIT}}/src/browser/media/code-server.png" />
<link rel="icon" href="../static-{{COMMIT}}/src/browser/media/favicon.ico" type="image/x-icon" />
<link rel="manifest" href="../static-{{COMMIT}}/src/browser/media/manifest.json" crossorigin="use-credentials">
<link data-name="vs/workbench/workbench.web.api" rel="stylesheet" href="../static-{{COMMIT}}/out/vs/workbench/workbench.web.api.css">
<link rel="apple-touch-icon" href="../static-{{COMMIT}}/src/browser/media/code-server.png" />
<meta name="apple-mobile-web-app-capable" content="yes">
<!-- Prefetch to avoid waterfall -->

View File

@ -19,9 +19,9 @@
<meta id="vscode-remote-nls-configuration" data-settings="{{NLS_CONFIGURATION}}">
<!-- Workbench Icon/Manifest/CSS -->
<link rel="icon" href="./static/src/browser/media/favicon.ico" type="image/x-icon" />
<link rel="manifest" href="./static/src/browser/media/manifest.json" crossorigin="use-credentials">
<link rel="apple-touch-icon" href="./static/src/browser/media/code-server.png" />
<link rel="icon" href="../static/src/browser/media/favicon.ico" type="image/x-icon" />
<link rel="manifest" href="../static/src/browser/media/manifest.json" crossorigin="use-credentials">
<link rel="apple-touch-icon" href="../static/src/browser/media/code-server.png" />
<meta name="apple-mobile-web-app-capable" content="yes">
</head>