@splitsoftware/splitio-commons 1.4.1 → 1.4.2-rc.2

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 (54) hide show
  1. package/CHANGES.txt +5 -0
  2. package/cjs/consent/sdkUserConsent.js +3 -3
  3. package/cjs/listeners/browser.js +7 -6
  4. package/cjs/sdkFactory/index.js +20 -2
  5. package/cjs/storages/AbstractSplitsCacheSync.js +1 -1
  6. package/cjs/storages/dataLoader.js +23 -15
  7. package/cjs/storages/inMemory/InMemoryStorage.js +15 -1
  8. package/cjs/storages/inMemory/InMemoryStorageCS.js +12 -0
  9. package/cjs/sync/submitters/submitterManager.js +28 -4
  10. package/cjs/sync/syncManagerOnline.js +40 -29
  11. package/cjs/sync/syncTask.js +15 -10
  12. package/cjs/trackers/impressionObserver/impressionObserverCS.js +1 -1
  13. package/cjs/utils/settingsValidation/index.js +13 -1
  14. package/esm/consent/sdkUserConsent.js +3 -3
  15. package/esm/listeners/browser.js +7 -6
  16. package/esm/sdkFactory/index.js +21 -3
  17. package/esm/storages/AbstractSplitsCacheSync.js +1 -1
  18. package/esm/storages/dataLoader.js +21 -13
  19. package/esm/storages/inMemory/InMemoryStorage.js +15 -1
  20. package/esm/storages/inMemory/InMemoryStorageCS.js +12 -0
  21. package/esm/sync/submitters/submitterManager.js +28 -4
  22. package/esm/sync/syncManagerOnline.js +40 -29
  23. package/esm/sync/syncTask.js +15 -10
  24. package/esm/trackers/impressionObserver/impressionObserverCS.js +1 -1
  25. package/esm/utils/settingsValidation/index.js +13 -1
  26. package/package.json +1 -1
  27. package/src/consent/sdkUserConsent.ts +4 -3
  28. package/src/listeners/browser.ts +8 -6
  29. package/src/sdkClient/clientAttributesDecoration.ts +9 -9
  30. package/src/sdkFactory/index.ts +23 -4
  31. package/src/storages/AbstractSplitsCacheSync.ts +1 -1
  32. package/src/storages/dataLoader.ts +20 -13
  33. package/src/storages/inMemory/InMemoryStorage.ts +16 -1
  34. package/src/storages/inMemory/InMemoryStorageCS.ts +13 -0
  35. package/src/sync/submitters/submitterManager.ts +30 -4
  36. package/src/sync/submitters/types.ts +7 -0
  37. package/src/sync/syncManagerOnline.ts +35 -23
  38. package/src/sync/syncTask.ts +17 -11
  39. package/src/sync/types.ts +2 -1
  40. package/src/trackers/impressionObserver/impressionObserverCS.ts +1 -1
  41. package/src/types.ts +11 -3
  42. package/src/utils/settingsValidation/index.ts +15 -1
  43. package/types/integrations/ga/autoRequire.d.ts +4 -0
  44. package/types/storages/dataLoader.d.ts +2 -2
  45. package/types/sync/submitters/submitterManager.d.ts +2 -1
  46. package/types/sync/submitters/types.d.ts +6 -0
  47. package/types/sync/syncTask.d.ts +2 -2
  48. package/types/sync/types.d.ts +2 -1
  49. package/types/trackers/impressionObserver/impressionObserverCS.d.ts +2 -2
  50. package/types/types.d.ts +10 -2
  51. package/types/utils/settingsValidation/index.d.ts +1 -0
  52. package/cjs/sync/syncTaskComposite.js +0 -26
  53. package/esm/sync/syncTaskComposite.js +0 -22
  54. package/src/sync/syncTaskComposite.ts +0 -26
package/CHANGES.txt CHANGED
@@ -1,3 +1,8 @@
1
+ 1.5.0 (June 24, 2022)
2
+ - Added a new config option to control the tasks that listen or poll for updates on feature flags and segments, via the new config sync.enabled . Running online Split will always pull the most recent updates upon initialization, this only affects updates fetching on a running instance. Useful when a consistent session experience is a must or to save resources when updates are not being used.
3
+ - Updated telemetry logic to track the anonymous config for user consent flag set to declined or unknown.
4
+ - Updated submitters logic, to avoid duplicating the post of impressions to Split cloud when the SDK is destroyed while its periodic post of impressions is running.
5
+
1
6
  1.4.1 (June 13, 2022)
2
7
  - Bugfixing - Updated submitters logic, to avoid dropping impressions and events that are being tracked while POST request is pending.
3
8
 
