backend 0.1.0
This release published @cookieyes/backend@0.1.0, @cookieyes/ui@0.1.0, @cookieyes/vanilla@0.1.0, @cookieyes/react@0.14.0, @cookieyes/nextjs@0.8.5, @cookieyes/core@0.13.0, @cookieyes/cli@0.3.5, @cookieyes/scripts@0.3.1 and @cookieyes/test@0.2.0.
Install
Minor changes
New: @cookieyes/backend, a consent server you run yourself. Self-hosted mode now has somewhere to send its records: install it, point it at your database, mount one handler, and every consent decision is stored once as proof.
createConsentBackend({ basePath, trustedOrigins, database }). Returns a web-standard handler(request) that mounts in Next.js, Hono, Bun, Deno and Cloudflare Workers as it is, and in Express, Fastify and node:http through toNodeHandler() from @cookieyes/backend/node. Point the SDK at it with mode: "self-hosted" and apiUrl. It works with every SDK version from @cookieyes/core 0.9.0, which added the record id it uses to store each decision once.
Your database, your client. postgresStore() takes any client with query(text, params), such as pg, postgres.js or Neon. sqliteStore() takes node:sqlite, better-sqlite3 or bun:sqlite, and d1Store() a Cloudflare D1 binding. The package has no runtime dependencies. Create the table with npx cookieyes-backend migrate --apply, or backend.migrate().
Stored once, kept minimal. A retried record is stored once, even when copies arrive at the same moment, and a different decision under a known id is refused with 409. Each row holds when, what the visitor saw and chose, and where. No IP address is stored unless you turn on ipAddress, and then only truncated or hashed. Unknown fields are dropped, and logs never name a visitor.
Safe in public. Only trustedOrigins may save from a browser. Bodies are validated and limited to 4 KB, and requests are rate limited per visitor and per client address.
Data requests and retention. With apiKeys set, the owner routes look up a visitor's records as JSON or CSV, delete them, and prune records older than retentionMonths (24 by default). /status reports whether the server and its database are ready, and /openapi.json and /docs describe the contract.
In @cookieyes/backend.
Each consent record now says which SDK build sent it and whether the visitor's browser sent Global Privacy Control, so a stored record carries more of the evidence you need to show what the visitor saw.
New: sdkVersion and gpc in the self-hosted payload. sdkVersion is the version of @cookieyes/core that built the record. gpc is true when navigator.globalPrivacyControl was on at the moment of the decision, and false otherwise. Both are on ConsentPayload and are sent with every record. Neither is part of recordId, so a record still waiting in the retry queue from an earlier version keeps its id and is not stored twice.
Measured with pnpm size: @cookieyes/core grows by 38 bytes of gzip.
In @cookieyes/core.
The banner now works under a strict Content Security Policy when it is server-rendered, "Powered by" is translated, and every control in the opt-out dialog and the reload notice can be styled by part.
Fixed: the announcement could show on the page under a strict style-src. The banner's screen-reader announcer was server-rendered with an inline style attribute, which a policy without 'unsafe-inline' blocks, so its text could appear as plain text on the page. It now uses the .cy-sr-only class from the stylesheet, which critical.css includes. The server-rendered banner has no inline style attributes.
Fixed: "Powered by" is translated. The visible words of the branding link were English in every language. They now come from the poweredBy translation, split around the brand name that the logo shows.
New parts. The opt-out dialog's checkbox, checkbox-label, cancel, confirmation and branding, and the reload notice's reload button, now carry data-cy-part. CookieOptOut's classNames and styles accept the same names.
Changed: two opt-out dialogs no longer share a checkbox id. The id was the fixed cy-ccpa-optout; it is now generated per dialog. If your CSS or tests targeted #cy-ccpa-optout, use [data-cy-part="checkbox"] instead.
Removed: data-pos on the recall button. No stylesheet rule or setting read it.
Measured with pnpm size: the React banner and interface layers grow by 19 bytes of gzip and styles.css by 34, for the new part attributes, the translated branding and the .cy-sr-only rule.
In @cookieyes/react, @cookieyes/ui.
The banner's stylesheet, theme tokens and markup now live in one framework-free package, @cookieyes/ui, so every framework draws the same banner.
New: @cookieyes/ui. It holds styles.css and critical.css, the --cy-* theme tokens, the CY_PART and CY_STATE styling hooks, and the markup of the banner, the preferences dialog, the opt-out dialog, the reload notice and the recall button as plain data. renderToString turns that markup into HTML for a server or a static page, and renderToDom turns it into elements in a browser. Text is always escaped or inserted as text, never as HTML. It also holds the behaviour every framework shares: the focus trap, Escape to close, returning focus to whatever opened a dialog, the delayed screen-reader announcement, applying a custom theme, and the rules for when each component shows and how the opt-out dialog counts down. Each behaviour takes the elements it works on and returns a function that undoes it. It is the base for framework adapters; React and Next.js users do not need to install it.
@cookieyes/react is built on it, with nothing to change on your side. @cookieyes/react/styles.css, @cookieyes/react/critical.css, CY_PART, CY_STATE and every component render exactly as before; a test compares React's output with @cookieyes/ui's markup for every state and fails on any difference. The only visible change is the tick in the opt-out confirmation, whose coordinates are now rounded to two decimals like the other icons, a difference smaller than one pixel.
Measured with pnpm size: the React banner and interface layers grow by 29 and 30 bytes of gzip, the cost of the shared behaviour taking the elements it works on as arguments; @cookieyes/core is unchanged.
In @cookieyes/react, @cookieyes/ui.
New: @cookieyes/test/backend, a conformance suite that proves a consent server keeps the contract the SDK's self-hosted mode relies on, whatever language it is written in.
runBackendConformance(target, { describe, it }). Point it at a running server's URL and an origin it trusts. It checks that a record sent ten times, or five times at once, is stored once; that a different decision under a stored id is refused; that bodies are validated and size-limited; that only trusted origins get in; and that /status, /openapi.json and the read route answer. With an owner key it also checks lookup, CSV export, delete and prune, and deletes every record it saved. BACKEND_CONTRACT_VERSION names the contract version it checks.
The fidelity table now covers the two new consent record fields: payload.gpc is false in Node unless navigator is stubbed, and payload.sdkVersion is 0.0.0-dev when core is linked from source.
In @cookieyes/test.
New: @cookieyes/test/conformance, one test suite that proves a banner behaves like every other CookieYes banner, whatever framework drew it.
runConformance(driver, { describe, it }). You describe your adapter in a small driver (mount, unmount, click, press a key, switch language) and the suite checks the banner, the preferences dialog, the opt-out dialog, the reload notice and the recall button: every element, part, state and accessible attribute against @cookieyes/ui's markup, Tab order and the focus trap, Escape, where focus goes, the screen-reader announcement, theming, translations, an automated axe-core check, and the first HTML when the driver provides serverHtml. pressInDocument moves focus on Tab the way a browser does, for drivers that run in jsdom.
@cookieyes/ui and axe-core are new optional peer dependencies, needed only for this entry point. Nothing changes for the existing entry points.
In @cookieyes/test.
New: @cookieyes/vanilla, the CookieYes banner for any web page with no framework, or a framework without an adapter yet.
start(config) puts the banner, the preferences and opt-out dialogs, the reload notice and the recall button on the page, each when its state calls for it. It takes the same configuration as initCookieYes, so a configuration copies across from React unchanged, and returns the consent runtime from @cookieyes/core. openPreferences() and openOptOut() open the dialogs from links of your own, and destroy() removes every element and listener start added. renderShell(config) returns the banner as HTML, for showing it before any script runs, and start takes it over without a flash; given a request's cookieHeader, it returns nothing for a visitor who has already decided.
The same banner as React. It draws @cookieyes/ui's markup, so parts, --cy-* variables, theme settings and translations are identical, and it passes the same conformance suite as @cookieyes/react, in jsdom and in a real browser. Translations are always inserted as text, never as HTML.
This is the open-source SDK, not the CookieYes platform script. It is configured in code, needs no account and never talks to CookieYes servers.
@cookieyes/ui and @cookieyes/vanilla are released together at the same version. ui no longer exports its icon functions, which nothing outside it used, and warnOnStyleCspViolations now returns a function that removes its listener.
Measured with pnpm size against an empty Next.js app: 13.14 KB gzipped for the banner and recall button. The two dialogs, 5.34 KB more, load the first time a visitor opens one.
In @cookieyes/ui, @cookieyes/vanilla.
Patch changes
Apps set up in self-hosted mode by cookieyes init now retry a consent record that their server failed to store.
Fixed: failed records were treated as stored. For self-hosted mode, init wrote a backend.persist adapter that sent the record with fetch but never threw when the server answered with an error, so the SDK took every record as stored and never sent it again. init now writes apiUrl: "<your URL>" instead, and the SDK's own sender checks the response and retries. If you set up self-hosted mode with an earlier version, replace the backend block in your config with apiUrl and the same URL.
In @cookieyes/cli.
The network blocker compares hosts in linear time, so a host or rule domain ending in a long run of dots can no longer slow it down.
Fixed: trailing dots were removed with a regular expression that took quadratic time. Matching still treats example.com. as example.com, exactly as before; only how the dots are removed changed.
Measured with pnpm size: no change to @cookieyes/core's bundles.
In @cookieyes/core.
The conformance suite's accessibility check no longer times out on a busy machine.
Fixed: the axe-core check could fail on time, not on a problem. Auditing the banner and the preferences dialog in jsdom took just over the test runner's 5 second default on loaded CI runners. That check now allows 30 seconds; what it checks is unchanged.
In @cookieyes/test.
Source
Read the full diff and commit history for backend 0.1.0 on GitHub.