refactor(frontend): 🎨 Make glitch-soc server prettier

This commit is contained in:
Jesse Wierzbinski 2024-04-15 21:46:49 -10:00
parent 10b4378a68
commit 7bf5d628b6
No known key found for this signature in database

View file

@ -1,25 +1,16 @@
import { join } from "node:path"; import { join } from "node:path";
import { redirect } from "@response"; import { redirect } from "@response";
import { config } from "config-manager"; import { config } from "config-manager";
import { retrieveUserFromToken, userToAPI } from "~database/entities/User"; import {
retrieveUserFromToken,
userToAPI,
type UserWithRelations,
} from "~database/entities/User";
import type { LogManager, MultiLogManager } from "~packages/log-manager"; import type { LogManager, MultiLogManager } from "~packages/log-manager";
import { languages } from "./glitch-languages"; import { languages } from "./glitch-languages";
import type { BunFile } from "bun";
export const handleGlitchRequest = async ( const handleManifestRequest = async () => {
req: Request,
logger: LogManager | MultiLogManager,
): Promise<Response | null> => {
const url = new URL(req.url);
let path = url.pathname;
const accessToken = req.headers
.get("Cookie")
?.match(/_session_id=(.*?)(;|$)/)?.[1];
const user = await retrieveUserFromToken(accessToken ?? "");
// Strip leading /web from path
if (path.startsWith("/web")) path = path.slice(4);
if (path === "/manifest") {
const manifest = { const manifest = {
id: "/home", id: "/home",
name: config.instance.name, name: config.instance.name,
@ -86,8 +77,7 @@ export const handleGlitchRequest = async (
start_url: "/", start_url: "/",
scope: "/", scope: "/",
share_target: { share_target: {
url_template: url_template: "share?title={title}\u0026text={text}\u0026url={url}",
"share?title={title}\u0026text={text}\u0026url={url}",
action: "share", action: "share",
method: "GET", method: "GET",
enctype: "application/x-www-form-urlencoded", enctype: "application/x-www-form-urlencoded",
@ -107,34 +97,21 @@ export const handleGlitchRequest = async (
Date: new Date().toUTCString(), Date: new Date().toUTCString(),
}, },
}); });
} };
if (path === "/auth/sign_in") { const handleSignInRequest = async (
req: Request,
path: string,
url: URL,
user: UserWithRelations | null,
accessToken: string,
) => {
if (req.method === "POST") { if (req.method === "POST") {
return redirect("/api/auth/mastodon-login", 307); if (url.searchParams.get("error")) {
} const fileContents = await Bun.file(
path = "/auth/sign_in.html"; join(config.frontend.glitch.assets, "/auth/sign_in.html"),
} ).text();
if (path === "/auth/sign_out") {
if (req.method === "POST") {
return redirect("/api/auth/mastodon-logout", 307);
}
}
// Redirect / to /index.html
if (path === "/" || path === "") path = "/index.html";
// If path doesn't have an extension (e.g. /about), serve index.html
// Also check if Accept header contains text/html
if (!path.includes(".") && req.headers.get("Accept")?.includes("text/html"))
path = "/index.html";
const file = Bun.file(join(config.frontend.glitch.assets, path));
if (await file.exists()) {
let fileContents = await file.text();
if (path === "/auth/sign_in.html" && url.searchParams.get("error")) {
// Insert error message as first child of form.form_container // Insert error message as first child of form.form_container
const rewriter = new HTMLRewriter() const rewriter = new HTMLRewriter()
.on("div.form-container", { .on("div.form-container", {
@ -153,51 +130,68 @@ export const handleGlitchRequest = async (
}) })
.transform(new Response(fileContents)); .transform(new Response(fileContents));
fileContents = await rewriter.text(); return returnFile(
} Bun.file(
for (const server of config.frontend.glitch.server) { join(config.frontend.glitch.assets, "/auth/sign_in.html"),
fileContents = fileContents.replaceAll( ),
`${new URL(server).origin}/`, await indexTransforms(await rewriter.text(), accessToken, user),
"/",
); );
fileContents = fileContents.replaceAll( }
new URL(server).host, return redirect("/api/auth/mastodon-login", 307);
new URL(config.http.base_url).host, }
const file = Bun.file(
join(config.frontend.glitch.assets, "/auth/sign_in.html"),
);
return returnFile(
file,
await indexTransforms(await file.text(), accessToken, user),
);
};
const handleSignOutRequest = async (req: Request) => {
if (req.method === "POST") {
return redirect("/api/auth/mastodon-logout", 307);
}
return redirect("/", 307);
};
const returnFile = async (file: BunFile, content?: string) => {
return new Response(content ?? (await file.text()), {
headers: {
"Content-Type": `${file.type}; charset=utf-8`,
"Content-Length": String(file.size),
Date: new Date().toUTCString(),
},
});
};
const handleDefaultRequest = async (
req: Request,
path: string,
user: UserWithRelations | null,
accessToken: string,
) => {
const file = Bun.file(join(config.frontend.glitch.assets, path));
if (await file.exists()) {
return returnFile(
file,
await indexTransforms(await file.text(), accessToken, user),
); );
} }
fileContents = fileContents.replaceAll( return null;
"Glitch-soc is free open source software forked from Mastodon.", };
"Lysand is free and open-source software using the Glitch-Soc frontend.",
);
fileContents = fileContents.replaceAll("Mastodon", "Lysand");
fileContents = fileContents.replaceAll(
"Lysand is free, open-source software, and a trademark of Mastodon gGmbH.",
"This is not a Mastodon instance.",
);
fileContents = fileContents.replaceAll(
"joinmastodon.org",
"lysand.org",
);
// Strip integrity attributes from script and link tags const indexTransforms = async (
const rewriter = new HTMLRewriter() fileContents: string,
.on("script", { accessToken: string,
element(element) { user: UserWithRelations | null,
element.removeAttribute("integrity"); ) => {
}, let newFileContents = fileContents;
})
.on("link", {
element(element) {
element.removeAttribute("integrity");
},
})
.transform(new Response(fileContents));
fileContents = await rewriter.text();
// Check if file is index
if (path === "/index.html") {
// Find script id="initial-state" and replace its contents with custom json // Find script id="initial-state" and replace its contents with custom json
const rewriter = new HTMLRewriter() const rewriter = new HTMLRewriter()
.on("script#initial-state", { .on("script#initial-state", {
@ -217,8 +211,7 @@ export const handleGlitchRequest = async (
repository: "lysand-org/lysand", repository: "lysand-org/lysand",
search_enabled: true, search_enabled: true,
single_user_mode: false, single_user_mode: false,
source_url: source_url: "https://github.com/lysand-org/lysand",
"https://github.com/lysand-org/lysand",
sso_redirect: null, sso_redirect: null,
status_page_url: null, status_page_url: null,
streaming_api_base_url: `wss://${ streaming_api_base_url: `wss://${
@ -258,14 +251,11 @@ export const handleGlitchRequest = async (
settings: {}, settings: {},
max_feed_hashtags: 4, max_feed_hashtags: 4,
poll_limits: { poll_limits: {
max_options: max_options: config.validation.max_poll_options,
config.validation.max_poll_options,
max_option_chars: max_option_chars:
config.validation.max_poll_option_size, config.validation.max_poll_option_size,
min_expiration: min_expiration: config.validation.min_poll_duration,
config.validation.min_poll_duration, max_expiration: config.validation.max_poll_duration,
max_expiration:
config.validation.max_poll_duration,
}, },
languages: languages, languages: languages,
push_subscription: null, push_subscription: null,
@ -274,19 +264,77 @@ export const handleGlitchRequest = async (
); );
}, },
}) })
.transform(new Response(fileContents)); .on("script", {
element(element) {
fileContents = await rewriter.text(); element.removeAttribute("integrity");
}
return new Response(fileContents, {
headers: {
"Content-Type": `${file.type}; charset=utf-8`,
"Content-Length": String(file.size),
Date: new Date().toUTCString(),
}, },
}); })
.on("link", {
element(element) {
element.removeAttribute("integrity");
},
})
.transform(new Response(newFileContents));
for (const server of config.frontend.glitch.server) {
newFileContents = newFileContents.replaceAll(
`${new URL(server).origin}/`,
"/",
);
newFileContents = newFileContents.replaceAll(
new URL(server).host,
new URL(config.http.base_url).host,
);
} }
return null; newFileContents = newFileContents.replaceAll(
"Glitch-soc is free open source software forked from Mastodon.",
"Lysand is free and open-source software using the Glitch-Soc frontend.",
);
newFileContents = newFileContents.replaceAll("Mastodon", "Lysand");
newFileContents = newFileContents.replaceAll(
"Lysand is free, open-source software, and a trademark of Mastodon gGmbH.",
"This is not a Mastodon instance.",
);
newFileContents = newFileContents.replaceAll(
"joinmastodon.org",
"lysand.org",
);
return rewriter.text();
};
export const handleGlitchRequest = async (
req: Request,
logger: LogManager | MultiLogManager,
): Promise<Response | null> => {
const url = new URL(req.url);
let path = url.pathname;
const accessToken =
req.headers.get("Cookie")?.match(/_session_id=(.*?)(;|$)/)?.[1] ?? "";
const user = await retrieveUserFromToken(accessToken ?? "");
// Strip leading /web from path
if (path.startsWith("/web")) path = path.slice(4);
if (path === "/manifest") {
return handleManifestRequest();
}
if (path === "/auth/sign_in") {
return handleSignInRequest(req, path, url, user, accessToken);
}
if (path === "/auth/sign_out") {
return handleSignOutRequest(req);
}
// Redirect / to /index.html
if (path === "/" || path === "") path = "/index.html";
// If path doesn't have an extension (e.g. /about), serve index.html
// Also check if Accept header contains text/html
if (!path.includes(".") && req.headers.get("Accept")?.includes("text/html"))
path = "/index.html";
return handleDefaultRequest(req, path, user, accessToken);
}; };