@kameleoon/javascript-sdk-core 5.16.1 → 5.17.1

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) {
@@ -2389,6 +2414,13 @@ let CustomData$1 = class CustomData {
2389
2414
  get name() {
2390
2415
  return this._name;
2391
2416
  }
2417
+ /**
2418
+ * @private
2419
+ * @method name - an internal getter for a name of custom data
2420
+ * */
2421
+ get values() {
2422
+ return this.value;
2423
+ }
2392
2424
  };
2393
2425
  CustomData$1.UNDEFINED_INDEX = -1;
2394
2426
 
@@ -2406,7 +2438,7 @@ let Conversion$1 = class Conversion {
2406
2438
  this.negative = negative;
2407
2439
  this.status = exports.TrackingStatus.Unsent;
2408
2440
  this.id = Math.floor(Math.random() * 1000000);
2409
- this.metadata = metadata;
2441
+ this._metadata = metadata;
2410
2442
  }
2411
2443
  set _id(id) {
2412
2444
  this.id = id;
@@ -2427,9 +2459,21 @@ let Conversion$1 = class Conversion {
2427
2459
  ? UrlParameter.Metadata + this._encodeMetadata()
2428
2460
  : ''));
2429
2461
  }
2462
+ /**
2463
+ * @private
2464
+ * @method metadata - an internal getter for a metadata of conversion
2465
+ * */
2430
2466
  get _metadata() {
2431
2467
  return this.metadata;
2432
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
+ }
2433
2477
  get data() {
2434
2478
  var _a;
2435
2479
  return {
@@ -2863,7 +2907,10 @@ class VisitsData {
2863
2907
  constructor(visits) {
2864
2908
  this.status = exports.TrackingStatus.Sent;
2865
2909
  this.visits = visits;
2866
- this.visitNumber = visits.length ? visits.length - 1 : 0;
2910
+ this._visitNumber = visits.length ? visits.length - 1 : 0;
2911
+ }
2912
+ get visitNumber() {
2913
+ return this._visitNumber;
2867
2914
  }
2868
2915
  get url() {
2869
2916
  return '';
@@ -2871,7 +2918,7 @@ class VisitsData {
2871
2918
  get data() {
2872
2919
  return {
2873
2920
  visits: this.visits,
2874
- visitNumber: this.visitNumber,
2921
+ visitNumber: this._visitNumber,
2875
2922
  type: exports.KameleoonData.VisitsData,
2876
2923
  status: this.status,
2877
2924
  };
@@ -2888,11 +2935,11 @@ class VisitsData {
2888
2935
  list: this.visits,
2889
2936
  visit,
2890
2937
  });
2891
- this.visitNumber = this.visits.length ? this.visits.length - 1 : 0;
2938
+ this._visitNumber = this.visits.length ? this.visits.length - 1 : 0;
2892
2939
  }
2893
2940
  updateVisitNumber(visitNumber) {
2894
- if (visitNumber > this.visitNumber) {
2895
- this.visitNumber = visitNumber;
2941
+ if (visitNumber > this._visitNumber) {
2942
+ this._visitNumber = visitNumber;
2896
2943
  }
2897
2944
  }
2898
2945
  }
@@ -3099,10 +3146,12 @@ class VisitProcessor {
3099
3146
  timeLastActivity: (_a = visit.timeLastEvent) !== null && _a !== void 0 ? _a : visit.timeStarted,
3100
3147
  };
3101
3148
  }
3102
- processVisitNumber(visit, isCurrentVisit) {
3103
- if (visit.staticDataEvent) {
3104
- const visitNumber = visit.staticDataEvent.data.visitNumber + (isCurrentVisit ? 0 : 1);
3105
- this.visitsData.updateVisitNumber(visitNumber);
3149
+ processVisitNumber(visit, visitOffset) {
3150
+ var _a, _b, _c;
3151
+ if (this.visitsData.visitNumber <= visitOffset &&
3152
+ ((_a = visit.staticDataEvent) === null || _a === void 0 ? void 0 : _a.data.visitNumber)) {
3153
+ const visitNumber = (_c = (_b = visit.staticDataEvent) === null || _b === void 0 ? void 0 : _b.data.visitNumber) !== null && _c !== void 0 ? _c : 0;
3154
+ this.visitsData.updateVisitNumber(visitNumber + visitOffset);
3106
3155
  }
3107
3156
  }
3108
3157
  processCbs(cbsData) {
@@ -3276,12 +3325,12 @@ class Parser {
3276
3325
  }
3277
3326
  if (currentVisit) {
3278
3327
  visitProcessor.processVisit(currentVisit);
3279
- visitProcessor.processVisitNumber(currentVisit, true);
3328
+ visitProcessor.processVisitNumber(currentVisit, 0);
3280
3329
  }
3281
- previousVisits === null || previousVisits === void 0 ? void 0 : previousVisits.forEach((visit) => {
3330
+ previousVisits === null || previousVisits === void 0 ? void 0 : previousVisits.forEach((visit, index) => {
3282
3331
  visitProcessor.processVisit(visit);
3283
3332
  visitProcessor.processVisitsData(visit);
3284
- visitProcessor.processVisitNumber(visit, false);
3333
+ visitProcessor.processVisitNumber(visit, index + 1);
3285
3334
  });
3286
3335
  visitProcessor.processKcs(kcs);
3287
3336
  visitProcessor.processCbs(cbs);
@@ -3522,7 +3571,7 @@ class DataProcessor {
3522
3571
  this.cleanupInterval = cleanupInterval;
3523
3572
  this.packageInfo = packageInfo;
3524
3573
  }
3525
- mutUpdateData({ infoData, visitorCode, mutData, dataItem, }) {
3574
+ mutUpdateData({ infoData, visitorCode, mutData, dataItem, extendTtl, }) {
3526
3575
  let { visitorReference, data, isReference } = this.dereferenceData(mutData, visitorCode);
3527
3576
  if (this.packageInfo.isServer &&
3528
3577
  isReference &&
@@ -3533,9 +3582,12 @@ class DataProcessor {
3533
3582
  delete mutData[visitorCode];
3534
3583
  visitorReference = visitorCode;
3535
3584
  }
3536
- const expirationTime = this.cleanupInterval
3537
- ? Date.now() + this.cleanupInterval
3538
- : 0;
3585
+ let expirationTime;
3586
+ if (extendTtl) {
3587
+ expirationTime = this.cleanupInterval
3588
+ ? Date.now() + this.cleanupInterval
3589
+ : 0;
3590
+ }
3539
3591
  switch (dataItem.data.type) {
3540
3592
  case exports.KameleoonData.CustomData: {
3541
3593
  this.updateCustomData({
@@ -3890,13 +3942,17 @@ class DataProcessor {
3890
3942
  return closestCleanupTime;
3891
3943
  }
3892
3944
  updateField({ key, value, data, visitorCode, expirationTime, }) {
3893
- data[visitorCode][key] = Object.assign(Object.assign({}, value), { expirationTime });
3945
+ var _a;
3946
+ const existing = data[visitorCode][key];
3947
+ 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() });
3894
3948
  }
3895
3949
  createField({ key, value, data, visitorCode, expirationTime, }) {
3896
3950
  data[visitorCode] = Object.assign(Object.assign({}, data[visitorCode]), { [key]: Object.assign(Object.assign({}, value), { expirationTime }) });
3897
3951
  }
3898
3952
  updateNestedField({ key, nestedKey, value, data, visitorCode, expirationTime, }) {
3899
- data[visitorCode][key][nestedKey] = Object.assign(Object.assign({}, value), { expirationTime });
3953
+ var _a;
3954
+ const existing = data[visitorCode][key][nestedKey];
3955
+ 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() });
3900
3956
  }
3901
3957
  createNestedField({ key, nestedKey, value, data, visitorCode, expirationTime, }) {
3902
3958
  var _a;
@@ -5162,15 +5218,7 @@ exports.KameleoonStorageKey = void 0;
5162
5218
  KameleoonStorageKey["ForcedExperimentVariation"] = "kameleoonForcedExperimentVariation";
5163
5219
  })(exports.KameleoonStorageKey || (exports.KameleoonStorageKey = {}));
5164
5220
  const DEFAULT_CLIENT_CONFIGURATION = {
5165
- data: {
5166
- customData: [],
5167
- featureFlags: [],
5168
- configuration: {
5169
- realTimeUpdate: false,
5170
- consentType: ConsentType.NotRequired,
5171
- dataApiDomain: 'data.kameleoon.io',
5172
- },
5173
- },
5221
+ data: DEFAULT_DATA_FILE_CONFIGURATION,
5174
5222
  lastUpdate: '',
5175
5223
  };
5176
5224
  // --- Note ---
@@ -5682,7 +5730,7 @@ class DataManager {
5682
5730
  return resultData;
5683
5731
  }
5684
5732
  storeTrackedData(data) {
5685
- this.storeData(data);
5733
+ this.storeData(data, false);
5686
5734
  const infoResult = this.infoStorage.read();
5687
5735
  if (!infoResult.ok) {
5688
5736
  return;
@@ -5734,15 +5782,18 @@ class DataManager {
5734
5782
  targetingData,
5735
5783
  visitorCode: firstParameter,
5736
5784
  kameleoonData: secondParameter,
5785
+ extendTtl: true,
5737
5786
  });
5738
5787
  }
5739
5788
  else {
5740
5789
  for (const [visitorCode, kameleoonData] of Object.entries(firstParameter)) {
5790
+ const extendTtl = typeof secondParameter[0] === 'boolean' ? secondParameter[0] : true;
5741
5791
  this.mutUpdateTargetingData({
5742
5792
  infoData,
5743
5793
  targetingData,
5744
5794
  visitorCode,
5745
5795
  kameleoonData,
5796
+ extendTtl,
5746
5797
  });
5747
5798
  }
5748
5799
  }
@@ -5859,8 +5910,8 @@ class DataManager {
5859
5910
  }
5860
5911
  return null;
5861
5912
  }
5862
- mutUpdateTargetingData({ infoData, visitorCode, kameleoonData, targetingData, }) {
5863
- var _a;
5913
+ mutUpdateTargetingData({ infoData, visitorCode, kameleoonData, targetingData, extendTtl, }) {
5914
+ var _a, _b, _c;
5864
5915
  for (const dataItem of kameleoonData) {
5865
5916
  // process custom data
5866
5917
  if (dataItem.data.type === exports.KameleoonData.CustomData) {
@@ -5876,16 +5927,21 @@ class DataManager {
5876
5927
  }
5877
5928
  // process metadata of conversions
5878
5929
  if (dataItem.data.type === exports.KameleoonData.Conversion) {
5879
- (_a = dataItem._metadata) === null || _a === void 0 ? void 0 : _a.forEach((item) => this.trySetCustomDataIndexByName(item));
5930
+ const conversion = dataItem;
5931
+ if (((_b = (_a = conversion._metadata) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0) > 0) {
5932
+ conversion._metadata = (_c = conversion._metadata) === null || _c === void 0 ? void 0 : _c.filter((item) => this.trySetCustomDataIndexByName(item));
5933
+ }
5880
5934
  }
5881
5935
  const expirationTime = this.dataProcessor.mutUpdateData({
5882
5936
  infoData,
5883
5937
  visitorCode,
5884
5938
  mutData: targetingData,
5885
5939
  dataItem,
5940
+ extendTtl,
5886
5941
  });
5887
5942
  const nextCleanup = infoData.nextDataCleanup;
5888
- if (!nextCleanup || (nextCleanup && expirationTime < nextCleanup)) {
5943
+ if (!nextCleanup ||
5944
+ (nextCleanup && expirationTime && expirationTime < nextCleanup)) {
5889
5945
  infoData.nextDataCleanup = expirationTime;
5890
5946
  }
5891
5947
  if (dataItem.data.status === exports.TrackingStatus.Unsent) {
@@ -5917,7 +5973,9 @@ class DataManager {
5917
5973
  var _a;
5918
5974
  const { data } = customData;
5919
5975
  const isDataValid = Boolean(data.value.length && data.value[0].length);
5920
- this.trySetCustomDataIndexByName(customData);
5976
+ if (!this.trySetCustomDataIndexByName(customData)) {
5977
+ return false;
5978
+ }
5921
5979
  if (data.index == CustomData$1.UNDEFINED_INDEX) {
5922
5980
  data.index = customData.index;
5923
5981
  }
@@ -5947,13 +6005,15 @@ class DataManager {
5947
6005
  return true;
5948
6006
  }
5949
6007
  trySetCustomDataIndexByName(customData) {
5950
- if (customData.index == CustomData$1.UNDEFINED_INDEX && customData.name) {
5951
- const cdIndex = this.customDataIndexByName.get(customData.name);
5952
- if (cdIndex === undefined) {
5953
- return;
5954
- }
5955
- customData.index = cdIndex;
5956
- }
6008
+ if (customData.index !== CustomData$1.UNDEFINED_INDEX)
6009
+ return true;
6010
+ if (!customData.name)
6011
+ return false;
6012
+ const cdIndex = this.customDataIndexByName.get(customData.name);
6013
+ if (cdIndex == null)
6014
+ return false;
6015
+ customData.index = cdIndex;
6016
+ return true;
5957
6017
  }
5958
6018
  get unsentDataVisitors() {
5959
6019
  const infoResult = this.infoStorage.read();
@@ -7057,6 +7117,13 @@ class Hasher {
7057
7117
  }
7058
7118
  }
7059
7119
 
7120
+ var LegalConsent;
7121
+ (function (LegalConsent) {
7122
+ LegalConsent[LegalConsent["Unknown"] = 0] = "Unknown";
7123
+ LegalConsent[LegalConsent["Given"] = 1] = "Given";
7124
+ LegalConsent[LegalConsent["NotGiven"] = 2] = "NotGiven";
7125
+ })(LegalConsent || (LegalConsent = {}));
7126
+
7060
7127
  class VariationConfiguration {
7061
7128
  constructor(externalStorage, externalStorageForcedExperimentVariations, externalStorageForcedFeatureVariations, visitorCodeManager, clientConfiguration) {
7062
7129
  this.storage = externalStorage;
@@ -7115,9 +7182,12 @@ class VariationConfiguration {
7115
7182
  }
7116
7183
  return buildExports.Ok(featureFlagVariations);
7117
7184
  }
7118
- getVariation({ visitorCode, visitorIdentifier, featureFlag, targetingData, packageInfo, clientConfiguration, dataManager, track = true, withAssignment = false, }) {
7185
+ getVariation({ visitorCode, visitorIdentifier, featureFlag, targetingData, packageInfo, clientConfiguration, dataManager, legalConsent, track = true, withAssignment = false, }) {
7119
7186
  KameleoonLogger.debug `CALL: VariationConfiguration.getVariation(visitorCode: ${visitorCode}, visitorIdentifier: ${visitorIdentifier}, featureFlag: ${featureFlag}, targetingData: ${targetingData}, packageInfo: ${packageInfo}, clientConfiguration, dataManager, withAssignment: ${withAssignment})`;
7120
7187
  const { rules, featureKey, id: featureFlagId, defaultVariationKey, } = featureFlag;
7188
+ const consent = clientConfiguration.isConsentRequired
7189
+ ? legalConsent
7190
+ : LegalConsent.Given;
7121
7191
  for (const rule of rules) {
7122
7192
  const { segment, experimentId, id, exposition, respoolTime, variationByExposition, } = rule;
7123
7193
  const forcedVariationData = this.getForcedExperimentVariation(visitorCode, rule.experimentId);
@@ -7164,6 +7234,16 @@ class VariationConfiguration {
7164
7234
  });
7165
7235
  KameleoonLogger.debug `Calculated ruleHash: ${ruleHash} for code: ${visitorIdentifier}`;
7166
7236
  if (ruleHash <= exposition) {
7237
+ // Checking if the evaluation is blocked due to the consent policy
7238
+ if (consent == LegalConsent.NotGiven &&
7239
+ rule.type == RuleType.EXPERIMENTATION) {
7240
+ const behaviour = clientConfiguration.consentBlockingBehaviour;
7241
+ if (behaviour == ConsentBlockingBehaviour.PartiallyBlocked) {
7242
+ break;
7243
+ }
7244
+ return buildExports.Err(new KameleoonError(exports.KameleoonException.FeatureFlagEnvironmentDisabled, `Evaluation of ${rule} is blocked because consent is not provided for visitor '${visitorCode}'`));
7245
+ }
7246
+ // evaluate with CB scores if applicable
7167
7247
  let variation = this.evaluateCBScores(rule, visitorIdentifier, targetingData);
7168
7248
  if (!variation) {
7169
7249
  const variationHash = Hasher.getHashDouble({
@@ -7525,7 +7605,6 @@ class KameleoonEventSource {
7525
7605
  const VISITOR_CODE_LENGTH = 16;
7526
7606
  const VISITOR_CODE_MAX_LENGTH = 255;
7527
7607
  const DEFAULT_MAX_AGE = 60 * 60 * 24 * 365;
7528
- const ZERO_MAX_AGE = 0;
7529
7608
  const PATH = '/';
7530
7609
 
7531
7610
  /**
@@ -7993,7 +8072,7 @@ class Tracker {
7993
8072
  this.dataManager.storeTrackedData(updatedData);
7994
8073
  }
7995
8074
  else {
7996
- this.dataManager.storeData(updatedData);
8075
+ this.dataManager.storeData(updatedData, false);
7997
8076
  }
7998
8077
  }
7999
8078
  getUnsentVisitorData(visitorCode, isConsentProvided) {
@@ -8345,16 +8424,23 @@ class KameleoonClient {
8345
8424
  const variations = new Map();
8346
8425
  const featureFlags = this.clientConfiguration.featureFlags;
8347
8426
  for (const featureFlag of featureFlags.values()) {
8348
- const variation = this._getFeatureVariation({
8349
- visitorCode,
8350
- featureKey: featureFlag.featureKey,
8351
- track,
8352
- });
8353
- if (variation.ok) {
8354
- if (!onlyActive || variation.data.key !== OFF_VARIATION_KEY) {
8427
+ try {
8428
+ const variation = this._getFeatureVariation({
8429
+ visitorCode,
8430
+ featureKey: featureFlag.featureKey,
8431
+ track,
8432
+ });
8433
+ if (variation.ok &&
8434
+ (!onlyActive || variation.data.key !== OFF_VARIATION_KEY)) {
8355
8435
  variations.set(featureFlag.featureKey, variation.data);
8356
8436
  }
8357
8437
  }
8438
+ catch (err) {
8439
+ if (err instanceof KameleoonError &&
8440
+ err.type !== exports.KameleoonException.FeatureFlagEnvironmentDisabled) {
8441
+ throw err;
8442
+ }
8443
+ }
8358
8444
  }
8359
8445
  KameleoonLogger.info `RETURN: KameleoonClient.getVariations(visitorCode: ${visitorCode}, onlyActive: ${onlyActive}, track: ${track}) -> (variations: ${variations})`;
8360
8446
  return variations;
@@ -8674,25 +8760,16 @@ class KameleoonClient {
8674
8760
  path: PATH,
8675
8761
  });
8676
8762
  }
8677
- else {
8678
- if (this.visitorCodeManager.consentRequired) {
8679
- setData({
8680
- visitorCode: '',
8681
- key: exports.KameleoonStorageKey.VisitorCode,
8682
- maxAge: ZERO_MAX_AGE,
8683
- path: PATH,
8684
- });
8685
- }
8686
- }
8687
8763
  KameleoonLogger.info `RETURN: KameleoonClient.setUserConsent(visitorCode: ${visitorCode}, consent: ${consent}, setData: ${setData})`;
8688
8764
  }
8689
8765
  updateConsentData(visitorCode, consent) {
8690
8766
  const readResult = this.consentDataStorage.read();
8767
+ const legalConsent = consent ? LegalConsent.Given : LegalConsent.NotGiven;
8691
8768
  if (!readResult.ok) {
8692
8769
  if (readResult.error.type === exports.KameleoonException.StorageEmpty) {
8693
8770
  this.consentDataStorage.write({
8694
8771
  [visitorCode]: {
8695
- consent,
8772
+ consent: legalConsent,
8696
8773
  },
8697
8774
  });
8698
8775
  }
@@ -8700,28 +8777,36 @@ class KameleoonClient {
8700
8777
  }
8701
8778
  const data = readResult.data;
8702
8779
  data[visitorCode] = {
8703
- consent,
8780
+ consent: legalConsent,
8704
8781
  };
8705
8782
  this.consentDataStorage.write(data);
8706
8783
  }
8784
+ getLegalConsent(visitorCode) {
8785
+ KameleoonLogger.debug `CALL: KameleoonClient.getLegalConsent(visitorCode: ${visitorCode})`;
8786
+ let legalConsent;
8787
+ const consentDataResult = this.consentDataStorage.read();
8788
+ legalConsent = consentDataResult.ok
8789
+ ? this.extractLegalConsent(consentDataResult.data[visitorCode])
8790
+ : LegalConsent.Unknown;
8791
+ KameleoonLogger.debug `RETURN: KameleoonClient.getLegalConsent(visitorCode: ${visitorCode}) -> (legalConsent: ${legalConsent})`;
8792
+ return legalConsent;
8793
+ }
8794
+ extractLegalConsent(consentData) {
8795
+ if (consentData === undefined)
8796
+ return LegalConsent.Unknown;
8797
+ if (typeof consentData === 'boolean') {
8798
+ return consentData ? LegalConsent.Given : LegalConsent.NotGiven;
8799
+ }
8800
+ const value = consentData.consent;
8801
+ if (typeof value === 'boolean')
8802
+ return value ? LegalConsent.Given : LegalConsent.NotGiven;
8803
+ return value;
8804
+ }
8707
8805
  _isConsentProvided(visitorCode) {
8708
8806
  KameleoonLogger.debug `CALL: KameleoonClient._isConsentProvided(visitorCode: ${visitorCode})`;
8709
8807
  const { isConsentRequired } = this.clientConfiguration;
8710
- const consentDataResult = this.consentDataStorage.read();
8711
- let isConsentProvided = false;
8712
- if (!isConsentRequired) {
8713
- isConsentProvided = true;
8714
- }
8715
- else if (consentDataResult.ok) {
8716
- const consentData = consentDataResult.data[visitorCode];
8717
- // for consistency with the old data in the storage
8718
- if (typeof consentData === 'boolean') {
8719
- isConsentProvided = consentData;
8720
- }
8721
- else {
8722
- isConsentProvided = consentData && consentData.consent;
8723
- }
8724
- }
8808
+ const isConsentProvided = !isConsentRequired ||
8809
+ this.getLegalConsent(visitorCode) == LegalConsent.Given;
8725
8810
  KameleoonLogger.debug `RETURN: KameleoonClient._isConsentProvided(visitorCode: ${visitorCode}) -> (isConsentProvided: ${isConsentProvided})`;
8726
8811
  return isConsentProvided;
8727
8812
  }
@@ -8749,22 +8834,32 @@ class KameleoonClient {
8749
8834
  if (!featureFlag.environmentEnabled) {
8750
8835
  continue;
8751
8836
  }
8752
- const evalExp = this._evaluate({
8753
- visitorCode,
8754
- featureFlag,
8755
- track: false,
8756
- save: false,
8757
- });
8758
- if (evalExp.variationKey !== OFF_VARIATION_KEY) {
8759
- activeVariations.push({
8760
- variationKey: evalExp.variationKey,
8761
- variationId: evalExp.variationId,
8762
- experimentId: evalExp.experimentId,
8763
- featureFlagId: featureFlag.id,
8764
- featureKey: featureFlag.featureKey,
8765
- rule: null,
8766
- isTargetedRule: evalExp.ruleType === RuleType.TARGETED_DELIVERY,
8837
+ try {
8838
+ const evalExp = this._evaluate({
8839
+ visitorCode,
8840
+ featureFlag,
8841
+ track: false,
8842
+ save: false,
8767
8843
  });
8844
+ if (evalExp.variationKey !== OFF_VARIATION_KEY) {
8845
+ activeVariations.push({
8846
+ variationKey: evalExp.variationKey,
8847
+ variationId: evalExp.variationId,
8848
+ experimentId: evalExp.experimentId,
8849
+ featureFlagId: featureFlag.id,
8850
+ featureKey: featureFlag.featureKey,
8851
+ rule: null,
8852
+ isTargetedRule: evalExp.ruleType === RuleType.TARGETED_DELIVERY,
8853
+ });
8854
+ }
8855
+ }
8856
+ catch (err) {
8857
+ if (err instanceof KameleoonError) {
8858
+ if (err.type != exports.KameleoonException.FeatureFlagEnvironmentDisabled) {
8859
+ KameleoonLogger.error `Unexpected error: ${err}`;
8860
+ }
8861
+ continue;
8862
+ }
8768
8863
  }
8769
8864
  }
8770
8865
  KameleoonLogger.debug `RETURN: KameleoonClient._getActiveFeatureVariations(visitorCode: ${visitorCode}) -> (activeVariations: ${activeVariations})`;
@@ -8785,6 +8880,7 @@ class KameleoonClient {
8785
8880
  else if (this._isVisitorNotInHoldout(visitorCode, track, save, featureFlag, visitorData) &&
8786
8881
  this._isFFUnrestrictedByMEGroup(visitorCode, featureFlag, visitorData)) {
8787
8882
  const visitorIdentifier = this._getCodeForHash(visitorCode, featureFlag.bucketingCustomDataIndex, visitorData);
8883
+ const legalConsent = this.getLegalConsent(visitorCode);
8788
8884
  const variationData = this.variationConfiguration
8789
8885
  .getVariation({
8790
8886
  visitorCode,
@@ -8796,6 +8892,7 @@ class KameleoonClient {
8796
8892
  clientConfiguration: this.clientConfiguration,
8797
8893
  dataManager: this.dataManager,
8798
8894
  packageInfo: this.externalPackageInfo,
8895
+ legalConsent,
8799
8896
  })
8800
8897
  .throw();
8801
8898
  evalExp =
@@ -8914,6 +9011,16 @@ class KameleoonClient {
8914
9011
  }
8915
9012
  KameleoonLogger.debug `CALL: KameleoonClient._isVisitorNotInHoldout(visitorCode: ${visitorCode}, track: ${track}, save: ${save}, featureFlag: ${featureFlag}, visitorData: ${visitorData})`;
8916
9013
  let isNotInHoldout = true;
9014
+ // Checking if the evaluation is blocked due to the consent policy
9015
+ const legalConsent = this.clientConfiguration.isConsentRequired
9016
+ ? this.getLegalConsent(visitorCode)
9017
+ : LegalConsent.Given;
9018
+ if (legalConsent == LegalConsent.NotGiven) {
9019
+ const behaviour = this.clientConfiguration.consentBlockingBehaviour;
9020
+ if (behaviour == ConsentBlockingBehaviour.CompletelyBlocked) {
9021
+ throw new KameleoonError(exports.KameleoonException.FeatureFlagEnvironmentDisabled, `Evaluation of holdout is blocked because consent is not provided for visitor '${visitorCode}'`);
9022
+ }
9023
+ }
8917
9024
  const codeForHash = this._getCodeForHash(visitorCode, featureFlag === null || featureFlag === void 0 ? void 0 : featureFlag.bucketingCustomDataIndex, visitorData);
8918
9025
  const holdoutHash = Hasher.getHashDouble({
8919
9026
  visitorIdentifier: codeForHash,