c15t 1.6.0 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/dist/index.cjs +658 -98
  3. package/dist/index.d.ts +4 -1
  4. package/dist/index.d.ts.map +1 -1
  5. package/dist/index.js +632 -93
  6. package/dist/libs/fetch-consent-banner.d.ts +2 -0
  7. package/dist/libs/fetch-consent-banner.d.ts.map +1 -1
  8. package/dist/libs/gtm.d.ts +7 -0
  9. package/dist/libs/gtm.d.ts.map +1 -1
  10. package/dist/libs/has.d.ts +9 -0
  11. package/dist/libs/has.d.ts.map +1 -1
  12. package/dist/libs/iframe-blocker/core.d.ts +90 -0
  13. package/dist/libs/iframe-blocker/core.d.ts.map +1 -0
  14. package/dist/libs/iframe-blocker/index.d.ts +3 -0
  15. package/dist/libs/iframe-blocker/index.d.ts.map +1 -0
  16. package/dist/libs/iframe-blocker/store.d.ts +35 -0
  17. package/dist/libs/iframe-blocker/store.d.ts.map +1 -0
  18. package/dist/libs/iframe-blocker/types.d.ts +26 -0
  19. package/dist/libs/iframe-blocker/types.d.ts.map +1 -0
  20. package/dist/libs/save-consents.d.ts +3 -0
  21. package/dist/libs/save-consents.d.ts.map +1 -1
  22. package/dist/libs/script-loader/core.d.ts +117 -0
  23. package/dist/libs/script-loader/core.d.ts.map +1 -0
  24. package/dist/libs/script-loader/index.d.ts +10 -0
  25. package/dist/libs/script-loader/index.d.ts.map +1 -0
  26. package/dist/libs/script-loader/store.d.ts +58 -0
  27. package/dist/libs/script-loader/store.d.ts.map +1 -0
  28. package/dist/libs/script-loader/types.d.ts +160 -0
  29. package/dist/libs/script-loader/types.d.ts.map +1 -0
  30. package/dist/libs/script-loader/utils.d.ts +56 -0
  31. package/dist/libs/script-loader/utils.d.ts.map +1 -0
  32. package/dist/libs/tracking-blocker.d.ts +53 -4
  33. package/dist/libs/tracking-blocker.d.ts.map +1 -1
  34. package/dist/store.d.ts +38 -3
  35. package/dist/store.d.ts.map +1 -1
  36. package/dist/store.initial-state.d.ts +1 -8
  37. package/dist/store.initial-state.d.ts.map +1 -1
  38. package/dist/store.type.d.ts +52 -0
  39. package/dist/store.type.d.ts.map +1 -1
  40. package/dist/types/callbacks.d.ts +21 -0
  41. package/dist/types/callbacks.d.ts.map +1 -1
  42. package/dist/types/compliance.d.ts +2 -0
  43. package/dist/types/compliance.d.ts.map +1 -1
  44. package/dist/types/gdpr.d.ts +23 -0
  45. package/dist/types/gdpr.d.ts.map +1 -1
  46. package/dist/types/index.d.ts +1 -29
  47. package/dist/types/index.d.ts.map +1 -1
  48. package/dist/version.d.ts +1 -1
  49. package/package.json +3 -3
package/dist/index.cjs CHANGED
@@ -26,16 +26,23 @@ __webpack_require__.r(__webpack_exports__);
26
26
  __webpack_require__.d(__webpack_exports__, {
27
27
  CustomClient: ()=>CustomClient,
28
28
  consentTypes: ()=>gdpr_consentTypes,
29
- C15tClient: ()=>C15tClient,
30
- defaultTranslationConfig: ()=>defaultTranslationConfig,
31
- deepMergeTranslations: ()=>translations_namespaceObject.deepMergeTranslations,
32
29
  mergeTranslationConfigs: ()=>translations_namespaceObject.mergeTranslationConfigs,
30
+ prepareTranslationConfig: ()=>translations_namespaceObject.prepareTranslationConfig,
31
+ deepMergeTranslations: ()=>translations_namespaceObject.deepMergeTranslations,
33
32
  createTrackingBlocker: ()=>createTrackingBlocker,
33
+ loadScripts: ()=>loadScripts,
34
+ getLoadedScriptIds: ()=>getLoadedScriptIds,
35
+ createConsentManagerStore: ()=>createConsentManagerStore,
36
+ unloadScripts: ()=>unloadScripts,
37
+ allConsentNames: ()=>allConsentNames,
38
+ C15tClient: ()=>C15tClient,
39
+ defaultTranslationConfig: ()=>defaultTranslationConfig,
34
40
  configureConsentManager: ()=>configureConsentManager,
41
+ createIframeBlocker: ()=>createIframeBlocker,
35
42
  detectBrowserLanguage: ()=>translations_namespaceObject.detectBrowserLanguage,
36
- prepareTranslationConfig: ()=>translations_namespaceObject.prepareTranslationConfig,
43
+ isScriptLoaded: ()=>isScriptLoaded,
37
44
  OfflineClient: ()=>OfflineClient,
38
- createConsentManagerStore: ()=>createConsentManagerStore,
45
+ updateScripts: ()=>core_updateScripts,
39
46
  API_ENDPOINTS: ()=>API_ENDPOINTS
40
47
  });
41
48
  const translations_namespaceObject = require("@c15t/translations");
@@ -643,6 +650,526 @@ function configureConsentManager(options) {
643
650
  clientRegistry.set(cacheKey, client);
644
651
  return client;
645
652
  }
