@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,36 @@
|
|
|
1
|
+
import { requireDialogSurface } from "./adapter.js";
|
|
2
|
+
import { buildProviderProps } from "./provider-props.js";
|
|
3
|
+
const svelteDialogAdapter = {
|
|
4
|
+
async mount (context) {
|
|
5
|
+
const [{ mount, unmount }, surface] = await Promise.all([
|
|
6
|
+
import("svelte"),
|
|
7
|
+
requireDialogSurface('svelte')()
|
|
8
|
+
]);
|
|
9
|
+
const component = mount(surface.default, {
|
|
10
|
+
props: {
|
|
11
|
+
...buildProviderProps(context.runtime, context.options),
|
|
12
|
+
kind: context.kind,
|
|
13
|
+
tab: context.tab
|
|
14
|
+
},
|
|
15
|
+
target: context.target
|
|
16
|
+
});
|
|
17
|
+
return {
|
|
18
|
+
close () {
|
|
19
|
+
context.runtime.kernel.set.activeUI('none');
|
|
20
|
+
},
|
|
21
|
+
async destroy () {
|
|
22
|
+
await unmount(component, {
|
|
23
|
+
outro: true
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
},
|
|
28
|
+
name: 'svelte',
|
|
29
|
+
async preload () {
|
|
30
|
+
await Promise.all([
|
|
31
|
+
import("svelte"),
|
|
32
|
+
requireDialogSurface('svelte')()
|
|
33
|
+
]);
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
export { svelteDialogAdapter };
|
package/dist/ui/vue.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { requireDialogSurface } from "./adapter.js";
|
|
2
|
+
import { buildProviderProps } from "./provider-props.js";
|
|
3
|
+
const vueDialogAdapter = {
|
|
4
|
+
async mount (context) {
|
|
5
|
+
const [{ createApp }, { c15tVue }, surface] = await Promise.all([
|
|
6
|
+
import("vue"),
|
|
7
|
+
import("@c15t/vue/vue-plugin"),
|
|
8
|
+
requireDialogSurface('vue')()
|
|
9
|
+
]);
|
|
10
|
+
const { options, runtime } = buildProviderProps(context.runtime, context.options);
|
|
11
|
+
const app = createApp(surface.default, {
|
|
12
|
+
kind: context.kind,
|
|
13
|
+
tab: context.tab
|
|
14
|
+
});
|
|
15
|
+
app.use(c15tVue, {
|
|
16
|
+
...options,
|
|
17
|
+
runtime
|
|
18
|
+
});
|
|
19
|
+
app.mount(context.target);
|
|
20
|
+
return {
|
|
21
|
+
close () {
|
|
22
|
+
context.runtime.kernel.set.activeUI('none');
|
|
23
|
+
},
|
|
24
|
+
destroy () {
|
|
25
|
+
app.unmount();
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
},
|
|
29
|
+
name: 'vue',
|
|
30
|
+
async preload () {
|
|
31
|
+
await Promise.all([
|
|
32
|
+
import("vue"),
|
|
33
|
+
import("@c15t/vue/vue-plugin"),
|
|
34
|
+
requireDialogSurface('vue')()
|
|
35
|
+
]);
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
export { vueDialogAdapter };
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Route handlers for `/api/c15t/init` and `/api/c15t/manifest`.
|
|
3
|
+
*
|
|
4
|
+
* Manifest mode moves policy resolution off the browser's critical path:
|
|
5
|
+
* the host fetches one geo-independent, CDN-cacheable manifest and resolves
|
|
6
|
+
* `/init` locally per request. These are the Astro handlers for that — the
|
|
7
|
+
* same contract `createSvelteKitConsentRouteHandlers` and
|
|
8
|
+
* `createNextConsentRouteHandlers` implement.
|
|
9
|
+
*
|
|
10
|
+
* Cache discipline:
|
|
11
|
+
* - The manifest route forwards the backend's `Cache-Control`/`ETag`
|
|
12
|
+
* verbatim and answers `If-None-Match` with `304`. The edge caches it;
|
|
13
|
+
* this process only dedupes bursts (see `@c15t/core/server`).
|
|
14
|
+
* - The init route is per-request (geo, language, GPC) and therefore
|
|
15
|
+
* `private, no-store`.
|
|
16
|
+
*/
|
|
17
|
+
import type { ManifestFetch } from '@c15t/core/server';
|
|
18
|
+
import type { C15tResolvedOptions } from '../types';
|
|
19
|
+
import type { FetchGvl } from './manifest-init';
|
|
20
|
+
/**
|
|
21
|
+
* The per-request context a route or the middleware can pass so a
|
|
22
|
+
* background refresh is registered with the platform. Astro adapters that
|
|
23
|
+
* cancel detached work after the response expose `waitUntil` on
|
|
24
|
+
* `locals.runtime.ctx` (Cloudflare); anything else is left alone.
|
|
25
|
+
*/
|
|
26
|
+
export interface RequestLifetime {
|
|
27
|
+
locals?: unknown;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Hands a promise to the `waitUntil` an Astro adapter exposes on
|
|
31
|
+
* `locals.runtime.ctx` (Cloudflare), so a background refresh outlives the
|
|
32
|
+
* response on runtimes that would cancel it. A no-op where there is none.
|
|
33
|
+
*/
|
|
34
|
+
export declare const waitUntilFromLocals: (revalidation: Promise<void>, locals: unknown) => void;
|
|
35
|
+
/** Options accepted by the route handler factory. */
|
|
36
|
+
export interface ConsentRouteHandlerOptions {
|
|
37
|
+
/** The resolved integration options. */
|
|
38
|
+
options: C15tResolvedOptions;
|
|
39
|
+
/** Override fetch, mainly for tests. */
|
|
40
|
+
fetch?: ManifestFetch;
|
|
41
|
+
/**
|
|
42
|
+
* Fetches the Global Vendor List when the resolved policy is IAB.
|
|
43
|
+
* Defaults to a plain `GET` of the manifest's GVL reference.
|
|
44
|
+
*/
|
|
45
|
+
fetchGvl?: FetchGvl;
|
|
46
|
+
/**
|
|
47
|
+
* Receives the promise of a background manifest revalidation started by
|
|
48
|
+
* this request, so the host can keep it alive past the response on
|
|
49
|
+
* runtimes that stop detached work once a response is sent (a platform
|
|
50
|
+
* `waitUntil`, for example). The promise never rejects. Not called when
|
|
51
|
+
* the manifest is fresh or the request itself waits on the upstream.
|
|
52
|
+
*/
|
|
53
|
+
onBackgroundRevalidate?: (revalidation: Promise<void>) => void;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Work out where `GET /manifest` lives for this request.
|
|
57
|
+
*
|
|
58
|
+
* Explicit options win, then `C15T_MANIFEST_URL` / `C15T_BACKEND_URL`.
|
|
59
|
+
*
|
|
60
|
+
* @param request - The incoming request, used to resolve relative URLs.
|
|
61
|
+
* @param options - The resolved integration options.
|
|
62
|
+
* @returns An absolute manifest URL.
|
|
63
|
+
* @throws {Error} When neither a manifest URL nor a backend URL is configured.
|
|
64
|
+
*/
|
|
65
|
+
export declare const resolveManifestSourceURL: (request: Request, options: C15tResolvedOptions) => string;
|
|
66
|
+
/**
|
|
67
|
+
* Build the `init` and `manifest` route handlers.
|
|
68
|
+
*
|
|
69
|
+
* @param handlerOptions - Integration options plus test seams.
|
|
70
|
+
* @returns `init`, `manifest`, and a `GET` that dispatches between them.
|
|
71
|
+
* @example
|
|
72
|
+
* ```ts
|
|
73
|
+
* // src/pages/api/c15t/init.ts
|
|
74
|
+
* import options from 'virtual:c15t/options';
|
|
75
|
+
* import { createConsentRouteHandlers } from '@c15t/astro/api';
|
|
76
|
+
*
|
|
77
|
+
* const handlers = createConsentRouteHandlers({ options });
|
|
78
|
+
* export const GET = ({ request }) => handlers.init(request);
|
|
79
|
+
* ```
|
|
80
|
+
*/
|
|
81
|
+
export declare const createConsentRouteHandlers: (handlerOptions: ConsentRouteHandlerOptions) => {
|
|
82
|
+
GET: (request: Request) => Promise<Response>;
|
|
83
|
+
init: (request: Request, lifetime?: RequestLifetime) => Promise<Response>;
|
|
84
|
+
manifest: (request: Request, lifetime?: RequestLifetime) => Promise<Response>;
|
|
85
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@c15t/astro/api` — route handlers for the injected consent endpoints.
|
|
3
|
+
*
|
|
4
|
+
* The manifest cache itself lives in `@c15t/core/server`, shared with the
|
|
5
|
+
* Next.js, Nuxt and SvelteKit layers so no two hosts can disagree about
|
|
6
|
+
* cache lifetimes or revalidation.
|
|
7
|
+
*/
|
|
8
|
+
export { createConsentRouteHandlers, resolveManifestSourceURL, waitUntilFromLocals, } from './handlers';
|
|
9
|
+
export type { ConsentRouteHandlerOptions, RequestLifetime } from './handlers';
|
|
10
|
+
export { loadConsentManifest, resolveManifestInit, resolveManifestSourceFrom, } from './manifest-init';
|
|
11
|
+
export type { FetchGvl, RequestSource, ResolvedInitOutput, } from './manifest-init';
|
|
12
|
+
export { clearManifestCache, createManifestRequestURL, fetchCachedManifest, getManifestSMaxAge, getManifestStaleWhileRevalidate, MANIFEST_DEDUPE_TTL_SECONDS, MANIFEST_PASSTHROUGH_HEADERS, } from '@c15t/core/server';
|
|
13
|
+
export type { CachedManifestResponse, FetchCachedManifestOptions, ManifestFetch, ManifestSourceConfig, } from '@c15t/core/server';
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `GET /api/c15t/init` — injected by the integration in manifest mode.
|
|
3
|
+
*
|
|
4
|
+
* Resolves the consent decision for this request from the cached manifest,
|
|
5
|
+
* so the browser gets an `/init` payload without the backend being on the
|
|
6
|
+
* request path and without shipping the manifest to the client.
|
|
7
|
+
*/
|
|
8
|
+
import type { APIRoute } from 'astro';
|
|
9
|
+
export declare const GET: APIRoute;
|
|
10
|
+
export declare const prerender = false;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import type { ManifestFetch } from '@c15t/core/server';
|
|
2
|
+
import type { ConsentManifest, ConsentManifestGVLReference, ConsentRequestHeaderInputs, GlobalVendorList, InitOutput } from '@c15t/schema/types';
|
|
3
|
+
import type { C15tResolvedOptions } from '../types';
|
|
4
|
+
/** Fetches the Global Vendor List when the resolved policy is IAB. */
|
|
5
|
+
export type FetchGvl = (input: {
|
|
6
|
+
reference: ConsentManifestGVLReference;
|
|
7
|
+
language: string;
|
|
8
|
+
fetch: ManifestFetch;
|
|
9
|
+
}) => Promise<GlobalVendorList | null>;
|
|
10
|
+
/**
|
|
11
|
+
* The parts of a request URL resolution needs.
|
|
12
|
+
*
|
|
13
|
+
* The middleware has `Astro.locals`-adjacent `Headers` and a URL string; the
|
|
14
|
+
* route handlers have a whole `Request`. Both narrow to this.
|
|
15
|
+
*/
|
|
16
|
+
export interface RequestSource {
|
|
17
|
+
/** The absolute request URL, when the caller has one. */
|
|
18
|
+
url?: string;
|
|
19
|
+
/** The incoming request headers. */
|
|
20
|
+
headers: Headers;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Work out where `GET /manifest` lives for this request.
|
|
24
|
+
*
|
|
25
|
+
* Explicit options win, then `C15T_MANIFEST_URL` / `C15T_BACKEND_URL`.
|
|
26
|
+
*
|
|
27
|
+
* @param source - The request URL and headers, used to resolve relative URLs.
|
|
28
|
+
* @param options - The resolved integration options.
|
|
29
|
+
* @returns An absolute manifest URL.
|
|
30
|
+
* @throws {Error} When neither a manifest URL nor a backend URL is configured.
|
|
31
|
+
*/
|
|
32
|
+
export declare const resolveManifestSourceFrom: (source: RequestSource, options: C15tResolvedOptions) => string;
|
|
33
|
+
/**
|
|
34
|
+
* Load the manifest for this request through the shared in-process cache.
|
|
35
|
+
*
|
|
36
|
+
* An inline `manifest` short-circuits the network entirely; otherwise this
|
|
37
|
+
* is `fetchCachedManifest`, so concurrent requests collapse into one
|
|
38
|
+
* upstream call and later ones revalidate by `ETag` on the backend's
|
|
39
|
+
* schedule instead of re-downloading per render.
|
|
40
|
+
*
|
|
41
|
+
* @param input - Request source, integration options, and a fetch seam.
|
|
42
|
+
* @returns The tenant manifest.
|
|
43
|
+
* @throws {Error} When the manifest source cannot be resolved or the
|
|
44
|
+
* upstream responds non-2xx.
|
|
45
|
+
*/
|
|
46
|
+
export declare const loadConsentManifest: (input: {
|
|
47
|
+
source: RequestSource;
|
|
48
|
+
options: C15tResolvedOptions;
|
|
49
|
+
fetch?: ManifestFetch;
|
|
50
|
+
query?: string;
|
|
51
|
+
onBackgroundRevalidate?: (revalidation: Promise<void>) => void;
|
|
52
|
+
}) => Promise<ConsentManifest>;
|
|
53
|
+
/** Plain `GET` of the manifest's GVL reference. */
|
|
54
|
+
export declare const defaultFetchGvl: FetchGvl;
|
|
55
|
+
/** An `InitOutput` carrying the overrides the request implied. */
|
|
56
|
+
export type ResolvedInitOutput = InitOutput & {
|
|
57
|
+
resolvedOverrides?: Record<string, unknown>;
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* Resolve one request's `/init` payload from an already-loaded manifest.
|
|
61
|
+
*
|
|
62
|
+
* @param input - The manifest, the request inputs, and the GVL seams.
|
|
63
|
+
* @returns The resolved init payload, with `resolvedOverrides` echoed back.
|
|
64
|
+
* `gvl` is `null` when the vendor list could not be fetched.
|
|
65
|
+
*/
|
|
66
|
+
export declare const resolveManifestInit: (input: {
|
|
67
|
+
manifest: ConsentManifest;
|
|
68
|
+
inputs: ConsentRequestHeaderInputs;
|
|
69
|
+
fetch?: ManifestFetch;
|
|
70
|
+
/** Same-origin init route that serves versioned public lists. */
|
|
71
|
+
gvlRoute?: string;
|
|
72
|
+
fetchGvl?: FetchGvl;
|
|
73
|
+
}) => Promise<ResolvedInitOutput>;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `GET /api/c15t/manifest` — injected by the integration in manifest mode.
|
|
3
|
+
*
|
|
4
|
+
* Proxies the backend's manifest with its cache headers intact, so a CDN in
|
|
5
|
+
* front of the site can cache it exactly as the backend intended.
|
|
6
|
+
*/
|
|
7
|
+
import type { APIRoute } from 'astro';
|
|
8
|
+
export declare const GET: APIRoute;
|
|
9
|
+
export declare const prerender = false;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The page's lazy IAB factory.
|
|
3
|
+
*
|
|
4
|
+
* The runtime calls `createIAB` synchronously, but a static import would
|
|
5
|
+
* put the whole TCF implementation in the boot chunk of every page,
|
|
6
|
+
* including sites that never turn IAB on. `createLazyIABFactory` from
|
|
7
|
+
* `@c15t/core/runtime` keeps the synchronous call site and defers the
|
|
8
|
+
* bytes; {@link whenIABReady} closes the window between the two, and the
|
|
9
|
+
* client awaits it before mounting the IAB dialog.
|
|
10
|
+
*/
|
|
11
|
+
/** The `createIAB` the client hands the runtime. */
|
|
12
|
+
export declare const lazyCreateIAB: import("@c15t/core/runtime").ConsentRuntimeIABFactory;
|
|
13
|
+
/** Resolves once every pending `@c15t/iab` load has settled. */
|
|
14
|
+
export declare const whenIABReady: () => Promise<void>;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Consent gating for scripts already present in the HTML.
|
|
3
|
+
*
|
|
4
|
+
* Astro renders a lot of third-party embeds inline, and the core script
|
|
5
|
+
* loader only knows about scripts declared in configuration. This scanner
|
|
6
|
+
* covers the other half: mark a tag as inert and label it, and it runs the
|
|
7
|
+
* moment consent allows it.
|
|
8
|
+
*
|
|
9
|
+
* ```astro
|
|
10
|
+
* <script data-c15t-category="measurement" is:inline type="text/plain">
|
|
11
|
+
* console.log('only with measurement consent');
|
|
12
|
+
* </script>
|
|
13
|
+
* ```
|
|
14
|
+
*
|
|
15
|
+
* `is:inline` matters: without it Astro hoists the tag into a bundled
|
|
16
|
+
* module and runs it regardless of consent.
|
|
17
|
+
*
|
|
18
|
+
* `data-c15t-category` accepts one category name. The tag is left alone
|
|
19
|
+
* until consent is granted, then replaced by a live `<script>` in the same
|
|
20
|
+
* position. Revoking consent does not un-run a script that already
|
|
21
|
+
* executed, so the element is marked and skipped instead.
|
|
22
|
+
*/
|
|
23
|
+
import type { ConsentSnapshot } from '@c15t/core';
|
|
24
|
+
/** Attribute that marks a script for consent gating. */
|
|
25
|
+
export declare const CATEGORY_ATTRIBUTE = "data-c15t-category";
|
|
26
|
+
/** Attribute stamped on a gated script once it has been activated. */
|
|
27
|
+
export declare const ACTIVATED_ATTRIBUTE = "data-c15t-activated";
|
|
28
|
+
/**
|
|
29
|
+
* Activate every gated script the current consent state allows.
|
|
30
|
+
*
|
|
31
|
+
* Safe to call repeatedly — activated scripts are stamped and skipped, so
|
|
32
|
+
* re-running it after a ClientRouter navigation only picks up new tags.
|
|
33
|
+
*
|
|
34
|
+
* @param snapshot - The current kernel snapshot.
|
|
35
|
+
* @param root - Where to scan. Defaults to the whole document.
|
|
36
|
+
* @returns The number of scripts activated by this pass.
|
|
37
|
+
*/
|
|
38
|
+
export declare const activateGatedScripts: (snapshot: ConsentSnapshot, root?: ParentNode) => number;
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@c15t/astro/client` — the page-level consent runtime.
|
|
3
|
+
*
|
|
4
|
+
* Astro is an MPA and islands do not share a component tree, so there is
|
|
5
|
+
* nothing for a provider to hang off. Instead the integration injects a
|
|
6
|
+
* boot script that creates exactly one runtime per page; islands, plain
|
|
7
|
+
* `<script>` tags and any framework on the page all read that one object.
|
|
8
|
+
*
|
|
9
|
+
* The runtime survives ClientRouter navigation: the module is evaluated
|
|
10
|
+
* once and re-attaches to the swapped DOM on `astro:page-load` and
|
|
11
|
+
* `astro:after-swap`, so consent state does not reset when a visitor moves
|
|
12
|
+
* between pages.
|
|
13
|
+
*
|
|
14
|
+
* ```ts
|
|
15
|
+
* import { getConsentClient } from '@c15t/astro/client';
|
|
16
|
+
*
|
|
17
|
+
* const c15t = getConsentClient();
|
|
18
|
+
* c15t?.subscribe((snapshot) => console.log(snapshot.effectivePermissions));
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
import type { ConsentSnapshot, ConsentState, KernelUser, Unsubscribe } from '@c15t/core';
|
|
22
|
+
import type { ConsentRuntime } from '@c15t/core/runtime';
|
|
23
|
+
import type { C15tClientOptionsExtension, C15tResolvedOptions } from './types';
|
|
24
|
+
import type { ConsentDialogKind } from './ui/adapter';
|
|
25
|
+
/** Attribute the server-rendered banner puts on its action buttons. */
|
|
26
|
+
export declare const ACTION_ATTRIBUTE = "data-c15t-action";
|
|
27
|
+
/** Attribute selecting which dialog a `customize` action opens. */
|
|
28
|
+
export declare const DIALOG_ATTRIBUTE = "data-c15t-dialog";
|
|
29
|
+
/**
|
|
30
|
+
* Attribute selecting which tab the IAB preference centre opens on.
|
|
31
|
+
*
|
|
32
|
+
* The IAB banner's "N partners" link is a `customize` action that should
|
|
33
|
+
* land on the vendors tab rather than purposes, the way the React, Svelte
|
|
34
|
+
* and Vue banners do.
|
|
35
|
+
*/
|
|
36
|
+
export declare const DIALOG_TAB_ATTRIBUTE = "data-c15t-tab";
|
|
37
|
+
/** Actions the banner can trigger. */
|
|
38
|
+
export type ConsentAction = 'accept' | 'reject' | 'customize' | 'dismiss' | 'close';
|
|
39
|
+
/** The page-level consent client. */
|
|
40
|
+
export interface AstroConsentClient {
|
|
41
|
+
/** The runtime that owns this page's kernel. */
|
|
42
|
+
readonly runtime: ConsentRuntime;
|
|
43
|
+
/** The resolved integration options. */
|
|
44
|
+
readonly options: C15tResolvedOptions;
|
|
45
|
+
/** The current consent snapshot. */
|
|
46
|
+
getConsent: () => ConsentSnapshot;
|
|
47
|
+
/**
|
|
48
|
+
* Observe consent changes.
|
|
49
|
+
*
|
|
50
|
+
* @param listener - Called with every new snapshot.
|
|
51
|
+
* @returns An unsubscribe function.
|
|
52
|
+
*/
|
|
53
|
+
subscribe: (listener: (snapshot: ConsentSnapshot) => void) => Unsubscribe;
|
|
54
|
+
/**
|
|
55
|
+
* Open a dialog, mounting its island on first use.
|
|
56
|
+
*
|
|
57
|
+
* @param kind - `'preferences'` (default) or `'iab'`.
|
|
58
|
+
* @param tab - Which IAB preference-centre tab to land on. Ignored by
|
|
59
|
+
* the preferences dialog, which has no tabs.
|
|
60
|
+
*/
|
|
61
|
+
openDialog: (kind?: ConsentDialogKind, tab?: 'purposes' | 'vendors') => Promise<void>;
|
|
62
|
+
/** Close the open dialog. */
|
|
63
|
+
closeDialog: () => void;
|
|
64
|
+
/** Accept every configured category. */
|
|
65
|
+
acceptAll: () => Promise<void>;
|
|
66
|
+
/** Reject everything but strictly necessary. */
|
|
67
|
+
rejectAll: () => Promise<void>;
|
|
68
|
+
/**
|
|
69
|
+
* Save a specific set of consents.
|
|
70
|
+
*
|
|
71
|
+
* @param consents - The categories to persist.
|
|
72
|
+
*/
|
|
73
|
+
save: (consents: Partial<ConsentState>) => Promise<void>;
|
|
74
|
+
/**
|
|
75
|
+
* Associate this consent record with an external identity.
|
|
76
|
+
*
|
|
77
|
+
* @param user - The external user.
|
|
78
|
+
*/
|
|
79
|
+
identify: (user: KernelUser) => Promise<void>;
|
|
80
|
+
/** Tear the runtime down. Mainly for tests. */
|
|
81
|
+
dispose: () => void;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Show or hide the persistent consent controls the page rendered on the
|
|
85
|
+
* server (`<ConsentDialogTrigger />`) as the policy resolution changes.
|
|
86
|
+
*
|
|
87
|
+
* With nothing to manage the controls stay hidden: before a rule resolves,
|
|
88
|
+
* and under a `none` rule that owes no rights. They appear on their own once
|
|
89
|
+
* a later init supplies a rule that does.
|
|
90
|
+
*
|
|
91
|
+
* @param snapshot - The current kernel snapshot.
|
|
92
|
+
*/
|
|
93
|
+
export declare const syncSurfaceVisibility: (snapshot: ConsentSnapshot) => void;
|
|
94
|
+
export declare const syncBannerVisibility: (snapshot: ConsentSnapshot) => void;
|
|
95
|
+
/**
|
|
96
|
+
* The page's consent client, if the integration has booted.
|
|
97
|
+
*
|
|
98
|
+
* @returns The client, or `null` outside the browser or before boot.
|
|
99
|
+
*/
|
|
100
|
+
export declare const getConsentClient: () => AstroConsentClient | null;
|
|
101
|
+
/**
|
|
102
|
+
* Wire the delegated handler for the server-rendered banner's buttons.
|
|
103
|
+
*
|
|
104
|
+
* The banner ships zero framework JavaScript: the buttons carry
|
|
105
|
+
* `data-c15t-action` and one document-level listener turns them into
|
|
106
|
+
* runtime calls. Calling this more than once is a no-op.
|
|
107
|
+
*/
|
|
108
|
+
export declare const attachBannerActions: () => void;
|
|
109
|
+
/**
|
|
110
|
+
* Create the page's consent runtime, or return the existing one.
|
|
111
|
+
*
|
|
112
|
+
* The integration calls this from the script it injects into every page;
|
|
113
|
+
* application code uses {@link getConsentClient} instead.
|
|
114
|
+
*
|
|
115
|
+
* @param options - The serialized integration options.
|
|
116
|
+
* @param extension - Non-serializable additions from `clientEntrypoint`.
|
|
117
|
+
* @returns The page-level consent client.
|
|
118
|
+
*/
|
|
119
|
+
export declare const boot: (options: C15tResolvedOptions, extension?: C15tClientOptionsExtension) => AstroConsentClient;
|
|
120
|
+
/**
|
|
121
|
+
* The current consent snapshot.
|
|
122
|
+
*
|
|
123
|
+
* @returns The snapshot, or `null` before boot.
|
|
124
|
+
*/
|
|
125
|
+
export declare const getConsent: () => ConsentSnapshot | null;
|
|
126
|
+
/**
|
|
127
|
+
* Observe consent changes.
|
|
128
|
+
*
|
|
129
|
+
* @param listener - Called with every new snapshot.
|
|
130
|
+
* @returns An unsubscribe function. A no-op before boot.
|
|
131
|
+
*/
|
|
132
|
+
export declare const subscribe: (listener: (snapshot: ConsentSnapshot) => void) => Unsubscribe;
|
|
133
|
+
/**
|
|
134
|
+
* Open a consent dialog.
|
|
135
|
+
*
|
|
136
|
+
* @param kind - `'preferences'` (default) or `'iab'`.
|
|
137
|
+
* @param tab - Which IAB preference-centre tab to open on.
|
|
138
|
+
*/
|
|
139
|
+
export declare const openDialog: (kind?: ConsentDialogKind, tab?: "purposes" | "vendors") => Promise<void>;
|
|
140
|
+
/**
|
|
141
|
+
* Download the dialog surface's chunks without mounting it.
|
|
142
|
+
*
|
|
143
|
+
* Use it from an idle callback when the first open needs to feel instant;
|
|
144
|
+
* skip it when you would rather not spend the bytes on visitors who never
|
|
145
|
+
* open the preference centre.
|
|
146
|
+
*/
|
|
147
|
+
export declare const preloadDialog: () => Promise<void>;
|
|
148
|
+
export { activateGatedScripts } from './browser/inline-scripts';
|
|
149
|
+
export type { ConsentRuntime } from '@c15t/core/runtime';
|
|
150
|
+
export type { ConsentDialogKind } from './ui/adapter';
|
|
151
|
+
export { registerDialogAdapter, registerDialogSurface } from './ui/adapter';
|
|
152
|
+
export type { ConsentDialogAdapter, ConsentDialogContext, ConsentDialogHandle, ConsentDialogSurfaceLoader, } from './ui/adapter';
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@c15t/astro` — consent management for Astro.
|
|
3
|
+
*
|
|
4
|
+
* The banner is a server-rendered `.astro` component with no framework
|
|
5
|
+
* JavaScript; the preference centre and the IAB dialog are Svelte islands
|
|
6
|
+
* mounted only when someone opens them; and because Astro islands never
|
|
7
|
+
* share a component tree, the kernel is a page-level singleton created by
|
|
8
|
+
* the script this integration injects rather than a provider.
|
|
9
|
+
*
|
|
10
|
+
* ```js
|
|
11
|
+
* // astro.config.mjs
|
|
12
|
+
* import { defineConfig } from 'astro/config';
|
|
13
|
+
* import svelte from '@astrojs/svelte';
|
|
14
|
+
* import c15t, { hosted } from '@c15t/astro';
|
|
15
|
+
*
|
|
16
|
+
* export default defineConfig({
|
|
17
|
+
* output: 'server',
|
|
18
|
+
* integrations: [svelte(), c15t({ mode: hosted({ url: '/api/c15t' }) })],
|
|
19
|
+
* });
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
export { c15t, c15t as default, resolveOptions } from './integration';
|
|
23
|
+
export { createConsentMiddleware } from './middleware-handler';
|
|
24
|
+
export type { ConsentMiddlewareOptions } from './middleware-handler';
|
|
25
|
+
export { hostedMode as hosted, manifestMode as manifest, offlineMode as offline, resolveTransportFactory, custom, } from './mode';
|
|
26
|
+
export type { ManifestClientEndpoints } from './mode';
|
|
27
|
+
export type { C15tAstroOptions, C15tClientOptionsExtension, C15tColorScheme, C15tEndpointOptions, C15tHostedDescriptor, C15tI18nOptions, C15tIABOptions, C15tLocals, C15tManifestDescriptor, C15tMiddlewareOptions, C15tModeDescriptor, C15tOfflineDescriptor, C15tResolvedOptions, C15tUIAdapterName, } from './types';
|
|
28
|
+
export type { ConsentDialogAdapter, ConsentDialogContext, ConsentDialogHandle, ConsentDialogKind, ConsentDialogSurfaceLoader, } from './ui/adapter';
|
|
29
|
+
export { registerDialogAdapter, registerDialogSurface } from './ui/adapter';
|
|
30
|
+
export type { ConsentRuntime, ConsentRuntimeOptions } from '@c15t/core/runtime';
|
|
31
|
+
export type { AllConsentNames, ConsentSnapshot, ConsentState, KernelConfig, LegalLinks, PolicyRule, Script, StorageConfig, } from '@c15t/core';
|
|
32
|
+
export { policyRulePresets } from '@c15t/core';
|
|
33
|
+
export type { Theme } from '@c15t/ui/theme';
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `c15t()` Astro integration.
|
|
3
|
+
*
|
|
4
|
+
* It wires four things into an Astro app:
|
|
5
|
+
*
|
|
6
|
+
* 1. A `pre`-order middleware that resolves consent for every request into
|
|
7
|
+
* `Astro.locals.c15t`.
|
|
8
|
+
* 2. A page-level boot script that creates the one consent runtime the page
|
|
9
|
+
* shares — Astro islands never share a component tree, so the runtime is
|
|
10
|
+
* a page singleton rather than a provider.
|
|
11
|
+
* 3. Optional `/api/c15t/init` and `/api/c15t/manifest` routes for
|
|
12
|
+
* `manifest` mode, with the same semantics as `@c15t/nextjs/api`.
|
|
13
|
+
* 4. A virtual module (`virtual:c15t/options`) carrying the serialized
|
|
14
|
+
* options to all of the above.
|
|
15
|
+
*/
|
|
16
|
+
import type { AstroIntegration } from 'astro';
|
|
17
|
+
import type { C15tAstroOptions, C15tResolvedOptions } from './types';
|
|
18
|
+
/**
|
|
19
|
+
* Normalize user options into the serializable shape every consumer reads.
|
|
20
|
+
*
|
|
21
|
+
* @param options - The options passed to `c15t()`.
|
|
22
|
+
* @returns Options with defaults applied.
|
|
23
|
+
* @throws {Error} When `mode` is missing, is not a mode descriptor, or is a
|
|
24
|
+
* manifest mode with nowhere to save consent.
|
|
25
|
+
*/
|
|
26
|
+
export declare const resolveOptions: (options: C15tAstroOptions) => C15tResolvedOptions;
|
|
27
|
+
/**
|
|
28
|
+
* Create the c15t Astro integration.
|
|
29
|
+
*
|
|
30
|
+
* @param options - Consent configuration for the site.
|
|
31
|
+
* @returns The Astro integration to list in `astro.config.mjs`.
|
|
32
|
+
* @throws {Error} When `mode` is missing, or when the Astro integration for
|
|
33
|
+
* the configured `ui` is not listed in `astro.config`.
|
|
34
|
+
* @example
|
|
35
|
+
* ```js
|
|
36
|
+
* import { defineConfig } from 'astro/config';
|
|
37
|
+
* import svelte from '@astrojs/svelte';
|
|
38
|
+
* import c15t, { hosted } from '@c15t/astro';
|
|
39
|
+
*
|
|
40
|
+
* export default defineConfig({
|
|
41
|
+
* output: 'server',
|
|
42
|
+
* integrations: [
|
|
43
|
+
* svelte(),
|
|
44
|
+
* c15t({
|
|
45
|
+
* mode: hosted({ url: 'https://consent.example.com' }),
|
|
46
|
+
* consentCategories: ['necessary', 'measurement', 'marketing'],
|
|
47
|
+
* }),
|
|
48
|
+
* ],
|
|
49
|
+
* });
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
52
|
+
export declare const c15t: (options: C15tAstroOptions) => AstroIntegration;
|
|
53
|
+
export default c15t;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cookie helpers for the server-side consent fetches.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Keeps only the named cookies from a `Cookie` request header.
|
|
6
|
+
*
|
|
7
|
+
* The server-side `/init` call replays the visitor's cookies at the consent
|
|
8
|
+
* backend. Replaying the whole header would hand a third-party host every
|
|
9
|
+
* session and auth cookie the site owns, so only the consent cookie travels.
|
|
10
|
+
*
|
|
11
|
+
* @param cookieHeader - The incoming `Cookie` header value.
|
|
12
|
+
* @param names - Cookie names to keep.
|
|
13
|
+
* @returns The filtered header, or `undefined` when nothing remains.
|
|
14
|
+
*/
|
|
15
|
+
export declare const filterCookieHeader: (cookieHeader: string, names: readonly string[]) => string | undefined;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The middleware factory behind `@c15t/astro/middleware`.
|
|
3
|
+
*
|
|
4
|
+
* Kept separate from the entrypoint so it can be constructed with explicit
|
|
5
|
+
* options — the entrypoint binds it to the build-time virtual module, and
|
|
6
|
+
* tests and advanced setups bind it to their own.
|
|
7
|
+
*/
|
|
8
|
+
import type { MiddlewareHandler } from 'astro';
|
|
9
|
+
import type { C15tResolvedOptions } from './types';
|
|
10
|
+
/** Options for {@link createConsentMiddleware}. */
|
|
11
|
+
export interface ConsentMiddlewareOptions {
|
|
12
|
+
/** Override fetch, mainly for tests. */
|
|
13
|
+
fetch?: typeof globalThis.fetch;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Build the `pre`-order middleware that populates `Astro.locals.c15t`.
|
|
17
|
+
*
|
|
18
|
+
* It reads the consent cookie and the geo/GPC headers, resolves the policy
|
|
19
|
+
* decision through the configured mode, and leaves the result on locals so
|
|
20
|
+
* the components render the right thing on the server and the browser boots
|
|
21
|
+
* without an `/init` roundtrip.
|
|
22
|
+
*
|
|
23
|
+
* A prerendered route is skipped: there is no per-visitor request to read,
|
|
24
|
+
* and resolving one would bake one visitor's geo into a shared HTML file.
|
|
25
|
+
* Use `<ConsentBannerDeferred />` when a cached page still needs live geo.
|
|
26
|
+
*
|
|
27
|
+
* So are the integration's own init and manifest routes, and anything
|
|
28
|
+
* listed in `middleware.skip` — those run `next()` with `Astro.locals.c15t`
|
|
29
|
+
* left unset.
|
|
30
|
+
*
|
|
31
|
+
* @param options - The resolved integration options.
|
|
32
|
+
* @param middlewareOptions - Test seams.
|
|
33
|
+
* @returns An Astro middleware handler.
|
|
34
|
+
*/
|
|
35
|
+
export declare const createConsentMiddleware: (options: C15tResolvedOptions, middlewareOptions?: ConsentMiddlewareOptions) => MiddlewareHandler;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Astro middleware entrypoint for `@c15t/astro`.
|
|
3
|
+
*
|
|
4
|
+
* The integration registers this with `order: 'pre'` so every route — pages,
|
|
5
|
+
* endpoints and server islands alike — sees `Astro.locals.c15t` already
|
|
6
|
+
* populated. See {@link createConsentMiddleware} for what it does.
|
|
7
|
+
*/
|
|
8
|
+
import type { C15tLocals } from './types';
|
|
9
|
+
declare global {
|
|
10
|
+
namespace App {
|
|
11
|
+
interface Locals {
|
|
12
|
+
/** Consent context resolved by the c15t middleware. */
|
|
13
|
+
c15t: C15tLocals;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export declare const onRequest: import("astro").MiddlewareHandler;
|
|
18
|
+
export default onRequest;
|
|
19
|
+
export { createConsentMiddleware } from './middleware-handler';
|
|
20
|
+
export type { ConsentMiddlewareOptions } from './middleware-handler';
|