diff --git a/common/spaces/http_space_primitives.ts b/common/spaces/http_space_primitives.ts index ea766f59..e45a8517 100644 --- a/common/spaces/http_space_primitives.ts +++ b/common/spaces/http_space_primitives.ts @@ -5,6 +5,7 @@ import { unregisterServiceWorkers, } from "../sw_util.ts"; import { encodePageURI } from "@silverbulletmd/silverbullet/lib/page_ref"; +import { urlPrefix, toRealUrl } from "../../lib/url_hack.ts"; const defaultFetchTimeout = 30000; // 30 seconds @@ -38,7 +39,8 @@ export class HttpSpacePrimitives implements SpacePrimitives { try { options.signal = AbortSignal.timeout(fetchTimeout); options.redirect = "manual"; - const result = await fetch(url, options); + + const result = await fetch(toRealUrl(url), options); if (result.status === 503) { throw new Error("Offline"); } @@ -67,7 +69,7 @@ export class HttpSpacePrimitives implements SpacePrimitives { "Received an authentication redirect, redirecting to URL: " + redirectHeader, ); - location.href = redirectHeader; + location.href = redirectHeader.startsWith(urlPrefix) ? redirectHeader : toRealUrl(redirectHeader); throw new Error("Redirected"); } else { console.error("Got a redirect status but no location header", result); @@ -82,7 +84,7 @@ export class HttpSpacePrimitives implements SpacePrimitives { result.url, ); alert("You are not authenticated, redirecting to: " + redirectHeader); - location.href = redirectHeader; + location.href = redirectHeader.startsWith(urlPrefix) ? redirectHeader : toRealUrl(redirectHeader); throw new Error("Not authenticated"); } else { // If not, let's reload diff --git a/lib/plugos/sandboxes/worker_sandbox.ts b/lib/plugos/sandboxes/worker_sandbox.ts index ebac3b91..edda8530 100644 --- a/lib/plugos/sandboxes/worker_sandbox.ts +++ b/lib/plugos/sandboxes/worker_sandbox.ts @@ -3,6 +3,7 @@ import type { ControllerMessage, WorkerMessage } from "../protocol.ts"; import type { Plug } from "../plug.ts"; import { AssetBundle, type AssetJson } from "../../asset_bundle/bundle.ts"; import type { Sandbox } from "./sandbox.ts"; +import { toRealUrl } from "../../url_hack.ts"; /** * Represents a "safe" execution environment for plug code @@ -36,7 +37,7 @@ export class WorkerSandbox implements Sandbox { console.warn("Double init of sandbox, ignoring"); return Promise.resolve(); } - this.worker = new Worker(this.workerUrl, { + this.worker = new Worker(toRealUrl(this.workerUrl), { ...this.workerOptions, type: "module", }); diff --git a/lib/url_hack.ts b/lib/url_hack.ts new file mode 100644 index 00000000..fe9bafa5 --- /dev/null +++ b/lib/url_hack.ts @@ -0,0 +1,49 @@ +export const urlPrefix = Deno.env.get('SB_URL_PREFIX') ?? (globalThis.silverBulletConfig ? globalThis.silverBulletConfig.urlPrefix : null) ?? ''; + +export const toRealUrl = (url : T) : T => { + if (typeof url === 'string') { + const stringUrl = url as string; + if (stringUrl.startsWith('http://') || stringUrl.startsWith('https://')) { + const parsedUrl = new URL(stringUrl); + parsedUrl.pathname = urlPrefix + parsedUrl.pathname; + //console.log("Converted ", url, parsedUrl.href) + return String(parsedUrl.href) as T; + } + else { + if (!stringUrl.startsWith('/')) { + console.log("Don't know how to deal with relative path: ", url); + } + //console.log("Converted ", url, urlPrefix + stringUrl) + return (urlPrefix + stringUrl) as T; + } + } + else if (url.protocol === 'http:' || url.protocol === 'https:') { + const parsedUrl = new URL(url as URL); + parsedUrl.pathname = urlPrefix + parsedUrl.pathname; + //console.log("Converted ", url, parsedUrl) + return parsedUrl as T; + } + else { + return url; + } +}; + +export const toInternalUrl = (url : string) => { + if (url.startsWith('http://') || url.startsWith('https://')) { + var parsedUrl = new URL(url); + if (parsedUrl.pathname.startsWith(urlPrefix)) { + parsedUrl.pathname = parsedUrl.pathname.substr(urlPrefix.length); + return parsedUrl.href; + } + else { + return url; + } + } else if (url.startsWith(urlPrefix)) { + return url.substr(urlPrefix.length); + } + else { + console.log("Don't know how to deal with relative path: ", url); + return url; + } +}; + diff --git a/server/http_server.ts b/server/http_server.ts index 1407342e..2b5b18bf 100644 --- a/server/http_server.ts +++ b/server/http_server.ts @@ -20,6 +20,7 @@ import { parsePageRef, } from "@silverbulletmd/silverbullet/lib/page_ref"; import { base64Encode } from "$lib/crypto.ts"; +import { urlPrefix, toRealUrl } from "$lib/url_hack.ts"; const authenticationExpirySeconds = 60 * 60 * 24 * 7; // 1 week @@ -102,6 +103,9 @@ export class HttpServer { .replace( "{{SPACE_PATH}}", spaceServer.pagesPath.replaceAll("\\", "\\\\"), + ).replaceAll( + "{{URL_PREFIX}}", + urlPrefix ) .replace( "{{DESCRIPTION}}", @@ -283,7 +287,7 @@ export class HttpServer { if (req.method === "GET") { if (assetName === "service_worker.js") { c.header("Cache-Control", "no-cache"); - const textData = new TextDecoder().decode(data); + const textData = new TextDecoder().decode(data as Uint8Array); // console.log( // "Swapping out config hash in service worker", // ); @@ -298,6 +302,12 @@ export class HttpServer { ), ); } + if (assetName.endsWith(".css")) { + const textData = new TextDecoder().decode(data as Uint8Array); + data = textData.replaceAll( + "{{URL_PREFIX}}", + urlPrefix); + } return Promise.resolve(c.body(data)); } // else e.g. HEAD, OPTIONS, don't send body } catch { @@ -320,11 +330,15 @@ export class HttpServer { const url = new URL(c.req.url); deleteCookie(c, authCookieName(url.host)); - return c.redirect("/.auth"); + return c.redirect(toRealUrl("/.auth")); }); this.app.get("/.auth", (c) => { - const html = this.clientAssetBundle.readTextFileSync(".client/auth.html"); + const html = this.clientAssetBundle.readTextFileSync(".client/auth.html") + .replaceAll( + "{{URL_PREFIX}}", + urlPrefix + ); return c.html(html); }).post( @@ -336,7 +350,7 @@ export class HttpServer { !username || typeof username !== "string" || !password || typeof password !== "string" ) { - return c.redirect("/.auth?error=0"); + return c.redirect(toRealUrl("/.auth?error=0")); } return { username, password }; @@ -367,14 +381,16 @@ export class HttpServer { }); const values = await c.req.parseBody(); const from = values["from"]; - return c.redirect(typeof from === "string" ? from : "/"); + const result = toRealUrl(typeof from === "string" ? from : "/"); + console.log(result); + return c.redirect(result); } else { console.error("Authentication failed, redirecting to auth page."); - return c.redirect("/.auth?error=1", 401); + return c.redirect(toRealUrl("/.auth?error=1"), 401); } }, ).all((c) => { - return c.redirect("/.auth"); + return c.redirect(toRealUrl("/.auth")); }); // Check auth @@ -389,9 +405,9 @@ export class HttpServer { const redirectToAuth = () => { // Try filtering api paths if (req.path.startsWith("/.") || req.path.endsWith(".md")) { - return c.redirect("/.auth", 401); + return c.redirect(toRealUrl("/.auth"), 401); } else { - return c.redirect(`/.auth?from=${req.path}`, 401); + return c.redirect(toRealUrl(`/.auth?from=${req.path}`), 401); } }; if (!excludedPaths.includes(url.pathname)) { @@ -462,7 +478,7 @@ export class HttpServer { } else { // Otherwise, redirect to the UI // The reason to do this is to handle authentication systems like Authelia nicely - return c.redirect("/"); + return c.redirect(toRealUrl("/")); } }); @@ -541,7 +557,7 @@ export class HttpServer { console.warn( "Request was without X-Sync-Mode nor a CORS request, redirecting to page", ); - return c.redirect(`/${name.slice(0, -mdExt.length)}`); + return c.redirect(toRealUrl(`/${name.slice(0, -mdExt.length)}`)); } if (name.startsWith(".")) { // Don't expose hidden files diff --git a/web/auth.html b/web/auth.html index 6ea9def2..580d3cb3 100644 --- a/web/auth.html +++ b/web/auth.html @@ -69,11 +69,11 @@

