diff --git a/AGENTS.md b/AGENTS.md index 7cb2d1b..510f0dd 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -84,8 +84,13 @@ npm run deploy # cf deploy --prebuilt; CI does this on every push to main ``` The site is the files in `dist/`, served as static assets, which cost -nothing. Its Worker's code (`src/worker.ts`) runs for `/docs/mcp` only -(`runWorkerFirst` in `cloudflare.config.ts`); keep every other address static. +nothing. Its Worker's code (`src/worker.ts`) runs for `/docs/mcp` and for +`/api/version` and `/api/usage`, the release check and its daily count +(`src/usage.ts`, one SQLite Durable Object; `runWorkerFirst` in +`cloudflare.config.ts`); keep every other address static. The count keeps a +day, a version and a platform, nothing else; `/privacy` says so, and must +change with it. The totals are private: `/api/usage` answers only a request +carrying the Worker's `USAGE_TOKEN` secret, and 404 while it is not set. Never add an adapter (`cf init` on this project adds the Astro Cloudflare adapter; don't run it). `www.firenook.dev` redirects to `firenook.dev` by a zone redirect rule; the Worker has no `workers.dev` address. diff --git a/cloudflare.config.ts b/cloudflare.config.ts index ef74ffd..62b9e73 100644 --- a/cloudflare.config.ts +++ b/cloudflare.config.ts @@ -1,12 +1,13 @@ // firenook.dev on Cloudflare: the files in dist/ (wrangler.config.ts), served -// without running code, and one Worker for one address: /docs/mcp, the docs' -// MCP server (src/worker.ts). Only requests under /docs/mcp reach it -// (runWorkerFirst); every other request is a static asset. /docs/install is +// without running code, and one Worker for three addresses: /docs/mcp, the +// docs' MCP server, and /api/version and /api/usage, the update check and its +// count, kept in one SQLite Durable Object (src/worker.ts, src/usage.ts). +// Only requests to those reach it (runWorkerFirst); every other request is a static asset. /docs/install is // served from docs/install.html, /docs/install.html redirects to // /docs/install, and an address with no file gets 404.html with a 404 // status. public/_headers sets the headers. www.firenook.dev is sent to // firenook.dev by a redirect rule. -import { bindings, defineConfig } from 'cf/config'; +import { bindings, defineConfig, exports } from 'cf/config'; export default defineConfig({ worker: { @@ -16,9 +17,13 @@ export default defineConfig({ assets: { htmlHandling: 'auto-trailing-slash', notFoundHandling: '404-page', - runWorkerFirst: ['/docs/mcp', '/docs/mcp/*'], + runWorkerFirst: ['/docs/mcp', '/docs/mcp/*', '/api/version', '/api/usage'], + }, + exports: { UsageCounter: exports.durableObject({ storage: 'sqlite' }) }, + env: { + ASSETS: bindings.assets(), + USAGE: bindings.durableObject({ worker: 'firenook-dev', exportName: 'UsageCounter' }), }, - env: { ASSETS: bindings.assets() }, domains: ['firenook.dev', 'www.firenook.dev'], // The site has one address: no copy on workers.dev. workersDev: false, diff --git a/scripts/check-site.mjs b/scripts/check-site.mjs index 1dd900e..6114faf 100644 --- a/scripts/check-site.mjs +++ b/scripts/check-site.mjs @@ -23,9 +23,13 @@ function walk(dir) { const files = walk(dist); const pages = files.filter((file) => file.endsWith('.html')); +/** Addresses the Worker answers (src/worker.ts), which have no file. */ +const answered = new Set(['/api/usage', '/api/version']); + /** The file a site path is served from, as the host serves it. */ function target(path) { const clean = decodeURIComponent(path.replace(/[?#].*$/, '')); + if (answered.has(clean)) return clean; const candidates = clean === '/' ? ['index.html'] : [clean.slice(1), `${clean.slice(1)}.html`, `${clean.slice(1)}/index.html`]; return candidates.map((candidate) => join(dist, candidate)).find((file) => existsSync(file) && statSync(file).isFile()); } diff --git a/src/content/docs/docs/get-started/install.mdx b/src/content/docs/docs/get-started/install.mdx index 7fdaafb..ab5ae0c 100644 --- a/src/content/docs/docs/get-started/install.mdx +++ b/src/content/docs/docs/get-started/install.mdx @@ -128,8 +128,10 @@ Nothing else is downloaded, at install or at start: no Java, no firebase-tools, no emulator archives. The one exception is an Extension from the registry, which is fetched once, on first use; `firenook ext:vendor` copies it into the project so that even that works offline. In a terminal, -Firenook also asks the npm registry once a day whether a newer release is out. -It never asks in CI, and `FIRENOOK_NO_UPDATE_CHECK=1` turns the check off. +Firenook also asks once a day whether a newer release is out (firenook.dev +from 0.2.5, which counts the day's checks; see [privacy](/privacy)). It never +asks in CI, and `FIRENOOK_NO_UPDATE_CHECK=1` or `DO_NOT_TRACK=1` turns the +check off. ## Keep its working data out of Git diff --git a/src/content/docs/docs/help/faq.md b/src/content/docs/docs/help/faq.md index 0be4514..2cc1e80 100644 --- a/src/content/docs/docs/help/faq.md +++ b/src/content/docs/docs/help/faq.md @@ -168,12 +168,17 @@ the official emulator imports. ### Does Firenook send telemetry? -No. Firenook sends no usage data, and the console loads nothing from the -internet. Three things can use the network: the release check, which asks -the npm registry for Firenook's version tags at most once a day in a -terminal; an Extension from the registry, on its first start; and your own -functions' code. Set `FIRENOOK_NO_UPDATE_CHECK=1` to turn the release check -off. See [without a Google login](/docs/help/no-login). +No telemetry, crash reports or anything about your project, and the console +loads nothing from the internet. One thing is counted: from 0.2.5, the +release check, which asks firenook.dev for Firenook's version tags at most +once a day in a terminal, names your Firenook version and platform, and +firenook.dev adds one to that day's count. No address, id or project is +kept, and only the daily totals are seen. Three things can +use the network: that check, an Extension from the registry on its first +start, and your own functions' code. Set `FIRENOOK_NO_UPDATE_CHECK=1` or +`DO_NOT_TRACK=1` to turn the release check off. See +[without a Google login](/docs/help/no-login) and the +[privacy page](/privacy). ### What does --no-diagnostics turn off? diff --git a/src/content/docs/docs/help/no-login.md b/src/content/docs/docs/help/no-login.md index 9558c07..7180eaf 100644 --- a/src/content/docs/docs/help/no-login.md +++ b/src/content/docs/docs/help/no-login.md @@ -64,12 +64,16 @@ process, so the request never leaves your machine. The emulators, the console and the terminal work offline. The console loads nothing from the internet and sends nothing anywhere. Firenook sends no -telemetry. Three things can still use the network: - -- **The release check.** In a terminal, the npm package asks the npm registry - for `firenook`'s version tags at most once a day, in the background, and - shows a newer release on the next start. It is off in CI, in the plain - output, and with `FIRENOOK_NO_UPDATE_CHECK=1` or `NO_UPDATE_NOTIFIER=1`. +telemetry and nothing about your project. Three things can still use the +network: + +- **The release check.** In a terminal, the npm package asks for + `firenook`'s version tags at most once a day, in the background, and shows + a newer release on the next start. From 0.2.5 it asks firenook.dev, which + counts the checks of each day by Firenook version and platform and keeps + nothing else, and the npm registry when firenook.dev does not answer. It is + off in CI, in the plain output, and with `FIRENOOK_NO_UPDATE_CHECK=1`, + `NO_UPDATE_NOTIFIER=1` or `DO_NOT_TRACK=1`. - **An Extension from the registry**, on its first start only (see below). - **Your own code.** Firenook is not a network sandbox. A function that calls a payment provider, an email service or any other API reaches it, as diff --git a/src/content/docs/docs/help/versions.md b/src/content/docs/docs/help/versions.md index bb1bb7b..d63ac59 100644 --- a/src/content/docs/docs/help/versions.md +++ b/src/content/docs/docs/help/versions.md @@ -38,11 +38,11 @@ npm records it as `^0.2.4`: an update can take a later `0.2` release, never `0.3` or a preview. To install one release in particular, name it, as in `npm install --save-dev firenook@0.2.4`. -Firenook never upgrades itself. In a terminal, it asks the npm registry for -the version tags at most once a day, in the background, and the next start +Firenook never upgrades itself. In a terminal, it asks for the version tags +(from firenook.dev from 0.2.5, the npm registry before) at most once a day, in the background, and the next start shows a newer release on your channel with the install command for your package manager. The check is off in CI, in the plain output, and with -`FIRENOOK_NO_UPDATE_CHECK=1` or `NO_UPDATE_NOTIFIER=1`. +`FIRENOOK_NO_UPDATE_CHECK=1`, `NO_UPDATE_NOTIFIER=1` or `DO_NOT_TRACK=1`. To upgrade, stop the emulators first, then install the new release with `npm install --save-dev firenook@latest`. Never replace the package while diff --git a/src/content/docs/docs/reference/configuration.md b/src/content/docs/docs/reference/configuration.md index 5dfc3de..452b2a5 100644 --- a/src/content/docs/docs/reference/configuration.md +++ b/src/content/docs/docs/reference/configuration.md @@ -193,10 +193,11 @@ in `engines.node`, such as `>=22`, is read as npm reads it. | `WT_SESSION` | Set by Windows Terminal; without it, the Windows console gets plain characters | | `FIRENOOK_NO_UPDATE_CHECK=1` | No release check | | `NO_UPDATE_NOTIFIER=1` | No release check | +| `DO_NOT_TRACK=1` | No release check (from 0.2.5) | | `XDG_CACHE_HOME`, `LOCALAPPDATA` | Where the release check keeps its last answer, in `firenook/update-check.json`. Defaults: `~/Library/Caches` on macOS, `%LOCALAPPDATA%` on Windows, `~/.cache` elsewhere | -`FIRENOOK_PLAIN`, `FIRENOOK_ASCII`, `CI`, `FIRENOOK_NO_UPDATE_CHECK` and -`NO_UPDATE_NOTIFIER` count as set unless they are empty, `0` or `false`, so +`FIRENOOK_PLAIN`, `FIRENOOK_ASCII`, `CI`, `FIRENOOK_NO_UPDATE_CHECK`, +`NO_UPDATE_NOTIFIER` and `DO_NOT_TRACK` count as set unless they are empty, `0` or `false`, so `CI=false` is not CI. ### Extensions diff --git a/src/content/docs/docs/tools/terminal.md b/src/content/docs/docs/tools/terminal.md index 232c4bc..8b1b4f4 100644 --- a/src/content/docs/docs/tools/terminal.md +++ b/src/content/docs/docs/tools/terminal.md @@ -420,14 +420,16 @@ folder that already has a `firebase.json`, the card shows what ## Release check -Once a day at most, in the background, Firenook asks the npm registry for -its latest version tags. The answer shows on the next start, as the Update +Once a day at most, in the background, Firenook asks for its latest version +tags: from 0.2.5 it asks firenook.dev, which answers npm's tags and counts +the day's checks by version and platform (see [privacy](/privacy)), and the +npm registry when firenook.dev does not answer. The answer shows on the next start, as the Update row of the ready summary, so the check never slows a start down. It follows the channel of the version you have: `latest` for a release such as 0.2.4, and `next` for a preview, which also hears of a later release on `latest`. It gives the install command for your package manager, from your lockfile. It never runs in plain output, in CI, or with -`FIRENOOK_NO_UPDATE_CHECK=1` or `NO_UPDATE_NOTIFIER=1`. As everywhere, a +`FIRENOOK_NO_UPDATE_CHECK=1`, `NO_UPDATE_NOTIFIER=1` or `DO_NOT_TRACK=1`. As everywhere, a variable set to nothing, `0` or `false` is not set: `CI=false` is not CI. ## Next diff --git a/src/lib/pages.ts b/src/lib/pages.ts index 5a94bb9..9971c3b 100644 --- a/src/lib/pages.ts +++ b/src/lib/pages.ts @@ -83,8 +83,8 @@ export const pages = { }, '/privacy': { title: 'Privacy · Firenook', - description: 'What firenook.dev and Firenook collect: the site counts visits without cookies, and Firenook sends no telemetry.', - card: { title: 'Privacy', subtitle: 'The site counts visits without cookies. Firenook sends no telemetry.' }, + description: 'What firenook.dev and Firenook collect: the site counts visits without cookies, and Firenook’s daily release check is counted by version and platform only.', + card: { title: 'Privacy', subtitle: 'The site counts visits without cookies. Firenook counts its daily release checks, nothing else.' }, crumb: 'Privacy', }, '/security': { diff --git a/src/pages/privacy.astro b/src/pages/privacy.astro index 74e4936..b78a3c5 100644 --- a/src/pages/privacy.astro +++ b/src/pages/privacy.astro @@ -3,10 +3,10 @@ import PolicyPage from '../components/PolicyPage.astro'; import { site } from '../lib/site'; --- - + What firenook.dev and the Firenook package collect about you. In short: the site counts visits without cookies, and - Firenook sends no telemetry. + Firenook counts its daily release check by version and platform, and sends nothing else.

This website

@@ -32,16 +32,19 @@ import { site } from '../lib/site';

Firenook

- Firenook runs on your machine. It sends no telemetry, usage data or crash reports, and keeps your data, logs and - diagnostics on your machine, mostly in your project’s .firenook folder. + Firenook runs on your machine. It sends no telemetry, crash reports or anything about your project, and keeps your + data, logs and diagnostics on your machine, mostly in your project’s .firenook folder.

It reaches the network in two cases: