From efb6c9df052c7bd7e89d89caa907c92a1bf7d945 Mon Sep 17 00:00:00 2001 From: Anthony Ettinger Date: Mon, 26 Oct 2026 09:59:47 +0000 Subject: [PATCH] Profullstack footer (webring + built.indie + pwamart badges) on the sign-in page Server-rendered @profullstack/footer (clearnet template) under the sign-in form, refreshed from the @latest CDN template every 15 min, plus /.well-known/openwebring.json, so app.moshcode.sh can join the Profullstack ring. Co-Authored-By: Claude Opus 5.5 --- apps/pwa/package-lock.json | 21 +++++++++++++++ apps/pwa/package.json | 1 + apps/pwa/pnpm-lock.yaml | 15 +++++++++++ apps/pwa/src/lib/ring-footer.mjs | 41 ++++++++++++++++++++++++++++++ apps/pwa/src/routes/auth.mjs | 2 ++ apps/pwa/src/server.mjs | 6 +++++ apps/pwa/test/ring-footer.test.mjs | 24 +++++++++++++++++ 7 files changed, 110 insertions(+) create mode 100644 apps/pwa/src/lib/ring-footer.mjs create mode 100644 apps/pwa/test/ring-footer.test.mjs diff --git a/apps/pwa/package-lock.json b/apps/pwa/package-lock.json index e2ad17bc..9d18f13e 100644 --- a/apps/pwa/package-lock.json +++ b/apps/pwa/package-lock.json @@ -10,6 +10,7 @@ "dependencies": { "@libsql/client": "^0.14.0", "@peculiar/x509": "^1.14.3", + "@profullstack/footer": "0.2.6", "@profullstack/libsql-pg": "^0.1.2", "@profullstack/notifications": "^0.1.0", "@profullstack/synconfig": "^0.1.1", @@ -396,6 +397,26 @@ "node": ">=20.0.0" } }, + "node_modules/@profullstack/footer": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/@profullstack/footer/-/footer-0.2.6.tgz", + "integrity": "sha512-BKwbDszAc7e0mYIBiPjgr/5WS8WyDt8IW718s/zW/8pCWOIB1BGwSNs6fmImRSo5kqIjQKUVbXp22jZQ5GEipA==", + "license": "MIT", + "bin": { + "profullstack-footer": "bin/profullstack-footer.js" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "react": ">=18" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + } + } + }, "node_modules/@profullstack/libsql-pg": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/@profullstack/libsql-pg/-/libsql-pg-0.1.2.tgz", diff --git a/apps/pwa/package.json b/apps/pwa/package.json index 87cfb74d..119c2f62 100644 --- a/apps/pwa/package.json +++ b/apps/pwa/package.json @@ -16,6 +16,7 @@ "dependencies": { "@libsql/client": "^0.14.0", "@peculiar/x509": "^1.14.3", + "@profullstack/footer": "0.2.6", "@profullstack/libsql-pg": "^0.1.2", "@profullstack/notifications": "^0.1.0", "@profullstack/synconfig": "^0.1.1", diff --git a/apps/pwa/pnpm-lock.yaml b/apps/pwa/pnpm-lock.yaml index 9b8f0a6b..c908a508 100644 --- a/apps/pwa/pnpm-lock.yaml +++ b/apps/pwa/pnpm-lock.yaml @@ -14,6 +14,9 @@ importers: '@peculiar/x509': specifier: ^1.14.3 version: 1.14.3 + '@profullstack/footer': + specifier: 0.2.6 + version: 0.2.6 '@profullstack/libsql-pg': specifier: ^0.1.2 version: 0.1.2 @@ -215,6 +218,16 @@ packages: resolution: {integrity: sha512-C2Xj8FZ0uHWeCXXqX5B4/gVFQmtSkiuOolzAgutjTfseNOHT3pUjljDZsTSxXFGgio54bCzVFqmEOUrIVk8RDA==} engines: {node: '>=20.0.0'} + '@profullstack/footer@0.2.6': + resolution: {integrity: sha512-BKwbDszAc7e0mYIBiPjgr/5WS8WyDt8IW718s/zW/8pCWOIB1BGwSNs6fmImRSo5kqIjQKUVbXp22jZQ5GEipA==} + engines: {node: '>=18'} + hasBin: true + peerDependencies: + react: '>=18' + peerDependenciesMeta: + react: + optional: true + '@profullstack/libsql-pg@0.1.2': resolution: {integrity: sha512-6YrQujssWhkyWInB0G4CWOhH6Pj6kydeK4RKrXOosxQ12flpO4VjUEGTabtSh9zZ7FzCRch0hwFNKApJl/7b4w==} engines: {node: '>=20'} @@ -874,6 +887,8 @@ snapshots: tslib: 2.8.1 tsyringe: 4.10.0 + '@profullstack/footer@0.2.6': {} + '@profullstack/libsql-pg@0.1.2': dependencies: pg: 8.23.0 diff --git a/apps/pwa/src/lib/ring-footer.mjs b/apps/pwa/src/lib/ring-footer.mjs new file mode 100644 index 00000000..c57fe184 --- /dev/null +++ b/apps/pwa/src/lib/ring-footer.mjs @@ -0,0 +1,41 @@ +// The Profullstack footer (links, copyright, the << Profullstack >> webring row and +// the built.indie + pwamart badges) from @profullstack/footer, on the public +// sign-in page. It is rendered into the HTML on the server because rssamplifier's +// ring verifier reads the page and never runs script. +// +// Pages render synchronously, so the footer is kept as a string: the bundled +// template at boot, then the @latest template from the CDN, refreshed every 15 +// minutes (a footer release reaches us with no redeploy). A failed refresh keeps +// the last good copy. app.moshcode.sh is an ICANN host: clearnet template only. +import { footerHtml, footerHtmlSync } from "@profullstack/footer"; + +export const RING_FOOTER_OPTIONS = { + site: "https://app.moshcode.sh/", + links: [ + { label: "moshcode.sh", href: "https://moshcode.sh/" }, + { label: "The pit", href: "/pit" }, + ], +}; + +let html = footerHtmlSync(RING_FOOTER_OPTIONS); + +/** The footer markup; never empty. */ +export function ringFooter() { + return html; +} + +/** Accept a rendered footer only when it still links the ring. */ +export function setRingFooter(next) { + if (typeof next !== "string" || !next.includes("/ring/profullstack") || next.length > 64_000) return false; + html = next; + return true; +} + +export function startRingFooterRefresh(everyMs = 15 * 60_000) { + if (process.env.PROFULLSTACK_FOOTER_OFFLINE === "1") return () => {}; + const run = () => { footerHtml(RING_FOOTER_OPTIONS).then(setRingFooter, () => {}); }; + run(); + const timer = setInterval(run, everyMs); + timer.unref?.(); + return () => clearInterval(timer); +} diff --git a/apps/pwa/src/routes/auth.mjs b/apps/pwa/src/routes/auth.mjs index e2d5e145..e2739377 100644 --- a/apps/pwa/src/routes/auth.mjs +++ b/apps/pwa/src/routes/auth.mjs @@ -6,6 +6,7 @@ import { hashPassword, verifyPassword } from "../lib/crypto.mjs"; import { createUserWithPassword, userByEmail } from "../lib/users.mjs"; import { dashboardHandler } from "./pages.mjs"; import { config } from "../config.mjs"; +import { ringFooter } from "../lib/ring-footer.mjs"; export const authRouter = Router(); @@ -45,6 +46,7 @@ function authPage(req, { error = "", mode = "in" } = {}) {

${footer} +
${ringFooter()}
`; return page({ title: "moshcode ▸ sign in", body }); diff --git a/apps/pwa/src/server.mjs b/apps/pwa/src/server.mjs index 15f99b47..f1f2de50 100644 --- a/apps/pwa/src/server.mjs +++ b/apps/pwa/src/server.mjs @@ -23,6 +23,8 @@ import { socialsRouter } from "./routes/socials.mjs"; import { adminRouter } from "./routes/admin.mjs"; import { MAX_BATCH, MAX_PUBLISH_BYTES } from "./lib/moshpit-content.mjs"; import { endExpiredLeases, expireOffers } from "./moshpit.mjs"; +import { startRingFooterRefresh, RING_FOOTER_OPTIONS } from "./lib/ring-footer.mjs"; +import { descriptor } from "@profullstack/footer"; const app = express(); app.disable("x-powered-by"); @@ -67,6 +69,9 @@ for (const [route, file] of Object.entries(vendor)) { } app.get("/healthz", (_req, res) => res.json({ ok: true, env: config.env })); +// The Profullstack webring descriptor (the ring footer is on the sign-in page). +app.get("/.well-known/openwebring.json", (_req, res) => + res.json(descriptor({ site: RING_FOOTER_OPTIONS.site, name: "moshcode app" }))); app.use(sessionMiddleware); @@ -150,6 +155,7 @@ async function main() { await sweep(); // Unref'd so it never holds the process open on its own. setInterval(sweep, SWEEP_MS).unref(); + startRingFooterRefresh(); app.listen(config.port, () => console.log(`🤘 app.moshcode.sh on :${config.port} (${config.env}) — ${config.origin}`)); // PID 1 in its container: without a handler `docker stop` waits 10 s for SIGKILL. for (const signal of ["SIGTERM", "SIGINT"]) process.on(signal, () => process.exit(0)); diff --git a/apps/pwa/test/ring-footer.test.mjs b/apps/pwa/test/ring-footer.test.mjs new file mode 100644 index 00000000..432286b1 --- /dev/null +++ b/apps/pwa/test/ring-footer.test.mjs @@ -0,0 +1,24 @@ +// The sign-in page carries the server-rendered Profullstack footer: the webring +// row (rssamplifier's verifier reads HTML, never runs script) and the clearnet +// template, never the pit.world one (app.moshcode.sh is an ICANN host). +import assert from "node:assert/strict"; +import test from "node:test"; + +process.env.PROFULLSTACK_FOOTER_OFFLINE = "1"; +const { ringFooter, setRingFooter, RING_FOOTER_OPTIONS } = await import("../src/lib/ring-footer.mjs"); + +test("ring footer links the Profullstack ring from app.moshcode.sh", () => { + const html = ringFooter(); + assert.match(html, /rssamplifier\.com\/ring\/profullstack/); + assert.match(html, /from=https%3A%2F%2Fapp\.moshcode\.sh%2F/); + assert.match(html, /built\.indie\.pit\.world\/for/); + assert.doesNotMatch(html, /pit\.world network/); + assert.equal(RING_FOOTER_OPTIONS.site, "https://app.moshcode.sh/"); +}); + +test("a refreshed footer that lost the ring is rejected", () => { + const before = ringFooter(); + assert.equal(setRingFooter(""), false); + assert.equal(setRingFooter(42), false); + assert.equal(ringFooter(), before); +});