@splitsoftware/splitio-commons 1.4.2-rc.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 (30) hide show
  1. package/CHANGES.txt +4 -4
  2. package/cjs/sdkFactory/index.js +20 -2
  3. package/cjs/storages/AbstractSplitsCacheSync.js +1 -1
  4. package/cjs/storages/dataLoader.js +23 -15
  5. package/cjs/storages/inMemory/InMemoryStorage.js +15 -1
  6. package/cjs/storages/inMemory/InMemoryStorageCS.js +12 -0
  7. package/cjs/sync/syncManagerOnline.js +2 -2
  8. package/cjs/trackers/impressionObserver/impressionObserverCS.js +1 -1
  9. package/cjs/utils/settingsValidation/index.js +8 -1
  10. package/esm/sdkFactory/index.js +21 -3
  11. package/esm/storages/AbstractSplitsCacheSync.js +1 -1
  12. package/esm/storages/dataLoader.js +21 -13
  13. package/esm/storages/inMemory/InMemoryStorage.js +15 -1
  14. package/esm/storages/inMemory/InMemoryStorageCS.js +12 -0
  15. package/esm/sync/syncManagerOnline.js +2 -2
  16. package/esm/trackers/impressionObserver/impressionObserverCS.js +1 -1
  17. package/esm/utils/settingsValidation/index.js +8 -1
  18. package/package.json +1 -1
  19. package/src/sdkFactory/index.ts +23 -4
  20. package/src/storages/AbstractSplitsCacheSync.ts +1 -1
  21. package/src/storages/dataLoader.ts +20 -13
  22. package/src/storages/inMemory/InMemoryStorage.ts +16 -1
  23. package/src/storages/inMemory/InMemoryStorageCS.ts +13 -0
  24. package/src/sync/syncManagerOnline.ts +2 -2
  25. package/src/trackers/impressionObserver/impressionObserverCS.ts +1 -1
  26. package/src/types.ts +5 -3
  27. package/src/utils/settingsValidation/index.ts +9 -1
  28. package/types/storages/dataLoader.d.ts +2 -2
  29. package/types/trackers/impressionObserver/impressionObserverCS.d.ts +2 -2
  30. package/types/types.d.ts +4 -2
package/CHANGES.txt CHANGED
@@ -1,7 +1,7 @@
1
- 1.4.2 (June XX, 2022)
2
- - Added `sync.enabled` property to SDK configuration to allow synchronize splits and segments only once.
3
- - Updated telemetry to submit data even if user consent is not granted.
4
- - Updated submitters logic, to avoid duplicating the post of impressions to Split cloud when the SDK is destroyed while performing its periodic post of impressions.
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
5
 
6
6
  1.4.1 (June 13, 2022)
7
7
  - Bugfixing - Updated submitters logic, to avoid dropping impressions and events that are being tracked while POST request is pending.
