@splitsoftware/splitio-commons 1.17.1-rc.1 → 1.17.1-rc.3

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 (73) hide show
  1. package/CHANGES.txt +1 -0
  2. package/cjs/readiness/readinessManager.js +7 -5
  3. package/cjs/sdkClient/sdkClientMethodCS.js +7 -4
  4. package/cjs/sdkClient/sdkClientMethodCSWithTT.js +7 -4
  5. package/cjs/sdkFactory/index.js +33 -10
  6. package/cjs/storages/AbstractSplitsCacheAsync.js +0 -7
  7. package/cjs/storages/AbstractSplitsCacheSync.js +0 -7
  8. package/cjs/storages/dataLoader.js +64 -32
  9. package/cjs/storages/inLocalStorage/SplitsCacheInLocal.js +1 -9
  10. package/cjs/storages/inLocalStorage/index.js +6 -1
  11. package/cjs/storages/inMemory/InMemoryStorageCS.js +16 -4
  12. package/cjs/storages/inRedis/RedisAdapter.js +1 -1
  13. package/cjs/storages/pluggable/index.js +37 -32
  14. package/cjs/sync/offline/syncTasks/fromObjectSyncTask.js +2 -7
  15. package/cjs/sync/polling/updaters/splitChangesUpdater.js +1 -10
  16. package/cjs/trackers/eventTracker.js +11 -9
  17. package/cjs/trackers/impressionsTracker.js +15 -13
  18. package/cjs/trackers/uniqueKeysTracker.js +5 -3
  19. package/cjs/utils/settingsValidation/storage/storageCS.js +1 -12
  20. package/esm/readiness/readinessManager.js +7 -5
  21. package/esm/sdkClient/sdkClientMethodCS.js +7 -4
  22. package/esm/sdkClient/sdkClientMethodCSWithTT.js +7 -4
  23. package/esm/sdkFactory/index.js +34 -11
  24. package/esm/storages/AbstractSplitsCacheAsync.js +0 -7
  25. package/esm/storages/AbstractSplitsCacheSync.js +0 -7
  26. package/esm/storages/dataLoader.js +61 -30
  27. package/esm/storages/inLocalStorage/SplitsCacheInLocal.js +1 -9
  28. package/esm/storages/inLocalStorage/index.js +7 -2
  29. package/esm/storages/inMemory/InMemoryStorageCS.js +16 -4
  30. package/esm/storages/inRedis/RedisAdapter.js +1 -1
  31. package/esm/storages/pluggable/index.js +37 -32
  32. package/esm/sync/offline/syncTasks/fromObjectSyncTask.js +3 -8
  33. package/esm/sync/polling/updaters/splitChangesUpdater.js +2 -11
  34. package/esm/trackers/eventTracker.js +11 -9
  35. package/esm/trackers/impressionsTracker.js +15 -13
  36. package/esm/trackers/uniqueKeysTracker.js +5 -3
  37. package/esm/utils/settingsValidation/storage/storageCS.js +0 -10
  38. package/package.json +1 -1
  39. package/src/readiness/readinessManager.ts +9 -7
  40. package/src/readiness/types.ts +1 -0
  41. package/src/sdkClient/sdkClientMethodCS.ts +5 -2
  42. package/src/sdkClient/sdkClientMethodCSWithTT.ts +5 -2
  43. package/src/sdkFactory/index.ts +37 -12
  44. package/src/sdkFactory/types.ts +3 -0
  45. package/src/storages/AbstractSplitsCacheAsync.ts +0 -8
  46. package/src/storages/AbstractSplitsCacheSync.ts +0 -8
  47. package/src/storages/dataLoader.ts +62 -32
  48. package/src/storages/inLocalStorage/SplitsCacheInLocal.ts +1 -10
  49. package/src/storages/inLocalStorage/index.ts +8 -2
  50. package/src/storages/inMemory/InMemoryStorageCS.ts +19 -4
  51. package/src/storages/inRedis/RedisAdapter.ts +1 -1
  52. package/src/storages/pluggable/index.ts +38 -33
  53. package/src/storages/types.ts +2 -6
  54. package/src/sync/offline/syncTasks/fromObjectSyncTask.ts +3 -7
  55. package/src/sync/polling/updaters/splitChangesUpdater.ts +3 -11
  56. package/src/trackers/eventTracker.ts +10 -7
  57. package/src/trackers/impressionsTracker.ts +12 -9
  58. package/src/trackers/types.ts +1 -0
  59. package/src/trackers/uniqueKeysTracker.ts +6 -4
  60. package/src/types.ts +9 -8
  61. package/src/utils/settingsValidation/storage/storageCS.ts +0 -13
  62. package/types/readiness/types.d.ts +1 -0
  63. package/types/sdkFactory/types.d.ts +2 -0
  64. package/types/storages/AbstractSplitsCacheAsync.d.ts +0 -5
  65. package/types/storages/AbstractSplitsCacheSync.d.ts +0 -5
  66. package/types/storages/dataLoader.d.ts +17 -6
  67. package/types/storages/inLocalStorage/SplitsCacheInLocal.d.ts +0 -6
  68. package/types/storages/types.d.ts +2 -4
  69. package/types/trackers/eventTracker.d.ts +1 -1
  70. package/types/trackers/impressionsTracker.d.ts +1 -1
  71. package/types/trackers/types.d.ts +1 -0
  72. package/types/types.d.ts +8 -8
  73. package/types/utils/settingsValidation/storage/storageCS.d.ts +0 -5
package/CHANGES.txt CHANGED
@@ -1,4 +1,5 @@
1
1
  2.0.0 (September XX, 2024)
2
+ - Updated internal storage factory to emit the SDK_READY_FROM_CACHE event when it corresponds, to clean up the initialization flow.
2
3
  - Added `factory.destroy()` method, which invokes the `destroy` method on all SDK clients created by the factory.
