mirror of
https://github.com/versia-pub/server.git
synced 2025-12-06 16:38:19 +01:00
refactor(frontend): 🎨 Make glitch-soc server prettier
This commit is contained in:
parent
10b4378a68
commit
7bf5d628b6
|
|
@ -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);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue