@1consent/react 0.0.0-stage → 0.1.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 1Consent
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,47 @@
1
- # Temporary Holding Version
1
+ # @1consent/react
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ React 19+ integration for 1Consent. It renders the synchronous CMP embed,
4
+ keeps third-party markup inert until consent, and exposes reactive consent
5
+ state without making server-rendered HTML visitor-specific.
6
+
7
+ ## Distribution status
8
+
9
+ The first supported release line is `0.1.0-beta.*` on npm's `beta` dist-tag.
10
+ The protected public-repository workflow stages each release with provenance
11
+ for a maintainer's npm 2FA approval. The first release uses a short-lived
12
+ stage-only token; later releases use trusted publishing. Before a release is
13
+ approved and verified against the registry, test the reviewed local tarball.
14
+
15
+ After verification, beta consumers install an explicit prerelease channel:
16
+
17
+ ```sh
18
+ npm install @1consent/react@beta
19
+ ```
20
+
21
+ ## Public API
22
+
23
+ - `OneConsentProvider` embeds the CMP and framework-associated fast path.
24
+ - `OneConsent` gates plain script, iframe, pixel, or other intrinsic markup by
25
+ configured service or category.
26
+ - `useConsent` returns the current decision and reacts to CMP updates.
27
+ - `OneConsentServiceMap` and `OneConsentCategoryMap` are declaration-merging
28
+ targets populated by `1c typegen`.
29
+
30
+ The generated declaration files are included in the package. React is the
31
+ only peer dependency, Zod is the only public runtime dependency, and 1Consent
32
+ workspace implementation packages are bundled into the JavaScript artifact
33
+ instead of becoming consumer dependencies.
34
+
35
+ See the 1Consent developer reference for the complete API and integration
36
+ guidance.
37
+
38
+ ## Before publication
39
+
40
+ From this repository's root, pack the reviewed artifact into an existing directory:
41
+
42
+ ```sh
43
+ npm pack ./packages/libs/react --pack-destination /path/to/tarballs
44
+ ```
45
+
46
+ Install that tarball in a React 19+ test project. This repository contains the
47
+ reviewed distribution artifacts; it does not contain the private source build.
@@ -0,0 +1,13 @@
1
+ /**
2
+ * @1consent/react (1CO-150)
3
+ *
4
+ * Framework-agnostic React integration for 1Consent. Works in any React 19+
5
+ * context: Next.js (App or Pages Router), Vite SPAs, Remix/React Router,
6
+ * Astro islands. No Next.js-specific APIs are used.
7
+ */
8
+ export type { OneConsentProps } from "./one-consent.js";
9
+ export { OneConsent } from "./one-consent.js";
10
+ export type { OneConsentEnvironment, OneConsentProviderProps } from "./provider.js";
11
+ export { OneConsentProvider } from "./provider.js";
12
+ export type { OneConsentCategoryMap, OneConsentServiceMap, RegisteredCategoryName, RegisteredServiceName, } from "./types.js";
13
+ export { useConsent } from "./use-consent.js";
package/dist/index.js ADDED
@@ -0,0 +1,559 @@
1
+ // ../../contracts/dist/consent-data-governance.js
2
+ var CONSENT_MAX_AGE_DAYS = 390;
3
+ var CONSENT_MAX_AGE_MS = CONSENT_MAX_AGE_DAYS * 24 * 60 * 60 * 1e3;
4
+ function isConsentExpiryFresh(expiresAt, now = Date.now()) {
5
+ if (!Number.isSafeInteger(expiresAt) || expiresAt <= 0)
6
+ return false;
7
+ if (!Number.isSafeInteger(now) || now < 0)
8
+ return false;
9
+ const latestPermittedExpiry = now + CONSENT_MAX_AGE_MS;
10
+ if (!Number.isSafeInteger(latestPermittedExpiry))
11
+ return false;
12
+ return expiresAt >= now && expiresAt <= latestPermittedExpiry;
13
+ }
14
+
15
+ // ../../contracts/dist/browser-storage.js
16
+ var BROWSER_STORAGE_KEY = "1consent";
17
+ var BROWSER_STORAGE_SCHEMA_VERSION = 1;
18
+ function emptyBrowserStore() {
19
+ return { schemaVersion: BROWSER_STORAGE_SCHEMA_VERSION, apps: {} };
20
+ }
21
+ function isRecord(value) {
22
+ return typeof value === "object" && value !== null && !Array.isArray(value);
23
+ }
24
+ function isOneConsentBrowserStore(value) {
25
+ return isRecord(value) && value.schemaVersion === BROWSER_STORAGE_SCHEMA_VERSION && isRecord(value.apps);
26
+ }
27
+ function parseBrowserStore(raw) {
28
+ if (!raw)
29
+ return emptyBrowserStore();
30
+ try {
31
+ const value = JSON.parse(raw);
32
+ return isOneConsentBrowserStore(value) ? value : emptyBrowserStore();
33
+ } catch {
34
+ return emptyBrowserStore();
35
+ }
36
+ }
37
+ function isBooleanRecord(value) {
38
+ if (!isRecord(value))
39
+ return false;
40
+ return Object.values(value).every((entry) => typeof entry === "boolean");
41
+ }
42
+ function isBrowserFastPathState(value) {
43
+ if (!isRecord(value))
44
+ return false;
45
+ return typeof value.structuralVersion === "string" && typeof value.consentEpoch === "number" && Number.isSafeInteger(value.consentEpoch) && value.consentEpoch >= 0 && typeof value.expiresAt === "number" && Number.isSafeInteger(value.expiresAt) && value.expiresAt >= 0 && isBooleanRecord(value.services) && isBooleanRecord(value.categories) && (value.decisionIntegrity === void 0 || typeof value.decisionIntegrity === "string") && (value.tcfCmpId === void 0 || typeof value.tcfCmpId === "number" && Number.isSafeInteger(value.tcfCmpId) && value.tcfCmpId >= 0 && value.tcfCmpId <= 4095) && (value.tcfValidationVersion === void 0 || typeof value.tcfValidationVersion === "number" && Number.isSafeInteger(value.tcfValidationVersion) && value.tcfValidationVersion > 0) && (value.tcfDisclosedVendorKey === void 0 || typeof value.tcfDisclosedVendorKey === "string" && /^(?:[1-9]\d*(?:,[1-9]\d*)*)?$/.test(value.tcfDisclosedVendorKey)) && (value.tcfApiDataIntegrity === void 0 || typeof value.tcfApiDataIntegrity === "string") && (value.gpcRevocable === void 0 || value.gpcRevocable === true) && (value.posture === void 0 || value.posture === true);
46
+ }
47
+ function isFreshBrowserFastPathState(value, now = Date.now()) {
48
+ if (!isBrowserFastPathState(value))
49
+ return false;
50
+ return isConsentExpiryFresh(value.expiresAt, now);
51
+ }
52
+
53
+ // ../../shared/dist/public-package-urls.js
54
+ var PUBLIC_PACKAGE_SERVICE_URLS = {
55
+ local: {
56
+ dashboard: "https://dashboard.1consent.local",
57
+ cmpApi: "https://cmp.1consent.local",
58
+ cmpBootstrap: "https://web.cmp.1consent.local"
59
+ },
60
+ develop: {
61
+ dashboard: "https://dashboard.1consent.dev",
62
+ cmpApi: "https://cmp.1consent.dev",
63
+ cmpBootstrap: "https://web.cmp.1consent.dev"
64
+ },
65
+ production: {
66
+ dashboard: "https://dashboard.1consent.app",
67
+ cmpApi: "https://cmp.1consent.app",
68
+ cmpBootstrap: "https://web.cmp.1consent.app"
69
+ }
70
+ };
71
+ function detectPublicPackageEnvironment() {
72
+ const explicit = (typeof process !== "undefined" && (process.env?.APP_ENV || process.env?.NEXT_PUBLIC_APP_ENV) || "").toLowerCase();
73
+ if (explicit === "local" || explicit === "develop" || explicit === "production") {
74
+ return explicit;
75
+ }
76
+ if (typeof window !== "undefined") {
77
+ const host = window.location.hostname;
78
+ if (host === "localhost" || host === "127.0.0.1" || host.endsWith(".local")) {
79
+ return "local";
80
+ }
81
+ if (host === "1consent.dev" || host.endsWith(".1consent.dev") || host.includes("develop") || host.includes("preview")) {
82
+ return "develop";
83
+ }
84
+ return "production";
85
+ }
86
+ const nodeEnv = process.env?.NODE_ENV;
87
+ if (nodeEnv === "development" || nodeEnv === "test")
88
+ return "local";
89
+ if (process.env?.VERCEL_ENV === "preview")
90
+ return "develop";
91
+ return "production";
92
+ }
93
+ function getPublicPackageServiceUrls(env) {
94
+ return PUBLIC_PACKAGE_SERVICE_URLS[env ?? detectPublicPackageEnvironment()];
95
+ }
96
+ var PUBLISH_STAGE_SUFFIX = {
97
+ preview: "preview.js",
98
+ live: "js",
99
+ staging: "staging.js"
100
+ };
101
+ function getCmpScriptUrl(appId, opts = {}) {
102
+ const stage = opts.stage ?? "live";
103
+ if (opts.frameworkId && stage !== "preview") {
104
+ throw new Error("Framework override is available in Preview only");
105
+ }
106
+ const urls = getPublicPackageServiceUrls(opts.env);
107
+ const baseUrl = stage === "preview" ? urls.cmpApi : opts.edge === false ? urls.cmpApi : urls.cmpBootstrap;
108
+ const base = `${baseUrl}/id/${appId}.${PUBLISH_STAGE_SUFFIX[stage]}`;
109
+ return opts.frameworkId ? `${base}?frameworkId=${opts.frameworkId}` : base;
110
+ }
111
+
112
+ // ../../contracts/dist/consent-fastpath.js
113
+ var SERVICE_TEMPLATE_ATTRS = ["data-1c-service", "data-1c-service-tag", "data-1c-tag"];
114
+ var CATEGORY_TEMPLATE_ATTR = "data-1c-category";
115
+ var ONCE_TEMPLATE_ATTR = "data-1c-once";
116
+ function fnv1a36(input) {
117
+ let hash = 2166136261;
118
+ for (let i = 0; i < input.length; i++) {
119
+ hash ^= input.charCodeAt(i);
120
+ hash = hash + ((hash << 1) + (hash << 4) + (hash << 7) + (hash << 8) + (hash << 24)) >>> 0;
121
+ }
122
+ return hash.toString(36);
123
+ }
124
+ function writeAlias(map, key, granted) {
125
+ map[key] = key in map ? map[key] === true && granted : granted;
126
+ }
127
+ function buildConsentAliasMaps(services, categories, serviceConsent, categoryConsent) {
128
+ const state = buildConsentAliasState(services, categories, serviceConsent, categoryConsent);
129
+ return { services: state.services, categories: state.categories };
130
+ }
131
+ function buildConsentAliasState(services, categories, serviceConsent, categoryConsent) {
132
+ const serviceAliases = {};
133
+ const memberCounts = /* @__PURE__ */ new Map();
134
+ let nonEssentialGranted = false;
135
+ for (const service of services) {
136
+ const granted = service.essential === true || serviceConsent[service.id] === true;
137
+ if (granted && service.essential !== true)
138
+ nonEssentialGranted = true;
139
+ writeAlias(serviceAliases, service.id, granted);
140
+ if (service.catalogServiceKey) {
141
+ writeAlias(serviceAliases, service.catalogServiceKey, granted);
142
+ }
143
+ if (service.tag)
144
+ writeAlias(serviceAliases, service.tag, granted);
145
+ if (service.name)
146
+ writeAlias(serviceAliases, service.name, granted);
147
+ if (service.categoryId) {
148
+ const counts = memberCounts.get(service.categoryId) ?? { total: 0, granted: 0 };
149
+ counts.total += 1;
150
+ if (granted)
151
+ counts.granted += 1;
152
+ memberCounts.set(service.categoryId, counts);
153
+ }
154
+ }
155
+ const categoryAliases = {};
156
+ for (const category of categories) {
157
+ const counts = memberCounts.get(category.id);
158
+ const derived = counts !== void 0 && counts.total > 0 && counts.granted === counts.total;
159
+ const granted = category.essential === true || categoryConsent[category.id] === true || derived;
160
+ if (granted && category.essential !== true && categoryConsent[category.id] === true) {
161
+ nonEssentialGranted = true;
162
+ }
163
+ writeAlias(categoryAliases, category.id, granted);
164
+ writeAlias(categoryAliases, category.name, granted);
165
+ }
166
+ return {
167
+ services: serviceAliases,
168
+ categories: categoryAliases,
169
+ nonEssentialGranted
170
+ };
171
+ }
172
+ function hasGpcSignal() {
173
+ if (typeof navigator === "undefined")
174
+ return false;
175
+ const nav = navigator;
176
+ return nav.globalPrivacyControl === true;
177
+ }
178
+
179
+ // src/serialize.ts
180
+ import { Children, isValidElement } from "react";
181
+ var VOID_ELEMENTS = /* @__PURE__ */ new Set([
182
+ "area",
183
+ "base",
184
+ "br",
185
+ "col",
186
+ "embed",
187
+ "hr",
188
+ "img",
189
+ "input",
190
+ "link",
191
+ "meta",
192
+ "source",
193
+ "track",
194
+ "wbr"
195
+ ]);
196
+ var RAW_TEXT_ELEMENTS = /* @__PURE__ */ new Set(["script", "style"]);
197
+ var SKIPPED_PROPS = /* @__PURE__ */ new Set(["children", "key", "ref", "dangerouslySetInnerHTML"]);
198
+ var PROP_TO_ATTR = {
199
+ className: "class",
200
+ htmlFor: "for",
201
+ acceptCharset: "accept-charset",
202
+ httpEquiv: "http-equiv"
203
+ };
204
+ var UNITLESS_STYLE_PROPS = /* @__PURE__ */ new Set([
205
+ "animationIterationCount",
206
+ "aspectRatio",
207
+ "columnCount",
208
+ "columns",
209
+ "flex",
210
+ "flexGrow",
211
+ "flexShrink",
212
+ "fontWeight",
213
+ "gridArea",
214
+ "gridColumn",
215
+ "gridColumnEnd",
216
+ "gridColumnStart",
217
+ "gridRow",
218
+ "gridRowEnd",
219
+ "gridRowStart",
220
+ "lineClamp",
221
+ "lineHeight",
222
+ "opacity",
223
+ "order",
224
+ "orphans",
225
+ "scale",
226
+ "tabSize",
227
+ "widows",
228
+ "zIndex",
229
+ "zoom"
230
+ ]);
231
+ var VALID_ATTR_NAME = /^[a-zA-Z_][a-zA-Z0-9_:.-]*$/;
232
+ function escapeText(value) {
233
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
234
+ }
235
+ function escapeAttribute(value) {
236
+ return escapeText(value).replace(/"/g, "&quot;");
237
+ }
238
+ function attributeName(prop) {
239
+ const mapped = PROP_TO_ATTR[prop];
240
+ if (mapped) return mapped;
241
+ return prop.toLowerCase();
242
+ }
243
+ function serializeStyle(style) {
244
+ const declarations = [];
245
+ for (const [prop, value] of Object.entries(style)) {
246
+ if (value === null || value === void 0 || value === "") continue;
247
+ const cssProp = prop.startsWith("--") ? prop : prop.replace(/[A-Z]/g, (ch) => `-${ch.toLowerCase()}`);
248
+ const css = typeof value === "number" && value !== 0 && !UNITLESS_STYLE_PROPS.has(prop) ? `${value}px` : String(value);
249
+ declarations.push(`${cssProp}:${css}`);
250
+ }
251
+ return declarations.join(";");
252
+ }
253
+ function isPlainObject(x) {
254
+ return typeof x === "object" && x !== null && !Array.isArray(x);
255
+ }
256
+ function serializeProps(tag, props) {
257
+ let html = "";
258
+ for (const [prop, value] of Object.entries(props)) {
259
+ if (SKIPPED_PROPS.has(prop)) continue;
260
+ if (value === null || value === void 0 || value === false) continue;
261
+ if (typeof value === "function") {
262
+ throw new Error(
263
+ `[1Consent] <OneConsent> children cannot carry event handlers or function props (found "${prop}" on <${tag}>). Consent-gated content is inert HTML \u2014 attach behavior inside the script itself.`
264
+ );
265
+ }
266
+ if (prop === "style") {
267
+ if (!isPlainObject(value)) continue;
268
+ const css = serializeStyle(value);
269
+ if (css) html += ` style="${escapeAttribute(css)}"`;
270
+ continue;
271
+ }
272
+ const attr = attributeName(prop);
273
+ if (!VALID_ATTR_NAME.test(attr)) {
274
+ throw new Error(
275
+ `[1Consent] Invalid attribute name "${prop}" on <${tag}> inside <OneConsent> \u2014 attribute names must match ${String(VALID_ATTR_NAME)}.`
276
+ );
277
+ }
278
+ if (value === true) {
279
+ html += ` ${attr}`;
280
+ } else {
281
+ html += ` ${attr}="${escapeAttribute(String(value))}"`;
282
+ }
283
+ }
284
+ return html;
285
+ }
286
+ function serializeNode(node, rawText) {
287
+ if (node === null || node === void 0 || typeof node === "boolean") return "";
288
+ if (typeof node === "string" || typeof node === "number") {
289
+ const text = String(node);
290
+ if (rawText) {
291
+ if (/<\/(script|style)/i.test(text)) {
292
+ throw new Error(
293
+ "[1Consent] Inline script/style content must not contain a closing `</script>`/`</style>` sequence \u2014 split or escape it (e.g. `<\\/script>`)."
294
+ );
295
+ }
296
+ return text;
297
+ }
298
+ return escapeText(text);
299
+ }
300
+ if (Array.isArray(node)) {
301
+ return node.map((child) => serializeNode(child, rawText)).join("");
302
+ }
303
+ if (isValidElement(node)) {
304
+ const { type } = node;
305
+ if (typeof type !== "string") {
306
+ throw new Error(
307
+ "[1Consent] <OneConsent> children must be plain HTML elements (<script>, <iframe>, <img>, \u2026). Component children cannot be gated as inert HTML \u2014 wrap the component's own output instead."
308
+ );
309
+ }
310
+ const props = isPlainObject(node.props) ? node.props : {};
311
+ const tag = type.toLowerCase();
312
+ let html = `<${tag}${serializeProps(tag, props)}>`;
313
+ if (VOID_ELEMENTS.has(tag)) return html;
314
+ const innerHtml = props.dangerouslySetInnerHTML;
315
+ if (isPlainObject(innerHtml) && typeof innerHtml.__html === "string") {
316
+ html += innerHtml.__html;
317
+ } else if ("children" in props && props.children !== void 0) {
318
+ if (!isReactNode(props.children)) {
319
+ throw new Error(
320
+ `[1Consent] Unsupported children on <${tag}> inside <OneConsent> \u2014 expected plain HTML, text, or arrays thereof.`
321
+ );
322
+ }
323
+ html += serializeNode(props.children, RAW_TEXT_ELEMENTS.has(tag));
324
+ }
325
+ return `${html}</${tag}>`;
326
+ }
327
+ throw new Error("[1Consent] Unsupported child in <OneConsent> \u2014 expected plain HTML.");
328
+ }
329
+ function isReactNode(x) {
330
+ return x === null || x === void 0 || typeof x === "string" || typeof x === "number" || typeof x === "boolean" || Array.isArray(x) || isValidElement(x);
331
+ }
332
+ function serializeChildren(children) {
333
+ return Children.toArray(children).map((child) => serializeNode(child, false)).join("");
334
+ }
335
+
336
+ // src/one-consent.tsx
337
+ import { jsx } from "react/jsx-runtime";
338
+ var SERVICE_ATTR = SERVICE_TEMPLATE_ATTRS[0] ?? "data-1c-service";
339
+ function OneConsent(props) {
340
+ const { nonce, children } = props;
341
+ const attr = props.service !== void 0 ? SERVICE_ATTR : CATEGORY_TEMPLATE_ATTR;
342
+ const key = props.service !== void 0 ? props.service : props.category;
343
+ if (typeof key !== "string" || key.length === 0) {
344
+ throw new Error(
345
+ "[1Consent] <OneConsent> requires a non-empty `service` or `category` prop."
346
+ );
347
+ }
348
+ const content = serializeChildren(children);
349
+ if (/<\/template/i.test(content)) {
350
+ throw new Error(
351
+ "[1Consent] <OneConsent> children must not contain a nested `</template>`."
352
+ );
353
+ }
354
+ const onceAttr = props.once ? ` ${ONCE_TEMPLATE_ATTR}="${fnv1a36(`${attr}=${key}|${content}`)}"` : "";
355
+ const nonceAttr = nonce ? ` nonce="${escapeAttribute(nonce)}"` : "";
356
+ const html = `<template ${attr}="${escapeAttribute(key)}"${onceAttr}>${content}</template><script${nonceAttr}>window.__1CONSENT_REACT_FASTPATH__&&window.__1CONSENT_REACT_FASTPATH__.resolve(document.currentScript.previousElementSibling);</script>`;
357
+ return /* @__PURE__ */ jsx(
358
+ "div",
359
+ {
360
+ "data-1c-slot": "",
361
+ style: { display: "contents" },
362
+ suppressHydrationWarning: true,
363
+ dangerouslySetInnerHTML: { __html: html }
364
+ }
365
+ );
366
+ }
367
+
368
+ // ../react-fastpath/dist/generated/engine-source.js
369
+ var REACT_FASTPATH_SOURCE = '"use strict";(()=>{(()=>{var re,oe,se,ce,ae,ie,le,ue;let c=window,g=document,I=["data-1c-service","data-1c-service-tag","data-1c-tag"],F="data-1c-category",H="data-1c-resolved",fe="data-1c-once",q="1consent:config",de=33696e6,ge=I.concat(F).map(e=>`template[${e}]:not([${H}])`).join(","),u=e=>typeof e=="object"&&e!==null&&!Array.isArray(e),pe=e=>{let t=2166136261;for(let n=0;n<e.length;n++)t^=e.charCodeAt(n),t=t+((t<<1)+(t<<4)+(t<<7)+(t<<8)+(t<<24))>>>0;return t.toString(36)},_e=e=>{var r,i;let t=((r=e.services)!=null?r:[]).map(o=>{var l,s,N;return`${o.id}\\0${(l=o.name)!=null?l:""}\\0${(s=o.tag)!=null?s:""}\\0${(N=o.categoryId)!=null?N:""}\\0${o.essential?1:0}${o.catalogServiceKey?`\\0k\\0${o.catalogServiceKey}`:""}${o.usSensitive?"\\x001":""}`}).sort(),n=((i=e.categories)!=null?i:[]).map(o=>`${o.id}\\0${o.name}\\0${o.essential?1:0}`).sort();return pe(`s:${t.join("")}|c:${n.join("")}`)},me=((re=g.currentScript)==null?void 0:re.getAttribute("data-1c-app-id"))||"",ye=((oe=g.currentScript)==null?void 0:oe.getAttribute("data-1c-framework-versions"))||"{}",L={};try{L=JSON.parse(ye)}catch{}let a=c.__1CONSENT_CONFIG__,M=(se=a==null?void 0:a.consentEpoch)!=null?se:0,E=a==null?void 0:a.framework.type,h=a==null?void 0:a.machine,v=(ce=a==null?void 0:a.languages)==null?void 0:ce.primary,C=a!==void 0&&a.appId===me&&(E==="gdpr"||E==="tcf"||E==="us")&&(h==="P"||h==="S"||h==="D")&&typeof v=="string"&&v.length>0&&Number.isSafeInteger(M)&&M>=0,p=C?a:void 0,A=(ae=p==null?void 0:p.appId)!=null?ae:"",m=(ie=p==null?void 0:p.framework.id)!=null?ie:"",R=C&&E!=null?E:"",k=C&&h!=null?h:"",O=C&&v!=null?v:"",B=u(L)?L[m]:void 0,b=typeof B=="string"?B:"",y=C?M:-1,Q=JSON.stringify([A,m,R,k,O,b,y]),$=!1,D=!1,V,j,K,U=!1,X=()=>{var e,t;try{let n=c.__1CONSENT_CONFIG__;return $||D||!n||A.length===0||m.length===0||y<0||n.appId!==A||n.framework.id!==m||n.framework.type!==R||n.machine!==k||((e=n.languages)==null?void 0:e.primary)!==O||((t=n.consentEpoch)!=null?t:0)!==y||b.length===0?!1:((n!==V||n.services!==j||n.categories!==K)&&(V=n,j=n.services,K=n.categories,U=_e(n)===b),U)}catch{return!1}},S=c.__1CONSENT_REACT_FASTPATH__;if(S){try{if(S.runtimeKey===Q&&X()&&typeof S.reconcile=="function"&&S.reconcile())return}catch{}try{typeof S.destroy=="function"&&S.destroy()}catch{}c.__1CONSENT_REACT_FASTPATH__===S&&delete c.__1CONSENT_REACT_FASTPATH__}let f=null,d=null,_,w,x=()=>{},T=()=>$||D?!1:X()?!0:(D=!0,f=null,d=null,_==null||_.disconnect(),x(),c.__1CONSENT_REACT_FASTPATH__===w&&delete c.__1CONSENT_REACT_FASTPATH__,!1),Y=new Set,z=e=>{if(!e||typeof e!="object")return!1;for(let t of Object.values(e))if(typeof t!="boolean")return!1;return!0},Se=()=>navigator.globalPrivacyControl===!0;try{let e=localStorage.getItem("1consent");if(e&&A&&m&&b&&y>=0){let t=Date.now(),n=t+de,r=JSON.parse(e),i=u(r)&&u(r.apps)?r.apps[A]:void 0,o=u(i)?i.frameworks:void 0,l=u(o)?o[m]:void 0,s=u(l)?l.fastPath:void 0,N=u(l)?l.consent:void 0,J=u(l)&&"consentEpoch"in l?l.consentEpoch:0,be=u(l)&&l.type===R&&l.machine===k&&typeof J=="number"&&Number.isSafeInteger(J)&&J>=y&&l.primaryLanguage===O,Ne=(p==null?void 0:p.framework.type)!=="tcf"||u(N)&&typeof N.tcString=="string"&&((ue=(le=c.__1cTcf)==null?void 0:le.tcData)==null?void 0:ue.eventStatus)==="tcloaded"&&c.__1cTcf.tcData.tcString===N.tcString;u(r)&&r.schemaVersion===1&&be&&u(s)&&s.structuralVersion===b&&typeof s.consentEpoch=="number"&&Number.isSafeInteger(s.consentEpoch)&&s.consentEpoch>=y&&Number.isSafeInteger(t)&&t>=0&&Number.isSafeInteger(n)&&typeof s.expiresAt=="number"&&Number.isSafeInteger(s.expiresAt)&&s.expiresAt>=t&&s.expiresAt<=n&&z(s.services)&&Ne&&!(s.gpcRevocable===!0&&Se())&&(f=s.services,d=z(s.categories)?s.categories:{})}}catch{}let Te=e=>{for(let t of I){let n=e.getAttribute(t);if(n!==null)return n}return e.getAttribute(F)},Ee=e=>{for(let n of I){let r=e.getAttribute(n);if(r!==null)return f!==null&&f[r]===!0}let t=e.getAttribute(F);return t!==null?d!==null&&d[t]===!0:!1},he=e=>{let t=g.createElement("script");for(let n of e.attributes)t.setAttribute(n.name,n.value);return e.src?e.hasAttribute("async")||(t.async=!1):t.text=e.text||"",t},P=e=>{if(!T()||e.hasAttribute(H)||!Ee(e))return!1;e.setAttribute(H,"");let t=e.getAttribute(fe);if(t!==null){if(Y.has(t))return!1;Y.add(t)}let n=e.content.cloneNode(!0);if(!(n instanceof DocumentFragment))return!1;for(let r of n.querySelectorAll("script"))r.replaceWith(he(r));return e.parentNode&&e.parentNode.insertBefore(n,e.nextSibling),!0},G=e=>e instanceof HTMLTemplateElement,Z=8,Ae=()=>new Promise(e=>{let t=c.scheduler;t!=null&&t.postTask?t.postTask(e,{priority:"user-visible"}):setTimeout(e,0)}),W=e=>{if(!T())return[];let t=[];for(let i of e.querySelectorAll(ge))G(i)&&t.push(i);let n=[],r=0;for(;r<t.length&&r<Z;r++){let i=t[r];if(i&&P(i)){let o=Te(i);o!==null&&n.push(o)}}return r<t.length&&(async()=>{for(;r<t.length;r++){r%Z===0&&await Ae();let i=t[r];if(i)try{P(i)}catch{}}})(),n},ee=!1,te=()=>T()?g.readyState!=="loading"?W(g):(ee||(ee=!0,g.addEventListener("DOMContentLoaded",()=>W(g),{once:!0})),[]):[];_=new MutationObserver(e=>{if(!(!T()||g.readyState==="loading"||f===null&&d===null))for(let t of e)for(let n of t.addedNodes)G(n)?P(n):n instanceof Element&&W(n)}),_.observe(g.documentElement,{childList:!0,subtree:!0});let ne=()=>{V=void 0,j=void 0,K=void 0,T()};w={appId:A,frameworkId:m,frameworkType:R,machine:k,primaryLanguage:O,structuralVersion:b,consentEpoch:y,runtimeKey:Q,apply(e,t){if(!T())return[];f=f||{},d=d||{};for(let n of Object.keys(e))f[n]=e[n]===!0;if(t)for(let n of Object.keys(t))d[n]=t[n]===!0;return te()},resolve(e){return e!==null&&G(e)?P(e):!1},reconcile:T,destroy:()=>{$=!0,f=null,d=null,_==null||_.disconnect(),x(),c.__1CONSENT_REACT_FASTPATH__===w&&delete c.__1CONSENT_REACT_FASTPATH__}},c.__1CONSENT_REACT_FASTPATH__=w,c.addEventListener(q,ne),x=()=>c.removeEventListener(q,ne),(f!==null||d!==null)&&te()})();})();';
370
+
371
+ // src/provider.tsx
372
+ import { Fragment, jsx as jsx2, jsxs } from "react/jsx-runtime";
373
+ function resolveEnvAppId() {
374
+ if (typeof process === "undefined" || !process?.env) return void 0;
375
+ return process.env.NEXT_PUBLIC_1CONSENT_APP_ID;
376
+ }
377
+ function OneConsentProvider({
378
+ appId,
379
+ fastPathVersions,
380
+ nonce,
381
+ cmpSrc,
382
+ // Auto-detection maps a customer's NODE_ENV=development to 1Consent's
383
+ // internal "local" zone (unreachable outside our stack) — default to
384
+ // production and let internal setups override explicitly.
385
+ environment = "production",
386
+ loadCmp = true,
387
+ children
388
+ }) {
389
+ const resolvedAppId = appId ?? resolveEnvAppId();
390
+ const resolvedCmpSrc = cmpSrc ?? (resolvedAppId !== void 0 ? getCmpScriptUrl(resolvedAppId, { env: environment }) : void 0);
391
+ if (loadCmp && resolvedCmpSrc === void 0) {
392
+ throw new Error(
393
+ "[1Consent] Missing App ID: pass the `appId` prop to <OneConsentProvider> or set the NEXT_PUBLIC_1CONSENT_APP_ID environment variable."
394
+ );
395
+ }
396
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
397
+ loadCmp && resolvedCmpSrc !== void 0 && /* @__PURE__ */ jsx2("script", { src: resolvedCmpSrc, nonce }),
398
+ /* @__PURE__ */ jsx2(
399
+ "script",
400
+ {
401
+ "data-1c-react-fastpath": "",
402
+ "data-1c-app-id": resolvedAppId ?? "",
403
+ "data-1c-framework-versions": JSON.stringify(fastPathVersions ?? {}),
404
+ nonce,
405
+ dangerouslySetInnerHTML: { __html: REACT_FASTPATH_SOURCE }
406
+ }
407
+ ),
408
+ children
409
+ ] });
410
+ }
411
+
412
+ // src/use-consent.ts
413
+ import { useEffect, useState } from "react";
414
+ function readMirrorConsent(key) {
415
+ try {
416
+ const api = window.__1CONSENT_REACT_FASTPATH__;
417
+ if (!api?.appId || !api.frameworkId || !api.structuralVersion || !Number.isSafeInteger(api.consentEpoch) || api.consentEpoch < 0)
418
+ return false;
419
+ const fastPathWindow = window;
420
+ const config = fastPathWindow.__1CONSENT_CONFIG__;
421
+ const liveEpoch = config?.consentEpoch ?? 0;
422
+ if (!config || config.appId !== api.appId || config.framework.id !== api.frameworkId || !Number.isSafeInteger(liveEpoch) || liveEpoch < 0 || liveEpoch !== api.consentEpoch || config.languages.primary.length === 0) {
423
+ return false;
424
+ }
425
+ const root = parseBrowserStore(localStorage.getItem(BROWSER_STORAGE_KEY));
426
+ const framework = root.apps[api.appId]?.frameworks[api.frameworkId];
427
+ if (!framework || framework.type !== config.framework.type || framework.machine !== config.machine || (framework.consentEpoch ?? 0) < liveEpoch || framework.primaryLanguage !== config.languages.primary) {
428
+ return false;
429
+ }
430
+ if (config.framework.type === "tcf") {
431
+ const tcString = framework.consent?.tcString;
432
+ const tcData = fastPathWindow.__1cTcf?.tcData;
433
+ if (typeof tcString !== "string" || tcData?.eventStatus !== "tcloaded" || tcData.tcString !== tcString) {
434
+ return false;
435
+ }
436
+ }
437
+ const fastPath = framework.fastPath;
438
+ if (!isFreshBrowserFastPathState(fastPath)) return false;
439
+ if (fastPath.structuralVersion !== api.structuralVersion) return false;
440
+ if (fastPath.consentEpoch < api.consentEpoch) return false;
441
+ if (fastPath.gpcRevocable === true && hasGpcSignal()) return false;
442
+ return fastPath.services[key] === true || fastPath.categories[key] === true;
443
+ } catch {
444
+ return false;
445
+ }
446
+ }
447
+ function isConsentEventBus(x) {
448
+ if (!x || typeof x !== "object") return false;
449
+ if (!("on" in x) || typeof x.on !== "function") return false;
450
+ if ("off" in x && x.off !== void 0 && typeof x.off !== "function") return false;
451
+ return true;
452
+ }
453
+ function getBus() {
454
+ if (typeof window === "undefined") return void 0;
455
+ const controller = window.__1consent;
456
+ if (!controller || typeof controller !== "object") return void 0;
457
+ if (!("bus" in controller)) return void 0;
458
+ return isConsentEventBus(controller.bus) ? controller.bus : void 0;
459
+ }
460
+ function isBooleanRecord2(value) {
461
+ return typeof value === "object" && value !== null && Object.values(value).every((entry) => typeof entry === "boolean");
462
+ }
463
+ function getLiveConsentState() {
464
+ if (typeof window === "undefined") return void 0;
465
+ const controller = window.__1consent;
466
+ if (!controller || typeof controller !== "object" || !("engine" in controller)) {
467
+ return void 0;
468
+ }
469
+ const engine = controller.engine;
470
+ if (!engine || typeof engine !== "object" || !("getEnforcementConsentState" in engine) || typeof engine.getEnforcementConsentState !== "function") {
471
+ return void 0;
472
+ }
473
+ const state = engine.getEnforcementConsentState();
474
+ if (!state || typeof state !== "object" || !("categories" in state) || !("services" in state)) {
475
+ return void 0;
476
+ }
477
+ return isBooleanRecord2(state.categories) && isBooleanRecord2(state.services) ? { categories: state.categories, services: state.services } : void 0;
478
+ }
479
+ function readLiveConsent(key) {
480
+ try {
481
+ const fastPathWindow = window;
482
+ const config = fastPathWindow.__1CONSENT_CONFIG__;
483
+ const state = getLiveConsentState();
484
+ if (!config || !state) return void 0;
485
+ const aliases = buildConsentAliasMaps(
486
+ config.services,
487
+ config.categories,
488
+ state.services,
489
+ state.categories
490
+ );
491
+ return aliases.services[key] === true || aliases.categories[key] === true;
492
+ } catch {
493
+ return void 0;
494
+ }
495
+ }
496
+ var busWaiters = /* @__PURE__ */ new Set();
497
+ var busPoller;
498
+ function whenBusAvailable(listener) {
499
+ const bus = getBus();
500
+ if (bus) {
501
+ listener(bus);
502
+ return () => void 0;
503
+ }
504
+ busWaiters.add(listener);
505
+ if (busPoller === void 0) {
506
+ busPoller = setInterval(() => {
507
+ const found = getBus();
508
+ if (!found) return;
509
+ clearInterval(busPoller);
510
+ busPoller = void 0;
511
+ for (const waiter of busWaiters) waiter(found);
512
+ busWaiters.clear();
513
+ }, 250);
514
+ }
515
+ return () => {
516
+ busWaiters.delete(listener);
517
+ if (busWaiters.size === 0 && busPoller !== void 0) {
518
+ clearInterval(busPoller);
519
+ busPoller = void 0;
520
+ }
521
+ };
522
+ }
523
+ function useConsent(key) {
524
+ const [granted, setGranted] = useState(false);
525
+ useEffect(() => {
526
+ setGranted(readMirrorConsent(key));
527
+ let disposed = false;
528
+ let attached = null;
529
+ const cancelWait = whenBusAvailable((bus) => {
530
+ if (disposed) return;
531
+ const mirrorHandler = () => {
532
+ if (!disposed) setGranted(readMirrorConsent(key));
533
+ };
534
+ const externalHandler = () => {
535
+ if (!disposed) setGranted(readLiveConsent(key) ?? readMirrorConsent(key));
536
+ };
537
+ bus.on("consent:changed", mirrorHandler);
538
+ bus.on("consent:saved", mirrorHandler);
539
+ bus.on("consent:external", externalHandler);
540
+ attached = { bus, mirrorHandler, externalHandler };
541
+ mirrorHandler();
542
+ });
543
+ return () => {
544
+ disposed = true;
545
+ cancelWait();
546
+ if (attached?.bus.off) {
547
+ attached.bus.off("consent:changed", attached.mirrorHandler);
548
+ attached.bus.off("consent:saved", attached.mirrorHandler);
549
+ attached.bus.off("consent:external", attached.externalHandler);
550
+ }
551
+ };
552
+ }, [key]);
553
+ return granted;
554
+ }
555
+ export {
556
+ OneConsent,
557
+ OneConsentProvider,
558
+ useConsent
559
+ };
@@ -0,0 +1,57 @@
1
+ /**
2
+ * @1consent/react — <OneConsent> (1CO-150)
3
+ *
4
+ * Wraps arbitrary third-party markup — a script, pixel, iframe, embed, or any
5
+ * plain HTML — and withholds it until consent:
6
+ *
7
+ * ```tsx
8
+ * <OneConsent service="Google Analytics">
9
+ * <script src="https://www.googletagmanager.com/gtag/js?id=G-XXX" />
10
+ * </OneConsent>
11
+ * ```
12
+ *
13
+ * Rendering strategy (identical on server and client — hydration-safe):
14
+ * a wrapper `<div style="display:contents">` whose innerHTML holds the inert
15
+ * `<template data-1c-service="…">` plus a one-line inline call into the
16
+ * React fastpath. React never reconciles inside `dangerouslySetInnerHTML`
17
+ * content, so the engine can insert promoted nodes there without hydration
18
+ * mismatches. On the server-rendered page the inline call executes at HTML
19
+ * parse time (before first paint); on SPA mounts innerHTML scripts never run
20
+ * and the engine's MutationObserver promotes the template instead.
21
+ *
22
+ * Head placement caveat: the wrapper `<div>` is not valid inside `<head>` —
23
+ * the HTML parser silently ends `<head>` at the div and reparents it (and
24
+ * everything after it) into `<body>`, which triggers a dev-mode hydration
25
+ * mismatch. Promotion works either way, but top-of-`<body>` placement is
26
+ * just as early and keeps the markup valid.
27
+ */
28
+ import type { ReactNode } from "react";
29
+ import type { RegisteredCategoryName, RegisteredServiceName } from "./types.js";
30
+ interface BaseProps {
31
+ /** CSP nonce for the inline parse-time resolve call. */
32
+ nonce?: string;
33
+ /**
34
+ * Promote this block at most once per page load (1CO-169).
35
+ *
36
+ * By default a remounted `<OneConsent>` (SPA navigation, conditional
37
+ * render) re-promotes: correct for visual content and UI-rendering
38
+ * scripts, whose promoted DOM was destroyed with the old mount. Opt in
39
+ * with `once` for global side-effect scripts (analytics loaders, pixels)
40
+ * that must not re-execute on remount. Blocks with the same gating key
41
+ * and identical children share a dedupe key — intentionally: "execute
42
+ * once" extends to duplicate copies of the same snippet.
43
+ */
44
+ once?: boolean;
45
+ children?: ReactNode;
46
+ }
47
+ export type OneConsentProps = BaseProps & ({
48
+ /** Service name, tag, or id as configured in the dashboard. */
49
+ service: RegisteredServiceName;
50
+ category?: never;
51
+ } | {
52
+ /** Category name or id as configured in the dashboard. */
53
+ category: RegisteredCategoryName;
54
+ service?: never;
55
+ });
56
+ export declare function OneConsent(props: OneConsentProps): import("react").JSX.Element;
57
+ export {};
@@ -0,0 +1,43 @@
1
+ /**
2
+ * @1consent/react — Provider (1CO-150)
3
+ *
4
+ * Server-renderable (RSC-compatible, no client APIs at render time). Renders:
5
+ * 1. the synchronous CMP embed config (autoblocker + live framework/epoch),
6
+ * 2. the inline React accelerator with framework-associated build stamps,
7
+ * 3. children.
8
+ *
9
+ * The server never reads consent: output is byte-identical for every visitor,
10
+ * so static rendering, ISR, and CDN caching are fully preserved.
11
+ */
12
+ import type { ReactNode } from "react";
13
+ export type OneConsentEnvironment = "local" | "develop" | "production";
14
+ export interface OneConsentProviderProps {
15
+ /**
16
+ * 1Consent application short id. Falls back to the
17
+ * `NEXT_PUBLIC_1CONSENT_APP_ID` environment variable where available.
18
+ */
19
+ appId?: string;
20
+ /**
21
+ * Framework-associated structural versions from `1c typegen`. The hosted
22
+ * config resolves the visitor's framework synchronously, then the inline
23
+ * accelerator selects only that framework's build stamp. Without the map
24
+ * the fast path stays cold and the CMP resolves templates after it loads.
25
+ */
26
+ fastPathVersions?: Readonly<Record<string, string>>;
27
+ /** CSP nonce forwarded to the inline engine script. */
28
+ nonce?: string;
29
+ /** Override the CMP embed script URL (derived from `appId` by default). */
30
+ cmpSrc?: string;
31
+ /**
32
+ * 1Consent environment the CMP embed is loaded from (default:
33
+ * `"production"`). Only 1Consent-internal setups need `"develop"`/`"local"`.
34
+ */
35
+ environment?: OneConsentEnvironment;
36
+ /**
37
+ * Load the CMP embed script (default true). Set to false when the embed
38
+ * is added by other means (e.g. GTM).
39
+ */
40
+ loadCmp?: boolean;
41
+ children?: ReactNode;
42
+ }
43
+ export declare function OneConsentProvider({ appId, fastPathVersions, nonce, cmpSrc, environment, loadCmp, children, }: OneConsentProviderProps): import("react").JSX.Element;
@@ -0,0 +1,13 @@
1
+ /**
2
+ * React Server Components entrypoint.
3
+ *
4
+ * `OneConsent` and `OneConsentProvider` only emit static markup and are safe
5
+ * to render on the server. `useConsent` is intentionally absent because it is
6
+ * a client hook; keeping it out of this conditional export prevents its
7
+ * React state/effect imports from contaminating an RSC dependency graph.
8
+ */
9
+ export type { OneConsentProps } from "./one-consent.js";
10
+ export { OneConsent } from "./one-consent.js";
11
+ export type { OneConsentEnvironment, OneConsentProviderProps } from "./provider.js";
12
+ export { OneConsentProvider } from "./provider.js";
13
+ export type { OneConsentCategoryMap, OneConsentServiceMap, RegisteredCategoryName, RegisteredServiceName, } from "./types.js";
@@ -0,0 +1,308 @@
1
+ // ../../shared/dist/public-package-urls.js
2
+ var PUBLIC_PACKAGE_SERVICE_URLS = {
3
+ local: {
4
+ dashboard: "https://dashboard.1consent.local",
5
+ cmpApi: "https://cmp.1consent.local",
6
+ cmpBootstrap: "https://web.cmp.1consent.local"
7
+ },
8
+ develop: {
9
+ dashboard: "https://dashboard.1consent.dev",
10
+ cmpApi: "https://cmp.1consent.dev",
11
+ cmpBootstrap: "https://web.cmp.1consent.dev"
12
+ },
13
+ production: {
14
+ dashboard: "https://dashboard.1consent.app",
15
+ cmpApi: "https://cmp.1consent.app",
16
+ cmpBootstrap: "https://web.cmp.1consent.app"
17
+ }
18
+ };
19
+ function detectPublicPackageEnvironment() {
20
+ const explicit = (typeof process !== "undefined" && (process.env?.APP_ENV || process.env?.NEXT_PUBLIC_APP_ENV) || "").toLowerCase();
21
+ if (explicit === "local" || explicit === "develop" || explicit === "production") {
22
+ return explicit;
23
+ }
24
+ if (typeof window !== "undefined") {
25
+ const host = window.location.hostname;
26
+ if (host === "localhost" || host === "127.0.0.1" || host.endsWith(".local")) {
27
+ return "local";
28
+ }
29
+ if (host === "1consent.dev" || host.endsWith(".1consent.dev") || host.includes("develop") || host.includes("preview")) {
30
+ return "develop";
31
+ }
32
+ return "production";
33
+ }
34
+ const nodeEnv = process.env?.NODE_ENV;
35
+ if (nodeEnv === "development" || nodeEnv === "test")
36
+ return "local";
37
+ if (process.env?.VERCEL_ENV === "preview")
38
+ return "develop";
39
+ return "production";
40
+ }
41
+ function getPublicPackageServiceUrls(env) {
42
+ return PUBLIC_PACKAGE_SERVICE_URLS[env ?? detectPublicPackageEnvironment()];
43
+ }
44
+ var PUBLISH_STAGE_SUFFIX = {
45
+ preview: "preview.js",
46
+ live: "js",
47
+ staging: "staging.js"
48
+ };
49
+ function getCmpScriptUrl(appId, opts = {}) {
50
+ const stage = opts.stage ?? "live";
51
+ if (opts.frameworkId && stage !== "preview") {
52
+ throw new Error("Framework override is available in Preview only");
53
+ }
54
+ const urls = getPublicPackageServiceUrls(opts.env);
55
+ const baseUrl = stage === "preview" ? urls.cmpApi : opts.edge === false ? urls.cmpApi : urls.cmpBootstrap;
56
+ const base = `${baseUrl}/id/${appId}.${PUBLISH_STAGE_SUFFIX[stage]}`;
57
+ return opts.frameworkId ? `${base}?frameworkId=${opts.frameworkId}` : base;
58
+ }
59
+
60
+ // ../../contracts/dist/consent-fastpath.js
61
+ var SERVICE_TEMPLATE_ATTRS = ["data-1c-service", "data-1c-service-tag", "data-1c-tag"];
62
+ var CATEGORY_TEMPLATE_ATTR = "data-1c-category";
63
+ var ONCE_TEMPLATE_ATTR = "data-1c-once";
64
+ function fnv1a36(input) {
65
+ let hash = 2166136261;
66
+ for (let i = 0; i < input.length; i++) {
67
+ hash ^= input.charCodeAt(i);
68
+ hash = hash + ((hash << 1) + (hash << 4) + (hash << 7) + (hash << 8) + (hash << 24)) >>> 0;
69
+ }
70
+ return hash.toString(36);
71
+ }
72
+
73
+ // src/serialize.ts
74
+ import { Children, isValidElement } from "react";
75
+ var VOID_ELEMENTS = /* @__PURE__ */ new Set([
76
+ "area",
77
+ "base",
78
+ "br",
79
+ "col",
80
+ "embed",
81
+ "hr",
82
+ "img",
83
+ "input",
84
+ "link",
85
+ "meta",
86
+ "source",
87
+ "track",
88
+ "wbr"
89
+ ]);
90
+ var RAW_TEXT_ELEMENTS = /* @__PURE__ */ new Set(["script", "style"]);
91
+ var SKIPPED_PROPS = /* @__PURE__ */ new Set(["children", "key", "ref", "dangerouslySetInnerHTML"]);
92
+ var PROP_TO_ATTR = {
93
+ className: "class",
94
+ htmlFor: "for",
95
+ acceptCharset: "accept-charset",
96
+ httpEquiv: "http-equiv"
97
+ };
98
+ var UNITLESS_STYLE_PROPS = /* @__PURE__ */ new Set([
99
+ "animationIterationCount",
100
+ "aspectRatio",
101
+ "columnCount",
102
+ "columns",
103
+ "flex",
104
+ "flexGrow",
105
+ "flexShrink",
106
+ "fontWeight",
107
+ "gridArea",
108
+ "gridColumn",
109
+ "gridColumnEnd",
110
+ "gridColumnStart",
111
+ "gridRow",
112
+ "gridRowEnd",
113
+ "gridRowStart",
114
+ "lineClamp",
115
+ "lineHeight",
116
+ "opacity",
117
+ "order",
118
+ "orphans",
119
+ "scale",
120
+ "tabSize",
121
+ "widows",
122
+ "zIndex",
123
+ "zoom"
124
+ ]);
125
+ var VALID_ATTR_NAME = /^[a-zA-Z_][a-zA-Z0-9_:.-]*$/;
126
+ function escapeText(value) {
127
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
128
+ }
129
+ function escapeAttribute(value) {
130
+ return escapeText(value).replace(/"/g, "&quot;");
131
+ }
132
+ function attributeName(prop) {
133
+ const mapped = PROP_TO_ATTR[prop];
134
+ if (mapped) return mapped;
135
+ return prop.toLowerCase();
136
+ }
137
+ function serializeStyle(style) {
138
+ const declarations = [];
139
+ for (const [prop, value] of Object.entries(style)) {
140
+ if (value === null || value === void 0 || value === "") continue;
141
+ const cssProp = prop.startsWith("--") ? prop : prop.replace(/[A-Z]/g, (ch) => `-${ch.toLowerCase()}`);
142
+ const css = typeof value === "number" && value !== 0 && !UNITLESS_STYLE_PROPS.has(prop) ? `${value}px` : String(value);
143
+ declarations.push(`${cssProp}:${css}`);
144
+ }
145
+ return declarations.join(";");
146
+ }
147
+ function isPlainObject(x) {
148
+ return typeof x === "object" && x !== null && !Array.isArray(x);
149
+ }
150
+ function serializeProps(tag, props) {
151
+ let html = "";
152
+ for (const [prop, value] of Object.entries(props)) {
153
+ if (SKIPPED_PROPS.has(prop)) continue;
154
+ if (value === null || value === void 0 || value === false) continue;
155
+ if (typeof value === "function") {
156
+ throw new Error(
157
+ `[1Consent] <OneConsent> children cannot carry event handlers or function props (found "${prop}" on <${tag}>). Consent-gated content is inert HTML \u2014 attach behavior inside the script itself.`
158
+ );
159
+ }
160
+ if (prop === "style") {
161
+ if (!isPlainObject(value)) continue;
162
+ const css = serializeStyle(value);
163
+ if (css) html += ` style="${escapeAttribute(css)}"`;
164
+ continue;
165
+ }
166
+ const attr = attributeName(prop);
167
+ if (!VALID_ATTR_NAME.test(attr)) {
168
+ throw new Error(
169
+ `[1Consent] Invalid attribute name "${prop}" on <${tag}> inside <OneConsent> \u2014 attribute names must match ${String(VALID_ATTR_NAME)}.`
170
+ );
171
+ }
172
+ if (value === true) {
173
+ html += ` ${attr}`;
174
+ } else {
175
+ html += ` ${attr}="${escapeAttribute(String(value))}"`;
176
+ }
177
+ }
178
+ return html;
179
+ }
180
+ function serializeNode(node, rawText) {
181
+ if (node === null || node === void 0 || typeof node === "boolean") return "";
182
+ if (typeof node === "string" || typeof node === "number") {
183
+ const text = String(node);
184
+ if (rawText) {
185
+ if (/<\/(script|style)/i.test(text)) {
186
+ throw new Error(
187
+ "[1Consent] Inline script/style content must not contain a closing `</script>`/`</style>` sequence \u2014 split or escape it (e.g. `<\\/script>`)."
188
+ );
189
+ }
190
+ return text;
191
+ }
192
+ return escapeText(text);
193
+ }
194
+ if (Array.isArray(node)) {
195
+ return node.map((child) => serializeNode(child, rawText)).join("");
196
+ }
197
+ if (isValidElement(node)) {
198
+ const { type } = node;
199
+ if (typeof type !== "string") {
200
+ throw new Error(
201
+ "[1Consent] <OneConsent> children must be plain HTML elements (<script>, <iframe>, <img>, \u2026). Component children cannot be gated as inert HTML \u2014 wrap the component's own output instead."
202
+ );
203
+ }
204
+ const props = isPlainObject(node.props) ? node.props : {};
205
+ const tag = type.toLowerCase();
206
+ let html = `<${tag}${serializeProps(tag, props)}>`;
207
+ if (VOID_ELEMENTS.has(tag)) return html;
208
+ const innerHtml = props.dangerouslySetInnerHTML;
209
+ if (isPlainObject(innerHtml) && typeof innerHtml.__html === "string") {
210
+ html += innerHtml.__html;
211
+ } else if ("children" in props && props.children !== void 0) {
212
+ if (!isReactNode(props.children)) {
213
+ throw new Error(
214
+ `[1Consent] Unsupported children on <${tag}> inside <OneConsent> \u2014 expected plain HTML, text, or arrays thereof.`
215
+ );
216
+ }
217
+ html += serializeNode(props.children, RAW_TEXT_ELEMENTS.has(tag));
218
+ }
219
+ return `${html}</${tag}>`;
220
+ }
221
+ throw new Error("[1Consent] Unsupported child in <OneConsent> \u2014 expected plain HTML.");
222
+ }
223
+ function isReactNode(x) {
224
+ return x === null || x === void 0 || typeof x === "string" || typeof x === "number" || typeof x === "boolean" || Array.isArray(x) || isValidElement(x);
225
+ }
226
+ function serializeChildren(children) {
227
+ return Children.toArray(children).map((child) => serializeNode(child, false)).join("");
228
+ }
229
+
230
+ // src/one-consent.tsx
231
+ import { jsx } from "react/jsx-runtime";
232
+ var SERVICE_ATTR = SERVICE_TEMPLATE_ATTRS[0] ?? "data-1c-service";
233
+ function OneConsent(props) {
234
+ const { nonce, children } = props;
235
+ const attr = props.service !== void 0 ? SERVICE_ATTR : CATEGORY_TEMPLATE_ATTR;
236
+ const key = props.service !== void 0 ? props.service : props.category;
237
+ if (typeof key !== "string" || key.length === 0) {
238
+ throw new Error(
239
+ "[1Consent] <OneConsent> requires a non-empty `service` or `category` prop."
240
+ );
241
+ }
242
+ const content = serializeChildren(children);
243
+ if (/<\/template/i.test(content)) {
244
+ throw new Error(
245
+ "[1Consent] <OneConsent> children must not contain a nested `</template>`."
246
+ );
247
+ }
248
+ const onceAttr = props.once ? ` ${ONCE_TEMPLATE_ATTR}="${fnv1a36(`${attr}=${key}|${content}`)}"` : "";
249
+ const nonceAttr = nonce ? ` nonce="${escapeAttribute(nonce)}"` : "";
250
+ const html = `<template ${attr}="${escapeAttribute(key)}"${onceAttr}>${content}</template><script${nonceAttr}>window.__1CONSENT_REACT_FASTPATH__&&window.__1CONSENT_REACT_FASTPATH__.resolve(document.currentScript.previousElementSibling);</script>`;
251
+ return /* @__PURE__ */ jsx(
252
+ "div",
253
+ {
254
+ "data-1c-slot": "",
255
+ style: { display: "contents" },
256
+ suppressHydrationWarning: true,
257
+ dangerouslySetInnerHTML: { __html: html }
258
+ }
259
+ );
260
+ }
261
+
262
+ // ../react-fastpath/dist/generated/engine-source.js
263
+ var REACT_FASTPATH_SOURCE = '"use strict";(()=>{(()=>{var re,oe,se,ce,ae,ie,le,ue;let c=window,g=document,I=["data-1c-service","data-1c-service-tag","data-1c-tag"],F="data-1c-category",H="data-1c-resolved",fe="data-1c-once",q="1consent:config",de=33696e6,ge=I.concat(F).map(e=>`template[${e}]:not([${H}])`).join(","),u=e=>typeof e=="object"&&e!==null&&!Array.isArray(e),pe=e=>{let t=2166136261;for(let n=0;n<e.length;n++)t^=e.charCodeAt(n),t=t+((t<<1)+(t<<4)+(t<<7)+(t<<8)+(t<<24))>>>0;return t.toString(36)},_e=e=>{var r,i;let t=((r=e.services)!=null?r:[]).map(o=>{var l,s,N;return`${o.id}\\0${(l=o.name)!=null?l:""}\\0${(s=o.tag)!=null?s:""}\\0${(N=o.categoryId)!=null?N:""}\\0${o.essential?1:0}${o.catalogServiceKey?`\\0k\\0${o.catalogServiceKey}`:""}${o.usSensitive?"\\x001":""}`}).sort(),n=((i=e.categories)!=null?i:[]).map(o=>`${o.id}\\0${o.name}\\0${o.essential?1:0}`).sort();return pe(`s:${t.join("")}|c:${n.join("")}`)},me=((re=g.currentScript)==null?void 0:re.getAttribute("data-1c-app-id"))||"",ye=((oe=g.currentScript)==null?void 0:oe.getAttribute("data-1c-framework-versions"))||"{}",L={};try{L=JSON.parse(ye)}catch{}let a=c.__1CONSENT_CONFIG__,M=(se=a==null?void 0:a.consentEpoch)!=null?se:0,E=a==null?void 0:a.framework.type,h=a==null?void 0:a.machine,v=(ce=a==null?void 0:a.languages)==null?void 0:ce.primary,C=a!==void 0&&a.appId===me&&(E==="gdpr"||E==="tcf"||E==="us")&&(h==="P"||h==="S"||h==="D")&&typeof v=="string"&&v.length>0&&Number.isSafeInteger(M)&&M>=0,p=C?a:void 0,A=(ae=p==null?void 0:p.appId)!=null?ae:"",m=(ie=p==null?void 0:p.framework.id)!=null?ie:"",R=C&&E!=null?E:"",k=C&&h!=null?h:"",O=C&&v!=null?v:"",B=u(L)?L[m]:void 0,b=typeof B=="string"?B:"",y=C?M:-1,Q=JSON.stringify([A,m,R,k,O,b,y]),$=!1,D=!1,V,j,K,U=!1,X=()=>{var e,t;try{let n=c.__1CONSENT_CONFIG__;return $||D||!n||A.length===0||m.length===0||y<0||n.appId!==A||n.framework.id!==m||n.framework.type!==R||n.machine!==k||((e=n.languages)==null?void 0:e.primary)!==O||((t=n.consentEpoch)!=null?t:0)!==y||b.length===0?!1:((n!==V||n.services!==j||n.categories!==K)&&(V=n,j=n.services,K=n.categories,U=_e(n)===b),U)}catch{return!1}},S=c.__1CONSENT_REACT_FASTPATH__;if(S){try{if(S.runtimeKey===Q&&X()&&typeof S.reconcile=="function"&&S.reconcile())return}catch{}try{typeof S.destroy=="function"&&S.destroy()}catch{}c.__1CONSENT_REACT_FASTPATH__===S&&delete c.__1CONSENT_REACT_FASTPATH__}let f=null,d=null,_,w,x=()=>{},T=()=>$||D?!1:X()?!0:(D=!0,f=null,d=null,_==null||_.disconnect(),x(),c.__1CONSENT_REACT_FASTPATH__===w&&delete c.__1CONSENT_REACT_FASTPATH__,!1),Y=new Set,z=e=>{if(!e||typeof e!="object")return!1;for(let t of Object.values(e))if(typeof t!="boolean")return!1;return!0},Se=()=>navigator.globalPrivacyControl===!0;try{let e=localStorage.getItem("1consent");if(e&&A&&m&&b&&y>=0){let t=Date.now(),n=t+de,r=JSON.parse(e),i=u(r)&&u(r.apps)?r.apps[A]:void 0,o=u(i)?i.frameworks:void 0,l=u(o)?o[m]:void 0,s=u(l)?l.fastPath:void 0,N=u(l)?l.consent:void 0,J=u(l)&&"consentEpoch"in l?l.consentEpoch:0,be=u(l)&&l.type===R&&l.machine===k&&typeof J=="number"&&Number.isSafeInteger(J)&&J>=y&&l.primaryLanguage===O,Ne=(p==null?void 0:p.framework.type)!=="tcf"||u(N)&&typeof N.tcString=="string"&&((ue=(le=c.__1cTcf)==null?void 0:le.tcData)==null?void 0:ue.eventStatus)==="tcloaded"&&c.__1cTcf.tcData.tcString===N.tcString;u(r)&&r.schemaVersion===1&&be&&u(s)&&s.structuralVersion===b&&typeof s.consentEpoch=="number"&&Number.isSafeInteger(s.consentEpoch)&&s.consentEpoch>=y&&Number.isSafeInteger(t)&&t>=0&&Number.isSafeInteger(n)&&typeof s.expiresAt=="number"&&Number.isSafeInteger(s.expiresAt)&&s.expiresAt>=t&&s.expiresAt<=n&&z(s.services)&&Ne&&!(s.gpcRevocable===!0&&Se())&&(f=s.services,d=z(s.categories)?s.categories:{})}}catch{}let Te=e=>{for(let t of I){let n=e.getAttribute(t);if(n!==null)return n}return e.getAttribute(F)},Ee=e=>{for(let n of I){let r=e.getAttribute(n);if(r!==null)return f!==null&&f[r]===!0}let t=e.getAttribute(F);return t!==null?d!==null&&d[t]===!0:!1},he=e=>{let t=g.createElement("script");for(let n of e.attributes)t.setAttribute(n.name,n.value);return e.src?e.hasAttribute("async")||(t.async=!1):t.text=e.text||"",t},P=e=>{if(!T()||e.hasAttribute(H)||!Ee(e))return!1;e.setAttribute(H,"");let t=e.getAttribute(fe);if(t!==null){if(Y.has(t))return!1;Y.add(t)}let n=e.content.cloneNode(!0);if(!(n instanceof DocumentFragment))return!1;for(let r of n.querySelectorAll("script"))r.replaceWith(he(r));return e.parentNode&&e.parentNode.insertBefore(n,e.nextSibling),!0},G=e=>e instanceof HTMLTemplateElement,Z=8,Ae=()=>new Promise(e=>{let t=c.scheduler;t!=null&&t.postTask?t.postTask(e,{priority:"user-visible"}):setTimeout(e,0)}),W=e=>{if(!T())return[];let t=[];for(let i of e.querySelectorAll(ge))G(i)&&t.push(i);let n=[],r=0;for(;r<t.length&&r<Z;r++){let i=t[r];if(i&&P(i)){let o=Te(i);o!==null&&n.push(o)}}return r<t.length&&(async()=>{for(;r<t.length;r++){r%Z===0&&await Ae();let i=t[r];if(i)try{P(i)}catch{}}})(),n},ee=!1,te=()=>T()?g.readyState!=="loading"?W(g):(ee||(ee=!0,g.addEventListener("DOMContentLoaded",()=>W(g),{once:!0})),[]):[];_=new MutationObserver(e=>{if(!(!T()||g.readyState==="loading"||f===null&&d===null))for(let t of e)for(let n of t.addedNodes)G(n)?P(n):n instanceof Element&&W(n)}),_.observe(g.documentElement,{childList:!0,subtree:!0});let ne=()=>{V=void 0,j=void 0,K=void 0,T()};w={appId:A,frameworkId:m,frameworkType:R,machine:k,primaryLanguage:O,structuralVersion:b,consentEpoch:y,runtimeKey:Q,apply(e,t){if(!T())return[];f=f||{},d=d||{};for(let n of Object.keys(e))f[n]=e[n]===!0;if(t)for(let n of Object.keys(t))d[n]=t[n]===!0;return te()},resolve(e){return e!==null&&G(e)?P(e):!1},reconcile:T,destroy:()=>{$=!0,f=null,d=null,_==null||_.disconnect(),x(),c.__1CONSENT_REACT_FASTPATH__===w&&delete c.__1CONSENT_REACT_FASTPATH__}},c.__1CONSENT_REACT_FASTPATH__=w,c.addEventListener(q,ne),x=()=>c.removeEventListener(q,ne),(f!==null||d!==null)&&te()})();})();';
264
+
265
+ // src/provider.tsx
266
+ import { Fragment, jsx as jsx2, jsxs } from "react/jsx-runtime";
267
+ function resolveEnvAppId() {
268
+ if (typeof process === "undefined" || !process?.env) return void 0;
269
+ return process.env.NEXT_PUBLIC_1CONSENT_APP_ID;
270
+ }
271
+ function OneConsentProvider({
272
+ appId,
273
+ fastPathVersions,
274
+ nonce,
275
+ cmpSrc,
276
+ // Auto-detection maps a customer's NODE_ENV=development to 1Consent's
277
+ // internal "local" zone (unreachable outside our stack) — default to
278
+ // production and let internal setups override explicitly.
279
+ environment = "production",
280
+ loadCmp = true,
281
+ children
282
+ }) {
283
+ const resolvedAppId = appId ?? resolveEnvAppId();
284
+ const resolvedCmpSrc = cmpSrc ?? (resolvedAppId !== void 0 ? getCmpScriptUrl(resolvedAppId, { env: environment }) : void 0);
285
+ if (loadCmp && resolvedCmpSrc === void 0) {
286
+ throw new Error(
287
+ "[1Consent] Missing App ID: pass the `appId` prop to <OneConsentProvider> or set the NEXT_PUBLIC_1CONSENT_APP_ID environment variable."
288
+ );
289
+ }
290
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
291
+ loadCmp && resolvedCmpSrc !== void 0 && /* @__PURE__ */ jsx2("script", { src: resolvedCmpSrc, nonce }),
292
+ /* @__PURE__ */ jsx2(
293
+ "script",
294
+ {
295
+ "data-1c-react-fastpath": "",
296
+ "data-1c-app-id": resolvedAppId ?? "",
297
+ "data-1c-framework-versions": JSON.stringify(fastPathVersions ?? {}),
298
+ nonce,
299
+ dangerouslySetInnerHTML: { __html: REACT_FASTPATH_SOURCE }
300
+ }
301
+ ),
302
+ children
303
+ ] });
304
+ }
305
+ export {
306
+ OneConsent,
307
+ OneConsentProvider
308
+ };
@@ -0,0 +1,25 @@
1
+ /**
2
+ * @1consent/react — Typed service/category names (1CO-150 / 1CO-151)
3
+ *
4
+ * These empty interfaces are augmented by the typegen CLI's generated module
5
+ * (`1c typegen`), turning `service`/`category` props into unions of the app's
6
+ * actual configured names. Without generated types they stay empty and the
7
+ * props fall back to `string`.
8
+ *
9
+ * Generated augmentation example:
10
+ * ```ts
11
+ * declare module "@1consent/react" {
12
+ * interface OneConsentServiceMap {
13
+ * "Google Analytics": true;
14
+ * }
15
+ * }
16
+ * ```
17
+ */
18
+ export interface OneConsentServiceMap {
19
+ }
20
+ export interface OneConsentCategoryMap {
21
+ }
22
+ /** Service name union when typegen ran; otherwise `string`. */
23
+ export type RegisteredServiceName = keyof OneConsentServiceMap extends never ? string : keyof OneConsentServiceMap;
24
+ /** Category name union when typegen ran; otherwise `string`. */
25
+ export type RegisteredCategoryName = keyof OneConsentCategoryMap extends never ? string : keyof OneConsentCategoryMap;
@@ -0,0 +1,10 @@
1
+ declare global {
2
+ interface Window {
3
+ __1consent?: unknown;
4
+ }
5
+ }
6
+ /**
7
+ * Whether the given service/category (name, tag, or id) has consent.
8
+ * Returns `false` until consent is known — render nothing or a placeholder.
9
+ */
10
+ export declare function useConsent(key: string): boolean;
package/package.json CHANGED
@@ -1,6 +1,57 @@
1
1
  {
2
- "name": "@1consent/react",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
2
+ "name": "@1consent/react",
3
+ "version": "0.1.0-beta.1",
4
+ "description": "React 19 consent components and hooks for the 1Consent CMP.",
5
+ "type": "module",
6
+ "sideEffects": false,
7
+ "license": "MIT",
8
+ "keywords": [
9
+ "consent",
10
+ "cmp",
11
+ "privacy",
12
+ "react",
13
+ "typescript"
14
+ ],
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "git+https://github.com/oneconsent/1consent-js.git",
18
+ "directory": "packages/libs/react"
19
+ },
20
+ "homepage": "https://github.com/oneconsent/1consent-js/tree/main/packages/libs/react#readme",
21
+ "bugs": {
22
+ "url": "https://github.com/oneconsent/1consent-js/issues"
23
+ },
24
+ "publishConfig": {
25
+ "access": "public",
26
+ "provenance": true,
27
+ "registry": "https://registry.npmjs.org/",
28
+ "tag": "beta"
29
+ },
30
+ "main": "./dist/index.js",
31
+ "types": "./dist/index.d.ts",
32
+ "files": [
33
+ "dist/index.js",
34
+ "dist/index.d.ts",
35
+ "dist/react-server.js",
36
+ "dist/react-server.d.ts",
37
+ "dist/one-consent.d.ts",
38
+ "dist/provider.d.ts",
39
+ "dist/types.d.ts",
40
+ "dist/use-consent.d.ts",
41
+ "README.md",
42
+ "LICENSE"
43
+ ],
44
+ "exports": {
45
+ ".": {
46
+ "types": "./dist/index.d.ts",
47
+ "react-server": "./dist/react-server.js",
48
+ "import": "./dist/index.js"
49
+ }
50
+ },
51
+ "peerDependencies": {
52
+ "react": ">=19.0.0"
53
+ },
54
+ "dependencies": {
55
+ "zod": "^4.4.3"
56
+ }
57
+ }