diff --git a/package.json b/package.json index b7f7226..eb2d91b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@profullstack/submitfleet", - "version": "0.3.0", + "version": "0.3.1", "description": "SubmitFleet: launch your product on 339 launch sites and directories from one profile. Prepared submissions, accounts with passkeys, a tracked launch queue, CLI, MCP server and API.", "type": "module", "license": "MIT", diff --git a/src/mcp.ts b/src/mcp.ts index 64840cd..4cde7aa 100644 --- a/src/mcp.ts +++ b/src/mcp.ts @@ -4,7 +4,7 @@ import type { SubmitFleet } from './service.js'; import { SubmitFleetError } from './util.js'; import type { Actor } from './workspaces.js'; -export const MCP_SERVER_VERSION = '0.3.0'; +export const MCP_SERVER_VERSION = '0.3.1'; type Json = unknown; diff --git a/src/pit-footer.ts b/src/pit-footer.ts new file mode 100644 index 0000000..9857b64 --- /dev/null +++ b/src/pit-footer.ts @@ -0,0 +1,39 @@ +/** + * The pit.world network footer, fetched server side from + * https://pit.world/snippets/footer.html and injected before on every + * page. Refetched at most hourly with a 3-second timeout; on failure the last + * good copy stays, or nothing renders, so pit.world being down never breaks a page. + */ +const PIT_FOOTER_URL = 'https://pit.world/snippets/footer.html?site=submitfleet.com'; +const TTL_MS = 60 * 60 * 1000; + +let cached = ''; +let fetchedAt = 0; +let inflight: Promise | null = null; + +export function refreshPitFooter(): Promise { + if (Date.now() - fetchedAt < TTL_MS) return Promise.resolve(); + inflight ??= (async () => { + try { + const res = await fetch(PIT_FOOTER_URL, { signal: AbortSignal.timeout(3000) }); + if (res.ok) { + const html = await res.text(); + if (html.includes('data-pit-footer')) cached = html; + } + } catch { + // Keep the last good footer. + } finally { + fetchedAt = Date.now(); + inflight = null; + } + })(); + return inflight; +} + +/** Inserts the footer as the last thing in . */ +export async function withPitFooter(body: string): Promise { + await refreshPitFooter(); + if (!cached) return body; + const i = body.lastIndexOf(''); + return i === -1 ? body : `${body.slice(0, i)}${cached}\n${body.slice(i)}`; +} diff --git a/src/web.ts b/src/web.ts index 4115000..886edbe 100644 --- a/src/web.ts +++ b/src/web.ts @@ -12,6 +12,7 @@ import * as oauth from './oauth.js'; import type { SubmitFleet } from './service.js'; import { SubmitFleetError } from './util.js'; import { authenticate, type Actor } from './workspaces.js'; +import { withPitFooter } from './pit-footer.js'; export const WEB_ROOT = join(dirname(fileURLToPath(import.meta.url)), '..', 'web'); @@ -26,7 +27,7 @@ const TYPES: Record = { }; export const PAGE_CSP = - "default-src 'self'; script-src 'self' https://crawlproof.com; style-src 'self'; img-src 'self' data: https:; connect-src 'self' https://crawlproof.com; font-src 'self'; frame-src https://crawlproof.com; frame-ancestors 'none'; base-uri 'none'; form-action 'self' http://127.0.0.1:* http://localhost:*"; + "default-src 'self'; script-src 'self' https://crawlproof.com; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; connect-src 'self' https://crawlproof.com; font-src 'self'; frame-src https://crawlproof.com; frame-ancestors 'none'; base-uri 'none'; form-action 'self' http://127.0.0.1:* http://localhost:*"; export interface WebOptions { mailer: Mailer; @@ -93,10 +94,10 @@ export function mountWeb(app: Hono<{ Variables: { actor: Actor } }>, fleet: Subm const o = origin(c); return { origin: o, rpID: new URL(o).hostname, rpName: 'SubmitFleet' }; }; - const html = (c: Context, body: string, status = 200) => { + const html = async (c: Context, body: string, status = 200) => { c.header('content-security-policy', PAGE_CSP); c.header('cache-control', 'no-store'); - return c.html(body, status as 200); + return c.html(await withPitFooter(body), status as 200); }; const startSession = async (c: Context, userId: string) => { const s = await auth.createSession(fleet.db, userId, c.req.header('user-agent') ?? null); diff --git a/web/assets/site.css b/web/assets/site.css index 347806d..e2dd0a7 100644 --- a/web/assets/site.css +++ b/web/assets/site.css @@ -163,7 +163,7 @@ section.block .sub { color: var(--fg-2); max-width: 60ch; font-size: 17px; margi .cta-band h2 { margin: 0; color: var(--bg); } .cta-band p { margin: 6px 0 0; color: color-mix(in srgb, var(--bg) 75%, transparent); } .cta-band .btn.primary { background: var(--bg); color: var(--fg); border-color: var(--bg); } -.cp-ad { margin-top: 8px; margin-bottom: 24px; } +.cp-ad { margin-top: 12px; margin-bottom: 12px; } .cp-ad iframe { display: block; width: 100%; height: 40px; border: 0; } footer.site { border-top: 1px solid var(--line); padding: 28px 0 40px; color: var(--muted); font-size: 13.5px; } footer.site .wrap { display: flex; flex-wrap: wrap; gap: 16px; justify-content: space-between; } diff --git a/web/docs.html b/web/docs.html index 9a233ab..5f750a3 100644 --- a/web/docs.html +++ b/web/docs.html @@ -16,6 +16,10 @@ + +

Docs

@@ -64,10 +68,6 @@

Self-hosting

- - diff --git a/web/index.html b/web/index.html index fd8581c..a8ff287 100644 --- a/web/index.html +++ b/web/index.html @@ -25,6 +25,10 @@ + +
{{TOTAL}} launch sites and directories, mapped @@ -98,10 +102,6 @@

What is in the catalog

- -