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

@growth-labs/consent

v0.7.1

Published

Geography-aware consent management. Banner UI, cookie gating, and a utility other packages use to check consent state. Generic — not analytics-specific.

Readme

@growth-labs/consent

Geography-aware consent management. Banner UI, cookie gating, and a utility other packages use to check consent state. Generic — not analytics-specific.

Config

import consent from '@growth-labs/consent'

consent({
  mode: 'geography',                             // 'required' | 'geography' | 'disabled'
  consentCookieName: 'gl_consent',
  autoInject: true, // default: package injects the banner script on every page
  purposes: {
    analytics: { label: 'Analytics', description: 'Helps us understand...', required: false },
    marketing: { label: 'Marketing', description: 'Personalized content...', required: false },
  },
  banner: {
    position: 'bottom',          // 'bottom' | 'top' | 'center'
    showManageLink: true,
    defaults: 'auto',            // 'auto' | 'unchecked' | 'checked'
    privacyPolicyUrl: '/privacy-policy/',
    privacyPolicyLabel: 'Privacy policy',
    showCloseButton: false,      // first-impression banner has no X (see below)
  },
  // requireConsentRegions defaults to EU/EEA + UK + Brazil
})

GDPR-Compliant Defaults

banner.defaults controls the pre-tick behaviour of non-required purpose checkboxes:

  • 'auto' (default): unchecked when the visitor's country is in requireConsentRegions (EU/UK/EEA/BR by default), checked otherwise. Per EDPB Guidelines 05/2020 and the CJEU Planet49 ruling, pre-ticked boxes do not constitute valid consent in the EU/UK.
  • 'unchecked': always unchecked. Strict opt-in everywhere.
  • 'checked': always pre-ticked. Legacy pre-0.5.x behaviour. Only legal in non-GDPR regions.

Required-purpose checkboxes (required: true) always stay checked and disabled — they are operational, not consent-bearing.

Privacy Policy Link

Setting banner.privacyPolicyUrl renders a link below the action buttons. Omit the option to suppress the link entirely. EDPB Guidelines 05/2020 §3.3.1 expects informed consent, which the link disclosure supports.

First-Impression Close Button

banner.showCloseButton defaults to false. Closing the first-impression banner via an "X" dismisses without recording a decision, which both re-triggers the banner on every subsequent pageview AND fails the EDPB affirmative-act test. The close affordance is therefore gated to the manage-preferences sub-view (opened from inside an active consent flow, where closing is safe).

What It Injects

Middleware: Reads the configured decision cookie and populates context.locals.consent with { managed, cookieName, required, granted: { analytics, marketing } }. The deterministic managed/cookieName fields let other packages hydrate the correct browser cookie without serializing the visitor's decision into public HTML.

Static descriptor: Managed HTML also carries deterministic <meta name="growth-labs-consent" content="{cookieName}">. It contains no visitor decision or geography. Analytics uses it only as a fail-closed fallback when a static artifact has no analytics config data node. It is emitted even when autoInject is false and on HTML error/fragment responses.

Banner: Auto-injected by the integration by default. With no decision cookie, it opens whenever the configured helper is true, missing, or malformed; only an explicit helper value of false keeps a first-visit banner closed. It supports Accept all / Decline / Manage toggles and gl:consent_reopen.

The Worker response body is deterministic across country and cookie state: the same hidden controller is present on every successful HTML response, and the browser reads the helper cookie to decide whether it opens. The middleware emits the helper cookie only when its value changes. Any response with Set-Cookie or Vary: Cookie is forced to private, no-store on Cache-Control, CDN-Cache-Control, and Cloudflare-CDN-Cache-Control.

Server-side consumers never trust the readable helper as authorization. An explicit decision wins; otherwise isConsentGranted() recomputes isConsentRequired(request) from trusted Worker request metadata and grants only when that server decision is non-required. The helper remains browser UI state. Sites with no consent integration, or mode: 'disabled', remain unconditional. Disabled mode publishes managed: false and emits no helper cookie, banner, or static descriptor. It also ignores stale decision/helper cookies when building Zaraz consent state, so all configured purposes remain granted.

The integration also injects GET /api/consent/status. It returns only { managed, required } with private/no-store headers. Zaraz and direct browser analytics use this route when there is no explicit decision, so a forged ${cookieName}_required=false cookie cannot activate third-party tracking.

With Astro's native security.csp enabled, the middleware registers the exact SHA-256 hash of its injected banner controller before rendering. Do not add unsafe-inline for consent; Astro combines this hash with its own generated script and style hashes. If WebCrypto cannot produce that hash, rendering fails closed with a contextual error and the cached computation is cleared so a later request can retry; the package never injects an unhashed banner script.

Set autoInject: false only when a site intentionally renders the package component itself. The manual component must receive the same runtime values the injected banner uses:

---
import ConsentBanner from '@growth-labs/consent/components/ConsentBanner.astro'

const purposes = [
  { key: 'analytics', label: 'Analytics', description: 'Helps us understand traffic' },
  { key: 'marketing', label: 'Marketing', description: 'Personalized content' },
]
---

<ConsentBanner
  consentRequired={false}
  cookieName="gl_consent"
  purposes={purposes}
  position="bottom"
  showManageLink={true}
/>

Programmatic Reopen

For footer links or privacy pages that should reopen the in-page banner instead of sending users to a separate route:

document.dispatchEvent(
  new CustomEvent('gl:consent_reopen', {
    detail: { view: 'preferences' }, // or 'main'
  }),
)

The banner reuses any existing consent cookie to prefill the checkboxes before it opens.

How Geography Detection Works

Uses trusted request.cf.country on Cloudflare Workers. Missing or malformed Workers metadata, including Cloudflare's unknown XX code, fails closed. Client-settable CF-IPCountry is ignored. If the country is in requireConsentRegions, consent is required.

Checking Consent in Other Packages

import { isConsentGranted } from '@growth-labs/consent/utils'

if (isConsentGranted(context, 'analytics')) {
  // Set tracking cookies, fire analytics
}

If @growth-labs/consent is NOT installed, isConsentGranted() always returns true (appropriate for US-only sites).

Integration Order

Consent middleware runs AFTER auth, BEFORE analytics. List order in astro.config.mjs: auth → consent → analytics.

Key Patterns

  • Virtual module: virtual:growth-labs/consent/config
  • Runtime state self-seeds from the virtual config in middleware, routes, and utils
  • The configured decision cookie (default gl_consent) stores analytics:true,marketing:false; its ${cookieName}_required helper is presentation state and never grants server writes or direct browser tracking
  • Banner is minimal HTML — consumers style with CSS/Tailwind
  • Client-side control uses gl:* CustomEvent events on document, including gl:consent_reopen
  • The current zaraz.consent API gates GA4 + third-party scripts; unresolved state is denied and queued events are released only after an explicit grant or the private status route confirms consent is not required