npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@thg-altitude/didomi-consent

v0.1.3

Published

Shared Didomi CMP loader + OneTrust → Didomi consent migration for THG Astro storefronts.

Readme

@thg-altitude/didomi-consent

Shared Didomi CMP loader + OneTrust → Didomi consent migration for Astro storefronts. Extracted from a production storefront so every brand consumes one audited implementation instead of copy-pasting it.

For a returning visitor who already made a OneTrust choice, it replays that choice into Didomi so they are not re-prompted; everyone else sees the standard Didomi notice. The replay preserves the user's original OneTrust expiry (it never resets the 12-month clock), honours live GPC, and fails safe to opt-out.


How it works

DidomiCmp.astro emits four things into <head>, in this load-bearing order:

  1. Flash-guard CSS — hides the notice only while an eligible migration is in flight (a class set by the runtime, removed the instant the write resolves or fails). A non-migrating or failed-write visitor always sees the standard banner.
  2. window.didomiConfig — set at page root before the loader.
  3. The eligibility gate + migration runner — reads the legacy OptanonConsent cookie, gates on GPC / staleness / reconsent / cookie-shape / real-interaction, then replays the choice via Didomi's standard API (setUserAgreeToAll, setUserDisagreeToAll, or a per-category setCurrentUserStatus). Once the SDK is ready it applies the optional regulation exclusion before writing anything. It also registers two expiry hooks that run on every load to enforce the original OneTrust expiry.
  4. The Didomi loader (spcloader) — targets the notice by id.

