@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.
@@ -247,10 +247,17 @@ class KameleoonError extends Error {
247
247
  this.message = ERROR_MESSAGES[type](secondParam, thirdParam);
248
248
  break;
249
249
  case KameleoonException.FeatureFlagVariationNotFound:
250
- case KameleoonException.FeatureFlagEnvironmentDisabled:
251
250
  case KameleoonException.FeatureFlagVariableNotFound:
252
251
  this.message = ERROR_MESSAGES[type](secondParam, thirdParam);
253
252
  break;
253
+ case KameleoonException.FeatureFlagEnvironmentDisabled:
254
+ if (thirdParam !== undefined) {
255
+ this.message = ERROR_MESSAGES[type](secondParam, thirdParam);
256
+ }
257
+ else {
258
+ this.message = secondParam;
259
+ }
260
+ break;
254
261
  case KameleoonException.StorageWrite:
255
262
  case KameleoonException.JSONParse:
256
263
  this.message = ERROR_MESSAGES[type](secondParam);
@@ -306,7 +313,7 @@ var RequestType;
306
313
  RequestType["RemoteData"] = "remoteData";
307
314
  })(RequestType || (RequestType = {}));
308
315
 
309
- const NUMBER_OF_RETRIES = 2;
316
+ const NUMBER_OF_RETRIES = 1;
310
317
  var Header;
