c15t 2.0.0-rc.8 → 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 +27 -1
- package/README.md +3 -3
- package/dist/index.cjs +116 -6
- package/dist/index.js +116 -6
- package/dist-types/client/client-factory.d.ts +1 -1
- package/dist-types/index.d.ts +1 -1
- package/dist-types/libs/policy.d.ts +10 -1
- package/dist-types/libs/save-consents.d.ts +1 -1
- package/dist-types/store/type.d.ts +57 -4
- package/dist-types/version.d.ts +1 -1
- package/docs/concepts/client-modes.md +1 -1
- package/docs/concepts/policy-packs.md +1 -1
- package/docs/iab/overview.md +9 -10
- package/docs/internationalization.md +1 -1
- package/docs/quickstart.md +4 -4
- package/package.json +5 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,31 @@
|
|
|
1
1
|
# c15t
|
|
2
2
|
|
|
3
|
+
## 2.0.0
|
|
4
|
+
|
|
5
|
+
### Major Changes
|
|
6
|
+
|
|
7
|
+
- 32617c9: Changelog available at https://c15t.com/changelog/2026-04-14-v2.0.0
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- Updated dependencies [32617c9]
|
|
12
|
+
- Updated dependencies [32617c9]
|
|
13
|
+
- @c15t/schema@2.0.0
|
|
14
|
+
- @c15t/translations@2.0.0
|
|
15
|
+
|
|
16
|
+
## 2.0.0-rc.10
|
|
17
|
+
|
|
18
|
+
### Patch Changes
|
|
19
|
+
|
|
20
|
+
- 9579b62: Add token-first legal-document consent groundwork for `2.0`.
|
|
21
|
+
|
|
22
|
+
- `c15t`: expand the unstable policy-consent input types so legal-document writes can prefer `documentSnapshotToken`, fall back to `policyHash`, and keep `policyId` only as a compatibility path.
|
|
23
|
+
- `@c15t/backend`: update legal-document consent writes to resolve append-only consent against a verified document snapshot token when configured, or against a provided document hash when only lighter-weight release proof is available.
|
|
24
|
+
- `@c15t/schema`: extend the subject consent schema and error shapes for legal-document snapshot tokens and hash-based legal-document resolution.
|
|
25
|
+
|
|
26
|
+
- Updated dependencies [9579b62]
|
|
27
|
+
- @c15t/schema@2.0.0-rc.6
|
|
28
|
+
|
|
3
29
|
## 2.0.0-rc.8
|
|
4
30
|
|
|
5
31
|
### Minor Changes
|
|
@@ -171,7 +197,7 @@
|
|
|
171
197
|
|
|
172
198
|
### Major Changes
|
|
173
199
|
|
|
174
|
-
- 126a78b: https://
|
|
200
|
+
- 126a78b: https://c15t.com/changelog/2026-02-12-v2.0.0-rc.0
|
|
175
201
|
|
|
176
202
|
### Patch Changes
|
|
177
203
|
|
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
[](https://github.com/c15t/c15t)
|
|
13
13
|
[](https://github.com/c15t/c15t/actions/workflows/ci.yml)
|
|
14
|
-
[](https://github.com/c15t/c15t/blob/main/LICENSE.md)
|
|
15
15
|
[](https://c15t.link/discord)
|
|
16
16
|
[](https://www.npmjs.com/package/c15t)
|
|
17
17
|
[](https://github.com/c15t/c15t)
|
|
@@ -116,8 +116,8 @@ Our preference is that you make use of GitHub's private vulnerability reporting
|
|
|
116
116
|
|
|
117
117
|
## License
|
|
118
118
|
|
|
119
|
-
[
|
|
119
|
+
[Apache License 2.0](https://github.com/c15t/c15t/blob/main/LICENSE.md)
|
|
120
120
|
|
|
121
121
|
---
|
|
122
122
|
|
|
123
|
-
**Built
|
|
123
|
+
**Built by [Inth](https://inth.com?utm_source=github&utm_medium=repopage_c15t)**
|
package/dist/index.cjs
CHANGED
|
@@ -601,14 +601,13 @@ const consent_types_consentTypes = [
|
|
|
601
601
|
}
|
|
602
602
|
];
|
|
603
603
|
const allConsentNames = consent_types_consentTypes.map((consent)=>consent.name);
|
|
604
|
-
const version = '2.0.0-rc.8';
|
|
605
604
|
const STORAGE_KEY_V2 = 'c15t';
|
|
606
605
|
const STORAGE_KEY = 'privacy-consent-storage';
|
|
607
606
|
const initial_state_initialState = {
|
|
608
607
|
debug: false,
|
|
609
608
|
config: {
|
|
610
609
|
pkg: 'c15t',
|
|
611
|
-
version:
|
|
610
|
+
version: "2.0.0",
|
|
612
611
|
mode: 'Unknown'
|
|
613
612
|
},
|
|
614
613
|
consents: consent_types_consentTypes.reduce((acc, consent)=>{
|
|
@@ -1768,11 +1767,24 @@ function flattenLayout(layout) {
|
|
|
1768
1767
|
}
|
|
1769
1768
|
function applyPolicyPurposeAllowlist(preferences, allowedPurposeIds) {
|
|
1770
1769
|
if (!allowedPurposeIds || 0 === allowedPurposeIds.length || allowedPurposeIds.includes('*')) return preferences;
|
|
1771
|
-
const allowed = new Set(
|
|
1770
|
+
const allowed = new Set([
|
|
1771
|
+
'necessary',
|
|
1772
|
+
...allowedPurposeIds
|
|
1773
|
+
]);
|
|
1772
1774
|
const next = {};
|
|
1773
1775
|
for (const [key, value] of Object.entries(preferences))next[key] = allowed.has(key) ? value : false;
|
|
1774
1776
|
return next;
|
|
1775
1777
|
}
|
|
1778
|
+
function stripDisallowedPreferenceKeys(preferences, allowedPurposeIds) {
|
|
1779
|
+
if (!allowedPurposeIds || 0 === allowedPurposeIds.length || allowedPurposeIds.includes('*')) return preferences;
|
|
1780
|
+
const allowed = new Set([
|
|
1781
|
+
'necessary',
|
|
1782
|
+
...allowedPurposeIds
|
|
1783
|
+
]);
|
|
1784
|
+
const next = {};
|
|
1785
|
+
for (const [key, value] of Object.entries(preferences))if (allowed.has(key)) next[key] = value;
|
|
1786
|
+
return next;
|
|
1787
|
+
}
|
|
1776
1788
|
function filterConsentCategoriesByPolicy(categories, allowedPurposeIds) {
|
|
1777
1789
|
const uniqueCategories = Array.from(new Set(categories));
|
|
1778
1790
|
if (!allowedPurposeIds || 0 === allowedPurposeIds.length || allowedPurposeIds.includes('*')) return uniqueCategories;
|
|
@@ -2801,6 +2813,7 @@ async function saveConsents({ manager, type, get, set, options, emitConsentChang
|
|
|
2801
2813
|
} else if ('necessary' === type) for (const consent of consentTypes)newConsents[consent.name] = true === consent.disabled ? consent.defaultValue : false;
|
|
2802
2814
|
const policyCategories = getEffectivePolicy(lastBannerFetchData)?.consent?.categories;
|
|
2803
2815
|
const effectiveConsents = applyPolicyPurposeAllowlist(newConsents, policyCategories);
|
|
2816
|
+
const requestPreferences = stripDisallowedPreferenceKeys(effectiveConsents, policyCategories);
|
|
2804
2817
|
const didChange = haveConsentsChanged(previousConsents, effectiveConsents, consentTypes);
|
|
2805
2818
|
const nextConsentCategoryLists = getConsentCategoryLists(effectiveConsents, consentCategories, consentTypes);
|
|
2806
2819
|
const previousConsentCategoryLists = getConsentCategoryLists(previousConsents, consentCategories, consentTypes);
|
|
@@ -2851,7 +2864,7 @@ async function saveConsents({ manager, type, get, set, options, emitConsentChang
|
|
|
2851
2864
|
const pendingSync = {
|
|
2852
2865
|
type,
|
|
2853
2866
|
subjectId,
|
|
2854
|
-
preferences:
|
|
2867
|
+
preferences: requestPreferences,
|
|
2855
2868
|
givenAt,
|
|
2856
2869
|
jurisdiction: locationInfo?.jurisdiction ?? void 0,
|
|
2857
2870
|
jurisdictionModel: model,
|
|
@@ -2890,7 +2903,7 @@ async function saveConsents({ manager, type, get, set, options, emitConsentChang
|
|
|
2890
2903
|
body: {
|
|
2891
2904
|
type: 'cookie_banner',
|
|
2892
2905
|
domain: window.location.hostname,
|
|
2893
|
-
preferences:
|
|
2906
|
+
preferences: requestPreferences,
|
|
2894
2907
|
subjectId,
|
|
2895
2908
|
jurisdiction: locationInfo?.jurisdiction ?? void 0,
|
|
2896
2909
|
jurisdictionModel: model ?? void 0,
|
|
@@ -3831,6 +3844,103 @@ const createConsentManagerStore = (manager, options = {})=>{
|
|
|
3831
3844
|
}
|
|
3832
3845
|
});
|
|
3833
3846
|
},
|
|
3847
|
+
unstable_acceptPolicyConsent: async (input)=>{
|
|
3848
|
+
const currentState = get();
|
|
3849
|
+
const currentInfo = currentState.consentInfo;
|
|
3850
|
+
const subjectId = currentInfo?.subjectId ?? generateSubjectId();
|
|
3851
|
+
const storedIdentifiers = sanitizeSubjectIdentifiers({
|
|
3852
|
+
externalId: currentInfo?.externalId,
|
|
3853
|
+
identityProvider: currentInfo?.identityProvider
|
|
3854
|
+
});
|
|
3855
|
+
const userIdentifiers = sanitizeSubjectIdentifiers({
|
|
3856
|
+
externalId: currentState.user?.id,
|
|
3857
|
+
identityProvider: currentState.user?.identityProvider
|
|
3858
|
+
});
|
|
3859
|
+
const inputIdentifiers = sanitizeSubjectIdentifiers({
|
|
3860
|
+
externalId: input.externalId,
|
|
3861
|
+
identityProvider: input.identityProvider
|
|
3862
|
+
});
|
|
3863
|
+
const externalId = inputIdentifiers.externalId ?? storedIdentifiers.externalId ?? userIdentifiers.externalId;
|
|
3864
|
+
const identityProvider = inputIdentifiers.identityProvider ?? storedIdentifiers.identityProvider ?? userIdentifiers.identityProvider;
|
|
3865
|
+
const givenAt = input.givenAt ?? Date.now();
|
|
3866
|
+
const domain = input.domain ?? ("u" > typeof window ? window.location.hostname : 'localhost');
|
|
3867
|
+
const isLegalDocumentType = 'privacy_policy' === input.type || 'terms_and_conditions' === input.type || 'dpa' === input.type;
|
|
3868
|
+
let legalDocumentFields = {};
|
|
3869
|
+
if (isLegalDocumentType) if (input.documentSnapshotToken) legalDocumentFields = {
|
|
3870
|
+
documentSnapshotToken: input.documentSnapshotToken
|
|
3871
|
+
};
|
|
3872
|
+
else if (input.policyHash) legalDocumentFields = {
|
|
3873
|
+
policyHash: input.policyHash
|
|
3874
|
+
};
|
|
3875
|
+
else if (input.policyId) legalDocumentFields = {
|
|
3876
|
+
policyId: input.policyId
|
|
3877
|
+
};
|
|
3878
|
+
else throw new Error('Legal document consent requires documentSnapshotToken, policyHash, or policyId.');
|
|
3879
|
+
const response = await manager.setConsent({
|
|
3880
|
+
body: {
|
|
3881
|
+
type: input.type,
|
|
3882
|
+
subjectId,
|
|
3883
|
+
domain,
|
|
3884
|
+
givenAt,
|
|
3885
|
+
uiSource: input.uiSource ?? 'api',
|
|
3886
|
+
...legalDocumentFields,
|
|
3887
|
+
...input.metadata ? {
|
|
3888
|
+
metadata: input.metadata
|
|
3889
|
+
} : {},
|
|
3890
|
+
...input.preferences ? {
|
|
3891
|
+
preferences: input.preferences
|
|
3892
|
+
} : {},
|
|
3893
|
+
...externalId ? {
|
|
3894
|
+
externalSubjectId: externalId
|
|
3895
|
+
} : {},
|
|
3896
|
+
...identityProvider ? {
|
|
3897
|
+
identityProvider
|
|
3898
|
+
} : {}
|
|
3899
|
+
}
|
|
3900
|
+
});
|
|
3901
|
+
if (!response.ok || !response.data) {
|
|
3902
|
+
const errorMsg = response.error?.message ?? 'Failed to accept policy consent';
|
|
3903
|
+
get().callbacks.onError?.({
|
|
3904
|
+
error: errorMsg
|
|
3905
|
+
});
|
|
3906
|
+
const error = new Error(errorMsg);
|
|
3907
|
+
error.code = response.error?.code;
|
|
3908
|
+
error.details = response.error?.details ?? null;
|
|
3909
|
+
error.status = response.error?.status;
|
|
3910
|
+
throw error;
|
|
3911
|
+
}
|
|
3912
|
+
const consent = {
|
|
3913
|
+
...response.data,
|
|
3914
|
+
givenAt: response.data.givenAt instanceof Date ? response.data.givenAt : new Date(response.data.givenAt)
|
|
3915
|
+
};
|
|
3916
|
+
const latestState = get();
|
|
3917
|
+
const latestInfo = latestState.consentInfo;
|
|
3918
|
+
const nextConsentInfo = {
|
|
3919
|
+
...latestInfo,
|
|
3920
|
+
time: givenAt,
|
|
3921
|
+
subjectId,
|
|
3922
|
+
...externalId ? {
|
|
3923
|
+
externalId
|
|
3924
|
+
} : {},
|
|
3925
|
+
...identityProvider ? {
|
|
3926
|
+
identityProvider
|
|
3927
|
+
} : {}
|
|
3928
|
+
};
|
|
3929
|
+
set({
|
|
3930
|
+
consentInfo: nextConsentInfo,
|
|
3931
|
+
...externalId ? {
|
|
3932
|
+
user: {
|
|
3933
|
+
id: externalId,
|
|
3934
|
+
identityProvider
|
|
3935
|
+
}
|
|
3936
|
+
} : {}
|
|
3937
|
+
});
|
|
3938
|
+
saveConsentToStorage({
|
|
3939
|
+
consents: latestState.consents,
|
|
3940
|
+
consentInfo: nextConsentInfo
|
|
3941
|
+
}, void 0, latestState.storageConfig);
|
|
3942
|
+
return consent;
|
|
3943
|
+
},
|
|
3834
3944
|
setOverrides: async (overrides)=>{
|
|
3835
3945
|
set({
|
|
3836
3946
|
overrides: {
|
|
@@ -3960,7 +4070,7 @@ function getOrCreateConsentRuntime(options, pkgInfo) {
|
|
|
3960
4070
|
config: {
|
|
3961
4071
|
...userConfig ?? {},
|
|
3962
4072
|
pkg: pkgInfo?.pkg || 'c15t',
|
|
3963
|
-
version: pkgInfo?.version ||
|
|
4073
|
+
version: pkgInfo?.version || "2.0.0",
|
|
3964
4074
|
mode: normalizedMode,
|
|
3965
4075
|
meta: {
|
|
3966
4076
|
...userConfig?.meta ?? {},
|
package/dist/index.js
CHANGED
|
@@ -525,14 +525,13 @@ const consent_types_consentTypes = [
|
|
|
525
525
|
}
|
|
526
526
|
];
|
|
527
527
|
const allConsentNames = consent_types_consentTypes.map((consent)=>consent.name);
|
|
528
|
-
const version = '2.0.0-rc.8';
|
|
529
528
|
const STORAGE_KEY_V2 = 'c15t';
|
|
530
529
|
const STORAGE_KEY = 'privacy-consent-storage';
|
|
531
530
|
const initial_state_initialState = {
|
|
532
531
|
debug: false,
|
|
533
532
|
config: {
|
|
534
533
|
pkg: 'c15t',
|
|
535
|
-
version:
|
|
534
|
+
version: "2.0.0",
|
|
536
535
|
mode: 'Unknown'
|
|
537
536
|
},
|
|
538
537
|
consents: consent_types_consentTypes.reduce((acc, consent)=>{
|
|
@@ -1692,11 +1691,24 @@ function flattenLayout(layout) {
|
|
|
1692
1691
|
}
|
|
1693
1692
|
function applyPolicyPurposeAllowlist(preferences, allowedPurposeIds) {
|
|
1694
1693
|
if (!allowedPurposeIds || 0 === allowedPurposeIds.length || allowedPurposeIds.includes('*')) return preferences;
|
|
1695
|
-
const allowed = new Set(
|
|
1694
|
+
const allowed = new Set([
|
|
1695
|
+
'necessary',
|
|
1696
|
+
...allowedPurposeIds
|
|
1697
|
+
]);
|
|
1696
1698
|
const next = {};
|
|
1697
1699
|
for (const [key, value] of Object.entries(preferences))next[key] = allowed.has(key) ? value : false;
|
|
1698
1700
|
return next;
|
|
1699
1701
|
}
|
|
1702
|
+
function stripDisallowedPreferenceKeys(preferences, allowedPurposeIds) {
|
|
1703
|
+
if (!allowedPurposeIds || 0 === allowedPurposeIds.length || allowedPurposeIds.includes('*')) return preferences;
|
|
1704
|
+
const allowed = new Set([
|
|
1705
|
+
'necessary',
|
|
1706
|
+
...allowedPurposeIds
|
|
1707
|
+
]);
|
|
1708
|
+
const next = {};
|
|
1709
|
+
for (const [key, value] of Object.entries(preferences))if (allowed.has(key)) next[key] = value;
|
|
1710
|
+
return next;
|
|
1711
|
+
}
|
|
1700
1712
|
function filterConsentCategoriesByPolicy(categories, allowedPurposeIds) {
|
|
1701
1713
|
const uniqueCategories = Array.from(new Set(categories));
|
|
1702
1714
|
if (!allowedPurposeIds || 0 === allowedPurposeIds.length || allowedPurposeIds.includes('*')) return uniqueCategories;
|
|
@@ -2724,6 +2736,7 @@ async function saveConsents({ manager, type, get, set, options, emitConsentChang
|
|
|
2724
2736
|
} else if ('necessary' === type) for (const consent of consentTypes)newConsents[consent.name] = true === consent.disabled ? consent.defaultValue : false;
|
|
2725
2737
|
const policyCategories = getEffectivePolicy(lastBannerFetchData)?.consent?.categories;
|
|
2726
2738
|
const effectiveConsents = applyPolicyPurposeAllowlist(newConsents, policyCategories);
|
|
2739
|
+
const requestPreferences = stripDisallowedPreferenceKeys(effectiveConsents, policyCategories);
|
|
2727
2740
|
const didChange = haveConsentsChanged(previousConsents, effectiveConsents, consentTypes);
|
|
2728
2741
|
const nextConsentCategoryLists = getConsentCategoryLists(effectiveConsents, consentCategories, consentTypes);
|
|
2729
2742
|
const previousConsentCategoryLists = getConsentCategoryLists(previousConsents, consentCategories, consentTypes);
|
|
@@ -2774,7 +2787,7 @@ async function saveConsents({ manager, type, get, set, options, emitConsentChang
|
|
|
2774
2787
|
const pendingSync = {
|
|
2775
2788
|
type,
|
|
2776
2789
|
subjectId,
|
|
2777
|
-
preferences:
|
|
2790
|
+
preferences: requestPreferences,
|
|
2778
2791
|
givenAt,
|
|
2779
2792
|
jurisdiction: locationInfo?.jurisdiction ?? void 0,
|
|
2780
2793
|
jurisdictionModel: model,
|
|
@@ -2813,7 +2826,7 @@ async function saveConsents({ manager, type, get, set, options, emitConsentChang
|
|
|
2813
2826
|
body: {
|
|
2814
2827
|
type: 'cookie_banner',
|
|
2815
2828
|
domain: window.location.hostname,
|
|
2816
|
-
preferences:
|
|
2829
|
+
preferences: requestPreferences,
|
|
2817
2830
|
subjectId,
|
|
2818
2831
|
jurisdiction: locationInfo?.jurisdiction ?? void 0,
|
|
2819
2832
|
jurisdictionModel: model ?? void 0,
|
|
@@ -3754,6 +3767,103 @@ const createConsentManagerStore = (manager, options = {})=>{
|
|
|
3754
3767
|
}
|
|
3755
3768
|
});
|
|
3756
3769
|
},
|
|
3770
|
+
unstable_acceptPolicyConsent: async (input)=>{
|
|
3771
|
+
const currentState = get();
|
|
3772
|
+
const currentInfo = currentState.consentInfo;
|
|
3773
|
+
const subjectId = currentInfo?.subjectId ?? generateSubjectId();
|
|
3774
|
+
const storedIdentifiers = sanitizeSubjectIdentifiers({
|
|
3775
|
+
externalId: currentInfo?.externalId,
|
|
3776
|
+
identityProvider: currentInfo?.identityProvider
|
|
3777
|
+
});
|
|
3778
|
+
const userIdentifiers = sanitizeSubjectIdentifiers({
|
|
3779
|
+
externalId: currentState.user?.id,
|
|
3780
|
+
identityProvider: currentState.user?.identityProvider
|
|
3781
|
+
});
|
|
3782
|
+
const inputIdentifiers = sanitizeSubjectIdentifiers({
|
|
3783
|
+
externalId: input.externalId,
|
|
3784
|
+
identityProvider: input.identityProvider
|
|
3785
|
+
});
|
|
3786
|
+
const externalId = inputIdentifiers.externalId ?? storedIdentifiers.externalId ?? userIdentifiers.externalId;
|
|
3787
|
+
const identityProvider = inputIdentifiers.identityProvider ?? storedIdentifiers.identityProvider ?? userIdentifiers.identityProvider;
|
|
3788
|
+
const givenAt = input.givenAt ?? Date.now();
|
|
3789
|
+
const domain = input.domain ?? ("u" > typeof window ? window.location.hostname : 'localhost');
|
|
3790
|
+
const isLegalDocumentType = 'privacy_policy' === input.type || 'terms_and_conditions' === input.type || 'dpa' === input.type;
|
|
3791
|
+
let legalDocumentFields = {};
|
|
3792
|
+
if (isLegalDocumentType) if (input.documentSnapshotToken) legalDocumentFields = {
|
|
3793
|
+
documentSnapshotToken: input.documentSnapshotToken
|
|
3794
|
+
};
|
|
3795
|
+
else if (input.policyHash) legalDocumentFields = {
|
|
3796
|
+
policyHash: input.policyHash
|
|
3797
|
+
};
|
|
3798
|
+
else if (input.policyId) legalDocumentFields = {
|
|
3799
|
+
policyId: input.policyId
|
|
3800
|
+
};
|
|
3801
|
+
else throw new Error('Legal document consent requires documentSnapshotToken, policyHash, or policyId.');
|
|
3802
|
+
const response = await manager.setConsent({
|
|
3803
|
+
body: {
|
|
3804
|
+
type: input.type,
|
|
3805
|
+
subjectId,
|
|
3806
|
+
domain,
|
|
3807
|
+
givenAt,
|
|
3808
|
+
uiSource: input.uiSource ?? 'api',
|
|
3809
|
+
...legalDocumentFields,
|
|
3810
|
+
...input.metadata ? {
|
|
3811
|
+
metadata: input.metadata
|
|
3812
|
+
} : {},
|
|
3813
|
+
...input.preferences ? {
|
|
3814
|
+
preferences: input.preferences
|
|
3815
|
+
} : {},
|
|
3816
|
+
...externalId ? {
|
|
3817
|
+
externalSubjectId: externalId
|
|
3818
|
+
} : {},
|
|
3819
|
+
...identityProvider ? {
|
|
3820
|
+
identityProvider
|
|
3821
|
+
} : {}
|
|
3822
|
+
}
|
|
3823
|
+
});
|
|
3824
|
+
if (!response.ok || !response.data) {
|
|
3825
|
+
const errorMsg = response.error?.message ?? 'Failed to accept policy consent';
|
|
3826
|
+
get().callbacks.onError?.({
|
|
3827
|
+
error: errorMsg
|
|
3828
|
+
});
|
|
3829
|
+
const error = new Error(errorMsg);
|
|
3830
|
+
error.code = response.error?.code;
|
|
3831
|
+
error.details = response.error?.details ?? null;
|
|
3832
|
+
error.status = response.error?.status;
|
|
3833
|
+
throw error;
|
|
3834
|
+
}
|
|
3835
|
+
const consent = {
|
|
3836
|
+
...response.data,
|
|
3837
|
+
givenAt: response.data.givenAt instanceof Date ? response.data.givenAt : new Date(response.data.givenAt)
|
|
3838
|
+
};
|
|
3839
|
+
const latestState = get();
|
|
3840
|
+
const latestInfo = latestState.consentInfo;
|
|
3841
|
+
const nextConsentInfo = {
|
|
3842
|
+
...latestInfo,
|
|
3843
|
+
time: givenAt,
|
|
3844
|
+
subjectId,
|
|
3845
|
+
...externalId ? {
|
|
3846
|
+
externalId
|
|
3847
|
+
} : {},
|
|
3848
|
+
...identityProvider ? {
|
|
3849
|
+
identityProvider
|
|
3850
|
+
} : {}
|
|
3851
|
+
};
|
|
3852
|
+
set({
|
|
3853
|
+
consentInfo: nextConsentInfo,
|
|
3854
|
+
...externalId ? {
|
|
3855
|
+
user: {
|
|
3856
|
+
id: externalId,
|
|
3857
|
+
identityProvider
|
|
3858
|
+
}
|
|
3859
|
+
} : {}
|
|
3860
|
+
});
|
|
3861
|
+
saveConsentToStorage({
|
|
3862
|
+
consents: latestState.consents,
|
|
3863
|
+
consentInfo: nextConsentInfo
|
|
3864
|
+
}, void 0, latestState.storageConfig);
|
|
3865
|
+
return consent;
|
|
3866
|
+
},
|
|
3757
3867
|
setOverrides: async (overrides)=>{
|
|
3758
3868
|
set({
|
|
3759
3869
|
overrides: {
|
|
@@ -3883,7 +3993,7 @@ function getOrCreateConsentRuntime(options, pkgInfo) {
|
|
|
3883
3993
|
config: {
|
|
3884
3994
|
...userConfig ?? {},
|
|
3885
3995
|
pkg: pkgInfo?.pkg || 'c15t',
|
|
3886
|
-
version: pkgInfo?.version ||
|
|
3996
|
+
version: pkgInfo?.version || "2.0.0",
|
|
3887
3997
|
mode: normalizedMode,
|
|
3888
3998
|
meta: {
|
|
3889
3999
|
...userConfig?.meta ?? {},
|
|
@@ -82,7 +82,7 @@ export type ConsentManagerOptions = {
|
|
|
82
82
|
* @remarks
|
|
83
83
|
* When provided, this takes precedence over `store.offlinePolicy`.
|
|
84
84
|
*
|
|
85
|
-
* @see {@link https://
|
|
85
|
+
* @see {@link https://c15t.com/docs/frameworks/javascript/policy-packs}
|
|
86
86
|
*/
|
|
87
87
|
offlinePolicy?: StoreOptions['offlinePolicy'];
|
|
88
88
|
/**
|
package/dist-types/index.d.ts
CHANGED
|
@@ -27,7 +27,7 @@ export { buildPrefetchScript } from './libs/prefetch';
|
|
|
27
27
|
export { emitScriptDebugEvent, getLoadedScriptIds, isScriptLoaded, loadScripts, type Script, type ScriptDebugAction, type ScriptDebugEvent, type ScriptDebugEventInput, type ScriptDebugListener, type ScriptDebugScope, type ScriptDebugSource, type ScriptLifecycleCallback, subscribeToScriptDebugEvents, unloadScripts, updateScripts, } from './libs/script-loader';
|
|
28
28
|
export { type ConsentRuntimeOptions, type ConsentRuntimePkgInfo, type ConsentRuntimeResult, clearConsentRuntimeCache, getOrCreateConsentRuntime, } from './runtime';
|
|
29
29
|
export { createConsentManagerStore } from './store';
|
|
30
|
-
export type { ActiveUI, ConsentStoreState, InitDataSource, OfflinePolicyConfig, PolicyScopeMode, PolicySurfaceState, PolicyUiAction, PolicyUiActionDirection, PolicyUiActionGroup, PolicyUiProfile, PolicyUiSurfaceConfig, SSRInitialData, SSRInitRequestContext, SSRInitRequestMetadata, SSRSkippedReason, StoreOptions, } from './store/type';
|
|
30
|
+
export type { ActiveUI, ConsentStoreState, InitDataSource, OfflinePolicyConfig, PolicyScopeMode, PolicySurfaceState, PolicyUiAction, PolicyUiActionDirection, PolicyUiActionGroup, PolicyUiProfile, PolicyUiSurfaceConfig, SSRInitialData, SSRInitRequestContext, SSRInitRequestMetadata, SSRSkippedReason, StoreOptions, UnstableGenericPolicyConsentInput, UnstableLegalDocumentConsentInput, UnstablePolicyConsentInput, } from './store/type';
|
|
31
31
|
export { defaultTranslationConfig } from './translations';
|
|
32
32
|
export type { Callback, Callbacks, OnBannerFetchedPayload, OnConsentChangedPayload, OnConsentSetPayload, OnErrorPayload, } from './types/callbacks';
|
|
33
33
|
export type { ConsentBannerResponse, ConsentState, LocationInfo, NamespaceProps, } from './types/compliance';
|
|
@@ -20,9 +20,18 @@ export interface PolicyValidationIssue {
|
|
|
20
20
|
* Any preference key not in `allowedPurposeIds` is forced to `false`.
|
|
21
21
|
* This prevents backend allowlist enforcement errors when clients hold
|
|
22
22
|
* additional consent keys (for example from IAB category mapping).
|
|
23
|
-
* When `allowedPurposeIds` contains `*`, no filtering is applied.
|
|
23
|
+
* When `allowedPurposeIds` contains `*`, no filtering is applied. `necessary`
|
|
24
|
+
* is always retained.
|
|
24
25
|
*/
|
|
25
26
|
export declare function applyPolicyPurposeAllowlist<T extends Record<string, boolean>>(preferences: T, allowedPurposeIds?: string[]): T;
|
|
27
|
+
/**
|
|
28
|
+
* Strips preference keys that are outside the active policy allowlist.
|
|
29
|
+
*
|
|
30
|
+
* Use this for API payloads when the backend enforces strict purpose scope and
|
|
31
|
+
* rejects unknown preference keys entirely. `necessary` is always retained to
|
|
32
|
+
* stay aligned with `applyPolicyPurposeAllowlist()`.
|
|
33
|
+
*/
|
|
34
|
+
export declare function stripDisallowedPreferenceKeys<T extends Record<string, boolean>>(preferences: T, allowedPurposeIds?: string[]): Partial<T>;
|
|
26
35
|
/**
|
|
27
36
|
* Filters consent categories against a policy purpose allowlist.
|
|
28
37
|
*
|
|
@@ -15,7 +15,7 @@ export interface PendingConsentSync {
|
|
|
15
15
|
subjectId: string;
|
|
16
16
|
externalId?: string;
|
|
17
17
|
identityProvider?: string;
|
|
18
|
-
preferences: ConsentState
|
|
18
|
+
preferences: Partial<ConsentState>;
|
|
19
19
|
givenAt: number;
|
|
20
20
|
jurisdiction?: string;
|
|
21
21
|
jurisdictionModel?: string | null;
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* @packageDocumentation
|
|
3
3
|
* Defines the core types and interfaces for the consent management store.
|
|
4
4
|
*/
|
|
5
|
-
import type { Branding, InitOutput, PolicyConfig, PolicyScopeMode, PolicyUiAction, PolicyUiActionDirection, PolicyUiActionGroup, PolicyUiProfile, PolicyUiSurfaceConfig } from '@c15t/schema/types';
|
|
5
|
+
import type { Branding, InitOutput, PolicyConfig, PolicyScopeMode, PolicyUiAction, PolicyUiActionDirection, PolicyUiActionGroup, PolicyUiProfile, PolicyUiSurfaceConfig, PostSubjectOutput } from '@c15t/schema/types';
|
|
6
6
|
import type { Model } from '../libs/determine-model';
|
|
7
7
|
import type { StorageConfig } from '../libs/cookie';
|
|
8
8
|
import type { HasCondition } from '../libs/has';
|
|
@@ -48,6 +48,53 @@ export interface PolicySurfaceState {
|
|
|
48
48
|
/** Scroll lock hint from backend runtime policy. */
|
|
49
49
|
scrollLock?: boolean;
|
|
50
50
|
}
|
|
51
|
+
type RequireAtLeastOne<T, Keys extends keyof T = keyof T> = Keys extends keyof T ? Required<Pick<T, Keys>> & Partial<Omit<T, Keys>> : never;
|
|
52
|
+
/**
|
|
53
|
+
* Experimental input for legal-document consent writes.
|
|
54
|
+
*
|
|
55
|
+
* @remarks
|
|
56
|
+
* Preferred identifier flow:
|
|
57
|
+
* - `documentSnapshotToken` for authoritative, signed release metadata
|
|
58
|
+
* - `policyHash` when the caller only knows the rendered document hash
|
|
59
|
+
* - `policyId` only as a compatibility fallback for older backends
|
|
60
|
+
*
|
|
61
|
+
* @experimental
|
|
62
|
+
*/
|
|
63
|
+
type UnstableLegalDocumentConsentInputBase = {
|
|
64
|
+
type: 'privacy_policy' | 'terms_and_conditions' | 'dpa';
|
|
65
|
+
policyId?: string;
|
|
66
|
+
policyHash?: string;
|
|
67
|
+
documentSnapshotToken?: string;
|
|
68
|
+
domain?: string;
|
|
69
|
+
givenAt?: number;
|
|
70
|
+
metadata?: Record<string, unknown>;
|
|
71
|
+
preferences?: Record<string, boolean>;
|
|
72
|
+
uiSource?: string;
|
|
73
|
+
externalId?: string;
|
|
74
|
+
identityProvider?: string;
|
|
75
|
+
};
|
|
76
|
+
export type UnstableLegalDocumentConsentInput = UnstableLegalDocumentConsentInputBase & RequireAtLeastOne<Pick<UnstableLegalDocumentConsentInputBase, 'policyId' | 'policyHash' | 'documentSnapshotToken'>>;
|
|
77
|
+
/**
|
|
78
|
+
* Experimental input for non-legal policy consent writes.
|
|
79
|
+
*
|
|
80
|
+
* @experimental
|
|
81
|
+
*/
|
|
82
|
+
export interface UnstableGenericPolicyConsentInput {
|
|
83
|
+
type: 'marketing_communications' | 'age_verification' | 'other';
|
|
84
|
+
domain?: string;
|
|
85
|
+
givenAt?: number;
|
|
86
|
+
metadata?: Record<string, unknown>;
|
|
87
|
+
preferences?: Record<string, boolean>;
|
|
88
|
+
uiSource?: string;
|
|
89
|
+
externalId?: string;
|
|
90
|
+
identityProvider?: string;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Experimental input for policy-based consent writes.
|
|
94
|
+
*
|
|
95
|
+
* @experimental
|
|
96
|
+
*/
|
|
97
|
+
export type UnstablePolicyConsentInput = UnstableLegalDocumentConsentInput | UnstableGenericPolicyConsentInput;
|
|
51
98
|
/**
|
|
52
99
|
* Offline policy preview payload for the headless runtime.
|
|
53
100
|
*
|
|
@@ -61,8 +108,8 @@ export interface PolicySurfaceState {
|
|
|
61
108
|
* resolve it locally
|
|
62
109
|
* - `policy` / `policyDecision`: inject a fully synthetic resolved result
|
|
63
110
|
*
|
|
64
|
-
* @see {@link https://
|
|
65
|
-
* @see {@link https://
|
|
111
|
+
* @see {@link https://c15t.com/docs/frameworks/javascript/policy-packs}
|
|
112
|
+
* @see {@link https://c15t.com/docs/frameworks/react/concepts/policy-packs}
|
|
66
113
|
*/
|
|
67
114
|
export type OfflinePolicyConfig = {
|
|
68
115
|
/**
|
|
@@ -440,7 +487,7 @@ export interface StoreOptions extends Partial<StoreConfig> {
|
|
|
440
487
|
*
|
|
441
488
|
* Ignored in hosted/custom modes.
|
|
442
489
|
*
|
|
443
|
-
* @see {@link https://
|
|
490
|
+
* @see {@link https://c15t.com/docs/frameworks/javascript/policy-packs}
|
|
444
491
|
*/
|
|
445
492
|
offlinePolicy?: OfflinePolicyConfig;
|
|
446
493
|
/**
|
|
@@ -646,6 +693,12 @@ export interface StoreActions {
|
|
|
646
693
|
* @throws {Error} When the underlying identify-user request fails
|
|
647
694
|
*/
|
|
648
695
|
identifyUser: (user: User) => Promise<void>;
|
|
696
|
+
/**
|
|
697
|
+
* Writes a policy-based consent such as terms and conditions.
|
|
698
|
+
*
|
|
699
|
+
* @experimental
|
|
700
|
+
*/
|
|
701
|
+
unstable_acceptPolicyConsent: (input: UnstablePolicyConsentInput) => Promise<PostSubjectOutput>;
|
|
649
702
|
/**
|
|
650
703
|
* Updates the selected consent state for a specific consent type.
|
|
651
704
|
*
|
package/dist-types/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const version = "2.0.0
|
|
1
|
+
export declare const version = "2.0.0";
|
|
@@ -15,7 +15,7 @@ c15t supports three client modes that determine how consent data is stored and s
|
|
|
15
15
|
|
|
16
16
|
## Hosted Mode (Recommended)
|
|
17
17
|
|
|
18
|
-
The default mode. Connects to a c15t backend for full consent lifecycle management. We recommend using [
|
|
18
|
+
The default mode. Connects to a c15t backend for full consent lifecycle management. We recommend using [inth.com](https://inth.com) for a fully managed experience, but you can [self-host](/docs/self-host) as well.
|
|
19
19
|
|
|
20
20
|
> ℹ️ **Info:**
|
|
21
21
|
> mode: 'hosted' is the preferred value. The legacy alias mode: 'c15t' is still supported for backward compatibility.
|
|
@@ -8,7 +8,7 @@ A policy pack is an ordered array of policies. Each policy targets a region or c
|
|
|
8
8
|
|
|
9
9
|
There are three ways to configure policy packs:
|
|
10
10
|
|
|
11
|
-
1. **
|
|
11
|
+
1. **inth.com (recommended)** — use [inth.com](https://inth.com) as your hosted backend. Configure packs visually in the dashboard or via API — no code changes required. Works with any frontend, including static sites.
|
|
12
12
|
2. **Self-hosted backend** — define packs in code via `policyPacks` and resolve them from real request geo data. Full control over policy logic and storage.
|
|
13
13
|
3. **Offline fallback** — pass the same policy shapes to the frontend via `offlinePolicy.policyPacks`. Use this mainly for local development, demos, deterministic testing, or resilience when the backend is temporarily unreachable. If you omit `offlinePolicy.policyPacks`, c15t falls back to a synthetic worldwide opt-in banner instead of no-banner mode.
|
|
14
14
|
|
package/docs/iab/overview.md
CHANGED
|
@@ -15,12 +15,12 @@ When your site participates in the IAB ecosystem (ad exchanges, SSPs, DSPs, DMPs
|
|
|
15
15
|
|
|
16
16
|
## CMP Registration
|
|
17
17
|
|
|
18
|
-
[
|
|
18
|
+
[inth.com](https://inth.com) is pending validation as an IAB Europe-registered CMP for c15t. Once approved, when you use inth.com as your backend, the correct CMP ID will be automatically provided to your client via the `/init` endpoint — no client-side configuration needed.
|
|
19
19
|
|
|
20
20
|
If you self-host the c15t backend and have your own CMP registration with IAB Europe, you can configure your CMP ID on the backend via `advanced.iab.cmpId` or on the client via the `iab.cmpId` option. A valid (non-zero) CMP ID is required for IAB TCF compliance.
|
|
21
21
|
|
|
22
22
|
> ℹ️ **Info:**
|
|
23
|
-
> If you heavily customize or build your own IAB banner or dialog (rather than using the default IABConsentBanner and IABConsentDialog components), you cannot use
|
|
23
|
+
> If you heavily customize or build your own IAB banner or dialog (rather than using the default IABConsentBanner and IABConsentDialog components), you cannot use inth.com's CMP ID. You must register your own CMP with IAB Europe and use your own CMP ID.
|
|
24
24
|
|
|
25
25
|
## How c15t Implements TCF
|
|
26
26
|
|
|
@@ -40,17 +40,17 @@ Enable IAB TCF in the runtime options:
|
|
|
40
40
|
|
|
41
41
|
```ts
|
|
42
42
|
import { getOrCreateConsentRuntime } from 'c15t';
|
|
43
|
+
import { iab } from '@c15t/iab';
|
|
43
44
|
|
|
44
45
|
const { consentStore } = getOrCreateConsentRuntime({
|
|
45
46
|
mode: 'hosted',
|
|
46
47
|
backendURL: 'https://your-instance.c15t.dev',
|
|
47
|
-
iab: {
|
|
48
|
-
enabled: true,
|
|
48
|
+
iab: iab({
|
|
49
49
|
vendors: [1, 2, 10, 25], // IAB vendor IDs you work with
|
|
50
|
-
// cmpId is automatically provided by the backend (
|
|
50
|
+
// cmpId is automatically provided by the backend (inth.com).
|
|
51
51
|
// Only set this if you have your own CMP registration with IAB Europe.
|
|
52
52
|
// cmpId: 123,
|
|
53
|
-
},
|
|
53
|
+
}),
|
|
54
54
|
});
|
|
55
55
|
```
|
|
56
56
|
|
|
@@ -91,14 +91,13 @@ window.__tcfapi('getTCData', 2, (tcData, success) => {
|
|
|
91
91
|
|
|
92
92
|
## IAB Configuration Options
|
|
93
93
|
|
|
94
|
-
|
|
94
|
+
Configure IAB mode with `iab({ ... })` from `@c15t/iab`. The factory enables the addon and injects the runtime module automatically. The user-facing options are:
|
|
95
95
|
|
|
96
96
|
|Option|Type|Description|
|
|
97
97
|
|--|--|--|
|
|
98
|
-
|`
|
|
99
|
-
|`cmpId`|`number`|CMP ID registered with IAB Europe. Automatically provided by the backend when using consent.io. Only set this if you have your own CMP registration.|
|
|
98
|
+
|`cmpId`|`number`|CMP ID registered with IAB Europe. Automatically provided by the backend when using inth.com. Only set this if you have your own CMP registration.|
|
|
100
99
|
|`vendors`|`number[]`|IAB vendor IDs that your site works with|
|
|
101
|
-
|`
|
|
100
|
+
|`customVendors`|`NonIABVendor[]`|Custom vendors not in the IAB registry|
|
|
102
101
|
|
|
103
102
|
## Key Concepts
|
|
104
103
|
|
|
@@ -10,7 +10,7 @@ There are two ways c15t can load translations: client-side or server-side.
|
|
|
10
10
|
|
|
11
11
|
|Server-side|Client-side|
|
|
12
12
|
|--|--|
|
|
13
|
-
|The best way to reduce bundle size and improve performance. We can detect the user's language based on the browser's language settings, allowing for the most accurate translations. By default, when using a [
|
|
13
|
+
|The best way to reduce bundle size and improve performance. We can detect the user's language based on the browser's language settings, allowing for the most accurate translations. By default, when using a [inth.com](https://inth.com) hosted instance, [these languages](https://github.com/c15t/c15t/tree/main/packages/translations/src/translations) are supported.|Bundled with the application allowing for multiple languages to be supported without the need for a backend. The more translations you have, the larger the bundle size will be, which may impact the performance of your application.|
|
|
14
14
|
|
|
15
15
|
## Basic Configuration
|
|
16
16
|
|
package/docs/quickstart.md
CHANGED
|
@@ -126,10 +126,10 @@ Install c15t agent skills to let AI agents help with styling, i18n, scripts & ot
|
|
|
126
126
|
|
|
127
127
|
|Package manager|Command|
|
|
128
128
|
|:--|:--|
|
|
129
|
-
|npm|`npx @c15t/cli
|
|
130
|
-
|pnpm|`pnpm dlx @c15t/cli
|
|
131
|
-
|yarn|`yarn dlx @c15t/cli
|
|
132
|
-
|bun|`bunx @c15t/cli
|
|
129
|
+
|npm|`npx @c15t/cli skills`|
|
|
130
|
+
|pnpm|`pnpm dlx @c15t/cli skills`|
|
|
131
|
+
|yarn|`yarn dlx @c15t/cli skills`|
|
|
132
|
+
|bun|`bunx @c15t/cli skills`|
|
|
133
133
|
|
|
134
134
|
See [AI Agents](/docs/ai-agents) for bundled package docs and agent skills.
|
|
135
135
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "c15t",
|
|
3
|
-
"version": "2.0.0
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "Developer-first CMP for JavaScript: cookie banner, consent manager, preferences centre. GDPR ready with minimal setup and rich customization",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"nextjs",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"url": "https://github.com/c15t/c15t.git",
|
|
27
27
|
"directory": "packages/core"
|
|
28
28
|
},
|
|
29
|
-
"license": "
|
|
29
|
+
"license": "Apache-2.0",
|
|
30
30
|
"type": "module",
|
|
31
31
|
"exports": {
|
|
32
32
|
".": {
|
|
@@ -64,12 +64,12 @@
|
|
|
64
64
|
"not op_mini all"
|
|
65
65
|
],
|
|
66
66
|
"dependencies": {
|
|
67
|
-
"@c15t/schema": "2.0.0
|
|
68
|
-
"@c15t/translations": "2.0.0
|
|
67
|
+
"@c15t/schema": "2.0.0",
|
|
68
|
+
"@c15t/translations": "2.0.0",
|
|
69
69
|
"zustand": "5.0.12"
|
|
70
70
|
},
|
|
71
71
|
"devDependencies": {
|
|
72
|
-
"@c15t/typescript-config": "0.0.1
|
|
72
|
+
"@c15t/typescript-config": "0.0.1",
|
|
73
73
|
"@c15t/vitest-config": "1.0.0",
|
|
74
74
|
"genversion": "3.2.0",
|
|
75
75
|
"vitest-localstorage-mock": "0.1.2"
|