diff --git a/server.js b/server.js index 427367e8..219d3e4f 100644 --- a/server.js +++ b/server.js @@ -30,6 +30,16 @@ const NOSTR_CACHE_TTL_MS = 60 * 1000 const RELAY_TIMEOUT_MS = 1500 const NOSTR_TIMEOUT_MS = 1800 +const META_TOKENS = { + card: "__FLOTILLA_META_CARD__", + description: "__FLOTILLA_META_DESCRIPTION__", + image: "__FLOTILLA_META_IMAGE__", + site: "__FLOTILLA_META_SITE__", + title: "__FLOTILLA_META_TITLE__", + type: "__FLOTILLA_META_TYPE__", + url: "__FLOTILLA_META_URL__", +} + const staticTitles = new Map([ ["/", "Redirecting"], ["/home", "Home"], @@ -86,6 +96,7 @@ const indexHtml = await readFile(indexPath, "utf8").catch(error => { }) const defaults = getHtmlDefaults(indexHtml) +const htmlTemplate = createHtmlTemplate(indexHtml) const app = new Hono() const staticFiles = serveStatic({ root: "./build", @@ -103,7 +114,7 @@ app.get("*", async c => { const origin = getRequestOrigin(c.req.raw, requestUrl) const meta = await buildRouteMeta(requestUrl, origin) - const html = renderHtml(indexHtml, meta) + const html = renderHtml(htmlTemplate, meta) c.header("Cache-Control", "no-cache") @@ -297,7 +308,7 @@ async function buildJoinMeta(meta, requestUrl, origin) { const relayUrl = parseInviteRelay(requestUrl) if (!relayUrl) { - meta.title = staticTitles.get("/join") || "Join Space" + meta.title = staticTitles.get("/join") return meta } @@ -553,30 +564,50 @@ async function requestEvents(relayUrl, filters) { } } -function renderHtml(html, meta) { +function createHtmlTemplate(html) { const $ = loadHtml(html) - upsertTitle($, meta.title) - upsertMetaTag($, "name", "description", meta.description) - upsertMetaTag($, "name", "og:url", meta.url) - upsertMetaTag($, "name", "og:type", meta.type) - upsertMetaTag($, "name", "og:title", meta.title) - upsertMetaTag($, "name", "og:description", meta.description) - upsertMetaTag($, "name", "twitter:card", meta.card) - upsertMetaTag($, "name", "twitter:site", meta.site) - upsertMetaTag($, "name", "twitter:title", meta.title) - upsertMetaTag($, "name", "twitter:description", meta.description) - upsertMetaTag($, "name", "twitter:image", meta.image) + upsertTitle($, META_TOKENS.title) + upsertMetaTag($, "name", "description", META_TOKENS.description) + upsertMetaTag($, "name", "og:url", META_TOKENS.url) + upsertMetaTag($, "name", "og:type", META_TOKENS.type) + upsertMetaTag($, "name", "og:title", META_TOKENS.title) + upsertMetaTag($, "name", "og:description", META_TOKENS.description) + upsertMetaTag($, "name", "twitter:card", META_TOKENS.card) + upsertMetaTag($, "name", "twitter:site", META_TOKENS.site) + upsertMetaTag($, "name", "twitter:title", META_TOKENS.title) + upsertMetaTag($, "name", "twitter:description", META_TOKENS.description) + upsertMetaTag($, "name", "twitter:image", META_TOKENS.image) - upsertMetaTag($, "property", "og:url", meta.url) - upsertMetaTag($, "property", "og:type", meta.type) - upsertMetaTag($, "property", "og:title", meta.title) - upsertMetaTag($, "property", "og:description", meta.description) - upsertMetaTag($, "property", "og:image", meta.image) + upsertMetaTag($, "property", "og:url", META_TOKENS.url) + upsertMetaTag($, "property", "og:type", META_TOKENS.type) + upsertMetaTag($, "property", "og:title", META_TOKENS.title) + upsertMetaTag($, "property", "og:description", META_TOKENS.description) + upsertMetaTag($, "property", "og:image", META_TOKENS.image) return $.html() } +function renderHtml(template, meta) { + return template + .replaceAll(META_TOKENS.card, escapeHtml(meta.card)) + .replaceAll(META_TOKENS.description, escapeHtml(meta.description)) + .replaceAll(META_TOKENS.image, escapeHtml(meta.image)) + .replaceAll(META_TOKENS.site, escapeHtml(meta.site)) + .replaceAll(META_TOKENS.title, escapeHtml(meta.title)) + .replaceAll(META_TOKENS.type, escapeHtml(meta.type)) + .replaceAll(META_TOKENS.url, escapeHtml(meta.url)) +} + +function escapeHtml(value) { + return String(value) + .replaceAll("&", "&") + .replaceAll("<", "<") + .replaceAll(">", ">") + .replaceAll('"', """) + .replaceAll("'", "'") +} + function upsertTitle($, value) { let titleTag = $("head > title").first()