@@ -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 {
@@ -19,9 +19,9 @@ 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, syncEnabled = _a.sync.enabled, 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
26
  var pushManager = syncEnabled && streamingEnabled && pollingManager && pushManagerFactory ?
27
27
  pushManagerFactory(params, pollingManager) :
@@ -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
@@ -127,6 +127,9 @@ function settingsValidation(config, validationParams) {
127
127
  // ensure a valid SDK mode
128
128
  // @ts-ignore, modify readonly prop
129
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
+ }
130
133
  // ensure a valid Storage based on mode defined.
131
134
  // @ts-ignore, modify readonly prop
132
135
  if (storage)
@@ -169,9 +172,13 @@ function settingsValidation(config, validationParams) {
169
172
  scheduler.pushRetryBackoffBase = fromSecondsToMillis(scheduler.pushRetryBackoffBase);
170
173
  }
171
174
  // validate sync enabled
172
- if (withDefaults.sync.enabled !== false) { // @ts-ignore, modify readonly prop
175
+ if (withDefaults.sync.enabled !== false) {
173
176
  withDefaults.sync.enabled = true;
174
177
  }
178
+ // validate sync onlySubmitters
179
+ if (withDefaults.sync.onlySubmitters !== true) {
180
+ withDefaults.sync.onlySubmitters = false;
181
+ }
175
182
  // validate the `splitFilters` settings and parse splits query
176
183
  var splitFiltersValidation = (0, splitFilters_1.validateSplitFilters)(log, withDefaults.sync.splitFilters, withDefaults.mode);
177
184
  withDefaults.sync.splitFilters = splitFiltersValidation.validFilters;
@@ -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`.
@@ -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 {
@@ -16,9 +16,9 @@ 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, syncEnabled = _a.sync.enabled, 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
23
  var pushManager = syncEnabled && streamingEnabled && pollingManager && pushManagerFactory ?
24
24
  pushManagerFactory(params, pollingManager) :
@@ -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() {
@@ -124,6 +124,9 @@ export function settingsValidation(config, validationParams) {
124
124
  // ensure a valid SDK mode
125
125
  // @ts-ignore, modify readonly prop
126
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
+ }
127
130
  // ensure a valid Storage based on mode defined.
128
131
  // @ts-ignore, modify readonly prop
129
132
  if (storage)
@@ -166,9 +169,13 @@ export function settingsValidation(config, validationParams) {
166
169
  scheduler.pushRetryBackoffBase = fromSecondsToMillis(scheduler.pushRetryBackoffBase);
167
170
  }
168
171
  // validate sync enabled
169
- if (withDefaults.sync.enabled !== false) { // @ts-ignore, modify readonly prop
172
+ if (withDefaults.sync.enabled !== false) {
170
173
  withDefaults.sync.enabled = true;
171
174
  }
175
+ // validate sync onlySubmitters
176
+ if (withDefaults.sync.onlySubmitters !== true) {
177
+ withDefaults.sync.onlySubmitters = false;
178
+ }
172
179
  // validate the `splitFilters` settings and parse splits query
173
180
  var splitFiltersValidation = validateSplitFilters(log, withDefaults.sync.splitFilters, withDefaults.mode);
174
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.2-rc.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",
@@ -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
  }
@@ -6,6 +6,8 @@ import { IStorageFactoryParams, IStorageSync } from '../types';
6
6
  import { ImpressionCountsCacheInMemory } from './ImpressionCountsCacheInMemory';
7
7
  import { LOCALHOST_MODE, STORAGE_MEMORY } from '../../utils/constants';
8
8
  import { TelemetryCacheInMemory } from './TelemetryCacheInMemory';
9
+ import { SplitIO } from '../../types';
10
+ import { setToArray, ISet } from '../../utils/lang/sets';
9
11
 
10
12
  /**
11
13
  * InMemory storage factory for standalone server-side SplitFactory
@@ -29,7 +31,20 @@ export function InMemoryStorageFactory(params: IStorageFactoryParams): IStorageS
29
31
  this.impressions.clear();
30
32
  this.impressionCounts && this.impressionCounts.clear();
31
33
  this.events.clear();
32
- }
34
+ },
35
+
36
+ // @ts-ignore, private method, for POC
37
+ getSnapshot(): SplitIO.PreloadedData {
38
+ return {
39
+ lastUpdated: Date.now(), // @ts-ignore accessing private prop
40
+ since: this.splits.changeNumber, // @ts-ignore accessing private prop
41
+ splitsData: this.splits.splitsCache, // @ts-ignore accessing private prop
42
+ segmentsData: Object.keys(this.segments.segmentCache).reduce((prev, cur) => { // @ts-ignore accessing private prop
43
+ prev[cur] = setToArray(this.segments.segmentCache[cur] as ISet<string>);
44
+ return prev;
45
+ }, {})
46
+ };
47
+ },
33
48
  };
34
49
  }
35
50
 
@@ -6,6 +6,7 @@ import { IStorageSync, IStorageFactoryParams } from '../types';
6
6
  import { ImpressionCountsCacheInMemory } from './ImpressionCountsCacheInMemory';
7
7
  import { LOCALHOST_MODE, STORAGE_MEMORY } from '../../utils/constants';
8
8
  import { shouldRecordTelemetry, TelemetryCacheInMemory } from './TelemetryCacheInMemory';
9
+ import { SplitIO } from '../../types';
9
10
 
10
11
  /**
11
12
  * InMemory storage factory for standalone client-side SplitFactory
@@ -31,6 +32,18 @@ export function InMemoryStorageCSFactory(params: IStorageFactoryParams): IStorag
31
32
  this.events.clear();
32
33
  },
33
34
 
35
+ // @ts-ignore, private method, for POC
36
+ getSnapshot(): SplitIO.PreloadedData {
37
+ return {
38
+ lastUpdated: Date.now(), // @ts-ignore accessing private prop
39
+ since: this.splits.changeNumber, // @ts-ignore accessing private prop
40
+ splitsData: this.splits.splitsCache,
41
+ mySegmentsData: { // @ts-ignore accessing private prop
42
+ [params.matchingKey as string]: Object.keys(this.segments.segmentCache)
43
+ }
44
+ };
45
+ },
46
+
34
47
  // When using shared instanciation with MEMORY we reuse everything but segments (they are unique per key)
35
48
  shared() {
36
49
  return {
@@ -28,10 +28,10 @@ export function syncManagerOnlineFactory(
28
28
  */
29
29
  return function (params: ISdkFactoryContextSync): ISyncManagerCS {
30
30
 
31
- const { settings, settings: { log, streamingEnabled, sync: { enabled: syncEnabled } }, telemetryTracker } = params;
31
+ const { settings, settings: { log, streamingEnabled, sync: { enabled: syncEnabled, onlySubmitters } }, telemetryTracker } = params;
32
32
 
33
33
  /** Polling Manager */
34
- const pollingManager = pollingManagerFactory && pollingManagerFactory(params);
34
+ const pollingManager = onlySubmitters ? undefined : pollingManagerFactory && pollingManagerFactory(params);
35
35
 
36
36
  /** Push Manager */
37
37
  const pushManager = syncEnabled && streamingEnabled && pollingManager && pushManagerFactory ?
@@ -4,7 +4,7 @@ import { buildKey } from './buildKey';
4
4
  import { ImpressionDTO } from '../../types';
5
5
 
6
6
  export function hashImpression32(impression: ImpressionDTO) {
7
- return hash(buildKey(impression));
7
+ return hash(buildKey(impression)).toString();
8
8
  }
9
9
 
10
10
  const LAST_SEEN_CACHE_SIZE = 500; // cache up to 500 impression hashes
package/src/types.ts CHANGED
@@ -4,7 +4,7 @@ import { ILogger } from './logger/types';
4
4
  import { ISdkFactoryContext } from './sdkFactory/types';
5
5
  /* eslint-disable no-use-before-define */
6
6
 
7
- import { IStorageFactoryParams, IStorageSync, IStorageAsync, IStorageSyncFactory, IStorageAsyncFactory } from './storages/types';
7
+ import { IStorageFactoryParams, IStorageSync, IStorageAsync, IStorageSyncFactory, IStorageAsyncFactory, DataLoader } from './storages/types';
8
8
  import { ISyncManagerCS } from './sync/types';
9
9
 
10
10
  /**
@@ -98,6 +98,7 @@ export interface ISettings {
98
98
  eventsFirstPushWindow: number
99
99
  },
100
100
  readonly storage: IStorageSyncFactory | IStorageAsyncFactory,
101
+ readonly dataLoader?: DataLoader,
101
102
  readonly integrations: Array<{
102
103
  readonly type: string,
103
104
  (params: IIntegrationFactoryParams): IIntegration | void
@@ -118,7 +119,8 @@ export interface ISettings {
118
119
  impressionsMode: SplitIO.ImpressionsMode,
119
120
  __splitFiltersValidation: ISplitFiltersValidation,
120
121
  localhostMode?: SplitIO.LocalhostFactory,
121
- enabled: boolean
122
+ enabled: boolean,
123
+ onlySubmitters: boolean
122
124
  },
123
125
  readonly runtime: {
124
126
  ip: string | false
@@ -752,7 +754,7 @@ export namespace SplitIO {
752
754
  * This property is ignored if `mySegmentsData` was provided.
753
755
  */
754
756
  segmentsData?: {
755
- [segmentName: string]: string
757
+ [segmentName: string]: string[]
756
758
  },
757
759
  }
758
760
  /**
@@ -147,6 +147,9 @@ export function settingsValidation(config: unknown, validationParams: ISettingsV
147
147
  // ensure a valid SDK mode
148
148
  // @ts-ignore, modify readonly prop
149
149
  withDefaults.mode = mode(withDefaults.core.authorizationKey, withDefaults.mode);
150
+ if (withDefaults.sync.onlySubmitters && withDefaults.mode === STANDALONE_MODE && !withDefaults.dataLoader) {
151
+ throw new Error('To use `onlySubmitters` param in standalone mode, DataLoader is required to preload data into the storage');
152
+ }
150
153
 
151
154
  // ensure a valid Storage based on mode defined.
152
155
  // @ts-ignore, modify readonly prop
@@ -193,10 +196,15 @@ export function settingsValidation(config: unknown, validationParams: ISettingsV
193
196
  }
194
197
 
195
198
  // validate sync enabled
196
- if (withDefaults.sync.enabled !== false) { // @ts-ignore, modify readonly prop
199
+ if (withDefaults.sync.enabled !== false) {
197
200
  withDefaults.sync.enabled = true;
198
201
  }
199
202
 
203
+ // validate sync onlySubmitters
204
+ if (withDefaults.sync.onlySubmitters !== true) {
205
+ withDefaults.sync.onlySubmitters = false;
206
+ }
207
+
200
208
  // validate the `splitFilters` settings and parse splits query
201
209
  const splitFiltersValidation = validateSplitFilters(log, withDefaults.sync.splitFilters, withDefaults.mode);
202
210
  withDefaults.sync.splitFilters = splitFiltersValidation.validFilters;
@@ -1,10 +1,10 @@
1
1
  import { SplitIO } from '../types';
2
2
  import { DataLoader } from './types';
3
3
  /**
4
- * Factory of client-side storage loader
4
+ * Factory of storage loader
5
5
  *
6
6
  * @param preloadedData validated data following the format proposed in https://github.com/godaddy/split-javascript-data-loader
7
7
  * and extended with a `mySegmentsData` property.
8
8
  * @returns function to preload the storage
9
9
  */
10
- export declare function dataLoaderFactory(preloadedData: SplitIO.PreloadedData): DataLoader;
10
+ export declare function DataLoaderFactory(preloadedData: SplitIO.PreloadedData): DataLoader;
@@ -1,4 +1,4 @@
1
1
  import { ImpressionObserver } from './ImpressionObserver';
2
2
  import { ImpressionDTO } from '../../types';
3
- export declare function hashImpression32(impression: ImpressionDTO): number;
4
- export declare function impressionObserverCSFactory(): ImpressionObserver<number>;
3
+ export declare function hashImpression32(impression: ImpressionDTO): string;
4
+ export declare function impressionObserverCSFactory(): ImpressionObserver<string>;
package/types/types.d.ts CHANGED
@@ -2,7 +2,7 @@ import { ISplitFiltersValidation } from './dtos/types';
2
2
  import { IIntegration, IIntegrationFactoryParams } from './integrations/types';
3
3
  import { ILogger } from './logger/types';
4
4
  import { ISdkFactoryContext } from './sdkFactory/types';
5
- import { IStorageFactoryParams, IStorageSync, IStorageAsync, IStorageSyncFactory, IStorageAsyncFactory } from './storages/types';
5
+ import { IStorageFactoryParams, IStorageSync, IStorageAsync, IStorageSyncFactory, IStorageAsyncFactory, DataLoader } from './storages/types';
6
6
  import { ISyncManagerCS } from './sync/types';
7
7
  /**
8
8
  * Reduced version of NodeJS.EventEmitter interface with the minimal methods used by the SDK
@@ -92,6 +92,7 @@ export interface ISettings {
92
92
  eventsFirstPushWindow: number;
93
93
  };
94
94
  readonly storage: IStorageSyncFactory | IStorageAsyncFactory;
95
+ readonly dataLoader?: DataLoader;
95
96
  readonly integrations: Array<{
96
97
  readonly type: string;
97
98
  (params: IIntegrationFactoryParams): IIntegration | void;
@@ -113,6 +114,7 @@ export interface ISettings {
113
114
  __splitFiltersValidation: ISplitFiltersValidation;
114
115
  localhostMode?: SplitIO.LocalhostFactory;
115
116
  enabled: boolean;
117
+ onlySubmitters: boolean;
116
118
  };
117
119
  readonly runtime: {
118
120
  ip: string | false;
@@ -749,7 +751,7 @@ export declare namespace SplitIO {
749
751
  * This property is ignored if `mySegmentsData` was provided.
750
752
  */
751
753
  segmentsData?: {
752
- [segmentName: string]: string;
754
+ [segmentName: string]: string[];
753
755
  };
754
756
  }
755
757
  /**