@kameleoon/javascript-sdk-core 5.16.0 → 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.
@@ -218,10 +218,17 @@ class KameleoonError extends Error {
218
218
  this.message = ERROR_MESSAGES[type](secondParam, thirdParam);
219
219
  break;
220
220
  case KameleoonException.FeatureFlagVariationNotFound:
221
- case KameleoonException.FeatureFlagEnvironmentDisabled:
222
221
  case KameleoonException.FeatureFlagVariableNotFound:
223
222
  this.message = ERROR_MESSAGES[type](secondParam, thirdParam);
224
223
  break;
224
+ case KameleoonException.FeatureFlagEnvironmentDisabled:
225
+ if (thirdParam !== undefined) {
226
+ this.message = ERROR_MESSAGES[type](secondParam, thirdParam);
227
+ }
228
+ else {
229
+ this.message = secondParam;
230
+ }
231
+ break;
225
232
  case KameleoonException.StorageWrite:
226
233
  case KameleoonException.JSONParse:
227
234
  this.message = ERROR_MESSAGES[type](secondParam);
@@ -277,7 +284,7 @@ var RequestType;
277
284
  RequestType["RemoteData"] = "remoteData";
278
285
  })(RequestType || (RequestType = {}));
279
286
 
280
- const NUMBER_OF_RETRIES = 2;
287
+ const NUMBER_OF_RETRIES = 1;
281
288
  var Header;
