diff --git a/apps/pwa/package.json b/apps/pwa/package.json index e2555bbe..ab5eb799 100644 --- a/apps/pwa/package.json +++ b/apps/pwa/package.json @@ -14,7 +14,7 @@ "@logicsrc/cli": "workspace:*", "@profullstack/coinpay-onboard": "0.3.1", "@profullstack/key-rotation": "0.1.0", - "@profullstack/footer": "0.5.0", + "@profullstack/footer": "latest", "@profullstack/libsql-pg": "^0.1.4", "@profullstack/profile": "0.1.6", "@simplewebauthn/browser": "^14.0.0", diff --git a/apps/pwa/src/lib/ring-footer.mjs b/apps/pwa/src/lib/ring-footer.mjs index 265f6469..93bf4e4e 100644 --- a/apps/pwa/src/lib/ring-footer.mjs +++ b/apps/pwa/src/lib/ring-footer.mjs @@ -1,16 +1,19 @@ // The Profullstack footer (links, copyright, the << Profullstack >> webring row and -// the built.indie + pwamart badges) from @profullstack/footer, on the public +// the square 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. // -// This origin holds vault identity keys in browser storage, so the footer is the -// template BUNDLED in the pinned package version only. It used to be refreshed -// from jsdelivr @latest every 15 minutes, which made whoever could publish the -// footer package able to put HTML on this origin, past a bypassable sanitizer. -// A footer change now reaches this app by bumping the pinned dependency. +// This origin holds vault identity keys in browser storage, so the footer uses +// the package's bundled template only: nothing is fetched at runtime. A footer +// release reaches this app through the shared latest dependency and normal deploy. import { footerHtmlSync } from "@profullstack/footer"; -export const RING_FOOTER_OPTIONS = { site: "https://app.logicsrc.com/" }; +const SITE = "https://app.logicsrc.com/"; +export const RING_FOOTER_OPTIONS = { + site: SITE, + builtIndieHref: `https://built.indie.pit.world/for?site=${SITE}`, + pwamartHref: `https://pwamart.com/for?site=${SITE}`, +}; const html = footerHtmlSync(RING_FOOTER_OPTIONS); diff --git a/apps/pwa/test/ring-footer.test.mjs b/apps/pwa/test/ring-footer.test.mjs index 89a56e4f..3691c494 100644 --- a/apps/pwa/test/ring-footer.test.mjs +++ b/apps/pwa/test/ring-footer.test.mjs @@ -1,16 +1,19 @@ -// The sign-in page carries the Profullstack footer (webring row + badges), from -// the template bundled in the pinned package only: nothing is fetched at runtime. +// The sign-in page carries the shared Profullstack footer (webring row + square badges) +// from the package's bundled template only: nothing is fetched at runtime. import assert from "node:assert/strict"; import test from "node:test"; import { readFileSync } from "node:fs"; import * as ringFooterModule from "../src/lib/ring-footer.mjs"; -test("the bundled footer links the ring and both badges for app.logicsrc.com", () => { +test("the shared footer links the ring and both square badges for app.logicsrc.com", () => { const html = ringFooterModule.ringFooter(); assert.match(html, /rssamplifier\.com\/ring\/profullstack/); - assert.match(html, /built\.indie\.pit\.world/); - assert.match(html, /pwamart\.com\/for\.svg\?site=https%3A%2F%2Fapp\.logicsrc\.com%2F|pwamart\.com\/for\.svg\?site=https:\/\/app\.logicsrc\.com\//); + assert.match(html, /href="https:\/\/built\.indie\.pit\.world\/for\?site=https:\/\/app\.logicsrc\.com\//); + assert.match(html, /src="https:\/\/logicsrc\.com\/openicon\/set\/hq\/svg\/built-indie\.svg"/); + assert.match(html, /href="https:\/\/pwamart\.com\/for\?site=https:\/\/app\.logicsrc\.com\//); + assert.match(html, /src="https:\/\/logicsrc\.com\/openicon\/set\/hq\/svg\/pwamart\.svg"/); + assert.match(html, /pfs-footer__badge--publisher img\{width:32px;height:32px/); }); test("the footer cannot be swapped at runtime (no CDN refresh)", () => { @@ -20,7 +23,7 @@ test("the footer cannot be swapped at runtime (no CDN refresh)", () => { assert.doesNotMatch(src, /footerHtml\(/); }); -test("the footer package is pinned to an exact version", () => { +test("the footer package follows the shared latest release", () => { const pkg = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")); - assert.match(pkg.dependencies["@profullstack/footer"], /^\d+\.\d+\.\d+$/); + assert.equal(pkg.dependencies["@profullstack/footer"], "latest"); }); diff --git a/bun.lock b/bun.lock index 2a0fc4a3..0a5e0d38 100644 --- a/bun.lock +++ b/bun.lock @@ -84,7 +84,7 @@ "dependencies": { "@logicsrc/cli": "workspace:*", "@profullstack/coinpay-onboard": "0.3.1", - "@profullstack/footer": "0.5.0", + "@profullstack/footer": "latest", "@profullstack/key-rotation": "0.1.0", "@profullstack/libsql-pg": "^0.1.4", "@profullstack/profile": "0.1.6", @@ -759,7 +759,7 @@ "@profullstack/emailer": ["@profullstack/emailer@1.0.3", "", {}, "sha512-VyAtCcfnSI6LOwJvWogCr3ZNmEsaZw+iuWrqnosLsdcKsbRyCrCmXigMNZJZEjlgYlR+lK//PwNPG+xtCFvWAw=="], - "@profullstack/footer": ["@profullstack/footer@0.5.0", "", { "peerDependencies": { "react": ">=18" }, "optionalPeers": ["react"], "bin": { "profullstack-footer": "bin/profullstack-footer.js" } }, "sha512-TrllS87nK7J0c/UgYckaFIWVhLOtr6m73XXFSHIuSfOg7NfuWUdwjtsfMXGaZiiUnmMYj3zhGcfzW0DicDwl8w=="], + "@profullstack/footer": ["@profullstack/footer@0.6.11", "", { "peerDependencies": { "react": ">=18" }, "optionalPeers": ["react"], "bin": { "profullstack-footer": "bin/profullstack-footer.js" } }, "sha512-9khfQbBM2p5cqMqUg8+K1atF2muYWA0AQaVI6C8gH6Cl+D+7Wc2zeaPmtDMque8HZzA3tdaqoCAAUfsufaL17Q=="], "@profullstack/form-guard": ["@profullstack/form-guard@0.1.1", "", {}, "sha512-RGLGfQhjq40f+JqEhSmsJnS0I5sFWjYdrYx4VYtkUqz8xvioN2yyR+6uFK0mnDAStKrYytlngg3meUsrChm/OQ=="], @@ -1453,7 +1453,7 @@ "@logicsrc/openmcp/@profullstack/footer": ["@profullstack/footer@0.2.6", "", { "peerDependencies": { "react": ">=18" }, "optionalPeers": ["react"], "bin": { "profullstack-footer": "bin/profullstack-footer.js" } }, "sha512-BKwbDszAc7e0mYIBiPjgr/5WS8WyDt8IW718s/zW/8pCWOIB1BGwSNs6fmImRSo5kqIjQKUVbXp22jZQ5GEipA=="], - "@logicsrc/web/@profullstack/footer": ["@profullstack/footer@0.4.0", "", { "peerDependencies": { "react": ">=18" }, "optionalPeers": ["react"], "bin": { "profullstack-footer": "bin/profullstack-footer.js" } }, "sha512-EJZnMUQmWDi9Gzx6BAjogloeaPFFSSCHtHJuC9dsXiOz8M87hfn6Z+Zx9AMYWk8s6xemPrPlkXKGB3PZugkcEA=="], + "@logicsrc/web/@profullstack/footer": ["@profullstack/footer@0.6.11", "", { "peerDependencies": { "react": ">=18" }, "optionalPeers": ["react"], "bin": { "profullstack-footer": "bin/profullstack-footer.js" } }, "sha512-9khfQbBM2p5cqMqUg8+K1atF2muYWA0AQaVI6C8gH6Cl+D+7Wc2zeaPmtDMque8HZzA3tdaqoCAAUfsufaL17Q=="], "@modelcontextprotocol/sdk/content-type": ["content-type@1.0.5", "", {}, "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="],