refactor: address maintainer feedback on routing logic and env fallbacks
This commit is contained in:
@@ -2,9 +2,10 @@ import path from "node:path"
|
|||||||
import {promises as fs} from "node:fs"
|
import {promises as fs} from "node:fs"
|
||||||
import {fileURLToPath} from "node:url"
|
import {fileURLToPath} from "node:url"
|
||||||
|
|
||||||
|
import "dotenv/config"
|
||||||
import {serve} from "@hono/node-server"
|
import {serve} from "@hono/node-server"
|
||||||
import {serveStatic} from "@hono/node-server/serve-static"
|
import {serveStatic} from "@hono/node-server/serve-static"
|
||||||
import {fetchRelay} from "@welshman/app"
|
import {loadRelay} from "@welshman/app"
|
||||||
import {displayRelayUrl, normalizeRelayUrl} from "@welshman/util"
|
import {displayRelayUrl, normalizeRelayUrl} from "@welshman/util"
|
||||||
import {load} from "cheerio"
|
import {load} from "cheerio"
|
||||||
import {Hono} from "hono"
|
import {Hono} from "hono"
|
||||||
@@ -26,15 +27,8 @@ try {
|
|||||||
process.exit(1)
|
process.exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
const TEMPLATE_DOCUMENT = load(TEMPLATE_HTML)
|
const PLATFORM_NAME = process.env.VITE_PLATFORM_NAME
|
||||||
const DEFAULT_PLATFORM_NAME =
|
const PLATFORM_DESCRIPTION = process.env.VITE_PLATFORM_DESCRIPTION
|
||||||
process.env.VITE_PLATFORM_NAME ||
|
|
||||||
TEMPLATE_DOCUMENT('meta[property="og:title"]').attr("content") ||
|
|
||||||
"Flotilla"
|
|
||||||
const DEFAULT_PLATFORM_DESCRIPTION =
|
|
||||||
process.env.VITE_PLATFORM_DESCRIPTION ||
|
|
||||||
TEMPLATE_DOCUMENT('meta[name="description"]').attr("content") ||
|
|
||||||
"Flotilla is nostr - for communities."
|
|
||||||
|
|
||||||
// Match client-side decode logic
|
// Match client-side decode logic
|
||||||
const decodeRelay = url => {
|
const decodeRelay = url => {
|
||||||
@@ -61,72 +55,142 @@ const requestUrlFromContext = context => {
|
|||||||
return requestUrl
|
return requestUrl
|
||||||
}
|
}
|
||||||
|
|
||||||
const resolveMetadata = async requestUrl => {
|
const fetchRelayMeta = async relayUrl => {
|
||||||
const pathname = requestUrl.pathname
|
if (!relayUrl) return undefined
|
||||||
let relayParam = undefined
|
try {
|
||||||
|
return await loadRelay(normalizeRelayUrl(relayUrl))
|
||||||
// Match /join?r=...
|
} catch (err) {
|
||||||
if (pathname === "/join" || pathname === "/join/") {
|
console.error(`Failed to fetch relay metadata for ${relayUrl}:`, err)
|
||||||
relayParam = requestUrl.searchParams.get("r")
|
|
||||||
}
|
|
||||||
// Match /spaces/:relay/...
|
|
||||||
else if (pathname.startsWith("/spaces/")) {
|
|
||||||
const parts = pathname.split("/").filter(Boolean)
|
|
||||||
if (parts.length >= 2) {
|
|
||||||
relayParam = decodeRelay(parts[1])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!relayParam) {
|
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
const buildDefaultImage = requestUrl => {
|
||||||
// Note: fetchRelay from @welshman/app handles the ws->http conversion and caching
|
return new URL("/maskable-icon-512x512.png", requestUrl.origin).toString()
|
||||||
const relayMetadata = await fetchRelay(normalizeRelayUrl(relayParam))
|
}
|
||||||
|
|
||||||
if (!relayMetadata) {
|
const getMetadataForInvite = async (url, match) => {
|
||||||
return undefined
|
const relayParam = url.searchParams.get("r")
|
||||||
}
|
if (!relayParam) return undefined
|
||||||
|
|
||||||
const relayDisplay = displayRelayUrl(relayParam)
|
const relayMetadata = await fetchRelayMeta(relayParam)
|
||||||
const spaceName = relayMetadata.name
|
if (!relayMetadata) return undefined
|
||||||
const relayDescription = relayMetadata.description
|
|
||||||
|
|
||||||
const title = spaceName
|
const relayDisplay = displayRelayUrl(relayParam)
|
||||||
? `Invite to ${spaceName} on ${DEFAULT_PLATFORM_NAME}`
|
const spaceName = relayMetadata.name
|
||||||
: `Invite to a Space on ${DEFAULT_PLATFORM_NAME}`
|
const relayDescription = relayMetadata.description
|
||||||
|
|
||||||
const parts = []
|
const title = spaceName
|
||||||
if (spaceName) {
|
? `Invite to ${spaceName} on ${PLATFORM_NAME}`
|
||||||
parts.push(`You are invited to join ${spaceName} on ${DEFAULT_PLATFORM_NAME}.`)
|
: `Invite to a Space on ${PLATFORM_NAME}`
|
||||||
} else {
|
|
||||||
parts.push(`You are invited to join a space on ${DEFAULT_PLATFORM_NAME}.`)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (relayDisplay) parts.push(`Relay: ${relayDisplay}.`)
|
const parts = []
|
||||||
if (relayDescription) parts.push(relayDescription)
|
if (spaceName) {
|
||||||
else parts.push(DEFAULT_PLATFORM_DESCRIPTION)
|
parts.push(`You are invited to join ${spaceName} on ${PLATFORM_NAME}.`)
|
||||||
|
} else {
|
||||||
|
parts.push(`You are invited to join a space on ${PLATFORM_NAME}.`)
|
||||||
|
}
|
||||||
|
|
||||||
const description = parts.join(" ")
|
if (relayDisplay) parts.push(`Relay: ${relayDisplay}.`)
|
||||||
const image =
|
if (relayDescription) parts.push(relayDescription)
|
||||||
|
else parts.push(PLATFORM_DESCRIPTION)
|
||||||
|
|
||||||
|
const description = parts.join(" ")
|
||||||
|
const image =
|
||||||
|
relayMetadata.icon ||
|
||||||
|
relayMetadata.picture ||
|
||||||
|
relayMetadata.image ||
|
||||||
|
buildDefaultImage(url)
|
||||||
|
|
||||||
|
return {
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
image,
|
||||||
|
url: url.toString(),
|
||||||
|
site: url.origin,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getMetadataForSpace = async (url, match) => {
|
||||||
|
const relayParam = decodeRelay(match[1])
|
||||||
|
if (!relayParam) return undefined
|
||||||
|
|
||||||
|
const relayMetadata = await fetchRelayMeta(relayParam)
|
||||||
|
if (!relayMetadata) return undefined
|
||||||
|
|
||||||
|
const spaceName = relayMetadata.name || displayRelayUrl(relayParam)
|
||||||
|
|
||||||
|
return {
|
||||||
|
title: `${spaceName} on ${PLATFORM_NAME}`,
|
||||||
|
description: relayMetadata.description || PLATFORM_DESCRIPTION,
|
||||||
|
image:
|
||||||
relayMetadata.icon ||
|
relayMetadata.icon ||
|
||||||
relayMetadata.picture ||
|
relayMetadata.picture ||
|
||||||
relayMetadata.image ||
|
relayMetadata.image ||
|
||||||
new URL("/maskable-icon-512x512.png", requestUrl.origin).toString()
|
buildDefaultImage(url),
|
||||||
|
url: url.toString(),
|
||||||
return {
|
site: url.origin,
|
||||||
title,
|
|
||||||
description,
|
|
||||||
image,
|
|
||||||
url: requestUrl.toString(),
|
|
||||||
site: requestUrl.origin,
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
return undefined
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getMetadataForSpaceSection = async (url, match) => {
|
||||||
|
const spaceMeta = await getMetadataForSpace(url, match)
|
||||||
|
if (!spaceMeta) return undefined
|
||||||
|
|
||||||
|
const section = match[2]
|
||||||
|
const sectionName = section.charAt(0).toUpperCase() + section.slice(1)
|
||||||
|
spaceMeta.title = `${sectionName} on ${spaceMeta.title}`
|
||||||
|
return spaceMeta
|
||||||
|
}
|
||||||
|
|
||||||
|
const getMetadataForSpaceItem = async (url, match) => {
|
||||||
|
const spaceMeta = await getMetadataForSpace(url, match)
|
||||||
|
if (!spaceMeta) return undefined
|
||||||
|
|
||||||
|
const section = match[2]
|
||||||
|
let itemType = "Item"
|
||||||
|
if (section === "calendar") itemType = "Event"
|
||||||
|
if (section === "threads") itemType = "Thread"
|
||||||
|
if (section === "polls") itemType = "Poll"
|
||||||
|
if (section === "goals") itemType = "Goal"
|
||||||
|
if (section === "classifieds") itemType = "Listing"
|
||||||
|
|
||||||
|
spaceMeta.title = `${itemType} on ${spaceMeta.title}`
|
||||||
|
return spaceMeta
|
||||||
|
}
|
||||||
|
|
||||||
|
const getMetadataForRoom = async (url, match) => {
|
||||||
|
const spaceMeta = await getMetadataForSpace(url, match)
|
||||||
|
if (!spaceMeta) return undefined
|
||||||
|
|
||||||
|
// Room metadata requires fetching from Nostr, which can be added later.
|
||||||
|
spaceMeta.title = `Room on ${spaceMeta.title}`
|
||||||
|
return spaceMeta
|
||||||
|
}
|
||||||
|
|
||||||
|
const routes = [
|
||||||
|
[/^\/join\/?$/, getMetadataForInvite],
|
||||||
|
[/^\/spaces\/([^/]+)\/(calendar|chat|threads|polls|goals|classifieds|recent)\/?$/, getMetadataForSpaceSection],
|
||||||
|
[/^\/spaces\/([^/]+)\/(calendar|threads|polls|goals|classifieds)\/([^/]+)\/?$/, getMetadataForSpaceItem],
|
||||||
|
[/^\/spaces\/([^/]+)\/([^/]+)\/?$/, getMetadataForRoom],
|
||||||
|
[/^\/spaces\/([^/]+)\/?$/, getMetadataForSpace],
|
||||||
|
]
|
||||||
|
|
||||||
|
const getMetadataForRoute = async url => {
|
||||||
|
for (const [regex, getMetadata] of routes) {
|
||||||
|
const match = url.pathname.match(regex)
|
||||||
|
if (match) {
|
||||||
|
try {
|
||||||
|
return await getMetadata(url, match)
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`Error generating metadata for route ${url.pathname}:`, err)
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
const injectMeta = metadata => {
|
const injectMeta = metadata => {
|
||||||
const $ = load(TEMPLATE_HTML)
|
const $ = load(TEMPLATE_HTML)
|
||||||
|
|
||||||
@@ -196,7 +260,7 @@ app.get("*", async context => {
|
|||||||
return context.text("Not found", 404)
|
return context.text("Not found", 404)
|
||||||
}
|
}
|
||||||
|
|
||||||
const metadata = await resolveMetadata(requestUrl)
|
const metadata = await getMetadataForRoute(requestUrl)
|
||||||
const html = metadata ? injectMeta(metadata) : TEMPLATE_HTML
|
const html = metadata ? injectMeta(metadata) : TEMPLATE_HTML
|
||||||
|
|
||||||
return context.html(html, 200, {
|
return context.html(html, 200, {
|
||||||
|
|||||||
Reference in New Issue
Block a user