@splitsoftware/splitio-commons 1.2.1-rc.2 → 1.2.1-rc.5

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.
Files changed (111) hide show
  1. package/cjs/integrations/ga/GoogleAnalyticsToSplit.js +4 -2
  2. package/cjs/integrations/ga/SplitToGoogleAnalytics.js +4 -2
  3. package/cjs/listeners/browser.js +14 -10
  4. package/cjs/logger/constants.js +6 -4
  5. package/cjs/logger/messages/error.js +3 -2
  6. package/cjs/logger/messages/info.js +3 -2
  7. package/cjs/sdkClient/client.js +10 -4
  8. package/cjs/sdkFactory/index.js +6 -4
  9. package/cjs/sdkFactory/userConsentProps.js +34 -0
  10. package/cjs/storages/KeyBuilderCS.js +11 -1
  11. package/cjs/storages/inLocalStorage/MySegmentsCacheInLocal.js +23 -3
  12. package/cjs/storages/inLocalStorage/index.js +1 -1
  13. package/cjs/storages/inMemory/ImpressionsCacheInMemory.js +15 -1
  14. package/cjs/storages/inMemory/InMemoryStorage.js +1 -1
  15. package/cjs/storages/inMemory/InMemoryStorageCS.js +1 -1
  16. package/cjs/storages/pluggable/index.js +2 -2
  17. package/cjs/sync/streaming/pushManager.js +9 -2
  18. package/cjs/sync/submitters/eventsSyncTask.js +9 -4
  19. package/cjs/sync/submitters/impressionsSyncTask.js +13 -1
  20. package/cjs/sync/syncManagerOnline.js +11 -7
  21. package/cjs/utils/consent.js +10 -0
  22. package/cjs/utils/constants/index.js +5 -1
  23. package/cjs/utils/settingsValidation/consent.js +16 -0
  24. package/cjs/utils/settingsValidation/impressionsMode.js +6 -6
  25. package/cjs/utils/settingsValidation/index.js +6 -1
  26. package/cjs/utils/settingsValidation/{runtime/browser.js → runtime.js} +1 -0
  27. package/esm/integrations/ga/GoogleAnalyticsToSplit.js +4 -2
  28. package/esm/integrations/ga/SplitToGoogleAnalytics.js +4 -2
  29. package/esm/listeners/browser.js +14 -10
  30. package/esm/logger/constants.js +4 -2
  31. package/esm/logger/messages/error.js +3 -2
  32. package/esm/logger/messages/info.js +3 -2
  33. package/esm/sdkClient/client.js +11 -5
  34. package/esm/sdkFactory/index.js +6 -4
  35. package/esm/sdkFactory/userConsentProps.js +30 -0
  36. package/esm/storages/KeyBuilderCS.js +11 -1
  37. package/esm/storages/inLocalStorage/MySegmentsCacheInLocal.js +23 -3
  38. package/esm/storages/inLocalStorage/index.js +1 -1
  39. package/esm/storages/inMemory/ImpressionsCacheInMemory.js +15 -1
  40. package/esm/storages/inMemory/InMemoryStorage.js +1 -1
  41. package/esm/storages/inMemory/InMemoryStorageCS.js +1 -1
  42. package/esm/storages/pluggable/index.js +2 -2
  43. package/esm/sync/streaming/pushManager.js +9 -2
  44. package/esm/sync/submitters/eventsSyncTask.js +10 -5
  45. package/esm/sync/submitters/impressionsSyncTask.js +13 -1
  46. package/esm/sync/syncManagerOnline.js +11 -7
  47. package/esm/utils/consent.js +6 -0
  48. package/esm/utils/constants/index.js +4 -0
  49. package/esm/utils/settingsValidation/consent.js +12 -0
  50. package/esm/utils/settingsValidation/impressionsMode.js +7 -7
  51. package/esm/utils/settingsValidation/index.js +6 -1
  52. package/esm/utils/settingsValidation/{runtime/browser.js → runtime.js} +1 -0
  53. package/package.json +1 -2
  54. package/src/integrations/ga/GoogleAnalyticsToSplit.ts +7 -4
  55. package/src/integrations/ga/SplitToGoogleAnalytics.ts +7 -4
  56. package/src/integrations/types.ts +5 -0
  57. package/src/listeners/browser.ts +13 -9
  58. package/src/logger/constants.ts +4 -2
  59. package/src/logger/messages/error.ts +3 -2
  60. package/src/logger/messages/info.ts +3 -2
  61. package/src/logger/types.ts +4 -4
  62. package/src/sdkClient/client.ts +7 -5
  63. package/src/sdkFactory/index.ts +6 -4
  64. package/src/sdkFactory/types.ts +2 -0
  65. package/src/sdkFactory/userConsentProps.ts +37 -0
  66. package/src/storages/KeyBuilderCS.ts +13 -1
  67. package/src/storages/inLocalStorage/MySegmentsCacheInLocal.ts +23 -3
  68. package/src/storages/inLocalStorage/index.ts +1 -1
  69. package/src/storages/inMemory/ImpressionsCacheInMemory.ts +22 -1
  70. package/src/storages/inMemory/InMemoryStorage.ts +1 -1
  71. package/src/storages/inMemory/InMemoryStorageCS.ts +1 -1
  72. package/src/storages/pluggable/index.ts +2 -2
  73. package/src/storages/types.ts +6 -2
  74. package/src/sync/streaming/pushManager.ts +11 -2
  75. package/src/sync/submitters/eventsSyncTask.ts +11 -5
  76. package/src/sync/submitters/impressionsSyncTask.ts +16 -1
  77. package/src/sync/syncManagerOnline.ts +13 -7
  78. package/src/sync/types.ts +4 -1
  79. package/src/types.ts +21 -0
  80. package/src/utils/consent.ts +8 -0
  81. package/src/utils/constants/index.ts +5 -0
  82. package/src/utils/settingsValidation/consent.ts +14 -0
  83. package/src/utils/settingsValidation/impressionsMode.ts +7 -8
  84. package/src/utils/settingsValidation/index.ts +7 -1
  85. package/src/utils/settingsValidation/runtime.ts +9 -0
  86. package/src/utils/settingsValidation/types.ts +2 -0
  87. package/types/integrations/ga/GoogleAnalyticsToSplit.d.ts +2 -2
  88. package/types/integrations/ga/SplitToGoogleAnalytics.d.ts +2 -3
  89. package/types/integrations/types.d.ts +4 -0
  90. package/types/logger/constants.d.ts +4 -2
  91. package/types/logger/types.d.ts +4 -4
  92. package/types/sdkFactory/types.d.ts +1 -0
  93. package/types/sdkFactory/userConsentProps.d.ts +6 -0
  94. package/types/storages/KeyBuilderCS.d.ts +2 -0
  95. package/types/storages/inMemory/ImpressionsCacheInMemory.d.ts +9 -0
  96. package/types/storages/types.d.ts +3 -1
  97. package/types/sync/types.d.ts +3 -0
  98. package/types/types.d.ts +21 -0
  99. package/types/utils/consent.d.ts +2 -0
  100. package/types/utils/constants/index.d.ts +3 -0
  101. package/types/utils/settingsValidation/consent.d.ts +5 -0
  102. package/types/utils/settingsValidation/impressionsMode.d.ts +1 -1
  103. package/types/utils/settingsValidation/runtime/browser.d.ts +2 -4
  104. package/types/utils/settingsValidation/runtime/node.d.ts +1 -4
  105. package/types/utils/settingsValidation/runtime.d.ts +2 -0
  106. package/types/utils/settingsValidation/types.d.ts +2 -0
  107. package/types/utils/settingsValidation/userConsent.d.ts +5 -0
  108. package/cjs/utils/settingsValidation/runtime/node.js +0 -22
  109. package/esm/utils/settingsValidation/runtime/node.js +0 -17
  110. package/src/utils/settingsValidation/runtime/browser.ts +0 -6
  111. package/src/utils/settingsValidation/runtime/node.ts +0 -22
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.validateConsent = void 0;
4
+ var constants_1 = require("../../logger/constants");
5
+ var constants_2 = require("../constants");
6
+ var userConsentValues = [constants_2.CONSENT_DECLINED, constants_2.CONSENT_GRANTED, constants_2.CONSENT_UNKNOWN];
7
+ function validateConsent(_a) {
8
+ var userConsent = _a.userConsent, log = _a.log;
9
+ if (typeof userConsent === 'string')
10
+ userConsent = userConsent.toUpperCase();
11
+ if (userConsentValues.indexOf(userConsent) > -1)
12
+ return userConsent;
13
+ log.error(constants_1.ERROR_INVALID_CONFIG_PARAM, ['userConsent', userConsentValues, constants_2.CONSENT_GRANTED]);
14
+ return constants_2.CONSENT_GRANTED;
15
+ }
16
+ exports.validateConsent = validateConsent;
@@ -4,11 +4,11 @@ exports.validImpressionsMode = void 0;
4
4
  var constants_1 = require("../../logger/constants");
