@gradial/aci 0.1.41 → 0.1.43
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/CHANGELOG.md +15 -11
- package/README.md +4 -4
- package/dist/astro/index.d.ts +1 -1
- package/dist/astro/index.js +1 -1
- package/dist/astro/page.d.ts +6 -0
- package/dist/astro/page.js +3 -2
- package/dist/audience-data-models.d.ts +286 -0
- package/dist/audience-data-models.js +46 -0
- package/dist/cli/compile-registry.d.mts +6 -0
- package/dist/cli/compile-registry.mjs +774 -0
- package/dist/cli/materialize-template.d.mts +19 -0
- package/dist/cli/materialize-template.mjs +128 -0
- package/dist/cli/validate-content.d.mts +32 -0
- package/dist/cli/validate-content.mjs +464 -0
- package/dist/cli/verify-next-cdn-routes.d.mts +2 -0
- package/dist/cli/verify-next-cdn-routes.mjs +47 -0
- package/dist/collect/aci.js +1 -0
- package/dist/collect/aci.js.sha256 +1 -0
- package/dist/collect/assignments.d.ts +65 -0
- package/dist/collect/assignments.js +138 -0
- package/dist/collect/batcher.d.ts +23 -0
- package/dist/collect/batcher.js +28 -6
- package/dist/collect/consent/envelope.d.ts +9 -3
- package/dist/collect/consent/envelope.js +58 -40
- package/dist/collect/declarative.d.ts +1 -1
- package/dist/collect/declarative.js +7 -1
- package/dist/collect/index.js +39 -6
- package/dist/collect/linkclicks.js +4 -2
- package/dist/collect/pageviews.d.ts +15 -3
- package/dist/collect/pageviews.js +28 -13
- package/dist/collect/transport.d.ts +4 -0
- package/dist/collect/transport.js +37 -2
- package/dist/consent/portable-envelope.d.ts +9 -0
- package/dist/consent/portable-envelope.js +110 -0
- package/dist/consent-banner/index.d.ts +33 -0
- package/dist/consent-banner/index.js +100 -0
- package/dist/content/assets.d.ts +1 -0
- package/dist/content/assets.js +1 -0
- package/dist/content/personalization.d.ts +35 -0
- package/dist/content/personalization.js +57 -0
- package/dist/content/resolve-slots.js +1 -0
- package/dist/content/types.d.ts +9 -1
- package/dist/edge/personalization-core.d.ts +11 -0
- package/dist/edge/personalization-core.js +265 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/next/assignments-script.d.ts +14 -0
- package/dist/next/assignments-script.js +17 -0
- package/dist/next/collect-route.d.ts +19 -0
- package/dist/next/collect-route.js +422 -0
- package/dist/next/envelope-digest.d.ts +21 -0
- package/dist/next/envelope-digest.js +58 -0
- package/dist/next/index.d.ts +1 -0
- package/dist/next/index.js +1 -0
- package/dist/next/middleware.d.ts +31 -3
- package/dist/next/middleware.js +254 -30
- package/dist/next/page.js +2 -1
- package/dist/next/personalization-host.d.ts +127 -0
- package/dist/next/personalization-host.js +257 -0
- package/dist/next/server.d.ts +1 -0
- package/dist/next/server.js +9 -6
- package/dist/next/vercel-collect-sdk-route.d.ts +27 -0
- package/dist/next/vercel-collect-sdk-route.js +205 -0
- package/dist/next/vercel-personalization.d.ts +4 -0
- package/dist/next/vercel-personalization.js +4 -0
- package/dist/next/vercel-s3-personalization.d.ts +79 -0
- package/dist/next/vercel-s3-personalization.js +574 -0
- package/dist/providers/s3.d.ts +20 -5
- package/dist/providers/s3.js +93 -23
- package/dist/react/VideoPlayer.d.ts +6 -6
- package/dist/react/index.d.ts +6 -6
- package/dist/testing/conformance/checks.js +75 -4
- package/dist/types/block-ref.d.ts +2 -0
- package/dist/types/block-ref.js +1 -0
- package/dist/types/page.d.ts +1 -1
- package/package.json +44 -15
- package/src/astro/ConsentBanner.astro +62 -0
- package/src/cli/compile-registry.mjs +24 -10
- package/src/cli/validate-content.mjs +52 -13
- package/src/cli/verify-next-cdn-routes.mjs +5 -4
- package/src/consent-banner/styles.css +110 -0
- package/src/types/block-ref.test.ts +45 -0
- package/src/types/block-ref.ts +2 -0
- package/src/types/page.ts +1 -1
- package/templates/astro/starter-content/src/cms/contracts/components/index.ts +2 -0
- package/templates/astro/starter-content/src/cms/contracts/components/personalizationChoice.contract.ts +17 -0
- package/templates/astro/starter-content/src/cms/registry.ts +3 -0
- package/templates/astro/starter-content/src/components/sections/PersonalizationChoice.astro +41 -0
- package/templates/astro/starter-content/src/fixtures/atomicBlocks.ts +7 -6
- package/templates/astro/starter-content/src/fixtures/composition.ts +3 -2
- package/templates/astro/starter-content/src/fixtures/sections.ts +2 -1
- package/templates/astro/starter-content/src/render/RenderPage.astro +3 -2
- package/templates/astro/template/src/layouts/SiteLayout.astro +3 -1
- package/templates/astro/template/src/pages/[...slug].astro +1 -0
- package/templates/common/.agents/skills/authoring/SKILL.md +11 -3
- package/templates/common/.agents/skills/authoring/references/block-slots.md +3 -0
- package/templates/common/.content/audiences/preference-blue/_index.json +11 -0
- package/templates/common/.content/audiences/preference-red/_index.json +11 -0
- package/templates/common/.content/overlays/audience/preference-blue/pages/home/_index.json +13 -0
- package/templates/common/.content/overlays/audience/preference-red/pages/home/_index.json +13 -0
- package/templates/common/.content/pages/home/_index.json +124 -4
- package/templates/common/.content/segments/preference-blue/_index.json +20 -0
- package/templates/common/.content/segments/preference-red/_index.json +20 -0
- package/templates/common/contracts/components/index.ts +21 -1
- package/templates/common/contracts/components/personalizationChoice.contract.ts +17 -0
- package/templates/common/starter-content/.content/pages/about/_index.json +3 -0
- package/templates/common/starter-content/.content/pages/home/_index.json +9 -0
- package/templates/nextjs/starter-content/src/cms/contracts/components/index.ts +3 -1
- package/templates/nextjs/starter-content/src/cms/contracts/components/personalizationChoice.contract.ts +17 -0
- package/templates/nextjs/starter-content/src/cms/registry.ts +3 -0
- package/templates/nextjs/starter-content/src/components/sections/PersonalizationChoice.tsx +53 -0
- package/templates/nextjs/starter-content/src/middleware.ts +41 -0
- package/templates/nextjs/template/src/app/collect/route.ts +18 -0
- package/templates/nextjs/template/src/middleware.ts +36 -2
- package/templates/nextjs/template/src/middleware.ts.tmpl +31 -1
- package/templates/astro/template/.aci.yaml +0 -28
- package/templates/common/.content/.gradial-dam/blobs/sha256/7f/bf/7fbfc7fe230ff12688c496f660119e41d9725c4504d82e310c15665c73c09292 +0 -0
- package/templates/common/.content/.gradial-dam/blobs/sha256/a0/f5/a0f506689de9b15a7055f70103a3f02cd3ba105771ae2de6754df2ede1210d3a +0 -0
- package/templates/common/.content/.gradial-dam/blobs/sha256/dd/b7/ddb7e9642be08317bcd4b2bd26b58ba8d0d34dd7239e03e684a4f3634fa3845b +0 -0
- package/templates/common/.content/.gradial-dam/blobs/sha256/fb/32/fb3260e8286ea8231ea1c77b9c0be92d6edd9026565731eb14cfdcfd6b1b75b3 +0 -0
- package/templates/common/.content/config/robots.txt +0 -2
- package/templates/common/.content/config/site.json +0 -197
- package/templates/common/.content/dam/folders/seed/ast_site_about_hero/asset.json +0 -9
- package/templates/common/.content/dam/folders/seed/ast_site_about_hero/editorial.json +0 -14
- package/templates/common/.content/dam/folders/seed/ast_site_about_hero/governance.json +0 -23
- package/templates/common/.content/dam/folders/seed/ast_site_about_hero/versions/v0001.json +0 -37
- package/templates/common/.content/dam/folders/seed/ast_site_home_hero/asset.json +0 -9
- package/templates/common/.content/dam/folders/seed/ast_site_home_hero/editorial.json +0 -14
- package/templates/common/.content/dam/folders/seed/ast_site_home_hero/governance.json +0 -23
- package/templates/common/.content/dam/folders/seed/ast_site_home_hero/versions/v0001.json +0 -37
- package/templates/common/.content/fragments/navbar/_index.json +0 -25
- package/templates/common/.content/fragments/site-footer/_index.json +0 -62
- package/templates/common/.content/pages/about/_index.json +0 -51
- package/templates/nextjs/template/.aci.yaml +0 -26
package/dist/content/types.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { SitePersonalizationConfig } from './personalization.js';
|
|
1
|
+
import type { ExperimentAssignmentContext, SitePersonalizationConfig } from './personalization.js';
|
|
2
2
|
export type ButtonVariant = 'primary' | 'secondary' | 'tertiary' | 'outline' | 'ghost';
|
|
3
3
|
export interface KernelImage {
|
|
4
4
|
src: string;
|
|
@@ -42,11 +42,13 @@ export interface KernelPageSeo {
|
|
|
42
42
|
canonical?: string;
|
|
43
43
|
}
|
|
44
44
|
export interface KernelPageBlock {
|
|
45
|
+
$type: 'component';
|
|
45
46
|
id: string;
|
|
46
47
|
component: string;
|
|
47
48
|
props: Record<string, unknown>;
|
|
48
49
|
}
|
|
49
50
|
export interface KernelAtomicBlock {
|
|
51
|
+
$type: 'component';
|
|
50
52
|
id: string;
|
|
51
53
|
component: string;
|
|
52
54
|
props: Record<string, unknown>;
|
|
@@ -58,6 +60,12 @@ export interface KernelPage {
|
|
|
58
60
|
layout: string;
|
|
59
61
|
metadata: KernelPageSeo;
|
|
60
62
|
regions: Record<string, KernelPageBlock[]>;
|
|
63
|
+
/**
|
|
64
|
+
* Build-time experiment assignment context (personalization-reporting
|
|
65
|
+
* design/04); present only on experiment variant payloads. The starters
|
|
66
|
+
* render it as the data-aci-assignments tag.
|
|
67
|
+
*/
|
|
68
|
+
experiments?: ExperimentAssignmentContext[];
|
|
61
69
|
}
|
|
62
70
|
export interface KernelSiteConfig {
|
|
63
71
|
id: string;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export function selectPersonalizationVariant(request: any, route: any, claims: any, experiments: any, identity: any): any;
|
|
2
|
+
export function resolvePersonalizationDecision(request: any, route: any, claims: any, experiments: any, identity: any): {
|
|
3
|
+
variant: any;
|
|
4
|
+
experiment: {
|
|
5
|
+
experimentId: any;
|
|
6
|
+
armId: any;
|
|
7
|
+
allocationVersion: string;
|
|
8
|
+
variantKey: any;
|
|
9
|
+
bucket: number;
|
|
10
|
+
} | null;
|
|
11
|
+
};
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
// Runtime-neutral personalization selection. Vercel imports this module and
|
|
2
|
+
// the CloudFront Terraform wrapper inlines the same source into its function.
|
|
3
|
+
// Keep this file free of platform APIs and dependencies.
|
|
4
|
+
export function selectPersonalizationVariant(request, route, claims, experiments, identity) {
|
|
5
|
+
return resolvePersonalizationDecision(request, route, claims, experiments, identity).variant;
|
|
6
|
+
}
|
|
7
|
+
export function resolvePersonalizationDecision(request, route, claims, experiments, identity) {
|
|
8
|
+
if (!route)
|
|
9
|
+
return { variant: null, experiment: null };
|
|
10
|
+
let variant = selectRuleVariant(request, route, claims);
|
|
11
|
+
let experiment = null;
|
|
12
|
+
if (identity && identity.usable && route.variants && experiments) {
|
|
13
|
+
const candidates = Object.values(experiments)
|
|
14
|
+
.filter((item) => (item.kind === 'ab' || item.kind === 'abn') && item.status === 'running')
|
|
15
|
+
.filter((item) => routeHasExperiment(route, item.id))
|
|
16
|
+
.sort((left, right) => (right.priority || 0) - (left.priority || 0)
|
|
17
|
+
|| String(left.id || '').localeCompare(String(right.id || '')));
|
|
18
|
+
for (let index = 0; index < candidates.length; index++) {
|
|
19
|
+
const item = candidates[index];
|
|
20
|
+
const assignment = assignExperiment(item, identity);
|
|
21
|
+
const candidate = assignment && route.variants[assignment.variantKey];
|
|
22
|
+
if (!candidate)
|
|
23
|
+
continue;
|
|
24
|
+
if (!variant || betterVariant(candidate.priority || 0, candidate.variantKey, variant.priority || 0, variant.variantKey)) {
|
|
25
|
+
variant = candidate;
|
|
26
|
+
experiment = assignment;
|
|
27
|
+
}
|
|
28
|
+
break;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return { variant: variant || null, experiment };
|
|
32
|
+
}
|
|
33
|
+
function selectRuleVariant(request, route, claims) {
|
|
34
|
+
if (!route.variants)
|
|
35
|
+
return null;
|
|
36
|
+
const matches = [];
|
|
37
|
+
for (const key in route.variants) {
|
|
38
|
+
const variant = route.variants[key];
|
|
39
|
+
const values = variant.variantValues || variantValuesFromKey(key);
|
|
40
|
+
if (!requestDimensionsMatch(request, values))
|
|
41
|
+
continue;
|
|
42
|
+
const membershipMatch = (values.audience && claims && (claims.aud || []).includes(values.audience))
|
|
43
|
+
|| (values.segment && claims && (claims.seg || []).includes(values.segment));
|
|
44
|
+
const ruleMatch = variant.rule ? evaluateRule(request, variant.rule) : false;
|
|
45
|
+
if (membershipMatch || ruleMatch)
|
|
46
|
+
matches.push(variant);
|
|
47
|
+
}
|
|
48
|
+
matches.sort((left, right) => (right.priority || 0) - (left.priority || 0)
|
|
49
|
+
|| String(left.variantKey || '').localeCompare(String(right.variantKey || '')));
|
|
50
|
+
return matches[0] || null;
|
|
51
|
+
}
|
|
52
|
+
// CloudFront KVS values are capped at 1 KiB. The variant map key is the
|
|
53
|
+
// canonical encoding of its dimensions, so compact route records omit the
|
|
54
|
+
// duplicated variantValues object and recover it at the edge.
|
|
55
|
+
function variantValuesFromKey(key) {
|
|
56
|
+
const values = {};
|
|
57
|
+
const parts = String(key || '').replace(/&/g, ';').split(';');
|
|
58
|
+
for (let index = 0; index < parts.length; index++) {
|
|
59
|
+
const separator = parts[index].indexOf('=');
|
|
60
|
+
if (separator > 0)
|
|
61
|
+
values[parts[index].slice(0, separator)] = parts[index].slice(separator + 1);
|
|
62
|
+
}
|
|
63
|
+
return values;
|
|
64
|
+
}
|
|
65
|
+
function routeHasExperiment(route, experimentID) {
|
|
66
|
+
for (const key in route.variants || {}) {
|
|
67
|
+
const values = route.variants[key].variantValues || variantValuesFromKey(key);
|
|
68
|
+
if (values.experiment === experimentID)
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
function assignExperiment(experiment, identity) {
|
|
74
|
+
const version = String(experiment.allocationVersion || '').trim();
|
|
75
|
+
const salt = String(experiment.salt || '').trim();
|
|
76
|
+
const siteID = String(identity.siteId || '').trim();
|
|
77
|
+
const identityKey = `${String(identity.namespace || '').trim()}:${String(identity.identityHash || '').trim()}`;
|
|
78
|
+
if (!version || !salt || !siteID || identityKey === ':')
|
|
79
|
+
return null;
|
|
80
|
+
const input = [siteID, String(experiment.id || ''), version, identityKey, salt].join('\0');
|
|
81
|
+
const bucket = sha256Bucket(input);
|
|
82
|
+
let cumulative = 0;
|
|
83
|
+
const arms = experiment.arms || [];
|
|
84
|
+
for (let index = 0; index < arms.length; index++) {
|
|
85
|
+
const arm = arms[index];
|
|
86
|
+
if ((arm.allocationBasisPoints || 0) <= 0)
|
|
87
|
+
continue;
|
|
88
|
+
cumulative += arm.allocationBasisPoints;
|
|
89
|
+
if (bucket < cumulative) {
|
|
90
|
+
return {
|
|
91
|
+
experimentId: experiment.id,
|
|
92
|
+
armId: arm.id,
|
|
93
|
+
allocationVersion: version,
|
|
94
|
+
variantKey: arm.variantKey,
|
|
95
|
+
bucket,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
function betterVariant(candidatePriority, candidateKey, bestPriority, bestKey) {
|
|
102
|
+
if (candidatePriority !== bestPriority)
|
|
103
|
+
return candidatePriority > bestPriority;
|
|
104
|
+
return String(candidateKey || '').localeCompare(String(bestKey || '')) < 0;
|
|
105
|
+
}
|
|
106
|
+
function requestDimensionsMatch(request, values) {
|
|
107
|
+
for (const rawKey in values) {
|
|
108
|
+
const rawWant = values[rawKey];
|
|
109
|
+
const key = String(rawKey).trim();
|
|
110
|
+
const normalized = key.toLowerCase();
|
|
111
|
+
const want = String(rawWant).trim();
|
|
112
|
+
if (!key || !want || ['audience', 'segment', 'experiment', 'experimentarm'].includes(normalized))
|
|
113
|
+
continue;
|
|
114
|
+
const candidates = (request.query[key] || []).concat(request.query['aci_' + key] || [], request.headers['x-aci-' + normalized] || '', request.headers['x-gradial-' + normalized] || '', request.cookies[key] || '', request.cookies['aci_' + key] || '');
|
|
115
|
+
if (normalized === 'locale') {
|
|
116
|
+
const locales = String(request.headers['accept-language'] || '')
|
|
117
|
+
.split(',').map((part) => part.split(';')[0].trim()).filter(Boolean);
|
|
118
|
+
for (let localeIndex = 0; localeIndex < locales.length; localeIndex++)
|
|
119
|
+
candidates.push(locales[localeIndex]);
|
|
120
|
+
}
|
|
121
|
+
if (!candidates.some((candidate) => normalized === 'locale'
|
|
122
|
+
? String(candidate).toLowerCase() === want.toLowerCase()
|
|
123
|
+
: String(candidate) === want))
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
return true;
|
|
127
|
+
}
|
|
128
|
+
function evaluateRule(request, rule) {
|
|
129
|
+
const all = rule.all || [];
|
|
130
|
+
const any = rule.any || [];
|
|
131
|
+
if (all.length + any.length === 0)
|
|
132
|
+
return false;
|
|
133
|
+
return all.every((condition) => evaluateCondition(request, condition))
|
|
134
|
+
&& (any.length === 0 || any.some((condition) => evaluateCondition(request, condition)));
|
|
135
|
+
}
|
|
136
|
+
function evaluateCondition(request, condition) {
|
|
137
|
+
const values = signalValues(request, condition.field);
|
|
138
|
+
if (condition.operator === 'exists')
|
|
139
|
+
return values.length > 0;
|
|
140
|
+
if (values.length === 0 || !['string', 'number', 'boolean'].includes(typeof condition.value))
|
|
141
|
+
return false;
|
|
142
|
+
const expected = String(condition.value);
|
|
143
|
+
return values.some((value) => {
|
|
144
|
+
switch (condition.operator) {
|
|
145
|
+
case 'equals': return value === expected;
|
|
146
|
+
case 'contains': return value.includes(expected);
|
|
147
|
+
case 'startsWith': return value.startsWith(expected);
|
|
148
|
+
default: return false;
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
function signalValues(request, field) {
|
|
153
|
+
if (field.startsWith('query.'))
|
|
154
|
+
return request.query[field.slice(6)] || [];
|
|
155
|
+
if (field.startsWith('header.'))
|
|
156
|
+
return request.headers[field.slice(7).toLowerCase()] ? [request.headers[field.slice(7).toLowerCase()]] : [];
|
|
157
|
+
if (field.startsWith('cookie.'))
|
|
158
|
+
return request.cookies[field.slice(7)] ? [request.cookies[field.slice(7)]] : [];
|
|
159
|
+
if (field.startsWith('utm.'))
|
|
160
|
+
return request.query['utm_' + field.slice(4)] || [];
|
|
161
|
+
if (field.startsWith('geo.')) {
|
|
162
|
+
const geo = request.signals && request.signals.geo;
|
|
163
|
+
const value = geo && geo[field.slice(4).toLowerCase()];
|
|
164
|
+
return value ? [value] : [];
|
|
165
|
+
}
|
|
166
|
+
if (field === 'referrer') {
|
|
167
|
+
const value = request.headers.referer || request.headers.referrer;
|
|
168
|
+
return value ? [value] : [];
|
|
169
|
+
}
|
|
170
|
+
return [];
|
|
171
|
+
}
|
|
172
|
+
// SHA-256 mirrors experiment.assignment.go: the first eight digest bytes are
|
|
173
|
+
// interpreted big-endian and reduced modulo 10,000 basis points.
|
|
174
|
+
// CLOUDFRONT_STRIP_START — the build script removes this block for CloudFront
|
|
175
|
+
// Functions (experiment bucketing moves to the hosting Lambda to stay under
|
|
176
|
+
// the 10 KiB function size limit).
|
|
177
|
+
function sha256Bucket(value) {
|
|
178
|
+
const bytes = utf8(value);
|
|
179
|
+
const words = new Uint32Array(64);
|
|
180
|
+
const bitLength = bytes.length * 8;
|
|
181
|
+
const paddedLength = ((bytes.length + 9 + 63) >> 6) << 6;
|
|
182
|
+
const data = new Uint8Array(paddedLength);
|
|
183
|
+
data.set(bytes);
|
|
184
|
+
data[bytes.length] = 128;
|
|
185
|
+
// The message length is a 64-bit big-endian integer across the final
|
|
186
|
+
// eight bytes. The previous code wrote a single truncated byte into each
|
|
187
|
+
// word's most significant position, corrupting every digest (caught by
|
|
188
|
+
// the assignment parity vectors, spec experiment-variant-caching REQ-9).
|
|
189
|
+
const bitsHigh = Math.floor(bitLength / 0x100000000);
|
|
190
|
+
const bitsLow = bitLength >>> 0;
|
|
191
|
+
data[paddedLength - 8] = (bitsHigh >>> 24) & 255;
|
|
192
|
+
data[paddedLength - 7] = (bitsHigh >>> 16) & 255;
|
|
193
|
+
data[paddedLength - 6] = (bitsHigh >>> 8) & 255;
|
|
194
|
+
data[paddedLength - 5] = bitsHigh & 255;
|
|
195
|
+
data[paddedLength - 4] = (bitsLow >>> 24) & 255;
|
|
196
|
+
data[paddedLength - 3] = (bitsLow >>> 16) & 255;
|
|
197
|
+
data[paddedLength - 2] = (bitsLow >>> 8) & 255;
|
|
198
|
+
data[paddedLength - 1] = bitsLow & 255;
|
|
199
|
+
let a = 0x6a09e667, b = 0xbb67ae85, c = 0x3c6ef372, d = 0xa54ff53a;
|
|
200
|
+
let e = 0x510e527f, f = 0x9b05688c, g = 0x1f83d9ab, h = 0x5be0cd19;
|
|
201
|
+
const k = [0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2];
|
|
202
|
+
for (let offset = 0; offset < paddedLength; offset += 64) {
|
|
203
|
+
for (let i = 0; i < 16; i++) {
|
|
204
|
+
const p = offset + i * 4;
|
|
205
|
+
words[i] = (data[p] << 24) | (data[p + 1] << 16) | (data[p + 2] << 8) | data[p + 3];
|
|
206
|
+
}
|
|
207
|
+
for (let i = 16; i < 64; i++) {
|
|
208
|
+
const x = words[i - 15], y = words[i - 2];
|
|
209
|
+
words[i] = (words[i - 16] + (rotr(x, 7) ^ rotr(x, 18) ^ (x >>> 3)) + words[i - 7] + (rotr(y, 17) ^ rotr(y, 19) ^ (y >>> 10))) >>> 0;
|
|
210
|
+
}
|
|
211
|
+
let aa = a, bb = b, cc = c, dd = d, ee = e, ff = f, gg = g, hh = h;
|
|
212
|
+
for (let i = 0; i < 64; i++) {
|
|
213
|
+
const s1 = rotr(ee, 6) ^ rotr(ee, 11) ^ rotr(ee, 25);
|
|
214
|
+
const ch = (ee & ff) ^ (~ee & gg);
|
|
215
|
+
const t1 = (hh + s1 + ch + k[i] + words[i]) >>> 0;
|
|
216
|
+
const s0 = rotr(aa, 2) ^ rotr(aa, 13) ^ rotr(aa, 22);
|
|
217
|
+
const maj = (aa & bb) ^ (aa & cc) ^ (bb & cc);
|
|
218
|
+
const t2 = (s0 + maj) >>> 0;
|
|
219
|
+
hh = gg;
|
|
220
|
+
gg = ff;
|
|
221
|
+
ff = ee;
|
|
222
|
+
ee = (dd + t1) >>> 0;
|
|
223
|
+
dd = cc;
|
|
224
|
+
cc = bb;
|
|
225
|
+
bb = aa;
|
|
226
|
+
aa = (t1 + t2) >>> 0;
|
|
227
|
+
}
|
|
228
|
+
a = (a + aa) >>> 0;
|
|
229
|
+
b = (b + bb) >>> 0;
|
|
230
|
+
c = (c + cc) >>> 0;
|
|
231
|
+
d = (d + dd) >>> 0;
|
|
232
|
+
e = (e + ee) >>> 0;
|
|
233
|
+
f = (f + ff) >>> 0;
|
|
234
|
+
g = (g + gg) >>> 0;
|
|
235
|
+
h = (h + hh) >>> 0;
|
|
236
|
+
}
|
|
237
|
+
// Reduce the 64-bit big-endian value modulo 10000 in 32-bit halves:
|
|
238
|
+
// (hi*2^32 + lo) % 10000 == ((hi%10000) * (2^32 % 10000) + lo%10000) % 10000,
|
|
239
|
+
// and 2^32 % 10000 = 7296. Multiplying hi by 2^32 directly exceeds
|
|
240
|
+
// Number.MAX_SAFE_INTEGER and rounds away the low bits — that float error
|
|
241
|
+
// made most JS buckets diverge from assignment.go (caught by the
|
|
242
|
+
// assignment parity vectors, spec experiment-variant-caching REQ-9).
|
|
243
|
+
return (((a >>> 0) % 10000) * 7296 + ((b >>> 0) % 10000)) % 10000;
|
|
244
|
+
}
|
|
245
|
+
function rotr(value, amount) { return (value >>> amount) | (value << (32 - amount)); }
|
|
246
|
+
function utf8(value) {
|
|
247
|
+
const out = [];
|
|
248
|
+
for (let i = 0; i < value.length; i++) {
|
|
249
|
+
let code = value.charCodeAt(i);
|
|
250
|
+
if (code >= 0xd800 && code <= 0xdbff && i + 1 < value.length) {
|
|
251
|
+
const next = value.charCodeAt(++i);
|
|
252
|
+
code = 0x10000 + ((code - 0xd800) << 10) + (next - 0xdc00);
|
|
253
|
+
}
|
|
254
|
+
if (code < 0x80)
|
|
255
|
+
out.push(code);
|
|
256
|
+
else if (code < 0x800)
|
|
257
|
+
out.push(0xc0 | (code >> 6), 0x80 | (code & 63));
|
|
258
|
+
else if (code < 0x10000)
|
|
259
|
+
out.push(0xe0 | (code >> 12), 0x80 | ((code >> 6) & 63), 0x80 | (code & 63));
|
|
260
|
+
else
|
|
261
|
+
out.push(0xf0 | (code >> 18), 0x80 | ((code >> 12) & 63), 0x80 | ((code >> 6) & 63), 0x80 | (code & 63));
|
|
262
|
+
}
|
|
263
|
+
return new Uint8Array(out);
|
|
264
|
+
}
|
|
265
|
+
// CLOUDFRONT_STRIP_END
|
package/dist/index.d.ts
CHANGED
|
@@ -15,3 +15,4 @@ export { AssetSchema, isImage, isAsset, } from './types/media.js';
|
|
|
15
15
|
export * from './audience-data-models.js';
|
|
16
16
|
export * from './define-data-model.js';
|
|
17
17
|
export * from './types/index.js';
|
|
18
|
+
export { mountConsentBanner, hasConsentCookie, type ConsentBannerOptions, } from './consent-banner/index.js';
|
package/dist/index.js
CHANGED
|
@@ -9,6 +9,7 @@ export { AssetSchema, isImage, isAsset, } from './types/media.js';
|
|
|
9
9
|
export * from './audience-data-models.js';
|
|
10
10
|
export * from './define-data-model.js';
|
|
11
11
|
export * from './types/index.js';
|
|
12
|
+
export { mountConsentBanner, hasConsentCookie, } from './consent-banner/index.js';
|
|
12
13
|
// Note: content/ is NOT re-exported here because it imports node:path,
|
|
13
14
|
// making it incompatible with client-side bundlers. Import server-only
|
|
14
15
|
// content utilities from '@gradial/aci/content' explicitly.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type AssignmentsPayloadSource } from '../content/personalization.js';
|
|
2
|
+
export interface AssignmentsScriptProps {
|
|
3
|
+
page: AssignmentsPayloadSource | null | undefined;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* AssignmentsScript renders the page-level experiment assignment tag
|
|
7
|
+
* (`<script type="application/json" data-aci-assignments>` with a
|
|
8
|
+
* {"v":1,"experiments":[…]} body, personalization-reporting design/04) when
|
|
9
|
+
* the page payload carries a build-time `experiments` block, and nothing
|
|
10
|
+
* otherwise. createPage appends it to the page tree exactly once per page;
|
|
11
|
+
* sites composing their own pages can render it directly. The JSON body is
|
|
12
|
+
* HTML-safe (assignmentsScriptJSON \u-escapes angle brackets).
|
|
13
|
+
*/
|
|
14
|
+
export declare function AssignmentsScript({ page }: AssignmentsScriptProps): import("react").JSX.Element | null;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { assignmentsScriptJSON, } from '../content/personalization.js';
|
|
3
|
+
/**
|
|
4
|
+
* AssignmentsScript renders the page-level experiment assignment tag
|
|
5
|
+
* (`<script type="application/json" data-aci-assignments>` with a
|
|
6
|
+
* {"v":1,"experiments":[…]} body, personalization-reporting design/04) when
|
|
7
|
+
* the page payload carries a build-time `experiments` block, and nothing
|
|
8
|
+
* otherwise. createPage appends it to the page tree exactly once per page;
|
|
9
|
+
* sites composing their own pages can render it directly. The JSON body is
|
|
10
|
+
* HTML-safe (assignmentsScriptJSON \u-escapes angle brackets).
|
|
11
|
+
*/
|
|
12
|
+
export function AssignmentsScript({ page }) {
|
|
13
|
+
const json = assignmentsScriptJSON(page);
|
|
14
|
+
if (!json)
|
|
15
|
+
return null;
|
|
16
|
+
return (_jsx("script", { type: "application/json", "data-aci-assignments": "", dangerouslySetInnerHTML: { __html: json } }));
|
|
17
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type IdentitySigningKeys, type PersonalizationCollectionStore } from './personalization-host.js';
|
|
2
|
+
export interface PersonalizationCollectRouteConfig {
|
|
3
|
+
siteId: string;
|
|
4
|
+
store: PersonalizationCollectionStore;
|
|
5
|
+
identityKeys: IdentitySigningKeys;
|
|
6
|
+
/**
|
|
7
|
+
* Claims verify keys (kid -> secret) for withdrawal EVIDENCE
|
|
8
|
+
* (anonymous-experiments REQ-6): suppression fires only when the request
|
|
9
|
+
* presents a signature-verified aci_claims cookie — presence alone is
|
|
10
|
+
* spoofable, and under universal minting an unverified trigger would
|
|
11
|
+
* suppress every reject-all visitor. Empty = evidence never verifies =
|
|
12
|
+
* suppression disabled (fails safe).
|
|
13
|
+
*/
|
|
14
|
+
claimsKeys?: Record<string, string>;
|
|
15
|
+
retentionSeconds?: number;
|
|
16
|
+
secureCookies?: boolean;
|
|
17
|
+
now?: () => Date;
|
|
18
|
+
}
|
|
19
|
+
export declare function createPersonalizationCollectRoute(config: PersonalizationCollectRouteConfig): (request: Request) => Promise<Response>;
|