consentlayer 0.0.0 → 1.0.0-rc.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +87 -1
- package/dist/adapters/google-consent-mode.d.ts +20 -0
- package/dist/adapters/google-consent-mode.js +18 -0
- package/dist/adapters/google-consent-mode.js.map +1 -0
- package/dist/adapters/shopify.d.ts +49 -0
- package/dist/adapters/shopify.js +85 -0
- package/dist/adapters/shopify.js.map +1 -0
- package/dist/adapters/tcf.d.ts +20 -0
- package/dist/adapters/tcf.js +64 -0
- package/dist/adapters/tcf.js.map +1 -0
- package/dist/analytics-provider-07mxUgIK.d.ts +36 -0
- package/dist/angular.d.ts +56 -0
- package/dist/angular.js +120 -0
- package/dist/angular.js.map +1 -0
- package/dist/auto-tracking.d.ts +11 -0
- package/dist/auto-tracking.js +72 -0
- package/dist/auto-tracking.js.map +1 -0
- package/dist/chunk-7XLGT6U3.js +34 -0
- package/dist/chunk-7XLGT6U3.js.map +1 -0
- package/dist/chunk-BWPWDN4G.js +61 -0
- package/dist/chunk-BWPWDN4G.js.map +1 -0
- package/dist/chunk-DK45XPEY.js +77 -0
- package/dist/chunk-DK45XPEY.js.map +1 -0
- package/dist/chunk-ETU64ZV3.js +98 -0
- package/dist/chunk-ETU64ZV3.js.map +1 -0
- package/dist/chunk-FDJMIJFV.js +367 -0
- package/dist/chunk-FDJMIJFV.js.map +1 -0
- package/dist/chunk-HJGBVDPM.js +47 -0
- package/dist/chunk-HJGBVDPM.js.map +1 -0
- package/dist/chunk-J3KUPF4R.js +69 -0
- package/dist/chunk-J3KUPF4R.js.map +1 -0
- package/dist/chunk-LBEIEAJX.js +55 -0
- package/dist/chunk-LBEIEAJX.js.map +1 -0
- package/dist/chunk-LEAQJXJU.js +582 -0
- package/dist/chunk-LEAQJXJU.js.map +1 -0
- package/dist/chunk-R6I7OYQB.js +69 -0
- package/dist/chunk-R6I7OYQB.js.map +1 -0
- package/dist/chunk-RK2M57GW.js +54 -0
- package/dist/chunk-RK2M57GW.js.map +1 -0
- package/dist/chunk-RZ4TYRNY.js +128 -0
- package/dist/chunk-RZ4TYRNY.js.map +1 -0
- package/dist/chunk-TVSZJLRC.js +112 -0
- package/dist/chunk-TVSZJLRC.js.map +1 -0
- package/dist/chunk-TYOQYV45.js +79 -0
- package/dist/chunk-TYOQYV45.js.map +1 -0
- package/dist/chunk-UAIZU5WD.js +52 -0
- package/dist/chunk-UAIZU5WD.js.map +1 -0
- package/dist/chunk-VIIGBALD.js +14 -0
- package/dist/chunk-VIIGBALD.js.map +1 -0
- package/dist/consent-controller-DyHPNfyT.d.ts +47 -0
- package/dist/consentlayer-banner.global.js +20 -0
- package/dist/consentlayer.global.js +9 -0
- package/dist/create-analytics-Bzvm1UpE.d.ts +44 -0
- package/dist/ecommerce.d.ts +41 -0
- package/dist/ecommerce.js +43 -0
- package/dist/ecommerce.js.map +1 -0
- package/dist/index.d.ts +58 -0
- package/dist/index.js +42 -0
- package/dist/index.js.map +1 -0
- package/dist/next.d.ts +16 -0
- package/dist/next.js +27 -0
- package/dist/next.js.map +1 -0
- package/dist/nuxt/config.d.ts +7 -0
- package/dist/nuxt/config.js +6 -0
- package/dist/nuxt/config.js.map +1 -0
- package/dist/nuxt/index.d.ts +11 -0
- package/dist/nuxt/index.js +35 -0
- package/dist/nuxt/index.js.map +1 -0
- package/dist/nuxt/plugin.d.ts +5 -0
- package/dist/nuxt/plugin.js +24 -0
- package/dist/nuxt/plugin.js.map +1 -0
- package/dist/providers/contentsquare.d.ts +27 -0
- package/dist/providers/contentsquare.js +58 -0
- package/dist/providers/contentsquare.js.map +1 -0
- package/dist/providers/ga4.d.ts +46 -0
- package/dist/providers/ga4.js +3 -0
- package/dist/providers/ga4.js.map +1 -0
- package/dist/providers/gtm.d.ts +28 -0
- package/dist/providers/gtm.js +83 -0
- package/dist/providers/gtm.js.map +1 -0
- package/dist/providers/hotjar.d.ts +38 -0
- package/dist/providers/hotjar.js +68 -0
- package/dist/providers/hotjar.js.map +1 -0
- package/dist/providers/meta.d.ts +42 -0
- package/dist/providers/meta.js +65 -0
- package/dist/providers/meta.js.map +1 -0
- package/dist/providers/posthog.d.ts +42 -0
- package/dist/providers/posthog.js +91 -0
- package/dist/providers/posthog.js.map +1 -0
- package/dist/providers/sentry.d.ts +38 -0
- package/dist/providers/sentry.js +82 -0
- package/dist/providers/sentry.js.map +1 -0
- package/dist/providers/tiktok.d.ts +46 -0
- package/dist/providers/tiktok.js +72 -0
- package/dist/providers/tiktok.js.map +1 -0
- package/dist/providers/vercel.d.ts +14 -0
- package/dist/providers/vercel.js +17 -0
- package/dist/providers/vercel.js.map +1 -0
- package/dist/react/index.d.ts +78 -0
- package/dist/react/index.js +79 -0
- package/dist/react/index.js.map +1 -0
- package/dist/sentry/next.d.ts +1 -0
- package/dist/sentry/next.js +3 -0
- package/dist/sentry/next.js.map +1 -0
- package/dist/sentry/server.d.ts +24 -0
- package/dist/sentry/server.js +28 -0
- package/dist/sentry/server.js.map +1 -0
- package/dist/sentry-replay-HSFVEFCM.js +3 -0
- package/dist/sentry-replay-HSFVEFCM.js.map +1 -0
- package/dist/sentry-sdk-YQOGKGWD.js +3 -0
- package/dist/sentry-sdk-YQOGKGWD.js.map +1 -0
- package/dist/sentry-tracing-KWT7ITHF.js +3 -0
- package/dist/sentry-tracing-KWT7ITHF.js.map +1 -0
- package/dist/server/ga4.d.ts +15 -0
- package/dist/server/ga4.js +29 -0
- package/dist/server/ga4.js.map +1 -0
- package/dist/server/index.d.ts +116 -0
- package/dist/server/index.js +206 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/meta.d.ts +14 -0
- package/dist/server/meta.js +63 -0
- package/dist/server/meta.js.map +1 -0
- package/dist/server/sgtm.d.ts +18 -0
- package/dist/server/sgtm.js +34 -0
- package/dist/server/sgtm.js.map +1 -0
- package/dist/server/tiktok.d.ts +15 -0
- package/dist/server/tiktok.js +64 -0
- package/dist/server/tiktok.js.map +1 -0
- package/dist/solid.d.ts +59 -0
- package/dist/solid.js +3 -0
- package/dist/solid.js.map +1 -0
- package/dist/solidstart.d.ts +16 -0
- package/dist/solidstart.js +22 -0
- package/dist/solidstart.js.map +1 -0
- package/dist/svelte/GtmNoscript.svelte +18 -0
- package/dist/svelte/MetaPixelNoscript.svelte +18 -0
- package/dist/svelte.d.ts +55 -0
- package/dist/svelte.js +3 -0
- package/dist/svelte.js.map +1 -0
- package/dist/sveltekit.d.ts +18 -0
- package/dist/sveltekit.js +16 -0
- package/dist/sveltekit.js.map +1 -0
- package/dist/testing.d.ts +11 -0
- package/dist/testing.js +50 -0
- package/dist/testing.js.map +1 -0
- package/dist/types-BV_AFZKQ.d.ts +407 -0
- package/dist/vue.d.ts +92 -0
- package/dist/vue.js +97 -0
- package/dist/vue.js.map +1 -0
- package/package.json +278 -3
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/react/error-boundary.tsx","../../src/react/gtm-noscript.ts","../../src/react/meta-pixel-noscript.ts"],"names":["createElement"],"mappings":"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","file":"index.js","sourcesContent":["'use client'\n\nimport { Component, useEffect, type ContextType, type ReactNode } from 'react'\nimport type { Analytics, ErrorContext } from '../core/types'\nimport { latestAnalytics } from '../core/create-analytics'\nimport { AnalyticsContext } from './analytics-provider'\n\ntype FallbackProps = { error: unknown; reset: () => void }\n\n/** Props for `<ErrorBoundary>`. */\nexport type ErrorBoundaryProps = {\n children: ReactNode\n fallback: ReactNode | ((props: FallbackProps) => ReactNode)\n /** Defaults to the instance from `<AnalyticsProvider>`, else the one `createAnalytics()` made. */\n analytics?: Analytics\n tags?: ErrorContext['tags']\n}\n\n/** Renders `fallback` when a child throws, and sends the error through `captureError`. */\nexport class ErrorBoundary extends Component<ErrorBoundaryProps, { error: unknown; failed: boolean }> {\n static contextType = AnalyticsContext\n declare context: ContextType<typeof AnalyticsContext>\n state = { error: undefined as unknown, failed: false }\n\n static getDerivedStateFromError(error: unknown) {\n return { error, failed: true }\n }\n\n componentDidCatch(error: unknown) {\n const analytics = this.props.analytics ?? this.context ?? latestAnalytics()\n void analytics?.captureError(error, { source: 'boundary', tags: this.props.tags })\n }\n\n reset = () => this.setState({ error: undefined, failed: false })\n\n render() {\n if (!this.state.failed) return this.props.children\n const { fallback } = this.props\n return typeof fallback === 'function' ? fallback({ error: this.state.error, reset: this.reset }) : fallback\n }\n}\n\n/**\n * The component for Next.js `app/global-error.tsx`, which replaces the root\n * layout, so `<AnalyticsProvider>` isn't there: pass the instance.\n *\n * ```tsx\n * 'use client'\n * export default createGlobalError(analytics)\n * ```\n */\nexport function createGlobalError(\n analytics: Analytics,\n render: (props: FallbackProps) => ReactNode = ({ reset }) => (\n <main style={{ fontFamily: 'system-ui', padding: '2rem' }}>\n <h1>Something went wrong.</h1>\n <button type=\"button\" onClick={reset}>\n Try again\n </button>\n </main>\n ),\n) {\n return function GlobalError({ error, reset }: { error: Error & { digest?: string }; reset: () => void }) {\n useEffect(() => {\n void (async () => {\n await analytics.consent.bootstrap()\n await analytics.boot()\n await analytics.captureError(error, {\n source: 'boundary',\n level: 'fatal',\n tags: error.digest ? { digest: error.digest } : undefined,\n })\n })()\n }, [error])\n return (\n <html lang=\"en\">\n <body>{render({ error, reset })}</body>\n </html>\n )\n }\n}\n","import { createElement, type ComponentPropsWithoutRef } from 'react'\nimport { gtmNoscriptSrc } from '../core/noscript'\n\n/**\n * Google Tag Manager's `<noscript>` iframe. It loads the container only for\n * visitors without JavaScript, who never see the consent banner, so its tags\n * run without consent. Render it only where that is lawful for you. Renders\n * nothing without `enabled` or while `containerId` is unset.\n */\nexport function GtmNoscript(\n props: {\n containerId: string | undefined\n enabled?: boolean\n } & ComponentPropsWithoutRef<'iframe'>,\n) {\n const { containerId, enabled, ...iframeProps } = props\n if (!enabled || !containerId) return null\n return createElement(\n 'noscript',\n undefined,\n createElement('iframe', {\n title: 'Google Tag Manager',\n ...iframeProps,\n height: iframeProps.height ?? 0,\n width: iframeProps.width ?? 0,\n style: { display: 'none', visibility: 'hidden', ...iframeProps.style },\n src: gtmNoscriptSrc(containerId),\n }),\n )\n}\n","import { createElement, type ComponentPropsWithoutRef } from 'react'\nimport { metaNoscriptSrc } from '../core/noscript'\n\n/**\n * Meta's `<noscript>` PageView pixel. It fires only for visitors without\n * JavaScript, who never see the consent banner, so it tracks them without\n * consent. Render it only where that is lawful for you. Renders nothing without\n * `enabled` or while `pixelId` is unset.\n */\nexport function MetaPixelNoscript(\n props: {\n pixelId: string | undefined\n enabled?: boolean\n } & ComponentPropsWithoutRef<'img'>,\n) {\n const { pixelId, enabled, ...imageProps } = props\n if (!enabled || !pixelId) return null\n return createElement(\n 'noscript',\n undefined,\n createElement('img', {\n ...imageProps,\n alt: '',\n height: imageProps.height ?? 1,\n width: imageProps.width ?? 1,\n style: { display: 'none', ...imageProps.style },\n src: metaNoscriptSrc(pixelId),\n }),\n )\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { withSentryConfig } from '@sentry/nextjs/config';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"next.js","sourcesContent":[]}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { captureRequestError } from '@sentry/nextjs';
|
|
2
|
+
|
|
3
|
+
/** Options for `sentryInstrumentation`. */
|
|
4
|
+
type SentryServerOptions = {
|
|
5
|
+
dsn: string | undefined;
|
|
6
|
+
environment?: string;
|
|
7
|
+
release?: string;
|
|
8
|
+
/** Performance tracing sample rate. Off by default. */
|
|
9
|
+
tracesSampleRate?: number;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Sentry for Next.js server and edge code, without personal data.
|
|
13
|
+
*
|
|
14
|
+
* ```ts
|
|
15
|
+
* // instrumentation.ts
|
|
16
|
+
* export const { register, onRequestError } = sentryInstrumentation({ dsn: process.env.SENTRY_DSN })
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
declare function sentryInstrumentation(options: SentryServerOptions): {
|
|
20
|
+
register: () => void;
|
|
21
|
+
onRequestError: typeof captureRequestError;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export { type SentryServerOptions, sentryInstrumentation };
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { captureRequestError, init } from '@sentry/nextjs';
|
|
2
|
+
|
|
3
|
+
// src/sentry/server.ts
|
|
4
|
+
function sentryInstrumentation(options) {
|
|
5
|
+
return {
|
|
6
|
+
register: () => {
|
|
7
|
+
if (!options.dsn) return;
|
|
8
|
+
init({
|
|
9
|
+
dsn: options.dsn,
|
|
10
|
+
environment: options.environment,
|
|
11
|
+
release: options.release,
|
|
12
|
+
tracesSampleRate: options.tracesSampleRate,
|
|
13
|
+
dataCollection: {
|
|
14
|
+
userInfo: false,
|
|
15
|
+
cookies: false,
|
|
16
|
+
httpHeaders: false,
|
|
17
|
+
httpBodies: [],
|
|
18
|
+
urlQueryParams: false
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
},
|
|
22
|
+
onRequestError: captureRequestError
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export { sentryInstrumentation };
|
|
27
|
+
//# sourceMappingURL=server.js.map
|
|
28
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/sentry/server.ts"],"names":[],"mappings":";;;AAmBO,SAAS,sBAAsB,OAAA,EAA8B;AAClE,EAAA,OAAO;AAAA,IACL,UAAU,MAAM;AACd,MAAA,IAAI,CAAC,QAAQ,GAAA,EAAK;AAClB,MAAA,IAAA,CAAK;AAAA,QACH,KAAK,OAAA,CAAQ,GAAA;AAAA,QACb,aAAa,OAAA,CAAQ,WAAA;AAAA,QACrB,SAAS,OAAA,CAAQ,OAAA;AAAA,QACjB,kBAAkB,OAAA,CAAQ,gBAAA;AAAA,QAC1B,cAAA,EAAgB;AAAA,UACd,QAAA,EAAU,KAAA;AAAA,UACV,OAAA,EAAS,KAAA;AAAA,UACT,WAAA,EAAa,KAAA;AAAA,UACb,YAAY,EAAC;AAAA,UACb,cAAA,EAAgB;AAAA;AAClB,OACD,CAAA;AAAA,IACH,CAAA;AAAA,IACA,cAAA,EAAgB;AAAA,GAClB;AACF","file":"server.js","sourcesContent":["import { captureRequestError, init } from '@sentry/nextjs'\n\n/** Options for `sentryInstrumentation`. */\nexport type SentryServerOptions = {\n dsn: string | undefined\n environment?: string\n release?: string\n /** Performance tracing sample rate. Off by default. */\n tracesSampleRate?: number\n}\n\n/**\n * Sentry for Next.js server and edge code, without personal data.\n *\n * ```ts\n * // instrumentation.ts\n * export const { register, onRequestError } = sentryInstrumentation({ dsn: process.env.SENTRY_DSN })\n * ```\n */\nexport function sentryInstrumentation(options: SentryServerOptions) {\n return {\n register: () => {\n if (!options.dsn) return\n init({\n dsn: options.dsn,\n environment: options.environment,\n release: options.release,\n tracesSampleRate: options.tracesSampleRate,\n dataCollection: {\n userInfo: false,\n cookies: false,\n httpHeaders: false,\n httpBodies: [],\n urlQueryParams: false,\n },\n })\n },\n onRequestError: captureRequestError,\n }\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"sentry-replay-HSFVEFCM.js","sourcesContent":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"sentry-sdk-YQOGKGWD.js","sourcesContent":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"sentry-tracing-KWT7ITHF.js","sourcesContent":[]}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { ServerDestination } from './index.js';
|
|
2
|
+
import '../types-BV_AFZKQ.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* GA4 Measurement Protocol, for server-only events (webhooks). It augments
|
|
6
|
+
* gtag rather than replacing it, so `createAnalyticsHandler` doesn't take it.
|
|
7
|
+
* Off (`undefined`) while `measurementId` or `apiSecret` is unset.
|
|
8
|
+
*/
|
|
9
|
+
declare function ga4Server(options: {
|
|
10
|
+
measurementId: string | undefined;
|
|
11
|
+
apiSecret: string | undefined;
|
|
12
|
+
fetchImpl?: typeof fetch;
|
|
13
|
+
}): ServerDestination<false> | undefined;
|
|
14
|
+
|
|
15
|
+
export { ga4Server };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { sendMeasurementProtocol } from '../chunk-HJGBVDPM.js';
|
|
2
|
+
|
|
3
|
+
// src/server/ga4.ts
|
|
4
|
+
var collectUrl = (measurementId, apiSecret) => `https://www.google-analytics.com/mp/collect?${new URLSearchParams({ measurement_id: measurementId, api_secret: apiSecret })}`;
|
|
5
|
+
function ga4Server(options) {
|
|
6
|
+
const measurementId = options.measurementId?.trim();
|
|
7
|
+
const apiSecret = options.apiSecret?.trim();
|
|
8
|
+
if (!measurementId || !apiSecret) {
|
|
9
|
+
if (measurementId || apiSecret)
|
|
10
|
+
console.warn("[consentlayer] ga4Server is off: set both measurementId and apiSecret.");
|
|
11
|
+
return void 0;
|
|
12
|
+
}
|
|
13
|
+
const fetchImpl = options.fetchImpl ?? globalThis.fetch;
|
|
14
|
+
return {
|
|
15
|
+
id: "ga4",
|
|
16
|
+
requiredConsent: "analytics",
|
|
17
|
+
forwardBrowserEvents: false,
|
|
18
|
+
send: (event, request) => sendMeasurementProtocol(
|
|
19
|
+
{ url: collectUrl(measurementId, apiSecret), sender: "ga4Server", receiver: "GA4 Measurement Protocol" },
|
|
20
|
+
event,
|
|
21
|
+
request,
|
|
22
|
+
fetchImpl
|
|
23
|
+
)
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export { ga4Server };
|
|
28
|
+
//# sourceMappingURL=ga4.js.map
|
|
29
|
+
//# sourceMappingURL=ga4.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/server/ga4.ts"],"names":[],"mappings":";;;AAGA,IAAM,UAAA,GAAa,CAAC,aAAA,EAAuB,SAAA,KACzC,CAAA,4CAAA,EAA+C,IAAI,eAAA,CAAgB,EAAE,cAAA,EAAgB,aAAA,EAAe,UAAA,EAAY,SAAA,EAAW,CAAC,CAAA,CAAA;AAOvH,SAAS,UAAU,OAAA,EAIe;AACvC,EAAA,MAAM,aAAA,GAAgB,OAAA,CAAQ,aAAA,EAAe,IAAA,EAAK;AAClD,EAAA,MAAM,SAAA,GAAY,OAAA,CAAQ,SAAA,EAAW,IAAA,EAAK;AAC1C,EAAA,IAAI,CAAC,aAAA,IAAiB,CAAC,SAAA,EAAW;AAChC,IAAA,IAAI,aAAA,IAAiB,SAAA;AACnB,MAAA,OAAA,CAAQ,KAAK,wEAAwE,CAAA;AACvF,IAAA,OAAO,MAAA;AAAA,EACT;AACA,EAAA,MAAM,SAAA,GAAY,OAAA,CAAQ,SAAA,IAAa,UAAA,CAAW,KAAA;AAClD,EAAA,OAAO;AAAA,IACL,EAAA,EAAI,KAAA;AAAA,IACJ,eAAA,EAAiB,WAAA;AAAA,IACjB,oBAAA,EAAsB,KAAA;AAAA,IACtB,IAAA,EAAM,CAAC,KAAA,EAAO,OAAA,KACZ,uBAAA;AAAA,MACE,EAAE,KAAK,UAAA,CAAW,aAAA,EAAe,SAAS,CAAA,EAAG,MAAA,EAAQ,WAAA,EAAa,QAAA,EAAU,0BAAA,EAA2B;AAAA,MACvG,KAAA;AAAA,MACA,OAAA;AAAA,MACA;AAAA;AACF,GACJ;AACF","file":"ga4.js","sourcesContent":["import type { ServerDestination } from './handler'\nimport { sendMeasurementProtocol } from './measurement-protocol'\n\nconst collectUrl = (measurementId: string, apiSecret: string) =>\n `https://www.google-analytics.com/mp/collect?${new URLSearchParams({ measurement_id: measurementId, api_secret: apiSecret })}`\n\n/**\n * GA4 Measurement Protocol, for server-only events (webhooks). It augments\n * gtag rather than replacing it, so `createAnalyticsHandler` doesn't take it.\n * Off (`undefined`) while `measurementId` or `apiSecret` is unset.\n */\nexport function ga4Server(options: {\n measurementId: string | undefined\n apiSecret: string | undefined\n fetchImpl?: typeof fetch\n}): ServerDestination<false> | undefined {\n const measurementId = options.measurementId?.trim()\n const apiSecret = options.apiSecret?.trim()\n if (!measurementId || !apiSecret) {\n if (measurementId || apiSecret)\n console.warn('[consentlayer] ga4Server is off: set both measurementId and apiSecret.')\n return undefined\n }\n const fetchImpl = options.fetchImpl ?? globalThis.fetch\n return {\n id: 'ga4',\n requiredConsent: 'analytics',\n forwardBrowserEvents: false,\n send: (event, request) =>\n sendMeasurementProtocol(\n { url: collectUrl(measurementId, apiSecret), sender: 'ga4Server', receiver: 'GA4 Measurement Protocol' },\n event,\n request,\n fetchImpl,\n ),\n }\n}\n"]}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { p as ConsentState, z as ConsentCategories, M as ServerEvent } from '../types-BV_AFZKQ.js';
|
|
2
|
+
|
|
3
|
+
/** One row of the consent log. Rows are only ever appended. */
|
|
4
|
+
type ConsentRecord = {
|
|
5
|
+
consentId: string;
|
|
6
|
+
createdAt: Date;
|
|
7
|
+
consent: ConsentState;
|
|
8
|
+
policyVersion: string;
|
|
9
|
+
userId?: string;
|
|
10
|
+
/** Sent again by the browser after the consent cookie was lost, not chosen at `createdAt`. */
|
|
11
|
+
restored: boolean;
|
|
12
|
+
};
|
|
13
|
+
/** Where `createConsentHandler` appends records. */
|
|
14
|
+
interface ConsentStore {
|
|
15
|
+
append(record: ConsentRecord): Promise<void>;
|
|
16
|
+
}
|
|
17
|
+
/** What the signed cookie proves: this browser's latest recorded choice. */
|
|
18
|
+
type ConsentProof = {
|
|
19
|
+
consentId: string;
|
|
20
|
+
consent: ConsentState;
|
|
21
|
+
policyVersion: string;
|
|
22
|
+
};
|
|
23
|
+
type ConsentCookieOptions = {
|
|
24
|
+
/** At least 32 characters, e.g. `process.env.CONSENT_SECRET`. */
|
|
25
|
+
secret: string | undefined;
|
|
26
|
+
/** Default `__Host-consentlayer_proof`, or `consentlayer_proof` on `localhost`, `127.0.0.1` and `[::1]`. */
|
|
27
|
+
cookie?: string;
|
|
28
|
+
};
|
|
29
|
+
/** The verified proof from the request's consent cookie, or `null` if it is missing or tampered with. */
|
|
30
|
+
declare function readConsentCookie(request: Request, options: ConsentCookieOptions): Promise<ConsentProof | null>;
|
|
31
|
+
/**
|
|
32
|
+
* A web-standard route handler for `createAnalytics({ server: { consentEndpoint } })`.
|
|
33
|
+
* Validates each choice, appends a record and answers with the signed consent cookie
|
|
34
|
+
* that `createAnalyticsHandler({ consentCookie })` trusts.
|
|
35
|
+
*/
|
|
36
|
+
declare function createConsentHandler(options: ConsentCookieOptions & {
|
|
37
|
+
store: ConsentStore;
|
|
38
|
+
/** The privacy policy the visitor saw, e.g. `'3.2'`. */
|
|
39
|
+
policyVersion: string;
|
|
40
|
+
/** Your own categories, as passed to `createAnalytics({ categories })`. */
|
|
41
|
+
categories?: (keyof ConsentCategories)[];
|
|
42
|
+
/** The signed-in user, from your session. Never read from the request body. */
|
|
43
|
+
userId?: (request: Request) => string | undefined | Promise<string | undefined>;
|
|
44
|
+
onError?: (error: unknown) => void;
|
|
45
|
+
}): (request: Request) => Promise<Response>;
|
|
46
|
+
type SqlTag = (strings: TemplateStringsArray, ...values: unknown[]) => PromiseLike<unknown>;
|
|
47
|
+
/** Postgres through any `sql` tagged template: postgres.js, Neon or Vercel Postgres. */
|
|
48
|
+
declare function postgresStore(sql: SqlTag): ConsentStore;
|
|
49
|
+
/** Upstash Redis or Vercel KV: one list per consent ID, `consent:<consentId>`. */
|
|
50
|
+
declare function upstashStore(redis: {
|
|
51
|
+
rpush(key: string, ...values: string[]): Promise<unknown>;
|
|
52
|
+
}, options?: {
|
|
53
|
+
prefix?: string;
|
|
54
|
+
}): ConsentStore;
|
|
55
|
+
/** supabase-js. */
|
|
56
|
+
declare function supabaseStore(supabase: {
|
|
57
|
+
from(table: string): {
|
|
58
|
+
insert(row: object): PromiseLike<{
|
|
59
|
+
error: unknown;
|
|
60
|
+
}>;
|
|
61
|
+
};
|
|
62
|
+
}, options?: {
|
|
63
|
+
table?: string;
|
|
64
|
+
}): ConsentStore;
|
|
65
|
+
/** Prisma: pass the model, e.g. `prismaStore(prisma.consentRecord)`. */
|
|
66
|
+
declare function prismaStore(model: {
|
|
67
|
+
create(args: {
|
|
68
|
+
data: object;
|
|
69
|
+
}): PromiseLike<unknown>;
|
|
70
|
+
}): ConsentStore;
|
|
71
|
+
/** Drizzle: pass the database and the table. */
|
|
72
|
+
declare function drizzleStore<Table>(db: {
|
|
73
|
+
insert(table: Table): {
|
|
74
|
+
values(row: object): PromiseLike<unknown>;
|
|
75
|
+
};
|
|
76
|
+
}, table: Table): ConsentStore;
|
|
77
|
+
|
|
78
|
+
/** Request details destinations use: client IP, user agent and cookies. */
|
|
79
|
+
type ServerRequestInfo = {
|
|
80
|
+
ip?: string;
|
|
81
|
+
userAgent?: string;
|
|
82
|
+
cookies: Record<string, string>;
|
|
83
|
+
};
|
|
84
|
+
/** A server-side destination. `send` throws when the vendor rejects the event. */
|
|
85
|
+
interface ServerDestination<Forward extends boolean = boolean> {
|
|
86
|
+
id: string;
|
|
87
|
+
requiredConsent: keyof ConsentState;
|
|
88
|
+
/** Whether `createAnalyticsHandler` may send browser events to it. */
|
|
89
|
+
forwardBrowserEvents: Forward;
|
|
90
|
+
send(event: ServerEvent, request?: ServerRequestInfo): Promise<void>;
|
|
91
|
+
}
|
|
92
|
+
/** Whether one destination accepted the event. */
|
|
93
|
+
type ServerSendResult = {
|
|
94
|
+
id: string;
|
|
95
|
+
ok: boolean;
|
|
96
|
+
error?: unknown;
|
|
97
|
+
};
|
|
98
|
+
/** Sends to every destination the event's consent and `only` allow, in parallel. `undefined` entries are destinations that are off. */
|
|
99
|
+
declare function sendServerEvent(event: ServerEvent, options: {
|
|
100
|
+
destinations: (ServerDestination | undefined)[];
|
|
101
|
+
request?: ServerRequestInfo;
|
|
102
|
+
}): Promise<ServerSendResult[]>;
|
|
103
|
+
/** IP, user agent and cookies of a request, for `sendServerEvent({ request })`. */
|
|
104
|
+
declare function requestInfo(request: Request): ServerRequestInfo;
|
|
105
|
+
/**
|
|
106
|
+
* A web-standard route handler for events forwarded by `createAnalytics({ server })`.
|
|
107
|
+
* Only destinations that accept browser events (e.g. `metaCapi`) can be passed.
|
|
108
|
+
*/
|
|
109
|
+
declare function createAnalyticsHandler(options: {
|
|
110
|
+
destinations: (ServerDestination<true> | undefined)[];
|
|
111
|
+
/** Grant only categories both the cookie `createConsentHandler` signs and the body grant; no valid cookie means denied. */
|
|
112
|
+
consentCookie?: ConsentCookieOptions;
|
|
113
|
+
onError?: (error: unknown) => void;
|
|
114
|
+
}): (request: Request) => Promise<Response>;
|
|
115
|
+
|
|
116
|
+
export { type ConsentCookieOptions, type ConsentProof, type ConsentRecord, type ConsentStore, type ServerDestination, ServerEvent, type ServerRequestInfo, type ServerSendResult, createAnalyticsHandler, createConsentHandler, drizzleStore, postgresStore, prismaStore, readConsentCookie, requestInfo, sendServerEvent, supabaseStore, upstashStore };
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import { parseCookieHeader, assertSameOrigin, parseJsonWithLimit, ANALYTICS_MAX_BODY } from '../chunk-DK45XPEY.js';
|
|
2
|
+
import { BUILT_IN_CATEGORIES, normalizeConsent } from '../chunk-TVSZJLRC.js';
|
|
3
|
+
import { assertValidConsent } from '../chunk-J3KUPF4R.js';
|
|
4
|
+
|
|
5
|
+
// src/server/consent.ts
|
|
6
|
+
var LOCAL_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "[::1]"]);
|
|
7
|
+
var isLocal = (request) => LOCAL_HOSTS.has(new URL(request.url).hostname);
|
|
8
|
+
var cookieName = (request, options) => options.cookie ?? (isLocal(request) ? "consentlayer_proof" : "__Host-consentlayer_proof");
|
|
9
|
+
var MAX_AGE = 400 * 24 * 60 * 60;
|
|
10
|
+
var encoder = new TextEncoder();
|
|
11
|
+
var toBase64Url = (bytes) => btoa(String.fromCharCode(...bytes)).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
12
|
+
var fromBase64Url = (value) => Uint8Array.from(atob(value.replace(/-/g, "+").replace(/_/g, "/")), (char) => char.charCodeAt(0));
|
|
13
|
+
function assertSecret(secret) {
|
|
14
|
+
if (!secret) throw new Error("[consentlayer] the consent secret is not set");
|
|
15
|
+
if (secret.length < 32) throw new Error("[consentlayer] the consent secret needs at least 32 characters");
|
|
16
|
+
}
|
|
17
|
+
var hmacKey = (secret) => {
|
|
18
|
+
assertSecret(secret);
|
|
19
|
+
return crypto.subtle.importKey("raw", encoder.encode(secret), { name: "HMAC", hash: "SHA-256" }, false, [
|
|
20
|
+
"sign",
|
|
21
|
+
"verify"
|
|
22
|
+
]);
|
|
23
|
+
};
|
|
24
|
+
async function sign(proof, secret) {
|
|
25
|
+
const payload = toBase64Url(encoder.encode(JSON.stringify(proof)));
|
|
26
|
+
const signature = await crypto.subtle.sign("HMAC", await hmacKey(secret), encoder.encode(payload));
|
|
27
|
+
return `${payload}.${toBase64Url(new Uint8Array(signature))}`;
|
|
28
|
+
}
|
|
29
|
+
async function readConsentCookie(request, options) {
|
|
30
|
+
const value = parseCookieHeader(request.headers.get("cookie") ?? "")[cookieName(request, options)];
|
|
31
|
+
const [payload, signature] = value?.split(".") ?? [];
|
|
32
|
+
if (!payload || !signature) return null;
|
|
33
|
+
try {
|
|
34
|
+
const valid = await crypto.subtle.verify(
|
|
35
|
+
"HMAC",
|
|
36
|
+
await hmacKey(options.secret),
|
|
37
|
+
fromBase64Url(signature),
|
|
38
|
+
encoder.encode(payload)
|
|
39
|
+
);
|
|
40
|
+
return valid ? JSON.parse(new TextDecoder().decode(fromBase64Url(payload))) : null;
|
|
41
|
+
} catch {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function createConsentHandler(options) {
|
|
46
|
+
const categories = [...BUILT_IN_CATEGORIES, ...options.categories ?? []];
|
|
47
|
+
return async (request) => {
|
|
48
|
+
try {
|
|
49
|
+
assertSecret(options.secret);
|
|
50
|
+
assertSameOrigin(request, { requireHeader: true });
|
|
51
|
+
const body = await parseJsonWithLimit(request, 4 * 1024);
|
|
52
|
+
try {
|
|
53
|
+
assertValidConsent(body?.consent, categories);
|
|
54
|
+
} catch {
|
|
55
|
+
return new Response(null, { status: 400 });
|
|
56
|
+
}
|
|
57
|
+
const consent = normalizeConsent(body.consent, categories);
|
|
58
|
+
const previous = await readConsentCookie(request, options);
|
|
59
|
+
const consentId = previous?.consentId ?? crypto.randomUUID();
|
|
60
|
+
const userId = await options.userId?.(request);
|
|
61
|
+
await options.store.append({
|
|
62
|
+
consentId,
|
|
63
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
64
|
+
consent,
|
|
65
|
+
policyVersion: options.policyVersion,
|
|
66
|
+
...userId ? { userId } : {},
|
|
67
|
+
restored: body.restored === true
|
|
68
|
+
});
|
|
69
|
+
const cookie = await sign({ consentId, consent, policyVersion: options.policyVersion }, options.secret);
|
|
70
|
+
return new Response(null, {
|
|
71
|
+
status: 204,
|
|
72
|
+
headers: {
|
|
73
|
+
"set-cookie": `${cookieName(request, options)}=${cookie}; Path=/; Max-Age=${MAX_AGE}; HttpOnly; SameSite=Lax${isLocal(request) ? "" : "; Secure"}`
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
} catch (error) {
|
|
77
|
+
const status = error.status;
|
|
78
|
+
if (typeof status === "number") return new Response(null, { status });
|
|
79
|
+
options.onError?.(error);
|
|
80
|
+
return new Response(null, { status: 500 });
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
var row = (record) => ({
|
|
85
|
+
consent_id: record.consentId,
|
|
86
|
+
created_at: record.createdAt.toISOString(),
|
|
87
|
+
consent: record.consent,
|
|
88
|
+
policy_version: record.policyVersion,
|
|
89
|
+
user_id: record.userId ?? null,
|
|
90
|
+
restored: record.restored
|
|
91
|
+
});
|
|
92
|
+
function postgresStore(sql) {
|
|
93
|
+
return {
|
|
94
|
+
async append(record) {
|
|
95
|
+
const r = row(record);
|
|
96
|
+
await sql`insert into consent_records (consent_id, created_at, consent, policy_version, user_id, restored)
|
|
97
|
+
values (${r.consent_id}, ${r.created_at}::timestamptz, ${JSON.stringify(r.consent)}::jsonb, ${r.policy_version}, ${r.user_id}, ${r.restored})`;
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
function upstashStore(redis, options = {}) {
|
|
102
|
+
return {
|
|
103
|
+
async append(record) {
|
|
104
|
+
await redis.rpush(`${options.prefix ?? "consent:"}${record.consentId}`, JSON.stringify(record));
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
function supabaseStore(supabase, options = {}) {
|
|
109
|
+
return {
|
|
110
|
+
async append(record) {
|
|
111
|
+
const { error } = await supabase.from(options.table ?? "consent_records").insert(row(record));
|
|
112
|
+
if (error) throw new Error("[consentlayer] supabase insert failed", { cause: error });
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
function prismaStore(model) {
|
|
117
|
+
return {
|
|
118
|
+
async append(record) {
|
|
119
|
+
await model.create({ data: { ...record, userId: record.userId ?? null } });
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
function drizzleStore(db, table) {
|
|
124
|
+
return {
|
|
125
|
+
async append(record) {
|
|
126
|
+
await db.insert(table).values({ ...record, userId: record.userId ?? null });
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// src/server/handler.ts
|
|
132
|
+
async function sendServerEvent(event, options) {
|
|
133
|
+
const targets = options.destinations.filter(
|
|
134
|
+
(destination) => destination !== void 0 && event.consent[destination.requiredConsent] === true && (!event.only || event.only.includes(destination.id))
|
|
135
|
+
);
|
|
136
|
+
const results = await Promise.allSettled(targets.map((destination) => destination.send(event, options.request)));
|
|
137
|
+
return targets.map((destination, index) => {
|
|
138
|
+
const result = results[index];
|
|
139
|
+
return result?.status === "rejected" ? { id: destination.id, ok: false, error: result.reason } : { id: destination.id, ok: true };
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
function requestInfo(request) {
|
|
143
|
+
const headers = request.headers;
|
|
144
|
+
return {
|
|
145
|
+
ip: headers.get("x-forwarded-for")?.split(",")[0]?.trim() || headers.get("x-real-ip") || void 0,
|
|
146
|
+
userAgent: headers.get("user-agent") ?? void 0,
|
|
147
|
+
cookies: parseCookieHeader(headers.get("cookie") ?? "")
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
var isObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
151
|
+
var isOptional = (value, check) => value === void 0 || check(value);
|
|
152
|
+
var isString = (value) => typeof value === "string";
|
|
153
|
+
var isStringArray = (value) => Array.isArray(value) && value.every(isString);
|
|
154
|
+
function parseServerEvent(body) {
|
|
155
|
+
const valid = isObject(body) && typeof body.name === "string" && /^[\w.:-]{1,100}$/.test(body.name) && isObject(body.consent) && ["preferences", "analytics", "marketing"].every(
|
|
156
|
+
(key) => typeof body.consent[key] === "boolean"
|
|
157
|
+
) && isOptional(body.id, isString) && isOptional(body.url, isString) && isOptional(body.clientId, isString) && isOptional(body.params, isObject) && isOptional(body.providers, isObject) && isOptional(body.only, isStringArray) && isOptional(
|
|
158
|
+
body.identity,
|
|
159
|
+
(identity) => isObject(identity) && Object.values(identity).every((value) => isOptional(value, isString))
|
|
160
|
+
);
|
|
161
|
+
if (!valid) throw Object.assign(new Error("Invalid event"), { status: 400 });
|
|
162
|
+
return body;
|
|
163
|
+
}
|
|
164
|
+
function createAnalyticsHandler(options) {
|
|
165
|
+
if (options.consentCookie && !options.consentCookie.secret) {
|
|
166
|
+
console.warn("[consentlayer] consentCookie.secret is not set: every event is treated as denied.");
|
|
167
|
+
}
|
|
168
|
+
const destinations = options.destinations.filter((destination) => destination?.forwardBrowserEvents);
|
|
169
|
+
return async (request) => {
|
|
170
|
+
try {
|
|
171
|
+
if (options.consentCookie?.secret) assertSecret(options.consentCookie.secret);
|
|
172
|
+
assertSameOrigin(request, { requireHeader: true });
|
|
173
|
+
if (!destinations.length)
|
|
174
|
+
return new Response(null, { status: 204, headers: { "consentlayer-forwarding": "off" } });
|
|
175
|
+
const event = parseServerEvent(await parseJsonWithLimit(request, ANALYTICS_MAX_BODY));
|
|
176
|
+
const proof = options.consentCookie && await readConsentCookie(request, options.consentCookie);
|
|
177
|
+
if (options.consentCookie) {
|
|
178
|
+
const recorded = proof?.consent ?? { preferences: false, analytics: false, marketing: false };
|
|
179
|
+
event.consent = Object.fromEntries(
|
|
180
|
+
Object.entries(recorded).map(([key, value]) => [
|
|
181
|
+
key,
|
|
182
|
+
value === true && event.consent[key] === true
|
|
183
|
+
])
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
const results = await sendServerEvent(event, {
|
|
187
|
+
destinations,
|
|
188
|
+
request: requestInfo(request)
|
|
189
|
+
});
|
|
190
|
+
for (const result of results) if (!result.ok) options.onError?.(result.error);
|
|
191
|
+
return new Response(null, {
|
|
192
|
+
status: 204,
|
|
193
|
+
headers: options.consentCookie && !proof ? { "consentlayer-consent": "missing" } : {}
|
|
194
|
+
});
|
|
195
|
+
} catch (error) {
|
|
196
|
+
const status = error.status;
|
|
197
|
+
if (typeof status === "number") return new Response(null, { status });
|
|
198
|
+
options.onError?.(error);
|
|
199
|
+
return new Response(null, { status: 500 });
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export { createAnalyticsHandler, createConsentHandler, drizzleStore, postgresStore, prismaStore, readConsentCookie, requestInfo, sendServerEvent, supabaseStore, upstashStore };
|
|
205
|
+
//# sourceMappingURL=index.js.map
|
|
206
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/server/consent.ts","../../src/server/handler.ts"],"names":[],"mappings":";;;;;AAoCA,IAAM,8BAAc,IAAI,GAAA,CAAI,CAAC,WAAA,EAAa,WAAA,EAAa,OAAO,CAAC,CAAA;AAC/D,IAAM,OAAA,GAAU,CAAC,OAAA,KAAqB,WAAA,CAAY,GAAA,CAAI,IAAI,GAAA,CAAI,OAAA,CAAQ,GAAG,CAAA,CAAE,QAAQ,CAAA;AACnF,IAAM,UAAA,GAAa,CAAC,OAAA,EAAkB,OAAA,KACpC,QAAQ,MAAA,KAAW,OAAA,CAAQ,OAAO,CAAA,GAAI,oBAAA,GAAuB,2BAAA,CAAA;AAE/D,IAAM,OAAA,GAAU,GAAA,GAAM,EAAA,GAAK,EAAA,GAAK,EAAA;AAEhC,IAAM,OAAA,GAAU,IAAI,WAAA,EAAY;AAChC,IAAM,WAAA,GAAc,CAAC,KAAA,KACnB,IAAA,CAAK,OAAO,YAAA,CAAa,GAAG,KAAK,CAAC,CAAA,CAC/B,QAAQ,KAAA,EAAO,GAAG,EAClB,OAAA,CAAQ,KAAA,EAAO,GAAG,CAAA,CAClB,OAAA,CAAQ,OAAO,EAAE,CAAA;AACtB,IAAM,aAAA,GAAgB,CAAC,KAAA,KACrB,UAAA,CAAW,KAAK,IAAA,CAAK,KAAA,CAAM,QAAQ,IAAA,EAAM,GAAG,EAAE,OAAA,CAAQ,IAAA,EAAM,GAAG,CAAC,CAAA,EAAG,CAAC,IAAA,KAAS,IAAA,CAAK,UAAA,CAAW,CAAC,CAAC,CAAA;AAE1F,SAAS,aAAa,MAAA,EAAsD;AACjF,EAAA,IAAI,CAAC,MAAA,EAAQ,MAAM,IAAI,MAAM,8CAA8C,CAAA;AAC3E,EAAA,IAAI,OAAO,MAAA,GAAS,EAAA,EAAI,MAAM,IAAI,MAAM,gEAAgE,CAAA;AAC1G;AAEA,IAAM,OAAA,GAAU,CAAC,MAAA,KAA+B;AAC9C,EAAA,YAAA,CAAa,MAAM,CAAA;AACnB,EAAA,OAAO,MAAA,CAAO,MAAA,CAAO,SAAA,CAAU,KAAA,EAAO,QAAQ,MAAA,CAAO,MAAM,CAAA,EAAG,EAAE,IAAA,EAAM,MAAA,EAAQ,IAAA,EAAM,SAAA,IAAa,KAAA,EAAO;AAAA,IACtG,MAAA;AAAA,IACA;AAAA,GACD,CAAA;AACH,CAAA;AAEA,eAAe,IAAA,CAAK,OAAqB,MAAA,EAAiC;AACxE,EAAA,MAAM,OAAA,GAAU,YAAY,OAAA,CAAQ,MAAA,CAAO,KAAK,SAAA,CAAU,KAAK,CAAC,CAAC,CAAA;AACjE,EAAA,MAAM,SAAA,GAAY,MAAM,MAAA,CAAO,MAAA,CAAO,IAAA,CAAK,MAAA,EAAQ,MAAM,OAAA,CAAQ,MAAM,CAAA,EAAG,OAAA,CAAQ,MAAA,CAAO,OAAO,CAAC,CAAA;AACjG,EAAA,OAAO,CAAA,EAAG,OAAO,CAAA,CAAA,EAAI,WAAA,CAAY,IAAI,UAAA,CAAW,SAAS,CAAC,CAAC,CAAA,CAAA;AAC7D;AAGA,eAAsB,iBAAA,CAAkB,SAAkB,OAAA,EAA6D;AACrH,EAAA,MAAM,KAAA,GAAQ,iBAAA,CAAkB,OAAA,CAAQ,OAAA,CAAQ,GAAA,CAAI,QAAQ,CAAA,IAAK,EAAE,CAAA,CAAE,UAAA,CAAW,OAAA,EAAS,OAAO,CAAC,CAAA;AACjG,EAAA,MAAM,CAAC,SAAS,SAAS,CAAA,GAAI,OAAO,KAAA,CAAM,GAAG,KAAK,EAAC;AACnD,EAAA,IAAI,CAAC,OAAA,IAAW,CAAC,SAAA,EAAW,OAAO,IAAA;AACnC,EAAA,IAAI;AACF,IAAA,MAAM,KAAA,GAAQ,MAAM,MAAA,CAAO,MAAA,CAAO,MAAA;AAAA,MAChC,MAAA;AAAA,MACA,MAAM,OAAA,CAAQ,OAAA,CAAQ,MAAM,CAAA;AAAA,MAC5B,cAAc,SAAS,CAAA;AAAA,MACvB,OAAA,CAAQ,OAAO,OAAO;AAAA,KACxB;AACA,IAAA,OAAO,KAAA,GAAS,IAAA,CAAK,KAAA,CAAM,IAAI,WAAA,EAAY,CAAE,MAAA,CAAO,aAAA,CAAc,OAAO,CAAC,CAAC,CAAA,GAAqB,IAAA;AAAA,EAClG,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,IAAA;AAAA,EACT;AACF;AAOO,SAAS,qBACd,OAAA,EAUyC;AACzC,EAAA,MAAM,UAAA,GAAa,CAAC,GAAG,mBAAA,EAAqB,GAAI,OAAA,CAAQ,UAAA,IAAc,EAAG,CAAA;AACzE,EAAA,OAAO,OAAO,OAAA,KAAY;AACxB,IAAA,IAAI;AACF,MAAA,YAAA,CAAa,QAAQ,MAAM,CAAA;AAC3B,MAAA,gBAAA,CAAiB,OAAA,EAAS,EAAE,aAAA,EAAe,IAAA,EAAM,CAAA;AACjD,MAAA,MAAM,IAAA,GAAO,MAAM,kBAAA,CAA8D,OAAA,EAAS,IAAI,IAAI,CAAA;AAClG,MAAA,IAAI;AACF,QAAA,kBAAA,CAAmB,IAAA,EAAM,SAAS,UAAU,CAAA;AAAA,MAC9C,CAAA,CAAA,MAAQ;AACN,QAAA,OAAO,IAAI,QAAA,CAAS,IAAA,EAAM,EAAE,MAAA,EAAQ,KAAK,CAAA;AAAA,MAC3C;AACA,MAAA,MAAM,OAAA,GAAU,gBAAA,CAAiB,IAAA,CAAK,OAAA,EAAyB,UAAU,CAAA;AACzE,MAAA,MAAM,QAAA,GAAW,MAAM,iBAAA,CAAkB,OAAA,EAAS,OAAO,CAAA;AACzD,MAAA,MAAM,SAAA,GAAY,QAAA,EAAU,SAAA,IAAa,MAAA,CAAO,UAAA,EAAW;AAC3D,MAAA,MAAM,MAAA,GAAS,MAAM,OAAA,CAAQ,MAAA,GAAS,OAAO,CAAA;AAC7C,MAAA,MAAM,OAAA,CAAQ,MAAM,MAAA,CAAO;AAAA,QACzB,SAAA;AAAA,QACA,SAAA,sBAAe,IAAA,EAAK;AAAA,QACpB,OAAA;AAAA,QACA,eAAe,OAAA,CAAQ,aAAA;AAAA,QACvB,GAAI,MAAA,GAAS,EAAE,MAAA,KAAW,EAAC;AAAA,QAC3B,QAAA,EAAU,KAAK,QAAA,KAAa;AAAA,OAC7B,CAAA;AACD,MAAA,MAAM,MAAA,GAAS,MAAM,IAAA,CAAK,EAAE,SAAA,EAAW,OAAA,EAAS,aAAA,EAAe,OAAA,CAAQ,aAAA,EAAc,EAAG,OAAA,CAAQ,MAAM,CAAA;AACtG,MAAA,OAAO,IAAI,SAAS,IAAA,EAAM;AAAA,QACxB,MAAA,EAAQ,GAAA;AAAA,QACR,OAAA,EAAS;AAAA,UACP,YAAA,EAAc,CAAA,EAAG,UAAA,CAAW,OAAA,EAAS,OAAO,CAAC,CAAA,CAAA,EAAI,MAAM,CAAA,kBAAA,EAAqB,OAAO,CAAA,wBAAA,EAA2B,OAAA,CAAQ,OAAO,CAAA,GAAI,KAAK,UAAU,CAAA;AAAA;AAClJ,OACD,CAAA;AAAA,IACH,SAAS,KAAA,EAAO;AACd,MAAA,MAAM,SAAU,KAAA,CAA+B,MAAA;AAC/C,MAAA,IAAI,OAAO,WAAW,QAAA,EAAU,OAAO,IAAI,QAAA,CAAS,IAAA,EAAM,EAAE,MAAA,EAAQ,CAAA;AACpE,MAAA,OAAA,CAAQ,UAAU,KAAK,CAAA;AACvB,MAAA,OAAO,IAAI,QAAA,CAAS,IAAA,EAAM,EAAE,MAAA,EAAQ,KAAK,CAAA;AAAA,IAC3C;AAAA,EACF,CAAA;AACF;AAEA,IAAM,GAAA,GAAM,CAAC,MAAA,MAA2B;AAAA,EACtC,YAAY,MAAA,CAAO,SAAA;AAAA,EACnB,UAAA,EAAY,MAAA,CAAO,SAAA,CAAU,WAAA,EAAY;AAAA,EACzC,SAAS,MAAA,CAAO,OAAA;AAAA,EAChB,gBAAgB,MAAA,CAAO,aAAA;AAAA,EACvB,OAAA,EAAS,OAAO,MAAA,IAAU,IAAA;AAAA,EAC1B,UAAU,MAAA,CAAO;AACnB,CAAA,CAAA;AAKO,SAAS,cAAc,GAAA,EAA2B;AACvD,EAAA,OAAO;AAAA,IACL,MAAM,OAAO,MAAA,EAAQ;AACnB,MAAA,MAAM,CAAA,GAAI,IAAI,MAAM,CAAA;AAEpB,MAAA,MAAM,GAAA,CAAA;AAAA,gBAAA,EACM,EAAE,UAAU,CAAA,EAAA,EAAK,EAAE,UAAU,CAAA,eAAA,EAAkB,KAAK,SAAA,CAAU,CAAA,CAAE,OAAO,CAAC,CAAA,SAAA,EAAY,EAAE,cAAc,CAAA,EAAA,EAAK,EAAE,OAAO,CAAA,EAAA,EAAK,EAAE,QAAQ,CAAA,CAAA,CAAA;AAAA,IAC/I;AAAA,GACF;AACF;AAGO,SAAS,YAAA,CACd,KAAA,EACA,OAAA,GAA+B,EAAC,EAClB;AACd,EAAA,OAAO;AAAA,IACL,MAAM,OAAO,MAAA,EAAQ;AACnB,MAAA,MAAM,KAAA,CAAM,KAAA,CAAM,CAAA,EAAG,OAAA,CAAQ,MAAA,IAAU,UAAU,CAAA,EAAG,MAAA,CAAO,SAAS,CAAA,CAAA,EAAI,IAAA,CAAK,SAAA,CAAU,MAAM,CAAC,CAAA;AAAA,IAChG;AAAA,GACF;AACF;AAGO,SAAS,aAAA,CACd,QAAA,EACA,OAAA,GAA8B,EAAC,EACjB;AACd,EAAA,OAAO;AAAA,IACL,MAAM,OAAO,MAAA,EAAQ;AACnB,MAAA,MAAM,EAAE,KAAA,EAAM,GAAI,MAAM,QAAA,CAAS,IAAA,CAAK,OAAA,CAAQ,KAAA,IAAS,iBAAiB,CAAA,CAAE,MAAA,CAAO,GAAA,CAAI,MAAM,CAAC,CAAA;AAC5F,MAAA,IAAI,KAAA,QAAa,IAAI,KAAA,CAAM,yCAAyC,EAAE,KAAA,EAAO,OAAO,CAAA;AAAA,IACtF;AAAA,GACF;AACF;AAGO,SAAS,YAAY,KAAA,EAA+E;AACzG,EAAA,OAAO;AAAA,IACL,MAAM,OAAO,MAAA,EAAQ;AACnB,MAAA,MAAM,KAAA,CAAM,MAAA,CAAO,EAAE,IAAA,EAAM,EAAE,GAAG,MAAA,EAAQ,MAAA,EAAQ,MAAA,CAAO,MAAA,IAAU,IAAA,EAAK,EAAG,CAAA;AAAA,IAC3E;AAAA,GACF;AACF;AAGO,SAAS,YAAA,CACd,IACA,KAAA,EACc;AACd,EAAA,OAAO;AAAA,IACL,MAAM,OAAO,MAAA,EAAQ;AACnB,MAAA,MAAM,EAAA,CAAG,MAAA,CAAO,KAAK,CAAA,CAAE,MAAA,CAAO,EAAE,GAAG,MAAA,EAAQ,MAAA,EAAQ,MAAA,CAAO,MAAA,IAAU,IAAA,EAAM,CAAA;AAAA,IAC5E;AAAA,GACF;AACF;;;AC1LA,eAAsB,eAAA,CACpB,OACA,OAAA,EAC6B;AAC7B,EAAA,MAAM,OAAA,GAAU,QAAQ,YAAA,CAAa,MAAA;AAAA,IACnC,CAAC,WAAA,KACC,WAAA,KAAgB,MAAA,IAChB,KAAA,CAAM,QAAQ,WAAA,CAAY,eAAe,CAAA,KAAM,IAAA,KAC9C,CAAC,KAAA,CAAM,IAAA,IAAQ,MAAM,IAAA,CAAK,QAAA,CAAS,YAAY,EAAE,CAAA;AAAA,GACtD;AACA,EAAA,MAAM,OAAA,GAAU,MAAM,OAAA,CAAQ,UAAA,CAAW,QAAQ,GAAA,CAAI,CAAC,WAAA,KAAgB,WAAA,CAAY,IAAA,CAAK,KAAA,EAAO,OAAA,CAAQ,OAAO,CAAC,CAAC,CAAA;AAC/G,EAAA,OAAO,OAAA,CAAQ,GAAA,CAAI,CAAC,WAAA,EAAa,KAAA,KAAU;AACzC,IAAA,MAAM,MAAA,GAAS,QAAQ,KAAK,CAAA;AAC5B,IAAA,OAAO,QAAQ,MAAA,KAAW,UAAA,GACtB,EAAE,EAAA,EAAI,WAAA,CAAY,IAAI,EAAA,EAAI,KAAA,EAAO,KAAA,EAAO,MAAA,CAAO,QAAkB,GACjE,EAAE,IAAI,WAAA,CAAY,EAAA,EAAI,IAAI,IAAA,EAAK;AAAA,EACrC,CAAC,CAAA;AACH;AAGO,SAAS,YAAY,OAAA,EAAqC;AAC/D,EAAA,MAAM,UAAU,OAAA,CAAQ,OAAA;AACxB,EAAA,OAAO;AAAA,IACL,EAAA,EAAI,OAAA,CAAQ,GAAA,CAAI,iBAAiB,GAAG,KAAA,CAAM,GAAG,CAAA,CAAE,CAAC,GAAG,IAAA,EAAK,IAAK,OAAA,CAAQ,GAAA,CAAI,WAAW,CAAA,IAAK,MAAA;AAAA,IACzF,SAAA,EAAW,OAAA,CAAQ,GAAA,CAAI,YAAY,CAAA,IAAK,MAAA;AAAA,IACxC,SAAS,iBAAA,CAAkB,OAAA,CAAQ,GAAA,CAAI,QAAQ,KAAK,EAAE;AAAA,GACxD;AACF;AAEA,IAAM,QAAA,GAAW,CAAC,KAAA,KAChB,OAAO,KAAA,KAAU,QAAA,IAAY,KAAA,KAAU,IAAA,IAAQ,CAAC,KAAA,CAAM,OAAA,CAAQ,KAAK,CAAA;AACrE,IAAM,aAAa,CAAC,KAAA,EAAgB,UAAuC,KAAA,KAAU,MAAA,IAAa,MAAM,KAAK,CAAA;AAC7G,IAAM,QAAA,GAAW,CAAC,KAAA,KAAmB,OAAO,KAAA,KAAU,QAAA;AACtD,IAAM,aAAA,GAAgB,CAAC,KAAA,KAAmB,KAAA,CAAM,QAAQ,KAAK,CAAA,IAAK,KAAA,CAAM,KAAA,CAAM,QAAQ,CAAA;AAEtF,SAAS,iBAAiB,IAAA,EAA4B;AACpD,EAAA,MAAM,KAAA,GACJ,SAAS,IAAI,CAAA,IACb,OAAO,IAAA,CAAK,IAAA,KAAS,YACrB,kBAAA,CAAmB,IAAA,CAAK,KAAK,IAAI,CAAA,IACjC,SAAS,IAAA,CAAK,OAAO,KACrB,CAAC,aAAA,EAAe,WAAA,EAAa,WAAW,CAAA,CAAE,KAAA;AAAA,IACxC,CAAC,GAAA,KAAQ,OAAQ,IAAA,CAAK,OAAA,CAAoC,GAAG,CAAA,KAAM;AAAA,GACrE,IACA,UAAA,CAAW,IAAA,CAAK,EAAA,EAAI,QAAQ,CAAA,IAC5B,UAAA,CAAW,IAAA,CAAK,GAAA,EAAK,QAAQ,CAAA,IAC7B,UAAA,CAAW,KAAK,QAAA,EAAU,QAAQ,CAAA,IAClC,UAAA,CAAW,IAAA,CAAK,MAAA,EAAQ,QAAQ,CAAA,IAChC,UAAA,CAAW,IAAA,CAAK,SAAA,EAAW,QAAQ,CAAA,IACnC,UAAA,CAAW,IAAA,CAAK,IAAA,EAAM,aAAa,CAAA,IACnC,UAAA;AAAA,IACE,IAAA,CAAK,QAAA;AAAA,IACL,CAAC,QAAA,KAAa,QAAA,CAAS,QAAQ,KAAK,MAAA,CAAO,MAAA,CAAO,QAAQ,CAAA,CAAE,MAAM,CAAC,KAAA,KAAU,UAAA,CAAW,KAAA,EAAO,QAAQ,CAAC;AAAA,GAC1G;AACF,EAAA,IAAI,CAAC,KAAA,EAAO,MAAM,MAAA,CAAO,MAAA,CAAO,IAAI,KAAA,CAAM,eAAe,CAAA,EAAG,EAAE,MAAA,EAAQ,GAAA,EAAK,CAAA;AAC3E,EAAA,OAAO,IAAA;AACT;AAMO,SAAS,uBAAuB,OAAA,EAKK;AAC1C,EAAA,IAAI,OAAA,CAAQ,aAAA,IAAiB,CAAC,OAAA,CAAQ,cAAc,MAAA,EAAQ;AAC1D,IAAA,OAAA,CAAQ,KAAK,mFAAmF,CAAA;AAAA,EAClG;AACA,EAAA,MAAM,eAAe,OAAA,CAAQ,YAAA,CAAa,OAAO,CAAC,WAAA,KAAgB,aAAa,oBAAoB,CAAA;AACnG,EAAA,OAAO,OAAO,OAAA,KAAY;AACxB,IAAA,IAAI;AACF,MAAA,IAAI,QAAQ,aAAA,EAAe,MAAA,EAAQ,YAAA,CAAa,OAAA,CAAQ,cAAc,MAAM,CAAA;AAC5E,MAAA,gBAAA,CAAiB,OAAA,EAAS,EAAE,aAAA,EAAe,IAAA,EAAM,CAAA;AACjD,MAAA,IAAI,CAAC,YAAA,CAAa,MAAA;AAChB,QAAA,OAAO,IAAI,QAAA,CAAS,IAAA,EAAM,EAAE,MAAA,EAAQ,GAAA,EAAK,OAAA,EAAS,EAAE,yBAAA,EAA2B,KAAA,EAAM,EAAG,CAAA;AAC1F,MAAA,MAAM,QAAQ,gBAAA,CAAiB,MAAM,kBAAA,CAAmB,OAAA,EAAS,kBAAkB,CAAC,CAAA;AACpF,MAAA,MAAM,QAAQ,OAAA,CAAQ,aAAA,IAAkB,MAAM,iBAAA,CAAkB,OAAA,EAAS,QAAQ,aAAa,CAAA;AAC9F,MAAA,IAAI,QAAQ,aAAA,EAAe;AACzB,QAAA,MAAM,QAAA,GAAW,OAAO,OAAA,IAAW,EAAE,aAAa,KAAA,EAAO,SAAA,EAAW,KAAA,EAAO,SAAA,EAAW,KAAA,EAAM;AAC5F,QAAA,KAAA,CAAM,UAAU,MAAA,CAAO,WAAA;AAAA,UACrB,MAAA,CAAO,QAAQ,QAAQ,CAAA,CAAE,IAAI,CAAC,CAAC,GAAA,EAAK,KAAK,CAAA,KAAM;AAAA,YAC7C,GAAA;AAAA,YACA,KAAA,KAAU,IAAA,IAAQ,KAAA,CAAM,OAAA,CAAQ,GAAyB,CAAA,KAAM;AAAA,WAChE;AAAA,SACH;AAAA,MACF;AACA,MAAA,MAAM,OAAA,GAAU,MAAM,eAAA,CAAgB,KAAA,EAAO;AAAA,QAC3C,YAAA;AAAA,QACA,OAAA,EAAS,YAAY,OAAO;AAAA,OAC7B,CAAA;AACD,MAAA,KAAA,MAAW,MAAA,IAAU,SAAS,IAAI,CAAC,OAAO,EAAA,EAAI,OAAA,CAAQ,OAAA,GAAU,MAAA,CAAO,KAAK,CAAA;AAC5E,MAAA,OAAO,IAAI,SAAS,IAAA,EAAM;AAAA,QACxB,MAAA,EAAQ,GAAA;AAAA,QACR,OAAA,EAAS,QAAQ,aAAA,IAAiB,CAAC,QAAQ,EAAE,sBAAA,EAAwB,SAAA,EAAU,GAAI;AAAC,OACrF,CAAA;AAAA,IACH,SAAS,KAAA,EAAO;AACd,MAAA,MAAM,SAAU,KAAA,CAA+B,MAAA;AAC/C,MAAA,IAAI,OAAO,WAAW,QAAA,EAAU,OAAO,IAAI,QAAA,CAAS,IAAA,EAAM,EAAE,MAAA,EAAQ,CAAA;AACpE,MAAA,OAAA,CAAQ,UAAU,KAAK,CAAA;AACvB,MAAA,OAAO,IAAI,QAAA,CAAS,IAAA,EAAM,EAAE,MAAA,EAAQ,KAAK,CAAA;AAAA,IAC3C;AAAA,EACF,CAAA;AACF","file":"index.js","sourcesContent":["import type { ConsentCategories, ConsentState } from '../core/types'\nimport { BUILT_IN_CATEGORIES, normalizeConsent } from '../core/utils'\nimport { assertValidConsent } from '../core/validation'\nimport { assertSameOrigin, parseCookieHeader, parseJsonWithLimit } from './http'\n\n/** One row of the consent log. Rows are only ever appended. */\nexport type ConsentRecord = {\n consentId: string\n createdAt: Date\n consent: ConsentState\n policyVersion: string\n userId?: string\n /** Sent again by the browser after the consent cookie was lost, not chosen at `createdAt`. */\n restored: boolean\n}\n\n/** Where `createConsentHandler` appends records. */\nexport interface ConsentStore {\n append(record: ConsentRecord): Promise<void>\n}\n\n/** What the signed cookie proves: this browser's latest recorded choice. */\nexport type ConsentProof = {\n consentId: string\n consent: ConsentState\n policyVersion: string\n}\n\nexport type ConsentCookieOptions = {\n /** At least 32 characters, e.g. `process.env.CONSENT_SECRET`. */\n secret: string | undefined\n /** Default `__Host-consentlayer_proof`, or `consentlayer_proof` on `localhost`, `127.0.0.1` and `[::1]`. */\n cookie?: string\n}\n\n// Safari drops Secure cookies on http://localhost, and __Host- requires Secure.\nconst LOCAL_HOSTS = new Set(['localhost', '127.0.0.1', '[::1]'])\nconst isLocal = (request: Request) => LOCAL_HOSTS.has(new URL(request.url).hostname)\nconst cookieName = (request: Request, options: ConsentCookieOptions) =>\n options.cookie ?? (isLocal(request) ? 'consentlayer_proof' : '__Host-consentlayer_proof')\n// ponytail: browsers cap cookie lifetimes at 400 days\nconst MAX_AGE = 400 * 24 * 60 * 60\n\nconst encoder = new TextEncoder()\nconst toBase64Url = (bytes: Uint8Array) =>\n btoa(String.fromCharCode(...bytes))\n .replace(/\\+/g, '-')\n .replace(/\\//g, '_')\n .replace(/=+$/, '')\nconst fromBase64Url = (value: string) =>\n Uint8Array.from(atob(value.replace(/-/g, '+').replace(/_/g, '/')), (char) => char.charCodeAt(0))\n\nexport function assertSecret(secret: string | undefined): asserts secret is string {\n if (!secret) throw new Error('[consentlayer] the consent secret is not set')\n if (secret.length < 32) throw new Error('[consentlayer] the consent secret needs at least 32 characters')\n}\n\nconst hmacKey = (secret: string | undefined) => {\n assertSecret(secret)\n return crypto.subtle.importKey('raw', encoder.encode(secret), { name: 'HMAC', hash: 'SHA-256' }, false, [\n 'sign',\n 'verify',\n ])\n}\n\nasync function sign(proof: ConsentProof, secret: string): Promise<string> {\n const payload = toBase64Url(encoder.encode(JSON.stringify(proof)))\n const signature = await crypto.subtle.sign('HMAC', await hmacKey(secret), encoder.encode(payload))\n return `${payload}.${toBase64Url(new Uint8Array(signature))}`\n}\n\n/** The verified proof from the request's consent cookie, or `null` if it is missing or tampered with. */\nexport async function readConsentCookie(request: Request, options: ConsentCookieOptions): Promise<ConsentProof | null> {\n const value = parseCookieHeader(request.headers.get('cookie') ?? '')[cookieName(request, options)]\n const [payload, signature] = value?.split('.') ?? []\n if (!payload || !signature) return null\n try {\n const valid = await crypto.subtle.verify(\n 'HMAC',\n await hmacKey(options.secret),\n fromBase64Url(signature),\n encoder.encode(payload),\n )\n return valid ? (JSON.parse(new TextDecoder().decode(fromBase64Url(payload))) as ConsentProof) : null\n } catch {\n return null\n }\n}\n\n/**\n * A web-standard route handler for `createAnalytics({ server: { consentEndpoint } })`.\n * Validates each choice, appends a record and answers with the signed consent cookie\n * that `createAnalyticsHandler({ consentCookie })` trusts.\n */\nexport function createConsentHandler(\n options: ConsentCookieOptions & {\n store: ConsentStore\n /** The privacy policy the visitor saw, e.g. `'3.2'`. */\n policyVersion: string\n /** Your own categories, as passed to `createAnalytics({ categories })`. */\n categories?: (keyof ConsentCategories)[]\n /** The signed-in user, from your session. Never read from the request body. */\n userId?: (request: Request) => string | undefined | Promise<string | undefined>\n onError?: (error: unknown) => void\n },\n): (request: Request) => Promise<Response> {\n const categories = [...BUILT_IN_CATEGORIES, ...(options.categories ?? [])]\n return async (request) => {\n try {\n assertSecret(options.secret)\n assertSameOrigin(request, { requireHeader: true })\n const body = await parseJsonWithLimit<{ consent?: unknown; restored?: unknown }>(request, 4 * 1024)\n try {\n assertValidConsent(body?.consent, categories)\n } catch {\n return new Response(null, { status: 400 })\n }\n const consent = normalizeConsent(body.consent as ConsentState, categories)!\n const previous = await readConsentCookie(request, options)\n const consentId = previous?.consentId ?? crypto.randomUUID()\n const userId = await options.userId?.(request)\n await options.store.append({\n consentId,\n createdAt: new Date(),\n consent,\n policyVersion: options.policyVersion,\n ...(userId ? { userId } : {}),\n restored: body.restored === true,\n })\n const cookie = await sign({ consentId, consent, policyVersion: options.policyVersion }, options.secret)\n return new Response(null, {\n status: 204,\n headers: {\n 'set-cookie': `${cookieName(request, options)}=${cookie}; Path=/; Max-Age=${MAX_AGE}; HttpOnly; SameSite=Lax${isLocal(request) ? '' : '; Secure'}`,\n },\n })\n } catch (error) {\n const status = (error as { status?: unknown }).status\n if (typeof status === 'number') return new Response(null, { status })\n options.onError?.(error)\n return new Response(null, { status: 500 })\n }\n }\n}\n\nconst row = (record: ConsentRecord) => ({\n consent_id: record.consentId,\n created_at: record.createdAt.toISOString(),\n consent: record.consent,\n policy_version: record.policyVersion,\n user_id: record.userId ?? null,\n restored: record.restored,\n})\n\ntype SqlTag = (strings: TemplateStringsArray, ...values: unknown[]) => PromiseLike<unknown>\n\n/** Postgres through any `sql` tagged template: postgres.js, Neon or Vercel Postgres. */\nexport function postgresStore(sql: SqlTag): ConsentStore {\n return {\n async append(record) {\n const r = row(record)\n // ponytail: fixed table name, since tagged templates can't take identifiers portably\n await sql`insert into consent_records (consent_id, created_at, consent, policy_version, user_id, restored)\n values (${r.consent_id}, ${r.created_at}::timestamptz, ${JSON.stringify(r.consent)}::jsonb, ${r.policy_version}, ${r.user_id}, ${r.restored})`\n },\n }\n}\n\n/** Upstash Redis or Vercel KV: one list per consent ID, `consent:<consentId>`. */\nexport function upstashStore(\n redis: { rpush(key: string, ...values: string[]): Promise<unknown> },\n options: { prefix?: string } = {},\n): ConsentStore {\n return {\n async append(record) {\n await redis.rpush(`${options.prefix ?? 'consent:'}${record.consentId}`, JSON.stringify(record))\n },\n }\n}\n\n/** supabase-js. */\nexport function supabaseStore(\n supabase: { from(table: string): { insert(row: object): PromiseLike<{ error: unknown }> } },\n options: { table?: string } = {},\n): ConsentStore {\n return {\n async append(record) {\n const { error } = await supabase.from(options.table ?? 'consent_records').insert(row(record))\n if (error) throw new Error('[consentlayer] supabase insert failed', { cause: error })\n },\n }\n}\n\n/** Prisma: pass the model, e.g. `prismaStore(prisma.consentRecord)`. */\nexport function prismaStore(model: { create(args: { data: object }): PromiseLike<unknown> }): ConsentStore {\n return {\n async append(record) {\n await model.create({ data: { ...record, userId: record.userId ?? null } })\n },\n }\n}\n\n/** Drizzle: pass the database and the table. */\nexport function drizzleStore<Table>(\n db: { insert(table: Table): { values(row: object): PromiseLike<unknown> } },\n table: Table,\n): ConsentStore {\n return {\n async append(record) {\n await db.insert(table).values({ ...record, userId: record.userId ?? null })\n },\n }\n}\n","import type { ConsentState, ServerEvent } from '../core/types'\nimport { assertSecret, readConsentCookie, type ConsentCookieOptions } from './consent'\nimport { ANALYTICS_MAX_BODY, assertSameOrigin, parseCookieHeader, parseJsonWithLimit } from './http'\n\nexport type { ServerEvent }\n\n/** Request details destinations use: client IP, user agent and cookies. */\nexport type ServerRequestInfo = {\n ip?: string\n userAgent?: string\n cookies: Record<string, string>\n}\n\n/** A server-side destination. `send` throws when the vendor rejects the event. */\nexport interface ServerDestination<Forward extends boolean = boolean> {\n id: string\n requiredConsent: keyof ConsentState\n /** Whether `createAnalyticsHandler` may send browser events to it. */\n forwardBrowserEvents: Forward\n send(event: ServerEvent, request?: ServerRequestInfo): Promise<void>\n}\n\n/** Whether one destination accepted the event. */\nexport type ServerSendResult = { id: string; ok: boolean; error?: unknown }\n\n/** Sends to every destination the event's consent and `only` allow, in parallel. `undefined` entries are destinations that are off. */\nexport async function sendServerEvent(\n event: ServerEvent,\n options: { destinations: (ServerDestination | undefined)[]; request?: ServerRequestInfo },\n): Promise<ServerSendResult[]> {\n const targets = options.destinations.filter(\n (destination): destination is ServerDestination =>\n destination !== undefined &&\n event.consent[destination.requiredConsent] === true &&\n (!event.only || event.only.includes(destination.id)),\n )\n const results = await Promise.allSettled(targets.map((destination) => destination.send(event, options.request)))\n return targets.map((destination, index) => {\n const result = results[index]\n return result?.status === 'rejected'\n ? { id: destination.id, ok: false, error: result.reason as unknown }\n : { id: destination.id, ok: true }\n })\n}\n\n/** IP, user agent and cookies of a request, for `sendServerEvent({ request })`. */\nexport function requestInfo(request: Request): ServerRequestInfo {\n const headers = request.headers\n return {\n ip: headers.get('x-forwarded-for')?.split(',')[0]?.trim() || headers.get('x-real-ip') || undefined,\n userAgent: headers.get('user-agent') ?? undefined,\n cookies: parseCookieHeader(headers.get('cookie') ?? ''),\n }\n}\n\nconst isObject = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value)\nconst isOptional = (value: unknown, check: (value: unknown) => boolean) => value === undefined || check(value)\nconst isString = (value: unknown) => typeof value === 'string'\nconst isStringArray = (value: unknown) => Array.isArray(value) && value.every(isString)\n\nfunction parseServerEvent(body: unknown): ServerEvent {\n const valid =\n isObject(body) &&\n typeof body.name === 'string' &&\n /^[\\w.:-]{1,100}$/.test(body.name) &&\n isObject(body.consent) &&\n ['preferences', 'analytics', 'marketing'].every(\n (key) => typeof (body.consent as Record<string, unknown>)[key] === 'boolean',\n ) &&\n isOptional(body.id, isString) &&\n isOptional(body.url, isString) &&\n isOptional(body.clientId, isString) &&\n isOptional(body.params, isObject) &&\n isOptional(body.providers, isObject) &&\n isOptional(body.only, isStringArray) &&\n isOptional(\n body.identity,\n (identity) => isObject(identity) && Object.values(identity).every((value) => isOptional(value, isString)),\n )\n if (!valid) throw Object.assign(new Error('Invalid event'), { status: 400 })\n return body as ServerEvent\n}\n\n/**\n * A web-standard route handler for events forwarded by `createAnalytics({ server })`.\n * Only destinations that accept browser events (e.g. `metaCapi`) can be passed.\n */\nexport function createAnalyticsHandler(options: {\n destinations: (ServerDestination<true> | undefined)[]\n /** Grant only categories both the cookie `createConsentHandler` signs and the body grant; no valid cookie means denied. */\n consentCookie?: ConsentCookieOptions\n onError?: (error: unknown) => void\n}): (request: Request) => Promise<Response> {\n if (options.consentCookie && !options.consentCookie.secret) {\n console.warn('[consentlayer] consentCookie.secret is not set: every event is treated as denied.')\n }\n const destinations = options.destinations.filter((destination) => destination?.forwardBrowserEvents)\n return async (request) => {\n try {\n if (options.consentCookie?.secret) assertSecret(options.consentCookie.secret)\n assertSameOrigin(request, { requireHeader: true })\n if (!destinations.length)\n return new Response(null, { status: 204, headers: { 'consentlayer-forwarding': 'off' } })\n const event = parseServerEvent(await parseJsonWithLimit(request, ANALYTICS_MAX_BODY))\n const proof = options.consentCookie && (await readConsentCookie(request, options.consentCookie))\n if (options.consentCookie) {\n const recorded = proof?.consent ?? { preferences: false, analytics: false, marketing: false }\n event.consent = Object.fromEntries(\n Object.entries(recorded).map(([key, value]) => [\n key,\n value === true && event.consent[key as keyof ConsentState] === true,\n ]),\n ) as ConsentState\n }\n const results = await sendServerEvent(event, {\n destinations,\n request: requestInfo(request),\n })\n for (const result of results) if (!result.ok) options.onError?.(result.error)\n return new Response(null, {\n status: 204,\n headers: options.consentCookie && !proof ? { 'consentlayer-consent': 'missing' } : {},\n })\n } catch (error) {\n const status = (error as { status?: unknown }).status\n if (typeof status === 'number') return new Response(null, { status })\n options.onError?.(error)\n return new Response(null, { status: 500 })\n }\n }\n}\n"]}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { ServerDestination } from './index.js';
|
|
2
|
+
import '../types-BV_AFZKQ.js';
|
|
3
|
+
|
|
4
|
+
/** Meta Conversions API. Receives browser events too; Meta dedupes them by event ID. Off (`undefined`) while `pixelId` or `accessToken` is unset. */
|
|
5
|
+
declare function metaCapi(options: {
|
|
6
|
+
pixelId: string | undefined;
|
|
7
|
+
accessToken: string | undefined;
|
|
8
|
+
testEventCode?: string;
|
|
9
|
+
/** Graph API version. */
|
|
10
|
+
apiVersion?: string;
|
|
11
|
+
fetchImpl?: typeof fetch;
|
|
12
|
+
}): ServerDestination<true> | undefined;
|
|
13
|
+
|
|
14
|
+
export { metaCapi };
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { sha256 } from '../chunk-DK45XPEY.js';
|
|
2
|
+
import { toMetaEvent } from '../chunk-LBEIEAJX.js';
|
|
3
|
+
import { isValidPhoneE164, isValidEmail } from '../chunk-J3KUPF4R.js';
|
|
4
|
+
|
|
5
|
+
// src/server/meta.ts
|
|
6
|
+
var isPlainValue = (value) => value === null || ["string", "number", "boolean"].includes(typeof value) || Array.isArray(value) || typeof value === "object" && value.constructor === Object;
|
|
7
|
+
function metaCapi(options) {
|
|
8
|
+
const pixelId = options.pixelId?.trim();
|
|
9
|
+
const accessToken = options.accessToken?.trim();
|
|
10
|
+
if (!pixelId || !accessToken) {
|
|
11
|
+
if (pixelId || accessToken) console.warn("[consentlayer] metaCapi is off: set both pixelId and accessToken.");
|
|
12
|
+
return void 0;
|
|
13
|
+
}
|
|
14
|
+
const fetchImpl = options.fetchImpl ?? globalThis.fetch;
|
|
15
|
+
return {
|
|
16
|
+
id: "meta",
|
|
17
|
+
requiredConsent: "marketing",
|
|
18
|
+
forwardBrowserEvents: true,
|
|
19
|
+
async send(event, request) {
|
|
20
|
+
const { name, params } = toMetaEvent({
|
|
21
|
+
id: event.id ?? "",
|
|
22
|
+
name: event.name,
|
|
23
|
+
params: event.params ?? {},
|
|
24
|
+
options: { providers: event.providers }
|
|
25
|
+
});
|
|
26
|
+
const email = event.identity?.email?.trim().toLowerCase();
|
|
27
|
+
const phone = event.identity?.phone && `+${event.identity.phone.replace(/\D/g, "")}`;
|
|
28
|
+
const url = new URL(`https://graph.facebook.com/${options.apiVersion ?? "v26.0"}/${pixelId}/events`);
|
|
29
|
+
url.searchParams.set("access_token", accessToken);
|
|
30
|
+
if (options.testEventCode) url.searchParams.set("test_event_code", options.testEventCode);
|
|
31
|
+
const response = await fetchImpl(url.href, {
|
|
32
|
+
method: "POST",
|
|
33
|
+
headers: { "Content-Type": "application/json" },
|
|
34
|
+
body: JSON.stringify({
|
|
35
|
+
data: [
|
|
36
|
+
{
|
|
37
|
+
event_name: name,
|
|
38
|
+
event_time: Math.floor(Date.now() / 1e3),
|
|
39
|
+
event_id: event.id,
|
|
40
|
+
event_source_url: event.url,
|
|
41
|
+
action_source: "website",
|
|
42
|
+
custom_data: Object.fromEntries(Object.entries(params).filter(([, value]) => isPlainValue(value))),
|
|
43
|
+
user_data: {
|
|
44
|
+
client_ip_address: request?.ip,
|
|
45
|
+
client_user_agent: request?.userAgent,
|
|
46
|
+
fbp: request?.cookies._fbp,
|
|
47
|
+
fbc: request?.cookies._fbc,
|
|
48
|
+
em: email && isValidEmail(email) ? await sha256(email) : void 0,
|
|
49
|
+
ph: phone && isValidPhoneE164(phone) ? await sha256(phone.slice(1)) : void 0,
|
|
50
|
+
external_id: await sha256(event.identity?.userId)
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
]
|
|
54
|
+
})
|
|
55
|
+
});
|
|
56
|
+
if (!response.ok) throw new Error(`Meta CAPI ${response.status}: ${await response.text()}`);
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export { metaCapi };
|
|
62
|
+
//# sourceMappingURL=meta.js.map
|
|
63
|
+
//# sourceMappingURL=meta.js.map
|