diff --git a/src/browser/app.tsx b/src/browser/app.tsx index dd0a6e251..16f639ed2 100644 --- a/src/browser/app.tsx +++ b/src/browser/app.tsx @@ -1,3 +1,4 @@ +import { field, logger } from "@coder/logger" import { getBasepath, navigate, setBasepath } from "hookrouter" import * as React from "react" import { Application, isExecutableApplication } from "../common/api" @@ -14,16 +15,25 @@ interface RedirectedApplication extends Application { redirected?: boolean } -const origin = typeof window !== "undefined" ? window.location.origin + window.location.pathname : undefined - +let resolved = false const App: React.FunctionComponent = (props) => { const [authed, setAuthed] = React.useState(props.options.authed) const [app, setApp] = React.useState(props.options.app) const [error, setError] = React.useState() - if (typeof window !== "undefined") { - const url = new URL(origin + props.options.basePath) + if (!resolved && typeof document !== "undefined") { + // Get the base path. We need the full URL for connecting the web socket. + // Use the path name plus the provided base path. For example: + // foo.com/base + ./ => foo.com/base + // foo.com/base/ + ./ => foo.com/base + // foo.com/base/bar + ./ => foo.com/base + // foo.com/base/bar/ + ./../ => foo.com/base + const parts = window.location.pathname.replace(/^\//g, "").split("/") + parts[parts.length - 1] = props.options.basePath + const url = new URL(window.location.origin + "/" + parts.join("/")) setBasepath(normalize(url.pathname)) + logger.debug("resolved base path", field("base", getBasepath())) + resolved = true // eslint-disable-next-line @typescript-eslint/no-explicit-any ;(window as any).setAuthed = (a: boolean): void => { diff --git a/src/node/http.ts b/src/node/http.ts index 7c023956c..5c2989e18 100644 --- a/src/node/http.ts +++ b/src/node/http.ts @@ -117,8 +117,9 @@ interface ProviderRoute extends Route { export interface HttpProviderOptions { readonly auth: AuthType - readonly password?: string + readonly base: string readonly commit: string + readonly password?: string } /** @@ -150,10 +151,16 @@ export abstract class HttpProvider { public abstract handleRequest(route: Route, request: http.IncomingMessage): Promise /** - * Get the base relative to the provided route. + * Get the base relative to the provided route. For each slash we need to go + * up a directory. For example: + * / => ./ + * /foo => ./ + * /foo/ => ./../ + * /foo/bar => ./../ + * /foo/bar/ => ./../../ */ public base(route: Route): string { - const depth = ((route.fullPath + "/").match(/\//g) || []).length + const depth = (route.originalPath.match(/\//g) || []).length return normalize("./" + (depth > 1 ? "../".repeat(depth - 1) : "")) } @@ -403,6 +410,7 @@ export class HttpServer { new provider( { auth: this.options.auth || AuthType.None, + base: `/${endpoint}`, commit: this.options.commit, password: this.options.password, }, @@ -510,11 +518,6 @@ export class HttpServer { 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 } @@ -572,7 +575,7 @@ export class HttpServer { } const parsedUrl = request.url ? url.parse(request.url, true) : { query: {}, pathname: "" } - const originalPath = parsedUrl.pathname || "" + const originalPath = parsedUrl.pathname || "/" const fullPath = normalize(originalPath) const { base, requestPath } = parse(fullPath) diff --git a/src/node/vscode/server.ts b/src/node/vscode/server.ts index acbf25eb3..6d26188b8 100644 --- a/src/node/vscode/server.ts +++ b/src/node/vscode/server.ts @@ -199,6 +199,8 @@ export class VscodeHttpProvider extends HttpProvider { ...response, content: response.content .replace(/{{COMMIT}}/g, options.commit) + .replace(/{{BASE}}/g, this.base(route)) + .replace(/{{VS_BASE}}/g, this.base(route) + this.options.base) .replace(`"{{REMOTE_USER_DATA_URI}}"`, `'${JSON.stringify(options.remoteUserDataUri)}'`) .replace(`"{{PRODUCT_CONFIGURATION}}"`, `'${JSON.stringify(options.productConfiguration)}'`) .replace(`"{{WORKBENCH_WEB_CONFIGURATION}}"`, `'${JSON.stringify(options.workbenchWebConfiguration)}'`) diff --git a/src/node/vscode/workbench-build.html b/src/node/vscode/workbench-build.html index 6a417ce8c..86c0de0ff 100644 --- a/src/node/vscode/workbench-build.html +++ b/src/node/vscode/workbench-build.html @@ -19,28 +19,27 @@ - - - - + + + + - + - - - - + + + - + diff --git a/src/node/vscode/workbench.html b/src/node/vscode/workbench.html index b7bb72b26..bb61eaf65 100644 --- a/src/node/vscode/workbench.html +++ b/src/node/vscode/workbench.html @@ -19,9 +19,9 @@ - - - + + + @@ -30,11 +30,12 @@ - +