Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions apps/pwa/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions apps/pwa/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
15 changes: 15 additions & 0 deletions apps/pwa/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

41 changes: 41 additions & 0 deletions apps/pwa/src/lib/ring-footer.mjs
Original file line number Diff line number Diff line change
@@ -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);
}
2 changes: 2 additions & 0 deletions apps/pwa/src/routes/auth.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down Expand Up @@ -45,6 +46,7 @@ function authPage(req, { error = "", mode = "in" } = {}) {
<p id="passkey-msg" class="mono faint" style="font-size:.72rem;text-align:center;margin:12px 0 0"></p>
</div></div>
</main>${footer}
<div class="wrap">${ringFooter()}</div>
<script src="/vendor/simplewebauthn-browser.umd.js"></script>
<script src="/passkey.js"></script>`;
return page({ title: "moshcode ▸ sign in", body });
Expand Down
6 changes: 6 additions & 0 deletions apps/pwa/src/server.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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));
Expand Down
24 changes: 24 additions & 0 deletions apps/pwa/test/ring-footer.test.mjs
Original file line number Diff line number Diff line change
@@ -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("<footer>nothing</footer>"), false);
assert.equal(setRingFooter(42), false);
assert.equal(ringFooter(), before);
});
Loading