@consentera/consent-sdk 2.0.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.
Files changed (48) hide show
  1. package/CHANGELOG.md +245 -0
  2. package/LICENSE +21 -0
  3. package/README.md +489 -0
  4. package/dist/consentera-consent.cjs +4919 -0
  5. package/dist/consentera-consent.cjs.map +1 -0
  6. package/dist/consentera-consent.min.js +2 -0
  7. package/dist/consentera-consent.min.js.map +1 -0
  8. package/dist/consentera-consent.mjs +4864 -0
  9. package/dist/consentera-consent.mjs.map +1 -0
  10. package/dist/react/index.cjs +2731 -0
  11. package/dist/react/index.cjs.map +1 -0
  12. package/dist/react/index.mjs +2724 -0
  13. package/dist/react/index.mjs.map +1 -0
  14. package/dist/types/consent/CallbackHandler.d.ts +246 -0
  15. package/dist/types/consent/ConsentManager.d.ts +128 -0
  16. package/dist/types/consent/ConsentSession.d.ts +127 -0
  17. package/dist/types/consent/ConsentValidator.d.ts +63 -0
  18. package/dist/types/consent/artifactRead.d.ts +48 -0
  19. package/dist/types/consent/consentPopup.d.ts +115 -0
  20. package/dist/types/core/ConsentEraClient.d.ts +106 -0
  21. package/dist/types/core/ConsenteraConsent.d.ts +163 -0
  22. package/dist/types/core/errors.d.ts +108 -0
  23. package/dist/types/core/http.d.ts +176 -0
  24. package/dist/types/core/version.d.ts +36 -0
  25. package/dist/types/df/DFConfigClient.d.ts +59 -0
  26. package/dist/types/gcm/ConsentModeBridge.d.ts +54 -0
  27. package/dist/types/gpp/GPPManager.d.ts +62 -0
  28. package/dist/types/index.d.mts +5 -0
  29. package/dist/types/index.d.ts +28 -0
  30. package/dist/types/principal/PrincipalClient.d.ts +34 -0
  31. package/dist/types/react/ConsentEraProvider.d.ts +58 -0
  32. package/dist/types/react/ConsentGate.d.ts +40 -0
  33. package/dist/types/react/index.d.mts +4 -0
  34. package/dist/types/react/index.d.ts +10 -0
  35. package/dist/types/react/useConsentEra.d.ts +65 -0
  36. package/dist/types/react/useConsentValidation.d.ts +23 -0
  37. package/dist/types/storage/ConsentStorage.d.ts +39 -0
  38. package/dist/types/tcf/TCFManager.d.ts +46 -0
  39. package/dist/types/types/consent-lifecycle.d.ts +804 -0
  40. package/dist/types/types/index.d.ts +311 -0
  41. package/dist/types/ui/ConsentBanner.d.ts +22 -0
  42. package/dist/types/ui/PreferenceCenter.d.ts +24 -0
  43. package/dist/types/utils/EventEmitter.d.ts +32 -0
  44. package/dist/types/utils/Logger.d.ts +16 -0
  45. package/dist/types/utils/browserStorage.d.ts +35 -0
  46. package/dist/types/utils/context.d.ts +81 -0
  47. package/dist/types/utils/helpers.d.ts +48 -0
  48. package/package.json +132 -0
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Consentera Consent SDK — the error taxonomy.
3
+ *
4
+ * ONE class with a discriminant, plus subclasses for `instanceof`. Before 2.0.0
5
+ * there was a single `ConsentEraApiError` carrying `statusCode` and the raw
6
+ * `responseBody`, which meant a caller who wanted to know *what went wrong* had
7
+ * to dig a string out of an untyped body — and the two things support asks for
8
+ * first, the canonical code and the request id, were both discarded. The API
9
+ * has carried them the whole time: the envelope is `{code, message}`
10
+ * (core/apierrors/errors.go:33-38) over ~141 codes of which 22 are canonical
11
+ * (core/apierrors/canonical_codes.go), and `X-Request-ID` is set by
12
+ * chi middleware and CORS-exposed so a browser can read it
13
+ * (cmd/api/main.go:1266, :1272).
14
+ *
15
+ * `kind` is the discriminant to switch on. The CODE is the platform's word and
16
+ * may be one of many; the KIND is this SDK's classification of it and is a
17
+ * closed set, so `switch (err.kind)` stays exhaustive when the platform adds a
18
+ * code. Both are on the error — never infer the kind from the code yourself.
19
+ */
20
+ /** The closed set of error classes a caller can switch on. */
21
+ export type ConsenteraErrorKind = 'config' | 'auth' | 'permission' | 'validation' | 'identity' | 'guardian' | 'not_found' | 'conflict' | 'rate_limit' | 'server' | 'network' | 'timeout' | 'cancelled' | 'unknown';
22
+ export interface ConsenteraErrorInit {
23
+ kind: ConsenteraErrorKind;
24
+ message: string;
25
+ status?: number;
26
+ code?: string;
27
+ requestId?: string;
28
+ responseBody?: unknown;
29
+ retryable?: boolean;
30
+ retryAfterMs?: number;
31
+ cause?: unknown;
32
+ }
33
+ /**
34
+ * Parse `Retry-After`, which RFC 9110 §10.2.3 allows to be either a count of
35
+ * seconds or an HTTP-date. Returns milliseconds, or undefined when the header
36
+ * is absent or unparseable — never NaN, because a NaN delay becomes an
37
+ * immediate retry and turns a 429 into a hot loop.
38
+ */
39
+ export declare function parseRetryAfterMs(header: string | null | undefined, now?: number): number | undefined;
40
+ /** The base error every road in this SDK throws. */
41
+ export declare class ConsenteraError extends Error {
42
+ /** This SDK's classification. A closed set — safe to switch on. */
43
+ readonly kind: ConsenteraErrorKind;
44
+ /** The platform's canonical error code, when the response carried one. */
45
+ readonly code: string | undefined;
46
+ /** HTTP status; 0 for a transport failure that never reached a status. */
47
+ readonly status: number;
48
+ /** `X-Request-ID` off the response. Quote this in a support ticket. */
49
+ readonly requestId: string | undefined;
50
+ /** The parsed response body, or the raw text when it was not JSON. */
51
+ readonly responseBody: unknown;
52
+ /** Whether THIS SDK would retry it. Already applied internally. */
53
+ readonly retryable: boolean;
54
+ /** Server-asked wait, from `Retry-After`, in ms. */
55
+ readonly retryAfterMs: number | undefined;
56
+ constructor(init: ConsenteraErrorInit);
57
+ /** @deprecated 2.0.0 — use `status`. Kept so 1.x `err.statusCode` still reads. */
58
+ get statusCode(): number;
59
+ /** A one-line form safe to log: no body, no identifiers. */
60
+ toString(): string;
61
+ }
62
+ export declare class ConsenteraConfigError extends ConsenteraError {
63
+ }
64
+ export declare class ConsenteraAuthError extends ConsenteraError {
65
+ }
66
+ export declare class ConsenteraPermissionError extends ConsenteraError {
67
+ }
68
+ export declare class ConsenteraValidationError extends ConsenteraError {
69
+ }
70
+ export declare class ConsenteraIdentityError extends ConsenteraError {
71
+ }
72
+ export declare class ConsenteraGuardianError extends ConsenteraError {
73
+ }
74
+ export declare class ConsenteraNotFoundError extends ConsenteraError {
75
+ }
76
+ export declare class ConsenteraConflictError extends ConsenteraError {
77
+ }
78
+ export declare class ConsenteraRateLimitError extends ConsenteraError {
79
+ }
80
+ export declare class ConsenteraServerError extends ConsenteraError {
81
+ }
82
+ export declare class ConsenteraNetworkError extends ConsenteraError {
83
+ }
84
+ export declare class ConsenteraTimeoutError extends ConsenteraError {
85
+ }
86
+ /** Build the error for a non-2xx response. */
87
+ export declare function errorFromResponse(args: {
88
+ status: number;
89
+ statusText?: string;
90
+ body: unknown;
91
+ requestId?: string;
92
+ retryAfterMs?: number;
93
+ method: string;
94
+ path: string;
95
+ }): ConsenteraError;
96
+ /** A request that never reached a status: DNS, TLS, offline, CORS. */
97
+ export declare function networkError(message: string, cause?: unknown): ConsenteraNetworkError;
98
+ /** The SDK's own deadline fired. */
99
+ export declare function timeoutError(message: string, cause?: unknown): ConsenteraTimeoutError;
100
+ /** The caller's AbortSignal fired. NEVER retryable: the caller asked to stop. */
101
+ export declare function cancelledError(message: string, cause?: unknown): ConsenteraError;
102
+ /** Misconfiguration found before anything went on the wire. */
103
+ export declare function configError(message: string, code?: string): ConsenteraConfigError;
104
+ /**
105
+ * @deprecated 2.0.0 — `ConsentEraApiError` is now an alias of {@link ConsenteraError}.
106
+ * `instanceof` and `.statusCode` still work; `.code`, `.kind` and `.requestId` are new.
107
+ */
108
+ export declare const ConsentEraApiError: typeof ConsenteraError;
@@ -0,0 +1,176 @@
1
+ /**
2
+ * Consentera Consent SDK — the transport.
3
+ *
4
+ * Everything that talks to the platform goes through `HttpTransport.request`.
5
+ * One implementation, because the four things below are the ones that are
6
+ * always missing when each caller writes its own `fetch`:
7
+ *
8
+ * 1. A DEADLINE. `fetch` has none. Before 2.0.0 a hung connection hung the
9
+ * caller's promise forever, which on a consent gate means a page that never
10
+ * renders. Every request now carries an AbortController, linked to the
11
+ * caller's own signal so `AbortSignal` cancellation still works.
12
+ * 2. RETRIES that are safe. Exponential backoff with full jitter on network
13
+ * failure, 5xx and 429, honouring `Retry-After` when the server sends one.
14
+ * 3. ONE IDEMPOTENCY KEY PER LOGICAL OPERATION. Before 2.0.0 the key was
15
+ * minted inside the request function from `Date.now()` + `Math.random()`,
16
+ * so it changed on every attempt and the caller could not supply one — a
17
+ * dedupe mechanism that was present and could not dedupe anything. The key
18
+ * is now minted ONCE per logical operation, reused on every retry of it,
19
+ * and `options.idempotencyKey` overrides it.
20
+ * 4. THE REQUEST ID. The platform sets `X-Request-ID` and CORS-exposes it
21
+ * (cmd/api/main.go:1266, :1272). It is now on every error.
22
+ *
23
+ * WHAT IS DELIBERATELY NOT HERE: no request or response BODY is ever logged.
24
+ * The body of a session create is the Data Principal's identifiers.
25
+ */
26
+ import type { Logger } from '../utils/Logger';
27
+ /**
28
+ * Which door a road goes through, and therefore what credential it may carry.
29
+ *
30
+ * OWNER RULING 2026-09-22: a browser bundle never carries a secret.
31
+ *
32
+ * - `df` identifier-bearing or tenant-scoped. Needs a DF credential, which
33
+ * is a SECRET. In a browser it must go through `proxyEndpoint` —
34
+ * the Data Fiduciary's own server, which holds the key.
35
+ * - `public` a public road (notice fetch, consent-page config/submit). May
36
+ * carry the public site key; carries no secret ever.
37
+ * - `session` bound to a consent session by its id and nonce. Carries no key
38
+ * at all: the session id IS the capability.
39
+ */
40
+ export type RoadClass = 'df' | 'public' | 'session';
41
+ export interface RetryPolicy {
42
+ /** Refuse retry if the server requires a longer wait; never retry early. Absent honors full Retry-After. */
43
+ maxRetryAfterMs?: number;
44
+ /** Total attempts including the first. 1 disables retrying. Default 3. */
45
+ attempts: number;
46
+ /** First backoff in ms; doubles per attempt. Default 250. */
47
+ baseDelayMs: number;
48
+ /** Ceiling for a single backoff, before Retry-After is considered. Default 4000. */
49
+ maxDelayMs: number;
50
+ }
51
+ export declare const DEFAULT_RETRY: RetryPolicy;
52
+ export declare const DEFAULT_TIMEOUT_MS = 10000;
53
+ export interface RequestOptions {
54
+ /** Which door. Decides the credential policy. Default 'df'. */
55
+ road?: RoadClass;
56
+ /** Query parameters; undefined/null values are dropped. */
57
+ query?: Record<string, string | number | boolean | undefined | null>;
58
+ /**
59
+ * The idempotency key for this LOGICAL operation. Supply it to make a retry
60
+ * from your own code — a user pressing the button twice, a job re-running —
61
+ * collapse into one write. Omit it and the SDK mints one per call and reuses
62
+ * it across its own retries.
63
+ */
64
+ idempotencyKey?: string;
65
+ /** Caller cancellation. Composed with the SDK's own deadline. */
66
+ signal?: AbortSignal;
67
+ /** Per-request deadline. Default 10s. */
68
+ timeoutMs?: number;
69
+ /** Per-request retry policy override. */
70
+ retry?: Partial<RetryPolicy>;
71
+ /** Extra headers for this request only. */
72
+ headers?: Record<string, string>;
73
+ /**
74
+ * Resolve with the STATUS and the body instead of the body alone — see
75
+ * {@link TransportResponse}. For a road where two success codes mean two
76
+ * different things: the artifact read answers 200 with the artifact and 202
77
+ * with "recorded, still being written", and a caller that only sees the body
78
+ * cannot tell a pending notice from an artifact.
79
+ */
80
+ raw?: boolean;
81
+ }
82
+ /**
83
+ * A 2xx answer with its status. Resolved instead of the bare body when the
84
+ * request asked for `raw: true`; a non-2xx is still a thrown ConsenteraError.
85
+ */
86
+ export interface TransportResponse<T> {
87
+ status: number;
88
+ body: T;
89
+ /** `X-Request-ID`, when the platform (or the Data Fiduciary's proxy) sent it. */
90
+ requestId?: string;
91
+ /** `Retry-After`, in ms, when the answer carried one (the artifact read's 202 does). */
92
+ retryAfterMs?: number;
93
+ }
94
+ export interface TransportConfig {
95
+ /** Internal API plane selection; cookie preferences share the canonical retry transport. */
96
+ apiPathPrefix?: '/api/v1/public' | '/api/v1/cookie-consent';
97
+ /** Sent as X-Tenant-Id on direct roads only; a proxy supplies the tenant. */
98
+ tenantId?: string;
99
+ apiEndpoint?: string;
100
+ proxyEndpoint?: string;
101
+ apiKey?: string;
102
+ siteKey?: string;
103
+ customHeaders?: Record<string, string> | (() => Record<string, string>);
104
+ /**
105
+ * ESCAPE HATCH, AND IT IS LOUD ON PURPOSE. Sending a secret key from a
106
+ * browser publishes it to every visitor. Setting this true is allowed only
107
+ * for a non-browser bundle run in a browser-shaped test harness; every
108
+ * request then warns.
109
+ */
110
+ unsafeAllowSecretKeyInBrowser?: boolean;
111
+ timeoutMs?: number;
112
+ retry?: Partial<RetryPolicy>;
113
+ /**
114
+ * Last look at every request body before it leaves the browser. Return the
115
+ * body to send, or throw to stop the call. The Data Fiduciary owns the data
116
+ * in these payloads; this is where they redact, add or refuse.
117
+ *
118
+ * Returning `null` is a REFUSAL and raises `BEFORE_SEND_REFUSED` — it never
119
+ * silently sends nothing, because a consent write that quietly did not happen
120
+ * is the failure this SDK exists to avoid.
121
+ */
122
+ beforeSend?: (req: {
123
+ method: string;
124
+ path: string;
125
+ body: unknown;
126
+ }) => unknown | null;
127
+ /** Default fetch implementation; injected in tests. */
128
+ fetchImpl?: typeof fetch;
129
+ }
130
+ /** `typeof window !== 'undefined'` in one place, so a test can reason about it. */
131
+ export declare function isBrowser(): boolean;
132
+ /** A secret DF key: the platform's prefixes for the two secret classes. */
133
+ export declare function isSecretKey(key: string | undefined): boolean;
134
+ /** A public site key. */
135
+ export declare function isSiteKey(key: string | undefined): boolean;
136
+ /**
137
+ * THE ONE REFUSAL, in one place — used by BOTH entry points (ConsentEraClient
138
+ * and ConsenteraConsent), because a rule enforced at only one door is not a
139
+ * rule. A secret key in a browser is not a warning: the bundle is public, so by
140
+ * the time it runs the key is already published to every visitor. Refusing at
141
+ * construction is the only point at which the integrator still has the option
142
+ * of not shipping it.
143
+ *
144
+ * `fix` is the entry-point-specific remedy (a proxy endpoint for the lifecycle
145
+ * client, a public site key for the cookie SDK); everything else — the reason,
146
+ * the prefix echo, the escape hatch — is identical, which is exactly why it
147
+ * lives here rather than being copied and left to drift.
148
+ */
149
+ export declare function assertNoSecretKeyInBrowser(opts: {
150
+ apiKey?: string;
151
+ unsafeAllowSecretKeyInBrowser?: boolean;
152
+ fix: string;
153
+ }): void;
154
+ /**
155
+ * Crypto-strong id. `Math.random()` was what minted idempotency keys before
156
+ * 2.0.0; it is neither unpredictable nor collision-safe at 6 characters.
157
+ */
158
+ export declare function newRequestId(): string;
159
+ /** Full-jitter backoff (AWS's "Exponential Backoff and Jitter"): random in [0, cap]. */
160
+ export declare function backoffMs(attempt: number, policy: RetryPolicy, random?: () => number): number;
161
+ export declare class HttpTransport {
162
+ private config;
163
+ private logger;
164
+ constructor(config: TransportConfig, logger: Logger);
165
+ /** Swap config after construction (the client re-reads customHeaders each call). */
166
+ updateConfig(patch: Partial<TransportConfig>): void;
167
+ /**
168
+ * The credential decision for one road, in one place so the policy can be
169
+ * read rather than reconstructed from call sites.
170
+ *
171
+ * Returns the auth headers to send, or throws a config error naming the fix.
172
+ */
173
+ authHeadersFor(road: RoadClass, viaProxy: boolean): Record<string, string>;
174
+ private urlFor;
175
+ request<T>(method: 'GET' | 'POST' | 'PUT' | 'DELETE', path: string, body?: unknown, options?: RequestOptions): Promise<T>;
176
+ }
@@ -0,0 +1,36 @@
1
+ /**
2
+ * The SDK's own version, sent on every request as `X-Consentera-SDK`.
3
+ *
4
+ * KEPT IN SYNC BY A TEST, NOT BY A BUILD STEP. `src/__tests__/core/version.test.ts`
5
+ * reads package.json and fails when the two disagree, so a release that forgets
6
+ * this file cannot go green. A build-time codegen would have been the other
7
+ * option; it was rejected because the constant then does not exist when a host
8
+ * builds from src, and because a generated file in the tree is one command from
9
+ * being overwritten with the wrong value and nothing noticing.
10
+ */
11
+ export declare const SDK_NAME = "consent-sdk-js";
12
+ export declare const SDK_VERSION = "2.0.0";
13
+ export declare const SDK_PLATFORM = "web";
14
+ /** The value of the `X-Consentera-SDK` header: `<surface>/<version>`. */
15
+ export declare const SDK_HEADER_VALUE = "js/2.0.0";
16
+ /**
17
+ * The `User-Agent` half of the pair, agreed across all six surfaces
18
+ * (coordinator ruling 2026-09-22):
19
+ *
20
+ * User-Agent: ConsenteraSDK/2.0.0 (<platform>; <runtime>)
21
+ * X-Consentera-SDK: <surface>/2.0.0
22
+ *
23
+ * `ConsenteraSDK/<version>` is the form the PLATFORM ALREADY PARSES:
24
+ * `internal/core/audit/user_agent_coarsening_test.go:39-40` asserts that
25
+ * `ConsenteraSDK/2.3.1 (Android 14; SM-G991B; build 4471)` coarsens to
26
+ * `ConsenteraSDK/2`, so this is the shape its audit pipeline expects rather
27
+ * than a new one. (A second spelling, `consentera-sdk/1.2`, appears in
28
+ * `validation_envelope_test.go:233`; the pair above is the agreed one.)
29
+ *
30
+ * IN A BROWSER THIS HEADER CANNOT BE SENT. `User-Agent` is a forbidden header
31
+ * name (fetch spec §forbidden-request-header), so a browser silently drops any
32
+ * attempt to set it — the SDK does not try, and the browser build identifies
33
+ * itself with `X-Consentera-SDK` alone. The Node build (and the CLI) send both,
34
+ * because there the header is ours to set.
35
+ */
36
+ export declare function userAgentValue(runtime?: string): string;
@@ -0,0 +1,59 @@
1
+ /**
2
+ * ConsentEra Consent SDK — DF Configuration Client
3
+ * Fetch Data Fiduciary configuration, purposes, and notice details
4
+ */
5
+ import { DFConfig, DFPurpose, NoticePurposesResponse } from '../types/consent-lifecycle';
6
+ import { Logger } from '../utils/Logger';
7
+ import type { RequestOptions } from '../core/http';
8
+ type RequestFn = <T>(method: 'GET' | 'POST' | 'PUT' | 'DELETE', path: string, body?: unknown, queryParams?: Record<string, string | number | boolean | undefined | null>, options?: RequestOptions) => Promise<T>;
9
+ export declare class DFConfigClient {
10
+ private request;
11
+ private logger;
12
+ private cachedConfig;
13
+ constructor(request: RequestFn, logger: Logger);
14
+ /**
15
+ * Get the full DF configuration (tenant info, purposes, notices, branding).
16
+ * Results are cached for the lifetime of the client instance.
17
+ */
18
+ getConfig(language?: string): Promise<DFConfig>;
19
+ /**
20
+ * Get all purposes configured for this DF.
21
+ */
22
+ getPurposes(): Promise<DFPurpose[]>;
23
+ /**
24
+ * Get purposes associated with a specific notice.
25
+ * This returns the ordered list of purposes as they appear in the notice.
26
+ */
27
+ getNoticePurposes(noticeInternalName?: string, language?: string): Promise<NoticePurposesResponse>;
28
+ /**
29
+ * Get the pre-rendered HTML+CSS snapshot for a notice, including purpose snapshot.
30
+ * Use this instead of getConfig() for bootstrapping notice-based consent UIs.
31
+ *
32
+ * @param noticeName - internal_name of the notice (required)
33
+ * @param noticeLanguage - language of the notice content (default: tenant primary language)
34
+ * @param templateLanguage - language for widget UI labels (optional)
35
+ */
36
+ getNoticeTemplate(noticeName: string, noticeLanguage?: string, templateLanguage?: string): Promise<Record<string, unknown>>;
37
+ /**
38
+ * Get the widget template for a consent SESSION's collection UI.
39
+ *
40
+ * THE ROUTE CHANGED IN 2.0.0, because the old one did not exist. This called
41
+ * GET /df/widget-template, which is a 404 — there is no such platform route.
42
+ * The real widget template is session-scoped: GET
43
+ * /consent/sessions/{id}/widget-template (routes_consent.go:125), which sits
44
+ * in the pre-auth throttle group and takes NO DF credential — the session id
45
+ * and its nonce are the capability. So this needs a session id, and rides the
46
+ * `session` road (no key, no proxy required).
47
+ *
48
+ * For the notice-authoring template keyed by internal_name, use
49
+ * {@link getNoticeTemplate}, which is the site-key-openable /df/notice/template.
50
+ *
51
+ * @param sessionId the consent_session_id from createSession()
52
+ */
53
+ getSessionWidgetTemplate(sessionId: string): Promise<Record<string, unknown>>;
54
+ /**
55
+ * Clear the cached config (useful after config changes).
56
+ */
57
+ clearCache(): void;
58
+ }
59
+ export {};
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Google Consent Mode v2 bridge.
3
+ *
4
+ * Maps Consentera purpose decisions onto the four Consent Mode v2 signals
5
+ * (analytics_storage, ad_storage, ad_user_data, ad_personalization) and
6
+ * pushes them through gtag's dataLayer — `default` denied at install (before
7
+ * any Google tag fires), `update` on every consent.changed event.
8
+ *
9
+ * Usage:
10
+ * const bridge = installConsentModeBridge(client, {
11
+ * analytics_storage: ['product_analytics'],
12
+ * ad_storage: ['marketing_communications'],
13
+ * ad_user_data: ['marketing_communications'],
14
+ * ad_personalization: ['marketing_communications'],
15
+ * });
16
+ * // after reading current state (e.g. update-context or validate):
17
+ * bridge.apply({ product_analytics: true, marketing_communications: false });
18
+ *
19
+ * The bridge never loads Google code itself — it only writes the standard
20
+ * dataLayer entries, so it is inert until/unless a Google tag is present.
21
+ */
22
+ export type ConsentModeSignal = 'analytics_storage' | 'ad_storage' | 'ad_user_data' | 'ad_personalization';
23
+ export type ConsentModeMapping = Partial<Record<ConsentModeSignal, string[]>>;
24
+ interface ChangedPayload {
25
+ dataPrincipalId: string;
26
+ changes: {
27
+ purpose_id: string;
28
+ status: string;
29
+ }[];
30
+ source: string;
31
+ }
32
+ interface BusLike {
33
+ on(event: string, cb: (payload: ChangedPayload) => void): void;
34
+ }
35
+ export declare class ConsentModeBridge {
36
+ private mapping;
37
+ /** purpose id/code → granted? — the bridge's view of current state. */
38
+ private state;
39
+ constructor(mapping: ConsentModeMapping);
40
+ /** Send the Consent Mode v2 `default` (everything mapped → denied). */
41
+ sendDefault(waitForUpdateMs?: number): void;
42
+ /** Replace the bridge's purpose state wholesale and push an update. */
43
+ apply(purposeStates: Record<string, boolean>): void;
44
+ /** Fold a consent.changed event into state and push an update. */
45
+ onChanged(payload: ChangedPayload): void;
46
+ private push;
47
+ }
48
+ /**
49
+ * Install the bridge: sends the denied `default` immediately and subscribes
50
+ * to the client's `consent.changed` bus. Returns the bridge so the caller can
51
+ * `apply()` the current state once known.
52
+ */
53
+ export declare function installConsentModeBridge(bus: BusLike, mapping: ConsentModeMapping): ConsentModeBridge;
54
+ export {};
@@ -0,0 +1,62 @@
1
+ /**
2
+ * GPP Manager
3
+ * IAB Global Privacy Platform implementation
4
+ */
5
+ import { GPPConfig, GPPData, ConsentPreferences } from '../types';
6
+ export declare class GPPManager {
7
+ private config;
8
+ private gppData;
9
+ constructor(config: GPPConfig);
10
+ /**
11
+ * Initialize GPP API
12
+ */
13
+ init(): Promise<void>;
14
+ /**
15
+ * Create __gpp stub
16
+ */
17
+ private createGPPApiStub;
18
+ /**
19
+ * Get default GPP data
20
+ */
21
+ private getDefaultGPPData;
22
+ /**
23
+ * Get section data
24
+ */
25
+ private getSectionData;
26
+ /**
27
+ * Get field value
28
+ */
29
+ private getFieldValue;
30
+ /**
31
+ * Update consent in GPP format
32
+ */
33
+ updateConsent(preferences: ConsentPreferences): Promise<void>;
34
+ /**
35
+ * Build GPP string
36
+ */
37
+ private buildGPPString;
38
+ /**
39
+ * Encode GPP string
40
+ */
41
+ private encodeGPPString;
42
+ /**
43
+ * Determine applicable sections
44
+ */
45
+ private determineSections;
46
+ /**
47
+ * Dispatch GPP event
48
+ */
49
+ private dispatchGPPEvent;
50
+ /**
51
+ * Get current GPP data
52
+ */
53
+ getGPPData(): GPPData | null;
54
+ /**
55
+ * Get GPP string
56
+ */
57
+ getGPPString(): string;
58
+ /**
59
+ * Destroy GPP manager
60
+ */
61
+ destroy(): void;
62
+ }
@@ -0,0 +1,5 @@
1
+ // GENERATED by scripts/emit-esm-types.mjs — do not edit.
2
+ // The ESM-flavoured door into the shared declaration tree; that script says
3
+ // why a .d.mts entry is needed at all.
4
+ export * from './index.js';
5
+ export { default } from './index.js';
@@ -0,0 +1,28 @@
1
+ /**
2
+ * ConsentEra Consent SDK
3
+ * DPDP, GDPR, TCF 2.2 compliant consent management for web applications
4
+ */
5
+ import { ConsentEraConsent } from './core/ConsenteraConsent';
6
+ import { ConsentBanner } from './ui/ConsentBanner';
7
+ import { PreferenceCenter } from './ui/PreferenceCenter';
8
+ import { ConsentStorage } from './storage/ConsentStorage';
9
+ import { TCFManager } from './tcf/TCFManager';
10
+ import { GPPManager } from './gpp/GPPManager';
11
+ export { ConsentEraConsent, ConsentBanner, PreferenceCenter, ConsentStorage, TCFManager, GPPManager, };
12
+ export * from './types';
13
+ export { ConsentEraClient } from './core/ConsentEraClient';
14
+ export { ConsenteraError, ConsentEraApiError, ConsenteraConfigError, ConsenteraAuthError, ConsenteraPermissionError, ConsenteraValidationError, ConsenteraIdentityError, ConsenteraGuardianError, ConsenteraNotFoundError, ConsenteraConflictError, ConsenteraRateLimitError, ConsenteraServerError, ConsenteraNetworkError, ConsenteraTimeoutError, parseRetryAfterMs, type ConsenteraErrorKind, } from './core/errors';
15
+ export { HttpTransport, isBrowser, isSecretKey, isSiteKey, newRequestId, DEFAULT_RETRY, DEFAULT_TIMEOUT_MS, type RoadClass, type RetryPolicy, type RequestOptions, type TransportResponse, type TransportConfig, } from './core/http';
16
+ export { SDK_NAME, SDK_VERSION, SDK_PLATFORM, SDK_HEADER_VALUE } from './core/version';
17
+ export { readStored, writeStored, removeStored, storageAvailable, storageKeys, type StorageKind, } from './utils/browserStorage';
18
+ export { ConsentSession } from './consent/ConsentSession';
19
+ export { ConsentValidator } from './consent/ConsentValidator';
20
+ export { ConsentManager } from './consent/ConsentManager';
21
+ export { CallbackHandler, verifyCallbackSignature, claimedCallbackStatus, type CallbackClaimedStatus, type CallbackResult, type CallbackStatus, type CallbackParams, type CallbackSignatureVerifier, type CallbackHandlerOptions, } from './consent/CallbackHandler';
22
+ export { readDecisionMessage, type ConsentPopupResult, type ConsentPopupDecision, type ConsentPopupDismissed, type ConsentPopupOptions, type DecisionStatus, } from './consent/consentPopup';
23
+ export { DFConfigClient } from './df/DFConfigClient';
24
+ export { PrincipalClient } from './principal/PrincipalClient';
25
+ export { ConsentModeBridge, installConsentModeBridge, type ConsentModeMapping, type ConsentModeSignal, } from './gcm/ConsentModeBridge';
26
+ export * from './types/consent-lifecycle';
27
+ export { principalBody, buildClientContext, type PrincipalRef, type ContextMode } from './utils/context';
28
+ export default ConsentEraConsent;
@@ -0,0 +1,34 @@
1
+ /**
2
+ * ConsentEra Consent SDK — Principal Client
3
+ * Data principal rights: Portal SSO, data export, deletion requests
4
+ */
5
+ import { ConsentEraClientConfig, PrincipalTokenResponse } from '../types/consent-lifecycle';
6
+ import { Logger } from '../utils/Logger';
7
+ import type { RequestOptions } from '../core/http';
8
+ type RequestFn = <T>(method: 'GET' | 'POST' | 'PUT' | 'DELETE', path: string, body?: unknown, queryParams?: Record<string, string | number | boolean | undefined | null>, options?: RequestOptions) => Promise<T>;
9
+ export declare class PrincipalClient {
10
+ private request;
11
+ private logger;
12
+ constructor(request: RequestFn, _config: ConsentEraClientConfig, logger: Logger);
13
+ /**
14
+ * Generate a principal portal SSO token.
15
+ * Returns a URL the data principal can visit to manage their consents.
16
+ *
17
+ * THIS ROAD STILL TAKES data_principal_ref, AND THAT IS CORRECT. It is
18
+ * identity/dfclient's (handlers.go:1418-1428), not the consent module's, so
19
+ * the 2026-09-21 ruling that refuses the field across the consent surface
20
+ * does not reach it. A sweep that converted this call would break a working
21
+ * endpoint.
22
+ */
23
+ getPortalToken(dataPrincipalRef: string, redirectPath?: string): Promise<PrincipalTokenResponse>;
24
+ /**
25
+ * Get the full portal URL for a data principal.
26
+ * Convenience method that returns just the redirect URL string.
27
+ */
28
+ getPortalUrl(dataPrincipalRef: string, redirectPath?: string): Promise<string>;
29
+ /**
30
+ * Open the principal portal in a new browser window/tab.
31
+ */
32
+ openPortal(dataPrincipalRef: string, redirectPath?: string): Promise<void>;
33
+ }
34
+ export {};
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Consentera Consent SDK — React Provider
3
+ *
4
+ * TWO THINGS CHANGED IN 2.0.0, both of which a React reviewer would have caught
5
+ * and neither of which a test would have:
6
+ *
7
+ * 1. The client used to be constructed inside `useMemo` with `setError` called
8
+ * from the factory — a setState DURING RENDER. React 18 warns about it and
9
+ * React 19 escalates the common cross-component case to an error; under
10
+ * StrictMode the factory also runs twice, so the error was set twice.
11
+ * 2. When construction failed the provider returned `<>{children}</>` WITHOUT
12
+ * the context, so every hook below it threw
13
+ * "useConsentEraClient must be used within <ConsentEraProvider>" — in a tree
14
+ * that plainly was inside one. The real cause (a missing tenantId, a secret
15
+ * key in a browser) sat in a state nothing rendered. The provider now always
16
+ * provides, with `client: null` and the real error on the context. Since
17
+ * WEB-038 no hook THROWS on it either: a throw during render blanks the
18
+ * page, so the hooks return `client: null` with the error and the gate
19
+ * renders its fallback.
20
+ *
21
+ * `'use client'` is at the top of this file AND emitted as a banner on the built
22
+ * bundle (rollup.config.js). Without it, importing this package from a Next.js
23
+ * App Router tree fails the build with "You're importing a component that needs
24
+ * createContext" — which is most React apps written today.
25
+ */
26
+ import { type ReactNode } from 'react';
27
+ import { ConsentEraClient } from '../core/ConsentEraClient';
28
+ import { ConsentEraClientConfig } from '../types/consent-lifecycle';
29
+ export interface ConsentEraContextValue {
30
+ /** The client, or null when the configuration was refused. */
31
+ client: ConsentEraClient | null;
32
+ loading: boolean;
33
+ /** Construction or runtime error. Non-null with `client: null` means config. */
34
+ error: Error | null;
35
+ }
36
+ export interface ConsentEraProviderProps {
37
+ config: ConsentEraClientConfig;
38
+ children: ReactNode;
39
+ }
40
+ export declare function ConsentEraProvider({ config, children }: ConsentEraProviderProps): import("react").JSX.Element;
41
+ /**
42
+ * The context as provided: `client` is null when the configuration was
43
+ * refused (then `error` says why) or when there is no provider at all (then
44
+ * `error` says that). NEVER throws.
45
+ */
46
+ export declare function useConsentEraContext(): ConsentEraContextValue;
47
+ /**
48
+ * The client, or `client: null` with the reason in `error`. NEVER throws
49
+ * during render (SDK register WEB-038).
50
+ *
51
+ * It used to throw when the provider's configuration was refused: a missing
52
+ * tenantId, a secret key in a browser. That threw from inside render, so a
53
+ * configuration mistake did not produce a closed consent gate. It produced a
54
+ * blank page, measured in the React demos. Render your fallback on
55
+ * `client === null`. `error` is a ConsenteraError whose `code` names the
56
+ * refusal (`SECRET_KEY_IN_BROWSER`, `ENDPOINT_REQUIRED`, …).
57
+ */
58
+ export declare function useConsentEraClient(): ConsentEraContextValue;
@@ -0,0 +1,40 @@
1
+ /**
2
+ * ConsentEra Consent SDK — ConsentGate Component
3
+ * Conditionally renders children based on consent validation.
4
+ *
5
+ * @example
6
+ * ```tsx
7
+ * <ConsentGate
8
+ * who={{ data_principal_identifiers: { email: "user@example.com" } }}
9
+ * purposeCode="marketing_email"
10
+ * fallback={<p>Marketing consent required.</p>}>
11
+ * <MarketingContent />
12
+ * </ConsentGate>
13
+ * ```
14
+ */
15
+ import { ReactNode } from 'react';
16
+ import type { PrincipalRef } from '../utils/context';
17
+ export interface ConsentGateProps {
18
+ /**
19
+ * Who to validate for: `{ data_principal_id }` when you hold the platform's
20
+ * uuid, or `{ data_principal_identifiers: { …the fields of this
21
+ * organisation's locked integration key… } }` (F015: an open, per-tenant
22
+ * scheme-keyed map, not a fixed set).
23
+ *
24
+ * This replaced a `dataPrincipalRef: string` prop that went on the wire as
25
+ * `data_principal_ref` — a field the API now refuses outright
26
+ * (`DATA_PRINCIPAL_REF_REFUSED`), so the old prop could only build a request
27
+ * that 400s. The mobile atom is `mobile` on this road and on create alike
28
+ * (F015); `phone` is refused by name.
29
+ */
30
+ who: PrincipalRef;
31
+ /** Purpose code to validate against */
32
+ purposeCode: string;
33
+ /** Content to render when consent is granted */
34
+ children: ReactNode;
35
+ /** Content to render when consent is denied or loading (defaults to null) */
36
+ fallback?: ReactNode;
37
+ /** Content to render while validation is in progress (defaults to fallback) */
38
+ loading?: ReactNode;
39
+ }
40
+ export declare function ConsentGate({ who, purposeCode, children, fallback, loading: loadingContent, }: ConsentGateProps): import("react").JSX.Element;