5
5
  var constants_2 = require("../constants");
6
6
  function validImpressionsMode(log, impressionsMode) {
7
- impressionsMode = impressionsMode.toUpperCase();
8
- if ([constants_2.DEBUG, constants_2.OPTIMIZED].indexOf(impressionsMode) === -1) {
9
- log.error(constants_1.ERROR_INVALID_IMPRESSIONS_MODE, [[constants_2.DEBUG, constants_2.OPTIMIZED], constants_2.OPTIMIZED]);
10
- impressionsMode = constants_2.OPTIMIZED;
11
- }
12
- return impressionsMode;
7
+ if (typeof impressionsMode === 'string')
8
+ impressionsMode = impressionsMode.toUpperCase();
9
+ if ([constants_2.DEBUG, constants_2.OPTIMIZED].indexOf(impressionsMode) > -1)
10
+ return impressionsMode;
11
+ log.error(constants_1.ERROR_INVALID_CONFIG_PARAM, ['impressionsMode', [constants_2.DEBUG, constants_2.OPTIMIZED], constants_2.OPTIMIZED]);
12
+ return constants_2.OPTIMIZED;
13
13
  }
14
14
  exports.validImpressionsMode = validImpressionsMode;
@@ -36,6 +36,8 @@ var base = {
36
36
  eventsPushRate: 60,
37
37
  // how many events will be queued before flushing
38
38
  eventsQueueSize: 500,
39
+ // how many impressions will be queued before flushing
40
+ impressionsQueueSize: 30000,
39
41
  // backoff base seconds to wait before re attempting to connect to push notifications
40
42
  pushRetryBackoffBase: 1,
41
43
  },
