refactor(api): 🔥 Remove Glitch-FE explicit support

This commit is contained in:
Jesse Wierzbinski 2024-08-27 16:45:05 +02:00
parent df466ecaa0
commit bec60fbf96
No known key found for this signature in database
12 changed files with 8 additions and 691 deletions

4
.gitignore vendored
View file

@ -179,10 +179,6 @@ log.txt
*.log *.log
build build
config/extended_description_test.md config/extended_description_test.md
glitch-old
glitch
glitch.tar.gz
glitch-dev
*.pem *.pem
oclif.manifest.json oclif.manifest.json
.direnv/ .direnv/

9
app.ts
View file

@ -11,7 +11,6 @@ import { bait } from "./middlewares/bait";
import { boundaryCheck } from "./middlewares/boundary-check"; import { boundaryCheck } from "./middlewares/boundary-check";
import { ipBans } from "./middlewares/ip-bans"; import { ipBans } from "./middlewares/ip-bans";
import { logger } from "./middlewares/logger"; import { logger } from "./middlewares/logger";
import { handleGlitchRequest } from "./packages/glitch-server/main";
import { routes } from "./routes"; import { routes } from "./routes";
import type { ApiRouteExports } from "./types/api"; import type { ApiRouteExports } from "./types/api";
@ -102,14 +101,6 @@ export const appFactory = async () => {
}); });
app.all("*", async (context) => { app.all("*", async (context) => {
if (config.frontend.glitch.enabled) {
const glitch = await handleGlitchRequest(context.req.raw);
if (glitch) {
return glitch;
}
}
const replacedUrl = new URL( const replacedUrl = new URL(
new URL(context.req.url).pathname, new URL(context.req.url).pathname,
config.frontend.url, config.frontend.url,

View file

@ -1,8 +1,7 @@
{ {
"$schema": "https://biomejs.dev/schemas/1.8.3/schema.json", "$schema": "https://biomejs.dev/schemas/1.8.3/schema.json",
"organizeImports": { "organizeImports": {
"enabled": true, "enabled": true
"ignore": ["node_modules", "dist", "glitch", "glitch-dev"]
}, },
"linter": { "linter": {
"enabled": true, "enabled": true,
@ -62,16 +61,17 @@
"useImportExtensions": "off", "useImportExtensions": "off",
"useThrowNewError": "warn" "useThrowNewError": "warn"
} }
}, }
"ignore": ["node_modules", "dist", "glitch", "glitch-dev"]
}, },
"formatter": { "formatter": {
"enabled": true, "enabled": true,
"indentStyle": "space", "indentStyle": "space",
"indentWidth": 4, "indentWidth": 4
"ignore": ["node_modules", "dist", "glitch", "glitch-dev"]
}, },
"javascript": { "javascript": {
"globals": ["Bun", "HTMLRewriter", "BufferEncoding"] "globals": ["Bun", "HTMLRewriter", "BufferEncoding"]
},
"files": {
"ignore": ["node_modules", "dist"]
} }
} }

View file

@ -120,7 +120,7 @@ bait_ips = ["127.0.0.1", "::1"]
bait_user_agents = ["curl", "wget"] bait_user_agents = ["curl", "wget"]
[frontend] [frontend]
# Enable custom frontends (warning: not enabling this or Glitch will make Versia Server only accessible via the Mastodon API) # Enable custom frontends (warning: not enabling this will make Versia Server only accessible via the Mastodon API)
# Frontends also control the OAuth flow, so if you disable this, you will need to use the Mastodon frontend # Frontends also control the OAuth flow, so if you disable this, you will need to use the Mastodon frontend
enabled = true enabled = true
# The URL to reach the frontend at (should be on a local network) # The URL to reach the frontend at (should be on a local network)
@ -141,14 +141,6 @@ url = "http://localhost:3000"
# This can be used to set up custom themes, etc on supported frontends. # This can be used to set up custom themes, etc on supported frontends.
# theme = "dark" # theme = "dark"
[frontend.glitch]
# Enable the Glitch frontend integration
enabled = false
# Glitch assets folder
assets = "glitch"
# Server the assets were ripped from (and any eventual CDNs)
server = ["https://tech.lgbt"]
[smtp] [smtp]
# SMTP server to use for sending emails # SMTP server to use for sending emails
server = "smtp.example.com" server = "smtp.example.com"

View file

@ -392,33 +392,6 @@
"$ref": "#/properties/http/properties/proxy/properties/address/anyOf/0", "$ref": "#/properties/http/properties/proxy/properties/address/anyOf/0",
"default": "http://localhost:3000" "default": "http://localhost:3000"
}, },
"glitch": {
"type": "object",
"properties": {
"enabled": {
"type": "boolean",
"default": false
},
"assets": {
"type": "string",
"minLength": 1,
"default": "glitch"
},
"server": {
"type": "array",
"items": {
"$ref": "#/properties/http/properties/proxy/properties/address/anyOf/0"
},
"default": []
}
},
"additionalProperties": false,
"default": {
"enabled": false,
"assets": "glitch",
"server": []
}
},
"routes": { "routes": {
"type": "object", "type": "object",
"properties": { "properties": {
@ -463,11 +436,6 @@
"default": { "default": {
"enabled": true, "enabled": true,
"url": "http://localhost:3000", "url": "http://localhost:3000",
"glitch": {
"enabled": false,
"assets": "glitch",
"server": []
},
"settings": {} "settings": {}
} }
}, },

View file

@ -5,7 +5,6 @@ services:
- ./logs:/app/dist/logs - ./logs:/app/dist/logs
- ./config:/app/dist/config - ./config:/app/dist/config
- ./uploads:/app/dist/uploads - ./uploads:/app/dist/uploads
- ./glitch:/app/dist/glitch
restart: unless-stopped restart: unless-stopped
container_name: versia container_name: versia
command: "cli start --all-threads" command: "cli start --all-threads"

View file

@ -1,37 +0,0 @@
# Enabling Glitch-Soc
Versia Server supports the use of the Glitch-Soc fork of Mastodon's frontend. Here's how to do it:
1. Download the latest Glitch FE package from [the releases page](https://github.com/lysand-org/server/releases) (it should be named `glitch.tar.gz` and be inside the assets of a normal Versia Server release).
2. Extract the contents of the package into a folder, which you can name `glitch` for simplicity. (if using Docker, now is the time to mount that folder into the container, for example with `-v /path/to/glitch:/app/dist/glitch`)
3. Change the config to enable Glitch-FE:
```toml
[frontend]
# Enable custom frontends (warning: not enabling this or Glitch will make Versia Server only accessible via the Mastodon API)
# Frontends also control the OAuth flow, so if you disable this, you will need to use the Mastodon frontend
enabled = true
# The URL to reach the frontend at (should be on a local network)
url = "http://localhost:3000"
[frontend.glitch]
# Enable the Glitch frontend integration
enabled = true
# Glitch assets folder
assets = "glitch"
# Server the assets were ripped from (and any eventual CDNs)
server = ["https://tech.lgbt"]
```
The `server` option can be left as-is, unless you have downloaded your own `index.html` file from a different Glitch instance.
4. Start Versia Server and navigate to `/` to see the Glitch frontend in action.
## How is this package created?
Glitch-FE is a React single-page app, which dynamically creates an `index.html` file on every request and builds all the other assets at build time.
The package static files were taken from a build of Glitch-Soc, while the index.html file was taken from [the tech.lgbt instance](https://tech.lgbt) with `cURL`.
Then, the paths in the `index.html` file were replaced with the correct paths for the static files (as they have different hashes in their names from the ones in the Glitch-Soc build).
At runtime, Versia Server dynamically edits the index.html file to replace the content with correct data, as well as disabling `integrity` checks on the script/link tags.
In the future, I'll find a way to make this less hacky and more user-friendly, but for now, this is the best I can do.

View file

@ -92,11 +92,6 @@ You may use the environment variables `NO_COLORS=true` and `NO_FANCY_DATES=true`
Please see the [CLI documentation](cli.md) for more information on how to use the CLI. Please see the [CLI documentation](cli.md) for more information on how to use the CLI.
> [!NOTE]
> You might be interested in running the [Glitch-Soc](glitch-soc.md) frontend, which is a fork of Mastodon's frontend with additional features.
>
> This is possible by following the instructions in [this file](glitch-soc.md).
## Updating the server ## Updating the server
Updating the server is as simple as pulling the latest changes from the repository and running `bun prod-build` again. You may need to run `bun install` again if there are new dependencies. Updating the server is as simple as pulling the latest changes from the repository and running `bun prod-build` again. You may need to run `bun install` again if there are new dependencies.

View file

@ -33,7 +33,7 @@
"start": "NODE_ENV=production bun run dist/index.js --prod", "start": "NODE_ENV=production bun run dist/index.js --prod",
"lint": "bunx @biomejs/biome check .", "lint": "bunx @biomejs/biome check .",
"build": "bun run build.ts", "build": "bun run build.ts",
"cloc": "cloc . --exclude-dir node_modules,dist,.output,.nuxt,meta,logs,glitch,glitch-dev --exclude-ext sql,log,pem", "cloc": "cloc . --exclude-dir node_modules,dist,.output,.nuxt,meta,logs --exclude-ext sql,log,pem",
"wc": "find server database *.ts docs packages types utils drizzle tests -type f -print0 | wc -m --files0-from=-", "wc": "find server database *.ts docs packages types utils drizzle tests -type f -print0 | wc -m --files0-from=-",
"cli": "bun run cli/index.ts", "cli": "bun run cli/index.ts",
"prune": "ts-prune | grep -v server/ | grep -v dist/ | grep -v '(used in module)'", "prune": "ts-prune | grep -v server/ | grep -v dist/ | grep -v '(used in module)'",

View file

@ -182,17 +182,6 @@ export const configValidator = z.object({
.object({ .object({
enabled: z.boolean().default(true), enabled: z.boolean().default(true),
url: zUrl.default("http://localhost:3000"), url: zUrl.default("http://localhost:3000"),
glitch: z
.object({
enabled: z.boolean().default(false),
assets: z.string().min(1).default("glitch"),
server: z.array(zUrl).default([]),
})
.default({
enabled: false,
assets: "glitch",
server: [],
}),
routes: z routes: z
.object({ .object({
home: zUrlPath.default("/"), home: zUrlPath.default("/"),
@ -213,11 +202,6 @@ export const configValidator = z.object({
.default({ .default({
enabled: true, enabled: true,
url: "http://localhost:3000", url: "http://localhost:3000",
glitch: {
enabled: false,
assets: "glitch",
server: [],
},
settings: {}, settings: {},
}), }),
smtp: z smtp: z

View file

@ -1,210 +0,0 @@
export const languages = [
["aa", "Afar", "Afaraf"],
["ab", "Abkhaz", "аҧсуа бызшәа"],
["ae", "Avestan", "avesta"],
["af", "Afrikaans", "Afrikaans"],
["ak", "Akan", "Akan"],
["am", "Amharic", "አማርኛ"],
["an", "Aragonese", "aragonés"],
["ar", "Arabic", "اللغة العربية"],
["as", "Assamese", "অসমীয়া"],
["av", "Avaric", "авар мацӀ"],
["ay", "Aymara", "aymar aru"],
["az", "Azerbaijani", "azərbaycan dili"],
["ba", "Bashkir", "башҡорт теле"],
["be", "Belarusian", "беларуская мова"],
["bg", "Bulgarian", "български език"],
["bh", "Bihari", "भोजपुरी"],
["bi", "Bislama", "Bislama"],
["bm", "Bambara", "bamanankan"],
["bn", "Bengali", "বাংলা"],
["bo", "Tibetan", "བོད་ཡིག"],
["br", "Breton", "brezhoneg"],
["bs", "Bosnian", "bosanski jezik"],
["ca", "Catalan", "Català"],
["ce", "Chechen", "нохчийн мотт"],
["ch", "Chamorro", "Chamoru"],
["co", "Corsican", "corsu"],
["cr", "Cree", "ᓀᐦᐃᔭᐍᐏᐣ"],
["cs", "Czech", "čeština"],
["cu", "Old Church Slavonic", "ѩзыкъ словѣньскъ"],
["cv", "Chuvash", "чӑваш чӗлхи"],
["cy", "Welsh", "Cymraeg"],
["da", "Danish", "dansk"],
["de", "German", "Deutsch"],
["dv", "Divehi", "Dhivehi"],
["dz", "Dzongkha", "རྫོང་ཁ"],
["ee", "Ewe", "Eʋegbe"],
["el", "Greek", "Ελληνικά"],
["en", "English", "English"],
["eo", "Esperanto", "Esperanto"],
["es", "Spanish", "Español"],
["et", "Estonian", "eesti"],
["eu", "Basque", "euskara"],
["fa", "Persian", "فارسی"],
["ff", "Fula", "Fulfulde"],
["fi", "Finnish", "suomi"],
["fj", "Fijian", "Vakaviti"],
["fo", "Faroese", "føroyskt"],
["fr", "French", "Français"],
["fy", "Western Frisian", "Frysk"],
["ga", "Irish", "Gaeilge"],
["gd", "Scottish Gaelic", "Gàidhlig"],
["gl", "Galician", "galego"],
["gu", "Gujarati", "ગુજરાતી"],
["gv", "Manx", "Gaelg"],
["ha", "Hausa", "هَوُسَ"],
["he", "Hebrew", "עברית"],
["hi", "Hindi", "हिन्दी"],
["ho", "Hiri Motu", "Hiri Motu"],
["hr", "Croatian", "Hrvatski"],
["ht", "Haitian", "Kreyòl ayisyen"],
["hu", "Hungarian", "magyar"],
["hy", "Armenian", "Հայերեն"],
["hz", "Herero", "Otjiherero"],
["ia", "Interlingua", "Interlingua"],
["id", "Indonesian", "Bahasa Indonesia"],
["ie", "Interlingue", "Interlingue"],
["ig", "Igbo", "Asụsụ Igbo"],
["ii", "Nuosu", "ꆈꌠ꒿ Nuosuhxop"],
["ik", "Inupiaq", "Iñupiaq"],
["io", "Ido", "Ido"],
["is", "Icelandic", "Íslenska"],
["it", "Italian", "Italiano"],
["iu", "Inuktitut", "ᐃᓄᒃᑎᑐᑦ"],
["ja", "Japanese", "日本語"],
["jv", "Javanese", "basa Jawa"],
["ka", "Georgian", "ქართული"],
["kg", "Kongo", "Kikongo"],
["ki", "Kikuyu", "Gĩkũyũ"],
["kj", "Kwanyama", "Kuanyama"],
["kk", "Kazakh", "қазақ тілі"],
["kl", "Kalaallisut", "kalaallisut"],
["km", "Khmer", "ខេមរភាសា"],
["kn", "Kannada", "ಕನ್ನಡ"],
["ko", "Korean", "한국어"],
["kr", "Kanuri", "Kanuri"],
["ks", "Kashmiri", "कश्मीरी"],
["ku", "Kurmanji (Kurdish)", "Kurmancî"],
["kv", "Komi", "коми кыв"],
["kw", "Cornish", "Kernewek"],
["ky", "Kyrgyz", "Кыргызча"],
["la", "Latin", "latine"],
["lb", "Luxembourgish", "Lëtzebuergesch"],
["lg", "Ganda", "Luganda"],
["li", "Limburgish", "Limburgs"],
["ln", "Lingala", "Lingála"],
["lo", "Lao", "ລາວ"],
["lt", "Lithuanian", "lietuvių kalba"],
["lu", "Luba-Katanga", "Tshiluba"],
["lv", "Latvian", "latviešu valoda"],
["mg", "Malagasy", "fiteny malagasy"],
["mh", "Marshallese", "Kajin M̧ajeļ"],
["mi", "Māori", "te reo Māori"],
["mk", "Macedonian", "македонски јазик"],
["ml", "Malayalam", "മലയാളം"],
["mn", "Mongolian", "Монгол хэл"],
["mr", "Marathi", "मराठी"],
["ms", "Malay", "Bahasa Melayu"],
["ms-Arab", "Jawi Malay", "بهاس ملايو"],
["mt", "Maltese", "Malti"],
["my", "Burmese", "ဗမာစာ"],
["na", "Nauru", "Ekakairũ Naoero"],
["nb", "Norwegian Bokmål", "Norsk bokmål"],
["nd", "Northern Ndebele", "isiNdebele"],
["ne", "Nepali", "नेपाली"],
["ng", "Ndonga", "Owambo"],
["nl", "Dutch", "Nederlands"],
["nn", "Norwegian Nynorsk", "Norsk Nynorsk"],
["no", "Norwegian", "Norsk"],
["nr", "Southern Ndebele", "isiNdebele"],
["nv", "Navajo", "Diné bizaad"],
["ny", "Chichewa", "chiCheŵa"],
["oc", "Occitan", "occitan"],
["oj", "Ojibwe", "ᐊᓂᔑᓈᐯᒧᐎᓐ"],
["om", "Oromo", "Afaan Oromoo"],
["or", "Oriya", "ଓଡ଼ିଆ"],
["os", "Ossetian", "ирон æвзаг"],
["pa", "Punjabi", "ਪੰਜਾਬੀ"],
["pi", "Pāli", "पाऴि"],
["pl", "Polish", "Polski"],
["ps", "Pashto", "پښتو"],
["pt", "Portuguese", "Português"],
["qu", "Quechua", "Runa Simi"],
["rm", "Romansh", "rumantsch grischun"],
["rn", "Kirundi", "Ikirundi"],
["ro", "Romanian", "Română"],
["ru", "Russian", "Русский"],
["rw", "Kinyarwanda", "Ikinyarwanda"],
["sa", "Sanskrit", "संस्कृतम्"],
["sc", "Sardinian", "sardu"],
["sd", "Sindhi", "सिन्धी"],
["se", "Northern Sami", "Davvisámegiella"],
["sg", "Sango", "yângâ tî sängö"],
["si", "Sinhala", "සිංහල"],
["sk", "Slovak", "slovenčina"],
["sl", "Slovenian", "slovenščina"],
["sn", "Shona", "chiShona"],
["so", "Somali", "Soomaaliga"],
["sq", "Albanian", "Shqip"],
["sr", "Serbian", "српски језик"],
["ss", "Swati", "SiSwati"],
["st", "Southern Sotho", "Sesotho"],
["su", "Sundanese", "Basa Sunda"],
["sv", "Swedish", "Svenska"],
["sw", "Swahili", "Kiswahili"],
["ta", "Tamil", "தமிழ்"],
["te", "Telugu", "తెలుగు"],
["tg", "Tajik", "тоҷикӣ"],
["th", "Thai", "ไทย"],
["ti", "Tigrinya", "ትግርኛ"],
["tk", "Turkmen", "Türkmen"],
["tl", "Tagalog", "Wikang Tagalog"],
["tn", "Tswana", "Setswana"],
["to", "Tonga", "faka Tonga"],
["tr", "Turkish", "Türkçe"],
["ts", "Tsonga", "Xitsonga"],
["tt", "Tatar", "татар теле"],
["tw", "Twi", "Twi"],
["ty", "Tahitian", "Reo Tahiti"],
["ug", "Uyghur", "ئۇيغۇرچە‎"],
["uk", "Ukrainian", "Українська"],
["ur", "Urdu", "اردو"],
["uz", "Uzbek", "Ўзбек"],
["ve", "Venda", "Tshivenḓa"],
["vi", "Vietnamese", "Tiếng Việt"],
["vo", "Volapük", "Volapük"],
["wa", "Walloon", "walon"],
["wo", "Wolof", "Wollof"],
["xh", "Xhosa", "isiXhosa"],
["yi", "Yiddish", "ייִדיש"],
["yo", "Yoruba", "Yorùbá"],
["za", "Zhuang", "Saɯ cueŋƅ"],
["zh", "Chinese", "中文"],
["zu", "Zulu", "isiZulu"],
["zh-CN", "Chinese (China)", "简体中文"],
["zh-HK", "Chinese (Hong Kong)", "繁體中文(香港)"],
["zh-TW", "Chinese (Taiwan)", "繁體中文(臺灣)"],
["zh-YUE", "Cantonese", "廣東話"],
["ast", "Asturian", "Asturianu"],
["chr", "Cherokee", "ᏣᎳᎩ ᎦᏬᏂᎯᏍᏗ"],
["ckb", "Sorani (Kurdish)", "سۆرانی"],
["cnr", "Montenegrin", "crnogorski"],
["csb", "Kashubian", "Kaszëbsczi"],
["jbo", "Lojban", "la .lojban."],
["kab", "Kabyle", "Taqbaylit"],
["ldn", "Láadan", "Láadan"],
["lfn", "Lingua Franca Nova", "lingua franca nova"],
["moh", "Mohawk", "Kanienʼkéha"],
["nds", "Low German", "Plattdüütsch"],
["pdc", "Pennsylvania Dutch", "Pennsilfaani-Deitsch"],
["sco", "Scots", "Scots"],
["sma", "Southern Sami", "Åarjelsaemien Gïele"],
["smj", "Lule Sami", "Julevsámegiella"],
["szl", "Silesian", "ślůnsko godka"],
["tok", "Toki Pona", "toki pona"],
["vai", "Vai", "ꕙꔤ"],
["xal", "Kalmyk", "Хальмг келн"],
["zba", "Balaibalan", "باليبلن"],
["zgh", "Standard Moroccan Tamazight", "ⵜⴰⵎⴰⵣⵉⵖⵜ"],
];

View file

@ -1,361 +0,0 @@
import { join } from "node:path";
import { redirect } from "@/response";
import type { BunFile } from "bun";
import { retrieveUserFromToken } from "~/classes/functions/user";
import { config } from "~/packages/config-manager/index";
import type { User } from "~/packages/database-interface/user";
import { languages } from "./glitch-languages";
const handleManifestRequest = () => {
const manifest = {
id: "/home",
name: config.instance.name,
short_name: config.instance.name,
icons: [
{
src: "/packs/media/icons/android-chrome-36x36-e67f2bc645cc669c04ffcbc17203aeac.png",
sizes: "36x36",
type: "image/png",
purpose: "any maskable",
},
{
src: "/packs/media/icons/android-chrome-48x48-d3afc36e9388913fb6add2476a556f67.png",
sizes: "48x48",
type: "image/png",
purpose: "any maskable",
},
{
src: "/packs/media/icons/android-chrome-72x72-23ee104da45dc5388d59b8b0fad866f2.png",
sizes: "72x72",
type: "image/png",
purpose: "any maskable",
},
{
src: "/packs/media/icons/android-chrome-96x96-fb2abfd885ab5de94025e09f6f9408b5.png",
sizes: "96x96",
type: "image/png",
purpose: "any maskable",
},
{
src: "/packs/media/icons/android-chrome-144x144-99b386f89a3a2a22440964eba3b9f242.png",
sizes: "144x144",
type: "image/png",
purpose: "any maskable",
},
{
src: "/packs/media/icons/android-chrome-192x192-8b4d35fdd9b5fa4592056ce687c9d0ba.png",
sizes: "192x192",
type: "image/png",
purpose: "any maskable",
},
{
src: "/packs/media/icons/android-chrome-256x256-fecf6504157e3b195dd0e604cd711730.png",
sizes: "256x256",
type: "image/png",
purpose: "any maskable",
},
{
src: "/packs/media/icons/android-chrome-384x384-dc559d916be51de4965dd7b8abf9c7c8.png",
sizes: "384x384",
type: "image/png",
purpose: "any maskable",
},
{
src: "/packs/media/icons/android-chrome-512x512-85515d059c83f47d8e77e0703ebb7ff5.png",
sizes: "512x512",
type: "image/png",
purpose: "any maskable",
},
],
theme_color: "#191b22",
background_color: "#191b22",
display: "standalone",
start_url: "/",
scope: "/",
share_target: {
url_template: "share?title={title}\u0026text={text}\u0026url={url}",
action: "share",
method: "GET",
enctype: "application/x-www-form-urlencoded",
params: { title: "title", text: "text", url: "url" },
},
shortcuts: [
{ name: "Compose new post", url: "/publish" },
{ name: "Notifications", url: "/notifications" },
{ name: "Explore", url: "/explore" },
],
};
return new Response(JSON.stringify(manifest), {
headers: {
"Content-Type": "application/json; charset=utf-8",
"Content-Length": String(JSON.stringify(manifest).length),
"Cache-Control": "max-age=31536000",
Date: new Date().toUTCString(),
},
});
};
const handleSignInRequest = async (
req: Request,
_path: string,
url: URL,
user: User | null,
accessToken: string,
) => {
if (req.method === "POST") {
if (url.searchParams.get("error")) {
const fileContents = await Bun.file(
join(config.frontend.glitch.assets, "/auth/sign_in.html"),
).text();
// Insert error message as first child of form.form_container
const rewriter = new HTMLRewriter()
.on("div.form-container", {
element(element) {
element.prepend(
` <div class='flash-message alert'>
<strong>${decodeURIComponent(
url.searchParams.get("error") ?? "",
)}</strong>
</div>`,
{
html: true,
},
);
},
})
.transform(new Response(fileContents));
return returnFile(
Bun.file(
join(config.frontend.glitch.assets, "/auth/sign_in.html"),
),
await brandingTransforms(
await rewriter.text(),
accessToken,
user,
),
);
}
return redirect("/api/auth/mastodon-login", 307);
}
const file = Bun.file(
join(config.frontend.glitch.assets, "/auth/sign_in.html"),
);
return returnFile(
file,
await htmlTransforms(
await brandingTransforms(await file.text(), accessToken, user),
accessToken,
user,
),
);
};
const handleSignOutRequest = (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),
"Cache-Control": "max-age=31536000",
Date: new Date().toUTCString(),
},
});
};
const handleDefaultRequest = async (
_req: Request,
path: string,
user: User | null,
accessToken: string,
) => {
const file = Bun.file(join(config.frontend.glitch.assets, path));
if (await file.exists()) {
const transformedText = await brandingTransforms(
path.endsWith(".html")
? await htmlTransforms(await file.text(), accessToken, user)
: await file.text(),
accessToken,
user,
);
return returnFile(file, transformedText);
}
return null;
};
const brandingTransforms = (
fileContents: string,
_accessToken: string,
_user: User | null,
) => {
let newFileContents = fileContents;
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,
);
}
newFileContents = newFileContents.replaceAll(
"Glitch-soc is free open source software forked from Mastodon.",
"Versia Server is free and open-source software using the Glitch-Soc frontend.",
);
newFileContents = newFileContents.replaceAll("Mastodon", "Versia Server");
newFileContents = newFileContents.replaceAll(
"Versia is free, open-source software, and a trademark of Versia gGmbH.",
"This is not a Mastodon instance.",
);
newFileContents = newFileContents.replaceAll(
"joinmastodon.org",
"versia.pub",
);
return newFileContents;
};
const htmlTransforms = async (
fileContents: string,
accessToken: string,
user: User | null,
) => {
// Find script id="initial-state" and replace its contents with custom json
const rewriter = new HTMLRewriter()
.on("script#initial-state", {
element(element) {
element.setInnerContent(
JSON.stringify({
meta: {
access_token: accessToken || null,
activity_api_enabled: true,
admin: null,
domain: new URL(config.http.base_url).host,
limited_federation_mode: false,
locale: "en",
mascot: "https://media.tech.lgbt/site_uploads/files/000/000/004/original/1a16a73feb5c2463.png",
profile_directory: true,
registrations_open: true,
repository: "lysand-org/server",
search_enabled: true,
single_user_mode: false,
source_url: "https://github.com/lysand-org/server",
sso_redirect: null,
status_page_url: null,
streaming_api_base_url: `wss://${
new URL(config.http.base_url).host
}`,
timeline_preview: true,
title: config.instance.name,
trends_as_landing_page: false,
trends_enabled: true,
version: "4.3.0-alpha.3+glitch",
auto_play_gif: null,
display_media: null,
reduce_motion: null,
use_blurhash: null,
me: user ? user.id : undefined,
},
compose: user
? {
text: "",
me: user.id,
default_privacy: "public",
default_sensitive: false,
default_language: "en",
}
: {
text: "",
},
accounts: user
? {
[user.id]: user.toApi(true),
}
: {},
media_attachments: {
accept_content_types:
config.validation.allowed_mime_types,
},
settings: {},
max_feed_hashtags: 4,
poll_limits: {
max_options: config.validation.max_poll_options,
max_option_chars:
config.validation.max_poll_option_size,
min_expiration: config.validation.min_poll_duration,
max_expiration: config.validation.max_poll_duration,
},
languages: languages,
push_subscription: null,
role: null,
}),
);
},
})
.on("script", {
element(element) {
element.removeAttribute("integrity");
},
})
.on("link", {
element(element) {
element.removeAttribute("integrity");
},
})
.transform(new Response(fileContents));
return rewriter.text();
};
export const handleGlitchRequest = async (
req: Request,
): 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);
}
if (
req.headers.get("Accept")?.includes("text/html") &&
!path.includes(".")
) {
path = "/index.html";
}
return handleDefaultRequest(req, path, user, accessToken);
};