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
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
2 changes: 1 addition & 1 deletion src/mcp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
39 changes: 39 additions & 0 deletions src/pit-footer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/**
* The pit.world network footer, fetched server side from
* https://pit.world/snippets/footer.html and injected before </body> 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<void> | null = null;

export function refreshPitFooter(): Promise<void> {
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 <body>. */
export async function withPitFooter(body: string): Promise<string> {
await refreshPitFooter();
if (!cached) return body;
const i = body.lastIndexOf('</body>');
return i === -1 ? body : `${body.slice(0, i)}${cached}\n${body.slice(i)}`;
}
7 changes: 4 additions & 3 deletions src/web.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');

Expand All @@ -26,7 +27,7 @@ const TYPES: Record<string, string> = {
};

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;
Expand Down Expand Up @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion web/assets/site.css
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
Expand Down
8 changes: 4 additions & 4 deletions web/docs.html
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@
<nav><a class="link" href="/login">Sign in</a><a class="btn primary" href="/signup">Get started</a></nav>
</div>
</header>
<!-- CrawlProof sponsored line, at the top of the content: a script-free cross-origin frame, so
the page needs no ad.js. text_link is
the one fluid format and carries its own "Sponsored" mark. -->
<aside class="cp-ad wrap" aria-label="Advertisement"><iframe src="https://crawlproof.com/api/ads/frame?slot=a03e3686-cb16-4b4c-bf87-c6526991b219&amp;format=text_link" title="Advertisement" height="40" loading="lazy" scrolling="no" sandbox="allow-popups allow-popups-to-escape-sandbox allow-top-navigation-by-user-activation"></iframe></aside>
<main class="wrap docs">
<section class="block">
<h1>Docs</h1>
Expand Down Expand Up @@ -64,10 +68,6 @@ <h2>Self-hosting</h2>
</section>
</main>

<!-- CrawlProof sponsored line: a script-free cross-origin frame (the page CSP
forbids inline styles, which ad.js's srcdoc creative needs). text_link is
the one fluid format and carries its own "Sponsored" mark. -->
<aside class="cp-ad wrap" aria-label="Advertisement"><iframe src="https://crawlproof.com/api/ads/frame?slot=a03e3686-cb16-4b4c-bf87-c6526991b219&amp;format=text_link" title="Advertisement" height="40" loading="lazy" scrolling="no" sandbox="allow-popups allow-popups-to-escape-sandbox allow-top-navigation-by-user-activation"></iframe></aside>

<footer class="site"><div class="wrap"><span>漏 2026 Profullstack, Inc.</span><span><a href="/">Home</a><a href="https://github.com/profullstack/submitfleet">GitHub</a></span></div></footer>
</body>
Expand Down
8 changes: 4 additions & 4 deletions web/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@
</div>
</header>

<!-- CrawlProof sponsored line, at the top of the content: a script-free cross-origin frame, so
the page needs no ad.js. text_link is
the one fluid format and carries its own "Sponsored" mark. -->
<aside class="cp-ad wrap" aria-label="Advertisement"><iframe src="https://crawlproof.com/api/ads/frame?slot=a03e3686-cb16-4b4c-bf87-c6526991b219&amp;format=text_link" title="Advertisement" height="40" loading="lazy" scrolling="no" sandbox="allow-popups allow-popups-to-escape-sandbox allow-top-navigation-by-user-activation"></iframe></aside>
<main>
<section class="hero wrap">
<span class="eyebrow">{{TOTAL}} launch sites and directories, mapped</span>
Expand Down Expand Up @@ -98,10 +102,6 @@ <h2>What is in the catalog</h2>
</section>
</main>

<!-- CrawlProof sponsored line: a script-free cross-origin frame (the page CSP
forbids inline styles, which ad.js's srcdoc creative needs). text_link is
the one fluid format and carries its own "Sponsored" mark. -->
<aside class="cp-ad wrap" aria-label="Advertisement"><iframe src="https://crawlproof.com/api/ads/frame?slot=a03e3686-cb16-4b4c-bf87-c6526991b219&amp;format=text_link" title="Advertisement" height="40" loading="lazy" scrolling="no" sandbox="allow-popups allow-popups-to-escape-sandbox allow-top-navigation-by-user-activation"></iframe></aside>

<footer class="site">
<div class="wrap">
Expand Down
Loading