- Login to + Login to SilverBullet

-
+
- - + + {{TITLE}} - + - + - - - + + + diff --git a/web/navigator.ts b/web/navigator.ts index 5a81df2b..98a5bf47 100644 --- a/web/navigator.ts +++ b/web/navigator.ts @@ -7,6 +7,7 @@ import type { Client } from "./client.ts"; import { cleanPageRef } from "@silverbulletmd/silverbullet/lib/resolve"; import { renderTheTemplate } from "$common/syscalls/template.ts"; import { safeRun } from "../lib/async.ts"; +import { toRealUrl, toInternalUrl } from "../lib/url_hack.ts"; export type PageState = PageRef & { scrollTop?: number; @@ -61,18 +62,18 @@ export class PathPageNavigator { globalThis.history.replaceState( cleanState, "", - `/${encodePageURI(currentState.page)}`, + toRealUrl(`/${encodePageURI(currentState.page)}`), ); globalThis.history.pushState( pageRef, "", - `/${encodePageURI(pageRef.page)}`, + toRealUrl(`/${encodePageURI(pageRef.page)}`), ); } else { globalThis.history.replaceState( pageRef, "", - `/${encodePageURI(pageRef.page)}`, + toRealUrl(`/${encodePageURI(pageRef.page)}`), ); } globalThis.dispatchEvent( @@ -148,7 +149,7 @@ export class PathPageNavigator { export function parsePageRefFromURI(): PageRef { const pageRef = parsePageRef(decodeURIComponent( - location.pathname.substring(1), + toInternalUrl(location.pathname).substring(1), )); if (location.hash) { diff --git a/web/styles/main.scss b/web/styles/main.scss index 59f18a3f..bbb71583 100644 --- a/web/styles/main.scss +++ b/web/styles/main.scss @@ -5,28 +5,28 @@ @font-face { font-family: "iA-Mono"; - src: url("/.client/iAWriterMonoS-Regular.woff2"); + src: url("{{URL_PREFIX}}/.client/iAWriterMonoS-Regular.woff2"); font-weight: normal; font-style: normal; } @font-face { font-family: "iA-Mono"; - src: url("/.client/iAWriterMonoS-Bold.woff2"); + src: url("{{URL_PREFIX}}/.client/iAWriterMonoS-Bold.woff2"); font-weight: bold; font-style: normal; } @font-face { font-family: "iA-Mono"; - src: url("/.client/iAWriterMonoS-Italic.woff2"); + src: url("{{URL_PREFIX}}/.client/iAWriterMonoS-Italic.woff2"); font-weight: normal; font-style: italic; } @font-face { font-family: "iA-Mono"; - src: url("/.client/iAWriterMonoS-BoldItalic.woff2"); + src: url("{{URL_PREFIX}}/.client/iAWriterMonoS-BoldItalic.woff2"); font-weight: bold; font-style: italic; } diff --git a/web/syscalls/editor.ts b/web/syscalls/editor.ts index 8c5a0b67..eff10bd1 100644 --- a/web/syscalls/editor.ts +++ b/web/syscalls/editor.ts @@ -16,6 +16,7 @@ import type { UploadFile } from "../../plug-api/types.ts"; import type { PageRef } from "@silverbulletmd/silverbullet/lib/page_ref"; import { openSearchPanel } from "@codemirror/search"; import { diffAndPrepareChanges } from "../cm_util.ts"; +import { toRealUrl, toInternalUrl } from "../../lib/url_hack.ts"; export function editorSyscalls(client: Client): SysCallMapping { const syscalls: SysCallMapping = { @@ -75,17 +76,17 @@ export function editorSyscalls(client: Client): SysCallMapping { }, "editor.openUrl": (_ctx, url: string, existingWindow = false) => { if (!existingWindow) { - const win = globalThis.open(url, "_blank"); + const win = globalThis.open(toRealUrl(url), "_blank"); if (win) { win.focus(); } } else { - location.href = url; + location.href = toRealUrl(url); } }, "editor.newWindow": () => { globalThis.open( - location.href, + toInternalUrl(location.href), "rnd" + Math.random(), `width=${globalThis.innerWidth},heigh=${globalThis.innerHeight}`, );