Try to add url_prefix support

pull/1131/head
Shihira Fung 2024-10-27 00:55:18 +08:00
parent 8acb112e4e
commit b1de476116
10 changed files with 108 additions and 35 deletions

View File

@ -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

View File

@ -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<HookT> implements Sandbox<HookT> {
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",
});

49
lib/url_hack.ts Normal file
View File

@ -0,0 +1,49 @@
export const urlPrefix = Deno.env.get('SB_URL_PREFIX') ?? (globalThis.silverBulletConfig ? globalThis.silverBulletConfig.urlPrefix : null) ?? '';
export const toRealUrl = <T extends (string | URL)>(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;
}
};

View File

@ -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

View File

@ -69,11 +69,11 @@
<body>
<header>
<h1>
Login to <img src="/.client/logo.png" style="height: 1ch" />
Login to <img src="{{URL_PREFIX}}/.client/logo.png" style="height: 1ch" />
SilverBullet
</h1>
</header>
<form action="/.auth" method="POST" id="login">
<form action="{{URL_PREFIX}}/.auth" method="POST" id="login">
<div class="error-message"></div>
<div>
<input

View File

@ -88,6 +88,7 @@ declare global {
syncOnly: boolean;
readOnly: boolean;
enableSpaceScript: boolean;
urlPrefix : string;
};
// deno-lint-ignore no-var
var client: Client;

View File

@ -6,8 +6,8 @@
name="viewport"
content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"
/>
<base href="/" />
<link rel="apple-touch-icon" href="/.client/logo.png" />
<base href="{{URL_PREFIX}}/" />
<link rel="apple-touch-icon" href="{{URL_PREFIX}}/.client/logo.png" />
<meta
name="theme-color"
content="#e1e1e1"
@ -21,7 +21,7 @@
<meta name="referrer" content="no-referrer" />
<title>{{TITLE}}</title>
<meta property="og:image" content="/.client/logo.png" />
<meta property="og:image" content="{{URL_PREFIX}}/.client/logo.png" />
<meta property="og:description" content="{{DESCRIPTION}}" />
<script>
@ -55,6 +55,7 @@
syncOnly: "{{SYNC_ONLY}}" === "true",
enableSpaceScript: "{{ENABLE_SPACE_SCRIPT}}" === "true",
readOnly: "{{READ_ONLY}}" === "true",
urlPrefix: "{{URL_PREFIX}}",
};
// But in case these variables aren't replaced by the server, fall back sync only mode
if (window.silverBulletConfig.spaceFolderPath.includes("{{")) {
@ -63,14 +64,15 @@
syncOnly: true,
readOnly: false,
enableSpaceScripts: false,
urlPrefix: "",
};
}
</script>
<link rel="stylesheet" href="/.client/main.css" />
<link rel="stylesheet" href="{{URL_PREFIX}}/.client/main.css" />
<style id="custom-styles"></style>
<script type="module" src="/.client/client.js"></script>
<link rel="manifest" href="/.client/manifest.json" />
<link rel="icon" type="image/x-icon" href="/.client/favicon.png" />
<script type="module" src="{{URL_PREFIX}}/.client/client.js"></script>
<link rel="manifest" href="{{URL_PREFIX}}/.client/manifest.json" />
<link rel="icon" type="image/x-icon" href="{{URL_PREFIX}}/.client/favicon.png" />
</head>
<body>

View File

@ -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) {

View File

@ -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;
}

View File

@ -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}`,
);