653
+ const gdpr_consentTypes = [
654
+ {
655
+ defaultValue: true,
656
+ description: 'These trackers are used for activities that are strictly necessary to operate or deliver the service you requested from us and, therefore, do not require you to consent.',
657
+ disabled: true,
658
+ display: true,
659
+ gdprType: 1,
660
+ name: 'necessary'
661
+ },
662
+ {
663
+ defaultValue: false,
664
+ description: 'These trackers enable basic interactions and functionalities that allow you to access selected features of our service and facilitate your communication with us.',
665
+ display: false,
666
+ gdprType: 2,
667
+ name: 'functionality'
668
+ },
669
+ {
670
+ defaultValue: false,
671
+ description: 'These trackers help us to measure traffic and analyze your behavior to improve our service.',
672
+ display: false,
673
+ gdprType: 4,
674
+ name: 'measurement'
675
+ },
676
+ {
677
+ defaultValue: false,
678
+ description: 'These trackers help us to improve the quality of your user experience and enable interactions with external content, networks, and platforms.',
679
+ display: false,
680
+ gdprType: 3,
681
+ name: 'experience'
682
+ },
683
+ {
684
+ defaultValue: false,
685
+ description: 'These trackers help us to deliver personalized ads or marketing content to you, and to measure their performance.',
686
+ display: false,
687
+ gdprType: 5,
688
+ name: 'marketing'
689
+ }
690
+ ];
691
+ const allConsentNames = gdpr_consentTypes.map((consent)=>consent.name);
692
+ function validateNonEmptyConditions(conditions, conditionType) {
693
+ if (0 === conditions.length) throw new TypeError(`${conditionType} condition cannot be empty`);
694
+ }
695
+ function evaluateCategoryCondition(category, consents) {
696
+ if (!(category in consents)) throw new Error(`Consent category "${category}" not found in consent state`);
697
+ return consents[category] || false;
698
+ }
699
+ function evaluateAndCondition(andCondition, consents) {
700
+ const andConditions = Array.isArray(andCondition) ? andCondition : [
701
+ andCondition
702
+ ];
703
+ validateNonEmptyConditions(andConditions, 'AND');
704
+ return andConditions.every((subCondition)=>evaluateConditionRecursive(subCondition, consents));
705
+ }
706
+ function evaluateOrCondition(orCondition, consents) {
707
+ const orConditions = Array.isArray(orCondition) ? orCondition : [
708
+ orCondition
709
+ ];
710
+ validateNonEmptyConditions(orConditions, 'OR');
711
+ return orConditions.some((subCondition)=>evaluateConditionRecursive(subCondition, consents));
712
+ }
713
+ function evaluateConditionRecursive(condition, consents) {
714
+ if ('string' == typeof condition) return evaluateCategoryCondition(condition, consents);
715
+ if ('object' == typeof condition && null !== condition) {
716
+ if ('and' in condition) return evaluateAndCondition(condition.and, consents);
717
+ if ('or' in condition) return evaluateOrCondition(condition.or, consents);
718
+ if ('not' in condition) return !evaluateConditionRecursive(condition.not, consents);
719
+ }
720
+ throw new TypeError(`Invalid condition structure: ${JSON.stringify(condition)}`);
721
+ }
722
+ function has_has(condition, consents) {
723
+ return evaluateConditionRecursive(condition, consents);
724
+ }
725
+ function extractConsentNamesFromCondition(condition) {
726
+ const categories = new Set();
727
+ function recurse(cond) {
728
+ if ('string' == typeof cond) return void categories.add(cond);
729
+ if ('object' == typeof cond && null !== cond) {
730
+ if ('and' in cond) {
731
+ const conditions = Array.isArray(cond.and) ? cond.and : [
732
+ cond.and
733
+ ];
734
+ conditions.forEach(recurse);
735
+ } else if ('or' in cond) {
736
+ const conditions = Array.isArray(cond.or) ? cond.or : [
737
+ cond.or
738
+ ];
739
+ conditions.forEach(recurse);
740
+ } else if ('not' in cond) recurse(cond.not);
741
+ }
742
+ }
743
+ recurse(condition);
744
+ return Array.from(categories);
745
+ }
746
+ function createDefaultConsentState() {
747
+ return {
748
+ experience: false,
749
+ functionality: false,
750
+ marketing: false,
751
+ measurement: false,
752
+ necessary: true
753
+ };
754
+ }
755
+ function determineRequiredConsent(iframe) {
756
+ const categoryAttr = iframe.getAttribute('data-category');
757
+ if (!categoryAttr) return;
758
+ if (!allConsentNames.includes(categoryAttr)) throw new Error(`Invalid category attribute "${categoryAttr}" on iframe. Must be one of: ${allConsentNames.join(', ')}`);
759
+ return categoryAttr;
760
+ }
761
+ function processIframeElement(iframe, consents) {
762
+ const dataSrc = iframe.getAttribute('data-src');
763
+ const requiredConsent = determineRequiredConsent(iframe);
764
+ if (!requiredConsent) return;
765
+ const hasConsent = has_has(requiredConsent, consents);
766
+ if (hasConsent) {
767
+ if (dataSrc && !iframe.src) {
768
+ iframe.src = dataSrc;
769
+ iframe.removeAttribute('data-src');
770
+ }
771
+ } else if (iframe.src) iframe.removeAttribute('src');
772
+ }
773
+ function createIframeBlocker(config = {}, initialConsents) {
774
+ const blockerConfig = {
775
+ disableAutomaticBlocking: false,
776
+ ...config
777
+ };
778
+ let consents = initialConsents || createDefaultConsentState();
779
+ function processIframes() {
780
+ const iframes = document.querySelectorAll('iframe');
781
+ iframes.forEach((iframe)=>{
782
+ processIframeElement(iframe, consents);
783
+ });
784
+ }
785
+ function setupMutationObserver() {
786
+ const observer = new MutationObserver((mutations)=>{
787
+ mutations.forEach((mutation)=>{
788
+ mutation.addedNodes.forEach((node)=>{
789
+ if (node.nodeType === Node.ELEMENT_NODE) {
790
+ const element = node;
791
+ if (element.tagName && 'IFRAME' === element.tagName.toUpperCase()) processIframeElement(element, consents);
792
+ const iframes = element.querySelectorAll?.('iframe');
793
+ if (iframes) iframes.forEach((iframe)=>{
794
+ processIframeElement(iframe, consents);
795
+ });
796
+ }
797
+ });
798
+ });
799
+ });
800
+ observer.observe(document.body, {
801
+ childList: true,
802
+ subtree: true
803
+ });
804
+ return observer;
805
+ }
806
+ let mutationObserver = null;
807
+ if (!blockerConfig.disableAutomaticBlocking) {
808
+ processIframes();
809
+ mutationObserver = setupMutationObserver();
810
+ }
811
+ return {
812
+ updateConsents: (newConsents)=>{
813
+ consents = {
814
+ ...consents,
815
+ ...newConsents
816
+ };
817
+ processIframes();
818
+ },
819
+ processIframes,
820
+ destroy: ()=>{
821
+ if (mutationObserver) {
822
+ mutationObserver.disconnect();
823
+ mutationObserver = null;
824
+ }
825
+ }
826
+ };
827
+ }
828
+ function getIframeConsentCategories() {
829
+ if ('undefined' == typeof document) return [];
830
+ const iframes = document.querySelectorAll('iframe[data-category]');
831
+ const categories = new Set();
832
+ iframes.forEach((iframe)=>{
833
+ const categoryAttr = iframe.getAttribute('data-category');
834
+ if (!categoryAttr) return;
835
+ const category = categoryAttr.trim();
836
+ if (allConsentNames.includes(category)) categories.add(category);
837
+ });
838
+ return Array.from(categories);
839
+ }
840
+ function processAllIframes(consents) {
841
+ if ('undefined' == typeof document) return;
842
+ const iframes = document.querySelectorAll('iframe');
843
+ iframes.forEach((iframe)=>{
844
+ processIframeElement(iframe, consents);
845
+ });
846
+ }
847
+ function setupIframeObserver(getConsents, onCategoriesDiscovered) {
848
+ const observer = new MutationObserver((mutations)=>{
849
+ const currentConsents = getConsents();
850
+ let hasNewCategories = false;
851
+ mutations.forEach((mutation)=>{
852
+ mutation.addedNodes.forEach((node)=>{
853
+ if (node.nodeType === Node.ELEMENT_NODE) {
854
+ const element = node;
855
+ if (element.tagName && 'IFRAME' === element.tagName.toUpperCase()) {
856
+ processIframeElement(element, currentConsents);
857
+ if (element.hasAttribute('data-category')) hasNewCategories = true;
858
+ }
859
+ const iframes = element.querySelectorAll?.('iframe');
860
+ if (iframes && iframes.length > 0) iframes.forEach((iframe)=>{
861
+ processIframeElement(iframe, currentConsents);
862
+ if (iframe.hasAttribute('data-category')) hasNewCategories = true;
863
+ });
864
+ }
865
+ });
866
+ });
867
+ if (hasNewCategories && onCategoriesDiscovered) {
868
+ const categories = getIframeConsentCategories();
869
+ if (categories.length > 0) onCategoriesDiscovered(categories);
870
+ }
871
+ });
872
+ observer.observe(document.body, {
873
+ childList: true,
874
+ subtree: true
875
+ });
876
+ return observer;
877
+ }
878
+ function generateRandomScriptId() {
879
+ if ('undefined' != typeof crypto && crypto.randomUUID) return crypto.randomUUID().replace(/-/g, '').substring(0, 8);
880
+ if ('undefined' != typeof crypto && crypto.getRandomValues) {
881
+ const array = new Uint8Array(4);
882
+ crypto.getRandomValues(array);
883
+ return Array.from(array, (byte)=>byte.toString(36)).join('').padEnd(8, '0').substring(0, 8);
884
+ }
885
+ const randomStr = Math.random().toString(36).substring(2);
886
+ return randomStr.padEnd(8, '0').substring(0, 8);
887
+ }
888
+ function getScriptElementId(scriptId, anonymizeId, scriptIdMap) {
889
+ if (anonymizeId) {
890
+ if (scriptIdMap[scriptId]) return scriptIdMap[scriptId];
891
+ scriptIdMap[scriptId] = generateRandomScriptId();
892
+ return scriptIdMap[scriptId];
893
+ }
894
+ return `c15t-script-${scriptId}`;
895
+ }
896
+ const loadedScripts = new Map();
897
+ function hasLoadedScript(src) {
898
+ return loadedScripts.has(src);
899
+ }
900
+ function getLoadedScript(src) {
901
+ return loadedScripts.get(src);
902
+ }
903
+ function setLoadedScript(src, element) {
904
+ loadedScripts.set(src, element);
905
+ }
906
+ function deleteLoadedScript(src) {
907
+ loadedScripts.delete(src);
908
+ }
909
+ function utils_getLoadedScriptsSnapshot() {
910
+ return loadedScripts;
911
+ }
912
+ function loadScripts(scripts, consents, scriptIdMap = {}) {
913
+ const loadedScriptIds = [];
914
+ scripts.forEach((script)=>{
915
+ if (!script.alwaysLoad && !has_has(script.category, consents)) return;
916
+ if (hasLoadedScript(script.id)) return void script.onConsentChange?.({
917
+ id: script.id,
918
+ elementId: getScriptElementId(script.id, false !== script.anonymizeId, scriptIdMap),
919
+ hasConsent: has_has(script.category, consents),
920
+ consents
921
+ });
922
+ if (script.src && script.textContent) throw new Error(`Script '${script.id}' cannot have both 'src' and 'textContent'. Choose one.`);
923
+ if (!script.src && !script.textContent && !script.callbackOnly) throw new Error(`Script '${script.id}' must have either 'src', 'textContent', or 'callbackOnly' set to true.`);
924
+ if (true === script.callbackOnly) {
925
+ const shouldAnonymize = false !== script.anonymizeId;
926
+ const elementId = getScriptElementId(script.id, shouldAnonymize, scriptIdMap);
927
+ const callbackInfo = {
928
+ id: script.id,
929
+ elementId,
930
+ consents,
931
+ hasConsent: has_has(script.category, consents)
932
+ };
933
+ if (script.onBeforeLoad) script.onBeforeLoad(callbackInfo);
934
+ if (script.onLoad) script.onLoad(callbackInfo);
935
+ setLoadedScript(script.id, null);
936
+ loadedScriptIds.push(script.id);
937
+ return;
938
+ }
939
+ const shouldAnonymize = false !== script.anonymizeId;
940
+ const elementId = getScriptElementId(script.id, shouldAnonymize, scriptIdMap);
941
+ if (true === script.persistAfterConsentRevoked) {
942
+ const existingElement = document.getElementById(elementId);
943
+ if (existingElement) {
944
+ const callbackInfo = {
945
+ id: script.id,
946
+ hasConsent: has_has(script.category, consents),
947
+ elementId,
948
+ consents,
949
+ element: existingElement
950
+ };
951
+ script.onConsentChange?.(callbackInfo);
952
+ script.onLoad?.(callbackInfo);
953
+ setLoadedScript(script.id, existingElement);
954
+ loadedScriptIds.push(script.id);
955
+ return;
956
+ }
957
+ }
958
+ const scriptElement = document.createElement("script");
959
+ scriptElement.id = elementId;
960
+ if (script.src) scriptElement.src = script.src;
961
+ else if (script.textContent) scriptElement.textContent = script.textContent;
962
+ if (script.fetchPriority) scriptElement.fetchPriority = script.fetchPriority;
963
+ if (script.async) scriptElement.async = true;
964
+ if (script.defer) scriptElement.defer = true;
965
+ if (script.nonce) scriptElement.nonce = script.nonce;
966
+ if (script.attributes) Object.entries(script.attributes).forEach(([key, value])=>{
967
+ scriptElement.setAttribute(key, value);
968
+ });
969
+ const callbackInfo = {
970
+ id: script.id,
971
+ hasConsent: has_has(script.category, consents),
972
+ elementId,
973
+ consents,
974
+ element: scriptElement
975
+ };
976
+ if (script.onLoad) if (script.textContent) setTimeout(()=>{
977
+ script.onLoad?.({
978
+ ...callbackInfo
979
+ });
980
+ }, 0);
981
+ else scriptElement.addEventListener('load', ()=>{
982
+ script.onLoad?.({
983
+ ...callbackInfo
984
+ });
985
+ });
986
+ if (script.onError) script.textContent || scriptElement.addEventListener('error', ()=>{
987
+ script.onError?.({
988
+ ...callbackInfo,
989
+ error: new Error(`Failed to load script: ${script.src}`)
990
+ });
991
+ });
992
+ if (script.onBeforeLoad) script.onBeforeLoad(callbackInfo);
993
+ document.head.appendChild(scriptElement);
994
+ setLoadedScript(script.id, scriptElement);
995
+ loadedScriptIds.push(script.id);
996
+ });
997
+ return loadedScriptIds;
998
+ }
999
+ function unloadScripts(scripts, consents, scriptIdMap = {}) {
1000
+ const unloadedScriptIds = [];
1001
+ scripts.forEach((script)=>{
1002
+ if (!hasLoadedScript(script.id)) return;
1003
+ if (script.alwaysLoad) return;
1004
+ if (!has_has(script.category, consents)) {
1005
+ const scriptElement = getLoadedScript(script.id);
1006
+ const elementId = scriptIdMap[script.id] || `c15t-script-${script.id}`;
1007
+ if (true === script.callbackOnly || null === scriptElement) {
1008
+ const callbackInfo = {
1009
+ id: script.id,
1010
+ elementId,
1011
+ consents,
1012
+ hasConsent: has_has(script.category, consents)
1013
+ };
1014
+ if (script.onDelete) script.onDelete(callbackInfo);
1015
+ deleteLoadedScript(script.id);
1016
+ unloadedScriptIds.push(script.id);
1017
+ } else if (scriptElement) {
1018
+ const callbackInfo = {
1019
+ id: script.id,
1020
+ elementId,
1021
+ consents,
1022
+ hasConsent: has_has(script.category, consents),
1023
+ element: scriptElement
1024
+ };
1025
+ if (script.onDelete) script.onDelete(callbackInfo);
1026
+ if (script.persistAfterConsentRevoked) {
1027
+ deleteLoadedScript(script.id);
1028
+ unloadedScriptIds.push(script.id);
1029
+ } else {
1030
+ scriptElement.remove();
1031
+ deleteLoadedScript(script.id);
1032
+ unloadedScriptIds.push(script.id);
1033
+ }
1034
+ }
1035
+ }
1036
+ });
1037
+ return unloadedScriptIds;
1038
+ }
1039
+ function core_updateScripts(scripts, consents, scriptIdMap = {}) {
1040
+ const unloaded = unloadScripts(scripts, consents, scriptIdMap);
1041
+ const loaded = loadScripts(scripts, consents, scriptIdMap);
1042
+ return {
1043
+ loaded,
1044
+ unloaded
1045
+ };
1046
+ }
1047
+ function isScriptLoaded(scriptId) {
1048
+ return hasLoadedScript(scriptId);
1049
+ }
1050
+ function getLoadedScriptIds() {
1051
+ return Array.from(utils_getLoadedScriptsSnapshot().keys());
1052
+ }
1053
+ function reloadScript(scriptId, scripts, consents, scriptIdMap = {}) {
1054
+ const script = scripts.find((s)=>s.id === scriptId);
1055
+ if (!script) return false;
1056
+ if (hasLoadedScript(scriptId)) {
1057
+ const scriptElement = getLoadedScript(scriptId);
1058
+ const elementId = scriptIdMap[scriptId] || `c15t-script-${scriptId}`;
1059
+ if (true === script.callbackOnly || null === scriptElement) {
1060
+ const callbackInfo = {
1061
+ id: scriptId,
1062
+ elementId,
1063
+ consents,
1064
+ hasConsent: has_has(script.category, consents)
1065
+ };
1066
+ if (script.onDelete) script.onDelete(callbackInfo);
1067
+ deleteLoadedScript(scriptId);
1068
+ } else if (scriptElement) {
1069
+ const callbackInfo = {
1070
+ id: scriptId,
1071
+ elementId,
1072
+ consents,
1073
+ hasConsent: has_has(script.category, consents),
1074
+ element: scriptElement
1075
+ };
1076
+ if (script.onDelete) script.onDelete(callbackInfo);
1077
+ if (!script.persistAfterConsentRevoked) scriptElement.remove();
1078
+ deleteLoadedScript(scriptId);
1079
+ }
1080
+ }
1081
+ if (!script.alwaysLoad && !has_has(script.category, consents)) return false;
1082
+ loadScripts([
1083
+ script
1084
+ ], consents, scriptIdMap);
1085
+ return true;
1086
+ }
1087
+ function createScriptManager(getState, setState, trackingBlocker) {
1088
+ const updateScriptsFn = ()=>{
1089
+ const { scripts, consents, scriptIdMap } = getState();
1090
+ const result = core_updateScripts(scripts, consents, scriptIdMap);
1091
+ const newLoadedScripts = {
1092
+ ...getState().loadedScripts
1093
+ };
1094
+ result.loaded.forEach((id)=>{
1095
+ newLoadedScripts[id] = true;
1096
+ });
1097
+ result.unloaded.forEach((id)=>{
1098
+ newLoadedScripts[id] = false;
1099
+ });
1100
+ setState({
1101
+ loadedScripts: newLoadedScripts
1102
+ });
1103
+ return result;
1104
+ };
1105
+ return {
1106
+ updateScripts: ()=>updateScriptsFn(),
1107
+ setScripts: (scripts)=>{
1108
+ if (trackingBlocker) trackingBlocker.destroy();
1109
+ const state = getState();
1110
+ const newScriptIdMap = {
1111
+ ...state.scriptIdMap
1112
+ };
1113
+ scripts.forEach((script)=>{
1114
+ if (false !== script.anonymizeId) newScriptIdMap[script.id] = generateRandomScriptId();
1115
+ });
1116
+ const newCategories = scripts.flatMap((script)=>extractConsentNamesFromCondition(script.category));
1117
+ const allCategories = [
1118
+ ...new Set([
1119
+ ...state.gdprTypes,
1120
+ ...newCategories
1121
+ ])
1122
+ ];
1123
+ setState({
1124
+ scripts: [
1125
+ ...state.scripts,
1126
+ ...scripts
1127
+ ],
1128
+ scriptIdMap: newScriptIdMap,
1129
+ gdprTypes: allCategories
1130
+ });
1131
+ updateScriptsFn();
1132
+ },
1133
+ removeScript: (scriptId)=>{
1134
+ const state = getState();
1135
+ const script = state.scripts.find((s)=>s.id === scriptId);
1136
+ if (hasLoadedScript(scriptId)) {
1137
+ const scriptElement = getLoadedScript(scriptId);
1138
+ if (scriptElement) {
1139
+ const elementId = state.scriptIdMap[scriptId] || `c15t-script-${scriptId}`;
1140
+ const callbackInfo = {
1141
+ id: scriptId,
1142
+ elementId,
1143
+ consents: state.consents,
1144
+ element: scriptElement,
1145
+ hasConsent: has_has(script?.category ?? 'necessary', state.consents)
1146
+ };
1147
+ if (script?.onDelete) script.onDelete(callbackInfo);
1148
+ scriptElement.remove();
1149
+ deleteLoadedScript(scriptId);
1150
+ }
1151
+ }
1152
+ const newScriptIdMap = {
1153
+ ...state.scriptIdMap
1154
+ };
1155
+ delete newScriptIdMap[scriptId];
1156
+ setState({
1157
+ scripts: state.scripts.filter((script)=>script.id !== scriptId),
1158
+ loadedScripts: {
1159
+ ...state.loadedScripts,
1160
+ [scriptId]: false
1161
+ },
1162
+ scriptIdMap: newScriptIdMap
1163
+ });
1164
+ },
1165
+ reloadScript: (scriptId)=>{
1166
+ const state = getState();
1167
+ return reloadScript(scriptId, state.scripts, state.consents, state.scriptIdMap);
1168
+ },
1169
+ isScriptLoaded: (scriptId)=>isScriptLoaded(scriptId),
1170
+ getLoadedScriptIds: ()=>getLoadedScriptIds()
1171
+ };
1172
+ }
646
1173
  const DEFAULT_DOMAIN_CONSENT_MAP = {
647
1174
  'www.google-analytics.com': 'measurement',
648
1175
  'analytics.google.com': 'measurement',
@@ -788,7 +1315,7 @@ const DEFAULT_DOMAIN_CONSENT_MAP = {
788
1315
  'app.contentsquare.com': 'experience'
789
1316
  };
790
1317
  const tracking_domains = DEFAULT_DOMAIN_CONSENT_MAP;
791
- function createDefaultConsentState() {
1318
+ function tracking_blocker_createDefaultConsentState() {
792
1319
  return {
793
1320
  experience: false,
794
1321
  functionality: false,
@@ -808,7 +1335,7 @@ function createTrackingBlocker(config = {}, initialConsents) {
808
1335
  ...config.domainConsentMap
809
1336
  }
810
1337
  };
811
- let consents = initialConsents || createDefaultConsentState();
1338
+ let consents = initialConsents || tracking_blocker_createDefaultConsentState();
812
1339
  const originalFetch = window.fetch;
813
1340
  const originalXHR = window.XMLHttpRequest;
814
1341
  function normalizeDomain(domain) {
@@ -889,7 +1416,7 @@ function hasConsentFor(consentType, consents, honorDoNotTrack) {
889
1416
  const effectiveConsents = getEffectiveConsents(consents, honorDoNotTrack);
890
1417
  return effectiveConsents[consentType] || false;
891
1418
  }
892
- function consent_utils_hasConsented(consentInfo) {
1419
+ function hasConsented(consentInfo) {
893
1420
  return null !== consentInfo;
894
1421
  }
895
1422
  function checkLocalStorageAccess(set) {
@@ -908,16 +1435,17 @@ function checkLocalStorageAccess(set) {
908
1435
  }
909
1436
  return false;
910
1437
  }
911
- function updateStore(data, { set, get, initialTranslationConfig }, hasLocalStorageAccess) {
1438
+ function updateStore(data, { set, get, initialTranslationConfig, trackingBlocker }, hasLocalStorageAccess) {
912
1439
  const { consentInfo, ignoreGeoLocation, callbacks, setDetectedCountry } = get();
913
1440
  const { translations, location, showConsentBanner } = data;
1441
+ const shouldAutoGrantConsents = data.jurisdiction?.code === 'NONE' && !data.showConsentBanner;
914
1442
  const updatedStore = {
915
1443
  isLoadingConsentInfo: false,
916
1444
  branding: data.branding ?? 'c15t',
917
1445
  ...null === consentInfo ? {
918
1446
  showPopup: showConsentBanner && hasLocalStorageAccess || ignoreGeoLocation
919
1447
  } : {},
920
- ...data.jurisdiction?.code === 'NONE' && !data.showConsentBanner && {
1448
+ ...shouldAutoGrantConsents && {
921
1449
  consents: {
922
1450
  necessary: true,
923
1451
  functionality: true,
@@ -934,7 +1462,8 @@ function updateStore(data, { set, get, initialTranslationConfig }, hasLocalStora
934
1462
  },
935
1463
  jurisdictionInfo: data.jurisdiction
936
1464
  };
937
- if (translations?.language && translations?.translations) {
1465
+ translations?.language && translations?.translations;
1466
+ {
938
1467
  const translationConfig = (0, translations_namespaceObject.prepareTranslationConfig)({
939
1468
  translations: {
940
1469
  [translations.language]: translations.translations
@@ -948,6 +1477,15 @@ function updateStore(data, { set, get, initialTranslationConfig }, hasLocalStora
948
1477
  updatedStore.hasFetchedBanner = true;
949
1478
  updatedStore.lastBannerFetchData = data;
950
1479
  set(updatedStore);
1480
+ if (shouldAutoGrantConsents) callbacks?.onConsentSet?.({
1481
+ preferences: {
1482
+ necessary: true,
1483
+ functionality: true,
1484
+ experience: true,
1485
+ marketing: true,
1486
+ measurement: true
1487
+ }
1488
+ });
951
1489
  callbacks?.onBannerFetched?.({
952
1490
  showConsentBanner: data.showConsentBanner,
953
1491
  jurisdiction: data.jurisdiction,
@@ -957,24 +1495,26 @@ function updateStore(data, { set, get, initialTranslationConfig }, hasLocalStora
957
1495
  translations: translations.translations
958
1496
  }
959
1497
  });
1498
+ trackingBlocker?.updateConsents(get().consents);
1499
+ get().updateScripts();
960
1500
  }
961
1501
  async function fetchConsentBannerInfo(config) {
962
1502
  const { get, set, manager, initialData } = config;
963
- const { hasConsented, callbacks } = get();
964
- if ('undefined' == typeof window || hasConsented()) return void set({
965
- isLoadingConsentInfo: false
966
- });
1503
+ const { callbacks } = get();
1504
+ if ('undefined' == typeof window) return;
967
1505
  const hasLocalStorageAccess = checkLocalStorageAccess(set);
968
1506
  if (!hasLocalStorageAccess) return;
969
1507
  set({
970
1508
  isLoadingConsentInfo: true
971
1509
  });
972
- if (initialData) {
1510
+ if (initialData) try {
973
1511
  const showConsentBanner = await initialData;
974
1512
  if (showConsentBanner) {
975
1513
  updateStore(showConsentBanner, config, true);
976
1514
  return showConsentBanner;
977
1515
  }
1516
+ } catch (error) {
1517
+ throw error;
978
1518
  }
979
1519
  try {
980
1520
  const { data, error } = await manager.showConsentBanner({
@@ -1078,78 +1618,49 @@ function updateGTMConsent(consentState) {
1078
1618
  if (!window.gtag) return;
1079
1619
  window.gtag('consent', 'update', mapConsentStateToGTM(consentState));
1080
1620
  }
1081
- function validateNonEmptyConditions(conditions, conditionType) {
1082
- if (0 === conditions.length) throw new TypeError(`${conditionType} condition cannot be empty`);
1083
- }
1084
- function evaluateCategoryCondition(category, consents) {
1085
- if (!(category in consents)) throw new Error(`Consent category "${category}" not found in consent state`);
1086
- return consents[category] || false;
1087
- }
1088
- function evaluateAndCondition(andCondition, consents) {
1089
- const andConditions = Array.isArray(andCondition) ? andCondition : [
1090
- andCondition
1091
- ];
1092
- validateNonEmptyConditions(andConditions, 'AND');
1093
- return andConditions.every((subCondition)=>evaluateConditionRecursive(subCondition, consents));
1094
- }
1095
- function evaluateOrCondition(orCondition, consents) {
1096
- const orConditions = Array.isArray(orCondition) ? orCondition : [
1097
- orCondition
1098
- ];
1099
- validateNonEmptyConditions(orConditions, 'OR');
1100
- return orConditions.some((subCondition)=>evaluateConditionRecursive(subCondition, consents));
1101
- }
1102
- function evaluateConditionRecursive(condition, consents) {
1103
- if ('string' == typeof condition) return evaluateCategoryCondition(condition, consents);
1104
- if ('object' == typeof condition && null !== condition) {
1105
- if ('and' in condition) return evaluateAndCondition(condition.and, consents);
1106
- if ('or' in condition) return evaluateOrCondition(condition.or, consents);
1107
- if ('not' in condition) return !evaluateConditionRecursive(condition.not, consents);
1108
- }
1109
- throw new TypeError(`Invalid condition structure: ${JSON.stringify(condition)}`);
1110
- }
1111
- function has(condition, consents) {
1112
- return evaluateConditionRecursive(condition, consents);
1621
+ function createIframeManager(get, _set) {
1622
+ let observer = null;
1623
+ let isInitialized = false;
1624
+ return {
1625
+ initializeIframeBlocker: ()=>{
1626
+ if (isInitialized) return;
1627
+ if ('undefined' == typeof document) return;
1628
+ const state = get();
1629
+ if (state.iframeBlockerConfig?.disableAutomaticBlocking) return;
1630
+ const discoverAndRegisterCategories = ()=>{
1631
+ const iframeCategories = getIframeConsentCategories();
1632
+ if (iframeCategories.length > 0) get().updateConsentCategories(iframeCategories);
1633
+ };
1634
+ if ('loading' === document.readyState) document.addEventListener('DOMContentLoaded', discoverAndRegisterCategories);
1635
+ else discoverAndRegisterCategories();
1636
+ setTimeout(discoverAndRegisterCategories, 100);
1637
+ processAllIframes(state.consents);
1638
+ observer = setupIframeObserver(()=>get().consents, (categories)=>get().updateConsentCategories(categories));
1639
+ isInitialized = true;
1640
+ },
1641
+ updateIframeConsents: ()=>{
1642
+ if (!isInitialized) return;
1643
+ if ('undefined' == typeof document) return;
1644
+ const state = get();
1645
+ const { consents, iframeBlockerConfig } = state;
1646
+ if (iframeBlockerConfig?.disableAutomaticBlocking) return;
1647
+ processAllIframes(consents);
1648
+ },
1649
+ destroyIframeBlocker: ()=>{
1650
+ if (!isInitialized) return;
1651
+ if ('undefined' == typeof document) return;
1652
+ const state = get();
1653
+ const { iframeBlockerConfig } = state;
1654
+ if (iframeBlockerConfig?.disableAutomaticBlocking) return;
1655
+ if (observer) {
1656
+ observer.disconnect();
1657
+ observer = null;
1658
+ }
1659
+ isInitialized = false;
1660
+ }
1661
+ };
1113
1662
  }
1114
- const gdpr_consentTypes = [
1115
- {
1116
- defaultValue: true,
1117
- description: 'These trackers are used for activities that are strictly necessary to operate or deliver the service you requested from us and, therefore, do not require you to consent.',
1118
- disabled: true,
1119
- display: true,
1120
- gdprType: 1,
1121
- name: 'necessary'
1122
- },
1123
- {
1124
- defaultValue: false,
1125
- description: 'These trackers enable basic interactions and functionalities that allow you to access selected features of our service and facilitate your communication with us.',
1126
- display: false,
1127
- gdprType: 2,
1128
- name: 'functionality'
1129
- },
1130
- {
1131
- defaultValue: false,
1132
- description: 'These trackers help us to measure traffic and analyze your behavior to improve our service.',
1133
- display: false,
1134
- gdprType: 4,
1135
- name: 'measurement'
1136
- },
1137
- {
1138
- defaultValue: false,
1139
- description: 'These trackers help us to improve the quality of your user experience and enable interactions with external content, networks, and platforms.',
1140
- display: false,
1141
- gdprType: 3,
1142
- name: 'experience'
1143
- },
1144
- {
1145
- defaultValue: false,
1146
- description: 'These trackers help us to deliver personalized ads or marketing content to you, and to measure their performance.',
1147
- display: false,
1148
- gdprType: 5,
1149
- name: 'marketing'
1150
- }
1151
- ];
1152
- const version = '1.6.0';
1663
+ const version = '1.7.0';
1153
1664
  const STORAGE_KEY = 'privacy-consent-storage';
1154
1665
  const initialState = {
1155
1666
  config: {
@@ -1206,6 +1717,9 @@ const initialState = {
1206
1717
  translationConfig: defaultTranslationConfig,
1207
1718
  includeNonDisplayedConsents: false,
1208
1719
  consentTypes: gdpr_consentTypes,
1720
+ iframeBlockerConfig: {
1721
+ disableAutomaticBlocking: false
1722
+ },
1209
1723
  ignoreGeoLocation: false,
1210
1724
  privacySettings: {
1211
1725
  honorDoNotTrack: true
@@ -1223,13 +1737,17 @@ const initialState = {
1223
1737
  setLocationInfo: ()=>{},
1224
1738
  getDisplayedConsents: ()=>[],
1225
1739
  hasConsented: ()=>false,
1226
- setTranslationConfig: ()=>{}
1740
+ setTranslationConfig: ()=>{},
1741
+ scripts: [],
1742
+ loadedScripts: {},
1743
+ scriptIdMap: {}
1227
1744
  };
1228
1745
  async function saveConsents({ manager, type, get, set, trackingBlocker }) {
1229
- const { callbacks, selectedConsents, consents, consentTypes } = get();
1746
+ const { callbacks, selectedConsents, consents, consentTypes, updateScripts, updateIframeConsents, gdprTypes } = get();
1230
1747
  const newConsents = selectedConsents ?? consents ?? {};
1231
- if ('all' === type) for (const consent of consentTypes)newConsents[consent.name] = true;
1232
- else if ('necessary' === type) for (const consent of consentTypes)newConsents[consent.name] = 'necessary' === consent.name;
1748
+ if ('all' === type) {
1749
+ for (const consent of consentTypes)if (gdprTypes.includes(consent.name)) newConsents[consent.name] = true;
1750
+ } else if ('necessary' === type) for (const consent of consentTypes)newConsents[consent.name] = 'necessary' === consent.name;
1233
1751
  const consentInfo = {
1234
1752
  time: Date.now(),
1235
1753
  type: type
@@ -1242,7 +1760,9 @@ async function saveConsents({ manager, type, get, set, trackingBlocker }) {
1242
1760
  });
1243
1761
  await new Promise((resolve)=>setTimeout(resolve, 0));
1244
1762
  trackingBlocker?.updateConsents(newConsents);
1763
+ updateIframeConsents();
1245
1764
  updateGTMConsent(newConsents);
1765
+ updateScripts();
1246
1766
  try {
1247
1767
  localStorage.setItem(STORAGE_KEY, JSON.stringify({
1248
1768
  consents: newConsents,
@@ -1287,13 +1807,17 @@ const getStoredConsent = ()=>{
1287
1807
  const createConsentManagerStore = (manager, options = {})=>{
1288
1808
  const { namespace = 'c15tStore', trackingBlockerConfig, isConsentDomain = false, translationConfig } = options;
1289
1809
  const storedConsent = getStoredConsent();
1290
- const trackingBlocker = 'undefined' != typeof window ? createTrackingBlocker(trackingBlockerConfig || {}, storedConsent?.consents || initialState.consents) : null;
1810
+ const shouldDisableTrackingBlocker = options.scripts && options.scripts.length > 0;
1811
+ const trackingBlocker = 'undefined' == typeof window || shouldDisableTrackingBlocker ? null : createTrackingBlocker(trackingBlockerConfig || {}, storedConsent?.consents || initialState.consents);
1291
1812
  const store = (0, vanilla_namespaceObject.createStore)((set, get)=>({
1292
1813
  ...initialState,
1814
+ gdprTypes: options.initialGdprTypes ?? initialState.gdprTypes,
1293
1815
  ignoreGeoLocation: options.ignoreGeoLocation ?? false,
1294
1816
  config: options.config ?? initialState.config,
1817
+ iframeBlockerConfig: options.iframeBlockerConfig ?? initialState.iframeBlockerConfig,
1295
1818
  isConsentDomain,
1296
1819
  callbacks: options.callbacks ?? initialState.callbacks,
1820
+ scripts: options.scripts ?? initialState.scripts,
1297
1821
  translationConfig: translationConfig || initialState.translationConfig,
1298
1822
  ...storedConsent ? {
1299
1823
  consents: storedConsent.consents,
@@ -1392,6 +1916,9 @@ const createConsentManagerStore = (manager, options = {})=>{
1392
1916
  [name]: callback
1393
1917
  }
1394
1918
  }));
1919
+ if ('onConsentSet' === name && callback && 'function' == typeof callback) callback?.({
1920
+ preferences: currentState.consents
1921
+ });
1395
1922
  if ('onBannerFetched' === name && currentState.hasFetchedBanner && currentState.lastBannerFetchData && callback && 'function' == typeof callback) {
1396
1923
  const { lastBannerFetchData } = currentState;
1397
1924
  callback?.({
@@ -1416,7 +1943,8 @@ const createConsentManagerStore = (manager, options = {})=>{
1416
1943
  initialData: options._initialData,
1417
1944
  initialTranslationConfig: options.initialTranslationConfig,
1418
1945
  get,
1419
- set
1946
+ set,
1947
+ trackingBlocker
1420
1948
  }),
1421
1949
  getDisplayedConsents: ()=>{
1422
1950
  const { gdprTypes, consentTypes } = get();
@@ -1424,7 +1952,7 @@ const createConsentManagerStore = (manager, options = {})=>{
1424
1952
  },
1425
1953
  hasConsented: ()=>{
1426
1954
  const { consentInfo } = get();
1427
- return consent_utils_hasConsented(consentInfo);
1955
+ return hasConsented(consentInfo);
1428
1956
  },
1429
1957
  getEffectiveConsents: ()=>{
1430
1958
  const { consents, privacySettings } = get();
@@ -1436,14 +1964,29 @@ const createConsentManagerStore = (manager, options = {})=>{
1436
1964
  },
1437
1965
  has: (condition)=>{
1438
1966
  const { consents } = get();
1439
- return has(condition, consents);
1967
+ return has_has(condition, consents);
1440
1968
  },
1441
1969
  setTranslationConfig: (config)=>{
1442
1970
  set({
1443
1971
  translationConfig: config
1444
1972
  });
1445
- }
1973
+ },
1974
+ updateConsentCategories: (newCategories)=>{
1975
+ const allCategories = [
1976
+ ...new Set([
1977
+ ...get().gdprTypes,
1978
+ ...newCategories
1979
+ ])
1980
+ ];
1981
+ set({
1982
+ gdprTypes: allCategories
1983
+ });
1984
+ },
1985
+ ...createScriptManager(get, set, trackingBlocker),
1986
+ ...createIframeManager(get, set)
1446
1987
  }));
1988
+ store.getState().initializeIframeBlocker();
1989
+ if (options.scripts && options.scripts.length > 0) store.getState().updateConsentCategories(options.scripts.flatMap((script)=>extractConsentNamesFromCondition(script.category)));
1447
1990
  if ('undefined' != typeof window) {
1448
1991
  window[namespace] = store;
1449
1992
  if (options.unstable_googleTagManager) try {
@@ -1454,7 +1997,10 @@ const createConsentManagerStore = (manager, options = {})=>{
1454
1997
  } catch (e) {
1455
1998
  console.error('Failed to setup Google Tag Manager:', e);
1456
1999
  }
1457
- if (!getStoredConsent()) store.getState().fetchConsentBannerInfo();
2000
+ store.getState().callbacks.onConsentSet?.({
2001
+ preferences: store.getState().consents
2002
+ });
2003
+ store.getState().fetchConsentBannerInfo();
1458
2004
  }
1459
2005
  return store;
1460
2006
  };
@@ -1462,29 +2008,43 @@ exports.API_ENDPOINTS = __webpack_exports__.API_ENDPOINTS;
1462
2008
  exports.C15tClient = __webpack_exports__.C15tClient;
1463
2009
  exports.CustomClient = __webpack_exports__.CustomClient;
1464
2010
  exports.OfflineClient = __webpack_exports__.OfflineClient;
2011
+ exports.allConsentNames = __webpack_exports__.allConsentNames;
1465
2012
  exports.configureConsentManager = __webpack_exports__.configureConsentManager;
1466
2013
  exports.consentTypes = __webpack_exports__.consentTypes;
1467
2014
  exports.createConsentManagerStore = __webpack_exports__.createConsentManagerStore;
2015
+ exports.createIframeBlocker = __webpack_exports__.createIframeBlocker;
1468
2016
  exports.createTrackingBlocker = __webpack_exports__.createTrackingBlocker;
1469
2017
  exports.deepMergeTranslations = __webpack_exports__.deepMergeTranslations;
1470
2018
  exports.defaultTranslationConfig = __webpack_exports__.defaultTranslationConfig;
1471
2019
  exports.detectBrowserLanguage = __webpack_exports__.detectBrowserLanguage;
2020
+ exports.getLoadedScriptIds = __webpack_exports__.getLoadedScriptIds;
2021
+ exports.isScriptLoaded = __webpack_exports__.isScriptLoaded;
2022
+ exports.loadScripts = __webpack_exports__.loadScripts;
1472
2023
  exports.mergeTranslationConfigs = __webpack_exports__.mergeTranslationConfigs;
1473
2024
  exports.prepareTranslationConfig = __webpack_exports__.prepareTranslationConfig;
2025
+ exports.unloadScripts = __webpack_exports__.unloadScripts;
2026
+ exports.updateScripts = __webpack_exports__.updateScripts;
1474
2027
  for(var __webpack_i__ in __webpack_exports__)if (-1 === [
1475
2028
  "API_ENDPOINTS",
1476
2029
  "C15tClient",
1477
2030
  "CustomClient",
1478
2031
  "OfflineClient",
2032
+ "allConsentNames",
1479
2033
  "configureConsentManager",
1480
2034
  "consentTypes",
1481
2035
  "createConsentManagerStore",
2036
+ "createIframeBlocker",
1482
2037
  "createTrackingBlocker",
1483
2038
  "deepMergeTranslations",
1484
2039
  "defaultTranslationConfig",
1485
2040
  "detectBrowserLanguage",
2041
+ "getLoadedScriptIds",
2042
+ "isScriptLoaded",
2043
+ "loadScripts",
1486
2044
  "mergeTranslationConfigs",
1487
- "prepareTranslationConfig"
2045
+ "prepareTranslationConfig",
2046
+ "unloadScripts",
2047
+ "updateScripts"
1488
2048
  ].indexOf(__webpack_i__)) exports[__webpack_i__] = __webpack_exports__[__webpack_i__];
1489
2049
  Object.defineProperty(exports, '__esModule', {
1490
2050
  value: true