@@ -31,10 +31,10 @@ function createUserConsentAPI(params) {
31
31
  log.info(constants_1.USER_CONSENT_UPDATED, [settings.userConsent, newConsentStatus]); // @ts-ignore, modify readonly prop
32
32
  settings.userConsent = newConsentStatus;
33
33
  if (consent) { // resumes submitters if transitioning to GRANTED
34
- (_a = syncManager === null || syncManager === void 0 ? void 0 : syncManager.submitter) === null || _a === void 0 ? void 0 : _a.start();
34
+ (_a = syncManager === null || syncManager === void 0 ? void 0 : syncManager.submitterManager) === null || _a === void 0 ? void 0 : _a.start();
35
35
  }
36
- else { // pauses submitters and drops tracked data if transitioning to DECLINED
37
- (_b = syncManager === null || syncManager === void 0 ? void 0 : syncManager.submitter) === null || _b === void 0 ? void 0 : _b.stop();
36
+ else { // pauses submitters (except telemetry), and drops tracked data if transitioning to DECLINED
37
+ (_b = syncManager === null || syncManager === void 0 ? void 0 : syncManager.submitterManager) === null || _b === void 0 ? void 0 : _b.stop(true);
38
38
  // @ts-ignore, clear method is present in storage for standalone and partial consumer mode
39
39
  if (events.clear)
40
40
  events.clear(); // @ts-ignore
@@ -53,7 +53,7 @@ var BrowserSignalListener = /** @class */ (function () {
53
53
  BrowserSignalListener.prototype.flushData = function () {
54
54
  if (!this.syncManager)
55
55
  return; // In consumer mode there is not sync manager and data to flush
56
- // Flush data if there is user consent
56
+ // Flush impressions & events data if there is user consent
57
57
  if ((0, consent_1.isConsentGranted)(this.settings)) {
58
58
  var eventsUrl = this.settings.urls.events;
59
59
  var extraMetadata = {
@@ -64,11 +64,12 @@ var BrowserSignalListener = /** @class */ (function () {
64
64
  this._flushData(eventsUrl + '/events/beacon', this.storage.events, this.serviceApi.postEventsBulk);
65
65
  if (this.storage.impressionCounts)
66
66
  this._flushData(eventsUrl + '/testImpressions/count/beacon', this.storage.impressionCounts, this.serviceApi.postTestImpressionsCount, impressionCountsSubmitter_1.fromImpressionCountsCollector);
67
- if (this.storage.telemetry) {
68
- var telemetryUrl = this.settings.urls.telemetry;
69
- var telemetryCacheAdapter = (0, telemetrySubmitter_1.telemetryCacheStatsAdapter)(this.storage.telemetry, this.storage.splits, this.storage.segments);
70
- this._flushData(telemetryUrl + '/v1/metrics/usage/beacon', telemetryCacheAdapter, this.serviceApi.postMetricsUsage);
71
- }
67
+ }
68
+ // Flush telemetry data
69
+ if (this.storage.telemetry) {
70
+ var telemetryUrl = this.settings.urls.telemetry;
71
+ var telemetryCacheAdapter = (0, telemetrySubmitter_1.telemetryCacheStatsAdapter)(this.storage.telemetry, this.storage.splits, this.storage.segments);
72
+ this._flushData(telemetryUrl + '/v1/metrics/usage/beacon', telemetryCacheAdapter, this.serviceApi.postMetricsUsage);
72
73
  }
73
74
  // Close streaming connection
74
75
  if (this.syncManager.pushManager)
@@ -25,13 +25,14 @@ function sdkFactory(params) {
25
25
  (0, apiKey_1.validateAndTrackApiKey)(log, settings.core.authorizationKey);
26
26
  var sdkReadinessManager = (0, sdkReadinessManager_1.sdkReadinessManagerFactory)(log, platform.EventEmitter, settings.startup.readyTimeout);
27
27
  var readiness = sdkReadinessManager.readinessManager;
28
+ var matchingKey = (0, key_1.getMatching)(settings.core.key);
28
29
  // @TODO consider passing the settings object, so that each storage access only what it needs
29
30
  var storageFactoryParams = {
30
31
  impressionsQueueSize: settings.scheduler.impressionsQueueSize,
31
32
  eventsQueueSize: settings.scheduler.eventsQueueSize,
32
33
  optimize: (0, utils_1.shouldBeOptimized)(settings),
33
34
  // ATM, only used by InLocalStorage
34
- matchingKey: (0, key_1.getMatching)(settings.core.key),
35
+ matchingKey: matchingKey,
35
36
  splitFiltersValidation: settings.sync.__splitFiltersValidation,
36
37
  // ATM, only used by PluggableStorage
37
38
  mode: settings.mode,
@@ -47,7 +48,21 @@ function sdkFactory(params) {
47
48
  log: log
48
49
  };
49
50
  var storage = storageFactory(storageFactoryParams);
50
- // @TODO add support for dataloader: `if (params.dataLoader) params.dataLoader(storage);`
51
+ // @TODO dataLoader requires validation
52
+ if (settings.dataLoader) {
53
+ settings.dataLoader(storage, matchingKey);
54
+ Promise.resolve(storage.splits.checkCache()).then(function (cacheReady) {
55
+ if (cacheReady) {
56
+ if (settings.sync.onlySubmitters) { // emit SDK_READY to not timeout when not synchronizing splits & segments
57
+ readiness.splits.emit(constants_2.SDK_SPLITS_ARRIVED);
58
+ readiness.segments.emit(constants_2.SDK_SEGMENTS_ARRIVED);
59
+ }
60
+ else { // emit SDK_READY_FROM_CACHE
61
+ readiness.splits.emit(constants_2.SDK_SPLITS_CACHE_LOADED);
62
+ }
63
+ }
64
+ });
65
+ }
51
66
  var integrationsManager = integrationsManagerFactory && integrationsManagerFactory({ settings: settings, storage: storage });
52
67
  // trackers
53
68
  var observer = impressionsObserverFactory && impressionsObserverFactory();
@@ -79,6 +94,9 @@ function sdkFactory(params) {
79
94
  // Logger wrapper API
80
95
  Logger: (0, sdkLogger_1.createLoggerAPI)(settings.log),
81
96
  settings: settings,
97
+ // @TODO remove
98
+ __storage: storage,
99
+ __ctx: ctx
82
100
  }, extraProps && extraProps(ctx));
83
101
  }
84
102
  exports.sdkFactory = sdkFactory;
@@ -33,7 +33,7 @@ var AbstractSplitsCacheSync = /** @class */ (function () {
33
33
  * It is used as condition to emit SDK_SPLITS_CACHE_LOADED, and then SDK_READY_FROM_CACHE.
34
34
  */
35
35
  AbstractSplitsCacheSync.prototype.checkCache = function () {
36
- return false;
36
+ return this.getChangeNumber() > -1;
37
37
  };
38
38
  /**
39
39
  * Kill `name` split and set `defaultTreatment` and `changeNumber`.
@@ -1,26 +1,27 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.dataLoaderFactory = void 0;
3
+ exports.DataLoaderFactory = void 0;
4
4
  var browser_1 = require("../utils/constants/browser");
5
5
  /**
6
- * Factory of client-side storage loader
6
+ * Factory of storage loader
7
7
  *
8
8
  * @param preloadedData validated data following the format proposed in https://github.com/godaddy/split-javascript-data-loader
9
9
  * and extended with a `mySegmentsData` property.
10
10
  * @returns function to preload the storage
11
11
  */
12
- function dataLoaderFactory(preloadedData) {
12
+ function DataLoaderFactory(preloadedData) {
13
13
  /**
14
14
  * Storage-agnostic adaptation of `loadDataIntoLocalStorage` function
15
15
  * (https://github.com/godaddy/split-javascript-data-loader/blob/master/src/load-data.js)
16
16
  *
17
17
  * @param storage object containing `splits` and `segments` cache (client-side variant)
18
- * @param userId user key string of the provided MySegmentsCache
18
+ * @param userKey user key (matching key) of the provided MySegmentsCache
19
19
  *
20
- * @TODO extend to support SegmentsCache (server-side variant) by making `userId` optional and adding the corresponding logic.
21
20
  * @TODO extend to load data on shared mySegments storages. Be specific when emitting SDK_READY_FROM_CACHE on shared clients. Maybe the serializer should provide the `useSegments` flag.
21
+ * @TODO add logs, and input validation in this module, in favor of size reduction.
22
+ * @TODO unit tests
22
23
  */
23
- return function loadData(storage, userId) {
24
+ return function loadData(storage, userKey) {
24
25
  // Do not load data if current preloadedData is empty
25
26
  if (Object.keys(preloadedData).length === 0)
26
27
  return;
@@ -36,16 +37,23 @@ function dataLoaderFactory(preloadedData) {
36
37
  storage.splits.setChangeNumber(since);
37
38
  // splitsData in an object where the property is the split name and the pertaining value is a stringified json of its data
38
39
  storage.splits.addSplits(Object.keys(splitsData).map(function (splitName) { return [splitName, splitsData[splitName]]; }));
39
- // add mySegments data
40
- var mySegmentsData = preloadedData.mySegmentsData && preloadedData.mySegmentsData[userId];
41
- if (!mySegmentsData) {
42
- // segmentsData in an object where the property is the segment name and the pertaining value is a stringified object that contains the `added` array of userIds
43
- mySegmentsData = Object.keys(segmentsData).filter(function (segmentName) {
44
- var userIds = JSON.parse(segmentsData[segmentName]).added;
45
- return Array.isArray(userIds) && userIds.indexOf(userId) > -1;
40
+ if (userKey) { // add mySegments data (client-side)
41
+ var mySegmentsData = preloadedData.mySegmentsData && preloadedData.mySegmentsData[userKey];
42
+ if (!mySegmentsData) {
43
+ // segmentsData in an object where the property is the segment name and the pertaining value is a stringified object that contains the `added` array of userIds
44
+ mySegmentsData = Object.keys(segmentsData).filter(function (segmentName) {
45
+ var userKeys = segmentsData[segmentName];
46
+ return userKeys.indexOf(userKey) > -1;
47
+ });
48
+ }
49
+ storage.segments.resetSegments(mySegmentsData);
50
+ }
51
+ else { // add segments data (server-side)
52
+ Object.keys(segmentsData).filter(function (segmentName) {
53
+ var userKeys = segmentsData[segmentName];
54
+ storage.segments.addToSegment(segmentName, userKeys);
46
55
  });
47
56
  }
48
- storage.segments.resetSegments(mySegmentsData);
49
57
  };
50
58
  }
51
- exports.dataLoaderFactory = dataLoaderFactory;
59
+ exports.DataLoaderFactory = DataLoaderFactory;
@@ -8,6 +8,7 @@ var EventsCacheInMemory_1 = require("./EventsCacheInMemory");
8
8
  var ImpressionCountsCacheInMemory_1 = require("./ImpressionCountsCacheInMemory");
9
9
  var constants_1 = require("../../utils/constants");
10
10
  var TelemetryCacheInMemory_1 = require("./TelemetryCacheInMemory");
11
+ var sets_1 = require("../../utils/lang/sets");
11
12
  /**
12
13
  * InMemory storage factory for standalone server-side SplitFactory
13
14
  *
@@ -28,7 +29,20 @@ function InMemoryStorageFactory(params) {
28
29
  this.impressions.clear();
29
30
  this.impressionCounts && this.impressionCounts.clear();
30
31
  this.events.clear();
31
- }
32
+ },
33
+ // @ts-ignore, private method, for POC
34
+ getSnapshot: function () {
35
+ var _this = this;
36
+ return {
37
+ lastUpdated: Date.now(),
38
+ since: this.splits.changeNumber,
39
+ splitsData: this.splits.splitsCache,
40
+ segmentsData: Object.keys(this.segments.segmentCache).reduce(function (prev, cur) {
41
+ prev[cur] = (0, sets_1.setToArray)(_this.segments.segmentCache[cur]);
42
+ return prev;
43
+ }, {})
44
+ };
45
+ },
32
46
  };
33
47
  }
34
48
  exports.InMemoryStorageFactory = InMemoryStorageFactory;
@@ -29,6 +29,18 @@ function InMemoryStorageCSFactory(params) {
29
29
  this.impressionCounts && this.impressionCounts.clear();
30
30
  this.events.clear();
31
31
  },
32
+ // @ts-ignore, private method, for POC
33
+ getSnapshot: function () {
34
+ var _a;
35
+ return {
36
+ lastUpdated: Date.now(),
37
+ since: this.splits.changeNumber,
38
+ splitsData: this.splits.splitsCache,
39
+ mySegmentsData: (_a = {},
40
+ _a[params.matchingKey] = Object.keys(this.segments.segmentCache),
41
+ _a)
42
+ };
43
+ },
32
44
  // When using shared instanciation with MEMORY we reuse everything but segments (they are unique per key)
33
45
  shared: function () {
34
46
  return {
@@ -1,7 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.submitterManagerFactory = void 0;
4
- var syncTaskComposite_1 = require("../syncTaskComposite");
5
4
  var eventsSubmitter_1 = require("./eventsSubmitter");
6
5
  var impressionsSubmitter_1 = require("./impressionsSubmitter");
7
6
  var impressionCountsSubmitter_1 = require("./impressionCountsSubmitter");
@@ -15,8 +14,33 @@ function submitterManagerFactory(params) {
15
14
  if (impressionCountsSubmitter)
16
15
  submitters.push(impressionCountsSubmitter);
17
16
  var telemetrySubmitter = (0, telemetrySubmitter_1.telemetrySubmitterFactory)(params);
18
- if (telemetrySubmitter)
19
- submitters.push(telemetrySubmitter);
20
- return (0, syncTaskComposite_1.syncTaskComposite)(submitters);
17
+ return {
18
+ // `onlyTelemetry` true if SDK is created with userConsent not GRANTED
19
+ start: function (onlyTelemetry) {
20
+ if (!onlyTelemetry)
21
+ submitters.forEach(function (submitter) { return submitter.start(); });
22
+ if (telemetrySubmitter)
23
+ telemetrySubmitter.start();
24
+ },
25
+ // `allExceptTelemetry` true if userConsent is changed to DECLINED
26
+ stop: function (allExceptTelemetry) {
27
+ submitters.forEach(function (submitter) { return submitter.stop(); });
28
+ if (!allExceptTelemetry && telemetrySubmitter)
29
+ telemetrySubmitter.stop();
30
+ },
31
+ isRunning: function () {
32
+ return submitters.some(function (submitter) { return submitter.isRunning(); });
33
+ },
34
+ // Flush data. Called with `onlyTelemetry` true if SDK is destroyed with userConsent not GRANTED
35
+ execute: function (onlyTelemetry) {
36
+ var promises = onlyTelemetry ? [] : submitters.map(function (submitter) { return submitter.execute(); });
37
+ if (telemetrySubmitter)
38
+ promises.push(telemetrySubmitter.execute());
39
+ return Promise.all(promises);
40
+ },
41
+ isExecuting: function () {
42
+ return submitters.some(function (submitter) { return submitter.isExecuting(); });
43
+ }
44
+ };
21
45
  }
22
46
  exports.submitterManagerFactory = submitterManagerFactory;
@@ -19,16 +19,16 @@ function syncManagerOnlineFactory(pollingManagerFactory, pushManagerFactory) {
19
19
  * SyncManager factory for modular SDK
20
20
  */
21
21
  return function (params) {
22
- var settings = params.settings, _a = params.settings, log = _a.log, streamingEnabled = _a.streamingEnabled, telemetryTracker = params.telemetryTracker;
22
+ var settings = params.settings, _a = params.settings, log = _a.log, streamingEnabled = _a.streamingEnabled, _b = _a.sync, syncEnabled = _b.enabled, onlySubmitters = _b.onlySubmitters, telemetryTracker = params.telemetryTracker;
23
23
  /** Polling Manager */
24
- var pollingManager = pollingManagerFactory && pollingManagerFactory(params);
24
+ var pollingManager = onlySubmitters ? undefined : pollingManagerFactory && pollingManagerFactory(params);
25
25
  /** Push Manager */
26
- var pushManager = streamingEnabled && pollingManager && pushManagerFactory ?
26
+ var pushManager = syncEnabled && streamingEnabled && pollingManager && pushManagerFactory ?
27
27
  pushManagerFactory(params, pollingManager) :
28
28
  undefined;
29
29
  /** Submitter Manager */
30
30
  // It is not inyected as push and polling managers, because at the moment it is required
31
- var submitter = (0, submitterManager_1.submitterManagerFactory)(params);
31
+ var submitterManager = (0, submitterManager_1.submitterManagerFactory)(params);
32
32
  /** Sync Manager logic */
33
33
  function startPolling() {
34
34
  if (pollingManager.isRunning()) {
@@ -61,7 +61,7 @@ function syncManagerOnlineFactory(pollingManagerFactory, pushManagerFactory) {
61
61
  // E.g.: user consent, app state changes (Page hide, Foreground/Background, Online/Offline).
62
62
  pollingManager: pollingManager,
63
63
  pushManager: pushManager,
64
- submitter: submitter,
64
+ submitterManager: submitterManager,
65
65
  /**
66
66
  * Method used to start the syncManager for the first time, or resume it after being stopped.
67
67
  */
@@ -69,21 +69,29 @@ function syncManagerOnlineFactory(pollingManagerFactory, pushManagerFactory) {
69
69
  running = true;
70
70
  // start syncing splits and segments
71
71
  if (pollingManager) {
72
- if (pushManager) {
73
- // Doesn't call `syncAll` when the syncManager is resuming
72
+ // If synchronization is disabled pushManager and pollingManager should not start
73
+ if (syncEnabled) {
74
+ if (pushManager) {
75
+ // Doesn't call `syncAll` when the syncManager is resuming
76
+ if (startFirstTime) {
77
+ pollingManager.syncAll();
78
+ startFirstTime = false;
79
+ }
80
+ pushManager.start();
81
+ }
82
+ else {
83
+ pollingManager.start();
84
+ }
85
+ }
86
+ else {
74
87
  if (startFirstTime) {
75
88
  pollingManager.syncAll();
76
89
  startFirstTime = false;
77
90
  }
78
- pushManager.start();
79
- }
80
- else {
81
- pollingManager.start();
82
91
  }
83
92
  }
84
93
  // start periodic data recording (events, impressions, telemetry).
85
- if ((0, consent_1.isConsentGranted)(settings))
86
- submitter.start();
94
+ submitterManager.start(!(0, consent_1.isConsentGranted)(settings));
87
95
  },
88
96
  /**
89
97
  * Method used to stop/pause the syncManager.
@@ -96,16 +104,13 @@ function syncManagerOnlineFactory(pollingManagerFactory, pushManagerFactory) {
96
104
  if (pollingManager && pollingManager.isRunning())
97
105
  pollingManager.stop();
98
106
  // stop periodic data recording (events, impressions, telemetry).
99
- submitter.stop();
107
+ submitterManager.stop();
100
108
  },
101
109
  isRunning: function () {
102
110
  return running;
103
111
  },
104
112
  flush: function () {
105
- if ((0, consent_1.isConsentGranted)(settings))
106
- return submitter.execute();
107
- else
108
- return Promise.resolve();
113
+ return submitterManager.execute(!(0, consent_1.isConsentGranted)(settings));
109
114
  },
110
115
  // [Only used for client-side]
111
116
  // If polling and push managers are defined (standalone mode), they implement the interfaces for client-side
@@ -116,22 +121,28 @@ function syncManagerOnlineFactory(pollingManagerFactory, pushManagerFactory) {
116
121
  return {
117
122
  isRunning: mySegmentsSyncTask.isRunning,
118
123
  start: function () {
119
- if (pushManager) {
120
- if (pollingManager.isRunning()) {
121
- // if doing polling, we must start the periodic fetch of data
122
- if (storage.splits.usesSegments())
123
- mySegmentsSyncTask.start();
124
+ if (syncEnabled) {
125
+ if (pushManager) {
126
+ if (pollingManager.isRunning()) {
127
+ // if doing polling, we must start the periodic fetch of data
128
+ if (storage.splits.usesSegments())
129
+ mySegmentsSyncTask.start();
130
+ }
131
+ else {
132
+ // if not polling, we must execute the sync task for the initial fetch
133
+ // of segments since `syncAll` was already executed when starting the main client
134
+ mySegmentsSyncTask.execute();
135
+ }
136
+ pushManager.add(matchingKey, mySegmentsSyncTask);
124
137
  }
125
138
  else {
126
- // if not polling, we must execute the sync task for the initial fetch
127
- // of segments since `syncAll` was already executed when starting the main client
128
- mySegmentsSyncTask.execute();
139
+ if (storage.splits.usesSegments())
140
+ mySegmentsSyncTask.start();
129
141
  }
130
- pushManager.add(matchingKey, mySegmentsSyncTask);
131
142
  }
132
143
  else {
133
- if (storage.splits.usesSegments())
134
- mySegmentsSyncTask.start();
144
+ if (!readinessManager.isReady())
145
+ mySegmentsSyncTask.execute();
135
146
  }
136
147
  },
137
148
  stop: function () {
@@ -4,8 +4,8 @@ exports.syncTaskFactory = void 0;
4
4
  var constants_1 = require("../logger/constants");
5
5
  /**
6
6
  * Creates a syncTask that handles the periodic execution of a given task ("start" and "stop" methods).
7
- * The task can be executed once calling the "execute" method.
8
- * NOTE: Multiple calls to "execute" are not queued. Use "isExecuting" method to handle synchronization.
7
+ * The task can be also executed by calling the "execute" method. Multiple execute calls are chained to run secuentially and avoid race conditions.
8
+ * For example, submitters executed on SDK destroy or full queue, while periodic execution is pending.
9
9
  *
10
10
  * @param log Logger instance.
11
11
  * @param task Task to execute that returns a promise that NEVER REJECTS. Otherwise, periodic execution can result in Unhandled Promise Rejections.
@@ -15,8 +15,8 @@ var constants_1 = require("../logger/constants");
15
15
  */
16
16
  function syncTaskFactory(log, task, period, taskName) {
17
17
  if (taskName === void 0) { taskName = 'task'; }
18
- // Flag that indicates if the task is being executed
19
- var executing = false;
18
+ // Task promise while it is pending. Undefined once the promise is resolved
19
+ var pendingTask;
20
20
  // flag that indicates if the task periodic execution has been started/stopped.
21
21
  var running = false;
22
22
  // Auxiliar counter used to avoid race condition when calling `start` & `stop` intermittently
@@ -30,13 +30,19 @@ function syncTaskFactory(log, task, period, taskName) {
30
30
  for (var _i = 0; _i < arguments.length; _i++) {
31
31
  args[_i] = arguments[_i];
32
32
  }
33
- executing = true;
33
+ // If task is executing, chain the new execution
34
+ if (pendingTask) {
35
+ return pendingTask.then(function () {
36
+ return execute.apply(void 0, args);
37
+ });
38
+ }
39
+ // Execute task
34
40
  log.debug(constants_1.SYNC_TASK_EXECUTE, [taskName]);
35
- return task.apply(void 0, args).then(function (result) {
36
- executing = false;
41
+ pendingTask = task.apply(void 0, args).then(function (result) {
42
+ pendingTask = undefined;
37
43
  return result;
38
44
  });
39
- // No need to handle promise rejection because it is a pre-condition that provided task never rejects.
45
+ return pendingTask;
40
46
  }
41
47
  function periodicExecute(currentRunningId) {
42
48
  return execute.apply(void 0, runningArgs).then(function (result) {
@@ -48,10 +54,9 @@ function syncTaskFactory(log, task, period, taskName) {
48
54
  });
49
55
  }
50
56
  return {
51
- // @TODO check if we need to queued `execute` calls, to avoid possible race conditions on submitters and updaters with streaming.
52
57
  execute: execute,
53
58
  isExecuting: function () {
54
- return executing;
59
+ return pendingTask !== undefined;
55
60
  },
56
61
  start: function () {
57
62
  var args = [];
@@ -5,7 +5,7 @@ var ImpressionObserver_1 = require("./ImpressionObserver");
5
5
  var murmur3_1 = require("../../utils/murmur3/murmur3");
6
6
  var buildKey_1 = require("./buildKey");
7
7
  function hashImpression32(impression) {
8
- return (0, murmur3_1.hash)((0, buildKey_1.buildKey)(impression));
8
+ return (0, murmur3_1.hash)((0, buildKey_1.buildKey)(impression)).toString();
9
9
  }
10
10
  exports.hashImpression32 = hashImpression32;
11
11
  var LAST_SEEN_CACHE_SIZE = 500; // cache up to 500 impression hashes
@@ -73,7 +73,8 @@ exports.base = {
73
73
  splitFilters: undefined,
74
74
  // impressions collection mode
75
75
  impressionsMode: constants_1.OPTIMIZED,
76
- localhostMode: undefined
76
+ localhostMode: undefined,
77
+ enabled: true
77
78
  },
78
79
  // Logger
79
80
  log: undefined
@@ -126,6 +127,9 @@ function settingsValidation(config, validationParams) {
126
127
  // ensure a valid SDK mode
127
128
  // @ts-ignore, modify readonly prop
128
129
  withDefaults.mode = (0, mode_1.mode)(withDefaults.core.authorizationKey, withDefaults.mode);
130
+ if (withDefaults.sync.onlySubmitters && withDefaults.mode === constants_1.STANDALONE_MODE && !withDefaults.dataLoader) {
131
+ throw new Error('To use `onlySubmitters` param in standalone mode, DataLoader is required to preload data into the storage');
132
+ }
129
133
  // ensure a valid Storage based on mode defined.
130
134
  // @ts-ignore, modify readonly prop
131
135
  if (storage)
@@ -167,6 +171,14 @@ function settingsValidation(config, validationParams) {
167
171
  // We are not checking if bases are positive numbers. Thus, we might be reauthenticating immediately (`setTimeout` with NaN or negative number)
168
172
  scheduler.pushRetryBackoffBase = fromSecondsToMillis(scheduler.pushRetryBackoffBase);
169
173
  }
174
+ // validate sync enabled
175
+ if (withDefaults.sync.enabled !== false) {
176
+ withDefaults.sync.enabled = true;
177
+ }
178
+ // validate sync onlySubmitters
179
+ if (withDefaults.sync.onlySubmitters !== true) {
180
+ withDefaults.sync.onlySubmitters = false;
181
+ }
170
182
  // validate the `splitFilters` settings and parse splits query
171
183
  var splitFiltersValidation = (0, splitFilters_1.validateSplitFilters)(log, withDefaults.sync.splitFilters, withDefaults.mode);
172
184
  withDefaults.sync.splitFilters = splitFiltersValidation.validFilters;
@@ -28,10 +28,10 @@ export function createUserConsentAPI(params) {
28
28
  log.info(USER_CONSENT_UPDATED, [settings.userConsent, newConsentStatus]); // @ts-ignore, modify readonly prop
29
29
  settings.userConsent = newConsentStatus;
30
30
  if (consent) { // resumes submitters if transitioning to GRANTED
31
- (_a = syncManager === null || syncManager === void 0 ? void 0 : syncManager.submitter) === null || _a === void 0 ? void 0 : _a.start();
31
+ (_a = syncManager === null || syncManager === void 0 ? void 0 : syncManager.submitterManager) === null || _a === void 0 ? void 0 : _a.start();
32
32
  }
33
- else { // pauses submitters and drops tracked data if transitioning to DECLINED
34
- (_b = syncManager === null || syncManager === void 0 ? void 0 : syncManager.submitter) === null || _b === void 0 ? void 0 : _b.stop();
33
+ else { // pauses submitters (except telemetry), and drops tracked data if transitioning to DECLINED
34
+ (_b = syncManager === null || syncManager === void 0 ? void 0 : syncManager.submitterManager) === null || _b === void 0 ? void 0 : _b.stop(true);
35
35
  // @ts-ignore, clear method is present in storage for standalone and partial consumer mode
36
36
  if (events.clear)
37
37
  events.clear(); // @ts-ignore
@@ -50,7 +50,7 @@ var BrowserSignalListener = /** @class */ (function () {
50
50
  BrowserSignalListener.prototype.flushData = function () {
51
51
  if (!this.syncManager)
52
52
  return; // In consumer mode there is not sync manager and data to flush
53
- // Flush data if there is user consent
53
+ // Flush impressions & events data if there is user consent
54
54
  if (isConsentGranted(this.settings)) {
55
55
  var eventsUrl = this.settings.urls.events;
56
56
  var extraMetadata = {
@@ -61,11 +61,12 @@ var BrowserSignalListener = /** @class */ (function () {
61
61
  this._flushData(eventsUrl + '/events/beacon', this.storage.events, this.serviceApi.postEventsBulk);
62
62
  if (this.storage.impressionCounts)
63
63
  this._flushData(eventsUrl + '/testImpressions/count/beacon', this.storage.impressionCounts, this.serviceApi.postTestImpressionsCount, fromImpressionCountsCollector);
64
- if (this.storage.telemetry) {
65
- var telemetryUrl = this.settings.urls.telemetry;
66
- var telemetryCacheAdapter = telemetryCacheStatsAdapter(this.storage.telemetry, this.storage.splits, this.storage.segments);
67
- this._flushData(telemetryUrl + '/v1/metrics/usage/beacon', telemetryCacheAdapter, this.serviceApi.postMetricsUsage);
68
- }
64
+ }
65
+ // Flush telemetry data
66
+ if (this.storage.telemetry) {
67
+ var telemetryUrl = this.settings.urls.telemetry;
68
+ var telemetryCacheAdapter = telemetryCacheStatsAdapter(this.storage.telemetry, this.storage.splits, this.storage.segments);
69
+ this._flushData(telemetryUrl + '/v1/metrics/usage/beacon', telemetryCacheAdapter, this.serviceApi.postMetricsUsage);
69
70
  }
70
71
  // Close streaming connection
71
72
  if (this.syncManager.pushManager)
@@ -8,7 +8,7 @@ import { validateAndTrackApiKey } from '../utils/inputValidation/apiKey';
8
8
  import { createLoggerAPI } from '../logger/sdkLogger';
9
9
  import { NEW_FACTORY, RETRIEVE_MANAGER } from '../logger/constants';
10
10
  import { metadataBuilder } from '../storages/metadataBuilder';
11
- import { SDK_SPLITS_ARRIVED, SDK_SEGMENTS_ARRIVED } from '../readiness/constants';
11
+ import { SDK_SPLITS_ARRIVED, SDK_SEGMENTS_ARRIVED, SDK_SPLITS_CACHE_LOADED } from '../readiness/constants';
12
12
  import { objectAssign } from '../utils/lang/objectAssign';
13
13
  /**
14
14
  * Modular SDK factory
@@ -22,13 +22,14 @@ export function sdkFactory(params) {
22
22
  validateAndTrackApiKey(log, settings.core.authorizationKey);
23
23
  var sdkReadinessManager = sdkReadinessManagerFactory(log, platform.EventEmitter, settings.startup.readyTimeout);
24
24
  var readiness = sdkReadinessManager.readinessManager;
25
+ var matchingKey = getMatching(settings.core.key);
25
26
  // @TODO consider passing the settings object, so that each storage access only what it needs
26
27
  var storageFactoryParams = {
27
28
  impressionsQueueSize: settings.scheduler.impressionsQueueSize,
28
29
  eventsQueueSize: settings.scheduler.eventsQueueSize,
29
30
  optimize: shouldBeOptimized(settings),
30
31
  // ATM, only used by InLocalStorage
31
- matchingKey: getMatching(settings.core.key),
32
+ matchingKey: matchingKey,
32
33
  splitFiltersValidation: settings.sync.__splitFiltersValidation,
33
34
  // ATM, only used by PluggableStorage
34
35
  mode: settings.mode,
@@ -44,7 +45,21 @@ export function sdkFactory(params) {
44
45
  log: log
45
46
  };
46
47
  var storage = storageFactory(storageFactoryParams);
47
- // @TODO add support for dataloader: `if (params.dataLoader) params.dataLoader(storage);`
48
+ // @TODO dataLoader requires validation
49
+ if (settings.dataLoader) {
50
+ settings.dataLoader(storage, matchingKey);
51
+ Promise.resolve(storage.splits.checkCache()).then(function (cacheReady) {
52
+ if (cacheReady) {
53
+ if (settings.sync.onlySubmitters) { // emit SDK_READY to not timeout when not synchronizing splits & segments
54
+ readiness.splits.emit(SDK_SPLITS_ARRIVED);
55
+ readiness.segments.emit(SDK_SEGMENTS_ARRIVED);
56
+ }
57
+ else { // emit SDK_READY_FROM_CACHE
58
+ readiness.splits.emit(SDK_SPLITS_CACHE_LOADED);
59
+ }
60
+ }
61
+ });
62
+ }
48
63
  var integrationsManager = integrationsManagerFactory && integrationsManagerFactory({ settings: settings, storage: storage });
49
64
  // trackers
50
65
  var observer = impressionsObserverFactory && impressionsObserverFactory();
@@ -76,5 +91,8 @@ export function sdkFactory(params) {
76
91
  // Logger wrapper API
77
92
  Logger: createLoggerAPI(settings.log),
78
93
  settings: settings,
94
+ // @TODO remove
95
+ __storage: storage,
96
+ __ctx: ctx
79
97
  }, extraProps && extraProps(ctx));
80
98
  }
@@ -30,7 +30,7 @@ var AbstractSplitsCacheSync = /** @class */ (function () {
30
30
  * It is used as condition to emit SDK_SPLITS_CACHE_LOADED, and then SDK_READY_FROM_CACHE.
31
31
  */
32
32
  AbstractSplitsCacheSync.prototype.checkCache = function () {
33
- return false;
33
+ return this.getChangeNumber() > -1;
34
34
  };
35
35
  /**
36
36
  * Kill `name` split and set `defaultTreatment` and `changeNumber`.