@c15t/astro 3.0.0-alpha.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/AGENTS.md +86 -0
- package/SKILL.md +14 -0
- package/dist/api/handlers.js +91 -0
- package/dist/api/index.js +3 -0
- package/dist/api/init.js +10 -0
- package/dist/api/manifest-init.js +103 -0
- package/dist/api/manifest.js +10 -0
- package/dist/browser/iab.js +5 -0
- package/dist/browser/inline-scripts.js +47 -0
- package/dist/client.js +276 -0
- package/dist/index.js +5 -0
- package/dist/integration.js +170 -0
- package/dist/libs/cookies.js +9 -0
- package/dist/middleware-handler.js +31 -0
- package/dist/middleware.js +6 -0
- package/dist/mode.js +50 -0
- package/dist/server.js +291 -0
- package/dist/styles.css +2 -0
- package/dist/types.js +0 -0
- package/dist/ui/adapter.js +37 -0
- package/dist/ui/provider-props.js +12 -0
- package/dist/ui/react.js +34 -0
- package/dist/ui/svelte.js +36 -0
- package/dist/ui/vue.js +38 -0
- package/dist-types/api/handlers.d.ts +85 -0
- package/dist-types/api/index.d.ts +13 -0
- package/dist-types/api/init.d.ts +10 -0
- package/dist-types/api/manifest-init.d.ts +73 -0
- package/dist-types/api/manifest.d.ts +9 -0
- package/dist-types/browser/iab.d.ts +14 -0
- package/dist-types/browser/inline-scripts.d.ts +38 -0
- package/dist-types/client.d.ts +152 -0
- package/dist-types/index.d.ts +33 -0
- package/dist-types/integration.d.ts +53 -0
- package/dist-types/libs/cookies.d.ts +15 -0
- package/dist-types/middleware-handler.d.ts +35 -0
- package/dist-types/middleware.d.ts +20 -0
- package/dist-types/mode.d.ts +75 -0
- package/dist-types/server.d.ts +130 -0
- package/dist-types/types.d.ts +305 -0
- package/dist-types/ui/adapter.d.ts +122 -0
- package/dist-types/ui/provider-props.d.ts +37 -0
- package/dist-types/ui/react.d.ts +19 -0
- package/dist-types/ui/svelte.d.ts +21 -0
- package/dist-types/ui/vue.d.ts +19 -0
- package/docs/README.md +86 -0
- package/docs/assets/v3/brand-bar.png +0 -0
- package/docs/assets/v3/brand-card.png +0 -0
- package/docs/assets/v3/choice-wall.png +0 -0
- package/docs/assets/v3/mobile-card.png +0 -0
- package/docs/assets/v3/preferences.png +0 -0
- package/docs/customization/overview.md +45 -0
- package/docs/customization/recipes.md +79 -0
- package/docs/customization/slots.md +55 -0
- package/docs/customization/tokens.md +76 -0
- package/docs/customization/translations.md +49 -0
- package/docs/frameworks/astro/quickstart.md +186 -0
- package/docs/guides/consent-state.md +60 -0
- package/docs/guides/data-fetching.md +163 -0
- package/docs/guides/deployment-modes.md +63 -0
- package/docs/guides/troubleshooting.md +68 -0
- package/docs/guides/verify-consent.md +62 -0
- package/docs/integrations/adobe-analytics.md +294 -0
- package/docs/integrations/ahrefs-analytics.md +291 -0
- package/docs/integrations/amplitude.md +292 -0
- package/docs/integrations/building-integrations.md +47 -0
- package/docs/integrations/clearbit.md +291 -0
- package/docs/integrations/cloudflare-web-analytics.md +292 -0
- package/docs/integrations/crisp.md +294 -0
- package/docs/integrations/databuddy.md +311 -0
- package/docs/integrations/fathom-analytics.md +293 -0
- package/docs/integrations/google-maps.md +384 -0
- package/docs/integrations/google-tag-manager.md +306 -0
- package/docs/integrations/google-tag.md +305 -0
- package/docs/integrations/heap.md +292 -0
- package/docs/integrations/hightouch.md +293 -0
- package/docs/integrations/hotjar.md +292 -0
- package/docs/integrations/intercom.md +285 -0
- package/docs/integrations/linkedin-insights.md +291 -0
- package/docs/integrations/logrocket.md +293 -0
- package/docs/integrations/matomo-analytics.md +316 -0
- package/docs/integrations/meta-pixel.md +285 -0
- package/docs/integrations/microsoft-clarity.md +292 -0
- package/docs/integrations/microsoft-uet.md +291 -0
- package/docs/integrations/mixpanel-analytics.md +295 -0
- package/docs/integrations/openai-pixel.md +441 -0
- package/docs/integrations/overview.md +111 -0
- package/docs/integrations/pirsch.md +295 -0
- package/docs/integrations/plausible-analytics.md +295 -0
- package/docs/integrations/posthog.md +397 -0
- package/docs/integrations/promptwatch.md +291 -0
- package/docs/integrations/reddit-pixel.md +294 -0
- package/docs/integrations/rudderstack.md +328 -0
- package/docs/integrations/rybbit-analytics.md +296 -0
- package/docs/integrations/segment.md +292 -0
- package/docs/integrations/snapchat-pixel.md +293 -0
- package/docs/integrations/tiktok-pixel.md +291 -0
- package/docs/integrations/umami-analytics.md +295 -0
- package/docs/integrations/vercel-analytics.md +293 -0
- package/docs/integrations/x-pixel.md +291 -0
- package/docs/integrations/youtube.md +405 -0
- package/docs/upgrade-v3.md +334 -0
- package/package.json +184 -0
- package/src/components/branding.astro +152 -0
- package/src/components/consent-script.astro +34 -0
- package/src/components/iab-panel.astro +43 -0
- package/src/components/iab-prompt.astro +316 -0
- package/src/components/islands/iab-dialog-surface.svelte +15 -0
- package/src/components/islands/iab-dialog-surface.tsx +25 -0
- package/src/components/islands/iab-dialog-surface.vue +20 -0
- package/src/components/islands/panel-surface.svelte +57 -0
- package/src/components/islands/panel-surface.tsx +67 -0
- package/src/components/islands/panel-surface.vue +39 -0
- package/src/components/panel-trigger.astro +48 -0
- package/src/components/panel.astro +65 -0
- package/src/components/prompt-deferred.astro +54 -0
- package/src/components/prompt.astro +325 -0
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transport selection for `@c15t/astro`.
|
|
3
|
+
*
|
|
4
|
+
* Astro evaluates `astro.config.mjs` at build time, but the browser boot
|
|
5
|
+
* script is a string the integration injects. A transport factory cannot
|
|
6
|
+
* cross that boundary, so the integration takes a plain descriptor and both
|
|
7
|
+
* sides turn it into a {@link ProviderTransportFactory} themselves.
|
|
8
|
+
*/
|
|
9
|
+
import { custom } from '@c15t/core';
|
|
10
|
+
import type { ProviderTransportFactory } from '@c15t/core';
|
|
11
|
+
import type { C15tHostedDescriptor, C15tManifestDescriptor, C15tModeDescriptor, C15tOfflineDescriptor } from './types';
|
|
12
|
+
/**
|
|
13
|
+
* Talk to a c15t backend over HTTP.
|
|
14
|
+
*
|
|
15
|
+
* @param options - Backend URL and request options.
|
|
16
|
+
* @returns A serializable hosted-mode descriptor.
|
|
17
|
+
* @example
|
|
18
|
+
* ```ts
|
|
19
|
+
* import { c15t, hosted } from '@c15t/astro';
|
|
20
|
+
*
|
|
21
|
+
* export default defineConfig({
|
|
22
|
+
* integrations: [c15t({ mode: hosted({ url: 'https://consent.example.com' }) })],
|
|
23
|
+
* });
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
export declare const hostedMode: (options: Omit<C15tHostedDescriptor, "type">) => C15tHostedDescriptor;
|
|
27
|
+
/**
|
|
28
|
+
* Resolve policies locally with no backend.
|
|
29
|
+
*
|
|
30
|
+
* @param options - Explicit policy rules to resolve against.
|
|
31
|
+
* @returns A serializable offline-mode descriptor.
|
|
32
|
+
* @example
|
|
33
|
+
* ```ts
|
|
34
|
+
* c15t({ mode: offline() })
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
export declare const offlineMode: (options?: Omit<C15tOfflineDescriptor, "type">) => C15tOfflineDescriptor;
|
|
38
|
+
/**
|
|
39
|
+
* Resolve `/init` from a cached consent manifest.
|
|
40
|
+
*
|
|
41
|
+
* The server resolves it in-process; the browser goes through the injected
|
|
42
|
+
* `/api/c15t/init` route, so no manifest or translation bundle reaches the
|
|
43
|
+
* client.
|
|
44
|
+
*
|
|
45
|
+
* @param options - Manifest URL, inline manifest, or backend URL.
|
|
46
|
+
* @returns A serializable manifest-mode descriptor.
|
|
47
|
+
* @example
|
|
48
|
+
* ```ts
|
|
49
|
+
* c15t({ mode: manifest({ backendURL: process.env.C15T_BACKEND_URL }) })
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
52
|
+
export declare const manifestMode: (options?: Omit<C15tManifestDescriptor, "type">) => C15tManifestDescriptor;
|
|
53
|
+
/** Where the browser reaches manifest-resolved init data. */
|
|
54
|
+
export interface ManifestClientEndpoints {
|
|
55
|
+
/** Route that returns a resolved `InitOutput`. */
|
|
56
|
+
initPath: string;
|
|
57
|
+
/** Backend base URL used for `POST /subjects`. */
|
|
58
|
+
backendURL?: string;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Turn a mode descriptor into the transport factory a kernel needs.
|
|
62
|
+
*
|
|
63
|
+
* `manifest` resolves through `endpoints.initPath` so the browser never
|
|
64
|
+
* downloads a manifest or the full translation catalogue. Server code that
|
|
65
|
+
* wants in-process manifest resolution uses
|
|
66
|
+
* `createServerManifestFactory` from `@c15t/astro/server` instead.
|
|
67
|
+
*
|
|
68
|
+
* @param descriptor - The serialized mode descriptor.
|
|
69
|
+
* @param endpoints - Route paths used by `manifest` mode.
|
|
70
|
+
* @returns A transport factory for `createConsentKernel`.
|
|
71
|
+
* @throws {Error} When the descriptor carries an unknown `type`.
|
|
72
|
+
*/
|
|
73
|
+
export declare const resolveTransportFactory: (descriptor: C15tModeDescriptor, endpoints?: ManifestClientEndpoints) => ProviderTransportFactory;
|
|
74
|
+
/** Escape hatch for a caller-supplied transport. */
|
|
75
|
+
export { custom };
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import type { ConsentSnapshot, KernelConfig, KernelTranslations } from '@c15t/core';
|
|
2
|
+
import type { ConsentRequestHeaderInputs } from '@c15t/schema/types';
|
|
3
|
+
import type { C15tColorScheme, C15tLocals, C15tResolvedOptions } from './types';
|
|
4
|
+
/** Input for {@link resolveConsentContext}. */
|
|
5
|
+
export interface ResolveConsentContextOptions {
|
|
6
|
+
/** The incoming request headers. */
|
|
7
|
+
headers: Headers;
|
|
8
|
+
/**
|
|
9
|
+
* The absolute request URL. Used to resolve a relative `backendURL` or
|
|
10
|
+
* `manifestURL` against this request's own origin and protocol; without
|
|
11
|
+
* it the shared resolver assumes `https`.
|
|
12
|
+
*/
|
|
13
|
+
url?: string;
|
|
14
|
+
/** The integration options, already normalized. */
|
|
15
|
+
options: C15tResolvedOptions;
|
|
16
|
+
/** Override fetch, mainly for tests. */
|
|
17
|
+
fetch?: typeof globalThis.fetch;
|
|
18
|
+
/**
|
|
19
|
+
* Skip the server-side init roundtrip and return cookie + geo only.
|
|
20
|
+
* Useful for static output where every request shares one render.
|
|
21
|
+
*/
|
|
22
|
+
skipPrefetch?: boolean;
|
|
23
|
+
/**
|
|
24
|
+
* Receives the promise of a background manifest revalidation started by
|
|
25
|
+
* this render, so the host can keep it alive past the response on
|
|
26
|
+
* runtimes that stop detached work once a response is sent. The
|
|
27
|
+
* middleware passes the adapter's `waitUntil` from `locals.runtime.ctx`
|
|
28
|
+
* when there is one. The promise never rejects.
|
|
29
|
+
*/
|
|
30
|
+
onBackgroundRevalidate?: (revalidation: Promise<void>) => void;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Resolve the language the surfaces should render in.
|
|
34
|
+
*
|
|
35
|
+
* @param options - Integration options.
|
|
36
|
+
* @param inputs - Request inputs from the geo/language headers.
|
|
37
|
+
* @returns The kernel translations for this request.
|
|
38
|
+
*/
|
|
39
|
+
export declare const resolveTranslations: (options: C15tResolvedOptions, inputs: ConsentRequestHeaderInputs) => KernelTranslations;
|
|
40
|
+
/**
|
|
41
|
+
* Read cookies and geo headers into a baseline `KernelConfig`.
|
|
42
|
+
*
|
|
43
|
+
* Does no network work and sets no cookies, so it is safe on every runtime
|
|
44
|
+
* including static prerenders.
|
|
45
|
+
*
|
|
46
|
+
* @param headers - The incoming request headers.
|
|
47
|
+
* @param options - The integration options.
|
|
48
|
+
* @returns A config seeded with stored consent and request overrides.
|
|
49
|
+
*/
|
|
50
|
+
export declare const readInitialConsentConfig: (headers: Headers, options: C15tResolvedOptions) => {
|
|
51
|
+
config: KernelConfig;
|
|
52
|
+
inputs: ConsentRequestHeaderInputs;
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* Derive the kernel snapshot the server would hand a freshly booted page.
|
|
56
|
+
*
|
|
57
|
+
* Kernel construction is pure — no DOM, no network, no storage — so this is
|
|
58
|
+
* safe to run per request.
|
|
59
|
+
*
|
|
60
|
+
* @param config - The resolved kernel configuration.
|
|
61
|
+
* @returns The snapshot, including derived `activeUI` and policy UI hints.
|
|
62
|
+
*/
|
|
63
|
+
export declare const snapshotFromConfig: (config: KernelConfig) => ConsentSnapshot;
|
|
64
|
+
/**
|
|
65
|
+
* Resolve everything the page needs about consent for one request.
|
|
66
|
+
*
|
|
67
|
+
* Reads the consent cookie and the geo/GPC headers, prefetches the policy
|
|
68
|
+
* decision through the configured mode, and derives whether the banner
|
|
69
|
+
* should render server-side.
|
|
70
|
+
*
|
|
71
|
+
* @param input - Request headers and integration options.
|
|
72
|
+
* @returns The value the middleware stores on `Astro.locals.c15t`.
|
|
73
|
+
* @example
|
|
74
|
+
* ```ts
|
|
75
|
+
* const c15t = await resolveConsentContext({ headers: request.headers, options });
|
|
76
|
+
* ```
|
|
77
|
+
*/
|
|
78
|
+
export declare const resolveConsentContext: (input: ResolveConsentContextOptions) => Promise<C15tLocals>;
|
|
79
|
+
/**
|
|
80
|
+
* Build the inline `<script>` body that hands the browser its boot payload.
|
|
81
|
+
*
|
|
82
|
+
* The payload is the already-resolved `KernelConfig`, not a fetch: the
|
|
83
|
+
* browser starts with the same decision the server rendered, so there is no
|
|
84
|
+
* network init per page and no banner flicker.
|
|
85
|
+
*
|
|
86
|
+
* @param config - The resolved kernel configuration.
|
|
87
|
+
* @returns JavaScript safe for inline `<script>` injection.
|
|
88
|
+
*/
|
|
89
|
+
export declare const buildConfigScript: (config: KernelConfig) => string;
|
|
90
|
+
/**
|
|
91
|
+
* Build the first-paint colour-scheme script.
|
|
92
|
+
*
|
|
93
|
+
* Dark mode is the `c15t-dark` class on `<html>`, and the client boot sets
|
|
94
|
+
* it — but that runs after the stylesheet has already painted the
|
|
95
|
+
* server-rendered banner in the light palette. This runs in `<head>`,
|
|
96
|
+
* before the browser has anything to paint, so a system-dark visitor never
|
|
97
|
+
* sees the flash. It is deliberately framework-free and unbundled: a
|
|
98
|
+
* module script would be deferred and lose the race.
|
|
99
|
+
*
|
|
100
|
+
* `'light'` emits nothing. Light is the absence of the class, so there is
|
|
101
|
+
* nothing to do before paint.
|
|
102
|
+
*
|
|
103
|
+
* @param colorScheme - The resolved colour scheme.
|
|
104
|
+
* @returns Script source, or an empty string when none is needed.
|
|
105
|
+
* @example
|
|
106
|
+
* ```astro
|
|
107
|
+
* <script is:inline set:html={buildColorSchemeScript('system')} />
|
|
108
|
+
* ```
|
|
109
|
+
*/
|
|
110
|
+
export declare const buildColorSchemeScript: (colorScheme: C15tColorScheme) => string;
|
|
111
|
+
export { buildPrefetchScript } from '@c15t/core';
|
|
112
|
+
export type { KernelConfig } from '@c15t/core';
|
|
113
|
+
/**
|
|
114
|
+
* The per-request identity the emission guard keys off — in practice
|
|
115
|
+
* `Astro.locals`, which Astro creates fresh for every request.
|
|
116
|
+
*/
|
|
117
|
+
export interface ConfigEmissionScope {
|
|
118
|
+
c15t?: C15tLocals;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Claim the one-per-request emission of the inline config script.
|
|
122
|
+
*
|
|
123
|
+
* `<ConsentScript />` and `<ConsentBanner />` both want to inline the boot
|
|
124
|
+
* payload, and a page may well contain both. The first caller for a given
|
|
125
|
+
* request wins; every later caller renders nothing.
|
|
126
|
+
*
|
|
127
|
+
* @param locals - The current `Astro.locals`, used as the request identity.
|
|
128
|
+
* @returns `true` for the first caller of this request.
|
|
129
|
+
*/
|
|
130
|
+
export declare const markConfigEmitted: (locals: ConfigEmissionScope) => boolean;
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public option and locals types for `@c15t/astro`.
|
|
3
|
+
*
|
|
4
|
+
* Everything the integration accepts must survive `JSON.stringify`: the
|
|
5
|
+
* options are serialized once at build time into a virtual module that the
|
|
6
|
+
* middleware, the `.astro` components and the injected client boot script
|
|
7
|
+
* all import. Callbacks and other live values belong in the module named
|
|
8
|
+
* by {@link C15tAstroOptions.clientEntrypoint}.
|
|
9
|
+
*/
|
|
10
|
+
import type { AllConsentNames, ConsentSnapshot, ConsentPresentation, KernelConfig, LegalLinks, Script, StorageConfig } from '@c15t/core';
|
|
11
|
+
import type { PolicyRule, PolicyResolution, ConsentManifest, GlobalVendorList } from '@c15t/schema/types';
|
|
12
|
+
import type { Theme } from '@c15t/ui/theme';
|
|
13
|
+
/** Transport selection, in a form that survives serialization. */
|
|
14
|
+
export type C15tModeDescriptor = C15tHostedDescriptor | C15tOfflineDescriptor | C15tManifestDescriptor;
|
|
15
|
+
/** Talk to a c15t backend over HTTP. */
|
|
16
|
+
export interface C15tHostedDescriptor {
|
|
17
|
+
type: 'hosted';
|
|
18
|
+
/** Backend base URL. Absolute, or same-origin like `/api/c15t`. */
|
|
19
|
+
url: string;
|
|
20
|
+
/** Domain recorded when consent is saved. */
|
|
21
|
+
domain?: string;
|
|
22
|
+
/** Extra headers forwarded to the backend. */
|
|
23
|
+
headers?: Record<string, string>;
|
|
24
|
+
}
|
|
25
|
+
/** Resolve policies locally with no backend at all. */
|
|
26
|
+
export interface C15tOfflineDescriptor {
|
|
27
|
+
type: 'offline';
|
|
28
|
+
/** Policy packs resolved locally. */
|
|
29
|
+
policyRules?: PolicyRule[];
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Resolve `/init` from a cached consent manifest.
|
|
33
|
+
*
|
|
34
|
+
* The server resolves the manifest per request; the browser talks to the
|
|
35
|
+
* injected `/api/c15t/init` route, which is manifest-backed and cached.
|
|
36
|
+
*/
|
|
37
|
+
export interface C15tManifestDescriptor {
|
|
38
|
+
type: 'manifest';
|
|
39
|
+
/** `GET /manifest` URL. Defaults to `${backendURL}/manifest`. */
|
|
40
|
+
manifestURL?: string;
|
|
41
|
+
/** Backend base URL used for `POST /subjects`. */
|
|
42
|
+
backendURL?: string;
|
|
43
|
+
/** Inline manifest. Takes precedence over `manifestURL`. */
|
|
44
|
+
manifest?: ConsentManifest;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Which framework renders the on-demand dialog islands.
|
|
48
|
+
*
|
|
49
|
+
* Svelte is the default because it is the smallest: its runtime costs
|
|
50
|
+
* roughly 14 KB gzipped against React's ~45 KB. A site already shipping
|
|
51
|
+
* React or Vue should say so and reuse what it has instead of downloading
|
|
52
|
+
* a second framework for one dialog. The choice is never inferred — a
|
|
53
|
+
* silent change to what a page downloads is worse than an explicit one.
|
|
54
|
+
*/
|
|
55
|
+
export type C15tUIAdapterName = 'svelte' | 'react' | 'vue';
|
|
56
|
+
/**
|
|
57
|
+
* How the consent surfaces pick light or dark.
|
|
58
|
+
*
|
|
59
|
+
* Dark mode is the `c15t-dark` class on `<html>`, not a
|
|
60
|
+
* `prefers-color-scheme` block, so something has to set it. `'system'`
|
|
61
|
+
* follows `prefers-color-scheme` and keeps following it; `'light'` and
|
|
62
|
+
* `'dark'` pin it.
|
|
63
|
+
*/
|
|
64
|
+
export type C15tColorScheme = 'light' | 'dark' | 'system';
|
|
65
|
+
/** Route paths the integration can inject. */
|
|
66
|
+
export interface C15tEndpointOptions {
|
|
67
|
+
/**
|
|
68
|
+
* Inject `GET /api/c15t/init` and `GET /api/c15t/manifest`.
|
|
69
|
+
*
|
|
70
|
+
* Required for `mode: manifest()` unless you write the routes yourself.
|
|
71
|
+
*
|
|
72
|
+
* @default true when `mode.type === 'manifest'`, otherwise false
|
|
73
|
+
*/
|
|
74
|
+
enabled?: boolean;
|
|
75
|
+
/** @default '/api/c15t/init' */
|
|
76
|
+
initPath?: string;
|
|
77
|
+
/** @default '/api/c15t/manifest' */
|
|
78
|
+
manifestPath?: string;
|
|
79
|
+
}
|
|
80
|
+
/** How the integration registers its `pre`-order middleware. */
|
|
81
|
+
export interface C15tMiddlewareOptions {
|
|
82
|
+
/**
|
|
83
|
+
* Register `@c15t/astro/middleware` at all.
|
|
84
|
+
*
|
|
85
|
+
* @default true
|
|
86
|
+
*/
|
|
87
|
+
enabled?: boolean;
|
|
88
|
+
/**
|
|
89
|
+
* Extra path prefixes the middleware leaves alone.
|
|
90
|
+
*
|
|
91
|
+
* The integration's own init and manifest routes are always skipped, so
|
|
92
|
+
* this is only for routes of your own that must not resolve consent —
|
|
93
|
+
* health checks, webhooks, anything that would otherwise pay for a
|
|
94
|
+
* decision it never renders. A path matches when it is the pathname
|
|
95
|
+
* exactly or a parent segment of it, so `'/api'` covers `/api/health`.
|
|
96
|
+
*
|
|
97
|
+
* `Astro.locals.c15t` is left unset on a skipped route.
|
|
98
|
+
*
|
|
99
|
+
* @example ['/api/webhooks', '/healthz']
|
|
100
|
+
*/
|
|
101
|
+
skip?: string[];
|
|
102
|
+
}
|
|
103
|
+
/** Options accepted by the `c15t()` Astro integration. */
|
|
104
|
+
export interface C15tAstroOptions {
|
|
105
|
+
/** Host layout and styling constrained by the active policy. */
|
|
106
|
+
presentation?: ConsentPresentation;
|
|
107
|
+
/**
|
|
108
|
+
* Transport selection. Build it with `hosted()`, `offline()` or
|
|
109
|
+
* `manifest()` so the descriptor stays well-formed.
|
|
110
|
+
*/
|
|
111
|
+
mode: C15tModeDescriptor;
|
|
112
|
+
/** Categories offered in the banner and preference centre. */
|
|
113
|
+
consentCategories?: AllConsentNames[];
|
|
114
|
+
/** Consent-gated scripts handed to the core script loader. */
|
|
115
|
+
scripts?: Script[];
|
|
116
|
+
/**
|
|
117
|
+
* IAB TCF configuration. `false` disables it.
|
|
118
|
+
*
|
|
119
|
+
* Only the serializable fields are accepted here; a live GVL fetcher
|
|
120
|
+
* belongs in {@link C15tAstroOptions.clientEntrypoint}.
|
|
121
|
+
*/
|
|
122
|
+
iab?: C15tIABOptions | false;
|
|
123
|
+
/** Cookie/localStorage configuration for persisted consent. */
|
|
124
|
+
storageConfig?: StorageConfig;
|
|
125
|
+
/** Locale and message overrides. */
|
|
126
|
+
i18n?: C15tI18nOptions;
|
|
127
|
+
/** Theme tokens applied to the banner and dialog surfaces. */
|
|
128
|
+
theme?: Theme;
|
|
129
|
+
/**
|
|
130
|
+
* Light or dark for the banner and dialogs.
|
|
131
|
+
*
|
|
132
|
+
* `'system'` follows `prefers-color-scheme` and keeps following it as
|
|
133
|
+
* the visitor changes it. `<ConsentScript />` writes the class from a
|
|
134
|
+
* tiny inline script in `<head>`, so the server-rendered banner is
|
|
135
|
+
* already dark on its first paint rather than flashing light.
|
|
136
|
+
*
|
|
137
|
+
* @default 'system'
|
|
138
|
+
*/
|
|
139
|
+
colorScheme?: C15tColorScheme;
|
|
140
|
+
/** Legal links rendered inline in the banner and dialog. */
|
|
141
|
+
legalLinks?: LegalLinks;
|
|
142
|
+
/**
|
|
143
|
+
* Framework used to render the on-demand dialog islands.
|
|
144
|
+
*
|
|
145
|
+
* `'svelte'` ships the least JavaScript and is the default. Pick
|
|
146
|
+
* `'react'` or `'vue'` when the site already loads that runtime, so the
|
|
147
|
+
* dialog reuses it instead of adding a second framework. Whichever you
|
|
148
|
+
* pick, install the matching Astro integration — `@astrojs/svelte`,
|
|
149
|
+
* `@astrojs/react` or `@astrojs/vue` — and list it before `c15t()`.
|
|
150
|
+
*
|
|
151
|
+
* @default 'svelte'
|
|
152
|
+
*/
|
|
153
|
+
ui?: C15tUIAdapterName;
|
|
154
|
+
/** Injected API routes. */
|
|
155
|
+
endpoints?: C15tEndpointOptions | boolean;
|
|
156
|
+
/**
|
|
157
|
+
* Module specifier whose default export is a
|
|
158
|
+
* {@link C15tClientOptionsExtension}. Use it for anything that cannot be
|
|
159
|
+
* serialized — callbacks, a custom GVL fetcher, scripts with lifecycle
|
|
160
|
+
* hooks.
|
|
161
|
+
*
|
|
162
|
+
* @example './src/c15t.client.ts'
|
|
163
|
+
*/
|
|
164
|
+
clientEntrypoint?: string;
|
|
165
|
+
/**
|
|
166
|
+
* Register the `pre`-order middleware that populates `Astro.locals.c15t`.
|
|
167
|
+
*
|
|
168
|
+
* `false` is the same as `{ enabled: false }`. The middleware already
|
|
169
|
+
* skips the integration's own init and manifest routes, so a site that
|
|
170
|
+
* serves its own manifest does not have to hand-roll one to break the
|
|
171
|
+
* cycle; use `skip` to add routes of your own.
|
|
172
|
+
*
|
|
173
|
+
* @default true
|
|
174
|
+
*/
|
|
175
|
+
middleware?: boolean | C15tMiddlewareOptions;
|
|
176
|
+
/**
|
|
177
|
+
* Fail the build when the Astro integration for {@link C15tAstroOptions.ui}
|
|
178
|
+
* is missing. Set to `false` for banner-only sites, which render no
|
|
179
|
+
* island at all.
|
|
180
|
+
*
|
|
181
|
+
* @default true
|
|
182
|
+
*/
|
|
183
|
+
requireUIIntegration?: boolean;
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* IAB TCF options accepted by the integration.
|
|
187
|
+
*
|
|
188
|
+
* The serializable subset of the runtime's `RuntimeIABOptions`: a custom
|
|
189
|
+
* fetcher cannot survive the trip into the injected boot script, so that
|
|
190
|
+
* belongs in {@link C15tAstroOptions.clientEntrypoint} instead. A vendor
|
|
191
|
+
* list itself is plain JSON and does travel — see {@link C15tIABOptions.gvl}.
|
|
192
|
+
*/
|
|
193
|
+
export interface C15tIABOptions {
|
|
194
|
+
/** Set `false` to keep IAB configured but inert. */
|
|
195
|
+
enabled?: boolean;
|
|
196
|
+
/**
|
|
197
|
+
* A vendor list to use as-is, instead of fetching one.
|
|
198
|
+
*
|
|
199
|
+
* The server needs a GVL to render `<IABConsentBanner />` at all — the
|
|
200
|
+
* banner names the purposes and counts the vendors — so hosted and
|
|
201
|
+
* manifest mode get theirs from `/init`. Offline mode has no backend to
|
|
202
|
+
* ask, which is what this is for: a pinned list, or a fixture in a
|
|
203
|
+
* demo. It is inlined into the page's boot payload, so keep it trimmed
|
|
204
|
+
* to the vendors the site actually works with.
|
|
205
|
+
*/
|
|
206
|
+
gvl?: GlobalVendorList;
|
|
207
|
+
/** IAB-registered CMP ID. A hosted backend can supply it through `/init`. */
|
|
208
|
+
cmpId?: number;
|
|
209
|
+
/** CMP version reported through `__tcfapi`. */
|
|
210
|
+
cmpVersion?: number;
|
|
211
|
+
/** Restricts the vendor list to these vendor IDs. */
|
|
212
|
+
vendors?: number[];
|
|
213
|
+
/** Publisher country code used in the TC string. */
|
|
214
|
+
publisherCountryCode?: string;
|
|
215
|
+
/** Whether the CMP is service-specific rather than global. */
|
|
216
|
+
isServiceSpecific?: boolean;
|
|
217
|
+
/**
|
|
218
|
+
* Fetch the vendor list from this URL on the server.
|
|
219
|
+
*
|
|
220
|
+
* Goes through the shared in-process cache in `@c15t/core/server`, so
|
|
221
|
+
* concurrent renders collapse onto one download. Ignored when
|
|
222
|
+
* {@link C15tIABOptions.gvl} is set.
|
|
223
|
+
*/
|
|
224
|
+
gvlURL?: string;
|
|
225
|
+
}
|
|
226
|
+
/** Locale configuration accepted by the integration. */
|
|
227
|
+
export interface C15tI18nOptions {
|
|
228
|
+
/** Force a locale instead of negotiating `Accept-Language`. */
|
|
229
|
+
locale?: string;
|
|
230
|
+
/** Per-language message overrides, deep-merged over the defaults. */
|
|
231
|
+
messages?: Record<string, unknown>;
|
|
232
|
+
/**
|
|
233
|
+
* Negotiate the locale from the request's `Accept-Language` header.
|
|
234
|
+
*
|
|
235
|
+
* @default true
|
|
236
|
+
*/
|
|
237
|
+
detectLanguage?: boolean;
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Non-serializable additions merged over the integration options in the
|
|
241
|
+
* browser. Default-export this from
|
|
242
|
+
* {@link C15tAstroOptions.clientEntrypoint}.
|
|
243
|
+
*/
|
|
244
|
+
export interface C15tClientOptionsExtension {
|
|
245
|
+
scripts?: Script[];
|
|
246
|
+
callbacks?: Record<string, unknown>;
|
|
247
|
+
/** Merged over the serialized theme. */
|
|
248
|
+
theme?: Theme;
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* The serialized options shape shared by the middleware, the components and
|
|
252
|
+
* the client boot script.
|
|
253
|
+
*
|
|
254
|
+
* @internal
|
|
255
|
+
*/
|
|
256
|
+
export interface C15tResolvedOptions extends Omit<C15tAstroOptions, 'endpoints' | 'middleware' | 'requireUIIntegration'> {
|
|
257
|
+
ui: C15tUIAdapterName;
|
|
258
|
+
colorScheme: C15tColorScheme;
|
|
259
|
+
endpoints: Required<Omit<C15tEndpointOptions, 'enabled'>> & {
|
|
260
|
+
enabled: boolean;
|
|
261
|
+
};
|
|
262
|
+
middleware: Required<C15tMiddlewareOptions>;
|
|
263
|
+
}
|
|
264
|
+
/** Consent context the middleware attaches to every request. */
|
|
265
|
+
export interface C15tLocals {
|
|
266
|
+
/**
|
|
267
|
+
* Server-resolved kernel configuration. Inline it into the page with
|
|
268
|
+
* `<ConsentBanner />` or `buildConfigScript()` so the browser boots with
|
|
269
|
+
* no `/init` roundtrip.
|
|
270
|
+
*/
|
|
271
|
+
config: KernelConfig;
|
|
272
|
+
/**
|
|
273
|
+
* The kernel snapshot derived from {@link C15tLocals.config}. Components
|
|
274
|
+
* read translations, policy UI hints and consent state from here so the
|
|
275
|
+
* server and the browser agree on first paint.
|
|
276
|
+
*/
|
|
277
|
+
snapshot: ConsentSnapshot;
|
|
278
|
+
/** Whether the server decided this request should see the banner. */
|
|
279
|
+
shouldShowBanner: boolean;
|
|
280
|
+
/**
|
|
281
|
+
* Whether a policy rule is resolved for this request. Every c15t consent
|
|
282
|
+
* surface renders nothing until one is: an unconfigured, failed, or
|
|
283
|
+
* unmatched resolution leaves nothing to consent to, and the browser shows
|
|
284
|
+
* the surfaces on its own once a later init supplies a rule.
|
|
285
|
+
*/
|
|
286
|
+
hasPolicy: boolean;
|
|
287
|
+
/**
|
|
288
|
+
* Whether the resolved rule owes any consent UI. A prompt owes a banner
|
|
289
|
+
* and a preference center; rights owe a way back to preferences. A `none`
|
|
290
|
+
* rule with no rights owes neither, so no surface renders while the
|
|
291
|
+
* permissions it grants apply. `false` until a rule is resolved.
|
|
292
|
+
*/
|
|
293
|
+
hasConsentUi: boolean;
|
|
294
|
+
/** Resolved policy decision, when the transport produced one. */
|
|
295
|
+
decision: PolicyResolution;
|
|
296
|
+
/** Normalized request inputs (geo, language, GPC). */
|
|
297
|
+
inputs: {
|
|
298
|
+
country?: string;
|
|
299
|
+
region?: string;
|
|
300
|
+
language?: string;
|
|
301
|
+
gpc?: boolean;
|
|
302
|
+
};
|
|
303
|
+
/** The integration options, as the browser will receive them. */
|
|
304
|
+
options: C15tResolvedOptions;
|
|
305
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The dialog surface seam.
|
|
3
|
+
*
|
|
4
|
+
* The banner is server-rendered `.astro` with no framework at all, but the
|
|
5
|
+
* preference centre and the IAB dialog need real component state. Rather
|
|
6
|
+
* than hard-wiring one framework through the whole package, dialogs go
|
|
7
|
+
* through this adapter: `svelte`, `react` and `vue` each implement it, and
|
|
8
|
+
* a site downloads exactly the one its `ui` option names.
|
|
9
|
+
*
|
|
10
|
+
* Nothing framework-specific is named here, and the registry starts empty
|
|
11
|
+
* on purpose. A built-in entry pointing at the React adapter would make
|
|
12
|
+
* every build resolve `@c15t/react` and `react-dom`, which a Svelte-only
|
|
13
|
+
* site does not install. The integration knows `ui` at config time, so it
|
|
14
|
+
* registers the one adapter and the one surface into the page boot script;
|
|
15
|
+
* that is the only place a framework specifier appears.
|
|
16
|
+
*/
|
|
17
|
+
import type { ConsentRuntime } from '@c15t/core/runtime';
|
|
18
|
+
import type { C15tResolvedOptions, C15tUIAdapterName } from '../types';
|
|
19
|
+
/** Which dialog a caller asked for. */
|
|
20
|
+
export type ConsentDialogKind = 'preferences' | 'iab';
|
|
21
|
+
/** Context handed to an adapter when a dialog opens. */
|
|
22
|
+
export interface ConsentDialogContext {
|
|
23
|
+
/** The page-level runtime that owns the kernel. */
|
|
24
|
+
runtime: ConsentRuntime;
|
|
25
|
+
/** The resolved integration options. */
|
|
26
|
+
options: C15tResolvedOptions;
|
|
27
|
+
/** The element the surface mounts into. */
|
|
28
|
+
target: HTMLElement;
|
|
29
|
+
/** Which dialog to show. */
|
|
30
|
+
kind: ConsentDialogKind;
|
|
31
|
+
/**
|
|
32
|
+
* Which IAB preference-centre tab to open on. Ignored by the
|
|
33
|
+
* preferences dialog, which has no tabs.
|
|
34
|
+
*/
|
|
35
|
+
tab?: 'purposes' | 'vendors';
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Loads the component a dialog adapter mounts.
|
|
39
|
+
*
|
|
40
|
+
* The loader is registered by the `.astro` dialog component rather than
|
|
41
|
+
* imported here, so the specifier is resolved by the consuming app's build
|
|
42
|
+
* — which is the only build that knows how to compile a `.svelte` file —
|
|
43
|
+
* and the chunk stays out of the page until someone opens a dialog.
|
|
44
|
+
*/
|
|
45
|
+
export type ConsentDialogSurfaceLoader = () => Promise<{
|
|
46
|
+
default: unknown;
|
|
47
|
+
}>;
|
|
48
|
+
/**
|
|
49
|
+
* Register the component a dialog adapter should mount.
|
|
50
|
+
*
|
|
51
|
+
* @param name - The adapter the surface belongs to.
|
|
52
|
+
* @param load - Loader returning the surface module.
|
|
53
|
+
* @example
|
|
54
|
+
* ```ts
|
|
55
|
+
* registerDialogSurface('svelte', () =>
|
|
56
|
+
* import('@c15t/astro/islands/panel-surface.svelte')
|
|
57
|
+
* );
|
|
58
|
+
* ```
|
|
59
|
+
*/
|
|
60
|
+
export declare const registerDialogSurface: (name: C15tUIAdapterName, load: ConsentDialogSurfaceLoader) => void;
|
|
61
|
+
/**
|
|
62
|
+
* The registered surface loader for an adapter.
|
|
63
|
+
*
|
|
64
|
+
* @param name - The adapter name.
|
|
65
|
+
* @returns The loader.
|
|
66
|
+
* @throws {Error} When no surface has been registered for that adapter.
|
|
67
|
+
*/
|
|
68
|
+
export declare const requireDialogSurface: (name: C15tUIAdapterName) => ConsentDialogSurfaceLoader;
|
|
69
|
+
/** A mounted dialog surface. */
|
|
70
|
+
export interface ConsentDialogHandle {
|
|
71
|
+
/** Hide the dialog without destroying the surface. */
|
|
72
|
+
close: () => void;
|
|
73
|
+
/** Unmount and release everything. */
|
|
74
|
+
destroy: () => Promise<void> | void;
|
|
75
|
+
}
|
|
76
|
+
/** A dialog surface implementation. */
|
|
77
|
+
export interface ConsentDialogAdapter {
|
|
78
|
+
/** Adapter name, matching the `ui` integration option. */
|
|
79
|
+
readonly name: C15tUIAdapterName;
|
|
80
|
+
/**
|
|
81
|
+
* Mount the surface. Called on the first open only; later opens reuse
|
|
82
|
+
* the returned handle.
|
|
83
|
+
*
|
|
84
|
+
* @param context - Runtime, options and mount target.
|
|
85
|
+
* @returns A handle controlling the mounted surface.
|
|
86
|
+
*/
|
|
87
|
+
mount: (context: ConsentDialogContext) => Promise<ConsentDialogHandle>;
|
|
88
|
+
/**
|
|
89
|
+
* Warm the surface's chunks without mounting anything, so the first
|
|
90
|
+
* open does not wait on a download.
|
|
91
|
+
*/
|
|
92
|
+
preload?: () => Promise<void>;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Register a dialog surface implementation.
|
|
96
|
+
*
|
|
97
|
+
* @param name - The adapter name, matched against the `ui` option.
|
|
98
|
+
* @param load - Loader returning the adapter. Called at most once.
|
|
99
|
+
*/
|
|
100
|
+
export declare const registerDialogAdapter: (name: C15tUIAdapterName, load: () => Promise<ConsentDialogAdapter>) => void;
|
|
101
|
+
/**
|
|
102
|
+
* Empties both registries.
|
|
103
|
+
*
|
|
104
|
+
* The registries are module state, so a test that registers an adapter
|
|
105
|
+
* would otherwise decide what the next one sees.
|
|
106
|
+
*
|
|
107
|
+
* Tests only.
|
|
108
|
+
*
|
|
109
|
+
* @internal
|
|
110
|
+
*/
|
|
111
|
+
export declare const resetDialogRegistriesForTest: () => void;
|
|
112
|
+
/**
|
|
113
|
+
* Load the adapter named by the `ui` option.
|
|
114
|
+
*
|
|
115
|
+
* The import is dynamic so a page that never opens a dialog never
|
|
116
|
+
* downloads the framework behind it.
|
|
117
|
+
*
|
|
118
|
+
* @param name - The adapter name.
|
|
119
|
+
* @returns The adapter.
|
|
120
|
+
* @throws {Error} When no adapter is registered under that name.
|
|
121
|
+
*/
|
|
122
|
+
export declare const loadDialogAdapter: (name: C15tUIAdapterName) => Promise<ConsentDialogAdapter>;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Props for the Svelte dialog island.
|
|
3
|
+
*
|
|
4
|
+
* The page runtime owns the kernel and every side-effecting module, so the
|
|
5
|
+
* provider is handed it through `runtime` and neither starts nor disposes
|
|
6
|
+
* it. Everything else it gets here is presentation: categories, legal links
|
|
7
|
+
* and theme tokens the banner and dialog both read.
|
|
8
|
+
*/
|
|
9
|
+
import type { ConsentRuntime } from '@c15t/core/runtime';
|
|
10
|
+
import type { C15tResolvedOptions } from '../types';
|
|
11
|
+
/**
|
|
12
|
+
* The slice of the integration options a dialog island renders with.
|
|
13
|
+
*
|
|
14
|
+
* Named so every framework surface can state the same shape instead of
|
|
15
|
+
* widening to `Record<string, unknown>` and casting it back.
|
|
16
|
+
*/
|
|
17
|
+
export interface DialogPresentationOptions {
|
|
18
|
+
consentCategories?: C15tResolvedOptions['consentCategories'];
|
|
19
|
+
legalLinks?: C15tResolvedOptions['legalLinks'];
|
|
20
|
+
presentation?: C15tResolvedOptions['presentation'];
|
|
21
|
+
theme?: C15tResolvedOptions['theme'];
|
|
22
|
+
}
|
|
23
|
+
/** Props handed to `ConsentManagerProvider` by the Svelte dialog surface. */
|
|
24
|
+
export interface DialogProviderProps {
|
|
25
|
+
/** The page-level runtime. The provider borrows it, it does not own it. */
|
|
26
|
+
runtime: ConsentRuntime;
|
|
27
|
+
/** Presentation options forwarded to the provider. */
|
|
28
|
+
options: DialogPresentationOptions;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Build the provider props for a dialog island.
|
|
32
|
+
*
|
|
33
|
+
* @param runtime - The page runtime that owns the kernel.
|
|
34
|
+
* @param options - The resolved integration options.
|
|
35
|
+
* @returns Props for `ConsentManagerProvider`.
|
|
36
|
+
*/
|
|
37
|
+
export declare const buildProviderProps: (runtime: ConsentRuntime, options: C15tResolvedOptions) => DialogProviderProps;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The React dialog surface.
|
|
3
|
+
*
|
|
4
|
+
* For a site that already ships React, mounting the preference centre in
|
|
5
|
+
* React costs nothing new: the runtime is on the page already, and the
|
|
6
|
+
* dialog reuses it instead of pulling a second framework alongside it. A
|
|
7
|
+
* site that does not ship React should stay on the Svelte default, which
|
|
8
|
+
* is roughly a third of the runtime weight.
|
|
9
|
+
*
|
|
10
|
+
* Mounting happens on the first open through `mount()`, so the island's
|
|
11
|
+
* chunks are paid for only by visitors who click "Customize".
|
|
12
|
+
*
|
|
13
|
+
* The island itself is registered by the integration rather than imported
|
|
14
|
+
* here: a `.tsx` file can only be compiled by the consuming app's build,
|
|
15
|
+
* and only that build knows whether `@astrojs/react` is present.
|
|
16
|
+
*/
|
|
17
|
+
import type { ConsentDialogAdapter } from './adapter';
|
|
18
|
+
/** The React 18/19 dialog surface implementation. */
|
|
19
|
+
export declare const reactDialogAdapter: ConsentDialogAdapter;
|