Each script is the storefront-audited runtime, shipped by stringifying a single real function (src/runtime/*.js). That means the exact code running in the browser is the exact code the tests import and drive — one source, no drift. is:inline keeps the scripts verbatim (no Astro processing/hashing), matching the CSP treatment storefronts already grant these Didomi scripts.

Config is passed as props, never read from Astro.locals, so the package is decoupled from any repo's config shape. Each consumer keeps a tiny wrapper.


Install

Published on the public npm registry — no .npmrc, scope routing or auth token needed in the consuming repo:

npm install @thg-altitude/didomi-consent

Or as a git dependency (no registry step):

npm install github:THG-Commerce/didomi-consent#v0.1.3

The package ships raw .astro/.ts/.js source (no build step). Astro's Vite pipeline compiles the .astro component and transpiles the helpers in the consumer's build — the same way @thg-altitude/* component packages work.


Fastest path: the auto-setup skill

This repo carries a Claude coding-agent skill, didomi-setup, that does the wiring below for you — detecting this repo's head layout, footer, and tenant-config shape (they differ per storefront), then self-verifying and handing off the compliance steps it must not do itself.

The skill is deliberately NOT part of the published package. It is written against internal deployment conventions and is of no use outside them, so it is excluded from files in package.json: npm install does not deliver it and there is no npx command. Install it from a clone of this repo:

git clone https://github.com/THG-Commerce/didomi-consent.git
node didomi-consent/bin/cli.mjs install-skill --dir ../my-storefront
# or, with no --dir, into the current repo

Then ask your agent to "set up Didomi on this storefront" (or run /didomi-setup). Re-running the command re-syncs the skill to that checkout. The skill source lives at skills/didomi-setup/SKILL.md and is the single source of truth for the procedure (it includes the self-verification and browser-test recipes). The manual steps below are what it automates.

If you change the skill, keep it out of files. The npm pack check under Testing fails the build if skills/ or bin/ reappears in the tarball.

Wiring a storefront (3 steps)

1. Wrapper. Add src/components/Consent/DidomiCmp.astro from examples/consumer-wrapper.astro. It reads the repo's tracking.didomi and forwards it as props. This keeps the existing Layout.astro mount unchanged:

import DidomiCmp from '@components/Consent/DidomiCmp.astro';
{tenantConfig?.features?.hasDidomi && <DidomiCmp />}

Mount it in <head> before any tracker / GTM / analytics so consent gates them.

2. Footer "Cookie settings" link. Wire the click handler to the shared helper so no repo re-implements it:

import { openCookieSettings } from '@thg-altitude/didomi-consent/cookie-settings';

document.querySelectorAll('.cookie-settings').forEach((el) =>
	el.addEventListener('click', (e) => {
		e.preventDefault();
		openCookieSettings(); // Didomi.preferences.show(), OneTrust fallback
	}),
);

Show the link when features.hasDidomi (or the legacy OneTrust flag) is on.

3. Tenant config + feature flag. Add to the tenant config and gate on features.hasDidomi:

{
	"features": { "hasDidomi": true },
	"tracking": {
		"didomi": {
			"apiKey": "<your Didomi public API key>", // from the Didomi console
			"noticeId": "<brand notice id>",
			"reconsentMonths": 12,
			"migrateFromOneTrust": true,
			"debug": false
		}
	}
}

hasDidomi should be mutually exclusive with any hasOneTrust / hasNativeCookieBanner flag. Type the config from the package if useful: import type { DidomiConsentConfig } from '@thg-altitude/didomi-consent/types'.


Config reference

| Prop | Required | Default | Meaning | |------|----------|---------|---------| | apiKey | ✓ | — | Org-wide public Didomi API key (safe to commit; it's in the loader URL). | | noticeId | ✓ | — | Brand/locale notice id (target_type=notice). | | reconsentMonths | | 12 | Reconsent window; the migrated consent keeps the user's original expiry. | | migrateFromOneTrust | | true | Replay legacy OneTrust choices. When false, only the expiry-enforcement of already-migrated users runs — safe to switch off once OneTrust is decommissioned. | | reconsentRegulations | | — | Opt-in. Didomi regulation ids whose visitors must not have a legacy choice replayed — see Excluding a regulation. | | debug | | false | Emit [didomi-migration] console diagnostics. Set false for go-live. | | css | | — | Tenant CSS appended after the base notice styling (the flash-guard rules always ship). |


Excluding a regulation from migration

Some jurisdictions are not worth carrying a replayed consent into. reconsentRegulations withholds the migration for those visitors only — they see the standard Didomi notice and give fresh consent, while everyone else still migrates:

{ "reconsentRegulations": ["cpra", "ccpa"] }   // US visitors under a state privacy law

The signal is Didomi.getCurrentUserStatus().regulation — Didomi's own IP-derived answer, read once the SDK is ready. Matching is case-insensitive, so the lower-case ids from the Didomi console work as-is.

The list must mirror the regulations enabled on that brand's notice. Didomi supports ~20 US state laws (cpra, cpa, ctdpa, vcdpa, ucpa, tdpsa, ocpa, njdpa, …), but a visitor only reports one if it is configured on the notice they were served — an unconfigured geography reports NONE, not its state law. Check the notice in the console rather than copying a list. Conversely, enabling a new regulation in the console does not update this list: those visitors will migrate until the tenant config is changed too.

Two properties worth knowing:

  • It can only ever subtract. Only an explicit match withholds the migration. An unlisted regulation, or one the SDK cannot report, migrates exactly as it did before the option existed — so switching this on can never mass-re-prompt a population that migrates today. The trade is the other direction: if getCurrentUserStatus() stops reporting a regulation, an excluded visitor is migrated rather than re-prompted.
  • The OneTrust cookies are cleaned up, not orphaned. A withheld visitor keeps them until they answer the notice — until then they are the only consent record on the device — and they are deleted the moment a real choice is made. If Didomi already holds a complete status (no notice is coming, so no choice event will ever fire) they are deleted straight away instead. Waiting requires positive confirmation that a notice is coming: if the SDK cannot answer — an older build without shouldUserStatusBeCollected, or a call that throws — the cookies are deleted immediately rather than left waiting for an event that may never arrive.

Note the regime differs from GDPR: under CPRA/CCPA a US notice may be an opt-out link rather than a blocking banner, and a NONE visitor may see no notice at all. Confirm what the notice actually does in the console before relying on "they'll be re-prompted".


Compliance invariants (locked by tests)

  • Only a real choice is replayed — requires OptanonAlertBoxClosed or interactionCount > 0; the pre-answer default cookie is ignored.
  • Original expiry preserved — anchored on OptanonAlertBoxClosed (OneTrust rolls OptanonConsent.datestamp forward on every load), never re-extended. OneTrust cookies are deleted only after the expiry-anchor provenance is persisted.
  • Live GPC winsnavigator.globalPrivacyControl === true blocks replay even if the stored cookie predates the signal.
  • Fail-safe to opt-out — a truncated cookie takes the partial path (never agree-to-all); any purpose/vendor that can't be mapped deterministically is denied.
  • Excluded regulations are never replayed, and never orphan a cookie — a visitor withheld by reconsentRegulations gets no write of any kind (no reset() either, so an existing Didomi choice is untouched), and their OneTrust cookies are deleted once fresh consent exists — or immediately, if no notice is coming or the SDK cannot confirm one is.

Testing

Pure Node, no browser, no deps beyond the toolchain:

npm run test       # node --test — drives the real shipped runtime against cookie fixtures
npm run typecheck  # tsc --noEmit over the type surface
npm run check      # both

The suite (test/migration.test.mjs) installs a browser-global mock, imports the same migrationRuntime that DidomiCmp.astro ships, seeds OptanonConsent / OptanonAlertBoxClosed fixtures, runs it, and asserts the exact Didomi API calls and cookie side-effects for accept / reject / partial / GPC / stale / no-interaction / expiry-reset / persist-failure / malformed / truncated scenarios, plus the reconsentRegulations cases (excluded / unlisted / unreported / opted-out, and both cookie-cleanup paths).

test/packaging.test.mjs additionally asserts against the real npm pack output that skills/ and bin/ never reach the tarball — they are written against internal deployment conventions and are not published. Adding either back to files fails npm run check.


Releasing

  1. Bump version in package.json.
  2. Create a GitHub Release tagged vX.Y.Z (matching the version).
  3. publish.yml runs npm run check then npm publish to the public npm registry using the NPM_TOKEN repo secret — an npm automation token for an account with publish rights on the @thg-altitude scope.

To publish by hand instead, from a clean checkout with NPM_TOKEN exported:

npm run check && npm publish

publishConfig in package.json pins the registry and access: public, so no --registry / --access flag is needed.


Renaming the package

The name/scope appear in exactly these places — change them together, and keep the scope one the publishing npm account actually owns:

  • package.jsonname, repository.url, publishConfig
  • consuming repos → every import / dependency entry
  • skills/didomi-setup/SKILL.md → the install step and its examples
  • this README's examples

Nothing in src/runtime/ or test/ references the package name.