@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
@@ -1,23 +1,24 @@
1
1
  import { DEFAULT_CACHE_EXPIRATION_IN_MILLIS } from '../utils/constants/browser';
2
2
  /**
3
- * Factory of client-side storage loader
3
+ * Factory of storage loader
4
4
  *
5
5
  * @param preloadedData validated data following the format proposed in https://github.com/godaddy/split-javascript-data-loader
6
6
  * and extended with a `mySegmentsData` property.
7
7
  * @returns function to preload the storage
8
8
  */
9
- export function dataLoaderFactory(preloadedData) {
9
+ export function DataLoaderFactory(preloadedData) {
10
10
  /**
11
11
  * Storage-agnostic adaptation of `loadDataIntoLocalStorage` function
12
12
  * (https://github.com/godaddy/split-javascript-data-loader/blob/master/src/load-data.js)
13
13
  *
14
14
  * @param storage object containing `splits` and `segments` cache (client-side variant)
15
- * @param userId user key string of the provided MySegmentsCache
15
+ * @param userKey user key (matching key) of the provided MySegmentsCache
16
16
  *
17
- * @TODO extend to support SegmentsCache (server-side variant) by making `userId` optional and adding the corresponding logic.
18
17
  * @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.
18
+ * @TODO add logs, and input validation in this module, in favor of size reduction.
19
+ * @TODO unit tests
19
20
  */
20
- return function loadData(storage, userId) {
21
+ return function loadData(storage, userKey) {
21
22
  // Do not load data if current preloadedData is empty
22
23
  if (Object.keys(preloadedData).length === 0)
23
24
  return;
@@ -33,15 +34,22 @@ export function dataLoaderFactory(preloadedData) {
33
34
  storage.splits.setChangeNumber(since);
34
35
  // splitsData in an object where the property is the split name and the pertaining value is a stringified json of its data
35
36
  storage.splits.addSplits(Object.keys(splitsData).map(function (splitName) { return [splitName, splitsData[splitName]]; }));
36
- // add mySegments data
37
- var mySegmentsData = preloadedData.mySegmentsData && preloadedData.mySegmentsData[userId];
38
- if (!mySegmentsData) {
39
- // 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
40
- mySegmentsData = Object.keys(segmentsData).filter(function (segmentName) {
41
- var userIds = JSON.parse(segmentsData[segmentName]).added;
42
- return Array.isArray(userIds) && userIds.indexOf(userId) > -1;
37
+ if (userKey) { // add mySegments data (client-side)
38
+ var mySegmentsData = preloadedData.mySegmentsData && preloadedData.mySegmentsData[userKey];
39
+ if (!mySegmentsData) {
40
+ // 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
41
+ mySegmentsData = Object.keys(segmentsData).filter(function (segmentName) {
42
+ var userKeys = segmentsData[segmentName];
43
+ return userKeys.indexOf(userKey) > -1;
44
+ });
45
+ }
46
+ storage.segments.resetSegments(mySegmentsData);
47
+ }
48
+ else { // add segments data (server-side)
49
+ Object.keys(segmentsData).filter(function (segmentName) {
50
+ var userKeys = segmentsData[segmentName];
51
+ storage.segments.addToSegment(segmentName, userKeys);
43
52
  });
44
53
  }
45
- storage.segments.resetSegments(mySegmentsData);
46
54
  };
47
55
  }
@@ -5,6 +5,7 @@ import { EventsCacheInMemory } from './EventsCacheInMemory';
5
5
  import { ImpressionCountsCacheInMemory } from './ImpressionCountsCacheInMemory';
6
6
  import { LOCALHOST_MODE, STORAGE_MEMORY } from '../../utils/constants';
7
7
  import { TelemetryCacheInMemory } from './TelemetryCacheInMemory';
8
+ import { setToArray } from '../../utils/lang/sets';
8
9
  /**
9
10
  * InMemory storage factory for standalone server-side SplitFactory
10
11
  *
@@ -25,7 +26,20 @@ export function InMemoryStorageFactory(params) {
25
26
  this.impressions.clear();
26
27
  this.impressionCounts && this.impressionCounts.clear();
27
28
  this.events.clear();
28
- }
29
+ },
30
+ // @ts-ignore, private method, for POC
31
+ getSnapshot: function () {
32
+ var _this = this;
33
+ return {
34
+ lastUpdated: Date.now(),
35
+ since: this.splits.changeNumber,
36
+ splitsData: this.splits.splitsCache,
37
+ segmentsData: Object.keys(this.segments.segmentCache).reduce(function (prev, cur) {
38
+ prev[cur] = setToArray(_this.segments.segmentCache[cur]);
39
+ return prev;
40
+ }, {})
41
+ };
42
+ },
29
43
  };
30
44
  }
31
45
  InMemoryStorageFactory.type = STORAGE_MEMORY;
@@ -26,6 +26,18 @@ export function InMemoryStorageCSFactory(params) {
26
26
  this.impressionCounts && this.impressionCounts.clear();
27
27
  this.events.clear();
28
28
  },
29
+ // @ts-ignore, private method, for POC
30
+ getSnapshot: function () {
31
+ var _a;
32
+ return {
33
+ lastUpdated: Date.now(),
34
+ since: this.splits.changeNumber,
35
+ splitsData: this.splits.splitsCache,
36
+ mySegmentsData: (_a = {},
37
+ _a[params.matchingKey] = Object.keys(this.segments.segmentCache),
38
+ _a)
39
+ };
40
+ },
29
41
  // When using shared instanciation with MEMORY we reuse everything but segments (they are unique per key)
30
42
  shared: function () {
31
43
  return {
@@ -1,4 +1,3 @@
1
- import { syncTaskComposite } from '../syncTaskComposite';
2
1
  import { eventsSubmitterFactory } from './eventsSubmitter';
3
2
  import { impressionsSubmitterFactory } from './impressionsSubmitter';
4
3
  import { impressionCountsSubmitterFactory } from './impressionCountsSubmitter';
@@ -12,7 +11,32 @@ export function submitterManagerFactory(params) {
12
11
  if (impressionCountsSubmitter)
13
12
  submitters.push(impressionCountsSubmitter);
14
13
  var telemetrySubmitter = telemetrySubmitterFactory(params);
15
- if (telemetrySubmitter)
16
- submitters.push(telemetrySubmitter);
17
- return syncTaskComposite(submitters);
14
+ return {
15
+ // `onlyTelemetry` true if SDK is created with userConsent not GRANTED
16
+ start: function (onlyTelemetry) {
17
+ if (!onlyTelemetry)
18
+ submitters.forEach(function (submitter) { return submitter.start(); });
19
+ if (telemetrySubmitter)
20
+ telemetrySubmitter.start();
21
+ },
22
+ // `allExceptTelemetry` true if userConsent is changed to DECLINED
23
+ stop: function (allExceptTelemetry) {
24
+ submitters.forEach(function (submitter) { return submitter.stop(); });
25
+ if (!allExceptTelemetry && telemetrySubmitter)
26
+ telemetrySubmitter.stop();
27
+ },
28
+ isRunning: function () {
29
+ return submitters.some(function (submitter) { return submitter.isRunning(); });
30
+ },
31
+ // Flush data. Called with `onlyTelemetry` true if SDK is destroyed with userConsent not GRANTED
32
+ execute: function (onlyTelemetry) {
33
+ var promises = onlyTelemetry ? [] : submitters.map(function (submitter) { return submitter.execute(); });
34
+ if (telemetrySubmitter)
35
+ promises.push(telemetrySubmitter.execute());
36
+ return Promise.all(promises);
37
+ },
38
+ isExecuting: function () {
39
+ return submitters.some(function (submitter) { return submitter.isExecuting(); });
40
+ }
41
+ };
18
42
  }
@@ -16,16 +16,16 @@ export function syncManagerOnlineFactory(pollingManagerFactory, pushManagerFacto
16
16
  * SyncManager factory for modular SDK
17
17
  */
18
18
  return function (params) {
19
- var settings = params.settings, _a = params.settings, log = _a.log, streamingEnabled = _a.streamingEnabled, telemetryTracker = params.telemetryTracker;
19
+ 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;
20
20
  /** Polling Manager */
21
- var pollingManager = pollingManagerFactory && pollingManagerFactory(params);
21
+ var pollingManager = onlySubmitters ? undefined : pollingManagerFactory && pollingManagerFactory(params);
22
22
  /** Push Manager */
23
- var pushManager = streamingEnabled && pollingManager && pushManagerFactory ?
23
+ var pushManager = syncEnabled && streamingEnabled && pollingManager && pushManagerFactory ?
24
24
  pushManagerFactory(params, pollingManager) :
25
25
  undefined;
26
26
  /** Submitter Manager */
27
27
  // It is not inyected as push and polling managers, because at the moment it is required
28
- var submitter = submitterManagerFactory(params);
28
+ var submitterManager = submitterManagerFactory(params);
29
29
  /** Sync Manager logic */
30
30
  function startPolling() {
31
31
  if (pollingManager.isRunning()) {
@@ -58,7 +58,7 @@ export function syncManagerOnlineFactory(pollingManagerFactory, pushManagerFacto
58
58
  // E.g.: user consent, app state changes (Page hide, Foreground/Background, Online/Offline).
59
59
  pollingManager: pollingManager,
60
60
  pushManager: pushManager,
61
- submitter: submitter,
61
+ submitterManager: submitterManager,
62
62
  /**
63
63
  * Method used to start the syncManager for the first time, or resume it after being stopped.
64
64
  */
@@ -66,21 +66,29 @@ export function syncManagerOnlineFactory(pollingManagerFactory, pushManagerFacto
66
66
  running = true;
67
67
  // start syncing splits and segments
68
68
  if (pollingManager) {
69
- if (pushManager) {
70
- // Doesn't call `syncAll` when the syncManager is resuming
69
+ // If synchronization is disabled pushManager and pollingManager should not start
70
+ if (syncEnabled) {
71
+ if (pushManager) {
72
+ // Doesn't call `syncAll` when the syncManager is resuming
73
+ if (startFirstTime) {
74
+ pollingManager.syncAll();
75
+ startFirstTime = false;
76
+ }
77
+ pushManager.start();
78
+ }
79
+ else {
80
+ pollingManager.start();
81
+ }
82
+ }
83
+ else {
71
84
  if (startFirstTime) {
72
85
  pollingManager.syncAll();
73
86
  startFirstTime = false;
74
87
  }
75
- pushManager.start();
76
- }
77
- else {
78
- pollingManager.start();
79
88
  }
80
89
  }
81
90
  // start periodic data recording (events, impressions, telemetry).
82
- if (isConsentGranted(settings))
83
- submitter.start();
91
+ submitterManager.start(!isConsentGranted(settings));
84
92
  },
85
93
  /**
86
94
  * Method used to stop/pause the syncManager.
@@ -93,16 +101,13 @@ export function syncManagerOnlineFactory(pollingManagerFactory, pushManagerFacto
93
101
  if (pollingManager && pollingManager.isRunning())
94
102
  pollingManager.stop();
95
103
  // stop periodic data recording (events, impressions, telemetry).
96
- submitter.stop();
104
+ submitterManager.stop();
97
105
  },
98
106
  isRunning: function () {
99
107
  return running;
100
108
  },
101
109
  flush: function () {
102
- if (isConsentGranted(settings))
103
- return submitter.execute();
104
- else
105
- return Promise.resolve();
110
+ return submitterManager.execute(!isConsentGranted(settings));
106
111
  },
107
112
  // [Only used for client-side]
108
113
  // If polling and push managers are defined (standalone mode), they implement the interfaces for client-side
@@ -113,22 +118,28 @@ export function syncManagerOnlineFactory(pollingManagerFactory, pushManagerFacto
113
118
  return {
114
119
  isRunning: mySegmentsSyncTask.isRunning,
115
120
  start: function () {
116
- if (pushManager) {
117
- if (pollingManager.isRunning()) {
118
- // if doing polling, we must start the periodic fetch of data
119
- if (storage.splits.usesSegments())
120
- mySegmentsSyncTask.start();
121
+ if (syncEnabled) {
122
+ if (pushManager) {
123
+ if (pollingManager.isRunning()) {
124
+ // if doing polling, we must start the periodic fetch of data
125
+ if (storage.splits.usesSegments())
126
+ mySegmentsSyncTask.start();
127
+ }
128
+ else {
129
+ // if not polling, we must execute the sync task for the initial fetch
130
+ // of segments since `syncAll` was already executed when starting the main client
131
+ mySegmentsSyncTask.execute();
132
+ }
133
+ pushManager.add(matchingKey, mySegmentsSyncTask);
121
134
  }
122
135
  else {
123
- // if not polling, we must execute the sync task for the initial fetch
124
- // of segments since `syncAll` was already executed when starting the main client
125
- mySegmentsSyncTask.execute();
136
+ if (storage.splits.usesSegments())
137
+ mySegmentsSyncTask.start();
126
138
  }
127
- pushManager.add(matchingKey, mySegmentsSyncTask);
128
139
  }
129
140
  else {
130
- if (storage.splits.usesSegments())
131
- mySegmentsSyncTask.start();
141
+ if (!readinessManager.isReady())
142
+ mySegmentsSyncTask.execute();
132
143
  }
133
144
  },
134
145
  stop: function () {
@@ -1,8 +1,8 @@
1
1
  import { SYNC_TASK_EXECUTE, SYNC_TASK_START, SYNC_TASK_STOP } from '../logger/constants';
2
2
  /**
3
3
  * Creates a syncTask that handles the periodic execution of a given task ("start" and "stop" methods).
4
- * The task can be executed once calling the "execute" method.
5
- * NOTE: Multiple calls to "execute" are not queued. Use "isExecuting" method to handle synchronization.
4
+ * The task can be also executed by calling the "execute" method. Multiple execute calls are chained to run secuentially and avoid race conditions.
5
+ * For example, submitters executed on SDK destroy or full queue, while periodic execution is pending.
6
6
  *
7
7
  * @param log Logger instance.
8
8
  * @param task Task to execute that returns a promise that NEVER REJECTS. Otherwise, periodic execution can result in Unhandled Promise Rejections.
@@ -12,8 +12,8 @@ import { SYNC_TASK_EXECUTE, SYNC_TASK_START, SYNC_TASK_STOP } from '../logger/co
12
12
  */
13
13
  export function syncTaskFactory(log, task, period, taskName) {
14
14
  if (taskName === void 0) { taskName = 'task'; }
15
- // Flag that indicates if the task is being executed
16
- var executing = false;
15
+ // Task promise while it is pending. Undefined once the promise is resolved
16
+ var pendingTask;
17
17
  // flag that indicates if the task periodic execution has been started/stopped.
18
18
  var running = false;
19
19
  // Auxiliar counter used to avoid race condition when calling `start` & `stop` intermittently
@@ -27,13 +27,19 @@ export function syncTaskFactory(log, task, period, taskName) {
27
27
  for (var _i = 0; _i < arguments.length; _i++) {
28
28
  args[_i] = arguments[_i];
29
29
  }
30
- executing = true;
30
+ // If task is executing, chain the new execution
31
+ if (pendingTask) {
32
+ return pendingTask.then(function () {
33
+ return execute.apply(void 0, args);
34
+ });
35
+ }
36
+ // Execute task
31
37
  log.debug(SYNC_TASK_EXECUTE, [taskName]);
32
- return task.apply(void 0, args).then(function (result) {
33
- executing = false;
38
+ pendingTask = task.apply(void 0, args).then(function (result) {
39
+ pendingTask = undefined;
34
40
  return result;
35
41
  });
36
- // No need to handle promise rejection because it is a pre-condition that provided task never rejects.
42
+ return pendingTask;
37
43
  }
38
44
  function periodicExecute(currentRunningId) {
39
45
  return execute.apply(void 0, runningArgs).then(function (result) {
@@ -45,10 +51,9 @@ export function syncTaskFactory(log, task, period, taskName) {
45
51
  });
46
52
  }
47
53
  return {
48
- // @TODO check if we need to queued `execute` calls, to avoid possible race conditions on submitters and updaters with streaming.
49
54
  execute: execute,
50
55
  isExecuting: function () {
51
- return executing;
56
+ return pendingTask !== undefined;
52
57
  },
53
58
  start: function () {
54
59
  var args = [];
@@ -2,7 +2,7 @@ import { ImpressionObserver } from './ImpressionObserver';
2
2
  import { hash } from '../../utils/murmur3/murmur3';
3
3
  import { buildKey } from './buildKey';
4
4
  export function hashImpression32(impression) {
5
- return hash(buildKey(impression));
5
+ return hash(buildKey(impression)).toString();
6
6
  }
7
7
  var LAST_SEEN_CACHE_SIZE = 500; // cache up to 500 impression hashes
8
8
  export function impressionObserverCSFactory() {
@@ -70,7 +70,8 @@ export var base = {
70
70
  splitFilters: undefined,
71
71
  // impressions collection mode
72
72
  impressionsMode: OPTIMIZED,
73
- localhostMode: undefined
73
+ localhostMode: undefined,
74
+ enabled: true
74
75
  },
75
76
  // Logger
76
77
  log: undefined
@@ -123,6 +124,9 @@ export function settingsValidation(config, validationParams) {
123
124
  // ensure a valid SDK mode
124
125
  // @ts-ignore, modify readonly prop
125
126
  withDefaults.mode = mode(withDefaults.core.authorizationKey, withDefaults.mode);
127
+ if (withDefaults.sync.onlySubmitters && withDefaults.mode === STANDALONE_MODE && !withDefaults.dataLoader) {
128
+ throw new Error('To use `onlySubmitters` param in standalone mode, DataLoader is required to preload data into the storage');
129
+ }
126
130
  // ensure a valid Storage based on mode defined.
127
131
  // @ts-ignore, modify readonly prop
128
132
  if (storage)
@@ -164,6 +168,14 @@ export function settingsValidation(config, validationParams) {
164
168
  // We are not checking if bases are positive numbers. Thus, we might be reauthenticating immediately (`setTimeout` with NaN or negative number)
165
169
  scheduler.pushRetryBackoffBase = fromSecondsToMillis(scheduler.pushRetryBackoffBase);
166
170
  }
171
+ // validate sync enabled
172
+ if (withDefaults.sync.enabled !== false) {
173
+ withDefaults.sync.enabled = true;
174
+ }
175
+ // validate sync onlySubmitters
176
+ if (withDefaults.sync.onlySubmitters !== true) {
177
+ withDefaults.sync.onlySubmitters = false;
178
+ }
167
179
  // validate the `splitFilters` settings and parse splits query
168
180
  var splitFiltersValidation = validateSplitFilters(log, withDefaults.sync.splitFilters, withDefaults.mode);
169
181
  withDefaults.sync.splitFilters = splitFiltersValidation.validFilters;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@splitsoftware/splitio-commons",
3
- "version": "1.4.1",
3
+ "version": "1.4.2-rc.2",
4
4
  "description": "Split Javascript SDK common components",
5
5
  "main": "cjs/index.js",
6
6
  "module": "esm/index.js",
@@ -34,9 +34,10 @@ export function createUserConsentAPI(params: ISdkFactoryContext) {
34
34
  settings.userConsent = newConsentStatus;
35
35
 
36
36
  if (consent) { // resumes submitters if transitioning to GRANTED
37
- syncManager?.submitter?.start();
38
- } else { // pauses submitters and drops tracked data if transitioning to DECLINED
39
- syncManager?.submitter?.stop();
37
+ syncManager?.submitterManager?.start();
38
+ } else { // pauses submitters (except telemetry), and drops tracked data if transitioning to DECLINED
39
+ syncManager?.submitterManager?.stop(true);
40
+
40
41
  // @ts-ignore, clear method is present in storage for standalone and partial consumer mode
41
42
  if (events.clear) events.clear(); // @ts-ignore
42
43
  if (impressions.clear) impressions.clear();
@@ -67,7 +67,7 @@ export class BrowserSignalListener implements ISignalListener {
67
67
  flushData() {
68
68
  if (!this.syncManager) return; // In consumer mode there is not sync manager and data to flush
69
69
 
70
- // Flush data if there is user consent
70
+ // Flush impressions & events data if there is user consent
71
71
  if (isConsentGranted(this.settings)) {
72
72
  const eventsUrl = this.settings.urls.events;
73
73
  const extraMetadata = {
@@ -78,11 +78,13 @@ export class BrowserSignalListener implements ISignalListener {
78
78
  this._flushData(eventsUrl + '/testImpressions/beacon', this.storage.impressions, this.serviceApi.postTestImpressionsBulk, this.fromImpressionsCollector, extraMetadata);
79
79
  this._flushData(eventsUrl + '/events/beacon', this.storage.events, this.serviceApi.postEventsBulk);
80
80
  if (this.storage.impressionCounts) this._flushData(eventsUrl + '/testImpressions/count/beacon', this.storage.impressionCounts, this.serviceApi.postTestImpressionsCount, fromImpressionCountsCollector);
81
- if (this.storage.telemetry) {
82
- const telemetryUrl = this.settings.urls.telemetry;
83
- const telemetryCacheAdapter = telemetryCacheStatsAdapter(this.storage.telemetry, this.storage.splits, this.storage.segments);
84
- this._flushData(telemetryUrl + '/v1/metrics/usage/beacon', telemetryCacheAdapter, this.serviceApi.postMetricsUsage);
85
- }
81
+ }
82
+
83
+ // Flush telemetry data
84
+ if (this.storage.telemetry) {
85
+ const telemetryUrl = this.settings.urls.telemetry;
86
+ const telemetryCacheAdapter = telemetryCacheStatsAdapter(this.storage.telemetry, this.storage.splits, this.storage.segments);
87
+ this._flushData(telemetryUrl + '/v1/metrics/usage/beacon', telemetryCacheAdapter, this.serviceApi.postMetricsUsage);
86
88
  }
87
89
 
88
90
  // Close streaming connection
@@ -5,7 +5,7 @@ import { ILogger } from '../logger/types';
5
5
  import { objectAssign } from '../utils/lang/objectAssign';
6
6
 
7
7
  /**
8
- * Add in memory attributes storage methods and combine them with any attribute received from the getTreatment/s call
8
+ * Add in memory attributes storage methods and combine them with any attribute received from the getTreatment/s call
9
9
  */
10
10
  export function clientAttributesDecoration<TClient extends SplitIO.IClient | SplitIO.IAsyncClient>(log: ILogger, client: TClient) {
11
11
 
@@ -52,10 +52,10 @@ export function clientAttributesDecoration<TClient extends SplitIO.IClient | Spl
52
52
  getTreatments: getTreatments,
53
53
  getTreatmentsWithConfig: getTreatmentsWithConfig,
54
54
  track: track,
55
-
55
+
56
56
  /**
57
57
  * Add an attribute to client's in memory attributes storage
58
- *
58
+ *
59
59
  * @param {string} attributeName Attrinute name
60
60
  * @param {string, number, boolean, list} attributeValue Attribute value
61
61
  * @returns {boolean} true if the attribute was stored and false otherways
@@ -70,7 +70,7 @@ export function clientAttributesDecoration<TClient extends SplitIO.IClient | Spl
70
70
 
71
71
  /**
72
72
  * Returns the attribute with the given key
73
- *
73
+ *
74
74
  * @param {string} attributeName Attribute name
75
75
  * @returns {Object} Attribute with the given key
76
76
  */
@@ -81,7 +81,7 @@ export function clientAttributesDecoration<TClient extends SplitIO.IClient | Spl
81
81
 
82
82
  /**
83
83
  * Add to client's in memory attributes storage the attributes in 'attributes'
84
- *
84
+ *
85
85
  * @param {Object} attributes Object with attributes to store
86
86
  * @returns true if attributes were stored an false otherways
87
87
  */
@@ -92,7 +92,7 @@ export function clientAttributesDecoration<TClient extends SplitIO.IClient | Spl
92
92
 
93
93
  /**
94
94
  * Return all the attributes stored in client's in memory attributes storage
95
- *
95
+ *
96
96
  * @returns {Object} returns all the stored attributes
97
97
  */
98
98
  getAttributes(): Record<string, Object> {
@@ -101,8 +101,8 @@ export function clientAttributesDecoration<TClient extends SplitIO.IClient | Spl
101
101
 
102
102
  /**
103
103
  * Removes from client's in memory attributes storage the attribute with the given key
104
- *
105
- * @param {string} attributeName
104
+ *
105
+ * @param {string} attributeName
106
106
  * @returns {boolean} true if attribute was removed and false otherways
107
107
  */
108
108
  removeAttribute(attributeName: string) {
@@ -116,7 +116,7 @@ export function clientAttributesDecoration<TClient extends SplitIO.IClient | Spl
116
116
  clearAttributes() {
117
117
  return attributeStorage.clear();
118
118
  }
119
-
119
+
120
120
  });
121
121
 
122
122
  }
@@ -3,7 +3,7 @@ import { sdkReadinessManagerFactory } from '../readiness/sdkReadinessManager';
3
3
  import { impressionsTrackerFactory } from '../trackers/impressionsTracker';
4
4
  import { eventTrackerFactory } from '../trackers/eventTracker';
5
5
  import { telemetryTrackerFactory } from '../trackers/telemetryTracker';
6
- import { IStorageFactoryParams } from '../storages/types';
6
+ import { IStorageFactoryParams, IStorageSync } from '../storages/types';
7
7
  import { SplitIO } from '../types';
8
8
  import { getMatching } from '../utils/key';
9
9
  import { shouldBeOptimized } from '../trackers/impressionObserver/utils';
@@ -11,7 +11,7 @@ import { validateAndTrackApiKey } from '../utils/inputValidation/apiKey';
11
11
  import { createLoggerAPI } from '../logger/sdkLogger';
12
12
  import { NEW_FACTORY, RETRIEVE_MANAGER } from '../logger/constants';
13
13
  import { metadataBuilder } from '../storages/metadataBuilder';
14
- import { SDK_SPLITS_ARRIVED, SDK_SEGMENTS_ARRIVED } from '../readiness/constants';
14
+ import { SDK_SPLITS_ARRIVED, SDK_SEGMENTS_ARRIVED, SDK_SPLITS_CACHE_LOADED } from '../readiness/constants';
15
15
  import { objectAssign } from '../utils/lang/objectAssign';
16
16
 
17
17
  /**
@@ -32,6 +32,7 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ICsSDK | SplitIO.
32
32
 
33
33
  const sdkReadinessManager = sdkReadinessManagerFactory(log, platform.EventEmitter, settings.startup.readyTimeout);
34
34
  const readiness = sdkReadinessManager.readinessManager;
35
+ const matchingKey = getMatching(settings.core.key);
35
36
 
36
37
  // @TODO consider passing the settings object, so that each storage access only what it needs
37
38
  const storageFactoryParams: IStorageFactoryParams = {
@@ -40,7 +41,7 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ICsSDK | SplitIO.
40
41
  optimize: shouldBeOptimized(settings),
41
42
 
42
43
  // ATM, only used by InLocalStorage
43
- matchingKey: getMatching(settings.core.key),
44
+ matchingKey,
44
45
  splitFiltersValidation: settings.sync.__splitFiltersValidation,
45
46
 
46
47
  // ATM, only used by PluggableStorage
@@ -58,7 +59,21 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ICsSDK | SplitIO.
58
59
  };
59
60
 
60
61
  const storage = storageFactory(storageFactoryParams);
61
- // @TODO add support for dataloader: `if (params.dataLoader) params.dataLoader(storage);`
62
+
63
+ // @TODO dataLoader requires validation
64
+ if (settings.dataLoader) {
65
+ settings.dataLoader(storage as IStorageSync, matchingKey);
66
+ Promise.resolve(storage.splits.checkCache()).then(cacheReady => {
67
+ if (cacheReady) {
68
+ if (settings.sync.onlySubmitters) { // emit SDK_READY to not timeout when not synchronizing splits & segments
69
+ readiness.splits.emit(SDK_SPLITS_ARRIVED);
70
+ readiness.segments.emit(SDK_SEGMENTS_ARRIVED);
71
+ } else { // emit SDK_READY_FROM_CACHE
72
+ readiness.splits.emit(SDK_SPLITS_CACHE_LOADED);
73
+ }
74
+ }
75
+ });
76
+ }
62
77
 
63
78
  const integrationsManager = integrationsManagerFactory && integrationsManagerFactory({ settings, storage });
64
79
 
@@ -103,5 +118,9 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ICsSDK | SplitIO.
103
118
  Logger: createLoggerAPI(settings.log),
104
119
 
105
120
  settings,
121
+
122
+ // @TODO remove
123
+ __storage: storage,
124
+ __ctx: ctx
106
125
  }, extraProps && extraProps(ctx));
107
126
  }
@@ -50,7 +50,7 @@ export abstract class AbstractSplitsCacheSync implements ISplitsCacheSync {
50
50
  * It is used as condition to emit SDK_SPLITS_CACHE_LOADED, and then SDK_READY_FROM_CACHE.
51
51
  */
52
52
  checkCache(): boolean {
53
- return false;
53
+ return this.getChangeNumber() > -1;
54
54
  }
55
55
 
56
56
  /**
@@ -3,25 +3,26 @@ import { DEFAULT_CACHE_EXPIRATION_IN_MILLIS } from '../utils/constants/browser';
3
3
  import { DataLoader, ISegmentsCacheSync, ISplitsCacheSync } from './types';
4
4
 
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
- export function dataLoaderFactory(preloadedData: SplitIO.PreloadedData): DataLoader {
12
+ export function DataLoaderFactory(preloadedData: SplitIO.PreloadedData): DataLoader {
13
13
 
14
14
  /**
15
15
  * Storage-agnostic adaptation of `loadDataIntoLocalStorage` function
16
16
  * (https://github.com/godaddy/split-javascript-data-loader/blob/master/src/load-data.js)
17
17
  *
18
18
  * @param storage object containing `splits` and `segments` cache (client-side variant)
19
- * @param userId user key string of the provided MySegmentsCache
19
+ * @param userKey user key (matching key) of the provided MySegmentsCache
20
20
  *
21
- * @TODO extend to support SegmentsCache (server-side variant) by making `userId` optional and adding the corresponding logic.
22
21
  * @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.
22
+ * @TODO add logs, and input validation in this module, in favor of size reduction.
23
+ * @TODO unit tests
23
24
  */
24
- return function loadData(storage: { splits: ISplitsCacheSync, segments: ISegmentsCacheSync }, userId: string) {
25
+ return function loadData(storage: { splits: ISplitsCacheSync, segments: ISegmentsCacheSync }, userKey?: string) {
25
26
  // Do not load data if current preloadedData is empty
26
27
  if (Object.keys(preloadedData).length === 0) return;
27
28
 
@@ -41,15 +42,21 @@ export function dataLoaderFactory(preloadedData: SplitIO.PreloadedData): DataLoa
41
42
  // splitsData in an object where the property is the split name and the pertaining value is a stringified json of its data
42
43
  storage.splits.addSplits(Object.keys(splitsData).map(splitName => [splitName, splitsData[splitName]]));
43
44
 
44
- // add mySegments data
45
- let mySegmentsData = preloadedData.mySegmentsData && preloadedData.mySegmentsData[userId];
46
- if (!mySegmentsData) {
47
- // 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
48
- mySegmentsData = Object.keys(segmentsData).filter(segmentName => {
49
- const userIds = JSON.parse(segmentsData[segmentName]).added;
50
- return Array.isArray(userIds) && userIds.indexOf(userId) > -1;
45
+ if (userKey) { // add mySegments data (client-side)
46
+ let mySegmentsData = preloadedData.mySegmentsData && preloadedData.mySegmentsData[userKey];
47
+ if (!mySegmentsData) {
48
+ // 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
49
+ mySegmentsData = Object.keys(segmentsData).filter(segmentName => {
50
+ const userKeys = segmentsData[segmentName];
51
+ return userKeys.indexOf(userKey) > -1;
52
+ });
53
+ }
54
+ storage.segments.resetSegments(mySegmentsData);
55
+ } else { // add segments data (server-side)
56
+ Object.keys(segmentsData).filter(segmentName => {
57
+ const userKeys = segmentsData[segmentName];
58
+ storage.segments.addToSegment(segmentName, userKeys);
51
59
  });
52
60
  }
53
- storage.segments.resetSegments(mySegmentsData);
54
61
  };
55
62
  }