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