Fix base path

Now it should work whether you have a trailing slash or not.
This commit is contained in:
Asher 2020-02-13 12:40:36 -06:00
parent cc79edb312
commit bf1be16d11
No known key found for this signature in database
GPG Key ID: D63C1EF81242354A
5 changed files with 50 additions and 36 deletions

View File

@ -1,3 +1,4 @@
import { field, logger } from "@coder/logger"
import { getBasepath, navigate, setBasepath } from "hookrouter" import { getBasepath, navigate, setBasepath } from "hookrouter"
import * as React from "react" import * as React from "react"
import { Application, isExecutableApplication } from "../common/api" import { Application, isExecutableApplication } from "../common/api"
@ -14,16 +15,25 @@ interface RedirectedApplication extends Application {
redirected?: boolean redirected?: boolean
} }
const origin = typeof window !== "undefined" ? window.location.origin + window.location.pathname : undefined let resolved = false
const App: React.FunctionComponent<AppProps> = (props) => { 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<RedirectedApplication | undefined>(props.options.app) const [app, setApp] = React.useState<RedirectedApplication | undefined>(props.options.app)
const [error, setError] = React.useState<HttpError | Error | string>() const [error, setError] = React.useState<HttpError | Error | string>()
if (typeof window !== "undefined") { if (!resolved && typeof document !== "undefined") {
const url = new URL(origin + props.options.basePath) // 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)) setBasepath(normalize(url.pathname))
logger.debug("resolved base path", field("base", getBasepath()))
resolved = true
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
;(window as any).setAuthed = (a: boolean): void => { ;(window as any).setAuthed = (a: boolean): void => {

View File

@ -117,8 +117,9 @@ interface ProviderRoute extends Route {
export interface HttpProviderOptions { export interface HttpProviderOptions {
readonly auth: AuthType readonly auth: AuthType
readonly password?: string readonly base: string
readonly commit: string readonly commit: string
readonly password?: string
} }
/** /**
@ -150,10 +151,16 @@ export abstract class HttpProvider {
public abstract handleRequest(route: Route, 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. * 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 { 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) : "")) return normalize("./" + (depth > 1 ? "../".repeat(depth - 1) : ""))
} }
@ -403,6 +410,7 @@ export class HttpServer {
new provider( new provider(
{ {
auth: this.options.auth || AuthType.None, auth: this.options.auth || AuthType.None,
base: `/${endpoint}`,
commit: this.options.commit, commit: this.options.commit,
password: this.options.password, password: this.options.password,
}, },
@ -510,11 +518,6 @@ export class HttpServer {
return { redirect: redirect(route.fullPath) } 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 return undefined
} }
@ -572,7 +575,7 @@ export class HttpServer {
} }
const parsedUrl = request.url ? url.parse(request.url, true) : { query: {}, pathname: "" } const parsedUrl = request.url ? url.parse(request.url, true) : { query: {}, pathname: "" }
const originalPath = parsedUrl.pathname || "" const originalPath = parsedUrl.pathname || "/"
const fullPath = normalize(originalPath) const fullPath = normalize(originalPath)
const { base, requestPath } = parse(fullPath) const { base, requestPath } = parse(fullPath)

View File

@ -199,6 +199,8 @@ export class VscodeHttpProvider extends HttpProvider {
...response, ...response,
content: response.content content: response.content
.replace(/{{COMMIT}}/g, options.commit) .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(`"{{REMOTE_USER_DATA_URI}}"`, `'${JSON.stringify(options.remoteUserDataUri)}'`)
.replace(`"{{PRODUCT_CONFIGURATION}}"`, `'${JSON.stringify(options.productConfiguration)}'`) .replace(`"{{PRODUCT_CONFIGURATION}}"`, `'${JSON.stringify(options.productConfiguration)}'`)
.replace(`"{{WORKBENCH_WEB_CONFIGURATION}}"`, `'${JSON.stringify(options.workbenchWebConfiguration)}'`) .replace(`"{{WORKBENCH_WEB_CONFIGURATION}}"`, `'${JSON.stringify(options.workbenchWebConfiguration)}'`)

View File

@ -19,28 +19,27 @@
<meta id="vscode-remote-nls-configuration" data-settings="{{NLS_CONFIGURATION}}"> <meta id="vscode-remote-nls-configuration" data-settings="{{NLS_CONFIGURATION}}">
<!-- Workbench Icon/Manifest/CSS --> <!-- Workbench Icon/Manifest/CSS -->
<link rel="icon" href="../static-{{COMMIT}}/src/browser/media/favicon.ico" type="image/x-icon" /> <link rel="icon" href="{{BASE}}/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="manifest" href="{{BASE}}/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 data-name="vs/workbench/workbench.web.api" rel="stylesheet" href="{{VS_BASE}}/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="apple-touch-icon" href="{{BASE}}/static-{{COMMIT}}/src/browser/media/code-server.png" />
<meta name="apple-mobile-web-app-capable" content="yes"> <meta name="apple-mobile-web-app-capable" content="yes">
<!-- Prefetch to avoid waterfall --> <!-- Prefetch to avoid waterfall -->
<link rel="prefetch" href="./static-{{COMMIT}}/node_modules/semver-umd/lib/semver-umd.js"> <link rel="prefetch" href="{{BASE}}/static-{{COMMIT}}/node_modules/semver-umd/lib/semver-umd.js">
</head> </head>
<body aria-label=""> <body aria-label="">
</body> </body>
<!-- Startup (do not modify order of script tags!) --> <!-- Startup (do not modify order of script tags!) -->
<!-- NOTE:coder: Modified to work against the current path and use the commit for caching. -->
<script> <script>
// NOTE: Changes to inline scripts require update of content security policy const parts = window.location.pathname.replace(/^\//g, "").split("/")
const basePath = window.location.pathname.replace(/\/+$/, ''); parts[parts.length - 1] = "{{VS_BASE}}"
const base = window.location.origin + basePath; const url = new URL(window.location.origin + "/" + parts.join("/"))
const el = document.getElementById('vscode-remote-commit'); const el = document.getElementById('vscode-remote-commit');
const commit = el ? el.getAttribute('data-settings') : ""; const commit = el ? el.getAttribute('data-settings') : "";
const staticBase = base + '/static-' + commit; const staticBase = url.href + '/static-' + commit;
let nlsConfig; let nlsConfig;
try { try {
nlsConfig = JSON.parse(document.getElementById('vscode-remote-nls-configuration').getAttribute('data-settings')); nlsConfig = JSON.parse(document.getElementById('vscode-remote-nls-configuration').getAttribute('data-settings'));
@ -54,7 +53,7 @@
// FIXME: Only works if path separators are /. // FIXME: Only works if path separators are /.
const path = nlsConfig._resolvedLanguagePackCoreLocation const path = nlsConfig._resolvedLanguagePackCoreLocation
+ '/' + bundle.replace(/\//g, '!') + '.nls.json'; + '/' + bundle.replace(/\//g, '!') + '.nls.json';
fetch(`${base}/resource/?path=${encodeURIComponent(path)}`) fetch(`${url.href}/resource/?path=${encodeURIComponent(path)}`)
.then((response) => response.json()) .then((response) => response.json())
.then((json) => { .then((json) => {
bundles[bundle] = json; bundles[bundle] = json;
@ -78,15 +77,14 @@
'vs/nls': nlsConfig, 'vs/nls': nlsConfig,
}; };
</script> </script>
<script src="./static-{{COMMIT}}/out/vs/loader.js"></script> <script src="{{VS_BASE}}/static-{{COMMIT}}/out/vs/loader.js"></script>
<script src="./static-{{COMMIT}}/out/vs/workbench/workbench.web.api.nls.js"></script> <script src="{{VS_BASE}}/static-{{COMMIT}}/out/vs/workbench/workbench.web.api.nls.js"></script>
<script src="./static-{{COMMIT}}/out/vs/workbench/workbench.web.api.js"></script> <script src="{{VS_BASE}}/static-{{COMMIT}}/out/vs/workbench/workbench.web.api.js"></script>
<!-- TODO@coder: This errors with multiple anonymous define calls (one is <!-- TODO@coder: This errors with multiple anonymous define calls (one is
workbench.js and one is semver-umd.js). For now use the same method found in workbench.js and one is semver-umd.js). For now use the same method found in
workbench-dev.html. Appears related to the timing of the script load events. --> workbench-dev.html. Appears related to the timing of the script load events. -->
<!-- <script src="./static-{{COMMIT}}/out/vs/workbench/workbench.js"></script> --> <!-- <script src="{{VS_BASE}}/static-{{COMMIT}}/out/vs/workbench/workbench.js"></script> -->
<script> <script>
// NOTE: Changes to inline scripts require update of content security policy
require(['vs/code/browser/workbench/workbench'], function() {}); require(['vs/code/browser/workbench/workbench'], function() {});
</script> </script>
</html> </html>

View File

@ -19,9 +19,9 @@
<meta id="vscode-remote-nls-configuration" data-settings="{{NLS_CONFIGURATION}}"> <meta id="vscode-remote-nls-configuration" data-settings="{{NLS_CONFIGURATION}}">
<!-- Workbench Icon/Manifest/CSS --> <!-- Workbench Icon/Manifest/CSS -->
<link rel="icon" href="../static/src/browser/media/favicon.ico" type="image/x-icon" /> <link rel="icon" href="{{BASE}}/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="manifest" href="{{BASE}}/static/src/browser/media/manifest.json" crossorigin="use-credentials">
<link rel="apple-touch-icon" href="../static/src/browser/media/code-server.png" /> <link rel="apple-touch-icon" href="{{BASE}}/static/src/browser/media/code-server.png" />
<meta name="apple-mobile-web-app-capable" content="yes"> <meta name="apple-mobile-web-app-capable" content="yes">
</head> </head>
@ -30,11 +30,12 @@
<!-- Startup (do not modify order of script tags!) --> <!-- Startup (do not modify order of script tags!) -->
<script> <script>
const basePath = window.location.pathname.replace(/\/+$/, ''); const parts = window.location.pathname.replace(/^\//g, "").split("/")
const base = window.location.origin + basePath; parts[parts.length - 1] = "{{VS_BASE}}"
const url = new URL(window.location.origin + "/" + parts.join("/"))
const el = document.getElementById('vscode-remote-commit'); const el = document.getElementById('vscode-remote-commit');
const commit = el ? el.getAttribute('data-settings') : ""; const commit = el ? el.getAttribute('data-settings') : "";
const staticBase = base + '/static-' + commit; const staticBase = url.href + '/static-' + commit;
self.require = { self.require = {
baseUrl: `${staticBase}/out`, baseUrl: `${staticBase}/out`,
paths: { paths: {
@ -48,7 +49,7 @@
}, },
}; };
</script> </script>
<script src="./static/out/vs/loader.js"></script> <script src="{{VS_BASE}}/static/out/vs/loader.js"></script>
<script> <script>
require(['vs/code/browser/workbench/workbench'], function() {}); require(['vs/code/browser/workbench/workbench'], function() {});
</script> </script>