282
289
  (function (Header) {
283
290
  Header["UserAgent"] = "User-Agent";
@@ -856,6 +863,9 @@ class UrlProvider {
856
863
  const currentDomain = this.domains[UrlType.DataApi];
857
864
  this.domains[UrlType.DataApi] = currentDomain.replace(/^[^.]+/, subDomain);
858
865
  }
866
+ get dataApiDomain() {
867
+ return this.domains[UrlType.DataApi];
868
+ }
859
869
  getClientConfigurationUrl(timeStamp) {
860
870
  this.isInitialized();
861
871
  const baseUrl = `https://${this.domains[UrlType.ClientConfiguration]}/v3/`;
@@ -1115,6 +1125,11 @@ var ConsentType;
1115
1125
  ConsentType["Required"] = "REQUIRED";
1116
1126
  ConsentType["NotRequired"] = "NOT_REQUIRED";
1117
1127
  })(ConsentType || (ConsentType = {}));
1128
+ var ConsentBlockingBehaviour;
1129
+ (function (ConsentBlockingBehaviour) {
1130
+ ConsentBlockingBehaviour["PartiallyBlocked"] = "PARTIALLY_BLOCK";
1131
+ ConsentBlockingBehaviour["CompletelyBlocked"] = "FULLY_BLOCK";
1132
+ })(ConsentBlockingBehaviour || (ConsentBlockingBehaviour = {}));
1118
1133
 
1119
1134
  class EventManager {
1120
1135
  addEventHandler(eventType, callback) {
@@ -1142,17 +1157,14 @@ var EventType;
1142
1157
  EventType["ConfigurationUpdate"] = "configurationUpdate";
1143
1158
  })(EventType || (EventType = {}));
1144
1159
 
1145
- ({
1146
- configuration: {
1147
- consentType: ConsentType.NotRequired},
1148
- });
1149
- const DEFAULT_CLIENT_CONFIGURATION$1 = {
1160
+ const DEFAULT_DATA_FILE_CONFIGURATION = {
1150
1161
  customData: [],
1151
1162
  featureFlags: [],
1152
1163
  configuration: {
1153
1164
  realTimeUpdate: false,
1154
1165
  consentType: ConsentType.NotRequired,
1155
1166
  dataApiDomain: 'data.kameleoon.io',
1167
+ consentOptOutBehavior: ConsentBlockingBehaviour.PartiallyBlocked,
1156
1168
  },
1157
1169
  };
1158
1170
 
@@ -1176,7 +1188,7 @@ class ClientConfiguration {
1176
1188
  constructor({ updateInterval, urlProvider, storage, requester, dataManager, eventSource, externalVisitorCodeManager, eventManager, externalPackageInfo, defaultDataFile, }) {
1177
1189
  this.updateConfigurationIntervalId = null;
1178
1190
  this.updateType = UpdateType.Polling;
1179
- this.configurationData = DEFAULT_CLIENT_CONFIGURATION$1;
1191
+ this.configurationData = DEFAULT_DATA_FILE_CONFIGURATION;
1180
1192
  this.featureFlagsData = new Map();
1181
1193
  this.isTargetedDeliveryRule = null;
1182
1194
  this.segmentsData = null;
@@ -1185,6 +1197,7 @@ class ClientConfiguration {
1185
1197
  this.mappedRules = null;
1186
1198
  this.mappedExperiments = null;
1187
1199
  this.usedDefaultDataFile = false;
1200
+ this.blockingBehaviourMode = ConsentBlockingBehaviour.PartiallyBlocked;
1188
1201
  this.CACHE_REVALIDATE_PERIOD = 90 * Milliseconds.Minute;
1189
1202
  this.urlProvider = urlProvider;
1190
1203
  this.requester = requester;
@@ -1359,6 +1372,9 @@ class ClientConfiguration {
1359
1372
  get isConsentRequired() {
1360
1373
  return this.configuration.consentType === ConsentType.Required;
1361
1374
  }
1375
+ get consentBlockingBehaviour() {
1376
+ return this.blockingBehaviourMode;
1377
+ }
1362
1378
  get hasAnyTargetedDeliveryRule() {
1363
1379
  if (this.isTargetedDeliveryRule !== null) {
1364
1380
  return this.isTargetedDeliveryRule;
@@ -1487,6 +1503,7 @@ class ClientConfiguration {
1487
1503
  KameleoonLogger.info `Configuration update type was toggled to ${UpdateType[updateType]}`;
1488
1504
  yield this.initialize();
1489
1505
  }
1506
+ this.blockingBehaviourMode = this.consentBlockingBehaviourFromStr(clientConfigurationData.configuration.consentOptOutBehavior);
1490
1507
  return buildExports.Ok(toggleUpdateType);
1491
1508
  });
1492
1509
  }
@@ -1593,6 +1610,14 @@ class ClientConfiguration {
1593
1610
  this.visitorCodeManager.consentRequired =
1594
1611
  this.isConsentRequired && !this.hasAnyTargetedDeliveryRule;
1595
1612
  }
1613
+ consentBlockingBehaviourFromStr(str) {
1614
+ if (str === ConsentBlockingBehaviour.PartiallyBlocked ||
1615
+ str === ConsentBlockingBehaviour.CompletelyBlocked) {
1616
+ return str;
1617
+ }
1618
+ KameleoonLogger.error(`Unexpected consent blocking type '${str}'`);
1619
+ return ConsentBlockingBehaviour.PartiallyBlocked;
1620
+ }
1596
1621
  }
1597
1622
 
1598
1623
  function constructTypeMap(indexMap) {
@@ -2271,11 +2296,11 @@ let CustomData$1 = class CustomData {
2271
2296
  const isNumber = typeof first === 'number';
2272
2297
  const isBoolean = typeof second === 'boolean';
2273
2298
  if (isNumber) {
2274
- this.index = first;
2299
+ this._index = first;
2275
2300
  }
2276
2301
  else {
2277
- this.name = first;
2278
- this.index = -1;
2302
+ this._name = first;
2303
+ this._index = CustomData.UNDEFINED_INDEX;
2279
2304
  }
2280
2305
  this.overwrite = isBoolean ? second : true;
2281
2306
  this.value = isBoolean
@@ -2286,7 +2311,7 @@ let CustomData$1 = class CustomData {
2286
2311
  // --- Note ---
2287
2312
  // If SDK is used in vanilla JS codebase, then you're also able to create an instance
2288
2313
  // with no required data, we don't want send anything to tracking in that case
2289
- if (typeof this.index !== 'number') {
2314
+ if (typeof this._index !== 'number') {
2290
2315
  return '';
2291
2316
  }
2292
2317
  const uniqueValues = [...new Set(this.value)];
@@ -2303,7 +2328,7 @@ let CustomData$1 = class CustomData {
2303
2328
  }
2304
2329
  return (UrlEventType.CustomData +
2305
2330
  UrlParameter.Index +
2306
- this.index +
2331
+ this._index +
2307
2332
  UrlParameter.ValuesCountMap +
2308
2333
  encodeURIComponent(JSON.stringify(resultValue)) +
2309
2334
  UrlParameter.Overwrite +
@@ -2311,7 +2336,7 @@ let CustomData$1 = class CustomData {
2311
2336
  identifierParameter);
2312
2337
  }
2313
2338
  get data() {
2314
- return Object.assign(Object.assign({ index: this.index }, (this.name !== undefined ? { name: this.name } : {})), { value: this.value, type: KameleoonData.CustomData, isIdentifier: this.isIdentifier, status: this.status, overwrite: this.overwrite });
2339
+ return Object.assign(Object.assign({ index: this._index }, (this._name !== undefined ? { name: this._name } : {})), { value: this.value, type: KameleoonData.CustomData, isIdentifier: this.isIdentifier, status: this.status, overwrite: this.overwrite });
2315
2340
  }
2316
2341
  get status() {
2317
2342
  if (this._isMappingIdentifier) {
@@ -2330,7 +2355,7 @@ let CustomData$1 = class CustomData {
2330
2355
  let customData;
2331
2356
  if (name) {
2332
2357
  customData = new CustomData(name, overwrite !== null && overwrite !== void 0 ? overwrite : true, ...value);
2333
- customData._index = index;
2358
+ customData.index = index;
2334
2359
  }
2335
2360
  else {
2336
2361
  customData = new CustomData(index, overwrite !== null && overwrite !== void 0 ? overwrite : true, ...value);
@@ -2367,13 +2392,35 @@ let CustomData$1 = class CustomData {
2367
2392
  }
2368
2393
  /**
2369
2394
  * @private
2370
- * @method _index - an internal setter for setting index of custom data
2395
+ * @method index - an internal setter for setting index of custom data
2371
2396
  * @param {number} value - an index value
2372
2397
  * */
2373
- set _index(value) {
2374
- this.index = value;
2398
+ set index(value) {
2399
+ this._index = value;
2400
+ }
2401
+ /**
2402
+ * @private
2403
+ * @method index - an internal getter for index of custom data
2404
+ * */
2405
+ get index() {
2406
+ return this._index;
2407
+ }
2408
+ /**
2409
+ * @private
2410
+ * @method name - an internal getter for a name of custom data
2411
+ * */
2412
+ get name() {
2413
+ return this._name;
2414
+ }
2415
+ /**
2416
+ * @private
2417
+ * @method name - an internal getter for a name of custom data
2418
+ * */
2419
+ get values() {
2420
+ return this.value;
2375
2421
  }
2376
2422
  };
2423
+ CustomData$1.UNDEFINED_INDEX = -1;
2377
2424
 
2378
2425
  /**
2379
2426
  * @class
@@ -2389,7 +2436,7 @@ let Conversion$1 = class Conversion {
2389
2436
  this.negative = negative;
2390
2437
  this.status = TrackingStatus.Unsent;
2391
2438
  this.id = Math.floor(Math.random() * 1000000);
2392
- this.metadata = metadata;
2439
+ this._metadata = metadata;
2393
2440
  }
2394
2441
  set _id(id) {
2395
2442
  this.id = id;
@@ -2410,6 +2457,21 @@ let Conversion$1 = class Conversion {
2410
2457
  ? UrlParameter.Metadata + this._encodeMetadata()
2411
2458
  : ''));
2412
2459
  }
2460
+ /**
2461
+ * @private
2462
+ * @method metadata - an internal getter for a metadata of conversion
2463
+ * */
2464
+ get _metadata() {
2465
+ return this.metadata;
2466
+ }
2467
+ /**
2468
+ * @private
2469
+ * @method metadata - an internal setter for setting metadata of conversion
2470
+ * @param {number} value - an index value
2471
+ * */
2472
+ set _metadata(value) {
2473
+ this.metadata = value;
2474
+ }
2413
2475
  get data() {
2414
2476
  var _a;
2415
2477
  return {
@@ -3502,7 +3564,7 @@ class DataProcessor {
3502
3564
  this.cleanupInterval = cleanupInterval;
3503
3565
  this.packageInfo = packageInfo;
3504
3566
  }
3505
- mutUpdateData({ infoData, visitorCode, mutData, dataItem, }) {
3567
+ mutUpdateData({ infoData, visitorCode, mutData, dataItem, extendTtl, }) {
3506
3568
  let { visitorReference, data, isReference } = this.dereferenceData(mutData, visitorCode);
3507
3569
  if (this.packageInfo.isServer &&
3508
3570
  isReference &&
@@ -3513,9 +3575,12 @@ class DataProcessor {
3513
3575
  delete mutData[visitorCode];
3514
3576
  visitorReference = visitorCode;
3515
3577
  }
3516
- const expirationTime = this.cleanupInterval
3517
- ? Date.now() + this.cleanupInterval
3518
- : 0;
3578
+ let expirationTime;
3579
+ if (extendTtl) {
3580
+ expirationTime = this.cleanupInterval
3581
+ ? Date.now() + this.cleanupInterval
3582
+ : 0;
3583
+ }
3519
3584
  switch (dataItem.data.type) {
3520
3585
  case KameleoonData.CustomData: {
3521
3586
  this.updateCustomData({
@@ -3870,13 +3935,17 @@ class DataProcessor {
3870
3935
  return closestCleanupTime;
3871
3936
  }
3872
3937
  updateField({ key, value, data, visitorCode, expirationTime, }) {
3873
- data[visitorCode][key] = Object.assign(Object.assign({}, value), { expirationTime });
3938
+ var _a;
3939
+ const existing = data[visitorCode][key];
3940
+ 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() });
3874
3941
  }
3875
3942
  createField({ key, value, data, visitorCode, expirationTime, }) {
3876
3943
  data[visitorCode] = Object.assign(Object.assign({}, data[visitorCode]), { [key]: Object.assign(Object.assign({}, value), { expirationTime }) });
3877
3944
  }
3878
3945
  updateNestedField({ key, nestedKey, value, data, visitorCode, expirationTime, }) {
3879
- data[visitorCode][key][nestedKey] = Object.assign(Object.assign({}, value), { expirationTime });
3946
+ var _a;
3947
+ const existing = data[visitorCode][key][nestedKey];
3948
+ 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() });
3880
3949
  }
3881
3950
  createNestedField({ key, nestedKey, value, data, visitorCode, expirationTime, }) {
3882
3951
  var _a;
@@ -5142,15 +5211,7 @@ var KameleoonStorageKey;
5142
5211
  KameleoonStorageKey["ForcedExperimentVariation"] = "kameleoonForcedExperimentVariation";
5143
5212
  })(KameleoonStorageKey || (KameleoonStorageKey = {}));
5144
5213
  const DEFAULT_CLIENT_CONFIGURATION = {
5145
- data: {
5146
- customData: [],
5147
- featureFlags: [],
5148
- configuration: {
5149
- realTimeUpdate: false,
5150
- consentType: ConsentType.NotRequired,
5151
- dataApiDomain: 'data.kameleoon.io',
5152
- },
5153
- },
5214
+ data: DEFAULT_DATA_FILE_CONFIGURATION,
5154
5215
  lastUpdate: '',
5155
5216
  };
5156
5217
  // --- Note ---
@@ -5662,7 +5723,7 @@ class DataManager {
5662
5723
  return resultData;
5663
5724
  }
5664
5725
  storeTrackedData(data) {
5665
- this.storeData(data);
5726
+ this.storeData(data, false);
5666
5727
  const infoResult = this.infoStorage.read();
5667
5728
  if (!infoResult.ok) {
5668
5729
  return;
@@ -5714,15 +5775,18 @@ class DataManager {
5714
5775
  targetingData,
5715
5776
  visitorCode: firstParameter,
5716
5777
  kameleoonData: secondParameter,
5778
+ extendTtl: true,
5717
5779
  });
5718
5780
  }
5719
5781
  else {
5720
5782
  for (const [visitorCode, kameleoonData] of Object.entries(firstParameter)) {
5783
+ const extendTtl = typeof secondParameter[0] === 'boolean' ? secondParameter[0] : true;
5721
5784
  this.mutUpdateTargetingData({
5722
5785
  infoData,
5723
5786
  targetingData,
5724
5787
  visitorCode,
5725
5788
  kameleoonData,
5789
+ extendTtl,
5726
5790
  });
5727
5791
  }
5728
5792
  }
@@ -5839,8 +5903,10 @@ class DataManager {
5839
5903
  }
5840
5904
  return null;
5841
5905
  }
5842
- mutUpdateTargetingData({ infoData, visitorCode, kameleoonData, targetingData, }) {
5906
+ mutUpdateTargetingData({ infoData, visitorCode, kameleoonData, targetingData, extendTtl, }) {
5907
+ var _a, _b, _c;
5843
5908
  for (const dataItem of kameleoonData) {
5909
+ // process custom data
5844
5910
  if (dataItem.data.type === KameleoonData.CustomData) {
5845
5911
  const customDataIsValid = this.processCustomData({
5846
5912
  infoData,
@@ -5852,14 +5918,23 @@ class DataManager {
5852
5918
  continue;
5853
5919
  }
5854
5920
  }
5921
+ // process metadata of conversions
5922
+ if (dataItem.data.type === KameleoonData.Conversion) {
5923
+ const conversion = dataItem;
5924
+ if (((_b = (_a = conversion._metadata) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0) > 0) {
5925
+ conversion._metadata = (_c = conversion._metadata) === null || _c === void 0 ? void 0 : _c.filter((item) => this.trySetCustomDataIndexByName(item));
5926
+ }
5927
+ }
5855
5928
  const expirationTime = this.dataProcessor.mutUpdateData({
5856
5929
  infoData,
5857
5930
  visitorCode,
5858
5931
  mutData: targetingData,
5859
5932
  dataItem,
5933
+ extendTtl,
5860
5934
  });
5861
5935
  const nextCleanup = infoData.nextDataCleanup;
5862
- if (!nextCleanup || (nextCleanup && expirationTime < nextCleanup)) {
5936
+ if (!nextCleanup ||
5937
+ (nextCleanup && expirationTime && expirationTime < nextCleanup)) {
5863
5938
  infoData.nextDataCleanup = expirationTime;
5864
5939
  }
5865
5940
  if (dataItem.data.status === TrackingStatus.Unsent) {
@@ -5891,13 +5966,11 @@ class DataManager {
5891
5966
  var _a;
5892
5967
  const { data } = customData;
5893
5968
  const isDataValid = Boolean(data.value.length && data.value[0].length);
5894
- if (data.name) {
5895
- const cdIndex = this.customDataIndexByName.get(data.name);
5896
- if (cdIndex === undefined) {
5897
- return false;
5898
- }
5899
- data.index = cdIndex;
5900
- customData._index = cdIndex;
5969
+ if (!this.trySetCustomDataIndexByName(customData)) {
5970
+ return false;
5971
+ }
5972
+ if (data.index == CustomData$1.UNDEFINED_INDEX) {
5973
+ data.index = customData.index;
5901
5974
  }
5902
5975
  if (this.mappingIdentifierCustomDataIndex === data.index && isDataValid) {
5903
5976
  customData._isMappingIdentifier = true;
@@ -5924,6 +5997,17 @@ class DataManager {
5924
5997
  }
5925
5998
  return true;
5926
5999
  }
6000
+ trySetCustomDataIndexByName(customData) {
6001
+ if (customData.index !== CustomData$1.UNDEFINED_INDEX)
6002
+ return true;
6003
+ if (!customData.name)
6004
+ return false;
6005
+ const cdIndex = this.customDataIndexByName.get(customData.name);
6006
+ if (cdIndex == null)
6007
+ return false;
6008
+ customData.index = cdIndex;
6009
+ return true;
6010
+ }
5927
6011
  get unsentDataVisitors() {
5928
6012
  const infoResult = this.infoStorage.read();
5929
6013
  if (!infoResult.ok) {
@@ -7026,6 +7110,13 @@ class Hasher {
7026
7110
  }
7027
7111
  }
7028
7112
 
7113
+ var LegalConsent;
7114
+ (function (LegalConsent) {
7115
+ LegalConsent[LegalConsent["Unknown"] = 0] = "Unknown";
7116
+ LegalConsent[LegalConsent["Given"] = 1] = "Given";
7117
+ LegalConsent[LegalConsent["NotGiven"] = 2] = "NotGiven";
7118
+ })(LegalConsent || (LegalConsent = {}));
7119
+
7029
7120
  class VariationConfiguration {
7030
7121
  constructor(externalStorage, externalStorageForcedExperimentVariations, externalStorageForcedFeatureVariations, visitorCodeManager, clientConfiguration) {
7031
7122
  this.storage = externalStorage;
@@ -7084,9 +7175,12 @@ class VariationConfiguration {
7084
7175
  }
7085
7176
  return buildExports.Ok(featureFlagVariations);
7086
7177
  }
7087
- getVariation({ visitorCode, visitorIdentifier, featureFlag, targetingData, packageInfo, clientConfiguration, dataManager, track = true, withAssignment = false, }) {
7178
+ getVariation({ visitorCode, visitorIdentifier, featureFlag, targetingData, packageInfo, clientConfiguration, dataManager, legalConsent, track = true, withAssignment = false, }) {
7088
7179
  KameleoonLogger.debug `CALL: VariationConfiguration.getVariation(visitorCode: ${visitorCode}, visitorIdentifier: ${visitorIdentifier}, featureFlag: ${featureFlag}, targetingData: ${targetingData}, packageInfo: ${packageInfo}, clientConfiguration, dataManager, withAssignment: ${withAssignment})`;
7089
7180
  const { rules, featureKey, id: featureFlagId, defaultVariationKey, } = featureFlag;
7181
+ const consent = clientConfiguration.isConsentRequired
7182
+ ? legalConsent
7183
+ : LegalConsent.Given;
7090
7184
  for (const rule of rules) {
7091
7185
  const { segment, experimentId, id, exposition, respoolTime, variationByExposition, } = rule;
7092
7186
  const forcedVariationData = this.getForcedExperimentVariation(visitorCode, rule.experimentId);
@@ -7133,6 +7227,16 @@ class VariationConfiguration {
7133
7227
  });
7134
7228
  KameleoonLogger.debug `Calculated ruleHash: ${ruleHash} for code: ${visitorIdentifier}`;
7135
7229
  if (ruleHash <= exposition) {
7230
+ // Checking if the evaluation is blocked due to the consent policy
7231
+ if (consent == LegalConsent.NotGiven &&
7232
+ rule.type == RuleType.EXPERIMENTATION) {
7233
+ const behaviour = clientConfiguration.consentBlockingBehaviour;
7234
+ if (behaviour == ConsentBlockingBehaviour.PartiallyBlocked) {
7235
+ break;
7236
+ }
7237
+ return buildExports.Err(new KameleoonError(KameleoonException.FeatureFlagEnvironmentDisabled, `Evaluation of ${rule} is blocked because consent is not provided for visitor '${visitorCode}'`));
7238
+ }
7239
+ // evaluate with CB scores if applicable
7136
7240
  let variation = this.evaluateCBScores(rule, visitorIdentifier, targetingData);
7137
7241
  if (!variation) {
7138
7242
  const variationHash = Hasher.getHashDouble({
@@ -7494,7 +7598,6 @@ class KameleoonEventSource {
7494
7598
  const VISITOR_CODE_LENGTH = 16;
7495
7599
  const VISITOR_CODE_MAX_LENGTH = 255;
7496
7600
  const DEFAULT_MAX_AGE = 60 * 60 * 24 * 365;
7497
- const ZERO_MAX_AGE = 0;
7498
7601
  const PATH = '/';
7499
7602
 
7500
7603
  /**
@@ -7962,7 +8065,7 @@ class Tracker {
7962
8065
  this.dataManager.storeTrackedData(updatedData);
7963
8066
  }
7964
8067
  else {
7965
- this.dataManager.storeData(updatedData);
8068
+ this.dataManager.storeData(updatedData, false);
7966
8069
  }
7967
8070
  }
7968
8071
  getUnsentVisitorData(visitorCode, isConsentProvided) {
@@ -8314,16 +8417,23 @@ class KameleoonClient {
8314
8417
  const variations = new Map();
8315
8418
  const featureFlags = this.clientConfiguration.featureFlags;
8316
8419
  for (const featureFlag of featureFlags.values()) {
8317
- const variation = this._getFeatureVariation({
8318
- visitorCode,
8319
- featureKey: featureFlag.featureKey,
8320
- track,
8321
- });
8322
- if (variation.ok) {
8323
- if (!onlyActive || variation.data.key !== OFF_VARIATION_KEY) {
8420
+ try {
8421
+ const variation = this._getFeatureVariation({
8422
+ visitorCode,
8423
+ featureKey: featureFlag.featureKey,
8424
+ track,
8425
+ });
8426
+ if (variation.ok &&
8427
+ (!onlyActive || variation.data.key !== OFF_VARIATION_KEY)) {
8324
8428
  variations.set(featureFlag.featureKey, variation.data);
8325
8429
  }
8326
8430
  }
8431
+ catch (err) {
8432
+ if (err instanceof KameleoonError &&
8433
+ err.type !== KameleoonException.FeatureFlagEnvironmentDisabled) {
8434
+ throw err;
8435
+ }
8436
+ }
8327
8437
  }
8328
8438
  KameleoonLogger.info `RETURN: KameleoonClient.getVariations(visitorCode: ${visitorCode}, onlyActive: ${onlyActive}, track: ${track}) -> (variations: ${variations})`;
8329
8439
  return variations;
@@ -8643,25 +8753,16 @@ class KameleoonClient {
8643
8753
  path: PATH,
8644
8754
  });
8645
8755
  }
8646
- else {
8647
- if (this.visitorCodeManager.consentRequired) {
8648
- setData({
8649
- visitorCode: '',
8650
- key: KameleoonStorageKey.VisitorCode,
8651
- maxAge: ZERO_MAX_AGE,
8652
- path: PATH,
8653
- });
8654
- }
8655
- }
8656
8756
  KameleoonLogger.info `RETURN: KameleoonClient.setUserConsent(visitorCode: ${visitorCode}, consent: ${consent}, setData: ${setData})`;
8657
8757
  }
8658
8758
  updateConsentData(visitorCode, consent) {
8659
8759
  const readResult = this.consentDataStorage.read();
8760
+ const legalConsent = consent ? LegalConsent.Given : LegalConsent.NotGiven;
8660
8761
  if (!readResult.ok) {
8661
8762
  if (readResult.error.type === KameleoonException.StorageEmpty) {
8662
8763
  this.consentDataStorage.write({
8663
8764
  [visitorCode]: {
8664
- consent,
8765
+ consent: legalConsent,
8665
8766
  },
8666
8767
  });
8667
8768
  }
@@ -8669,28 +8770,36 @@ class KameleoonClient {
8669
8770
  }
8670
8771
  const data = readResult.data;
8671
8772
  data[visitorCode] = {
8672
- consent,
8773
+ consent: legalConsent,
8673
8774
  };
8674
8775
  this.consentDataStorage.write(data);
8675
8776
  }
8777
+ getLegalConsent(visitorCode) {
8778
+ KameleoonLogger.debug `CALL: KameleoonClient.getLegalConsent(visitorCode: ${visitorCode})`;
8779
+ let legalConsent;
8780
+ const consentDataResult = this.consentDataStorage.read();
8781
+ legalConsent = consentDataResult.ok
8782
+ ? this.extractLegalConsent(consentDataResult.data[visitorCode])
8783
+ : LegalConsent.Unknown;
8784
+ KameleoonLogger.debug `RETURN: KameleoonClient.getLegalConsent(visitorCode: ${visitorCode}) -> (legalConsent: ${legalConsent})`;
8785
+ return legalConsent;
8786
+ }
8787
+ extractLegalConsent(consentData) {
8788
+ if (consentData === undefined)
8789
+ return LegalConsent.Unknown;
8790
+ if (typeof consentData === 'boolean') {
8791
+ return consentData ? LegalConsent.Given : LegalConsent.NotGiven;
8792
+ }
8793
+ const value = consentData.consent;
8794
+ if (typeof value === 'boolean')
8795
+ return value ? LegalConsent.Given : LegalConsent.NotGiven;
8796
+ return value;
8797
+ }
8676
8798
  _isConsentProvided(visitorCode) {
8677
8799
  KameleoonLogger.debug `CALL: KameleoonClient._isConsentProvided(visitorCode: ${visitorCode})`;
8678
8800
  const { isConsentRequired } = this.clientConfiguration;
8679
- const consentDataResult = this.consentDataStorage.read();
8680
- let isConsentProvided = false;
8681
- if (!isConsentRequired) {
8682
- isConsentProvided = true;
8683
- }
8684
- else if (consentDataResult.ok) {
8685
- const consentData = consentDataResult.data[visitorCode];
8686
- // for consistency with the old data in the storage
8687
- if (typeof consentData === 'boolean') {
8688
- isConsentProvided = consentData;
8689
- }
8690
- else {
8691
- isConsentProvided = consentData && consentData.consent;
8692
- }
8693
- }
8801
+ const isConsentProvided = !isConsentRequired ||
8802
+ this.getLegalConsent(visitorCode) == LegalConsent.Given;
8694
8803
  KameleoonLogger.debug `RETURN: KameleoonClient._isConsentProvided(visitorCode: ${visitorCode}) -> (isConsentProvided: ${isConsentProvided})`;
8695
8804
  return isConsentProvided;
8696
8805
  }
@@ -8718,22 +8827,32 @@ class KameleoonClient {
8718
8827
  if (!featureFlag.environmentEnabled) {
8719
8828
  continue;
8720
8829
  }
8721
- const evalExp = this._evaluate({
8722
- visitorCode,
8723
- featureFlag,
8724
- track: false,
8725
- save: false,
8726
- });
8727
- if (evalExp.variationKey !== OFF_VARIATION_KEY) {
8728
- activeVariations.push({
8729
- variationKey: evalExp.variationKey,
8730
- variationId: evalExp.variationId,
8731
- experimentId: evalExp.experimentId,
8732
- featureFlagId: featureFlag.id,
8733
- featureKey: featureFlag.featureKey,
8734
- rule: null,
8735
- isTargetedRule: evalExp.ruleType === RuleType.TARGETED_DELIVERY,
8830
+ try {
8831
+ const evalExp = this._evaluate({
8832
+ visitorCode,
8833
+ featureFlag,
8834
+ track: false,
8835
+ save: false,
8736
8836
  });
8837
+ if (evalExp.variationKey !== OFF_VARIATION_KEY) {
8838
+ activeVariations.push({
8839
+ variationKey: evalExp.variationKey,
8840
+ variationId: evalExp.variationId,
8841
+ experimentId: evalExp.experimentId,
8842
+ featureFlagId: featureFlag.id,
8843
+ featureKey: featureFlag.featureKey,
8844
+ rule: null,
8845
+ isTargetedRule: evalExp.ruleType === RuleType.TARGETED_DELIVERY,
8846
+ });
8847
+ }
8848
+ }
8849
+ catch (err) {
8850
+ if (err instanceof KameleoonError) {
8851
+ if (err.type != KameleoonException.FeatureFlagEnvironmentDisabled) {
8852
+ KameleoonLogger.error `Unexpected error: ${err}`;
8853
+ }
8854
+ continue;
8855
+ }
8737
8856
  }
8738
8857
  }
8739
8858
  KameleoonLogger.debug `RETURN: KameleoonClient._getActiveFeatureVariations(visitorCode: ${visitorCode}) -> (activeVariations: ${activeVariations})`;
@@ -8754,6 +8873,7 @@ class KameleoonClient {
8754
8873
  else if (this._isVisitorNotInHoldout(visitorCode, track, save, featureFlag, visitorData) &&
8755
8874
  this._isFFUnrestrictedByMEGroup(visitorCode, featureFlag, visitorData)) {
8756
8875
  const visitorIdentifier = this._getCodeForHash(visitorCode, featureFlag.bucketingCustomDataIndex, visitorData);
8876
+ const legalConsent = this.getLegalConsent(visitorCode);
8757
8877
  const variationData = this.variationConfiguration
8758
8878
  .getVariation({
8759
8879
  visitorCode,
@@ -8765,6 +8885,7 @@ class KameleoonClient {
8765
8885
  clientConfiguration: this.clientConfiguration,
8766
8886
  dataManager: this.dataManager,
8767
8887
  packageInfo: this.externalPackageInfo,
8888
+ legalConsent,
8768
8889
  })
8769
8890
  .throw();
8770
8891
  evalExp =
@@ -8883,6 +9004,14 @@ class KameleoonClient {
8883
9004
  }
8884
9005
  KameleoonLogger.debug `CALL: KameleoonClient._isVisitorNotInHoldout(visitorCode: ${visitorCode}, track: ${track}, save: ${save}, featureFlag: ${featureFlag}, visitorData: ${visitorData})`;
8885
9006
  let isNotInHoldout = true;
9007
+ // Checking if the evaluation is blocked due to the consent policy
9008
+ const legalConsent = this.getLegalConsent(visitorCode);
9009
+ if (legalConsent == LegalConsent.NotGiven) {
9010
+ const behaviour = this.clientConfiguration.consentBlockingBehaviour;
9011
+ if (behaviour == ConsentBlockingBehaviour.CompletelyBlocked) {
9012
+ throw new KameleoonError(KameleoonException.FeatureFlagEnvironmentDisabled, `Evaluation of holdout is blocked because consent is not provided for visitor '${visitorCode}'`);
9013
+ }
9014
+ }
8886
9015
  const codeForHash = this._getCodeForHash(visitorCode, featureFlag === null || featureFlag === void 0 ? void 0 : featureFlag.bucketingCustomDataIndex, visitorData);
8887
9016
  const holdoutHash = Hasher.getHashDouble({
8888
9017
  visitorIdentifier: codeForHash,