@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.
- package/CHANGELOG.md +245 -0
- package/LICENSE +21 -0
- package/README.md +489 -0
- package/dist/consentera-consent.cjs +4919 -0
- package/dist/consentera-consent.cjs.map +1 -0
- package/dist/consentera-consent.min.js +2 -0
- package/dist/consentera-consent.min.js.map +1 -0
- package/dist/consentera-consent.mjs +4864 -0
- package/dist/consentera-consent.mjs.map +1 -0
- package/dist/react/index.cjs +2731 -0
- package/dist/react/index.cjs.map +1 -0
- package/dist/react/index.mjs +2724 -0
- package/dist/react/index.mjs.map +1 -0
- package/dist/types/consent/CallbackHandler.d.ts +246 -0
- package/dist/types/consent/ConsentManager.d.ts +128 -0
- package/dist/types/consent/ConsentSession.d.ts +127 -0
- package/dist/types/consent/ConsentValidator.d.ts +63 -0
- package/dist/types/consent/artifactRead.d.ts +48 -0
- package/dist/types/consent/consentPopup.d.ts +115 -0
- package/dist/types/core/ConsentEraClient.d.ts +106 -0
- package/dist/types/core/ConsenteraConsent.d.ts +163 -0
- package/dist/types/core/errors.d.ts +108 -0
- package/dist/types/core/http.d.ts +176 -0
- package/dist/types/core/version.d.ts +36 -0
- package/dist/types/df/DFConfigClient.d.ts +59 -0
- package/dist/types/gcm/ConsentModeBridge.d.ts +54 -0
- package/dist/types/gpp/GPPManager.d.ts +62 -0
- package/dist/types/index.d.mts +5 -0
- package/dist/types/index.d.ts +28 -0
- package/dist/types/principal/PrincipalClient.d.ts +34 -0
- package/dist/types/react/ConsentEraProvider.d.ts +58 -0
- package/dist/types/react/ConsentGate.d.ts +40 -0
- package/dist/types/react/index.d.mts +4 -0
- package/dist/types/react/index.d.ts +10 -0
- package/dist/types/react/useConsentEra.d.ts +65 -0
- package/dist/types/react/useConsentValidation.d.ts +23 -0
- package/dist/types/storage/ConsentStorage.d.ts +39 -0
- package/dist/types/tcf/TCFManager.d.ts +46 -0
- package/dist/types/types/consent-lifecycle.d.ts +804 -0
- package/dist/types/types/index.d.ts +311 -0
- package/dist/types/ui/ConsentBanner.d.ts +22 -0
- package/dist/types/ui/PreferenceCenter.d.ts +24 -0
- package/dist/types/utils/EventEmitter.d.ts +32 -0
- package/dist/types/utils/Logger.d.ts +16 -0
- package/dist/types/utils/browserStorage.d.ts +35 -0
- package/dist/types/utils/context.d.ts +81 -0
- package/dist/types/utils/helpers.d.ts +48 -0
- 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,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;
|