@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,170 @@
|
|
|
1
|
+
const VIRTUAL_ID = 'virtual:c15t/options';
|
|
2
|
+
const RESOLVED_VIRTUAL_ID = `\0${VIRTUAL_ID}`;
|
|
3
|
+
const DEFAULT_INIT_PATH = '/api/c15t/init';
|
|
4
|
+
const DEFAULT_MANIFEST_PATH = '/api/c15t/manifest';
|
|
5
|
+
const UI_ADAPTERS = {
|
|
6
|
+
react: {
|
|
7
|
+
adapterExport: 'reactDialogAdapter',
|
|
8
|
+
adapterModule: '@c15t/astro/ui/react',
|
|
9
|
+
astroIntegration: '@astrojs/react',
|
|
10
|
+
packages: [
|
|
11
|
+
'@astrojs/react',
|
|
12
|
+
'@c15t/react',
|
|
13
|
+
'react',
|
|
14
|
+
'react-dom'
|
|
15
|
+
],
|
|
16
|
+
surfaceModule: '@c15t/astro/islands/panel-surface.tsx'
|
|
17
|
+
},
|
|
18
|
+
svelte: {
|
|
19
|
+
adapterExport: 'svelteDialogAdapter',
|
|
20
|
+
adapterModule: '@c15t/astro/ui/svelte',
|
|
21
|
+
astroIntegration: '@astrojs/svelte',
|
|
22
|
+
packages: [
|
|
23
|
+
'@astrojs/svelte',
|
|
24
|
+
'svelte'
|
|
25
|
+
],
|
|
26
|
+
surfaceModule: '@c15t/astro/islands/panel-surface.svelte'
|
|
27
|
+
},
|
|
28
|
+
vue: {
|
|
29
|
+
adapterExport: 'vueDialogAdapter',
|
|
30
|
+
adapterModule: '@c15t/astro/ui/vue',
|
|
31
|
+
astroIntegration: '@astrojs/vue',
|
|
32
|
+
packages: [
|
|
33
|
+
'@astrojs/vue',
|
|
34
|
+
'@c15t/vue',
|
|
35
|
+
'vue'
|
|
36
|
+
],
|
|
37
|
+
surfaceModule: '@c15t/astro/islands/panel-surface.vue'
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
const UI_ADAPTER_NAMES = Object.keys(UI_ADAPTERS);
|
|
41
|
+
const SUGGESTIBLE_ADAPTERS = [
|
|
42
|
+
'react',
|
|
43
|
+
'vue'
|
|
44
|
+
];
|
|
45
|
+
const integration_resolveMiddleware = function(options) {
|
|
46
|
+
const raw = 'boolean' == typeof options.middleware ? {
|
|
47
|
+
enabled: options.middleware
|
|
48
|
+
} : options.middleware ?? {};
|
|
49
|
+
return {
|
|
50
|
+
enabled: raw.enabled ?? true,
|
|
51
|
+
skip: raw.skip ?? []
|
|
52
|
+
};
|
|
53
|
+
};
|
|
54
|
+
const integration_resolveEndpoints = function(options) {
|
|
55
|
+
const raw = 'boolean' == typeof options.endpoints ? {
|
|
56
|
+
enabled: options.endpoints
|
|
57
|
+
} : options.endpoints ?? {};
|
|
58
|
+
return {
|
|
59
|
+
enabled: raw.enabled ?? 'manifest' === options.mode.type,
|
|
60
|
+
initPath: raw.initPath ?? DEFAULT_INIT_PATH,
|
|
61
|
+
manifestPath: raw.manifestPath ?? DEFAULT_MANIFEST_PATH
|
|
62
|
+
};
|
|
63
|
+
};
|
|
64
|
+
const integration_backendURLFromEnv = function() {
|
|
65
|
+
if ("u" < typeof process) return;
|
|
66
|
+
const env = process.env;
|
|
67
|
+
return env?.C15T_BACKEND_URL ?? env?.PUBLIC_C15T_BACKEND_URL;
|
|
68
|
+
};
|
|
69
|
+
const integration_resolveOptions = function(options) {
|
|
70
|
+
if (!options?.mode || 'object' != typeof options.mode) throw new Error('@c15t/astro: `mode` is required. Use hosted({ url }), offline() or manifest().');
|
|
71
|
+
if ('manifest' === options.mode.type && options.mode.manifestURL && !options.mode.backendURL && !integration_backendURLFromEnv()) throw new Error('@c15t/astro: manifest mode with a `manifestURL` also needs a `backendURL` (or C15T_BACKEND_URL) — that is where consent is saved, and the injected routes only serve init and manifest.');
|
|
72
|
+
if (void 0 !== options.ui && !(options.ui in UI_ADAPTERS)) throw new Error(`@c15t/astro: unknown \`ui\` ${JSON.stringify(options.ui)}. Supported adapters: ${UI_ADAPTER_NAMES.join(', ')}.`);
|
|
73
|
+
const { endpoints: _endpoints, middleware: _middleware, requireUIIntegration: _requireUIIntegration, ...rest } = options;
|
|
74
|
+
return {
|
|
75
|
+
...rest,
|
|
76
|
+
colorScheme: options.colorScheme ?? 'system',
|
|
77
|
+
endpoints: integration_resolveEndpoints(options),
|
|
78
|
+
middleware: integration_resolveMiddleware(options),
|
|
79
|
+
ui: options.ui ?? 'svelte'
|
|
80
|
+
};
|
|
81
|
+
};
|
|
82
|
+
const integration_createVirtualOptionsPlugin = function(resolved) {
|
|
83
|
+
const serialized = JSON.stringify(resolved);
|
|
84
|
+
return {
|
|
85
|
+
load (id) {
|
|
86
|
+
if (id !== RESOLVED_VIRTUAL_ID) return;
|
|
87
|
+
return `export default ${serialized};`;
|
|
88
|
+
},
|
|
89
|
+
name: 'c15t:options',
|
|
90
|
+
resolveId (id) {
|
|
91
|
+
return id === VIRTUAL_ID ? RESOLVED_VIRTUAL_ID : void 0;
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
};
|
|
95
|
+
const integration_buildBootScript = function(options, ui) {
|
|
96
|
+
const adapter = UI_ADAPTERS[ui];
|
|
97
|
+
const lines = [
|
|
98
|
+
`import options from '${VIRTUAL_ID}';`,
|
|
99
|
+
"import { boot, registerDialogAdapter, registerDialogSurface } from '@c15t/astro/client';",
|
|
100
|
+
`registerDialogAdapter('${ui}', async () => (await import('${adapter.adapterModule}')).${adapter.adapterExport});`,
|
|
101
|
+
`registerDialogSurface('${ui}', () => import('${adapter.surfaceModule}'));`
|
|
102
|
+
];
|
|
103
|
+
if (options.clientEntrypoint) lines.push(`import clientOptions from '${options.clientEntrypoint}';`, 'boot(options, clientOptions);');
|
|
104
|
+
else lines.push('boot(options);');
|
|
105
|
+
return lines.join('\n');
|
|
106
|
+
};
|
|
107
|
+
const integration_buildVitePlugins = async function(resolved) {
|
|
108
|
+
const plugins = [
|
|
109
|
+
integration_createVirtualOptionsPlugin(resolved)
|
|
110
|
+
];
|
|
111
|
+
if ('vue' === resolved.ui) try {
|
|
112
|
+
const { default: shimVueImports } = await import("@c15t/vue/vite");
|
|
113
|
+
plugins.push(shimVueImports());
|
|
114
|
+
} catch (cause) {
|
|
115
|
+
throw new Error(`@c15t/astro: \`ui: 'vue'\` needs ${UI_ADAPTERS.vue.packages.join(', ')} installed. Install them, or pick another \`ui\`.`, {
|
|
116
|
+
cause
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
return plugins;
|
|
120
|
+
};
|
|
121
|
+
const integration_suggestUIAdapter = function(options, installed, logger) {
|
|
122
|
+
if (void 0 !== options.ui) return;
|
|
123
|
+
const match = SUGGESTIBLE_ADAPTERS.find((name)=>installed.has(UI_ADAPTERS[name].astroIntegration));
|
|
124
|
+
if (!match) return;
|
|
125
|
+
logger.warn(`this site already loads ${UI_ADAPTERS[match].astroIntegration}; \`ui: '${match}'\` would render the consent dialog with it instead of adding the Svelte runtime. Set \`ui: 'svelte'\` to silence this.`);
|
|
126
|
+
};
|
|
127
|
+
const integration_c15t = function(options) {
|
|
128
|
+
const resolved = integration_resolveOptions(options);
|
|
129
|
+
return {
|
|
130
|
+
hooks: {
|
|
131
|
+
'astro:config:done' ({ config, logger }) {
|
|
132
|
+
const installed = new Set(config.integrations.map((integration)=>integration.name));
|
|
133
|
+
integration_suggestUIAdapter(options, installed, logger);
|
|
134
|
+
if (false === options.requireUIIntegration) return;
|
|
135
|
+
const adapter = UI_ADAPTERS[resolved.ui];
|
|
136
|
+
if (installed.has(adapter.astroIntegration)) return;
|
|
137
|
+
logger.error(`the preference-centre and IAB dialogs render as ${resolved.ui} islands, so ${adapter.astroIntegration} must be installed and listed in astro.config before c15t(). Install ${adapter.packages.join(', ')} and run \`npx astro add ${resolved.ui}\`, or pass \`requireUIIntegration: false\` if you only use the server-rendered banner.`);
|
|
138
|
+
throw new Error(`@c15t/astro: ui: ${JSON.stringify(resolved.ui)} needs ${adapter.astroIntegration}. Install it, or set \`requireUIIntegration: false\` for a banner-only site.`);
|
|
139
|
+
},
|
|
140
|
+
async 'astro:config:setup' ({ addMiddleware, injectRoute, injectScript, updateConfig }) {
|
|
141
|
+
updateConfig({
|
|
142
|
+
vite: {
|
|
143
|
+
plugins: await integration_buildVitePlugins(resolved)
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
if (resolved.middleware.enabled) addMiddleware({
|
|
147
|
+
entrypoint: '@c15t/astro/middleware',
|
|
148
|
+
order: 'pre'
|
|
149
|
+
});
|
|
150
|
+
injectScript('page', integration_buildBootScript(options, resolved.ui));
|
|
151
|
+
if (resolved.endpoints.enabled) {
|
|
152
|
+
injectRoute({
|
|
153
|
+
entrypoint: '@c15t/astro/api/init',
|
|
154
|
+
pattern: resolved.endpoints.initPath,
|
|
155
|
+
prerender: false
|
|
156
|
+
});
|
|
157
|
+
injectRoute({
|
|
158
|
+
entrypoint: '@c15t/astro/api/manifest',
|
|
159
|
+
pattern: resolved.endpoints.manifestPath,
|
|
160
|
+
prerender: false
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
},
|
|
165
|
+
name: '@c15t/astro'
|
|
166
|
+
};
|
|
167
|
+
};
|
|
168
|
+
const src_integration = integration_c15t;
|
|
169
|
+
export default src_integration;
|
|
170
|
+
export { integration_c15t as c15t, integration_resolveOptions as resolveOptions };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
const cookies_filterCookieHeader = function(cookieHeader, names) {
|
|
2
|
+
const allowed = new Set(names);
|
|
3
|
+
const kept = cookieHeader.split(';').map((pair)=>pair.trim()).filter((pair)=>{
|
|
4
|
+
const name = pair.split('=')[0]?.trim();
|
|
5
|
+
return void 0 !== name && allowed.has(name);
|
|
6
|
+
});
|
|
7
|
+
return kept.length > 0 ? kept.join('; ') : void 0;
|
|
8
|
+
};
|
|
9
|
+
export { cookies_filterCookieHeader as filterCookieHeader };
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { waitUntilFromLocals } from "./api/handlers.js";
|
|
2
|
+
import { resolveConsentContext } from "./server.js";
|
|
3
|
+
const middleware_handler_matchesPrefix = function(pathname, prefix) {
|
|
4
|
+
if (!prefix) return false;
|
|
5
|
+
const normalized = prefix.endsWith('/') ? prefix.slice(0, -1) : prefix;
|
|
6
|
+
return pathname === normalized || pathname.startsWith(`${normalized}/`);
|
|
7
|
+
};
|
|
8
|
+
const middleware_handler_resolveSkipPaths = function(options) {
|
|
9
|
+
return [
|
|
10
|
+
options.endpoints.initPath,
|
|
11
|
+
options.endpoints.manifestPath,
|
|
12
|
+
...options.middleware?.skip ?? []
|
|
13
|
+
].filter(Boolean);
|
|
14
|
+
};
|
|
15
|
+
const middleware_handler_createConsentMiddleware = function(options, middlewareOptions = {}) {
|
|
16
|
+
const skipPaths = middleware_handler_resolveSkipPaths(options);
|
|
17
|
+
return async (context, next)=>{
|
|
18
|
+
const { pathname } = new URL(context.request.url);
|
|
19
|
+
if (skipPaths.some((prefix)=>middleware_handler_matchesPrefix(pathname, prefix))) return await next();
|
|
20
|
+
context.locals.c15t = await resolveConsentContext({
|
|
21
|
+
fetch: middlewareOptions.fetch,
|
|
22
|
+
headers: context.request.headers,
|
|
23
|
+
onBackgroundRevalidate: (revalidation)=>waitUntilFromLocals(revalidation, context.locals),
|
|
24
|
+
options,
|
|
25
|
+
skipPrefetch: true === context.isPrerendered,
|
|
26
|
+
url: context.request.url
|
|
27
|
+
});
|
|
28
|
+
return await next();
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
export { middleware_handler_createConsentMiddleware as createConsentMiddleware };
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import options from "virtual:c15t/options";
|
|
2
|
+
import { createConsentMiddleware } from "./middleware-handler.js";
|
|
3
|
+
const onRequest = createConsentMiddleware(options);
|
|
4
|
+
const middleware = onRequest;
|
|
5
|
+
export default middleware;
|
|
6
|
+
export { createConsentMiddleware, onRequest };
|
package/dist/mode.js
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { createHostedTransport, createOfflineTransport, custom, hosted } from "@c15t/core";
|
|
2
|
+
const mode_hostedMode = function(options) {
|
|
3
|
+
return {
|
|
4
|
+
...options,
|
|
5
|
+
type: 'hosted'
|
|
6
|
+
};
|
|
7
|
+
};
|
|
8
|
+
const mode_offlineMode = function(options = {}) {
|
|
9
|
+
return {
|
|
10
|
+
...options,
|
|
11
|
+
type: 'offline'
|
|
12
|
+
};
|
|
13
|
+
};
|
|
14
|
+
const mode_manifestMode = function(options = {}) {
|
|
15
|
+
return {
|
|
16
|
+
...options,
|
|
17
|
+
type: 'manifest'
|
|
18
|
+
};
|
|
19
|
+
};
|
|
20
|
+
const createOfflineFactory = (descriptor)=>Object.assign((context)=>createOfflineTransport({
|
|
21
|
+
iabEnabled: context.iabEnabled,
|
|
22
|
+
policyRules: descriptor.policyRules ?? context.policyRules,
|
|
23
|
+
translations: context.translations
|
|
24
|
+
}), {
|
|
25
|
+
kind: 'offline'
|
|
26
|
+
});
|
|
27
|
+
const mode_resolveTransportFactory = function(descriptor, endpoints = {
|
|
28
|
+
initPath: '/api/c15t/init'
|
|
29
|
+
}) {
|
|
30
|
+
if ('hosted' === descriptor.type) return hosted({
|
|
31
|
+
domain: descriptor.domain,
|
|
32
|
+
headers: descriptor.headers,
|
|
33
|
+
url: descriptor.url
|
|
34
|
+
});
|
|
35
|
+
if ('offline' === descriptor.type) return createOfflineFactory(descriptor);
|
|
36
|
+
if ('manifest' === descriptor.type) {
|
|
37
|
+
const base = endpoints.initPath.replace(/\/init$/u, '');
|
|
38
|
+
const backendURL = endpoints.backendURL ?? descriptor.backendURL ?? base;
|
|
39
|
+
const transport = createHostedTransport({
|
|
40
|
+
assertDecisionInputs: true,
|
|
41
|
+
backendURL,
|
|
42
|
+
initURL: endpoints.initPath
|
|
43
|
+
});
|
|
44
|
+
return Object.assign(()=>transport, {
|
|
45
|
+
kind: 'hosted'
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
throw new Error(`@c15t/astro: unknown mode ${JSON.stringify(descriptor.type)}. Use hosted(), offline() or manifest().`);
|
|
49
|
+
};
|
|
50
|
+
export { custom, mode_hostedMode as hostedMode, mode_manifestMode as manifestMode, mode_offlineMode as offlineMode, mode_resolveTransportFactory as resolveTransportFactory };
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
import { buildPrefetchScript, c15tProtocolHeaders, createConsentKernel, createOfflineTransport, defaultTranslationConfig, deferInitGvl, deferInitGvlToRoute, mergeInitOutputIntoKernelConfig, mergeInitResponseIntoKernelConfig } from "@c15t/core";
|
|
2
|
+
import { CONSENT_STORAGE_KEY, readStoredRecordsFromCookieHeader } from "@c15t/core/modules/persistence";
|
|
3
|
+
import { isIABConfigured } from "@c15t/core/runtime";
|
|
4
|
+
import { fetchCachedGvl } from "@c15t/core/server";
|
|
5
|
+
import { readProducerPolicyContract } from "@c15t/core/transports";
|
|
6
|
+
import { CONSENT_REQUEST_HEADER_NAMES, consentInputsToOverrides, extractConsentRequestInputs, resolveBackendURL } from "@c15t/schema/types";
|
|
7
|
+
import { baseTranslations } from "@c15t/translations/all";
|
|
8
|
+
import { loadConsentManifest, resolveManifestInit } from "./api/manifest-init.js";
|
|
9
|
+
import { filterCookieHeader } from "./libs/cookies.js";
|
|
10
|
+
const server_resolveTranslations = function(options, inputs) {
|
|
11
|
+
const detect = options.i18n?.detectLanguage !== false;
|
|
12
|
+
const language = options.i18n?.locale ?? (detect ? inputs.language : void 0) ?? defaultTranslationConfig.defaultLanguage ?? 'en';
|
|
13
|
+
const catalogue = baseTranslations;
|
|
14
|
+
const base = catalogue[language] ?? defaultTranslationConfig.translations.en;
|
|
15
|
+
const overrides = options.i18n?.messages?.[language];
|
|
16
|
+
return {
|
|
17
|
+
language,
|
|
18
|
+
translations: overrides ? {
|
|
19
|
+
...base,
|
|
20
|
+
...overrides
|
|
21
|
+
} : base
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
const server_readInitialConsentConfig = function(headers, options) {
|
|
25
|
+
const now = Date.now();
|
|
26
|
+
const initialRecords = readStoredRecordsFromCookieHeader(headers.get('cookie') ?? void 0, options.storageConfig, now);
|
|
27
|
+
const inputs = extractConsentRequestInputs(headers, {
|
|
28
|
+
language: options.i18n?.locale
|
|
29
|
+
});
|
|
30
|
+
const config = {
|
|
31
|
+
initialPolicyPending: true,
|
|
32
|
+
initialPrivacySignals: {
|
|
33
|
+
gpc: inputs.gpc
|
|
34
|
+
},
|
|
35
|
+
initialRecords,
|
|
36
|
+
now
|
|
37
|
+
};
|
|
38
|
+
const overrides = consentInputsToOverrides({
|
|
39
|
+
country: inputs.country,
|
|
40
|
+
language: inputs.language,
|
|
41
|
+
region: inputs.region
|
|
42
|
+
});
|
|
43
|
+
if (Object.keys(overrides).length > 0) config.initialOverrides = overrides;
|
|
44
|
+
return {
|
|
45
|
+
config,
|
|
46
|
+
inputs
|
|
47
|
+
};
|
|
48
|
+
};
|
|
49
|
+
const LOOPBACK_HOSTS = new Set([
|
|
50
|
+
'localhost',
|
|
51
|
+
'127.0.0.1',
|
|
52
|
+
'[::1]',
|
|
53
|
+
'::1'
|
|
54
|
+
]);
|
|
55
|
+
const server_consentCookieName = function(options) {
|
|
56
|
+
return options.storageConfig?.storageKey ?? CONSENT_STORAGE_KEY;
|
|
57
|
+
};
|
|
58
|
+
const server_resolveAgainstRequest = function(url, headers, requestURL) {
|
|
59
|
+
if (requestURL) try {
|
|
60
|
+
const parsed = new URL(requestURL);
|
|
61
|
+
return resolveBackendURL(url, {
|
|
62
|
+
host: parsed.host,
|
|
63
|
+
'x-forwarded-proto': parsed.protocol.replace(':', '')
|
|
64
|
+
});
|
|
65
|
+
} catch {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
const host = headers.get('host');
|
|
69
|
+
return host ? resolveBackendURL(url, {
|
|
70
|
+
host
|
|
71
|
+
}) : null;
|
|
72
|
+
};
|
|
73
|
+
const server_mayForwardCookie = function(absolute, requestURL) {
|
|
74
|
+
let target;
|
|
75
|
+
try {
|
|
76
|
+
target = new URL(absolute);
|
|
77
|
+
} catch {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
if ('https:' === target.protocol) return true;
|
|
81
|
+
if (LOOPBACK_HOSTS.has(target.hostname)) return true;
|
|
82
|
+
if (!requestURL) return false;
|
|
83
|
+
try {
|
|
84
|
+
const from = new URL(requestURL);
|
|
85
|
+
return 'http:' === from.protocol && from.host === target.host;
|
|
86
|
+
} catch {
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
const INIT_HEADER_ALLOWLIST = new Set(CONSENT_REQUEST_HEADER_NAMES);
|
|
91
|
+
const server_configuredInitHeaders = function(headers) {
|
|
92
|
+
const allowed = {};
|
|
93
|
+
for (const [name, value] of Object.entries(headers ?? {})){
|
|
94
|
+
const normalized = name.toLowerCase();
|
|
95
|
+
if (INIT_HEADER_ALLOWLIST.has(normalized)) allowed[normalized] = value;
|
|
96
|
+
}
|
|
97
|
+
return allowed;
|
|
98
|
+
};
|
|
99
|
+
const server_forwardHeaders = function(headers, overrides, options) {
|
|
100
|
+
const forward = {
|
|
101
|
+
...c15tProtocolHeaders,
|
|
102
|
+
accept: 'application/json'
|
|
103
|
+
};
|
|
104
|
+
const detectedGpc = headers.get('sec-gpc');
|
|
105
|
+
if (null !== detectedGpc) forward['sec-gpc'] = detectedGpc;
|
|
106
|
+
const cookie = headers.get('cookie');
|
|
107
|
+
const scoped = cookie && false !== options.allowCookie ? filterCookieHeader(cookie, [
|
|
108
|
+
options.cookieName
|
|
109
|
+
]) : void 0;
|
|
110
|
+
if (scoped) forward.cookie = scoped;
|
|
111
|
+
if (overrides.country) forward['x-c15t-country'] = overrides.country;
|
|
112
|
+
if (overrides.region) forward['x-c15t-region'] = overrides.region;
|
|
113
|
+
if (overrides.language) forward['accept-language'] = overrides.language;
|
|
114
|
+
if (void 0 !== overrides.gpc) forward['x-c15t-gpc'] = overrides.gpc ? '1' : '0';
|
|
115
|
+
return forward;
|
|
116
|
+
};
|
|
117
|
+
const server_prefetchHosted = async function(input) {
|
|
118
|
+
const absolute = server_resolveAgainstRequest(input.backendURL, input.headers, input.url);
|
|
119
|
+
const fetchImpl = input.fetch ?? globalThis.fetch?.bind(globalThis);
|
|
120
|
+
if (!absolute || !fetchImpl) return input.base;
|
|
121
|
+
const allowCookie = server_mayForwardCookie(absolute, input.url);
|
|
122
|
+
const forwarded = {
|
|
123
|
+
...server_forwardHeaders(input.headers, input.base.initialOverrides ?? {}, {
|
|
124
|
+
allowCookie,
|
|
125
|
+
cookieName: server_consentCookieName(input.options)
|
|
126
|
+
}),
|
|
127
|
+
...server_configuredInitHeaders(input.configuredHeaders)
|
|
128
|
+
};
|
|
129
|
+
try {
|
|
130
|
+
const response = await fetchImpl(`${absolute}/init`, {
|
|
131
|
+
cache: 'no-store',
|
|
132
|
+
credentials: allowCookie ? 'include' : 'omit',
|
|
133
|
+
headers: forwarded,
|
|
134
|
+
method: 'GET'
|
|
135
|
+
});
|
|
136
|
+
if (!response.ok) return input.base;
|
|
137
|
+
const payload = await response.json();
|
|
138
|
+
return mergeInitOutputIntoKernelConfig(input.base, input.fetch || forwarded.cookie ? payload : deferInitGvl(payload, `${absolute}/init`, 'init', forwarded), {}, {
|
|
139
|
+
producerContract: readProducerPolicyContract(response.headers)
|
|
140
|
+
});
|
|
141
|
+
} catch {
|
|
142
|
+
return input.base;
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
const server_prefetchManifest = async function(input, mode) {
|
|
146
|
+
const source = {
|
|
147
|
+
headers: input.headers,
|
|
148
|
+
url: input.url
|
|
149
|
+
};
|
|
150
|
+
const target = mode.manifestURL ?? mode.backendURL;
|
|
151
|
+
const absoluteTarget = target ? server_resolveAgainstRequest(target, input.headers, input.url) : null;
|
|
152
|
+
try {
|
|
153
|
+
const manifest = await loadConsentManifest({
|
|
154
|
+
fetch: input.fetch,
|
|
155
|
+
onBackgroundRevalidate: input.onBackgroundRevalidate,
|
|
156
|
+
options: input.options,
|
|
157
|
+
source
|
|
158
|
+
});
|
|
159
|
+
const payload = await resolveManifestInit({
|
|
160
|
+
fetch: input.fetch,
|
|
161
|
+
fetchGvl: async ({ reference, language, fetch: fetchImpl })=>await fetchCachedGvl({
|
|
162
|
+
fetch: fetchImpl,
|
|
163
|
+
language,
|
|
164
|
+
url: reference.url
|
|
165
|
+
}),
|
|
166
|
+
inputs: input.inputs,
|
|
167
|
+
manifest
|
|
168
|
+
});
|
|
169
|
+
return mergeInitOutputIntoKernelConfig(input.base, !input.fetch && manifest.iab?.gvl ? deferInitGvlToRoute(payload, input.options.endpoints.initPath) : payload, server_forwardHeaders(input.headers, input.base.initialOverrides ?? {}, {
|
|
170
|
+
allowCookie: absoluteTarget ? server_mayForwardCookie(absoluteTarget, input.url) : false,
|
|
171
|
+
cookieName: server_consentCookieName(input.options)
|
|
172
|
+
}));
|
|
173
|
+
} catch {
|
|
174
|
+
return input.base;
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
const server_prefetchOffline = async function(input) {
|
|
178
|
+
const { options } = input;
|
|
179
|
+
const policyRules = 'offline' === options.mode.type ? options.mode.policyRules : void 0;
|
|
180
|
+
const transport = createOfflineTransport({
|
|
181
|
+
iabEnabled: isIABConfigured(options.iab),
|
|
182
|
+
policyRules,
|
|
183
|
+
translations: input.translations
|
|
184
|
+
});
|
|
185
|
+
try {
|
|
186
|
+
const response = await transport.init?.({
|
|
187
|
+
overrides: input.base.initialOverrides ?? {},
|
|
188
|
+
user: input.base.initialUser ?? null
|
|
189
|
+
});
|
|
190
|
+
if (!response) return input.base;
|
|
191
|
+
return mergeInitResponseIntoKernelConfig(input.base, response);
|
|
192
|
+
} catch {
|
|
193
|
+
return input.base;
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
const server_prefetchLocal = function(input) {
|
|
197
|
+
const { mode } = input.options;
|
|
198
|
+
return 'manifest' === mode.type ? server_prefetchManifest(input, mode) : server_prefetchOffline(input);
|
|
199
|
+
};
|
|
200
|
+
const server_withResolvedGvl = async function(input) {
|
|
201
|
+
const { iab } = input.options;
|
|
202
|
+
if (!(isIABConfigured(iab) && iab)) return input.config;
|
|
203
|
+
if (input.config.initialIab?.gvl || input.config.initialIab?.gvlReference) return input.config;
|
|
204
|
+
let gvl = iab.gvl ?? null;
|
|
205
|
+
if (!gvl && iab.gvlURL) try {
|
|
206
|
+
gvl = await fetchCachedGvl({
|
|
207
|
+
fetch: input.fetch,
|
|
208
|
+
language: input.language,
|
|
209
|
+
url: iab.gvlURL
|
|
210
|
+
});
|
|
211
|
+
} catch {
|
|
212
|
+
gvl = null;
|
|
213
|
+
}
|
|
214
|
+
if (!gvl) return input.config;
|
|
215
|
+
return {
|
|
216
|
+
...input.config,
|
|
217
|
+
initialIab: {
|
|
218
|
+
...input.config.initialIab ?? {},
|
|
219
|
+
cmpId: input.config.initialIab?.cmpId ?? iab.cmpId ?? null,
|
|
220
|
+
enabled: true,
|
|
221
|
+
gvl
|
|
222
|
+
}
|
|
223
|
+
};
|
|
224
|
+
};
|
|
225
|
+
const server_snapshotFromConfig = function(config) {
|
|
226
|
+
const kernel = createConsentKernel(config);
|
|
227
|
+
const snapshot = kernel.getServerSnapshot();
|
|
228
|
+
kernel.dispose();
|
|
229
|
+
return snapshot;
|
|
230
|
+
};
|
|
231
|
+
const server_resolveConsentContext = async function(input) {
|
|
232
|
+
const { headers, options } = input;
|
|
233
|
+
const { config: base, inputs } = server_readInitialConsentConfig(headers, options);
|
|
234
|
+
const translations = server_resolveTranslations(options, inputs);
|
|
235
|
+
let config = {
|
|
236
|
+
...base,
|
|
237
|
+
initialTranslations: translations
|
|
238
|
+
};
|
|
239
|
+
if (!input.skipPrefetch) config = 'hosted' === options.mode.type ? await server_prefetchHosted({
|
|
240
|
+
backendURL: options.mode.url,
|
|
241
|
+
base: config,
|
|
242
|
+
configuredHeaders: options.mode.headers,
|
|
243
|
+
fetch: input.fetch,
|
|
244
|
+
headers,
|
|
245
|
+
options,
|
|
246
|
+
url: input.url
|
|
247
|
+
}) : await server_prefetchLocal({
|
|
248
|
+
base: config,
|
|
249
|
+
fetch: input.fetch,
|
|
250
|
+
headers,
|
|
251
|
+
inputs,
|
|
252
|
+
onBackgroundRevalidate: input.onBackgroundRevalidate,
|
|
253
|
+
options,
|
|
254
|
+
translations,
|
|
255
|
+
url: input.url
|
|
256
|
+
});
|
|
257
|
+
config = await server_withResolvedGvl({
|
|
258
|
+
config,
|
|
259
|
+
fetch: input.fetch,
|
|
260
|
+
language: translations.language.split('-')[0] || 'en',
|
|
261
|
+
options
|
|
262
|
+
});
|
|
263
|
+
config.initialPolicyPending = void 0 === config.initialPolicyResolution;
|
|
264
|
+
const snapshot = server_snapshotFromConfig(config);
|
|
265
|
+
return {
|
|
266
|
+
config,
|
|
267
|
+
decision: snapshot.resolution,
|
|
268
|
+
hasConsentUi: 'matched' === snapshot.resolution.status && ('none' !== snapshot.policyRule.prompt || snapshot.policyRule.rights.length > 0),
|
|
269
|
+
hasPolicy: 'matched' === snapshot.resolution.status,
|
|
270
|
+
inputs,
|
|
271
|
+
options,
|
|
272
|
+
shouldShowBanner: !snapshot.policyPending && 'banner' === snapshot.activeUI,
|
|
273
|
+
snapshot
|
|
274
|
+
};
|
|
275
|
+
};
|
|
276
|
+
const server_buildConfigScript = function(config) {
|
|
277
|
+
const json = JSON.stringify(config ?? {}).replace(/</gu, '\\u003c');
|
|
278
|
+
return `window.__c15tAstroConfig=${json};`;
|
|
279
|
+
};
|
|
280
|
+
const server_buildColorSchemeScript = function(colorScheme) {
|
|
281
|
+
if ('light' === colorScheme) return '';
|
|
282
|
+
if ('dark' === colorScheme) return "document.documentElement.classList.add('c15t-dark');";
|
|
283
|
+
return "try{document.documentElement.classList.toggle('c15t-dark',matchMedia('(prefers-color-scheme:dark)').matches)}catch(e){}";
|
|
284
|
+
};
|
|
285
|
+
const emitted = new WeakSet();
|
|
286
|
+
const server_markConfigEmitted = function(locals) {
|
|
287
|
+
if (emitted.has(locals)) return false;
|
|
288
|
+
emitted.add(locals);
|
|
289
|
+
return true;
|
|
290
|
+
};
|
|
291
|
+
export { buildPrefetchScript, server_buildColorSchemeScript as buildColorSchemeScript, server_buildConfigScript as buildConfigScript, server_markConfigEmitted as markConfigEmitted, server_readInitialConsentConfig as readInitialConsentConfig, server_resolveConsentContext as resolveConsentContext, server_resolveTranslations as resolveTranslations, server_snapshotFromConfig as snapshotFromConfig };
|
package/dist/styles.css
ADDED
package/dist/types.js
ADDED
|
File without changes
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
const surfaces = new Map();
|
|
2
|
+
const adapter_registerDialogSurface = function(name, load) {
|
|
3
|
+
surfaces.set(name, load);
|
|
4
|
+
};
|
|
5
|
+
const adapter_requireDialogSurface = function(name) {
|
|
6
|
+
const load = surfaces.get(name);
|
|
7
|
+
if (!load) throw new Error(`@c15t/astro: no ${name} dialog surface registered. The integration registers it from the injected page script, so this usually means c15t() is missing from astro.config, or the page was rendered with \`middleware: false\` and no boot script.`);
|
|
8
|
+
return load;
|
|
9
|
+
};
|
|
10
|
+
const registry = new Map();
|
|
11
|
+
const pending = new Map();
|
|
12
|
+
const adapter_registerDialogAdapter = function(name, load) {
|
|
13
|
+
registry.set(name, load);
|
|
14
|
+
pending.delete(name);
|
|
15
|
+
};
|
|
16
|
+
const adapter_resetDialogRegistriesForTest = function() {
|
|
17
|
+
registry.clear();
|
|
18
|
+
surfaces.clear();
|
|
19
|
+
pending.clear();
|
|
20
|
+
};
|
|
21
|
+
const adapter_loadDialogAdapter = async function(name) {
|
|
22
|
+
const memo = pending.get(name);
|
|
23
|
+
if (memo) return await memo;
|
|
24
|
+
const load = registry.get(name);
|
|
25
|
+
if (!load) throw new Error(`@c15t/astro: no dialog adapter registered for ui: ${JSON.stringify(name)}. The integration registers the configured adapter from the injected page script; register your own with registerDialogAdapter().`);
|
|
26
|
+
const loading = (async ()=>{
|
|
27
|
+
try {
|
|
28
|
+
return await load();
|
|
29
|
+
} catch (error) {
|
|
30
|
+
pending.delete(name);
|
|
31
|
+
throw error;
|
|
32
|
+
}
|
|
33
|
+
})();
|
|
34
|
+
pending.set(name, loading);
|
|
35
|
+
return await loading;
|
|
36
|
+
};
|
|
37
|
+
export { adapter_loadDialogAdapter as loadDialogAdapter, adapter_registerDialogAdapter as registerDialogAdapter, adapter_registerDialogSurface as registerDialogSurface, adapter_requireDialogSurface as requireDialogSurface, adapter_resetDialogRegistriesForTest as resetDialogRegistriesForTest };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
const provider_props_buildProviderProps = function(runtime, options) {
|
|
2
|
+
return {
|
|
3
|
+
options: {
|
|
4
|
+
consentCategories: options.consentCategories,
|
|
5
|
+
legalLinks: options.legalLinks,
|
|
6
|
+
presentation: options.presentation,
|
|
7
|
+
theme: options.theme
|
|
8
|
+
},
|
|
9
|
+
runtime
|
|
10
|
+
};
|
|
11
|
+
};
|
|
12
|
+
export { provider_props_buildProviderProps as buildProviderProps };
|
package/dist/ui/react.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { requireDialogSurface } from "./adapter.js";
|
|
2
|
+
import { buildProviderProps } from "./provider-props.js";
|
|
3
|
+
const reactDialogAdapter = {
|
|
4
|
+
async mount (context) {
|
|
5
|
+
const [{ createElement }, { createRoot }, surface] = await Promise.all([
|
|
6
|
+
import("react"),
|
|
7
|
+
import("react-dom/client"),
|
|
8
|
+
requireDialogSurface('react')()
|
|
9
|
+
]);
|
|
10
|
+
const root = createRoot(context.target);
|
|
11
|
+
root.render(createElement(surface.default, {
|
|
12
|
+
...buildProviderProps(context.runtime, context.options),
|
|
13
|
+
kind: context.kind,
|
|
14
|
+
tab: context.tab
|
|
15
|
+
}));
|
|
16
|
+
return {
|
|
17
|
+
close () {
|
|
18
|
+
context.runtime.kernel.set.activeUI('none');
|
|
19
|
+
},
|
|
20
|
+
destroy () {
|
|
21
|
+
root.unmount();
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
},
|
|
25
|
+
name: 'react',
|
|
26
|
+
async preload () {
|
|
27
|
+
await Promise.all([
|
|
28
|
+
import("react"),
|
|
29
|
+
import("react-dom/client"),
|
|
30
|
+
requireDialogSurface('react')()
|
|
31
|
+
]);
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
export { reactDialogAdapter };
|