@kameleoon/javascript-sdk-core 5.16.1 → 5.17.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 CHANGED
@@ -1,5 +1,14 @@
1
1
  # Change Log
2
2
 
3
+ ## 5.17.0 (2025-12-17)
4
+
5
+ ### Features
6
+
7
+ - Fixed an issue where **[Kameleoon Data](https://developers.kameleoon.com/feature-management-and-experimentation/web-sdks/js-sdk/#data-types)** was retained beyond the configured **[`targetingDataCleanupInterval`](https://developers.kameleoon.com/feature-management-and-experimentation/web-sdks/js-sdk/#configuration-parameters)** if the **[Data API](https://developers.kameleoon.com/apis/data-api-rest/all-endpoints/post-visit-events/)** encountered internal errors. Expired data is now reliably removed in accordance with the cleanup interval.
8
+ - Updated evaluation and tracking logic to comply with GDPR requirements when consent is not given:
9
+ - If behavior is **partially blocked**, the default variation will be returned.
10
+ - If behavior is **completely blocked**, an exception will be thrown.
11
+
3
12
  ## 5.16.1 (2025-10-23)
4
13
 
5
14
  ### Patch Changes
@@ -8,7 +17,7 @@
8
17
 
9
18
  ## 5.16.0 (2025-10-22)
10
19
 
11
- ### Minor Changes
20
+ ### Features
12
21
 
13
22
  - Introduced a new [`getDataFile`](getDataFile) method. This method returns the current SDK configuration (also known as the **data file**) used for evaluation and targeting. It is **not** intended for production use to fetch variations for every feature flag in the returned list, as it is not optimized for performance. For that purpose, use [`getVariations`](getVariations) instead. `getDataFile` is mainly useful for debugging or QA, for example to let internal users manually select a variant for a specific feature flag in production.
14
23
 
@@ -1,7 +1,7 @@
1
1
  import { Result } from 'ts-res';
2
2
  import { KameleoonError } from '../kameleoonError/kameleoonError';
3
3
  import { SegmentType } from '../targeting';
4
- import { ClientConfigurationParametersType, ConfigurationType, ExperimentInfoType, ExperimentType, FeatureFlagType, MappedRuleType } from './types';
4
+ import { ClientConfigurationParametersType, ConfigurationType, ConsentBlockingBehaviour, ExperimentInfoType, ExperimentType, FeatureFlagType, MappedRuleType } from './types';
5
5
  import { MEGroup } from '../clientConfiguration/meGroup';
6
6
  interface IClientConfiguration {
7
7
  initialize: () => Promise<Result<void, KameleoonError>>;
@@ -38,6 +38,7 @@ export declare class ClientConfiguration implements IClientConfiguration {
38
38
  private externalPackageInfo;
39
39
  private usedDefaultDataFile;
40
40
  private defaultDataFile?;
41
+ private blockingBehaviourMode;
41
42
  private readonly CACHE_REVALIDATE_PERIOD;
42
43
  constructor({ updateInterval, urlProvider, storage, requester, dataManager, eventSource, externalVisitorCodeManager, eventManager, externalPackageInfo, defaultDataFile, }: ClientConfigurationParametersType);
43
44
  initialize(): Promise<Result<void, KameleoonError>>;
@@ -52,6 +53,7 @@ export declare class ClientConfiguration implements IClientConfiguration {
52
53
  get holdout(): ExperimentType | null;
53
54
  get meGroups(): Map<string, MEGroup>;
54
55
  get isConsentRequired(): boolean;
56
+ get consentBlockingBehaviour(): ConsentBlockingBehaviour;
55
57
  get hasAnyTargetedDeliveryRule(): boolean;
56
58
  private checkShouldUpdate;
57
59
  private readStorageData;
@@ -71,5 +73,6 @@ export declare class ClientConfiguration implements IClientConfiguration {
71
73
  private makeMEGroups;
72
74
  private updateStorageData;
73
75
  private updateConsentRequired;
76
+ private consentBlockingBehaviourFromStr;
74
77
  }
75
78
  export {};
@@ -1,3 +1,2 @@
1
- import { ClientConfigurationDataType, ConfigurationDataType } from '../clientConfiguration';
2
- export declare const DEFAULT_CONFIGURATION_DATA: ClientConfigurationDataType;
3
- export declare const DEFAULT_CLIENT_CONFIGURATION: ConfigurationDataType;
1
+ import { ConfigurationDataType } from '../clientConfiguration';
2
+ export declare const DEFAULT_DATA_FILE_CONFIGURATION: ConfigurationDataType;
@@ -50,6 +50,10 @@ export declare enum ConsentType {
50
50
  Required = "REQUIRED",
51
51
  NotRequired = "NOT_REQUIRED"
52
52
  }
53
+ export declare enum ConsentBlockingBehaviour {
54
+ PartiallyBlocked = "PARTIALLY_BLOCK",
55
+ CompletelyBlocked = "FULLY_BLOCK"
56
+ }
53
57
  export type DeviationType = {
54
58
  variationId: string;
55
59
  value: number;
@@ -129,6 +133,7 @@ export type ConfigurationType = {
129
133
  realTimeUpdate: boolean;
130
134
  consentType: ConsentType;
131
135
  dataApiDomain: string;
136
+ consentOptOutBehavior: string;
132
137
  };
133
138
  export type ClientConfigurationParametersType = {
134
139
  urlProvider: IUrlProvider;
@@ -249,10 +249,17 @@ class KameleoonError extends Error {
249
249
  this.message = ERROR_MESSAGES[type](secondParam, thirdParam);
250
250
  break;
251
251
  case exports.KameleoonException.FeatureFlagVariationNotFound:
252
- case exports.KameleoonException.FeatureFlagEnvironmentDisabled:
253
252
  case exports.KameleoonException.FeatureFlagVariableNotFound:
254
253
  this.message = ERROR_MESSAGES[type](secondParam, thirdParam);
255
254
  break;
255
+ case exports.KameleoonException.FeatureFlagEnvironmentDisabled:
256
+ if (thirdParam !== undefined) {
257
+ this.message = ERROR_MESSAGES[type](secondParam, thirdParam);
258
+ }
259
+ else {
260
+ this.message = secondParam;
261
+ }
262
+ break;
256
263
  case exports.KameleoonException.StorageWrite:
257
264
  case exports.KameleoonException.JSONParse:
258
265
  this.message = ERROR_MESSAGES[type](secondParam);
@@ -308,7 +315,7 @@ exports.RequestType = void 0;
308
315
  RequestType["RemoteData"] = "remoteData";
309
316
  })(exports.RequestType || (exports.RequestType = {}));
310
317
 
311
- const NUMBER_OF_RETRIES = 2;
318
+ const NUMBER_OF_RETRIES = 1;
312
319
  exports.Header = void 0;
313
320
  (function (Header) {
314
321
  Header["UserAgent"] = "User-Agent";
@@ -887,6 +894,9 @@ class UrlProvider {
887
894
  const currentDomain = this.domains[UrlType.DataApi];
888
895
  this.domains[UrlType.DataApi] = currentDomain.replace(/^[^.]+/, subDomain);
889
896
  }
897
+ get dataApiDomain() {
898
+ return this.domains[UrlType.DataApi];
899
+ }
890
900
  getClientConfigurationUrl(timeStamp) {
891
901
  this.isInitialized();
892
902
  const baseUrl = `https://${this.domains[UrlType.ClientConfiguration]}/v3/`;
@@ -1146,6 +1156,11 @@ var ConsentType;
1146
1156
  ConsentType["Required"] = "REQUIRED";
1147
1157
  ConsentType["NotRequired"] = "NOT_REQUIRED";
1148
1158
  })(ConsentType || (ConsentType = {}));
1159
+ var ConsentBlockingBehaviour;
1160
+ (function (ConsentBlockingBehaviour) {
1161
+ ConsentBlockingBehaviour["PartiallyBlocked"] = "PARTIALLY_BLOCK";
1162
+ ConsentBlockingBehaviour["CompletelyBlocked"] = "FULLY_BLOCK";
1163
+ })(ConsentBlockingBehaviour || (ConsentBlockingBehaviour = {}));
1149
1164
 
1150
1165
  class EventManager {
1151
1166
  addEventHandler(eventType, callback) {
@@ -1173,17 +1188,14 @@ exports.EventType = void 0;
1173
1188
  EventType["ConfigurationUpdate"] = "configurationUpdate";
1174
1189
  })(exports.EventType || (exports.EventType = {}));
1175
1190
 
1176
- ({
1177
- configuration: {
1178
- consentType: ConsentType.NotRequired},
1179
- });
1180
- const DEFAULT_CLIENT_CONFIGURATION$1 = {
1191
+ const DEFAULT_DATA_FILE_CONFIGURATION = {
1181
1192
  customData: [],
1182
1193
  featureFlags: [],
1183
1194
  configuration: {
1184
1195
  realTimeUpdate: false,
1185
1196
  consentType: ConsentType.NotRequired,
1186
1197
  dataApiDomain: 'data.kameleoon.io',
1198
+ consentOptOutBehavior: ConsentBlockingBehaviour.PartiallyBlocked,
1187
1199
  },
1188
1200
  };
1189
1201
 
@@ -1207,7 +1219,7 @@ class ClientConfiguration {
1207
1219
  constructor({ updateInterval, urlProvider, storage, requester, dataManager, eventSource, externalVisitorCodeManager, eventManager, externalPackageInfo, defaultDataFile, }) {
1208
1220
  this.updateConfigurationIntervalId = null;
1209
1221
  this.updateType = UpdateType.Polling;
1210
- this.configurationData = DEFAULT_CLIENT_CONFIGURATION$1;
1222
+ this.configurationData = DEFAULT_DATA_FILE_CONFIGURATION;
1211
1223
  this.featureFlagsData = new Map();
1212
1224
  this.isTargetedDeliveryRule = null;
1213
1225
  this.segmentsData = null;
@@ -1216,6 +1228,7 @@ class ClientConfiguration {
1216
1228
  this.mappedRules = null;
1217
1229
  this.mappedExperiments = null;
1218
1230
  this.usedDefaultDataFile = false;
1231
+ this.blockingBehaviourMode = ConsentBlockingBehaviour.PartiallyBlocked;
1219
1232
  this.CACHE_REVALIDATE_PERIOD = 90 * exports.Milliseconds.Minute;
1220
1233
  this.urlProvider = urlProvider;
1221
1234
  this.requester = requester;
@@ -1390,6 +1403,9 @@ class ClientConfiguration {
1390
1403
  get isConsentRequired() {
1391
1404
  return this.configuration.consentType === ConsentType.Required;
1392
1405
  }
1406
+ get consentBlockingBehaviour() {
1407
+ return this.blockingBehaviourMode;
1408
+ }
1393
1409
  get hasAnyTargetedDeliveryRule() {
1394
1410
  if (this.isTargetedDeliveryRule !== null) {
1395
1411
  return this.isTargetedDeliveryRule;
@@ -1518,6 +1534,7 @@ class ClientConfiguration {
1518
1534
  KameleoonLogger.info `Configuration update type was toggled to ${UpdateType[updateType]}`;
1519
1535
  yield this.initialize();
1520
1536
  }
1537
+ this.blockingBehaviourMode = this.consentBlockingBehaviourFromStr(clientConfigurationData.configuration.consentOptOutBehavior);
1521
1538
  return buildExports.Ok(toggleUpdateType);
1522
1539
  });
1523
1540
  }
@@ -1624,6 +1641,14 @@ class ClientConfiguration {
1624
1641
  this.visitorCodeManager.consentRequired =
1625
1642
  this.isConsentRequired && !this.hasAnyTargetedDeliveryRule;
1626
1643
  }
1644
+ consentBlockingBehaviourFromStr(str) {
1645
+ if (str === ConsentBlockingBehaviour.PartiallyBlocked ||
1646
+ str === ConsentBlockingBehaviour.CompletelyBlocked) {
1647
+ return str;
1648
+ }
1649
+ KameleoonLogger.error(`Unexpected consent blocking type '${str}'`);
1650
+ return ConsentBlockingBehaviour.PartiallyBlocked;
1651
+ }
1627
1652
  }
1628
1653
 
1629
1654
  function constructTypeMap(indexMap) {
@@ -2418,6 +2443,13 @@ let CustomData$1 = class CustomData {
2418
2443
  get name() {
2419
2444
  return this._name;
2420
2445
  }
2446
+ /**
2447
+ * @private
2448
+ * @method name - an internal getter for a name of custom data
2449
+ * */
2450
+ get values() {
2451
+ return this.value;
2452
+ }
2421
2453
  };
2422
2454
  CustomData$1.UNDEFINED_INDEX = -1;
2423
2455
 
@@ -2435,7 +2467,7 @@ let Conversion$1 = class Conversion {
2435
2467
  this.negative = negative;
2436
2468
  this.status = exports.TrackingStatus.Unsent;
2437
2469
  this.id = Math.floor(Math.random() * 1000000);
2438
- this.metadata = metadata;
2470
+ this._metadata = metadata;
2439
2471
  }
2440
2472
  set _id(id) {
2441
2473
  this.id = id;
@@ -2456,9 +2488,21 @@ let Conversion$1 = class Conversion {
2456
2488
  ? UrlParameter.Metadata + this._encodeMetadata()
2457
2489
  : ''));
2458
2490
  }
2491
+ /**
2492
+ * @private
2493
+ * @method metadata - an internal getter for a metadata of conversion
2494
+ * */
2459
2495
  get _metadata() {
2460
2496
  return this.metadata;
2461
2497
  }
2498
+ /**
2499
+ * @private
2500
+ * @method metadata - an internal setter for setting metadata of conversion
2501
+ * @param {number} value - an index value
2502
+ * */
2503
+ set _metadata(value) {
2504
+ this.metadata = value;
2505
+ }
2462
2506
  get data() {
2463
2507
  var _a;
2464
2508
  return {
@@ -3551,7 +3595,7 @@ class DataProcessor {
3551
3595
  this.cleanupInterval = cleanupInterval;
3552
3596
  this.packageInfo = packageInfo;
3553
3597
  }
3554
- mutUpdateData({ infoData, visitorCode, mutData, dataItem, }) {
3598
+ mutUpdateData({ infoData, visitorCode, mutData, dataItem, extendTtl, }) {
3555
3599
  let { visitorReference, data, isReference } = this.dereferenceData(mutData, visitorCode);
3556
3600
  if (this.packageInfo.isServer &&
3557
3601
  isReference &&
@@ -3562,9 +3606,12 @@ class DataProcessor {
3562
3606
  delete mutData[visitorCode];
3563
3607
  visitorReference = visitorCode;
3564
3608
  }
3565
- const expirationTime = this.cleanupInterval
3566
- ? Date.now() + this.cleanupInterval
3567
- : 0;
3609
+ let expirationTime;
3610
+ if (extendTtl) {
3611
+ expirationTime = this.cleanupInterval
3612
+ ? Date.now() + this.cleanupInterval
3613
+ : 0;
3614
+ }
3568
3615
  switch (dataItem.data.type) {
3569
3616
  case exports.KameleoonData.CustomData: {
3570
3617
  this.updateCustomData({
@@ -3919,13 +3966,17 @@ class DataProcessor {
3919
3966
  return closestCleanupTime;
3920
3967
  }
3921
3968
  updateField({ key, value, data, visitorCode, expirationTime, }) {
3922
- data[visitorCode][key] = Object.assign(Object.assign({}, value), { expirationTime });
3969
+ var _a;
3970
+ const existing = data[visitorCode][key];
3971
+ data[visitorCode][key] = Object.assign(Object.assign({}, value), { expirationTime: (_a = expirationTime !== null && expirationTime !== void 0 ? expirationTime : (existing.expirationTime && existing.expirationTime)) !== null && _a !== void 0 ? _a : Date.now() });
3923
3972
  }
3924
3973
  createField({ key, value, data, visitorCode, expirationTime, }) {
3925
3974
  data[visitorCode] = Object.assign(Object.assign({}, data[visitorCode]), { [key]: Object.assign(Object.assign({}, value), { expirationTime }) });
3926
3975
  }
3927
3976
  updateNestedField({ key, nestedKey, value, data, visitorCode, expirationTime, }) {
3928
- data[visitorCode][key][nestedKey] = Object.assign(Object.assign({}, value), { expirationTime });
3977
+ var _a;
3978
+ const existing = data[visitorCode][key][nestedKey];
3979
+ data[visitorCode][key][nestedKey] = Object.assign(Object.assign({}, value), { expirationTime: (_a = expirationTime !== null && expirationTime !== void 0 ? expirationTime : existing === null || existing === void 0 ? void 0 : existing.expirationTime) !== null && _a !== void 0 ? _a : Date.now() });
3929
3980
  }
3930
3981
  createNestedField({ key, nestedKey, value, data, visitorCode, expirationTime, }) {
3931
3982
  var _a;
@@ -5191,15 +5242,7 @@ exports.KameleoonStorageKey = void 0;
5191
5242
  KameleoonStorageKey["ForcedExperimentVariation"] = "kameleoonForcedExperimentVariation";
5192
5243
  })(exports.KameleoonStorageKey || (exports.KameleoonStorageKey = {}));
5193
5244
  const DEFAULT_CLIENT_CONFIGURATION = {
5194
- data: {
5195
- customData: [],
5196
- featureFlags: [],
5197
- configuration: {
5198
- realTimeUpdate: false,
5199
- consentType: ConsentType.NotRequired,
5200
- dataApiDomain: 'data.kameleoon.io',
5201
- },
5202
- },
5245
+ data: DEFAULT_DATA_FILE_CONFIGURATION,
5203
5246
  lastUpdate: '',
5204
5247
  };
5205
5248
  // --- Note ---
@@ -5711,7 +5754,7 @@ class DataManager {
5711
5754
  return resultData;
5712
5755
  }
5713
5756
  storeTrackedData(data) {
5714
- this.storeData(data);
5757
+ this.storeData(data, false);
5715
5758
  const infoResult = this.infoStorage.read();
5716
5759
  if (!infoResult.ok) {
5717
5760
  return;
@@ -5763,15 +5806,18 @@ class DataManager {
5763
5806
  targetingData,
5764
5807
  visitorCode: firstParameter,
5765
5808
  kameleoonData: secondParameter,
5809
+ extendTtl: true,
5766
5810
  });
5767
5811
  }
5768
5812
  else {
5769
5813
  for (const [visitorCode, kameleoonData] of Object.entries(firstParameter)) {
5814
+ const extendTtl = typeof secondParameter[0] === 'boolean' ? secondParameter[0] : true;
5770
5815
  this.mutUpdateTargetingData({
5771
5816
  infoData,
5772
5817
  targetingData,
5773
5818
  visitorCode,
5774
5819
  kameleoonData,
5820
+ extendTtl,
5775
5821
  });
5776
5822
  }
5777
5823
  }
@@ -5888,8 +5934,8 @@ class DataManager {
5888
5934
  }
5889
5935
  return null;
5890
5936
  }
5891
- mutUpdateTargetingData({ infoData, visitorCode, kameleoonData, targetingData, }) {
5892
- var _a;
5937
+ mutUpdateTargetingData({ infoData, visitorCode, kameleoonData, targetingData, extendTtl, }) {
5938
+ var _a, _b, _c;
5893
5939
  for (const dataItem of kameleoonData) {
5894
5940
  // process custom data
5895
5941
  if (dataItem.data.type === exports.KameleoonData.CustomData) {
@@ -5905,16 +5951,21 @@ class DataManager {
5905
5951
  }
5906
5952
  // process metadata of conversions
5907
5953
  if (dataItem.data.type === exports.KameleoonData.Conversion) {
5908
- (_a = dataItem._metadata) === null || _a === void 0 ? void 0 : _a.forEach((item) => this.trySetCustomDataIndexByName(item));
5954
+ const conversion = dataItem;
5955
+ if (((_b = (_a = conversion._metadata) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0) > 0) {
5956
+ conversion._metadata = (_c = conversion._metadata) === null || _c === void 0 ? void 0 : _c.filter((item) => this.trySetCustomDataIndexByName(item));
5957
+ }
5909
5958
  }
5910
5959
  const expirationTime = this.dataProcessor.mutUpdateData({
5911
5960
  infoData,
5912
5961
  visitorCode,
5913
5962
  mutData: targetingData,
5914
5963
  dataItem,
5964
+ extendTtl,
5915
5965
  });
5916
5966
  const nextCleanup = infoData.nextDataCleanup;
5917
- if (!nextCleanup || (nextCleanup && expirationTime < nextCleanup)) {
5967
+ if (!nextCleanup ||
5968
+ (nextCleanup && expirationTime && expirationTime < nextCleanup)) {
5918
5969
  infoData.nextDataCleanup = expirationTime;
5919
5970
  }
5920
5971
  if (dataItem.data.status === exports.TrackingStatus.Unsent) {
@@ -5946,7 +5997,9 @@ class DataManager {
5946
5997
  var _a;
5947
5998
  const { data } = customData;
5948
5999
  const isDataValid = Boolean(data.value.length && data.value[0].length);
5949
- this.trySetCustomDataIndexByName(customData);
6000
+ if (!this.trySetCustomDataIndexByName(customData)) {
6001
+ return false;
6002
+ }
5950
6003
  if (data.index == CustomData$1.UNDEFINED_INDEX) {
5951
6004
  data.index = customData.index;
5952
6005
  }
@@ -5976,13 +6029,15 @@ class DataManager {
5976
6029
  return true;
5977
6030
  }
5978
6031
  trySetCustomDataIndexByName(customData) {
5979
- if (customData.index == CustomData$1.UNDEFINED_INDEX && customData.name) {
5980
- const cdIndex = this.customDataIndexByName.get(customData.name);
5981
- if (cdIndex === undefined) {
5982
- return;
5983
- }
5984
- customData.index = cdIndex;
5985
- }
6032
+ if (customData.index !== CustomData$1.UNDEFINED_INDEX)
6033
+ return true;
6034
+ if (!customData.name)
6035
+ return false;
6036
+ const cdIndex = this.customDataIndexByName.get(customData.name);
6037
+ if (cdIndex == null)
6038
+ return false;
6039
+ customData.index = cdIndex;
6040
+ return true;
5986
6041
  }
5987
6042
  get unsentDataVisitors() {
5988
6043
  const infoResult = this.infoStorage.read();
@@ -7095,6 +7150,13 @@ class Hasher {
7095
7150
  }
7096
7151
  }
7097
7152
 
7153
+ var LegalConsent;
7154
+ (function (LegalConsent) {
7155
+ LegalConsent[LegalConsent["Unknown"] = 0] = "Unknown";
7156
+ LegalConsent[LegalConsent["Given"] = 1] = "Given";
7157
+ LegalConsent[LegalConsent["NotGiven"] = 2] = "NotGiven";
7158
+ })(LegalConsent || (LegalConsent = {}));
7159
+
7098
7160
  class VariationConfiguration {
7099
7161
  constructor(externalStorage, externalStorageForcedExperimentVariations, externalStorageForcedFeatureVariations, visitorCodeManager, clientConfiguration) {
7100
7162
  this.storage = externalStorage;
@@ -7153,9 +7215,12 @@ class VariationConfiguration {
7153
7215
  }
7154
7216
  return buildExports.Ok(featureFlagVariations);
7155
7217
  }
7156
- getVariation({ visitorCode, visitorIdentifier, featureFlag, targetingData, packageInfo, clientConfiguration, dataManager, track = true, withAssignment = false, }) {
7218
+ getVariation({ visitorCode, visitorIdentifier, featureFlag, targetingData, packageInfo, clientConfiguration, dataManager, legalConsent, track = true, withAssignment = false, }) {
7157
7219
  KameleoonLogger.debug `CALL: VariationConfiguration.getVariation(visitorCode: ${visitorCode}, visitorIdentifier: ${visitorIdentifier}, featureFlag: ${featureFlag}, targetingData: ${targetingData}, packageInfo: ${packageInfo}, clientConfiguration, dataManager, withAssignment: ${withAssignment})`;
7158
7220
  const { rules, featureKey, id: featureFlagId, defaultVariationKey, } = featureFlag;
7221
+ const consent = clientConfiguration.isConsentRequired
7222
+ ? legalConsent
7223
+ : LegalConsent.Given;
7159
7224
  for (const rule of rules) {
7160
7225
  const { segment, experimentId, id, exposition, respoolTime, variationByExposition, } = rule;
7161
7226
  const forcedVariationData = this.getForcedExperimentVariation(visitorCode, rule.experimentId);
@@ -7202,6 +7267,16 @@ class VariationConfiguration {
7202
7267
  });
7203
7268
  KameleoonLogger.debug `Calculated ruleHash: ${ruleHash} for code: ${visitorIdentifier}`;
7204
7269
  if (ruleHash <= exposition) {
7270
+ // Checking if the evaluation is blocked due to the consent policy
7271
+ if (consent == LegalConsent.NotGiven &&
7272
+ rule.type == RuleType.EXPERIMENTATION) {
7273
+ const behaviour = clientConfiguration.consentBlockingBehaviour;
7274
+ if (behaviour == ConsentBlockingBehaviour.PartiallyBlocked) {
7275
+ break;
7276
+ }
7277
+ return buildExports.Err(new KameleoonError(exports.KameleoonException.FeatureFlagEnvironmentDisabled, `Evaluation of ${rule} is blocked because consent is not provided for visitor '${visitorCode}'`));
7278
+ }
7279
+ // evaluate with CB scores if applicable
7205
7280
  let variation = this.evaluateCBScores(rule, visitorIdentifier, targetingData);
7206
7281
  if (!variation) {
7207
7282
  const variationHash = Hasher.getHashDouble({
@@ -7563,7 +7638,6 @@ class KameleoonEventSource {
7563
7638
  const VISITOR_CODE_LENGTH = 16;
7564
7639
  const VISITOR_CODE_MAX_LENGTH = 255;
7565
7640
  const DEFAULT_MAX_AGE = 60 * 60 * 24 * 365;
7566
- const ZERO_MAX_AGE = 0;
7567
7641
  const PATH = '/';
7568
7642
 
7569
7643
  /**
@@ -8031,7 +8105,7 @@ class Tracker {
8031
8105
  this.dataManager.storeTrackedData(updatedData);
8032
8106
  }
8033
8107
  else {
8034
- this.dataManager.storeData(updatedData);
8108
+ this.dataManager.storeData(updatedData, false);
8035
8109
  }
8036
8110
  }
8037
8111
  getUnsentVisitorData(visitorCode, isConsentProvided) {
@@ -8383,16 +8457,23 @@ class KameleoonClient {
8383
8457
  const variations = new Map();
8384
8458
  const featureFlags = this.clientConfiguration.featureFlags;
8385
8459
  for (const featureFlag of featureFlags.values()) {
8386
- const variation = this._getFeatureVariation({
8387
- visitorCode,
8388
- featureKey: featureFlag.featureKey,
8389
- track,
8390
- });
8391
- if (variation.ok) {
8392
- if (!onlyActive || variation.data.key !== OFF_VARIATION_KEY) {
8460
+ try {
8461
+ const variation = this._getFeatureVariation({
8462
+ visitorCode,
8463
+ featureKey: featureFlag.featureKey,
8464
+ track,
8465
+ });
8466
+ if (variation.ok &&
8467
+ (!onlyActive || variation.data.key !== OFF_VARIATION_KEY)) {
8393
8468
  variations.set(featureFlag.featureKey, variation.data);
8394
8469
  }
8395
8470
  }
8471
+ catch (err) {
8472
+ if (err instanceof KameleoonError &&
8473
+ err.type !== exports.KameleoonException.FeatureFlagEnvironmentDisabled) {
8474
+ throw err;
8475
+ }
8476
+ }
8396
8477
  }
8397
8478
  KameleoonLogger.info `RETURN: KameleoonClient.getVariations(visitorCode: ${visitorCode}, onlyActive: ${onlyActive}, track: ${track}) -> (variations: ${variations})`;
8398
8479
  return variations;
@@ -8712,25 +8793,16 @@ class KameleoonClient {
8712
8793
  path: PATH,
8713
8794
  });
8714
8795
  }
8715
- else {
8716
- if (this.visitorCodeManager.consentRequired) {
8717
- setData({
8718
- visitorCode: '',
8719
- key: exports.KameleoonStorageKey.VisitorCode,
8720
- maxAge: ZERO_MAX_AGE,
8721
- path: PATH,
8722
- });
8723
- }
8724
- }
8725
8796
  KameleoonLogger.info `RETURN: KameleoonClient.setUserConsent(visitorCode: ${visitorCode}, consent: ${consent}, setData: ${setData})`;
8726
8797
  }
8727
8798
  updateConsentData(visitorCode, consent) {
8728
8799
  const readResult = this.consentDataStorage.read();
8800
+ const legalConsent = consent ? LegalConsent.Given : LegalConsent.NotGiven;
8729
8801
  if (!readResult.ok) {
8730
8802
  if (readResult.error.type === exports.KameleoonException.StorageEmpty) {
8731
8803
  this.consentDataStorage.write({
8732
8804
  [visitorCode]: {
8733
- consent,
8805
+ consent: legalConsent,
8734
8806
  },
8735
8807
  });
8736
8808
  }
@@ -8738,28 +8810,36 @@ class KameleoonClient {
8738
8810
  }
8739
8811
  const data = readResult.data;
8740
8812
  data[visitorCode] = {
8741
- consent,
8813
+ consent: legalConsent,
8742
8814
  };
8743
8815
  this.consentDataStorage.write(data);
8744
8816
  }
8817
+ getLegalConsent(visitorCode) {
8818
+ KameleoonLogger.debug `CALL: KameleoonClient.getLegalConsent(visitorCode: ${visitorCode})`;
8819
+ let legalConsent;
8820
+ const consentDataResult = this.consentDataStorage.read();
8821
+ legalConsent = consentDataResult.ok
8822
+ ? this.extractLegalConsent(consentDataResult.data[visitorCode])
8823
+ : LegalConsent.Unknown;
8824
+ KameleoonLogger.debug `RETURN: KameleoonClient.getLegalConsent(visitorCode: ${visitorCode}) -> (legalConsent: ${legalConsent})`;
8825
+ return legalConsent;
8826
+ }
8827
+ extractLegalConsent(consentData) {
8828
+ if (consentData === undefined)
8829
+ return LegalConsent.Unknown;
8830
+ if (typeof consentData === 'boolean') {
8831
+ return consentData ? LegalConsent.Given : LegalConsent.NotGiven;
8832
+ }
8833
+ const value = consentData.consent;
8834
+ if (typeof value === 'boolean')
8835
+ return value ? LegalConsent.Given : LegalConsent.NotGiven;
8836
+ return value;
8837
+ }
8745
8838
  _isConsentProvided(visitorCode) {
8746
8839
  KameleoonLogger.debug `CALL: KameleoonClient._isConsentProvided(visitorCode: ${visitorCode})`;
8747
8840
  const { isConsentRequired } = this.clientConfiguration;
8748
- const consentDataResult = this.consentDataStorage.read();
8749
- let isConsentProvided = false;
8750
- if (!isConsentRequired) {
8751
- isConsentProvided = true;
8752
- }
8753
- else if (consentDataResult.ok) {
8754
- const consentData = consentDataResult.data[visitorCode];
8755
- // for consistency with the old data in the storage
8756
- if (typeof consentData === 'boolean') {
8757
- isConsentProvided = consentData;
8758
- }
8759
- else {
8760
- isConsentProvided = consentData && consentData.consent;
8761
- }
8762
- }
8841
+ const isConsentProvided = !isConsentRequired ||
8842
+ this.getLegalConsent(visitorCode) == LegalConsent.Given;
8763
8843
  KameleoonLogger.debug `RETURN: KameleoonClient._isConsentProvided(visitorCode: ${visitorCode}) -> (isConsentProvided: ${isConsentProvided})`;
8764
8844
  return isConsentProvided;
8765
8845
  }
@@ -8787,22 +8867,32 @@ class KameleoonClient {
8787
8867
  if (!featureFlag.environmentEnabled) {
8788
8868
  continue;
8789
8869
  }
8790
- const evalExp = this._evaluate({
8791
- visitorCode,
8792
- featureFlag,
8793
- track: false,
8794
- save: false,
8795
- });
8796
- if (evalExp.variationKey !== OFF_VARIATION_KEY) {
8797
- activeVariations.push({
8798
- variationKey: evalExp.variationKey,
8799
- variationId: evalExp.variationId,
8800
- experimentId: evalExp.experimentId,
8801
- featureFlagId: featureFlag.id,
8802
- featureKey: featureFlag.featureKey,
8803
- rule: null,
8804
- isTargetedRule: evalExp.ruleType === RuleType.TARGETED_DELIVERY,
8870
+ try {
8871
+ const evalExp = this._evaluate({
8872
+ visitorCode,
8873
+ featureFlag,
8874
+ track: false,
8875
+ save: false,
8805
8876
  });
8877
+ if (evalExp.variationKey !== OFF_VARIATION_KEY) {
8878
+ activeVariations.push({
8879
+ variationKey: evalExp.variationKey,
8880
+ variationId: evalExp.variationId,
8881
+ experimentId: evalExp.experimentId,
8882
+ featureFlagId: featureFlag.id,
8883
+ featureKey: featureFlag.featureKey,
8884
+ rule: null,
8885
+ isTargetedRule: evalExp.ruleType === RuleType.TARGETED_DELIVERY,
8886
+ });
8887
+ }
8888
+ }
8889
+ catch (err) {
8890
+ if (err instanceof KameleoonError) {
8891
+ if (err.type != exports.KameleoonException.FeatureFlagEnvironmentDisabled) {
8892
+ KameleoonLogger.error `Unexpected error: ${err}`;
8893
+ }
8894
+ continue;
8895
+ }
8806
8896
  }
8807
8897
  }
8808
8898
  KameleoonLogger.debug `RETURN: KameleoonClient._getActiveFeatureVariations(visitorCode: ${visitorCode}) -> (activeVariations: ${activeVariations})`;
@@ -8823,6 +8913,7 @@ class KameleoonClient {
8823
8913
  else if (this._isVisitorNotInHoldout(visitorCode, track, save, featureFlag, visitorData) &&
8824
8914
  this._isFFUnrestrictedByMEGroup(visitorCode, featureFlag, visitorData)) {
8825
8915
  const visitorIdentifier = this._getCodeForHash(visitorCode, featureFlag.bucketingCustomDataIndex, visitorData);
8916
+ const legalConsent = this.getLegalConsent(visitorCode);
8826
8917
  const variationData = this.variationConfiguration
8827
8918
  .getVariation({
8828
8919
  visitorCode,
@@ -8834,6 +8925,7 @@ class KameleoonClient {
8834
8925
  clientConfiguration: this.clientConfiguration,
8835
8926
  dataManager: this.dataManager,
8836
8927
  packageInfo: this.externalPackageInfo,
8928
+ legalConsent,
8837
8929
  })
8838
8930
  .throw();
8839
8931
  evalExp =
@@ -8952,6 +9044,14 @@ class KameleoonClient {
8952
9044
  }
8953
9045
  KameleoonLogger.debug `CALL: KameleoonClient._isVisitorNotInHoldout(visitorCode: ${visitorCode}, track: ${track}, save: ${save}, featureFlag: ${featureFlag}, visitorData: ${visitorData})`;
8954
9046
  let isNotInHoldout = true;
9047
+ // Checking if the evaluation is blocked due to the consent policy
9048
+ const legalConsent = this.getLegalConsent(visitorCode);
9049
+ if (legalConsent == LegalConsent.NotGiven) {
9050
+ const behaviour = this.clientConfiguration.consentBlockingBehaviour;
9051
+ if (behaviour == ConsentBlockingBehaviour.CompletelyBlocked) {
9052
+ throw new KameleoonError(exports.KameleoonException.FeatureFlagEnvironmentDisabled, `Evaluation of holdout is blocked because consent is not provided for visitor '${visitorCode}'`);
9053
+ }
9054
+ }
8955
9055
  const codeForHash = this._getCodeForHash(visitorCode, featureFlag === null || featureFlag === void 0 ? void 0 : featureFlag.bucketingCustomDataIndex, visitorData);
8956
9056
  const holdoutHash = Hasher.getHashDouble({
8957
9057
  visitorIdentifier: codeForHash,