@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.
@@ -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) {
@@ -2302,11 +2327,11 @@ let CustomData$1 = class CustomData {
2302
2327
  const isNumber = typeof first === 'number';
2303
2328
  const isBoolean = typeof second === 'boolean';
2304
2329
  if (isNumber) {
2305
- this.index = first;
2330
+ this._index = first;
2306
2331
  }
2307
2332
  else {
2308
- this.name = first;
2309
- this.index = -1;
2333
+ this._name = first;
2334
+ this._index = CustomData.UNDEFINED_INDEX;
2310
2335
  }
2311
2336
  this.overwrite = isBoolean ? second : true;
2312
2337
  this.value = isBoolean
@@ -2317,7 +2342,7 @@ let CustomData$1 = class CustomData {
2317
2342
  // --- Note ---
2318
2343
  // If SDK is used in vanilla JS codebase, then you're also able to create an instance
2319
2344
  // with no required data, we don't want send anything to tracking in that case
2320
- if (typeof this.index !== 'number') {
2345
+ if (typeof this._index !== 'number') {
2321
2346
  return '';
2322
2347
  }
2323
2348
  const uniqueValues = [...new Set(this.value)];
@@ -2334,7 +2359,7 @@ let CustomData$1 = class CustomData {
2334
2359
  }
2335
2360
  return (UrlEventType.CustomData +
2336
2361
  UrlParameter.Index +
2337
- this.index +
2362
+ this._index +
2338
2363
  UrlParameter.ValuesCountMap +
2339
2364
  encodeURIComponent(JSON.stringify(resultValue)) +
2340
2365
  UrlParameter.Overwrite +
@@ -2342,7 +2367,7 @@ let CustomData$1 = class CustomData {
2342
2367
  identifierParameter);
2343
2368
  }
2344
2369
  get data() {
2345
- 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 });
2370
+ 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 });
2346
2371
  }