@@ -81,7 +83,7 @@ function fromSecondsToMillis(n) {
81
83
  * @param validationParams defaults and fields validators used to validate and creates a settings object from a given config
82
84
  */
83
85
  function settingsValidation(config, validationParams) {
84
- var defaults = validationParams.defaults, runtime = validationParams.runtime, storage = validationParams.storage, integrations = validationParams.integrations, logger = validationParams.logger, localhost = validationParams.localhost;
86
+ var defaults = validationParams.defaults, runtime = validationParams.runtime, storage = validationParams.storage, integrations = validationParams.integrations, logger = validationParams.logger, localhost = validationParams.localhost, consent = validationParams.consent;
85
87
  // creates a settings object merging base, defaults and config objects.
86
88
  var withDefaults = (0, lang_1.merge)({}, base, defaults, config);
87
89
  // ensure a valid logger.
@@ -134,6 +136,9 @@ function settingsValidation(config, validationParams) {
134
136
  withDefaults.sync.__splitFiltersValidation = splitFiltersValidation;
135
137
  // ensure a valid impressionsMode
136
138
  withDefaults.sync.impressionsMode = (0, impressionsMode_1.validImpressionsMode)(log, withDefaults.sync.impressionsMode);
139
+ // ensure a valid user consent value
140
+ // @ts-ignore, modify readonly prop
141
+ withDefaults.userConsent = consent(withDefaults);
137
142
  return withDefaults;
138
143
  }
139
144
  exports.settingsValidation = settingsValidation;
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.validateRuntime = void 0;
4
+ // For client-side SDKs, machine IP and Hostname are not captured and sent to Split backend.
4
5
  function validateRuntime() {
5
6
  return {
6
7
  ip: false,
@@ -1,7 +1,9 @@
1
1
  import { GaToSplit } from './GaToSplit';
2
2
  export function GoogleAnalyticsToSplit(options) {
3
3
  // GaToSplit integration factory
4
- return function (params) {
4
+ function GoogleAnalyticsToSplitFactory(params) {
5
5
  return GaToSplit(options, params);
6
- };
6
+ }
7
+ GoogleAnalyticsToSplitFactory.type = 'GOOGLE_ANALYTICS_TO_SPLIT';
8
+ return GoogleAnalyticsToSplitFactory;
7
9
  }
@@ -2,7 +2,9 @@ import { SplitToGa } from './SplitToGa';
2
2
  export function SplitToGoogleAnalytics(options) {
3
3
  if (options === void 0) { options = {}; }
4
4
  // SplitToGa integration factory
5
- return function (params) {
5
+ function SplitToGoogleAnalyticsFactory(params) {
6
6
  return new SplitToGa(params.settings.log, options);
7
- };
7
+ }
8
+ SplitToGoogleAnalyticsFactory.type = 'SPLIT_TO_GOOGLE_ANALYTICS';
9
+ return SplitToGoogleAnalyticsFactory;
8
10
  }
@@ -3,6 +3,7 @@ import { fromImpressionCountsCollector } from '../sync/submitters/impressionCoun
3
3
  import { OPTIMIZED, DEBUG } from '../utils/constants';
4
4
  import { objectAssign } from '../utils/lang/objectAssign';
5
5
  import { CLEANUP_REGISTERING, CLEANUP_DEREGISTERING } from '../logger/constants';
6
+ import { isConsentGranted } from '../utils/consent';
6
7
  // 'unload' event is used instead of 'beforeunload', since 'unload' is not a cancelable event, so no other listeners can stop the event from occurring.
7
8
  var UNLOAD_DOM_EVENT = 'unload';
8
9
  var EVENT_NAME = 'for unload page event.';
@@ -48,15 +49,18 @@ var BrowserSignalListener = /** @class */ (function () {
48
49
  BrowserSignalListener.prototype.flushData = function () {
49
50
  if (!this.syncManager)
50
51
  return; // In consumer mode there is not sync manager and data to flush
51
- var eventsUrl = this.settings.urls.events;
52
- var extraMetadata = {
53
- // sim stands for Sync/Split Impressions Mode
54
- sim: this.settings.sync.impressionsMode === OPTIMIZED ? OPTIMIZED : DEBUG
55
- };
56
- this._flushData(eventsUrl + '/testImpressions/beacon', this.storage.impressions, this.serviceApi.postTestImpressionsBulk, this.fromImpressionsCollector, extraMetadata);
57
- this._flushData(eventsUrl + '/events/beacon', this.storage.events, this.serviceApi.postEventsBulk);
58
- if (this.storage.impressionCounts)
59
- this._flushData(eventsUrl + '/testImpressions/count/beacon', this.storage.impressionCounts, this.serviceApi.postTestImpressionsCount, fromImpressionCountsCollector);
52
+ // Flush data if there is user consent
53
+ if (isConsentGranted(this.settings)) {
54
+ var eventsUrl = this.settings.urls.events;
55
+ var extraMetadata = {
56
+ // sim stands for Sync/Split Impressions Mode
57
+ sim: this.settings.sync.impressionsMode === OPTIMIZED ? OPTIMIZED : DEBUG
58
+ };
59
+ this._flushData(eventsUrl + '/testImpressions/beacon', this.storage.impressions, this.serviceApi.postTestImpressionsBulk, this.fromImpressionsCollector, extraMetadata);
60
+ this._flushData(eventsUrl + '/events/beacon', this.storage.events, this.serviceApi.postEventsBulk);
61
+ if (this.storage.impressionCounts)
62
+ this._flushData(eventsUrl + '/testImpressions/count/beacon', this.storage.impressionCounts, this.serviceApi.postTestImpressionsCount, fromImpressionCountsCollector);
63
+ }
60
64
  // Close streaming connection
61
65
  if (this.syncManager.pushManager)
62
66
  this.syncManager.pushManager.stop();
@@ -66,7 +70,7 @@ var BrowserSignalListener = /** @class */ (function () {
66
70
  if (!cache.isEmpty()) {
67
71
  var dataPayload = fromCacheToPayload ? fromCacheToPayload(cache.state()) : cache.state();
68
72
  if (!this._sendBeacon(url, dataPayload, extraMetadata)) {
69
- postService(JSON.stringify(dataPayload)).catch(function () { }); // no-op just to catch a possible exceptions
73
+ postService(JSON.stringify(dataPayload)).catch(function () { }); // no-op just to catch a possible exception
70
74
  }
71
75
  cache.clear();
72
76
  }
@@ -60,13 +60,14 @@ export var STREAMING_RECONNECT = 111;
60
60
  export var STREAMING_CONNECTING = 112;
61
61
  export var STREAMING_DISABLED = 113;
62
62
  export var STREAMING_DISCONNECTING = 114;
63
- export var SUBMITTERS_PUSH_FULL_EVENTS_QUEUE = 115;
63
+ export var SUBMITTERS_PUSH_FULL_QUEUE = 115;
64
64
  export var SUBMITTERS_PUSH = 116;
65
65
  export var SYNC_START_POLLING = 117;
66
66
  export var SYNC_CONTINUE_POLLING = 118;
67
67
  export var SYNC_STOP_POLLING = 119;
68
68
  export var EVENTS_TRACKER_SUCCESS = 120;
69
69
  export var IMPRESSIONS_TRACKER_SUCCESS = 121;
70
+ export var USER_CONSENT_UPDATED = 122;
70
71
  export var ENGINE_VALUE_INVALID = 200;
71
72
  export var ENGINE_VALUE_NO_ATTRIBUTES = 201;
72
73
  export var CLIENT_NO_LISTENER = 202;
@@ -112,10 +113,11 @@ export var ERROR_INVALID_KEY_OBJECT = 317;
112
113
  export var ERROR_INVALID = 318;
113
114
  export var ERROR_EMPTY = 319;
114
115
  export var ERROR_EMPTY_ARRAY = 320;
115
- export var ERROR_INVALID_IMPRESSIONS_MODE = 321;
116
+ export var ERROR_INVALID_CONFIG_PARAM = 321;
116
117
  export var ERROR_HTTP = 322;
117
118
  export var ERROR_LOCALHOST_MODULE_REQUIRED = 323;
118
119
  export var ERROR_STORAGE_INVALID = 324;
120
+ export var ERROR_NOT_BOOLEAN = 325;
119
121
  // Log prefixes (a.k.a. tags or categories)
120
122
  export var LOG_PREFIX_SETTINGS = 'settings';
121
123
  export var LOG_PREFIX_INSTANTIATION = 'Factory instantiation';
@@ -12,7 +12,7 @@ export var codesError = [
12
12
  [c.ERROR_SYNC_OFFLINE_LOADING, c.LOG_PREFIX_SYNC_OFFLINE + 'There was an issue loading the mock Splits data, no changes will be applied to the current cache. %s'],
13
13
  [c.ERROR_STREAMING_SSE, c.LOG_PREFIX_SYNC_STREAMING + 'Failed to connect or error on streaming connection, with error message: %s'],
14
14
  [c.ERROR_STREAMING_AUTH, c.LOG_PREFIX_SYNC_STREAMING + 'Failed to authenticate for streaming. Error: %s.'],
15
- [c.ERROR_HTTP, ' Response status is not OK. Status: %s. URL: %s. Message: %s'],
15
+ [c.ERROR_HTTP, 'Response status is not OK. Status: %s. URL: %s. Message: %s'],
16
16
  // client status
17
17
  [c.ERROR_CLIENT_LISTENER, 'A listener was added for %s on the SDK, which has already fired and won\'t be emitted again. The callback won\'t be executed.'],
18
18
  [c.ERROR_CLIENT_DESTROYED, '%s: Client has already been destroyed - no calls possible.'],
@@ -27,8 +27,9 @@ export var codesError = [
27
27
  [c.ERROR_INVALID, '%s: you passed an invalid %s. It must be a non-empty string.'],
28
28
  [c.ERROR_EMPTY, '%s: you passed an empty %s. It must be a non-empty string.'],
29
29
  [c.ERROR_EMPTY_ARRAY, '%s: %s must be a non-empty array.'],
30
+ [c.ERROR_NOT_BOOLEAN, '%s: you must provide a boolean param.'],
30
31
  // initialization / settings validation
31
- [c.ERROR_INVALID_IMPRESSIONS_MODE, c.LOG_PREFIX_SETTINGS + ': you passed an invalid "impressionsMode". It should be one of the following values: %s. Defaulting to "%s" mode.'],
32
+ [c.ERROR_INVALID_CONFIG_PARAM, c.LOG_PREFIX_SETTINGS + ': you passed an invalid "%s" config param. It should be one of the following values: %s. Defaulting to "%s".'],
32
33
  [c.ERROR_LOCALHOST_MODULE_REQUIRED, c.LOG_PREFIX_SETTINGS + ': an invalid value was received for "sync.localhostMode" config. A valid entity should be provided for localhost mode.'],
33
34
  [c.ERROR_STORAGE_INVALID, c.LOG_PREFIX_SETTINGS + ': The provided storage is invalid.%s Fallbacking into default MEMORY storage'],
34
35
  ];
@@ -12,12 +12,13 @@ export var codesInfo = codesWarn.concat([
12
12
  [c.NEW_FACTORY, ' New Split SDK instance created.'],
13
13
  [c.EVENTS_TRACKER_SUCCESS, c.LOG_PREFIX_EVENTS_TRACKER + 'Successfully queued %s'],
14
14
  [c.IMPRESSIONS_TRACKER_SUCCESS, c.LOG_PREFIX_IMPRESSIONS_TRACKER + 'Successfully stored %s impression(s).'],
15
+ [c.USER_CONSENT_UPDATED, 'User consent changed from %s to %s.'],
15
16
  // synchronizer
16
17
  [c.POLLING_SMART_PAUSING, c.LOG_PREFIX_SYNC_POLLING + 'Turning segments data polling %s.'],
17
18
  [c.POLLING_START, c.LOG_PREFIX_SYNC_POLLING + 'Starting polling'],
18
19
  [c.POLLING_STOP, c.LOG_PREFIX_SYNC_POLLING + 'Stopping polling'],
19
20
  [c.SYNC_SPLITS_FETCH_RETRY, c.LOG_PREFIX_SYNC_SPLITS + 'Retrying download of splits #%s. Reason: %s'],
20
- [c.SUBMITTERS_PUSH_FULL_EVENTS_QUEUE, c.LOG_PREFIX_SYNC_SUBMITTERS + 'Flushing full events queue and reseting timer.'],
21
+ [c.SUBMITTERS_PUSH_FULL_QUEUE, c.LOG_PREFIX_SYNC_SUBMITTERS + 'Flushing full %s queue and reseting timer.'],
21
22
  [c.SUBMITTERS_PUSH, c.LOG_PREFIX_SYNC_SUBMITTERS + 'Pushing %s %s.'],
22
23
  [c.STREAMING_REFRESH_TOKEN, c.LOG_PREFIX_SYNC_STREAMING + 'Refreshing streaming token in %s seconds, and connecting streaming in %s seconds.'],
23
24
  [c.STREAMING_RECONNECT, c.LOG_PREFIX_SYNC_STREAMING + 'Attempting to reconnect streaming in %s seconds.'],
@@ -26,5 +27,5 @@ export var codesInfo = codesWarn.concat([
26
27
  [c.STREAMING_DISCONNECTING, c.LOG_PREFIX_SYNC_STREAMING + 'Disconnecting streaming.'],
27
28
  [c.SYNC_START_POLLING, c.LOG_PREFIX_SYNC_MANAGER + 'Streaming not available. Starting polling.'],
28
29
  [c.SYNC_CONTINUE_POLLING, c.LOG_PREFIX_SYNC_MANAGER + 'Streaming couldn\'t connect. Continue polling.'],
29
- [c.SYNC_STOP_POLLING, c.LOG_PREFIX_SYNC_MANAGER + 'Streaming (re)connected. Syncing and stopping polling.'],
30
+ [c.SYNC_STOP_POLLING, c.LOG_PREFIX_SYNC_MANAGER + 'Streaming connected. Syncing and stopping polling.'],
30
31
  ]);
@@ -4,20 +4,22 @@ import { getMatching, getBucketing } from '../utils/key';
4
4
  import { validateSplitExistance } from '../utils/inputValidation/splitExistance';
5
5
  import { validateTrafficTypeExistance } from '../utils/inputValidation/trafficTypeExistance';
6
6
  import { SDK_NOT_READY } from '../utils/labels';
7
- import { CONTROL } from '../utils/constants';
7
+ import { CONSENT_DECLINED, CONTROL } from '../utils/constants';
8
8
  import { IMPRESSION, IMPRESSION_QUEUEING } from '../logger/constants';
9
9
  /**
10
10
  * Creator of base client with getTreatments and track methods.
11
11
  */
12
12
  // @TODO missing time tracking to collect telemetry
13
13
  export function clientFactory(params) {
14
- var readinessManager = params.sdkReadinessManager.readinessManager, storage = params.storage, _a = params.settings, log = _a.log, mode = _a.mode, impressionsTracker = params.impressionsTracker, eventTracker = params.eventTracker;
14
+ var readinessManager = params.sdkReadinessManager.readinessManager, storage = params.storage, settings = params.settings, impressionsTracker = params.impressionsTracker, eventTracker = params.eventTracker;
15
+ var log = settings.log, mode = settings.mode;
15
16
  function getTreatment(key, splitName, attributes, withConfig) {
16
17
  if (withConfig === void 0) { withConfig = false; }
17
18
  var wrapUp = function (evaluationResult) {
18
19
  var queue = [];
19
20
  var treatment = processEvaluation(evaluationResult, splitName, key, attributes, withConfig, "getTreatment" + (withConfig ? 'withConfig' : ''), queue);
20
- impressionsTracker.track(queue, attributes);
21
+ if (settings.userConsent !== CONSENT_DECLINED)
22
+ impressionsTracker.track(queue, attributes);
21
23
  return treatment;
22
24
  };
23
25
  var evaluation = evaluateFeature(log, key, splitName, attributes, storage);
@@ -34,7 +36,8 @@ export function clientFactory(params) {
34
36
  Object.keys(evaluationResults).forEach(function (splitName) {
35
37
  treatments[splitName] = processEvaluation(evaluationResults[splitName], splitName, key, attributes, withConfig, "getTreatments" + (withConfig ? 'withConfig' : ''), queue);
36
38
  });
37
- impressionsTracker.track(queue, attributes);
39
+ if (settings.userConsent !== CONSENT_DECLINED)
40
+ impressionsTracker.track(queue, attributes);
38
41
  return treatments;
39
42
  };
40
43
  var evaluations = evaluateFeatures(log, key, splitNames, attributes, storage);
@@ -88,7 +91,10 @@ export function clientFactory(params) {
88
91
  };
89
92
  // This may be async but we only warn, we don't actually care if it is valid or not in terms of queueing the event.
90
93
  validateTrafficTypeExistance(log, readinessManager, storage.splits, mode, trafficTypeName, 'track');
91
- return eventTracker.track(eventData, size);
94
+ if (settings.userConsent !== CONSENT_DECLINED)
95
+ return eventTracker.track(eventData, size);
96
+ else
97
+ return false;
92
98
  }
93
99
  return {
94
100
  getTreatment: getTreatment,
@@ -8,11 +8,12 @@ import { createLoggerAPI } from '../logger/sdkLogger';
8
8
  import { NEW_FACTORY, RETRIEVE_MANAGER } from '../logger/constants';
9
9
  import { metadataBuilder } from '../storages/metadataBuilder';
10
10
  import { SDK_SPLITS_ARRIVED, SDK_SEGMENTS_ARRIVED } from '../readiness/constants';
11
+ import { objectAssign } from '../utils/lang/objectAssign';
11
12
  /**
12
13
  * Modular SDK factory
13
14
  */
14
15
  export function sdkFactory(params) {
15
- var settings = params.settings, platform = params.platform, storageFactory = params.storageFactory, splitApiFactory = params.splitApiFactory, syncManagerFactory = params.syncManagerFactory, SignalListener = params.SignalListener, impressionsObserverFactory = params.impressionsObserverFactory, impressionListener = params.impressionListener, integrationsManagerFactory = params.integrationsManagerFactory, sdkManagerFactory = params.sdkManagerFactory, sdkClientMethodFactory = params.sdkClientMethodFactory;
16
+ var settings = params.settings, platform = params.platform, storageFactory = params.storageFactory, splitApiFactory = params.splitApiFactory, extraProps = params.extraProps, syncManagerFactory = params.syncManagerFactory, SignalListener = params.SignalListener, impressionsObserverFactory = params.impressionsObserverFactory, impressionListener = params.impressionListener, integrationsManagerFactory = params.integrationsManagerFactory, sdkManagerFactory = params.sdkManagerFactory, sdkClientMethodFactory = params.sdkClientMethodFactory;
16
17
  var log = settings.log;
17
18
  // @TODO handle non-recoverable errors: not start sync, mark the SDK as destroyed, etc.
18
19
  // We will just log and allow for the SDK to end up throwing an SDK_TIMEOUT event for devs to handle.
@@ -22,6 +23,7 @@ export function sdkFactory(params) {
22
23
  var readinessManager = sdkReadinessManager.readinessManager;
23
24
  // @TODO consider passing the settings object, so that each storage access only what it needs
24
25
  var storageFactoryParams = {
26
+ impressionsQueueSize: settings.scheduler.impressionsQueueSize,
25
27
  eventsQueueSize: settings.scheduler.eventsQueueSize,
26
28
  optimize: shouldBeOptimized(settings),
27
29
  // ATM, only used by InLocalStorage
@@ -64,11 +66,11 @@ export function sdkFactory(params) {
64
66
  syncManager && syncManager.start();
65
67
  signalListener && signalListener.start();
66
68
  log.info(NEW_FACTORY);
67
- return {
69
+ // @ts-ignore
70
+ return objectAssign({
68
71
  // Split evaluation and event tracking engine
69
72
  client: clientMethod,
70
73
  // Manager API to explore available information
71
- // @ts-ignore
72
74
  manager: function () {
73
75
  log.debug(RETRIEVE_MANAGER);
74
76
  return managerInstance;
@@ -76,5 +78,5 @@ export function sdkFactory(params) {
76
78
  // Logger wrapper API
77
79
  Logger: createLoggerAPI(settings.log),
78
80
  settings: settings,
79
- };
81
+ }, extraProps && extraProps(settings, syncManager));
80
82
  }
@@ -0,0 +1,30 @@
1
+ import { ERROR_NOT_BOOLEAN, USER_CONSENT_UPDATED } from '../logger/constants';
2
+ import { CONSENT_GRANTED, CONSENT_DECLINED } from '../utils/constants';
3
+ // Extend client-side factory instances with user consent getter/setter
4
+ export function userConsentProps(settings, syncManager) {
5
+ var log = settings.log;
6
+ return {
7
+ setUserConsent: function (consent) {
8
+ var _a, _b;
9
+ // validate input param
10
+ if (typeof consent !== 'boolean') {
11
+ log.error(ERROR_NOT_BOOLEAN, ['setUserConsent']);
12
+ return false;
13
+ }
14
+ var newConsentStatus = consent ? CONSENT_GRANTED : CONSENT_DECLINED;
15
+ if (settings.userConsent !== newConsentStatus) {
16
+ // resume/pause submitters
17
+ if (consent)
18
+ (_a = syncManager === null || syncManager === void 0 ? void 0 : syncManager.submitter) === null || _a === void 0 ? void 0 : _a.start();
19
+ else
20
+ (_b = syncManager === null || syncManager === void 0 ? void 0 : syncManager.submitter) === null || _b === void 0 ? void 0 : _b.stop();
21
+ log.info(USER_CONSENT_UPDATED, [settings.userConsent, newConsentStatus]); // @ts-ignore, modify readonly prop
22
+ settings.userConsent = newConsentStatus;
23
+ }
24
+ return true;
25
+ },
26
+ getUserConsent: function () {
27
+ return settings.userConsent;
28
+ }
29
+ };
30
+ }
@@ -13,9 +13,19 @@ var KeyBuilderCS = /** @class */ (function (_super) {
13
13
  * @override
14
14
  */
15
15
  KeyBuilderCS.prototype.buildSegmentNameKey = function (segmentName) {
16
- return this.matchingKey + "." + this.prefix + ".segment." + segmentName;
16
+ return this.prefix + "." + this.matchingKey + ".segment." + segmentName;
17
17
  };
18
18
  KeyBuilderCS.prototype.extractSegmentName = function (builtSegmentKeyName) {
19
+ var prefix = this.prefix + "." + this.matchingKey + ".segment.";
20
+ if (startsWith(builtSegmentKeyName, prefix))
21
+ return builtSegmentKeyName.substr(prefix.length);
22
+ };
23
+ // @BREAKING: The key used to start with the matching key instead of the prefix, this was changed on version 10.17.3
24
+ KeyBuilderCS.prototype.buildOldSegmentNameKey = function (segmentName) {
25
+ return this.matchingKey + "." + this.prefix + ".segment." + segmentName;
26
+ };
27
+ // @BREAKING: The key used to start with the matching key instead of the prefix, this was changed on version 10.17.3
28
+ KeyBuilderCS.prototype.extractOldSegmentKey = function (builtSegmentKeyName) {
19
29
  var prefix = this.matchingKey + "." + this.prefix + ".segment.";
20
30
  if (startsWith(builtSegmentKeyName, prefix))
21
31
  return builtSegmentKeyName.substr(prefix.length);
@@ -57,9 +57,29 @@ var MySegmentsCacheInLocal = /** @class */ (function (_super) {
57
57
  var index;
58
58
  // Scan current values from localStorage
59
59
  var storedSegmentNames = Object.keys(localStorage).reduce(function (accum, key) {
60
- var name = _this.keys.extractSegmentName(key);
61
- if (name)
62
- accum.push(name);
60
+ var segmentName = _this.keys.extractSegmentName(key);
61
+ if (segmentName) {
62
+ accum.push(segmentName);
63
+ }
64
+ else {
65
+ // @BREAKING: This is only to clean up "old" keys. Remove this whole else code block.
66
+ segmentName = _this.keys.extractOldSegmentKey(key);
67
+ if (segmentName) { // this was an old segment key, let's clean up.
68
+ var newSegmentKey = _this.keys.buildSegmentNameKey(segmentName);
69
+ try {
70
+ // If the new format key is not there, create it.
71
+ if (!localStorage.getItem(newSegmentKey) && names.indexOf(segmentName) > -1) {
72
+ localStorage.setItem(newSegmentKey, DEFINED);
73
+ // we are migrating a segment, let's track it.
74
+ accum.push(segmentName);
75
+ }
76
+ localStorage.removeItem(key); // we migrated the current key, let's delete it.
77
+ }
78
+ catch (e) {
79
+ _this.log.error(e);
80
+ }
81
+ }
82
+ }
63
83
  return accum;
64
84
  }, []);
65
85
  // Extreme fast => everything is empty
@@ -30,7 +30,7 @@ export function InLocalStorage(options) {
30
30
  return {
31
31
  splits: new SplitsCacheInLocal(log, keys, expirationTimestamp, params.splitFiltersValidation),
32
32
  segments: new MySegmentsCacheInLocal(log, keys),
33
- impressions: new ImpressionsCacheInMemory(),
33
+ impressions: new ImpressionsCacheInMemory(params.impressionsQueueSize),
34
34
  impressionCounts: params.optimize ? new ImpressionCountsCacheInMemory() : undefined,
35
35
  events: new EventsCacheInMemory(params.eventsQueueSize),
36
36
  destroy: function () {
@@ -1,13 +1,27 @@
1
1
  var ImpressionsCacheInMemory = /** @class */ (function () {
2
- function ImpressionsCacheInMemory() {
2
+ /**
3
+ *
4
+ * @param impressionsQueueSize number of queued impressions to call onFullQueueCb.
5
+ * Default value is 0, that means no maximum value, in case we want to avoid this being triggered.
6
+ */
7
+ function ImpressionsCacheInMemory(impressionsQueueSize) {
8
+ if (impressionsQueueSize === void 0) { impressionsQueueSize = 0; }
9
+ this.maxQueue = impressionsQueueSize;
3
10
  this.queue = [];
4
11
  }
12
+ ImpressionsCacheInMemory.prototype.setOnFullQueueCb = function (cb) {
13
+ this.onFullQueue = cb;
14
+ };
5
15
  /**
6
16
  * Store impressions in sequential order
7
17
  */
8
18
  ImpressionsCacheInMemory.prototype.track = function (data) {
9
19
  var _a;
10
20
  (_a = this.queue).push.apply(_a, data);
21
+ // Check if the cache queue is full and we need to flush it.
22
+ if (this.maxQueue > 0 && this.queue.length >= this.maxQueue && this.onFullQueue) {
23
+ this.onFullQueue();
24
+ }
11
25
  };
12
26
  /**
13
27
  * Clear the data stored on the cache.
@@ -13,7 +13,7 @@ export function InMemoryStorageFactory(params) {
13
13
  return {
14
14
  splits: new SplitsCacheInMemory(),
15
15
  segments: new SegmentsCacheInMemory(),
16
- impressions: new ImpressionsCacheInMemory(),
16
+ impressions: new ImpressionsCacheInMemory(params.impressionsQueueSize),
17
17
  impressionCounts: params.optimize ? new ImpressionCountsCacheInMemory() : undefined,
18
18
  events: new EventsCacheInMemory(params.eventsQueueSize),
19
19
  // When using MEMORY we should clean all the caches to leave them empty
@@ -13,7 +13,7 @@ export function InMemoryStorageCSFactory(params) {
13
13
  return {
14
14
  splits: new SplitsCacheInMemory(),
15
15
  segments: new MySegmentsCacheInMemory(),
16
- impressions: new ImpressionsCacheInMemory(),
16
+ impressions: new ImpressionsCacheInMemory(params.impressionsQueueSize),
17
17
  impressionCounts: params.optimize ? new ImpressionCountsCacheInMemory() : undefined,
18
18
  events: new EventsCacheInMemory(params.eventsQueueSize),
19
19
  // When using MEMORY we should clean all the caches to leave them empty
@@ -51,7 +51,7 @@ export function PluggableStorage(options) {
51
51
  validatePluggableStorageOptions(options);
52
52
  var prefix = validatePrefix(options.prefix);
53
53
  function PluggableStorageFactory(_a) {
54
- var log = _a.log, metadata = _a.metadata, onReadyCb = _a.onReadyCb, mode = _a.mode, eventsQueueSize = _a.eventsQueueSize, optimize = _a.optimize;
54
+ var log = _a.log, metadata = _a.metadata, onReadyCb = _a.onReadyCb, mode = _a.mode, eventsQueueSize = _a.eventsQueueSize, impressionsQueueSize = _a.impressionsQueueSize, optimize = _a.optimize;
55
55
  var keys = new KeyBuilderSS(prefix, metadata);
56
56
  var wrapper = wrapperAdapter(log, options.wrapper);
57
57
  var isPartialConsumer = mode === CONSUMER_PARTIAL_MODE;
@@ -60,7 +60,7 @@ export function PluggableStorage(options) {
60
60
  return {
61
61
  splits: new SplitsCachePluggable(log, keys, wrapper),
62
62
  segments: new SegmentsCachePluggable(log, keys, wrapper),
63
- impressions: isPartialConsumer ? new ImpressionsCacheInMemory() : new ImpressionsCachePluggable(log, keys.buildImpressionsKey(), wrapper, metadata),
63
+ impressions: isPartialConsumer ? new ImpressionsCacheInMemory(impressionsQueueSize) : new ImpressionsCachePluggable(log, keys.buildImpressionsKey(), wrapper, metadata),
64
64
  impressionCounts: optimize ? new ImpressionCountsCacheInMemory() : undefined,
65
65
  events: isPartialConsumer ? promisifyEventsTrack(new EventsCacheInMemory(eventsQueueSize)) : new EventsCachePluggable(log, keys.buildEventsKey(), wrapper, metadata),
66
66
  // @TODO add telemetry cache when required
@@ -55,6 +55,7 @@ export function pushManagerFactory(params, pollingManager) {
55
55
  // It is used to halt the `connectPush` process if it was in progress.
56
56
  var disconnected;
57
57
  // flag that indicates a PUSH_NONRETRYABLE_ERROR, condition with which starting pushManager again is ignored.
58
+ // true if STREAMING_DISABLED control event, or 'pushEnabled: false', or non-recoverable SSE or Auth errors.
58
59
  var disabled; // `disabled` implies `disconnected === true`
59
60
  /** PushManager functions related to initialization */
60
61
  var connectPushRetryBackoff = new Backoff(connectPush, settings.scheduler.pushRetryBackoffBase);
@@ -255,13 +256,15 @@ export function pushManagerFactory(params, pollingManager) {
255
256
  return objectAssign(
256
257
  // Expose Event Emitter functionality and Event constants
257
258
  Object.create(pushEmitter), {
258
- // Stop/pause push mode
259
+ // Stop/pause push mode.
260
+ // It doesn't emit events. Neither PUSH_SUBSYSTEM_DOWN to start polling.
259
261
  stop: function () {
260
262
  disconnectPush(); // `handleNonRetryableError` cannot be used as `stop`, because it emits PUSH_SUBSYSTEM_DOWN event, which starts polling.
261
263
  if (userKey)
262
264
  this.remove(userKey); // Necessary to properly resume streaming in client-side (e.g., RN SDK transition to foreground).
263
265
  },
264
- // Start/resume push mode
266
+ // Start/resume push mode.
267
+ // It eventually emits PUSH_SUBSYSTEM_DOWN, that starts polling, or PUSH_SUBSYSTEM_UP, that executes a syncAll
265
268
  start: function () {
266
269
  // Guard condition to avoid calling `connectPush` again if the `start` method is called multiple times or if push has been disabled.
267
270
  if (disabled || disconnected === false)
@@ -272,6 +275,10 @@ export function pushManagerFactory(params, pollingManager) {
272
275
  else
273
276
  setTimeout(connectPush); // server-side runs in next cycle as in client-side, for consistency with client-side
274
277
  },
278
+ // true/false if start or stop was called last respectively
279
+ isRunning: function () {
280
+ return disconnected === false;
281
+ },
275
282
  // [Only for client-side]
276
283
  add: function (userKey, mySegmentsSyncTask) {
277
284
  var hash = hashUserKey(userKey);
@@ -1,11 +1,12 @@
1
1
  import { submitterSyncTaskFactory } from './submitterSyncTask';
2
- import { SUBMITTERS_PUSH_FULL_EVENTS_QUEUE } from '../../logger/constants';
2
+ import { SUBMITTERS_PUSH_FULL_QUEUE } from '../../logger/constants';
3
+ var DATA_NAME = 'events';
3
4
  /**
4
5
  * Sync task that periodically posts tracked events
5
6
  */
6
7
  export function eventsSyncTaskFactory(log, postEventsBulk, eventsCache, eventsPushRate, eventsFirstPushWindow, latencyTracker) {
7
8
  // don't retry events.
8
- var syncTask = submitterSyncTaskFactory(log, postEventsBulk, eventsCache, eventsPushRate, 'queued events', latencyTracker);
9
+ var syncTask = submitterSyncTaskFactory(log, postEventsBulk, eventsCache, eventsPushRate, DATA_NAME, latencyTracker);
9
10
  // Set a timer for the first push of events,
10
11
  if (eventsFirstPushWindow > 0) {
11
12
  var stopEventPublisherTimeout_1;
@@ -19,10 +20,14 @@ export function eventsSyncTaskFactory(log, postEventsBulk, eventsCache, eventsPu
19
20
  originalStop_1();
20
21
  };
21
22
  }
22
- // register eventsSubmitter to be executed when events cache is full
23
+ // register events submitter to be executed when events cache is full
23
24
  eventsCache.setOnFullQueueCb(function () {
24
- log.info(SUBMITTERS_PUSH_FULL_EVENTS_QUEUE);
25
- syncTask.execute();
25
+ if (syncTask.isRunning()) {
26
+ log.info(SUBMITTERS_PUSH_FULL_QUEUE, [DATA_NAME]);
27
+ syncTask.execute();
28
+ }
29
+ // If submitter is stopped (e.g., user consent declined or unknown, or app state offline), we don't send the data.
30
+ // Data will be sent when submitter is resumed.
26
31
  });
27
32
  return syncTask;
28
33
  }
@@ -1,5 +1,7 @@
1
1
  import { groupBy, forOwn } from '../../utils/lang';
2
2
  import { submitterSyncTaskFactory } from './submitterSyncTask';
3
+ import { SUBMITTERS_PUSH_FULL_QUEUE } from '../../logger/constants';
4
+ var DATA_NAME = 'impressions';
3
5
  /**
4
6
  * Converts `impressions` data from cache into request payload.
5
7
  */
@@ -32,5 +34,15 @@ export function fromImpressionsCollector(sendLabels, data) {
32
34
  export function impressionsSyncTaskFactory(log, postTestImpressionsBulk, impressionsCache, impressionsRefreshRate, sendLabels, latencyTracker) {
33
35
  if (sendLabels === void 0) { sendLabels = false; }
34
36
  // retry impressions only once.
35
- return submitterSyncTaskFactory(log, postTestImpressionsBulk, impressionsCache, impressionsRefreshRate, 'impressions', latencyTracker, fromImpressionsCollector.bind(undefined, sendLabels), 1);
37
+ var syncTask = submitterSyncTaskFactory(log, postTestImpressionsBulk, impressionsCache, impressionsRefreshRate, DATA_NAME, latencyTracker, fromImpressionsCollector.bind(undefined, sendLabels), 1);
38
+ // register impressions submitter to be executed when impressions cache is full
39
+ impressionsCache.setOnFullQueueCb(function () {
40
+ if (syncTask.isRunning()) {
41
+ log.info(SUBMITTERS_PUSH_FULL_QUEUE, [DATA_NAME]);
42
+ syncTask.execute();
43
+ }
44
+ // If submitter is stopped (e.g., user consent declined or unknown, or app state offline), we don't send the data.
45
+ // Data will be sent when submitter is resumed.
46
+ });
47
+ return syncTask;
36
48
  }