Skip to content

Commit 3bc490b

Browse files
committed
Adding support for hosting with URL prefix to make reverse-proxy setup on sub-path possible
1 parent 73f6b09 commit 3bc490b

File tree

17 files changed

+358
-52
lines changed

17 files changed

+358
-52
lines changed

cmd/server.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,22 @@ export async function serveCommand(
6262
);
6363
}
6464

65+
let hostUrlPrefix = Deno.env.get("SB_URL_PREFIX");
66+
if (hostUrlPrefix) {
67+
if (!hostUrlPrefix.startsWith("/")) {
68+
hostUrlPrefix = "/" + hostUrlPrefix;
69+
}
70+
if (hostUrlPrefix.endsWith("/")) {
71+
hostUrlPrefix = hostUrlPrefix.replace(/\/*$/, "");
72+
}
73+
74+
if (hostUrlPrefix !== "") {
75+
console.log(`Host URL Prefix: ${hostUrlPrefix}`);
76+
} else {
77+
hostUrlPrefix = undefined;
78+
}
79+
}
80+
6581
const userAuth = options.user ?? Deno.env.get("SB_USER");
6682

6783
let userCredentials: AuthOptions | undefined;
@@ -131,6 +147,7 @@ export async function serveCommand(
131147
shellBackend: backendConfig,
132148
enableSpaceScript,
133149
pagesPath: folder,
150+
hostUrlPrefix,
134151
});
135152
await httpServer.start();
136153

common/space_lua/stdlib/space_lua.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,8 @@ export const spaceluaApi = new LuaTable({
146146
if (typeof location === "undefined") {
147147
return null;
148148
} else {
149-
return location.protocol + "//" + location.host;
149+
//NOTE: Removing trailing slash to stay compatible with original code: `location.protocol + "//" + location.host;`
150+
return document.baseURI.replace(/\/*$/, "");
150151
}
151152
},
152153
),

common/syscalls/system.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ export function systemSyscalls(
158158
await client.ds.batchDelete(allKeys);
159159
}
160160
if (logout) {
161-
location.href = "/.logout";
161+
location.href = ".logout";
162162
} else {
163163
alert("Client wiped, feel free to navigate elsewhere");
164164
}

lib/url_prefix.test.ts

Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
import { assertEquals } from "@std/assert";
2+
import { applyUrlPrefix, removeUrlPrefix } from "$lib/url_prefix.ts";
3+
4+
Deno.test("url_prefix - removeUrlPrefix - with value", async (t) => {
5+
await t.step("Absolute URL, present, should be removed", () => {
6+
assertEquals(
7+
removeUrlPrefix("http://myserver/sb/relevant", "/sb"),
8+
"http://myserver/relevant",
9+
);
10+
assertEquals(
11+
removeUrlPrefix("https://myserver/sb/relevant", "/sb"),
12+
"https://myserver/relevant",
13+
);
14+
});
15+
16+
await t.step("Absolute URL, present, should only remove leading", () => {
17+
assertEquals(
18+
removeUrlPrefix("http://myserver/sb/sb/relevant/sb", "/sb"),
19+
"http://myserver/sb/relevant/sb",
20+
);
21+
assertEquals(
22+
removeUrlPrefix("http://myserver/relevant/sb", "/sb"),
23+
"http://myserver/relevant/sb",
24+
);
25+
});
26+
27+
await t.step("Absolute URL, absent, should be untouched", () => {
28+
assertEquals(
29+
removeUrlPrefix("http://myserver/other/relevant", "/sb"),
30+
"http://myserver/other/relevant",
31+
);
32+
assertEquals(
33+
removeUrlPrefix("https://myserver/other/relevant", "/sb"),
34+
"https://myserver/other/relevant",
35+
);
36+
});
37+
38+
await t.step("Absolute URL, queryString, should be preserved", () => {
39+
assertEquals(
40+
removeUrlPrefix("http://myserver/sb/sb/relevant/sb?param=arg", "/sb"),
41+
"http://myserver/sb/relevant/sb?param=arg",
42+
);
43+
});
44+
45+
await t.step("Absolute URL, unsupported, should be untouched", () => {
46+
assertEquals(
47+
removeUrlPrefix("ftp://myserver/sb/relevant", "/sb"),
48+
"ftp://myserver/sb/relevant",
49+
);
50+
});
51+
52+
await t.step("Host-Relative URL, present, should be removed", () => {
53+
assertEquals(removeUrlPrefix("/sb/relevant", "/sb"), "/relevant");
54+
});
55+
56+
await t.step("Host-Relative URL, present, should only remove leading", () => {
57+
assertEquals(
58+
removeUrlPrefix("/sb/sb/relevant/sb", "/sb"),
59+
"/sb/relevant/sb",
60+
);
61+
assertEquals(removeUrlPrefix("/relevant/sb", "/sb"), "/relevant/sb");
62+
});
63+
64+
await t.step("Host-Relative URL, queryString, should be preserved", () => {
65+
assertEquals(
66+
removeUrlPrefix("/sb/sb/relevant/sb?param=arg", "/sb"),
67+
"/sb/relevant/sb?param=arg",
68+
);
69+
assertEquals(
70+
removeUrlPrefix("/relevant/sb?param=arg", "/sb"),
71+
"/relevant/sb?param=arg",
72+
);
73+
});
74+
75+
await t.step("Host-Relative URL, absent, should be untouched", () => {
76+
assertEquals(removeUrlPrefix("/other/relevant", "/sb"), "/other/relevant");
77+
});
78+
79+
await t.step("Page-Relative URL, should be untouched", () => {
80+
assertEquals(removeUrlPrefix("sb/relevant", "/sb"), "sb/relevant");
81+
});
82+
});
83+
84+
Deno.test("url_prefix - removeUrlPrefix - no value", async (t) => {
85+
await t.step("Absolute URL, should be untouched", () => {
86+
assertEquals(
87+
removeUrlPrefix("http://myserver/sb/relevant", ""),
88+
"http://myserver/sb/relevant",
89+
);
90+
assertEquals(
91+
removeUrlPrefix("https://myserver/sb/relevant"),
92+
"https://myserver/sb/relevant",
93+
);
94+
});
95+
96+
await t.step("Host-Relative URL, should be untouched", () => {
97+
assertEquals(removeUrlPrefix("/sb/relevant", ""), "/sb/relevant");
98+
assertEquals(removeUrlPrefix("/sb/relevant"), "/sb/relevant");
99+
});
100+
101+
await t.step("Page-Relative URL, should be untouched", () => {
102+
assertEquals(removeUrlPrefix("sb/relevant", ""), "sb/relevant");
103+
assertEquals(removeUrlPrefix("sb/relevant"), "sb/relevant");
104+
});
105+
});
106+
107+
Deno.test("url_prefix - applyUrlPrefix - with value", async (t) => {
108+
await t.step("string, Absolute URL, should be prefixed", () => {
109+
assertEquals(
110+
applyUrlPrefix("http://myserver/relevant", "/sb"),
111+
"http://myserver/sb/relevant",
112+
);
113+
assertEquals(
114+
applyUrlPrefix("https://myserver/relevant", "/sb"),
115+
"https://myserver/sb/relevant",
116+
);
117+
});
118+
119+
await t.step("string, Absolute URL, should not care about dups", () => {
120+
assertEquals(
121+
applyUrlPrefix("http://myserver/sb/relevant/sb", "/sb"),
122+
"http://myserver/sb/sb/relevant/sb",
123+
);
124+
});
125+
126+
await t.step("string, Absolute URL, queryString should be preserved", () => {
127+
assertEquals(
128+
applyUrlPrefix("http://myserver/sb/relevant/sb?param=arg", "/sb"),
129+
"http://myserver/sb/sb/relevant/sb?param=arg",
130+
);
131+
});
132+
133+
await t.step("string, Absolute URL, unsupported, should be untouched", () => {
134+
assertEquals(
135+
applyUrlPrefix("ftp://myserver/relevant", "/sb"),
136+
"ftp://myserver/relevant",
137+
);
138+
});
139+
140+
await t.step("string, Host-Relative URL, should be prefixed", () => {
141+
assertEquals(applyUrlPrefix("/relevant", "/sb"), "/sb/relevant");
142+
});
143+
144+
await t.step("string, Host-Relative URL, should not care about dups", () => {
145+
assertEquals(
146+
applyUrlPrefix("/sb/relevant/sb", "/sb"),
147+
"/sb/sb/relevant/sb",
148+
);
149+
});
150+
151+
await t.step(
152+
"string, Host-Relative URL, queryString should be preserved",
153+
() => {
154+
assertEquals(
155+
applyUrlPrefix("/sb/relevant/sb?param=arg", "/sb"),
156+
"/sb/sb/relevant/sb?param=arg",
157+
);
158+
},
159+
);
160+
161+
await t.step("string, Page-Relative URL, should be untouched", () => {
162+
assertEquals(applyUrlPrefix("relevant", "/sb"), "relevant");
163+
});
164+
165+
await t.step("URL object, Absolute URL, should be prefixed", () => {
166+
assertEquals(
167+
applyUrlPrefix(new URL("http://myserver/relevant"), "/sb"),
168+
new URL("http://myserver/sb/relevant"),
169+
);
170+
});
171+
172+
await t.step(
173+
"URL object, Absolute URL, queryString should be preserved",
174+
() => {
175+
assertEquals(
176+
applyUrlPrefix(new URL("http://myserver/relevant?param=arg"), "/sb"),
177+
new URL("http://myserver/sb/relevant?param=arg"),
178+
);
179+
},
180+
);
181+
});
182+
183+
Deno.test("url_prefix - applyUrlPrefix - no value", async (t) => {
184+
await t.step("Absolute URL, should be untouched", () => {
185+
assertEquals(
186+
applyUrlPrefix("http://myserver/relevant", ""),
187+
"http://myserver/relevant",
188+
);
189+
assertEquals(
190+
applyUrlPrefix("https://myserver/relevant"),
191+
"https://myserver/relevant",
192+
);
193+
});
194+
195+
await t.step("Host-Relative URL, should be untouched", () => {
196+
assertEquals(applyUrlPrefix("/relevant", ""), "/relevant");
197+
assertEquals(applyUrlPrefix("/relevant"), "/relevant");
198+
});
199+
200+
await t.step("Page-Relative URL, should be untouched", () => {
201+
assertEquals(applyUrlPrefix("relevant", ""), "relevant");
202+
assertEquals(applyUrlPrefix("relevant"), "relevant");
203+
});
204+
});

lib/url_prefix.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
export function removeUrlPrefix(url: string, prefix?: string): string {
2+
if (!prefix || prefix === "") return url;
3+
4+
if (url.startsWith("http://") || url.startsWith("https://")) {
5+
const parsedUrl = new URL(url);
6+
if (parsedUrl.pathname.startsWith(prefix)) {
7+
parsedUrl.pathname = parsedUrl.pathname.substring(
8+
prefix.length,
9+
);
10+
return parsedUrl.href;
11+
} else {
12+
return url;
13+
}
14+
} else if (url.startsWith(prefix)) {
15+
return url.substring(prefix.length);
16+
} else {
17+
return url;
18+
}
19+
}
20+
21+
export function applyUrlPrefix<T extends (string | URL)>(
22+
url: T,
23+
prefix?: string,
24+
): T {
25+
if (!prefix || prefix === "") return url;
26+
27+
if (typeof url === "string") {
28+
const urlString = url as string;
29+
30+
if (urlString.startsWith("http://") || urlString.startsWith("https://")) {
31+
return applyUrlPrefix(new URL(urlString), prefix).href as T;
32+
} else if (urlString.startsWith("/")) {
33+
return (prefix + urlString) as T;
34+
} else {
35+
return url; //return page-relative paths as-is
36+
}
37+
} else if (url.protocol === "http:" || url.protocol === "https:") {
38+
const urlObj = new URL(url);
39+
urlObj.pathname = prefix + urlObj.pathname;
40+
return urlObj as T;
41+
} else {
42+
return url;
43+
}
44+
}

plugs/markdown/preview.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ export async function updateMarkdownPreview() {
3939
"rhs",
4040
2,
4141
`
42-
<link rel="stylesheet" href="/.client/main.css" />
42+
<link rel="stylesheet" href=".client/main.css" />
4343
<style>
4444
${css}
4545
${customStyles ?? ""}

0 commit comments

Comments
 (0)