@consentera/consent-sdk 2.0.0 → 2.1.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 +118 -8
- package/README.md +91 -2
- package/dist/consentera-consent.cjs +183 -16
- package/dist/consentera-consent.cjs.map +1 -1
- package/dist/consentera-consent.min.js +1 -1
- package/dist/consentera-consent.min.js.map +1 -1
- package/dist/consentera-consent.mjs +181 -17
- package/dist/consentera-consent.mjs.map +1 -1
- package/dist/react/index.cjs +177 -15
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.mjs +177 -15
- package/dist/react/index.mjs.map +1 -1
- package/dist/types/consent/ConsentManager.d.ts +10 -0
- package/dist/types/consent/ConsentValidator.d.ts +27 -0
- package/dist/types/core/ConsentEraClient.d.ts +1 -1
- package/dist/types/core/errors.d.ts +32 -1
- package/dist/types/core/http.d.ts +9 -0
- package/dist/types/core/version.d.ts +4 -4
- package/dist/types/index.d.ts +2 -2
- package/dist/types/types/consent-lifecycle.d.ts +31 -1
- package/package.json +1 -1
|
@@ -1745,6 +1745,15 @@ const CODE_KIND = {
|
|
|
1745
1745
|
CONFLICT: 'conflict',
|
|
1746
1746
|
IDEMPOTENCY_KEY_REUSE: 'conflict',
|
|
1747
1747
|
RATE_LIMIT_EXCEEDED: 'rate_limit',
|
|
1748
|
+
// 409 — the ORGANISATION's plan, never the person's answer. The consent cap
|
|
1749
|
+
// refuses a submit/update that would add a NEW (data principal, purpose)
|
|
1750
|
+
// grant past the plan's consents_max (platform
|
|
1751
|
+
// core/consentbridge/grant_cap.go: NewGrantCapRefusal), and nothing was
|
|
1752
|
+
// recorded. The DEPA ingest answers the same stop in its own vocabulary
|
|
1753
|
+
// (consent/depa/df_handlers.go). Its own kind, so it is never read as a
|
|
1754
|
+
// `conflict` (a duplicate), a network failure, or a refusal by the person.
|
|
1755
|
+
PLAN_LIMIT_REACHED: 'plan_limit',
|
|
1756
|
+
ARTEFACT_NOT_ACCEPTED_PLAN_LIMIT: 'plan_limit',
|
|
1748
1757
|
INTERNAL_ERROR: 'server',
|
|
1749
1758
|
INTERNAL_SERVER_ERROR: 'server',
|
|
1750
1759
|
DATABASE_ERROR: 'server',
|
|
@@ -1853,6 +1862,41 @@ class ConsenteraNetworkError extends ConsenteraError {
|
|
|
1853
1862
|
}
|
|
1854
1863
|
class ConsenteraTimeoutError extends ConsenteraError {
|
|
1855
1864
|
}
|
|
1865
|
+
/**
|
|
1866
|
+
* The organisation's plan cannot take this change: HTTP 409 with code
|
|
1867
|
+
* `PLAN_LIMIT_REACHED` (or `ARTEFACT_NOT_ACCEPTED_PLAN_LIMIT` on the DEPA
|
|
1868
|
+
* ingest). For consent it means the submit or update would have added a NEW
|
|
1869
|
+
* (data principal, purpose) grant past the plan's `consents_max`, and
|
|
1870
|
+
* `reasonCode` is `'new_consents_only'`.
|
|
1871
|
+
*
|
|
1872
|
+
* What it is NOT, and must never be shown or reported as:
|
|
1873
|
+
* * the person's refusal — they did not deny anything; do not record,
|
|
1874
|
+
* display or emit it as `denied`;
|
|
1875
|
+
* * a transient failure — retrying the same request gets the same answer
|
|
1876
|
+
* until the organisation's plan changes, so this SDK never retries it.
|
|
1877
|
+
*
|
|
1878
|
+
* `recorded` is always `false`: the platform wrote nothing, fired no callback
|
|
1879
|
+
* and emitted no event. Show the person a neutral "this organisation can't
|
|
1880
|
+
* accept new consents right now" (the platform's own `message` is written for
|
|
1881
|
+
* them) and leave their existing choices as they were.
|
|
1882
|
+
*/
|
|
1883
|
+
class ConsenteraPlanLimitError extends ConsenteraError {
|
|
1884
|
+
/** `details.reason_code` — `'new_consents_only'` for the consent cap. */
|
|
1885
|
+
reasonCode;
|
|
1886
|
+
/** `details.limit_key` — set on an admin create refusal (e.g. `'consents_max'`), absent on a consent submit. */
|
|
1887
|
+
limitKey;
|
|
1888
|
+
/** The envelope's `details`, as sent. */
|
|
1889
|
+
details;
|
|
1890
|
+
/** Always false: a plan-limit refusal records nothing. */
|
|
1891
|
+
recorded = false;
|
|
1892
|
+
constructor(init) {
|
|
1893
|
+
super({ ...init, retryable: false });
|
|
1894
|
+
const d = init.details;
|
|
1895
|
+
this.details = d;
|
|
1896
|
+
this.reasonCode = typeof d?.reason_code === 'string' ? d.reason_code : undefined;
|
|
1897
|
+
this.limitKey = typeof d?.limit_key === 'string' ? d.limit_key : undefined;
|
|
1898
|
+
}
|
|
1899
|
+
}
|
|
1856
1900
|
const KIND_CLASS = {
|
|
1857
1901
|
config: ConsenteraConfigError,
|
|
1858
1902
|
auth: ConsenteraAuthError,
|
|
@@ -1863,6 +1907,7 @@ const KIND_CLASS = {
|
|
|
1863
1907
|
not_found: ConsenteraNotFoundError,
|
|
1864
1908
|
conflict: ConsenteraConflictError,
|
|
1865
1909
|
rate_limit: ConsenteraRateLimitError,
|
|
1910
|
+
plan_limit: ConsenteraPlanLimitError,
|
|
1866
1911
|
server: ConsenteraServerError,
|
|
1867
1912
|
network: ConsenteraNetworkError,
|
|
1868
1913
|
timeout: ConsenteraTimeoutError,
|
|
@@ -1872,7 +1917,7 @@ const KIND_CLASS = {
|
|
|
1872
1917
|
function newOfKind(init) {
|
|
1873
1918
|
return new KIND_CLASS[init.kind](init);
|
|
1874
1919
|
}
|
|
1875
|
-
/** Pull `{code, message}` off a parsed body, tolerating the `{error:{...}}` wrapper. */
|
|
1920
|
+
/** Pull `{code, message, details}` off a parsed body, tolerating the `{error:{...}}` wrapper. */
|
|
1876
1921
|
function readEnvelope(body) {
|
|
1877
1922
|
if (!body || typeof body !== 'object')
|
|
1878
1923
|
return {};
|
|
@@ -1880,16 +1925,20 @@ function readEnvelope(body) {
|
|
|
1880
1925
|
const inner = o.error && typeof o.error === 'object' ? o.error : o;
|
|
1881
1926
|
const code = typeof inner.code === 'string' ? inner.code : undefined;
|
|
1882
1927
|
const message = typeof inner.message === 'string' ? inner.message : undefined;
|
|
1883
|
-
|
|
1928
|
+
const details = inner.details && typeof inner.details === 'object' && !Array.isArray(inner.details)
|
|
1929
|
+
? inner.details
|
|
1930
|
+
: undefined;
|
|
1931
|
+
return { code, message, details };
|
|
1884
1932
|
}
|
|
1885
1933
|
/** Build the error for a non-2xx response. */
|
|
1886
1934
|
function errorFromResponse(args) {
|
|
1887
|
-
const { code, message } = readEnvelope(args.body);
|
|
1935
|
+
const { code, message, details } = readEnvelope(args.body);
|
|
1888
1936
|
const kind = (code && CODE_KIND[code]) || kindForStatus(args.status);
|
|
1889
|
-
const retryable = args.status === 429 || args.status >= 500;
|
|
1937
|
+
const retryable = kind !== 'plan_limit' && (args.status === 429 || args.status >= 500);
|
|
1890
1938
|
return newOfKind({
|
|
1891
1939
|
kind,
|
|
1892
1940
|
code,
|
|
1941
|
+
details,
|
|
1893
1942
|
status: args.status,
|
|
1894
1943
|
requestId: args.requestId,
|
|
1895
1944
|
responseBody: args.body,
|
|
@@ -1932,7 +1981,7 @@ const ConsentEraApiError = ConsenteraError;
|
|
|
1932
1981
|
* being overwritten with the wrong value and nothing noticing.
|
|
1933
1982
|
*/
|
|
1934
1983
|
const SDK_NAME = 'consent-sdk-js';
|
|
1935
|
-
const SDK_VERSION = '2.
|
|
1984
|
+
const SDK_VERSION = '2.1.0';
|
|
1936
1985
|
const SDK_PLATFORM = 'web';
|
|
1937
1986
|
/** The value of the `X-Consentera-SDK` header: `<surface>/<version>`. */
|
|
1938
1987
|
const SDK_HEADER_VALUE = `js/${SDK_VERSION}`;
|
|
@@ -1940,8 +1989,8 @@ const SDK_HEADER_VALUE = `js/${SDK_VERSION}`;
|
|
|
1940
1989
|
* The `User-Agent` half of the pair, agreed across all six surfaces
|
|
1941
1990
|
* (coordinator ruling 2026-09-22):
|
|
1942
1991
|
*
|
|
1943
|
-
* User-Agent: ConsenteraSDK/2.
|
|
1944
|
-
* X-Consentera-SDK: <surface>/2.
|
|
1992
|
+
* User-Agent: ConsenteraSDK/2.1.0 (<platform>; <runtime>)
|
|
1993
|
+
* X-Consentera-SDK: <surface>/2.1.0
|
|
1945
1994
|
*
|
|
1946
1995
|
* `ConsenteraSDK/<version>` is the form the PLATFORM ALREADY PARSES:
|
|
1947
1996
|
* `internal/core/audit/user_agent_coarsening_test.go:39-40` asserts that
|
|
@@ -2043,7 +2092,7 @@ function newRequestId() {
|
|
|
2043
2092
|
// on such a browser should supply its own idempotencyKey.
|
|
2044
2093
|
return `nc-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
|
|
2045
2094
|
}
|
|
2046
|
-
function sleep(ms, signal) {
|
|
2095
|
+
function sleep$1(ms, signal) {
|
|
2047
2096
|
return new Promise((resolve, reject) => {
|
|
2048
2097
|
if (signal?.aborted) {
|
|
2049
2098
|
reject(cancelledError('request cancelled'));
|
|
@@ -2065,16 +2114,35 @@ function backoffMs(attempt, policy, random = Math.random) {
|
|
|
2065
2114
|
const exp = Math.min(policy.maxDelayMs, policy.baseDelayMs * 2 ** attempt);
|
|
2066
2115
|
return Math.floor(random() * exp);
|
|
2067
2116
|
}
|
|
2117
|
+
/**
|
|
2118
|
+
* The ONE normalisation of a configured base URL (HC-13): surrounding
|
|
2119
|
+
* whitespace and every trailing slash are trimmed, once, when the config is
|
|
2120
|
+
* read — and nothing else changes, so a gateway path prefix
|
|
2121
|
+
* ("https://gw.corp/consentera/") is kept. Every join below then adds exactly
|
|
2122
|
+
* one slash. The same rule Android (`trimEnd('/')`) and the CLI (`/\/+$/`)
|
|
2123
|
+
* apply.
|
|
2124
|
+
*/
|
|
2125
|
+
function normalizeBaseUrl(base) {
|
|
2126
|
+
return (typeof base === 'string' ? base.trim().replace(/\/+$/, '') : base);
|
|
2127
|
+
}
|
|
2128
|
+
function normalizeBases(config) {
|
|
2129
|
+
const out = { ...config };
|
|
2130
|
+
if (out.apiEndpoint !== undefined)
|
|
2131
|
+
out.apiEndpoint = normalizeBaseUrl(out.apiEndpoint);
|
|
2132
|
+
if (out.proxyEndpoint !== undefined)
|
|
2133
|
+
out.proxyEndpoint = normalizeBaseUrl(out.proxyEndpoint);
|
|
2134
|
+
return out;
|
|
2135
|
+
}
|
|
2068
2136
|
class HttpTransport {
|
|
2069
2137
|
config;
|
|
2070
2138
|
logger;
|
|
2071
2139
|
constructor(config, logger) {
|
|
2072
|
-
this.config = config;
|
|
2140
|
+
this.config = normalizeBases(config);
|
|
2073
2141
|
this.logger = logger;
|
|
2074
2142
|
}
|
|
2075
2143
|
/** Swap config after construction (the client re-reads customHeaders each call). */
|
|
2076
2144
|
updateConfig(patch) {
|
|
2077
|
-
this.config = { ...this.config, ...patch };
|
|
2145
|
+
this.config = { ...this.config, ...normalizeBases(patch) };
|
|
2078
2146
|
}
|
|
2079
2147
|
/**
|
|
2080
2148
|
* The credential decision for one road, in one place so the policy can be
|
|
@@ -2296,7 +2364,7 @@ class HttpTransport {
|
|
|
2296
2364
|
throw lastError;
|
|
2297
2365
|
const wait = lastError.retryAfterMs ?? backoffMs(attempt, policy);
|
|
2298
2366
|
this.logger.debug(`${method} ${path} retry ${attempt + 1}/${policy.attempts - 1} in ${wait}ms (${lastError.code ?? lastError.kind})`);
|
|
2299
|
-
await sleep(wait, options.signal);
|
|
2367
|
+
await sleep$1(wait, options.signal);
|
|
2300
2368
|
}
|
|
2301
2369
|
/* istanbul ignore next — the loop always throws on its last attempt. */
|
|
2302
2370
|
throw lastError ?? networkError(`${method} ${path} failed`);
|
|
@@ -2375,7 +2443,9 @@ class ConsentEraConsent extends EventEmitter {
|
|
|
2375
2443
|
"Consentera API origin (the SDK calls `${apiEndpoint}/api/v1/cookie-consent/*`); with " +
|
|
2376
2444
|
'the script-tag build, set `data-api-endpoint`.', 'ENDPOINT_REQUIRED');
|
|
2377
2445
|
}
|
|
2378
|
-
|
|
2446
|
+
// Trimmed ONCE, here (HC-13): "https://x/prefix/" and "https://x/prefix"
|
|
2447
|
+
// are the same base, the prefix is kept, and no request carries "//".
|
|
2448
|
+
this.config = this.mergeDefaults({ ...config, apiEndpoint: normalizeBaseUrl(config.apiEndpoint) });
|
|
2379
2449
|
this.logger = new Logger(config.apiKey ? 'info' : 'debug');
|
|
2380
2450
|
this.storage = new ConsentStorage(this.config.storage);
|
|
2381
2451
|
this.deviceId = this.getOrCreateDeviceId();
|
|
@@ -3684,6 +3754,56 @@ function principalBody(who) {
|
|
|
3684
3754
|
* ConsentEra Consent SDK — Consent Validation
|
|
3685
3755
|
* Validate consent status for single or multiple purposes
|
|
3686
3756
|
*/
|
|
3757
|
+
/** The longest the {@link ValidateOptions.retryWhenPending} re-ask will wait, in seconds. */
|
|
3758
|
+
const VALIDATE_PENDING_RETRY_CAP_SECONDS = 5;
|
|
3759
|
+
/** The wait used when an `applied: false` answer carries no usable `retry_after`. */
|
|
3760
|
+
const VALIDATE_PENDING_DEFAULT_SECONDS = 1;
|
|
3761
|
+
/**
|
|
3762
|
+
* The platform's validate answer, with `applied` and `retry_after` read the
|
|
3763
|
+
* one way every SDK reads them:
|
|
3764
|
+
*
|
|
3765
|
+
* * `applied` — a JSON boolean is kept; absent (a platform older than
|
|
3766
|
+
* 936715ad71) or any other type reads as `true`.
|
|
3767
|
+
* * `retry_after` — kept when it is a finite number >= 0 (rounded up to whole
|
|
3768
|
+
* seconds); anything else is dropped.
|
|
3769
|
+
*
|
|
3770
|
+
* Every other field passes through untouched.
|
|
3771
|
+
*/
|
|
3772
|
+
function normalizeValidateResponse(raw) {
|
|
3773
|
+
const r = raw;
|
|
3774
|
+
const out = { ...r };
|
|
3775
|
+
out.applied = typeof r.applied === 'boolean' ? r.applied : true;
|
|
3776
|
+
const ra = r.retry_after;
|
|
3777
|
+
if (typeof ra === 'number' && Number.isFinite(ra) && ra >= 0) {
|
|
3778
|
+
out.retry_after = Math.ceil(ra);
|
|
3779
|
+
}
|
|
3780
|
+
else {
|
|
3781
|
+
delete out.retry_after;
|
|
3782
|
+
}
|
|
3783
|
+
return out;
|
|
3784
|
+
}
|
|
3785
|
+
/** The wait before the one re-ask, in milliseconds. */
|
|
3786
|
+
function pendingRetryDelayMs(answer) {
|
|
3787
|
+
const asked = answer.retry_after ?? VALIDATE_PENDING_DEFAULT_SECONDS;
|
|
3788
|
+
return Math.min(asked, VALIDATE_PENDING_RETRY_CAP_SECONDS) * 1000;
|
|
3789
|
+
}
|
|
3790
|
+
function sleep(ms, signal) {
|
|
3791
|
+
return new Promise((resolve, reject) => {
|
|
3792
|
+
if (signal?.aborted) {
|
|
3793
|
+
reject(signal.reason ?? new Error('aborted'));
|
|
3794
|
+
return;
|
|
3795
|
+
}
|
|
3796
|
+
const t = setTimeout(() => {
|
|
3797
|
+
signal?.removeEventListener('abort', onAbort);
|
|
3798
|
+
resolve();
|
|
3799
|
+
}, ms);
|
|
3800
|
+
const onAbort = () => {
|
|
3801
|
+
clearTimeout(t);
|
|
3802
|
+
reject(signal?.reason ?? new Error('aborted'));
|
|
3803
|
+
};
|
|
3804
|
+
signal?.addEventListener('abort', onAbort, { once: true });
|
|
3805
|
+
});
|
|
3806
|
+
}
|
|
3687
3807
|
class ConsentValidator {
|
|
3688
3808
|
request;
|
|
3689
3809
|
logger;
|
|
@@ -3712,7 +3832,19 @@ class ConsentValidator {
|
|
|
3712
3832
|
*/
|
|
3713
3833
|
async check(who, purposeCode, options) {
|
|
3714
3834
|
const body = { ...principalBody(who), purpose_code: purposeCode };
|
|
3715
|
-
const
|
|
3835
|
+
const ask = async () => normalizeValidateResponse(await this.request('POST', '/consent/validate', body, undefined, { signal: options?.signal, timeoutMs: options?.timeoutMs }));
|
|
3836
|
+
let response = await ask();
|
|
3837
|
+
if (options?.retryWhenPending && response.applied === false) {
|
|
3838
|
+
// ONE re-ask, never a loop: the second answer is returned whatever it
|
|
3839
|
+
// says, so a projection that is still behind cannot hold the caller.
|
|
3840
|
+
const waitMs = pendingRetryDelayMs(response);
|
|
3841
|
+
this.logger.debug('Validate answer not yet applied; asking once more', {
|
|
3842
|
+
purpose: purposeCode,
|
|
3843
|
+
waitMs,
|
|
3844
|
+
});
|
|
3845
|
+
await sleep(waitMs, options.signal);
|
|
3846
|
+
response = await ask();
|
|
3847
|
+
}
|
|
3716
3848
|
// Never log the identifiers themselves — they are raw PII, and this line
|
|
3717
3849
|
// used to carry the opaque handle verbatim. Log WHICH way the person was
|
|
3718
3850
|
// named, which is what a support question actually needs.
|
|
@@ -3735,7 +3867,10 @@ class ConsentValidator {
|
|
|
3735
3867
|
...principalBody(who),
|
|
3736
3868
|
purpose_codes: purposeCodes,
|
|
3737
3869
|
};
|
|
3738
|
-
const
|
|
3870
|
+
const raw = await this.request('POST', '/consent/validate/bulk', body, undefined, { signal: options?.signal, timeoutMs: options?.timeoutMs });
|
|
3871
|
+
const response = Array.isArray(raw?.results)
|
|
3872
|
+
? { ...raw, results: raw.results.map((r) => normalizeValidateResponse(r)) }
|
|
3873
|
+
: raw;
|
|
3739
3874
|
this.logger.debug('Bulk consent validated', {
|
|
3740
3875
|
namedBy: 'data_principal_id' in who ? 'data_principal_id' : 'data_principal_identifiers',
|
|
3741
3876
|
purposes: purposeCodes.length,
|
|
@@ -3850,6 +3985,16 @@ class ConsentManager {
|
|
|
3850
3985
|
/**
|
|
3851
3986
|
* Update consent decisions for a data principal.
|
|
3852
3987
|
* Pass an array of purpose updates (grant or deny).
|
|
3988
|
+
*
|
|
3989
|
+
* @throws {ConsenteraPlanLimitError} HTTP 409 `PLAN_LIMIT_REACHED`,
|
|
3990
|
+
* `reasonCode: 'new_consents_only'` — the update would add a NEW
|
|
3991
|
+
* (data principal, purpose) grant past the organisation's plan
|
|
3992
|
+
* (`consents_max`). NOTHING was recorded and no `consent.changed` event
|
|
3993
|
+
* fires. It is not the person's refusal: never record or show it as
|
|
3994
|
+
* `denied`, and do not retry the same update — show a neutral "can't
|
|
3995
|
+
* accept new consents right now" instead. A denial, a withdrawal and a
|
|
3996
|
+
* re-grant of a purpose the person already holds are never refused.
|
|
3997
|
+
* `grant()` and `deny()` go through here and throw the same.
|
|
3853
3998
|
*/
|
|
3854
3999
|
async update(dataPrincipalId, updates, context, uiEventId = 'btn_save_preferences', options) {
|
|
3855
4000
|
const body = {
|
|
@@ -3959,7 +4104,7 @@ class ConsentManager {
|
|
|
3959
4104
|
captured_at: buildAffirmativeAction(uiEventId).captured_at,
|
|
3960
4105
|
client_context: this.context(),
|
|
3961
4106
|
};
|
|
3962
|
-
const response = await this.request('POST', '/consent/renew', body, undefined, { ...this.mutation(options), raw: true });
|
|
4107
|
+
const response = withRetryAfter(await this.request('POST', '/consent/renew', body, undefined, { ...this.mutation(options), raw: true }));
|
|
3963
4108
|
this.logger.info('Consent renewed', {
|
|
3964
4109
|
dataPrincipal: dataPrincipalId,
|
|
3965
4110
|
purposes: purposeIds,
|
|
@@ -3978,7 +4123,7 @@ class ConsentManager {
|
|
|
3978
4123
|
captured_at: buildAffirmativeAction(uiEventId).captured_at,
|
|
3979
4124
|
client_context: this.context(),
|
|
3980
4125
|
};
|
|
3981
|
-
const response = await this.request('POST', '/consent/renew/bulk', body, undefined, { ...this.mutation(options), raw: true });
|
|
4126
|
+
const response = withRetryAfter(await this.request('POST', '/consent/renew/bulk', body, undefined, { ...this.mutation(options), raw: true }));
|
|
3982
4127
|
this.logger.info('Bulk consent renewed', {
|
|
3983
4128
|
dataPrincipal: dataPrincipalId,
|
|
3984
4129
|
purposes: response.body.success_count,
|
|
@@ -3991,6 +4136,25 @@ class ConsentManager {
|
|
|
3991
4136
|
return response;
|
|
3992
4137
|
}
|
|
3993
4138
|
}
|
|
4139
|
+
/**
|
|
4140
|
+
* The renewal back-off is `retry_after` on the wire (seconds) — the platform's
|
|
4141
|
+
* canonical field name. RenewResponse used to model `retry_after_seconds`, which
|
|
4142
|
+
* the API never sends. For one release both spellings are read and both are
|
|
4143
|
+
* populated: `retry_after` wins when both arrive, a legacy `retry_after_seconds`
|
|
4144
|
+
* still decodes, and the deprecated alias mirrors the canonical value. Nothing is
|
|
4145
|
+
* added when neither is present, and the caller's object is never mutated.
|
|
4146
|
+
*/
|
|
4147
|
+
function withRetryAfter(response) {
|
|
4148
|
+
const body = response?.body;
|
|
4149
|
+
if (!body)
|
|
4150
|
+
return response;
|
|
4151
|
+
const canonical = typeof body.retry_after === 'number' ? body.retry_after : undefined;
|
|
4152
|
+
const legacy = typeof body.retry_after_seconds === 'number' ? body.retry_after_seconds : undefined;
|
|
4153
|
+
const value = canonical ?? legacy;
|
|
4154
|
+
if (value === undefined || (body.retry_after === value && body.retry_after_seconds === value))
|
|
4155
|
+
return response;
|
|
4156
|
+
return { ...response, body: { ...body, retry_after: value, retry_after_seconds: value } };
|
|
4157
|
+
}
|
|
3994
4158
|
|
|
3995
4159
|
/**
|
|
3996
4160
|
* Consentera Consent SDK — Callback Handler
|
|
@@ -4860,5 +5024,5 @@ if (typeof window !== 'undefined') {
|
|
|
4860
5024
|
window.ConsentEraConsent = ConsentEraConsent;
|
|
4861
5025
|
}
|
|
4862
5026
|
|
|
4863
|
-
export { CallbackHandler, ConsentBanner, ConsentEraApiError, ConsentEraClient, ConsentEraConsent, ConsentManager, ConsentModeBridge, ConsentSession, ConsentStorage, ConsentValidator, ConsenteraAuthError, ConsenteraConfigError, ConsenteraConflictError, ConsenteraError, ConsenteraGuardianError, ConsenteraIdentityError, ConsenteraNetworkError, ConsenteraNotFoundError, ConsenteraPermissionError, ConsenteraRateLimitError, ConsenteraServerError, ConsenteraTimeoutError, ConsenteraValidationError, DEFAULT_RETRY, DEFAULT_TIMEOUT_MS, DFConfigClient, GPPManager, HttpTransport, PreferenceCenter, PrincipalClient, SDK_HEADER_VALUE, SDK_NAME, SDK_PLATFORM, SDK_VERSION, TCFManager, buildClientContext, claimedCallbackStatus, ConsentEraConsent as default, installConsentModeBridge, isBrowser, isSecretKey, isSiteKey, newRequestId, parseRetryAfterMs, principalBody, readDecisionMessage, readStored, removeStored, storageAvailable, storageKeys, verifyCallbackSignature, writeStored };
|
|
5027
|
+
export { CallbackHandler, ConsentBanner, ConsentEraApiError, ConsentEraClient, ConsentEraConsent, ConsentManager, ConsentModeBridge, ConsentSession, ConsentStorage, ConsentValidator, ConsenteraAuthError, ConsenteraConfigError, ConsenteraConflictError, ConsenteraError, ConsenteraGuardianError, ConsenteraIdentityError, ConsenteraNetworkError, ConsenteraNotFoundError, ConsenteraPermissionError, ConsenteraPlanLimitError, ConsenteraRateLimitError, ConsenteraServerError, ConsenteraTimeoutError, ConsenteraValidationError, DEFAULT_RETRY, DEFAULT_TIMEOUT_MS, DFConfigClient, GPPManager, HttpTransport, PreferenceCenter, PrincipalClient, SDK_HEADER_VALUE, SDK_NAME, SDK_PLATFORM, SDK_VERSION, TCFManager, VALIDATE_PENDING_RETRY_CAP_SECONDS, buildClientContext, claimedCallbackStatus, ConsentEraConsent as default, installConsentModeBridge, isBrowser, isSecretKey, isSiteKey, newRequestId, normalizeValidateResponse, parseRetryAfterMs, principalBody, readDecisionMessage, readStored, removeStored, storageAvailable, storageKeys, verifyCallbackSignature, writeStored };
|
|
4864
5028
|
//# sourceMappingURL=consentera-consent.mjs.map
|