3
4
  - Added support for targeting rules based on large segments.
4
5
  - BREAKING CHANGES:
@@ -45,17 +45,14 @@ function readinessManagerFactory(EventEmitter, settings, splits) {
45
45
  splits.once(constants_1.SDK_SPLITS_CACHE_LOADED, checkIsReadyFromCache);
46
46
  // emit SDK_READY_TIMED_OUT
47
47
  var hasTimedout = false;
48
+ var readyTimeoutId;
48
49
  function timeout() {
49
- if (hasTimedout)
50
+ if (hasTimedout || isReady)
50
51
  return;
51
52
  hasTimedout = true;
52
53
  syncLastUpdate();
53
54
  gate.emit(constants_1.SDK_READY_TIMED_OUT, 'Split SDK emitted SDK_READY_TIMED_OUT event.');
54
55
  }
55
- var readyTimeoutId;
56
- if (readyTimeout > 0) {
57
- readyTimeoutId = setTimeout(timeout, readyTimeout);
58
- }
59
56
  // emit SDK_READY and SDK_UPDATE
60
57
  var isReady = false;
61
58
  splits.on(constants_1.SDK_SPLITS_ARRIVED, checkIsReadyOrUpdate);
@@ -116,6 +113,11 @@ function readinessManagerFactory(EventEmitter, settings, splits) {
116
113
  // Called on 403 error (client-side SDK key on server-side), to set the SDK as destroyed for
117
114
  // tracking and evaluations, while keeping event listeners to emit SDK_READY_TIMED_OUT event
118
115
  setDestroyed: function () { isDestroyed = true; },
116
+ init: function () {
117
+ if (readyTimeout > 0) {
118
+ readyTimeoutId = setTimeout(timeout, readyTimeout);
119
+ }
120
+ },
119
121
  destroy: function () {
120
122
  isDestroyed = true;
121
123
  syncLastUpdate();
@@ -14,7 +14,7 @@ var identity_1 = require("./identity");
14
14
  * Therefore, clients don't have a bound TT for the track method.
15
15
  */
16
16
  function sdkClientMethodCSFactory(params) {
17
- var clients = params.clients, storage = params.storage, syncManager = params.syncManager, sdkReadinessManager = params.sdkReadinessManager, _a = params.settings, key = _a.core.key, log = _a.log;
17
+ var clients = params.clients, storage = params.storage, syncManager = params.syncManager, sdkReadinessManager = params.sdkReadinessManager, _a = params.settings, key = _a.core.key, log = _a.log, whenInit = params.whenInit;
18
18
  var mainClientInstance = (0, clientCS_1.clientCSDecorator)(log, (0, sdkClient_1.sdkClientFactory)(params), key);
19
19
  var parsedDefaultKey = (0, key_2.keyParser)(key);
20
20
  var defaultInstanceId = (0, identity_1.buildInstanceId)(parsedDefaultKey);
@@ -47,15 +47,18 @@ function sdkClientMethodCSFactory(params) {
47
47
  // - Consumer mode: both syncManager and sharedSyncManager are undefined
48
48
  // - Consumer partial mode: syncManager is defined (only for submitters) but sharedSyncManager is undefined
49
49
  // @ts-ignore
50
- var sharedSyncManager = syncManager && sharedStorage && syncManager.shared(matchingKey, sharedSdkReadiness_1.readinessManager, sharedStorage);
50
+ var sharedSyncManager_1 = syncManager && sharedStorage && syncManager.shared(matchingKey, sharedSdkReadiness_1.readinessManager, sharedStorage);
51
51
  // As shared clients reuse all the storage information, we don't need to check here if we
52
52
  // will use offline or online mode. We should stick with the original decision.
53
53
  clients[instanceId] = (0, clientCS_1.clientCSDecorator)(log, (0, sdkClient_1.sdkClientFactory)((0, objectAssign_1.objectAssign)({}, params, {
54
54
  sdkReadinessManager: sharedSdkReadiness_1,
55
55
  storage: sharedStorage || storage,
56
- syncManager: sharedSyncManager,
56
+ syncManager: sharedSyncManager_1,
57
57
  }), true), validKey);
58
- sharedSyncManager && sharedSyncManager.start();
58
+ whenInit(function () {
59
+ sharedSdkReadiness_1.readinessManager.init();
60
+ sharedSyncManager_1 && sharedSyncManager_1.start();
61
+ });
59
62
  log.info(constants_1.NEW_SHARED_CLIENT);
60
63
  }
61
64
  else {
@@ -16,7 +16,7 @@ var identity_1 = require("./identity");
16
16
  * (default client) or the client method (shared clients).
17
17
  */
18
18
  function sdkClientMethodCSFactory(params) {
19
- var clients = params.clients, storage = params.storage, syncManager = params.syncManager, sdkReadinessManager = params.sdkReadinessManager, _a = params.settings, _b = _a.core, key = _b.key, trafficType = _b.trafficType, log = _a.log;
19
+ var clients = params.clients, storage = params.storage, syncManager = params.syncManager, sdkReadinessManager = params.sdkReadinessManager, _a = params.settings, _b = _a.core, key = _b.key, trafficType = _b.trafficType, log = _a.log, whenInit = params.whenInit;
20
20
  var mainClientInstance = (0, clientCS_1.clientCSDecorator)(log, (0, sdkClient_1.sdkClientFactory)(params), key, trafficType);
21
21
  var parsedDefaultKey = (0, key_2.keyParser)(key);
22
22
  var defaultInstanceId = (0, identity_1.buildInstanceId)(parsedDefaultKey, trafficType);
@@ -56,15 +56,18 @@ function sdkClientMethodCSFactory(params) {
56
56
  // - Consumer mode: both syncManager and sharedSyncManager are undefined
57
57
  // - Consumer partial mode: syncManager is defined (only for submitters) but sharedSyncManager is undefined
58
58
  // @ts-ignore
59
- var sharedSyncManager = syncManager && sharedStorage && syncManager.shared(matchingKey, sharedSdkReadiness_1.readinessManager, sharedStorage);
59
+ var sharedSyncManager_1 = syncManager && sharedStorage && syncManager.shared(matchingKey, sharedSdkReadiness_1.readinessManager, sharedStorage);
60
60
  // As shared clients reuse all the storage information, we don't need to check here if we
61
61
  // will use offline or online mode. We should stick with the original decision.
62
62
  clients[instanceId] = (0, clientCS_1.clientCSDecorator)(log, (0, sdkClient_1.sdkClientFactory)((0, objectAssign_1.objectAssign)({}, params, {
63
63
  sdkReadinessManager: sharedSdkReadiness_1,
64
64
  storage: sharedStorage || storage,
65
- syncManager: sharedSyncManager,
65
+ syncManager: sharedSyncManager_1,
66
66
  }), true), validKey, validTrafficType);
67
- sharedSyncManager && sharedSyncManager.start();
67
+ whenInit(function () {
68
+ sharedSdkReadiness_1.readinessManager.init();
69
+ sharedSyncManager_1 && sharedSyncManager_1.start();
70
+ });
68
71
  log.info(constants_1.NEW_SHARED_CLIENT);
69
72
  }
70
73
  else {
@@ -19,12 +19,19 @@ var constants_3 = require("../utils/constants");
19
19
  * Modular SDK factory
20
20
  */
21
21
  function sdkFactory(params) {
22
- var settings = params.settings, platform = params.platform, storageFactory = params.storageFactory, splitApiFactory = params.splitApiFactory, extraProps = params.extraProps, syncManagerFactory = params.syncManagerFactory, SignalListener = params.SignalListener, impressionsObserverFactory = params.impressionsObserverFactory, integrationsManagerFactory = params.integrationsManagerFactory, sdkManagerFactory = params.sdkManagerFactory, sdkClientMethodFactory = params.sdkClientMethodFactory, filterAdapterFactory = params.filterAdapterFactory;
22
+ var settings = params.settings, platform = params.platform, storageFactory = params.storageFactory, splitApiFactory = params.splitApiFactory, extraProps = params.extraProps, syncManagerFactory = params.syncManagerFactory, SignalListener = params.SignalListener, impressionsObserverFactory = params.impressionsObserverFactory, integrationsManagerFactory = params.integrationsManagerFactory, sdkManagerFactory = params.sdkManagerFactory, sdkClientMethodFactory = params.sdkClientMethodFactory, filterAdapterFactory = params.filterAdapterFactory, isPure = params.isPure;
23
23
  var log = settings.log, impressionsMode = settings.sync.impressionsMode;
24
24
  // @TODO handle non-recoverable errors, such as, global `fetch` not available, invalid SDK Key, etc.
25
25
  // On non-recoverable errors, we should mark the SDK as destroyed and not start synchronization.
26
- // We will just log and allow for the SDK to end up throwing an SDK_TIMEOUT event for devs to handle.
27
- (0, apiKey_1.validateAndTrackApiKey)(log, settings.core.authorizationKey);
26
+ // initialization
27
+ var isInit = false;
28
+ var initCallbacks = [];
29
+ function whenInit(cb) {
30
+ if (isInit)
31
+ cb();
32
+ else
33
+ initCallbacks.push(cb);
34
+ }
28
35
  var sdkReadinessManager = (0, sdkReadinessManager_1.sdkReadinessManagerFactory)(platform.EventEmitter, settings);
29
36
  var readiness = sdkReadinessManager.readinessManager;
30
37
  var storage = storageFactory({
@@ -38,8 +45,10 @@ function sdkFactory(params) {
38
45
  readiness.splits.emit(constants_2.SDK_SPLITS_ARRIVED);
39
46
  readiness.segments.emit(constants_2.SDK_SEGMENTS_ARRIVED);
40
47
  },
48
+ onReadyFromCacheCb: function () {
49
+ readiness.splits.emit(constants_2.SDK_SPLITS_CACHE_LOADED);
50
+ }
41
51
  });
42
- // @TODO add support for dataloader: `if (params.dataLoader) params.dataLoader(storage);`
43
52
  var clients = {};
44
53
  var telemetryTracker = (0, telemetryTracker_1.telemetryTrackerFactory)(storage.telemetry, platform.now);
45
54
  var integrationsManager = integrationsManagerFactory && integrationsManagerFactory({ settings: settings, storage: storage, telemetryTracker: telemetryTracker });
@@ -56,11 +65,11 @@ function sdkFactory(params) {
56
65
  default:
57
66
  strategy = (0, strategyDebug_1.strategyDebugFactory)(observer);
58
67
  }
59
- var impressionsTracker = (0, impressionsTracker_1.impressionsTrackerFactory)(settings, storage.impressions, strategy, integrationsManager, storage.telemetry);
60
- var eventTracker = (0, eventTracker_1.eventTrackerFactory)(settings, storage.events, integrationsManager, storage.telemetry);
68
+ var impressionsTracker = (0, impressionsTracker_1.impressionsTrackerFactory)(settings, storage.impressions, strategy, whenInit, integrationsManager, storage.telemetry);
69
+ var eventTracker = (0, eventTracker_1.eventTrackerFactory)(settings, storage.events, whenInit, integrationsManager, storage.telemetry);
61
70
  // splitApi is used by SyncManager and Browser signal listener
62
71
  var splitApi = splitApiFactory && splitApiFactory(settings, platform, telemetryTracker);
63
- var ctx = { clients: clients, splitApi: splitApi, eventTracker: eventTracker, impressionsTracker: impressionsTracker, telemetryTracker: telemetryTracker, uniqueKeysTracker: uniqueKeysTracker, sdkReadinessManager: sdkReadinessManager, readiness: readiness, settings: settings, storage: storage, platform: platform };
72
+ var ctx = { clients: clients, splitApi: splitApi, eventTracker: eventTracker, impressionsTracker: impressionsTracker, telemetryTracker: telemetryTracker, uniqueKeysTracker: uniqueKeysTracker, sdkReadinessManager: sdkReadinessManager, readiness: readiness, settings: settings, storage: storage, platform: platform, whenInit: whenInit };
64
73
  var syncManager = syncManagerFactory && syncManagerFactory(ctx);
65
74
  ctx.syncManager = syncManager;
66
75
  var signalListener = SignalListener && new SignalListener(syncManager, settings, storage, splitApi);
@@ -68,8 +77,22 @@ function sdkFactory(params) {
68
77
  // SDK client and manager
69
78
  var clientMethod = sdkClientMethodFactory(ctx);
70
79
  var managerInstance = sdkManagerFactory(settings, storage.splits, sdkReadinessManager);
71
- syncManager && syncManager.start();
72
- signalListener && signalListener.start();
80
+ function init() {
81
+ if (isInit)
82
+ return;
83
+ isInit = true;
84
+ // We will just log and allow for the SDK to end up throwing an SDK_TIMEOUT event for devs to handle.
85
+ (0, apiKey_1.validateAndTrackApiKey)(log, settings.core.authorizationKey);
86
+ readiness.init();
87
+ storage.init && storage.init();
88
+ uniqueKeysTracker && uniqueKeysTracker.start();
89
+ syncManager && syncManager.start();
90
+ signalListener && signalListener.start();
91
+ initCallbacks.forEach(function (cb) { return cb(); });
92
+ initCallbacks.length = 0;
93
+ }
94
+ if (!isPure)
95
+ init();
73
96
  log.info(constants_1.NEW_FACTORY);
74
97
  // @ts-ignore
75
98
  return (0, objectAssign_1.objectAssign)({
@@ -86,6 +109,6 @@ function sdkFactory(params) {
86
109
  destroy: function () {
87
110
  return Promise.all(Object.keys(clients).map(function (key) { return clients[key].destroy(); })).then(function () { });
88
111
  }
89
- }, extraProps && extraProps(ctx));
112
+ }, extraProps && extraProps(ctx), isPure && { init: init });
90
113
  }
91
114
  exports.sdkFactory = sdkFactory;
@@ -14,13 +14,6 @@ var AbstractSplitsCacheAsync = /** @class */ (function () {
14
14
  AbstractSplitsCacheAsync.prototype.usesSegments = function () {
15
15
  return Promise.resolve(true);
16
16
  };
17
- /**
18
- * Check if the splits information is already stored in cache.
19
- * Noop, just keeping the interface. This is used by client-side implementations only.
20
- */
21
- AbstractSplitsCacheAsync.prototype.checkCache = function () {
22
- return Promise.resolve(false);
23
- };
24
17
  /**
25
18
  * Kill `name` split and set `defaultTreatment` and `changeNumber`.
26
19
  * Used for SPLIT_KILL push notifications.
@@ -30,13 +30,6 @@ var AbstractSplitsCacheSync = /** @class */ (function () {
30
30
  var _this = this;
31
31
  return this.getSplitNames().map(function (key) { return _this.getSplit(key); });
32
32
  };
33
- /**
34
- * Check if the splits information is already stored in cache. This data can be preloaded.
35
- * It is used as condition to emit SDK_SPLITS_CACHE_LOADED, and then SDK_READY_FROM_CACHE.
36
- */
37
- AbstractSplitsCacheSync.prototype.checkCache = function () {
38
- return false;
39
- };
40
33
  /**
41
34
  * Kill `name` split and set `defaultTreatment` and `changeNumber`.
42
35
  * Used for SPLIT_KILL push notifications.
@@ -1,51 +1,83 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.dataLoaderFactory = void 0;
4
- var browser_1 = require("../utils/constants/browser");
3
+ exports.getSnapshot = exports.loadData = void 0;
4
+ var sets_1 = require("../utils/lang/sets");
5
+ var key_1 = require("../utils/key");
5
6
  /**
6
- * Factory of client-side storage loader
7
+ * Storage-agnostic adaptation of `loadDataIntoLocalStorage` function
8
+ * (https://github.com/godaddy/split-javascript-data-loader/blob/master/src/load-data.js)
7
9
  *
8
- * @param preloadedData validated data following the format proposed in https://github.com/godaddy/split-javascript-data-loader
9
- * and extended with a `mySegmentsData` property.
10
- * @returns function to preload the storage
10
+ * @param preloadedData validated data following the format proposed in https://github.com/godaddy/split-javascript-data-loader and extended with a `mySegmentsData` property.
11
+ * @param storage object containing `splits` and `segments` cache (client-side variant)
12
+ * @param userKey user key (matching key) of the provided MySegmentsCache
13
+ *
14
+ * @TODO extend to load largeSegments
15
+ * @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.
16
+ * @TODO add logs, and input validation in this module, in favor of size reduction.
17
+ * @TODO unit tests
11
18
  */
12
- function dataLoaderFactory(preloadedData) {
13
- /**
14
- * Storage-agnostic adaptation of `loadDataIntoLocalStorage` function
15
- * (https://github.com/godaddy/split-javascript-data-loader/blob/master/src/load-data.js)
16
- *
17
- * @param storage object containing `splits` and `segments` cache (client-side variant)
18
- * @param userId user key string of the provided MySegmentsCache
19
- *
20
- * @TODO extend to support SegmentsCache (server-side variant) by making `userId` optional and adding the corresponding logic.
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
- */
23
- return function loadData(storage, userId) {
24
- // Do not load data if current preloadedData is empty
25
- if (Object.keys(preloadedData).length === 0)
26
- return;
27
- var _a = preloadedData.lastUpdated, lastUpdated = _a === void 0 ? -1 : _a, _b = preloadedData.segmentsData, segmentsData = _b === void 0 ? {} : _b, _c = preloadedData.since, since = _c === void 0 ? -1 : _c, _d = preloadedData.splitsData, splitsData = _d === void 0 ? {} : _d;
19
+ function loadData(preloadedData, storage, matchingKey) {
20
+ // Do not load data if current preloadedData is empty
21
+ if (Object.keys(preloadedData).length === 0)
22
+ return;
23
+ var _a = preloadedData.segmentsData, segmentsData = _a === void 0 ? {} : _a, _b = preloadedData.since, since = _b === void 0 ? -1 : _b, _c = preloadedData.splitsData, splitsData = _c === void 0 ? [] : _c;
24
+ if (storage.splits) {
28
25
  var storedSince = storage.splits.getChangeNumber();
29
- var expirationTimestamp = Date.now() - browser_1.DEFAULT_CACHE_EXPIRATION_IN_MILLIS;
30
- // Do not load data if current localStorage data is more recent,
31
- // or if its `lastUpdated` timestamp is older than the given `expirationTimestamp`,
32
- if (storedSince > since || lastUpdated < expirationTimestamp)
26
+ // Do not load data if current data is more recent
27
+ if (storedSince > since)
33
28
  return;
34
29
  // cleaning up the localStorage data, since some cached splits might need be part of the preloaded data
35
30
  storage.splits.clear();
36
31
  storage.splits.setChangeNumber(since);
37
32
  // splitsData in an object where the property is the split name and the pertaining value is a stringified json of its data
38
- storage.splits.addSplits(Object.keys(splitsData).map(function (splitName) { return JSON.parse(splitsData[splitName]); }));
39
- // add mySegments data
40
- var mySegmentsData = preloadedData.mySegmentsData && preloadedData.mySegmentsData[userId];
33
+ storage.splits.addSplits(splitsData.map(function (split) { return ([split.name, split]); }));
34
+ }
35
+ if (matchingKey) { // add mySegments data (client-side)
36
+ var mySegmentsData = preloadedData.mySegmentsData && preloadedData.mySegmentsData[matchingKey];
41
37
  if (!mySegmentsData) {
42
38
  // 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
39
  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
+ var matchingKeys = segmentsData[segmentName];
41
+ return matchingKeys.indexOf(matchingKey) > -1;
46
42
  });
47
43
  }
48
44
  storage.segments.resetSegments({ k: mySegmentsData.map(function (s) { return ({ n: s }); }) });
45
+ }
46
+ else { // add segments data (server-side)
47
+ Object.keys(segmentsData).filter(function (segmentName) {
48
+ var matchingKeys = segmentsData[segmentName];
49
+ storage.segments.addToSegment(segmentName, matchingKeys);
50
+ });
51
+ }
52
+ }
53
+ exports.loadData = loadData;
54
+ function getSnapshot(storage, userKeys) {
55
+ return {
56
+ // lastUpdated: Date.now(),
57
+ since: storage.splits.getChangeNumber(),
58
+ splitsData: storage.splits.getAll(),
59
+ segmentsData: userKeys ?
60
+ undefined : // @ts-ignore accessing private prop
61
+ Object.keys(storage.segments.segmentCache).reduce(function (prev, cur) {
62
+ prev[cur] = (0, sets_1.setToArray)(storage.segments.segmentCache[cur]);
63
+ return prev;
64
+ }, {}),
65
+ mySegmentsData: userKeys ?
66
+ userKeys.reduce(function (prev, userKey) {
67
+ prev[(0, key_1.getMatching)(userKey)] = storage.shared ?
68
+ // Client-side segments
69
+ // @ts-ignore accessing private prop
70
+ Object.keys(storage.shared(userKey).segments.segmentCache) :
71
+ // Server-side segments
72
+ // @ts-ignore accessing private prop
73
+ Object.keys(storage.segments.segmentCache).reduce(function (prev, segmentName) {
74
+ return storage.segments.segmentCache[segmentName].has(userKey) ?
75
+ prev.concat(segmentName) :
76
+ prev;
77
+ }, []);
78
+ return prev;
79
+ }, {}) :
80
+ undefined
49
81
  };
50
82
  }
51
- exports.dataLoaderFactory = dataLoaderFactory;
83
+ exports.getSnapshot = getSnapshot;
@@ -186,14 +186,6 @@ var SplitsCacheInLocal = /** @class */ (function (_super) {
186
186
  return true;
187
187
  }
188
188
  };
189
- /**
190
- * Check if the splits information is already stored in browser LocalStorage.
191
- * In this function we could add more code to check if the data is valid.
192
- * @override
193
- */
194
- SplitsCacheInLocal.prototype.checkCache = function () {
195
- return this.getChangeNumber() > -1;
196
- };
197
189
  /**
198
190
  * Clean Splits cache if its `lastUpdated` timestamp is older than the given `expirationTimestamp`,
199
191
  *
@@ -216,7 +208,7 @@ var SplitsCacheInLocal = /** @class */ (function (_super) {
216
208
  // mark cache to update the new query filter on first successful splits fetch
217
209
  this.updateNewFilter = true;
218
210
  // if there is cache, clear it
219
- if (this.checkCache())
211
+ if (this.getChangeNumber() > -1)
220
212
  this.clear();
221
213
  }
222
214
  catch (e) {
@@ -30,7 +30,7 @@ function InLocalStorage(options) {
30
30
  params.settings.log.warn(constants_1.LOG_PREFIX + 'LocalStorage API is unavailable. Falling back to default MEMORY storage');
31
31
  return (0, InMemoryStorageCS_1.InMemoryStorageCSFactory)(params);
32
32
  }
33
- var settings = params.settings, _a = params.settings, log = _a.log, _b = _a.scheduler, impressionsQueueSize = _b.impressionsQueueSize, eventsQueueSize = _b.eventsQueueSize, _c = _a.sync, impressionsMode = _c.impressionsMode, __splitFiltersValidation = _c.__splitFiltersValidation;
33
+ var onReadyFromCacheCb = params.onReadyFromCacheCb, settings = params.settings, _a = params.settings, log = _a.log, _b = _a.scheduler, impressionsQueueSize = _b.impressionsQueueSize, eventsQueueSize = _b.eventsQueueSize, _c = _a.sync, impressionsMode = _c.impressionsMode, __splitFiltersValidation = _c.__splitFiltersValidation;
34
34
  var matchingKey = (0, key_1.getMatching)(settings.core.key);
35
35
  var keys = new KeyBuilderCS_1.KeyBuilderCS(prefix, matchingKey);
36
36
  var expirationTimestamp = Date.now() - browser_1.DEFAULT_CACHE_EXPIRATION_IN_MILLIS;
@@ -46,6 +46,11 @@ function InLocalStorage(options) {
46
46
  events: new EventsCacheInMemory_1.EventsCacheInMemory(eventsQueueSize),
47
47
  telemetry: (0, TelemetryCacheInMemory_1.shouldRecordTelemetry)(params) ? new TelemetryCacheInMemory_1.TelemetryCacheInMemory(splits, segments) : undefined,
48
48
  uniqueKeys: impressionsMode === constants_2.NONE ? new UniqueKeysCacheInMemoryCS_1.UniqueKeysCacheInMemoryCS() : undefined,
49
+ init: function () {
50
+ if (settings.mode === constants_2.LOCALHOST_MODE || splits.getChangeNumber() > -1) {
51
+ Promise.resolve().then(onReadyFromCacheCb);
52
+ }
53
+ },
49
54
  destroy: function () {
50
55
  var _a;
51
56
  this.splits = new SplitsCacheInMemory_1.SplitsCacheInMemory(__splitFiltersValidation);
@@ -9,13 +9,15 @@ var ImpressionCountsCacheInMemory_1 = require("./ImpressionCountsCacheInMemory")
9
9
  var constants_1 = require("../../utils/constants");
10
10
  var TelemetryCacheInMemory_1 = require("./TelemetryCacheInMemory");
11
11
  var UniqueKeysCacheInMemoryCS_1 = require("./UniqueKeysCacheInMemoryCS");
12
+ var key_1 = require("../../utils/key");
13
+ var dataLoader_1 = require("../dataLoader");
12
14
  /**
13
15
  * InMemory storage factory for standalone client-side SplitFactory
14
16
  *
15
17
  * @param params parameters required by EventsCacheSync
16
18
  */
17
19
  function InMemoryStorageCSFactory(params) {
18
- var _a = params.settings, _b = _a.scheduler, impressionsQueueSize = _b.impressionsQueueSize, eventsQueueSize = _b.eventsQueueSize, _c = _a.sync, impressionsMode = _c.impressionsMode, __splitFiltersValidation = _c.__splitFiltersValidation;
20
+ var _a = params.settings, _b = _a.scheduler, impressionsQueueSize = _b.impressionsQueueSize, eventsQueueSize = _b.eventsQueueSize, _c = _a.sync, impressionsMode = _c.impressionsMode, __splitFiltersValidation = _c.__splitFiltersValidation, preloadedData = _a.preloadedData, onReadyFromCacheCb = params.onReadyFromCacheCb;
19
21
  var splits = new SplitsCacheInMemory_1.SplitsCacheInMemory(__splitFiltersValidation);
20
22
  var segments = new MySegmentsCacheInMemory_1.MySegmentsCacheInMemory();
21
23
  var largeSegments = new MySegmentsCacheInMemory_1.MySegmentsCacheInMemory();
@@ -39,11 +41,16 @@ function InMemoryStorageCSFactory(params) {
39
41
  this.uniqueKeys && this.uniqueKeys.clear();
40
42
  },
41
43
  // When using shared instanciation with MEMORY we reuse everything but segments (they are unique per key)
42
- shared: function () {
44
+ shared: function (matchingKey) {
45
+ var segments = new MySegmentsCacheInMemory_1.MySegmentsCacheInMemory();
46
+ var largeSegments = new MySegmentsCacheInMemory_1.MySegmentsCacheInMemory();
47
+ if (preloadedData) {
48
+ (0, dataLoader_1.loadData)(preloadedData, { segments: segments, largeSegments: largeSegments }, matchingKey);
49
+ }
43
50
  return {
44
51
  splits: this.splits,
45
- segments: new MySegmentsCacheInMemory_1.MySegmentsCacheInMemory(),
46
- largeSegments: new MySegmentsCacheInMemory_1.MySegmentsCacheInMemory(),
52
+ segments: segments,
53
+ largeSegments: largeSegments,
47
54
  impressions: this.impressions,
48
55
  impressionCounts: this.impressionCounts,
49
56
  events: this.events,
@@ -68,6 +75,11 @@ function InMemoryStorageCSFactory(params) {
68
75
  if (storage.uniqueKeys)
69
76
  storage.uniqueKeys.track = noopTrack;
70
77
  }
78
+ if (preloadedData) {
79
+ (0, dataLoader_1.loadData)(preloadedData, storage, (0, key_1.getMatching)(params.settings.core.key));
80
+ if (splits.getChangeNumber() > -1)
81
+ onReadyFromCacheCb();
82
+ }
71
83
  return storage;
72
84
  }
73
85
  exports.InMemoryStorageCSFactory = InMemoryStorageCSFactory;
@@ -20,7 +20,7 @@ var DEFAULT_OPTIONS = {
20
20
  var DEFAULT_LIBRARY_OPTIONS = {
21
21
  enableOfflineQueue: false,
22
22
  connectTimeout: DEFAULT_OPTIONS.connectionTimeout,
23
- lazyConnect: false
23
+ lazyConnect: false // @TODO true to avoid side-effects on instantiation.
24
24
  };
25
25
  /**
26
26
  * Redis adapter on top of the library of choice (written with ioredis) for some extra control.
@@ -58,6 +58,7 @@ function PluggableStorage(options) {
58
58
  var metadata = (0, utils_1.metadataBuilder)(settings);
59
59
  var keys = new KeyBuilderSS_1.KeyBuilderSS(prefix, metadata);
60
60
  var wrapper = (0, wrapperAdapter_1.wrapperAdapter)(log, options.wrapper);
61
+ var connectPromise;
61
62
  var isSyncronizer = mode === undefined; // If mode is not defined, the synchronizer is running
62
63
  var isPartialConsumer = mode === constants_1.CONSUMER_PARTIAL_MODE;
63
64
  var telemetry = (0, TelemetryCacheInMemory_1.shouldRecordTelemetry)(params) || isSyncronizer ?
@@ -75,37 +76,6 @@ function PluggableStorage(options) {
75
76
  settings.core.key === undefined ? new UniqueKeysCacheInMemory_1.UniqueKeysCacheInMemory() : new UniqueKeysCacheInMemoryCS_1.UniqueKeysCacheInMemoryCS() :
76
77
  new UniqueKeysCachePluggable_1.UniqueKeysCachePluggable(log, keys.buildUniqueKeysKey(), wrapper) :
77
78
  undefined;
78
- // Connects to wrapper and emits SDK_READY event on main client
79
- var connectPromise = wrapper.connect().then(function () {
80
- if (isSyncronizer) {
81
- // In standalone or producer mode, clear storage if SDK key or feature flag filter has changed
82
- return wrapper.get(keys.buildHashKey()).then(function (hash) {
83
- var currentHash = (0, KeyBuilder_1.getStorageHash)(settings);
84
- if (hash !== currentHash) {
85
- log.info(constants_2.LOG_PREFIX + 'Storage HASH has changed (SDK key, flags filter criteria or flags spec version was modified). Clearing cache');
86
- return wrapper.getKeysByPrefix(keys.prefix + ".").then(function (storageKeys) {
87
- return Promise.all(storageKeys.map(function (storageKey) { return wrapper.del(storageKey); }));
88
- }).then(function () { return wrapper.set(keys.buildHashKey(), currentHash); });
89
- }
90
- }).then(function () {
91
- onReadyCb();
92
- });
93
- }
94
- else {
95
- // Start periodic flush of async storages if not running synchronizer (producer mode)
96
- if (impressionCountsCache && impressionCountsCache.start)
97
- impressionCountsCache.start();
98
- if (uniqueKeysCache && uniqueKeysCache.start)
99
- uniqueKeysCache.start();
100
- if (telemetry && telemetry.recordConfig)
101
- telemetry.recordConfig();
102
- onReadyCb();
103
- }
104
- }).catch(function (e) {
105
- e = e || new Error('Error connecting wrapper');
106
- onReadyCb(e);
107
- return e; // Propagate error for shared clients
108
- });
109
79
  return {
110
80
  splits: new SplitsCachePluggable_1.SplitsCachePluggable(log, keys, wrapper, settings.sync.__splitFiltersValidation),
111
81
  segments: new SegmentsCachePluggable_1.SegmentsCachePluggable(log, keys, wrapper),
@@ -114,6 +84,41 @@ function PluggableStorage(options) {
114
84
  events: isPartialConsumer ? promisifyEventsTrack(new EventsCacheInMemory_1.EventsCacheInMemory(eventsQueueSize)) : new EventsCachePluggable_1.EventsCachePluggable(log, keys.buildEventsKey(), wrapper, metadata),
115
85
  telemetry: telemetry,
116
86
  uniqueKeys: uniqueKeysCache,
87
+ init: function () {
88
+ if (connectPromise)
89
+ return connectPromise;
90
+ // Connects to wrapper and emits SDK_READY event on main client
91
+ return connectPromise = wrapper.connect().then(function () {
92
+ if (isSyncronizer) {
93
+ // In standalone or producer mode, clear storage if SDK key or feature flag filter has changed
94
+ return wrapper.get(keys.buildHashKey()).then(function (hash) {
95
+ var currentHash = (0, KeyBuilder_1.getStorageHash)(settings);
96
+ if (hash !== currentHash) {
97
+ log.info(constants_2.LOG_PREFIX + 'Storage HASH has changed (SDK key, flags filter criteria or flags spec version was modified). Clearing cache');
98
+ return wrapper.getKeysByPrefix(keys.prefix + ".").then(function (storageKeys) {
99
+ return Promise.all(storageKeys.map(function (storageKey) { return wrapper.del(storageKey); }));
100
+ }).then(function () { return wrapper.set(keys.buildHashKey(), currentHash); });
101
+ }
102
+ }).then(function () {
103
+ onReadyCb();
104
+ });
105
+ }
106
+ else {
107
+ // Start periodic flush of async storages if not running synchronizer (producer mode)
108
+ if (impressionCountsCache && impressionCountsCache.start)
109
+ impressionCountsCache.start();
110
+ if (uniqueKeysCache && uniqueKeysCache.start)
111
+ uniqueKeysCache.start();
112
+ if (telemetry && telemetry.recordConfig)
113
+ telemetry.recordConfig();
114
+ onReadyCb();
115
+ }
116
+ }).catch(function (e) {
117
+ e = e || new Error('Error connecting wrapper');
118
+ onReadyCb(e);
119
+ return e; // Propagate error for shared clients
120
+ });
121
+ },
117
122
  // Stop periodic flush and disconnect the underlying storage
118
123
  destroy: function () {
119
124
  return Promise.all(isSyncronizer ? [] : [
@@ -123,7 +128,7 @@ function PluggableStorage(options) {
123
128
  },
124
129
  // emits SDK_READY event on shared clients and returns a reference to the storage
125
130
  shared: function (_, onReadyCb) {
126
- connectPromise.then(onReadyCb);
131
+ this.init().then(onReadyCb);
127
132
  return (0, tslib_1.__assign)((0, tslib_1.__assign)({}, this), {
128
133
  // no-op destroy, to disconnect the wrapper only when the main client is destroyed
129
134
  destroy: function () { } });
@@ -46,13 +46,8 @@ function fromObjectUpdaterFactory(splitsParser, storage, readiness, settings) {
46
46
  readiness.splits.emit(constants_2.SDK_SPLITS_ARRIVED);
47
47
  if (startingUp) {
48
48
  startingUp = false;
49
- Promise.resolve(splitsCache.checkCache()).then(function (cacheReady) {
50
- // Emits SDK_READY_FROM_CACHE
51
- if (cacheReady)
52
- readiness.splits.emit(constants_2.SDK_SPLITS_CACHE_LOADED);
53
- // Emits SDK_READY
54
- readiness.segments.emit(constants_2.SDK_SEGMENTS_ARRIVED);
55
- });
49
+ // Emits SDK_READY
50
+ readiness.segments.emit(constants_2.SDK_SEGMENTS_ARRIVED);
56
51
  }
57
52
  return true;
58
53
  });
@@ -126,7 +126,7 @@ function splitChangesUpdaterFactory(log, splitChangesFetcher, splits, segments,
126
126
  function _splitChangesUpdater(since, retry) {
127
127
  if (retry === void 0) { retry = 0; }
128
128
  log.debug(constants_2.SYNC_SPLITS_FETCH, [since]);
129
- var fetcherPromise = Promise.resolve(splitUpdateNotification ?
129
+ return Promise.resolve(splitUpdateNotification ?
130
130
  { splits: [splitUpdateNotification.payload], till: splitUpdateNotification.changeNumber } :
131
131
  splitChangesFetcher(since, noCache, till, _promiseDecorator))
132
132
  .then(function (splitChanges) {
@@ -170,15 +170,6 @@ function splitChangesUpdaterFactory(log, splitChangesFetcher, splits, segments,
170
170
  }
171
171
  return false;
172
172
  });
173
- // After triggering the requests, if we have cached splits information let's notify that to emit SDK_READY_FROM_CACHE.
174
- // Wrapping in a promise since checkCache can be async.
175
- if (splitsEventEmitter && startingUp) {
176
- Promise.resolve(splits.checkCache()).then(function (isCacheReady) {
177
- if (isCacheReady)
178
- splitsEventEmitter.emit(constants_1.SDK_SPLITS_CACHE_LOADED);
179
- });
180
- }
181
- return fetcherPromise;
182
173
  }
183
174
  var sincePromise = Promise.resolve(splits.getChangeNumber()); // `getChangeNumber` never rejects or throws error
184
175
  return sincePromise.then(_splitChangesUpdater);
@@ -12,7 +12,7 @@ var mode_1 = require("../utils/settingsValidation/mode");
12
12
  * @param eventsCache cache to save events
13
13
  * @param integrationsManager optional event handler used for integrations
14
14
  */
15
- function eventTrackerFactory(settings, eventsCache, integrationsManager, telemetryCache) {
15
+ function eventTrackerFactory(settings, eventsCache, whenInit, integrationsManager, telemetryCache) {
16
16
  var log = settings.log, mode = settings.mode;
17
17
  var isAsync = (0, mode_1.isConsumerMode)(mode);
18
18
  function queueEventsCallback(eventData, tracked) {
@@ -23,14 +23,16 @@ function eventTrackerFactory(settings, eventsCache, integrationsManager, telemet
23
23
  log.info(constants_1.EVENTS_TRACKER_SUCCESS, [msg]);
24
24
  if (integrationsManager) {
25
25
  // Wrap in a timeout because we don't want it to be blocking.
26
- setTimeout(function () {
27
- // copy of event, to avoid unexpected behaviour if modified by integrations
28
- var eventDataCopy = (0, objectAssign_1.objectAssign)({}, eventData);
29
- if (properties)
30
- eventDataCopy.properties = (0, objectAssign_1.objectAssign)({}, properties);
31
- // integrationsManager does not throw errors (they are internally handled by each integration module)
32
- integrationsManager.handleEvent(eventDataCopy);
33
- }, 0);
26
+ whenInit(function () {
27
+ setTimeout(function () {
28
+ // copy of event, to avoid unexpected behaviour if modified by integrations
29
+ var eventDataCopy = (0, objectAssign_1.objectAssign)({}, eventData);
30
+ if (properties)
31
+ eventDataCopy.properties = (0, objectAssign_1.objectAssign)({}, properties);
32
+ // integrationsManager does not throw errors (they are internally handled by each integration module)
33
+ integrationsManager.handleEvent(eventDataCopy);
34
+ });
35
+ });
34
36
  }
35
37
  }
36
38
  else {