311
318
  (function (Header) {
312
319
  Header["UserAgent"] = "User-Agent";
@@ -885,6 +892,9 @@ class UrlProvider {
885
892
  const currentDomain = this.domains[UrlType.DataApi];
886
893
  this.domains[UrlType.DataApi] = currentDomain.replace(/^[^.]+/, subDomain);
887
894
  }
895
+ get dataApiDomain() {
896
+ return this.domains[UrlType.DataApi];
897
+ }
888
898
  getClientConfigurationUrl(timeStamp) {
889
899
  this.isInitialized();
890
900
  const baseUrl = `https://${this.domains[UrlType.ClientConfiguration]}/v3/`;
@@ -1144,6 +1154,11 @@ var ConsentType;
1144
1154
  ConsentType["Required"] = "REQUIRED";
1145
1155
  ConsentType["NotRequired"] = "NOT_REQUIRED";
1146
1156
  })(ConsentType || (ConsentType = {}));
1157
+ var ConsentBlockingBehaviour;
1158
+ (function (ConsentBlockingBehaviour) {
1159
+ ConsentBlockingBehaviour["PartiallyBlocked"] = "PARTIALLY_BLOCK";
1160
+ ConsentBlockingBehaviour["CompletelyBlocked"] = "FULLY_BLOCK";
1161
+ })(ConsentBlockingBehaviour || (ConsentBlockingBehaviour = {}));
1147
1162
 
1148
1163
  class EventManager {
1149
1164
  addEventHandler(eventType, callback) {
@@ -1171,17 +1186,14 @@ var EventType;
1171
1186
  EventType["ConfigurationUpdate"] = "configurationUpdate";
1172
1187
  })(EventType || (EventType = {}));
1173
1188
 
1174
- ({
1175
- configuration: {
1176
- consentType: ConsentType.NotRequired},
1177
- });
1178
- const DEFAULT_CLIENT_CONFIGURATION$1 = {
1189
+ const DEFAULT_DATA_FILE_CONFIGURATION = {
1179
1190
  customData: [],
1180
1191
  featureFlags: [],
1181
1192
  configuration: {
1182
1193
  realTimeUpdate: false,
1183
1194
  consentType: ConsentType.NotRequired,
1184
1195
  dataApiDomain: 'data.kameleoon.io',
1196
+ consentOptOutBehavior: ConsentBlockingBehaviour.PartiallyBlocked,
1185
1197
  },
1186
1198
  };
1187
1199
 
@@ -1205,7 +1217,7 @@ class ClientConfiguration {
1205
1217
  constructor({ updateInterval, urlProvider, storage, requester, dataManager, eventSource, externalVisitorCodeManager, eventManager, externalPackageInfo, defaultDataFile, }) {
1206
1218
  this.updateConfigurationIntervalId = null;
1207
1219
  this.updateType = UpdateType.Polling;
1208
- this.configurationData = DEFAULT_CLIENT_CONFIGURATION$1;
1220
+ this.configurationData = DEFAULT_DATA_FILE_CONFIGURATION;
1209
1221
  this.featureFlagsData = new Map();
1210
1222
  this.isTargetedDeliveryRule = null;
1211
1223
  this.segmentsData = null;
@@ -1214,6 +1226,7 @@ class ClientConfiguration {
1214
1226
  this.mappedRules = null;
1215
1227
  this.mappedExperiments = null;
1216
1228
  this.usedDefaultDataFile = false;
1229
+ this.blockingBehaviourMode = ConsentBlockingBehaviour.PartiallyBlocked;
1217
1230
  this.CACHE_REVALIDATE_PERIOD = 90 * Milliseconds.Minute;
1218
1231
  this.urlProvider = urlProvider;
1219
1232
  this.requester = requester;
@@ -1388,6 +1401,9 @@ class ClientConfiguration {
1388
1401
  get isConsentRequired() {
1389
1402
  return this.configuration.consentType === ConsentType.Required;
1390
1403
  }
1404
+ get consentBlockingBehaviour() {
1405
+ return this.blockingBehaviourMode;
1406
+ }
1391
1407
  get hasAnyTargetedDeliveryRule() {
1392
1408
  if (this.isTargetedDeliveryRule !== null) {
1393
1409
  return this.isTargetedDeliveryRule;
@@ -1516,6 +1532,7 @@ class ClientConfiguration {
1516
1532
  KameleoonLogger.info `Configuration update type was toggled to ${UpdateType[updateType]}`;
1517
1533
  yield this.initialize();
1518
1534
  }
1535
+ this.blockingBehaviourMode = this.consentBlockingBehaviourFromStr(clientConfigurationData.configuration.consentOptOutBehavior);
1519
1536
  return buildExports.Ok(toggleUpdateType);
1520
1537
  });
1521
1538
  }
@@ -1622,6 +1639,14 @@ class ClientConfiguration {
1622
1639
  this.visitorCodeManager.consentRequired =
1623
1640
  this.isConsentRequired && !this.hasAnyTargetedDeliveryRule;
1624
1641
  }
1642
+ consentBlockingBehaviourFromStr(str) {
1643
+ if (str === ConsentBlockingBehaviour.PartiallyBlocked ||
1644
+ str === ConsentBlockingBehaviour.CompletelyBlocked) {
1645
+ return str;
1646
+ }
1647
+ KameleoonLogger.error(`Unexpected consent blocking type '${str}'`);
1648
+ return ConsentBlockingBehaviour.PartiallyBlocked;
1649
+ }
1625
1650
  }
1626
1651
 
1627
1652
  function constructTypeMap(indexMap) {
@@ -2300,11 +2325,11 @@ let CustomData$1 = class CustomData {
2300
2325
  const isNumber = typeof first === 'number';
2301
2326
  const isBoolean = typeof second === 'boolean';
2302
2327
  if (isNumber) {
2303
- this.index = first;
2328
+ this._index = first;
2304
2329
  }
2305
2330
  else {
2306
- this.name = first;
2307
- this.index = -1;
2331
+ this._name = first;
2332
+ this._index = CustomData.UNDEFINED_INDEX;
2308
2333
  }
2309
2334
  this.overwrite = isBoolean ? second : true;
2310
2335
  this.value = isBoolean
@@ -2315,7 +2340,7 @@ let CustomData$1 = class CustomData {
2315
2340
  // --- Note ---
2316
2341
  // If SDK is used in vanilla JS codebase, then you're also able to create an instance
2317
2342
  // with no required data, we don't want send anything to tracking in that case
2318
- if (typeof this.index !== 'number') {
2343
+ if (typeof this._index !== 'number') {
2319
2344
  return '';
2320
2345
  }
2321
2346
  const uniqueValues = [...new Set(this.value)];
@@ -2332,7 +2357,7 @@ let CustomData$1 = class CustomData {
2332
2357
  }
2333
2358
  return (UrlEventType.CustomData +
2334
2359
  UrlParameter.Index +
2335
- this.index +
2360
+ this._index +
2336
2361
  UrlParameter.ValuesCountMap +
2337
2362
  encodeURIComponent(JSON.stringify(resultValue)) +
2338
2363
  UrlParameter.Overwrite +
@@ -2340,7 +2365,7 @@ let CustomData$1 = class CustomData {
2340
2365
  identifierParameter);
2341
2366
  }
2342
2367
  get data() {
2343
- 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 });
2368
+ 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 });
2344
2369
  }
2345
2370
  get status() {
2346
2371
  if (this._isMappingIdentifier) {
@@ -2359,7 +2384,7 @@ let CustomData$1 = class CustomData {
2359
2384
  let customData;
2360
2385
  if (name) {
2361
2386
  customData = new CustomData(name, overwrite !== null && overwrite !== void 0 ? overwrite : true, ...value);
2362
- customData._index = index;
2387
+ customData.index = index;
2363
2388
  }
2364
2389
  else {
2365
2390
  customData = new CustomData(index, overwrite !== null && overwrite !== void 0 ? overwrite : true, ...value);
@@ -2396,13 +2421,35 @@ let CustomData$1 = class CustomData {
2396
2421
  }
2397
2422
  /**
2398
2423
  * @private
2399
- * @method _index - an internal setter for setting index of custom data
2424
+ * @method index - an internal setter for setting index of custom data
2400
2425
  * @param {number} value - an index value
2401
2426
  * */
2402
- set _index(value) {
2403
- this.index = value;
2427
+ set index(value) {
2428
+ this._index = value;
2429
+ }
2430
+ /**
2431
+ * @private
2432
+ * @method index - an internal getter for index of custom data
2433
+ * */
2434
+ get index() {
2435
+ return this._index;
2436
+ }
2437
+ /**
2438
+ * @private
2439
+ * @method name - an internal getter for a name of custom data
2440
+ * */
2441
+ get name() {
2442
+ return this._name;
2443
+ }
2444
+ /**
2445
+ * @private
2446
+ * @method name - an internal getter for a name of custom data
2447
+ * */
2448
+ get values() {
2449
+ return this.value;
2404
2450
  }
2405
2451
  };
2452
+ CustomData$1.UNDEFINED_INDEX = -1;
2406
2453
 
2407
2454
  /**
2408
2455
  * @class
@@ -2418,7 +2465,7 @@ let Conversion$1 = class Conversion {
2418
2465
  this.negative = negative;
2419
2466
  this.status = TrackingStatus.Unsent;
2420
2467
  this.id = Math.floor(Math.random() * 1000000);
2421
- this.metadata = metadata;
2468
+ this._metadata = metadata;
2422
2469
  }
2423
2470
  set _id(id) {
2424
2471
  this.id = id;
@@ -2439,6 +2486,21 @@ let Conversion$1 = class Conversion {
2439
2486
  ? UrlParameter.Metadata + this._encodeMetadata()
2440
2487
  : ''));
2441
2488
  }
2489
+ /**
2490
+ * @private
2491
+ * @method metadata - an internal getter for a metadata of conversion
2492
+ * */
2493
+ get _metadata() {
2494
+ return this.metadata;
2495
+ }
2496
+ /**
2497
+ * @private
2498
+ * @method metadata - an internal setter for setting metadata of conversion
2499
+ * @param {number} value - an index value
2500
+ * */
2501
+ set _metadata(value) {
2502
+ this.metadata = value;
2503
+ }
2442
2504
  get data() {
2443
2505
  var _a;
2444
2506
  return {
@@ -3531,7 +3593,7 @@ class DataProcessor {
3531
3593
  this.cleanupInterval = cleanupInterval;
3532
3594
  this.packageInfo = packageInfo;
3533
3595
  }
3534
- mutUpdateData({ infoData, visitorCode, mutData, dataItem, }) {
3596
+ mutUpdateData({ infoData, visitorCode, mutData, dataItem, extendTtl, }) {
3535
3597
  let { visitorReference, data, isReference } = this.dereferenceData(mutData, visitorCode);
3536
3598
  if (this.packageInfo.isServer &&
3537
3599
  isReference &&
@@ -3542,9 +3604,12 @@ class DataProcessor {
3542
3604
  delete mutData[visitorCode];
3543
3605
  visitorReference = visitorCode;
3544
3606
  }
3545
- const expirationTime = this.cleanupInterval
3546
- ? Date.now() + this.cleanupInterval
3547
- : 0;
3607
+ let expirationTime;
3608
+ if (extendTtl) {
3609
+ expirationTime = this.cleanupInterval
3610
+ ? Date.now() + this.cleanupInterval
3611
+ : 0;
3612
+ }
3548
3613
  switch (dataItem.data.type) {
3549
3614
  case KameleoonData.CustomData: {
3550
3615
  this.updateCustomData({
@@ -3899,13 +3964,17 @@ class DataProcessor {
3899
3964
  return closestCleanupTime;
3900
3965
  }
3901
3966
  updateField({ key, value, data, visitorCode, expirationTime, }) {
3902
- data[visitorCode][key] = Object.assign(Object.assign({}, value), { expirationTime });
3967
+ var _a;
3968
+ const existing = data[visitorCode][key];
3969
+ 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() });
3903
3970
  }
3904
3971
  createField({ key, value, data, visitorCode, expirationTime, }) {
3905
3972
  data[visitorCode] = Object.assign(Object.assign({}, data[visitorCode]), { [key]: Object.assign(Object.assign({}, value), { expirationTime }) });
3906
3973
  }
3907
3974
  updateNestedField({ key, nestedKey, value, data, visitorCode, expirationTime, }) {
3908
- data[visitorCode][key][nestedKey] = Object.assign(Object.assign({}, value), { expirationTime });
3975
+ var _a;
3976
+ const existing = data[visitorCode][key][nestedKey];
3977
+ 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() });
3909
3978
  }
3910
3979
  createNestedField({ key, nestedKey, value, data, visitorCode, expirationTime, }) {
3911
3980
  var _a;
@@ -5171,15 +5240,7 @@ var KameleoonStorageKey;
5171
5240
  KameleoonStorageKey["ForcedExperimentVariation"] = "kameleoonForcedExperimentVariation";
5172
5241
  })(KameleoonStorageKey || (KameleoonStorageKey = {}));
5173
5242
  const DEFAULT_CLIENT_CONFIGURATION = {
5174
- data: {
5175
- customData: [],
5176
- featureFlags: [],
5177
- configuration: {
5178
- realTimeUpdate: false,
5179
- consentType: ConsentType.NotRequired,
5180
- dataApiDomain: 'data.kameleoon.io',
5181
- },
5182
- },
5243
+ data: DEFAULT_DATA_FILE_CONFIGURATION,
5183
5244
  lastUpdate: '',
5184
5245
  };
5185
5246
  // --- Note ---
@@ -5691,7 +5752,7 @@ class DataManager {
5691
5752
  return resultData;
5692
5753
  }
5693
5754
  storeTrackedData(data) {
5694
- this.storeData(data);
5755
+ this.storeData(data, false);
5695
5756
  const infoResult = this.infoStorage.read();
5696
5757
  if (!infoResult.ok) {
5697
5758
  return;
@@ -5743,15 +5804,18 @@ class DataManager {
5743
5804
  targetingData,
5744
5805
  visitorCode: firstParameter,
5745
5806
  kameleoonData: secondParameter,
5807
+ extendTtl: true,
5746
5808
  });
5747
5809
  }
5748
5810
  else {
5749
5811
  for (const [visitorCode, kameleoonData] of Object.entries(firstParameter)) {
5812
+ const extendTtl = typeof secondParameter[0] === 'boolean' ? secondParameter[0] : true;
5750
5813
  this.mutUpdateTargetingData({
5751
5814
  infoData,
5752
5815
  targetingData,
5753
5816
  visitorCode,
5754
5817
  kameleoonData,
5818
+ extendTtl,
5755
5819
  });
5756
5820
  }
5757
5821
  }
@@ -5868,8 +5932,10 @@ class DataManager {
5868
5932
  }
5869
5933
  return null;
5870
5934
  }
5871
- mutUpdateTargetingData({ infoData, visitorCode, kameleoonData, targetingData, }) {
5935
+ mutUpdateTargetingData({ infoData, visitorCode, kameleoonData, targetingData, extendTtl, }) {
5936
+ var _a, _b, _c;
5872
5937
  for (const dataItem of kameleoonData) {
5938
+ // process custom data
5873
5939
  if (dataItem.data.type === KameleoonData.CustomData) {
5874
5940
  const customDataIsValid = this.processCustomData({
5875
5941
  infoData,
@@ -5881,14 +5947,23 @@ class DataManager {
5881
5947
  continue;
5882
5948
  }
5883
5949
  }
5950
+ // process metadata of conversions
5951
+ if (dataItem.data.type === KameleoonData.Conversion) {
5952
+ const conversion = dataItem;
5953
+ if (((_b = (_a = conversion._metadata) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0) > 0) {
5954
+ conversion._metadata = (_c = conversion._metadata) === null || _c === void 0 ? void 0 : _c.filter((item) => this.trySetCustomDataIndexByName(item));
5955
+ }
5956
+ }
5884
5957
  const expirationTime = this.dataProcessor.mutUpdateData({
5885
5958
  infoData,
5886
5959
  visitorCode,
5887
5960
  mutData: targetingData,
5888
5961
  dataItem,
5962
+ extendTtl,
5889
5963
  });
5890
5964
  const nextCleanup = infoData.nextDataCleanup;
5891
- if (!nextCleanup || (nextCleanup && expirationTime < nextCleanup)) {
5965
+ if (!nextCleanup ||
5966
+ (nextCleanup && expirationTime && expirationTime < nextCleanup)) {
5892
5967
  infoData.nextDataCleanup = expirationTime;
5893
5968
  }
5894
5969
  if (dataItem.data.status === TrackingStatus.Unsent) {
@@ -5920,13 +5995,11 @@ class DataManager {
5920
5995
  var _a;
5921
5996
  const { data } = customData;
5922
5997
  const isDataValid = Boolean(data.value.length && data.value[0].length);
5923
- if (data.name) {
5924
- const cdIndex = this.customDataIndexByName.get(data.name);
5925
- if (cdIndex === undefined) {
5926
- return false;
5927
- }
5928
- data.index = cdIndex;
5929
- customData._index = cdIndex;
5998
+ if (!this.trySetCustomDataIndexByName(customData)) {
5999
+ return false;
6000
+ }
6001
+ if (data.index == CustomData$1.UNDEFINED_INDEX) {
6002
+ data.index = customData.index;
5930
6003
  }
5931
6004
  if (this.mappingIdentifierCustomDataIndex === data.index && isDataValid) {
5932
6005
  customData._isMappingIdentifier = true;
@@ -5953,6 +6026,17 @@ class DataManager {
5953
6026
  }
5954
6027
  return true;
5955
6028
  }
6029
+ trySetCustomDataIndexByName(customData) {
6030
+ if (customData.index !== CustomData$1.UNDEFINED_INDEX)
6031
+ return true;
6032
+ if (!customData.name)
6033
+ return false;
6034
+ const cdIndex = this.customDataIndexByName.get(customData.name);
6035
+ if (cdIndex == null)
6036
+ return false;
6037
+ customData.index = cdIndex;
6038
+ return true;
6039
+ }
5956
6040
  get unsentDataVisitors() {
5957
6041
  const infoResult = this.infoStorage.read();
5958
6042
  if (!infoResult.ok) {
@@ -7064,6 +7148,13 @@ class Hasher {
7064
7148
  }
7065
7149
  }
7066
7150
 
7151
+ var LegalConsent;
7152
+ (function (LegalConsent) {
7153
+ LegalConsent[LegalConsent["Unknown"] = 0] = "Unknown";
7154
+ LegalConsent[LegalConsent["Given"] = 1] = "Given";
7155
+ LegalConsent[LegalConsent["NotGiven"] = 2] = "NotGiven";
7156
+ })(LegalConsent || (LegalConsent = {}));
7157
+
7067
7158
  class VariationConfiguration {
7068
7159
  constructor(externalStorage, externalStorageForcedExperimentVariations, externalStorageForcedFeatureVariations, visitorCodeManager, clientConfiguration) {
7069
7160
  this.storage = externalStorage;
@@ -7122,9 +7213,12 @@ class VariationConfiguration {
7122
7213
  }
7123
7214
  return buildExports.Ok(featureFlagVariations);
7124
7215
  }
7125
- getVariation({ visitorCode, visitorIdentifier, featureFlag, targetingData, packageInfo, clientConfiguration, dataManager, track = true, withAssignment = false, }) {
7216
+ getVariation({ visitorCode, visitorIdentifier, featureFlag, targetingData, packageInfo, clientConfiguration, dataManager, legalConsent, track = true, withAssignment = false, }) {
7126
7217
  KameleoonLogger.debug `CALL: VariationConfiguration.getVariation(visitorCode: ${visitorCode}, visitorIdentifier: ${visitorIdentifier}, featureFlag: ${featureFlag}, targetingData: ${targetingData}, packageInfo: ${packageInfo}, clientConfiguration, dataManager, withAssignment: ${withAssignment})`;
7127
7218
  const { rules, featureKey, id: featureFlagId, defaultVariationKey, } = featureFlag;
7219
+ const consent = clientConfiguration.isConsentRequired
7220
+ ? legalConsent
7221
+ : LegalConsent.Given;
7128
7222
  for (const rule of rules) {
7129
7223
  const { segment, experimentId, id, exposition, respoolTime, variationByExposition, } = rule;
7130
7224
  const forcedVariationData = this.getForcedExperimentVariation(visitorCode, rule.experimentId);
@@ -7171,6 +7265,16 @@ class VariationConfiguration {
7171
7265
  });
7172
7266
  KameleoonLogger.debug `Calculated ruleHash: ${ruleHash} for code: ${visitorIdentifier}`;
7173
7267
  if (ruleHash <= exposition) {
7268
+ // Checking if the evaluation is blocked due to the consent policy
7269
+ if (consent == LegalConsent.NotGiven &&
7270
+ rule.type == RuleType.EXPERIMENTATION) {
7271
+ const behaviour = clientConfiguration.consentBlockingBehaviour;
7272
+ if (behaviour == ConsentBlockingBehaviour.PartiallyBlocked) {
7273
+ break;
7274
+ }
7275
+ return buildExports.Err(new KameleoonError(KameleoonException.FeatureFlagEnvironmentDisabled, `Evaluation of ${rule} is blocked because consent is not provided for visitor '${visitorCode}'`));
7276
+ }
7277
+ // evaluate with CB scores if applicable
7174
7278
  let variation = this.evaluateCBScores(rule, visitorIdentifier, targetingData);
7175
7279
  if (!variation) {
7176
7280
  const variationHash = Hasher.getHashDouble({
@@ -7532,7 +7636,6 @@ class KameleoonEventSource {
7532
7636
  const VISITOR_CODE_LENGTH = 16;
7533
7637
  const VISITOR_CODE_MAX_LENGTH = 255;
7534
7638
  const DEFAULT_MAX_AGE = 60 * 60 * 24 * 365;
7535
- const ZERO_MAX_AGE = 0;
7536
7639
  const PATH = '/';
7537
7640
 
7538
7641
  /**
@@ -8000,7 +8103,7 @@ class Tracker {
8000
8103
  this.dataManager.storeTrackedData(updatedData);
8001
8104
  }
8002
8105
  else {
8003
- this.dataManager.storeData(updatedData);
8106
+ this.dataManager.storeData(updatedData, false);
8004
8107
  }
8005
8108
  }
8006
8109
  getUnsentVisitorData(visitorCode, isConsentProvided) {
@@ -8352,16 +8455,23 @@ class KameleoonClient {
8352
8455
  const variations = new Map();
8353
8456
  const featureFlags = this.clientConfiguration.featureFlags;
8354
8457
  for (const featureFlag of featureFlags.values()) {
8355
- const variation = this._getFeatureVariation({
8356
- visitorCode,
8357
- featureKey: featureFlag.featureKey,
8358
- track,
8359
- });
8360
- if (variation.ok) {
8361
- if (!onlyActive || variation.data.key !== OFF_VARIATION_KEY) {
8458
+ try {
8459
+ const variation = this._getFeatureVariation({
8460
+ visitorCode,
8461
+ featureKey: featureFlag.featureKey,
8462
+ track,
8463
+ });
8464
+ if (variation.ok &&
8465
+ (!onlyActive || variation.data.key !== OFF_VARIATION_KEY)) {
8362
8466
  variations.set(featureFlag.featureKey, variation.data);
8363
8467
  }
8364
8468
  }
8469
+ catch (err) {
8470
+ if (err instanceof KameleoonError &&
8471
+ err.type !== KameleoonException.FeatureFlagEnvironmentDisabled) {
8472
+ throw err;
8473
+ }
8474
+ }
8365
8475
  }
8366
8476
  KameleoonLogger.info `RETURN: KameleoonClient.getVariations(visitorCode: ${visitorCode}, onlyActive: ${onlyActive}, track: ${track}) -> (variations: ${variations})`;
8367
8477
  return variations;
@@ -8681,25 +8791,16 @@ class KameleoonClient {
8681
8791
  path: PATH,
8682
8792
  });
8683
8793
  }
8684
- else {
8685
- if (this.visitorCodeManager.consentRequired) {
8686
- setData({
8687
- visitorCode: '',
8688
- key: KameleoonStorageKey.VisitorCode,
8689
- maxAge: ZERO_MAX_AGE,
8690
- path: PATH,
8691
- });
8692
- }
8693
- }
8694
8794
  KameleoonLogger.info `RETURN: KameleoonClient.setUserConsent(visitorCode: ${visitorCode}, consent: ${consent}, setData: ${setData})`;
8695
8795
  }
8696
8796
  updateConsentData(visitorCode, consent) {
8697
8797
  const readResult = this.consentDataStorage.read();
8798
+ const legalConsent = consent ? LegalConsent.Given : LegalConsent.NotGiven;
8698
8799
  if (!readResult.ok) {
8699
8800
  if (readResult.error.type === KameleoonException.StorageEmpty) {
8700
8801
  this.consentDataStorage.write({
8701
8802
  [visitorCode]: {
8702
- consent,
8803
+ consent: legalConsent,
8703
8804
  },
8704
8805
  });
8705
8806
  }
@@ -8707,28 +8808,36 @@ class KameleoonClient {
8707
8808
  }
8708
8809
  const data = readResult.data;
8709
8810
  data[visitorCode] = {
8710
- consent,
8811
+ consent: legalConsent,
8711
8812
  };
8712
8813
  this.consentDataStorage.write(data);
8713
8814
  }
8815
+ getLegalConsent(visitorCode) {
8816
+ KameleoonLogger.debug `CALL: KameleoonClient.getLegalConsent(visitorCode: ${visitorCode})`;
8817
+ let legalConsent;
8818
+ const consentDataResult = this.consentDataStorage.read();
8819
+ legalConsent = consentDataResult.ok
8820
+ ? this.extractLegalConsent(consentDataResult.data[visitorCode])
8821
+ : LegalConsent.Unknown;
8822
+ KameleoonLogger.debug `RETURN: KameleoonClient.getLegalConsent(visitorCode: ${visitorCode}) -> (legalConsent: ${legalConsent})`;
8823
+ return legalConsent;
8824
+ }
8825
+ extractLegalConsent(consentData) {
8826
+ if (consentData === undefined)
8827
+ return LegalConsent.Unknown;
8828
+ if (typeof consentData === 'boolean') {
8829
+ return consentData ? LegalConsent.Given : LegalConsent.NotGiven;
8830
+ }
8831
+ const value = consentData.consent;
8832
+ if (typeof value === 'boolean')
8833
+ return value ? LegalConsent.Given : LegalConsent.NotGiven;
8834
+ return value;
8835
+ }
8714
8836
  _isConsentProvided(visitorCode) {
8715
8837
  KameleoonLogger.debug `CALL: KameleoonClient._isConsentProvided(visitorCode: ${visitorCode})`;
8716
8838
  const { isConsentRequired } = this.clientConfiguration;
8717
- const consentDataResult = this.consentDataStorage.read();
8718
- let isConsentProvided = false;
8719
- if (!isConsentRequired) {
8720
- isConsentProvided = true;
8721
- }
8722
- else if (consentDataResult.ok) {
8723
- const consentData = consentDataResult.data[visitorCode];
8724
- // for consistency with the old data in the storage
8725
- if (typeof consentData === 'boolean') {
8726
- isConsentProvided = consentData;
8727
- }
8728
- else {
8729
- isConsentProvided = consentData && consentData.consent;
8730
- }
8731
- }
8839
+ const isConsentProvided = !isConsentRequired ||
8840
+ this.getLegalConsent(visitorCode) == LegalConsent.Given;
8732
8841
  KameleoonLogger.debug `RETURN: KameleoonClient._isConsentProvided(visitorCode: ${visitorCode}) -> (isConsentProvided: ${isConsentProvided})`;
8733
8842
  return isConsentProvided;
8734
8843
  }
@@ -8756,22 +8865,32 @@ class KameleoonClient {
8756
8865
  if (!featureFlag.environmentEnabled) {
8757
8866
  continue;
8758
8867
  }
8759
- const evalExp = this._evaluate({
8760
- visitorCode,
8761
- featureFlag,
8762
- track: false,
8763
- save: false,
8764
- });
8765
- if (evalExp.variationKey !== OFF_VARIATION_KEY) {
8766
- activeVariations.push({
8767
- variationKey: evalExp.variationKey,
8768
- variationId: evalExp.variationId,
8769
- experimentId: evalExp.experimentId,
8770
- featureFlagId: featureFlag.id,
8771
- featureKey: featureFlag.featureKey,
8772
- rule: null,
8773
- isTargetedRule: evalExp.ruleType === RuleType.TARGETED_DELIVERY,
8868
+ try {
8869
+ const evalExp = this._evaluate({
8870
+ visitorCode,
8871
+ featureFlag,
8872
+ track: false,
8873
+ save: false,
8774
8874
  });
8875
+ if (evalExp.variationKey !== OFF_VARIATION_KEY) {
8876
+ activeVariations.push({
8877
+ variationKey: evalExp.variationKey,
8878
+ variationId: evalExp.variationId,
8879
+ experimentId: evalExp.experimentId,
8880
+ featureFlagId: featureFlag.id,
8881
+ featureKey: featureFlag.featureKey,
8882
+ rule: null,
8883
+ isTargetedRule: evalExp.ruleType === RuleType.TARGETED_DELIVERY,
8884
+ });
8885
+ }
8886
+ }
8887
+ catch (err) {
8888
+ if (err instanceof KameleoonError) {
8889
+ if (err.type != KameleoonException.FeatureFlagEnvironmentDisabled) {
8890
+ KameleoonLogger.error `Unexpected error: ${err}`;
8891
+ }
8892
+ continue;
8893
+ }
8775
8894
  }
8776
8895
  }
8777
8896
  KameleoonLogger.debug `RETURN: KameleoonClient._getActiveFeatureVariations(visitorCode: ${visitorCode}) -> (activeVariations: ${activeVariations})`;
@@ -8792,6 +8911,7 @@ class KameleoonClient {
8792
8911
  else if (this._isVisitorNotInHoldout(visitorCode, track, save, featureFlag, visitorData) &&
8793
8912
  this._isFFUnrestrictedByMEGroup(visitorCode, featureFlag, visitorData)) {
8794
8913
  const visitorIdentifier = this._getCodeForHash(visitorCode, featureFlag.bucketingCustomDataIndex, visitorData);
8914
+ const legalConsent = this.getLegalConsent(visitorCode);
8795
8915
  const variationData = this.variationConfiguration
8796
8916
  .getVariation({
8797
8917
  visitorCode,
@@ -8803,6 +8923,7 @@ class KameleoonClient {
8803
8923
  clientConfiguration: this.clientConfiguration,
8804
8924
  dataManager: this.dataManager,
8805
8925
  packageInfo: this.externalPackageInfo,
8926
+ legalConsent,
8806
8927
  })
8807
8928
  .throw();
8808
8929
  evalExp =
@@ -8921,6 +9042,14 @@ class KameleoonClient {
8921
9042
  }
8922
9043
  KameleoonLogger.debug `CALL: KameleoonClient._isVisitorNotInHoldout(visitorCode: ${visitorCode}, track: ${track}, save: ${save}, featureFlag: ${featureFlag}, visitorData: ${visitorData})`;
8923
9044
  let isNotInHoldout = true;
9045
+ // Checking if the evaluation is blocked due to the consent policy
9046
+ const legalConsent = this.getLegalConsent(visitorCode);
9047
+ if (legalConsent == LegalConsent.NotGiven) {
9048
+ const behaviour = this.clientConfiguration.consentBlockingBehaviour;
9049
+ if (behaviour == ConsentBlockingBehaviour.CompletelyBlocked) {
9050
+ throw new KameleoonError(KameleoonException.FeatureFlagEnvironmentDisabled, `Evaluation of holdout is blocked because consent is not provided for visitor '${visitorCode}'`);
9051
+ }
9052
+ }
8924
9053
  const codeForHash = this._getCodeForHash(visitorCode, featureFlag === null || featureFlag === void 0 ? void 0 : featureFlag.bucketingCustomDataIndex, visitorData);
8925
9054
  const holdoutHash = Hasher.getHashDouble({
8926
9055
  visitorIdentifier: codeForHash,