diff --git a/src/server/handlers/manifest-handler.ts b/src/server/handlers/manifest-handler.ts new file mode 100644 index 00000000..bdaed3f9 --- /dev/null +++ b/src/server/handlers/manifest-handler.ts @@ -0,0 +1,28 @@ +import type { Request, Response } from "express"; +import { LemmyHttp } from "lemmy-js-client"; +import { getHttpBaseInternal } from "../../shared/env"; +import { wrapClient } from "../../shared/services/HttpService"; +import generateManifestJson from "../utils/generate-manifest-json"; +import { setForwardedHeaders } from "../utils/set-forwarded-headers"; + +let manifest: Awaited> | undefined = + undefined; + +export default async (req: Request, res: Response) => { + if (!manifest) { + const headers = setForwardedHeaders(req.headers); + const client = wrapClient(new LemmyHttp(getHttpBaseInternal(), headers)); + const site = await client.getSite({}); + + if (site.state === "success") { + manifest = await generateManifestJson(site.data); + } else { + res.sendStatus(500); + return; + } + } + + res.setHeader("content-type", "application/manifest+json"); + + res.send(manifest); +}; diff --git a/src/server/index.tsx b/src/server/index.tsx index f109fc11..144b596e 100644 --- a/src/server/index.tsx +++ b/src/server/index.tsx @@ -2,6 +2,7 @@ import express from "express"; import path from "path"; import process from "process"; import CatchAllHandler from "./handlers/catch-all-handler"; +import ManifestHandler from "./handlers/manifest-handler"; import RobotsHandler from "./handlers/robots-handler"; import ServiceWorkerHandler from "./handlers/service-worker-handler"; import ThemeHandler from "./handlers/theme-handler"; @@ -24,6 +25,7 @@ if (!process.env["LEMMY_UI_DISABLE_CSP"] && !process.env["LEMMY_UI_DEBUG"]) { server.get("/robots.txt", RobotsHandler); server.get("/service-worker.js", ServiceWorkerHandler); +server.get("/manifest", ManifestHandler); server.get("/css/themes/:name", ThemeHandler); server.get("/css/themelist", ThemesListHandler); server.get("/*", CatchAllHandler); diff --git a/src/server/utils/create-ssr-html.tsx b/src/server/utils/create-ssr-html.tsx index 5cc38d7d..2c35aa29 100644 --- a/src/server/utils/create-ssr-html.tsx +++ b/src/server/utils/create-ssr-html.tsx @@ -5,32 +5,35 @@ import sharp from "sharp"; import { ILemmyConfig, IsoDataOptionalSite } from "../../shared/interfaces"; import { favIconPngUrl, favIconUrl } from "../../shared/utils"; import { fetchIconPng } from "./fetch-icon-png"; -import { generateManifestBase64 } from "./generate-manifest-base64"; const customHtmlHeader = process.env["LEMMY_UI_CUSTOM_HTML_HEADER"] || ""; +let appleTouchIcon: string | undefined = undefined; + export async function createSsrHtml( root: string, isoData: IsoDataOptionalSite ) { const site = isoData.site_res; - const appleTouchIcon = site?.site_view.site.icon - ? `data:image/png;base64,${sharp( - await fetchIconPng(site.site_view.site.icon) - ) - .resize(180, 180) - .extend({ - bottom: 20, - top: 20, - left: 20, - right: 20, - background: "#222222", - }) - .png() - .toBuffer() - .then(buf => buf.toString("base64"))}` - : favIconPngUrl; + if (!appleTouchIcon) { + appleTouchIcon = site?.site_view.site.icon + ? `data:image/png;base64,${sharp( + await fetchIconPng(site.site_view.site.icon) + ) + .resize(180, 180) + .extend({ + bottom: 20, + top: 20, + left: 20, + right: 20, + background: "#222222", + }) + .png() + .toBuffer() + .then(buf => buf.toString("base64"))}` + : favIconPngUrl; + } const erudaStr = process.env["LEMMY_UI_DEBUG"] === "true" @@ -74,15 +77,7 @@ export async function createSsrHtml( /> - ${ - site && - `` - } + diff --git a/src/server/utils/generate-manifest-base64.ts b/src/server/utils/generate-manifest-json.ts similarity index 69% rename from src/server/utils/generate-manifest-base64.ts rename to src/server/utils/generate-manifest-json.ts index e89b1559..b03fd87b 100644 --- a/src/server/utils/generate-manifest-base64.ts +++ b/src/server/utils/generate-manifest-json.ts @@ -5,7 +5,7 @@ import sharp from "sharp"; import { getHttpBaseExternal } from "../../shared/env"; import { fetchIconPng } from "./fetch-icon-png"; -const iconSizes = [72, 96, 144, 192, 512]; +const iconSizes = [72, 96, 128, 144, 152, 192, 384, 512]; const defaultLogoPathDirectory = path.join( process.cwd(), @@ -14,7 +14,7 @@ const defaultLogoPathDirectory = path.join( "icons" ); -export async function generateManifestBase64({ +export default async function ({ my_user, site_view: { site, @@ -25,7 +25,7 @@ export async function generateManifestBase64({ const icon = site.icon ? await fetchIconPng(site.icon) : null; - const manifest = { + return { name: site.name, description: site.description ?? "A link aggregator for the fediverse", start_url: url, @@ -69,31 +69,24 @@ export async function generateManifestBase64({ short_name: "Communities", description: "Browse communities", }, - ] - .concat( - my_user - ? [ - { - name: "Create Post", - url: "/create_post", - short_name: "Create Post", - description: "Create a post.", - }, - ] - : [] - ) - .concat( - my_user?.local_user_view.person.admin || !community_creation_admin_only - ? [ - { - name: "Create Community", - url: "/create_community", - short_name: "Create Community", - description: "Create a community", - }, - ] - : [] - ), + { + name: "Create Post", + url: "/create_post", + short_name: "Create Post", + description: "Create a post.", + }, + ].concat( + my_user?.local_user_view.person.admin || !community_creation_admin_only + ? [ + { + name: "Create Community", + url: "/create_community", + short_name: "Create Community", + description: "Create a community", + }, + ] + : [] + ), related_applications: [ { platform: "f-droid", @@ -102,6 +95,4 @@ export async function generateManifestBase64({ }, ], }; - - return Buffer.from(JSON.stringify(manifest)).toString("base64"); }