2347
2372
  get status() {
2348
2373
  if (this._isMappingIdentifier) {
@@ -2361,7 +2386,7 @@ let CustomData$1 = class CustomData {
2361
2386
  let customData;
2362
2387
  if (name) {
2363
2388
  customData = new CustomData(name, overwrite !== null && overwrite !== void 0 ? overwrite : true, ...value);
2364
- customData._index = index;
2389
+ customData.index = index;
2365
2390
  }
2366
2391
  else {
2367
2392
  customData = new CustomData(index, overwrite !== null && overwrite !== void 0 ? overwrite : true, ...value);
@@ -2398,13 +2423,35 @@ let CustomData$1 = class CustomData {
2398
2423
  }
2399
2424
  /**
2400
2425
  * @private
2401
- * @method _index - an internal setter for setting index of custom data
2426
+ * @method index - an internal setter for setting index of custom data
2402
2427
  * @param {number} value - an index value
2403
2428
  * */
2404
- set _index(value) {
2405
- this.index = value;
2429
+ set index(value) {
2430
+ this._index = value;
2431
+ }
2432
+ /**
2433
+ * @private
2434
+ * @method index - an internal getter for index of custom data
2435
+ * */
2436
+ get index() {
2437
+ return this._index;
2438
+ }
2439
+ /**
2440
+ * @private
2441
+ * @method name - an internal getter for a name of custom data
2442
+ * */
2443
+ get name() {
2444
+ return this._name;
2445
+ }
2446
+ /**
2447
+ * @private
2448
+ * @method name - an internal getter for a name of custom data
2449
+ * */
2450
+ get values() {
2451
+ return this.value;
2406
2452
  }
2407
2453
  };
2454
+ CustomData$1.UNDEFINED_INDEX = -1;
2408
2455
 
2409
2456
  /**
2410
2457
  * @class
@@ -2420,7 +2467,7 @@ let Conversion$1 = class Conversion {
2420
2467
  this.negative = negative;
2421
2468
  this.status = exports.TrackingStatus.Unsent;
2422
2469
  this.id = Math.floor(Math.random() * 1000000);
2423
- this.metadata = metadata;
2470
+ this._metadata = metadata;
2424
2471
  }
2425
2472
  set _id(id) {
2426
2473
  this.id = id;
@@ -2441,6 +2488,21 @@ let Conversion$1 = class Conversion {
2441
2488
  ? UrlParameter.Metadata + this._encodeMetadata()
2442
2489
  : ''));
2443
2490
  }
2491
+ /**
2492
+ * @private
2493
+ * @method metadata - an internal getter for a metadata of conversion
2494
+ * */
2495
+ get _metadata() {
2496
+ return this.metadata;
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
+ }
2444
2506
  get data() {
2445
2507
  var _a;
2446
2508
  return {
@@ -3533,7 +3595,7 @@ class DataProcessor {
3533
3595
  this.cleanupInterval = cleanupInterval;
3534
3596
  this.packageInfo = packageInfo;
3535
3597
  }
3536
- mutUpdateData({ infoData, visitorCode, mutData, dataItem, }) {
3598
+ mutUpdateData({ infoData, visitorCode, mutData, dataItem, extendTtl, }) {
3537
3599
  let { visitorReference, data, isReference } = this.dereferenceData(mutData, visitorCode);
3538
3600
  if (this.packageInfo.isServer &&
3539
3601
  isReference &&
@@ -3544,9 +3606,12 @@ class DataProcessor {
3544
3606
  delete mutData[visitorCode];
3545
3607
  visitorReference = visitorCode;
3546
3608
  }
3547
- const expirationTime = this.cleanupInterval
3548
- ? Date.now() + this.cleanupInterval
3549
- : 0;
3609
+ let expirationTime;
3610
+ if (extendTtl) {
3611
+ expirationTime = this.cleanupInterval
3612
+ ? Date.now() + this.cleanupInterval
3613
+ : 0;
3614
+ }
3550
3615
  switch (dataItem.data.type) {
3551
3616
  case exports.KameleoonData.CustomData: {
3552
3617
  this.updateCustomData({
@@ -3901,13 +3966,17 @@ class DataProcessor {
3901
3966
  return closestCleanupTime;
3902
3967
  }
3903
3968
  updateField({ key, value, data, visitorCode, expirationTime, }) {
3904
- 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() });
3905
3972
  }
3906
3973
  createField({ key, value, data, visitorCode, expirationTime, }) {
3907
3974
  data[visitorCode] = Object.assign(Object.assign({}, data[visitorCode]), { [key]: Object.assign(Object.assign({}, value), { expirationTime }) });
3908
3975
  }
3909
3976
  updateNestedField({ key, nestedKey, value, data, visitorCode, expirationTime, }) {
3910
- 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() });
3911
3980
  }
3912
3981
  createNestedField({ key, nestedKey, value, data, visitorCode, expirationTime, }) {
3913
3982
  var _a;
@@ -5173,15 +5242,7 @@ exports.KameleoonStorageKey = void 0;
5173
5242
  KameleoonStorageKey["ForcedExperimentVariation"] = "kameleoonForcedExperimentVariation";
5174
5243
  })(exports.KameleoonStorageKey || (exports.KameleoonStorageKey = {}));
5175
5244
  const DEFAULT_CLIENT_CONFIGURATION = {
5176
- data: {
5177
- customData: [],
5178
- featureFlags: [],
5179
- configuration: {
5180
- realTimeUpdate: false,
5181
- consentType: ConsentType.NotRequired,
5182
- dataApiDomain: 'data.kameleoon.io',
5183
- },
5184
- },
5245
+ data: DEFAULT_DATA_FILE_CONFIGURATION,
5185
5246
  lastUpdate: '',
5186
5247
  };
5187
5248
  // --- Note ---
@@ -5693,7 +5754,7 @@ class DataManager {
5693
5754
  return resultData;
5694
5755
  }
5695
5756
  storeTrackedData(data) {
5696
- this.storeData(data);
5757
+ this.storeData(data, false);
5697
5758
  const infoResult = this.infoStorage.read();
5698
5759
  if (!infoResult.ok) {
5699
5760
  return;
@@ -5745,15 +5806,18 @@ class DataManager {
5745
5806
  targetingData,
5746
5807
  visitorCode: firstParameter,
5747
5808
  kameleoonData: secondParameter,
5809
+ extendTtl: true,
5748
5810
  });
5749
5811
  }
5750
5812
  else {
5751
5813
  for (const [visitorCode, kameleoonData] of Object.entries(firstParameter)) {
5814
+ const extendTtl = typeof secondParameter[0] === 'boolean' ? secondParameter[0] : true;
5752
5815
  this.mutUpdateTargetingData({
5753
5816
  infoData,
5754
5817
  targetingData,
5755
5818
  visitorCode,
5756
5819
  kameleoonData,
5820
+ extendTtl,
5757
5821
  });
5758
5822
  }
5759
5823
  }
@@ -5870,8 +5934,10 @@ class DataManager {
5870
5934
  }
5871
5935
  return null;
5872
5936
  }
5873
- mutUpdateTargetingData({ infoData, visitorCode, kameleoonData, targetingData, }) {
5937
+ mutUpdateTargetingData({ infoData, visitorCode, kameleoonData, targetingData, extendTtl, }) {
5938
+ var _a, _b, _c;
5874
5939
  for (const dataItem of kameleoonData) {
5940
+ // process custom data
5875
5941
  if (dataItem.data.type === exports.KameleoonData.CustomData) {
5876
5942
  const customDataIsValid = this.processCustomData({
5877
5943
  infoData,
@@ -5883,14 +5949,23 @@ class DataManager {
5883
5949
  continue;
5884
5950
  }
5885
5951
  }
5952
+ // process metadata of conversions
5953
+ if (dataItem.data.type === exports.KameleoonData.Conversion) {
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
+ }
5958
+ }
5886
5959
  const expirationTime = this.dataProcessor.mutUpdateData({
5887
5960
  infoData,
5888
5961
  visitorCode,
5889
5962
  mutData: targetingData,
5890
5963
  dataItem,
5964
+ extendTtl,
5891
5965
  });
5892
5966
  const nextCleanup = infoData.nextDataCleanup;
5893
- if (!nextCleanup || (nextCleanup && expirationTime < nextCleanup)) {
5967
+ if (!nextCleanup ||
5968
+ (nextCleanup && expirationTime && expirationTime < nextCleanup)) {
5894
5969
  infoData.nextDataCleanup = expirationTime;
5895
5970
  }
5896
5971
  if (dataItem.data.status === exports.TrackingStatus.Unsent) {
@@ -5922,13 +5997,11 @@ class DataManager {
5922
5997
  var _a;
5923
5998
  const { data } = customData;
5924
5999
  const isDataValid = Boolean(data.value.length && data.value[0].length);
5925
- if (data.name) {
5926
- const cdIndex = this.customDataIndexByName.get(data.name);
5927
- if (cdIndex === undefined) {
5928
- return false;
5929
- }
5930
- data.index = cdIndex;
5931
- customData._index = cdIndex;
6000
+ if (!this.trySetCustomDataIndexByName(customData)) {
6001
+ return false;
6002
+ }
6003
+ if (data.index == CustomData$1.UNDEFINED_INDEX) {
6004
+ data.index = customData.index;
5932
6005
  }
5933
6006
  if (this.mappingIdentifierCustomDataIndex === data.index && isDataValid) {
5934
6007
  customData._isMappingIdentifier = true;
@@ -5955,6 +6028,17 @@ class DataManager {
5955
6028
  }
5956
6029
  return true;
5957
6030
  }
6031
+ trySetCustomDataIndexByName(customData) {
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;
6041
+ }
5958
6042
  get unsentDataVisitors() {
5959
6043
  const infoResult = this.infoStorage.read();
5960
6044
  if (!infoResult.ok) {
@@ -7066,6 +7150,13 @@ class Hasher {
7066
7150
  }
7067
7151
  }
7068
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
+
7069
7160
  class VariationConfiguration {
7070
7161
  constructor(externalStorage, externalStorageForcedExperimentVariations, externalStorageForcedFeatureVariations, visitorCodeManager, clientConfiguration) {
7071
7162
  this.storage = externalStorage;
@@ -7124,9 +7215,12 @@ class VariationConfiguration {
7124
7215
  }
7125
7216
  return buildExports.Ok(featureFlagVariations);
7126
7217
  }
7127
- 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, }) {
7128
7219
  KameleoonLogger.debug `CALL: VariationConfiguration.getVariation(visitorCode: ${visitorCode}, visitorIdentifier: ${visitorIdentifier}, featureFlag: ${featureFlag}, targetingData: ${targetingData}, packageInfo: ${packageInfo}, clientConfiguration, dataManager, withAssignment: ${withAssignment})`;
7129
7220
  const { rules, featureKey, id: featureFlagId, defaultVariationKey, } = featureFlag;
7221
+ const consent = clientConfiguration.isConsentRequired
7222
+ ? legalConsent
7223
+ : LegalConsent.Given;
7130
7224
  for (const rule of rules) {
7131
7225
  const { segment, experimentId, id, exposition, respoolTime, variationByExposition, } = rule;
7132
7226
  const forcedVariationData = this.getForcedExperimentVariation(visitorCode, rule.experimentId);
@@ -7173,6 +7267,16 @@ class VariationConfiguration {
7173
7267
  });
7174
7268
  KameleoonLogger.debug `Calculated ruleHash: ${ruleHash} for code: ${visitorIdentifier}`;
7175
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
7176
7280
  let variation = this.evaluateCBScores(rule, visitorIdentifier, targetingData);
7177
7281
  if (!variation) {
7178
7282
  const variationHash = Hasher.getHashDouble({
@@ -7534,7 +7638,6 @@ class KameleoonEventSource {
7534
7638
  const VISITOR_CODE_LENGTH = 16;
7535
7639
  const VISITOR_CODE_MAX_LENGTH = 255;
7536
7640
  const DEFAULT_MAX_AGE = 60 * 60 * 24 * 365;
7537
- const ZERO_MAX_AGE = 0;
7538
7641
  const PATH = '/';
7539
7642
 
7540
7643
  /**
@@ -8002,7 +8105,7 @@ class Tracker {
8002
8105
  this.dataManager.storeTrackedData(updatedData);
8003
8106
  }
8004
8107
  else {
8005
- this.dataManager.storeData(updatedData);
8108
+ this.dataManager.storeData(updatedData, false);
8006
8109
  }
8007
8110
  }
8008
8111
  getUnsentVisitorData(visitorCode, isConsentProvided) {
@@ -8354,16 +8457,23 @@ class KameleoonClient {
8354
8457
  const variations = new Map();
8355
8458
  const featureFlags = this.clientConfiguration.featureFlags;
8356
8459
  for (const featureFlag of featureFlags.values()) {
8357
- const variation = this._getFeatureVariation({
8358
- visitorCode,
8359
- featureKey: featureFlag.featureKey,
8360
- track,
8361
- });
8362
- if (variation.ok) {
8363
- 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)) {
8364
8468
  variations.set(featureFlag.featureKey, variation.data);
8365
8469
  }
8366
8470
  }
8471
+ catch (err) {
8472
+ if (err instanceof KameleoonError &&
8473
+ err.type !== exports.KameleoonException.FeatureFlagEnvironmentDisabled) {
8474
+ throw err;
8475
+ }
8476
+ }
8367
8477
  }
8368
8478
  KameleoonLogger.info `RETURN: KameleoonClient.getVariations(visitorCode: ${visitorCode}, onlyActive: ${onlyActive}, track: ${track}) -> (variations: ${variations})`;
8369
8479
  return variations;
@@ -8683,25 +8793,16 @@ class KameleoonClient {
8683
8793
  path: PATH,
8684
8794
  });
8685
8795
  }
8686
- else {
8687
- if (this.visitorCodeManager.consentRequired) {
8688
- setData({
8689
- visitorCode: '',
8690
- key: exports.KameleoonStorageKey.VisitorCode,
8691
- maxAge: ZERO_MAX_AGE,
8692
- path: PATH,
8693
- });
8694
- }
8695
- }
8696
8796
  KameleoonLogger.info `RETURN: KameleoonClient.setUserConsent(visitorCode: ${visitorCode}, consent: ${consent}, setData: ${setData})`;
8697
8797
  }
8698
8798
  updateConsentData(visitorCode, consent) {
8699
8799
  const readResult = this.consentDataStorage.read();
8800
+ const legalConsent = consent ? LegalConsent.Given : LegalConsent.NotGiven;
8700
8801
  if (!readResult.ok) {
8701
8802
  if (readResult.error.type === exports.KameleoonException.StorageEmpty) {
8702
8803
  this.consentDataStorage.write({
8703
8804
  [visitorCode]: {
8704
- consent,
8805
+ consent: legalConsent,
8705
8806
  },
8706
8807
  });
8707
8808
  }
@@ -8709,28 +8810,36 @@ class KameleoonClient {
8709
8810
  }
8710
8811
  const data = readResult.data;
8711
8812
  data[visitorCode] = {
8712
- consent,
8813
+ consent: legalConsent,
8713
8814
  };
8714
8815
  this.consentDataStorage.write(data);
8715
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
+ }
8716
8838
  _isConsentProvided(visitorCode) {
8717
8839
  KameleoonLogger.debug `CALL: KameleoonClient._isConsentProvided(visitorCode: ${visitorCode})`;
8718
8840
  const { isConsentRequired } = this.clientConfiguration;
8719
- const consentDataResult = this.consentDataStorage.read();
8720
- let isConsentProvided = false;
8721
- if (!isConsentRequired) {
8722
- isConsentProvided = true;
8723
- }
8724
- else if (consentDataResult.ok) {
8725
- const consentData = consentDataResult.data[visitorCode];
8726
- // for consistency with the old data in the storage
8727
- if (typeof consentData === 'boolean') {
8728
- isConsentProvided = consentData;
8729
- }
8730
- else {
8731
- isConsentProvided = consentData && consentData.consent;
8732
- }
8733
- }
8841
+ const isConsentProvided = !isConsentRequired ||
8842
+ this.getLegalConsent(visitorCode) == LegalConsent.Given;
8734
8843
  KameleoonLogger.debug `RETURN: KameleoonClient._isConsentProvided(visitorCode: ${visitorCode}) -> (isConsentProvided: ${isConsentProvided})`;
8735
8844
  return isConsentProvided;
8736
8845
  }
@@ -8758,22 +8867,32 @@ class KameleoonClient {
8758
8867
  if (!featureFlag.environmentEnabled) {
8759
8868
  continue;
8760
8869
  }
8761
- const evalExp = this._evaluate({
8762
- visitorCode,
8763
- featureFlag,
8764
- track: false,
8765
- save: false,
8766
- });
8767
- if (evalExp.variationKey !== OFF_VARIATION_KEY) {
8768
- activeVariations.push({
8769
- variationKey: evalExp.variationKey,
8770
- variationId: evalExp.variationId,
8771
- experimentId: evalExp.experimentId,
8772
- featureFlagId: featureFlag.id,
8773
- featureKey: featureFlag.featureKey,
8774
- rule: null,
8775
- isTargetedRule: evalExp.ruleType === RuleType.TARGETED_DELIVERY,
8870
+ try {
8871
+ const evalExp = this._evaluate({
8872
+ visitorCode,
8873
+ featureFlag,
8874
+ track: false,
8875
+ save: false,
8776
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
+ }
8777
8896
  }
8778
8897
  }
8779
8898
  KameleoonLogger.debug `RETURN: KameleoonClient._getActiveFeatureVariations(visitorCode: ${visitorCode}) -> (activeVariations: ${activeVariations})`;
@@ -8794,6 +8913,7 @@ class KameleoonClient {
8794
8913
  else if (this._isVisitorNotInHoldout(visitorCode, track, save, featureFlag, visitorData) &&
8795
8914
  this._isFFUnrestrictedByMEGroup(visitorCode, featureFlag, visitorData)) {
8796
8915
  const visitorIdentifier = this._getCodeForHash(visitorCode, featureFlag.bucketingCustomDataIndex, visitorData);
8916
+ const legalConsent = this.getLegalConsent(visitorCode);
8797
8917
  const variationData = this.variationConfiguration
8798
8918
  .getVariation({
8799
8919
  visitorCode,
@@ -8805,6 +8925,7 @@ class KameleoonClient {
8805
8925
  clientConfiguration: this.clientConfiguration,
8806
8926
  dataManager: this.dataManager,
8807
8927
  packageInfo: this.externalPackageInfo,
8928
+ legalConsent,
8808
8929
  })
8809
8930
  .throw();
8810
8931
  evalExp =
@@ -8923,6 +9044,14 @@ class KameleoonClient {
8923
9044
  }
8924
9045
  KameleoonLogger.debug `CALL: KameleoonClient._isVisitorNotInHoldout(visitorCode: ${visitorCode}, track: ${track}, save: ${save}, featureFlag: ${featureFlag}, visitorData: ${visitorData})`;
8925
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
+ }
8926
9055
  const codeForHash = this._getCodeForHash(visitorCode, featureFlag === null || featureFlag === void 0 ? void 0 : featureFlag.bucketingCustomDataIndex, visitorData);
8927
9056
  const holdoutHash = Hasher.getHashDouble({
8928
9057
  visitorIdentifier: codeForHash,