@splitsoftware/splitio-commons 1.0.0 → 1.0.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 (116) hide show
  1. package/README.md +1 -1
  2. package/cjs/listeners/browser.js +3 -1
  3. package/cjs/logger/constants.js +4 -4
  4. package/cjs/logger/messages/error.js +2 -1
  5. package/cjs/logger/messages/warn.js +0 -1
  6. package/cjs/sdkClient/sdkClient.js +4 -4
  7. package/cjs/sdkClient/sdkClientMethodCS.js +16 -5
  8. package/cjs/sdkClient/sdkClientMethodCSWithTT.js +17 -6
  9. package/cjs/sdkFactory/index.js +6 -3
  10. package/cjs/storages/inMemory/InMemoryStorage.js +0 -3
  11. package/cjs/storages/inRedis/index.js +1 -2
  12. package/cjs/storages/pluggable/SplitsCachePluggable.js +1 -1
  13. package/cjs/storages/pluggable/constants.js +1 -1
  14. package/cjs/storages/pluggable/inMemoryWrapper.js +19 -5
  15. package/cjs/storages/pluggable/index.js +38 -15
  16. package/cjs/storages/pluggable/wrapperAdapter.js +3 -3
  17. package/cjs/sync/polling/updaters/mySegmentsUpdater.js +1 -1
  18. package/cjs/sync/streaming/SSEClient/index.js +0 -1
  19. package/cjs/sync/streaming/UpdateWorkers/SplitsUpdateWorker.js +0 -1
  20. package/cjs/sync/submitters/submitterManager.js +19 -0
  21. package/cjs/sync/syncManagerOnline.js +20 -26
  22. package/cjs/trackers/impressionObserver/utils.js +1 -1
  23. package/cjs/utils/MinEventEmitter.js +5 -5
  24. package/cjs/utils/constants/index.js +3 -2
  25. package/cjs/utils/env/isNode.js +4 -1
  26. package/cjs/utils/settingsValidation/mode.js +1 -1
  27. package/cjs/utils/settingsValidation/storage/storageCS.js +17 -3
  28. package/esm/listeners/browser.js +3 -1
  29. package/esm/logger/constants.js +3 -3
  30. package/esm/logger/messages/error.js +2 -1
  31. package/esm/logger/messages/warn.js +0 -1
  32. package/esm/sdkClient/sdkClient.js +5 -5
  33. package/esm/sdkClient/sdkClientMethodCS.js +16 -5
  34. package/esm/sdkClient/sdkClientMethodCSWithTT.js +17 -6
  35. package/esm/sdkFactory/index.js +6 -3
  36. package/esm/storages/inMemory/InMemoryStorage.js +0 -3
  37. package/esm/storages/inRedis/index.js +1 -2
  38. package/esm/storages/pluggable/SplitsCachePluggable.js +1 -1
  39. package/esm/storages/pluggable/constants.js +1 -1
  40. package/esm/storages/pluggable/inMemoryWrapper.js +19 -5
  41. package/esm/storages/pluggable/index.js +40 -16
  42. package/esm/storages/pluggable/wrapperAdapter.js +3 -3
  43. package/esm/sync/polling/updaters/mySegmentsUpdater.js +1 -1
  44. package/esm/sync/streaming/SSEClient/index.js +0 -1
  45. package/esm/sync/streaming/UpdateWorkers/SplitsUpdateWorker.js +0 -1
  46. package/esm/sync/submitters/submitterManager.js +15 -0
  47. package/esm/sync/syncManagerOnline.js +20 -26
  48. package/esm/trackers/impressionObserver/utils.js +2 -2
  49. package/esm/utils/MinEventEmitter.js +5 -5
  50. package/esm/utils/constants/index.js +2 -1
  51. package/esm/utils/env/isNode.js +4 -1
  52. package/esm/utils/settingsValidation/mode.js +2 -2
  53. package/esm/utils/settingsValidation/storage/storageCS.js +19 -5
  54. package/package.json +3 -4
  55. package/src/listeners/browser.ts +3 -1
  56. package/src/logger/constants.ts +3 -3
  57. package/src/logger/messages/error.ts +2 -1
  58. package/src/logger/messages/warn.ts +0 -1
  59. package/src/sdkClient/sdkClient.ts +6 -6
  60. package/src/sdkClient/sdkClientMethodCS.ts +14 -4
  61. package/src/sdkClient/sdkClientMethodCSWithTT.ts +15 -5
  62. package/src/sdkFactory/index.ts +7 -3
  63. package/src/sdkFactory/types.ts +2 -2
  64. package/src/services/splitApi.ts +4 -1
  65. package/src/services/types.ts +16 -2
  66. package/src/storages/inLocalStorage/index.ts +2 -2
  67. package/src/storages/inMemory/InMemoryStorage.ts +0 -3
  68. package/src/storages/inMemory/InMemoryStorageCS.ts +2 -2
  69. package/src/storages/inRedis/index.ts +1 -1
  70. package/src/storages/pluggable/EventsCachePluggable.ts +3 -3
  71. package/src/storages/pluggable/ImpressionsCachePluggable.ts +3 -3
  72. package/src/storages/pluggable/SegmentsCachePluggable.ts +3 -3
  73. package/src/storages/pluggable/SplitsCachePluggable.ts +4 -4
  74. package/src/storages/pluggable/constants.ts +1 -1
  75. package/src/storages/pluggable/inMemoryWrapper.ts +20 -6
  76. package/src/storages/pluggable/index.ts +46 -16
  77. package/src/storages/pluggable/wrapperAdapter.ts +5 -5
  78. package/src/storages/types.ts +24 -24
  79. package/src/sync/polling/updaters/mySegmentsUpdater.ts +1 -1
  80. package/src/sync/streaming/SSEClient/index.ts +5 -5
  81. package/src/sync/streaming/UpdateWorkers/SplitsUpdateWorker.ts +0 -1
  82. package/src/sync/submitters/submitterManager.ts +22 -0
  83. package/src/sync/syncManagerOnline.ts +26 -32
  84. package/src/sync/types.ts +1 -1
  85. package/src/trackers/impressionObserver/ImpressionObserver.ts +1 -1
  86. package/src/trackers/impressionObserver/utils.ts +2 -2
  87. package/src/types.ts +14 -13
  88. package/src/utils/MinEventEmitter.ts +10 -10
  89. package/src/utils/constants/index.ts +6 -4
  90. package/src/utils/env/isNode.ts +4 -1
  91. package/src/utils/settingsValidation/mode.ts +2 -2
  92. package/src/utils/settingsValidation/storage/storageCS.ts +21 -8
  93. package/types/logger/constants.d.ts +3 -3
  94. package/types/sdkFactory/types.d.ts +2 -2
  95. package/types/services/splitApi.d.ts +1 -1
  96. package/types/services/types.d.ts +13 -0
  97. package/types/storages/inMemory/InMemoryStorageCS.d.ts +2 -2
  98. package/types/storages/pluggable/EventsCachePluggable.d.ts +2 -2
  99. package/types/storages/pluggable/ImpressionsCachePluggable.d.ts +2 -2
  100. package/types/storages/pluggable/SegmentsCachePluggable.d.ts +5 -5
  101. package/types/storages/pluggable/SplitsCachePluggable.d.ts +3 -3
  102. package/types/storages/pluggable/constants.d.ts +1 -1
  103. package/types/storages/pluggable/inMemoryWrapper.d.ts +6 -3
  104. package/types/storages/pluggable/index.d.ts +2 -2
  105. package/types/storages/pluggable/wrapperAdapter.d.ts +4 -4
  106. package/types/storages/types.d.ts +21 -22
  107. package/types/sync/streaming/SSEClient/index.d.ts +4 -3
  108. package/types/sync/submitters/submitterManager.d.ts +4 -0
  109. package/types/sync/syncManagerOnline.d.ts +1 -1
  110. package/types/sync/types.d.ts +1 -1
  111. package/types/trackers/impressionObserver/ImpressionObserver.d.ts +1 -1
  112. package/types/types.d.ts +14 -14
  113. package/types/utils/MinEventEmitter.d.ts +6 -6
  114. package/types/utils/constants/index.d.ts +6 -4
  115. package/types/utils/env/isNode.d.ts +4 -0
  116. package/types/utils/settingsValidation/storage/storageCS.d.ts +6 -4
@@ -1,6 +1,6 @@
1
1
  import { InMemoryStorageCSFactory } from '../../../storages/inMemory/InMemoryStorageCS';
2
- import { WARN_STORAGE_INVALID } from '../../../logger/constants';
3
- import { LOCALHOST_MODE, STORAGE_LOCALSTORAGE, STORAGE_MEMORY } from '../../../utils/constants';
2
+ import { ERROR_STORAGE_INVALID } from '../../../logger/constants';
3
+ import { LOCALHOST_MODE, STANDALONE_MODE, STORAGE_PLUGGABLE, STORAGE_LOCALSTORAGE, STORAGE_MEMORY } from '../../../utils/constants';
4
4
  export function __InLocalStorageMockFactory(params) {
5
5
  var result = InMemoryStorageCSFactory(params);
6
6
  result.splits.checkCache = function () { return true; }; // to emit SDK_READY_FROM_CACHE
@@ -12,19 +12,33 @@ __InLocalStorageMockFactory.type = STORAGE_MEMORY;
12
12
  *
13
13
  * @param {any} settings config object provided by the user to initialize the sdk
14
14
  *
15
- * @returns {Object} valid storage factory. It might be the default `InMemoryStorageCSFactory` if the provided storage is invalid.
15
+ * @returns {Object} valid storage factory. Default to `InMemoryStorageCSFactory` if the provided storage is invalid or not compatible with the sdk mode if mode is standalone or localhost
16
+ *
17
+ * @throws error if mode is consumer and the provided storage is not compatible
16
18
  */
17
19
  export function validateStorageCS(settings) {
18
20
  var _a = settings.storage, storage = _a === void 0 ? InMemoryStorageCSFactory : _a, log = settings.log, mode = settings.mode;
19
21
  // If an invalid storage is provided, fallback into MEMORY
20
- if (typeof storage !== 'function' || storage.type !== STORAGE_MEMORY && storage.type !== STORAGE_LOCALSTORAGE) {
22
+ if (typeof storage !== 'function' || [STORAGE_MEMORY, STORAGE_LOCALSTORAGE, STORAGE_PLUGGABLE].indexOf(storage.type) === -1) {
21
23
  storage = InMemoryStorageCSFactory;
22
- log.warn(WARN_STORAGE_INVALID);
24
+ log.error(ERROR_STORAGE_INVALID);
23
25
  }
24
26
  // In localhost mode with InLocalStorage, fallback to a mock InLocalStorage to emit SDK_READY_FROM_CACHE
25
27
  if (mode === LOCALHOST_MODE && storage.type === STORAGE_LOCALSTORAGE) {
26
28
  return __InLocalStorageMockFactory;
27
29
  }
30
+ if ([LOCALHOST_MODE, STANDALONE_MODE].indexOf(mode) === -1) {
31
+ // Consumer modes require an async storage
32
+ if (storage.type !== STORAGE_PLUGGABLE)
33
+ throw new Error('A PluggableStorage instance is required on consumer mode');
34
+ }
35
+ else {
36
+ // Standalone and localhost modes require a sync storage
37
+ if (storage.type === STORAGE_PLUGGABLE) {
38
+ storage = InMemoryStorageCSFactory;
39
+ log.error(ERROR_STORAGE_INVALID, [' It requires consumer mode.']);
40
+ }
41
+ }
28
42
  // return default InMemory storage if provided one is not valid
29
43
  return storage;
30
44
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@splitsoftware/splitio-commons",
3
- "version": "1.0.0",
3
+ "version": "1.0.1-rc.3",
4
4
  "description": "Split Javascript SDK common components",
5
5
  "main": "cjs/index.js",
6
6
  "module": "esm/index.js",
@@ -49,10 +49,9 @@
49
49
  },
50
50
  "devDependencies": {
51
51
  "@types/google.analytics": "0.0.40",
52
- "@types/ioredis": "^4.14.1",
52
+ "@types/ioredis": "^4.28.0",
53
53
  "@types/jest": "^27.0.0",
54
54
  "@types/lodash": "^4.14.162",
55
- "@types/node": "^14.14.7",
56
55
  "@types/object-assign": "^4.0.30",
57
56
  "@typescript-eslint/eslint-plugin": "^4.2.0",
58
57
  "@typescript-eslint/parser": "^4.2.0",
@@ -61,7 +60,7 @@
61
60
  "eslint": "^7.32.0",
62
61
  "eslint-plugin-compat": "3.7.0",
63
62
  "fetch-mock": "^9.10.7",
64
- "ioredis": "^4.26.0",
63
+ "ioredis": "^4.28.0",
65
64
  "jest": "^27.2.3",
66
65
  "jest-localstorage-mock": "^2.4.3",
67
66
  "js-yaml": "^3.14.0",
@@ -63,6 +63,8 @@ export default class BrowserSignalListener implements ISignalListener {
63
63
  * using beacon API if possible, or falling back to regular post transport.
64
64
  */
65
65
  flushData() {
66
+ if (!this.syncManager) return; // In consumer mode there is not sync manager and data to flush
67
+
66
68
  const eventsUrl = this.settings.urls.events;
67
69
  const extraMetadata = {
68
70
  // sim stands for Sync/Split Impressions Mode
@@ -74,7 +76,7 @@ export default class BrowserSignalListener implements ISignalListener {
74
76
  if (this.storage.impressionCounts) this._flushData(eventsUrl + '/testImpressions/count/beacon', this.storage.impressionCounts, this.serviceApi.postTestImpressionsCount, fromImpressionCountsCollector);
75
77
 
76
78
  // Close streaming connection
77
- if (this.syncManager && this.syncManager.pushManager) this.syncManager.pushManager.stop();
79
+ if (this.syncManager.pushManager) this.syncManager.pushManager.stop();
78
80
  }
79
81
 
80
82
  private _flushData<TState>(url: string, cache: IRecorderCacheProducerSync<TState>, postService: (body: string) => Promise<IResponse>, fromCacheToPayload?: (cacheData: TState) => any, extraMetadata?: {}) {
@@ -91,9 +91,8 @@ export const WARN_INTEGRATION_INVALID = 218;
91
91
  export const WARN_SPLITS_FILTER_IGNORED = 219;
92
92
  export const WARN_SPLITS_FILTER_INVALID = 220;
93
93
  export const WARN_SPLITS_FILTER_EMPTY = 221;
94
- export const WARN_STORAGE_INVALID = 222;
95
- export const WARN_API_KEY = 223;
96
- export const STREAMING_PARSING_MY_SEGMENTS_UPDATE_V2 = 224;
94
+ export const WARN_API_KEY = 222;
95
+ export const STREAMING_PARSING_MY_SEGMENTS_UPDATE_V2 = 223;
97
96
 
98
97
  export const ERROR_ENGINE_COMBINER_IFELSEIF = 300;
99
98
  export const ERROR_LOGLEVEL_INVALID = 301;
@@ -119,6 +118,7 @@ export const ERROR_EMPTY_ARRAY = 320;
119
118
  export const ERROR_INVALID_IMPRESSIONS_MODE = 321;
120
119
  export const ERROR_HTTP = 322;
121
120
  export const ERROR_LOCALHOST_MODULE_REQUIRED = 323;
121
+ export const ERROR_STORAGE_INVALID = 324;
122
122
 
123
123
  // Log prefixes (a.k.a. tags or categories)
124
124
  export const LOG_PREFIX_SETTINGS = 'settings';
@@ -30,5 +30,6 @@ export const codesError: [number, string][] = [
30
30
  [c.ERROR_EMPTY_ARRAY, '%s: %s must be a non-empty array.'],
31
31
  // initialization / settings validation
32
32
  [c.ERROR_INVALID_IMPRESSIONS_MODE, c.LOG_PREFIX_SETTINGS + ': you passed an invalid "impressionsMode". It should be one of the following values: %s. Defaulting to "%s" mode.'],
33
- [c.ERROR_LOCALHOST_MODULE_REQUIRED, c.LOG_PREFIX_SETTINGS + ': an invalid value was received for "sync.localhostMode" config. A valid entity should be provided for localhost mode.']
33
+ [c.ERROR_LOCALHOST_MODULE_REQUIRED, c.LOG_PREFIX_SETTINGS + ': an invalid value was received for "sync.localhostMode" config. A valid entity should be provided for localhost mode.'],
34
+ [c.ERROR_STORAGE_INVALID, c.LOG_PREFIX_SETTINGS+': The provided storage is invalid.%s Fallbacking into default MEMORY storage'],
34
35
  ];
@@ -29,7 +29,6 @@ export const codesWarn: [number, string][] = codesError.concat([
29
29
  [c.WARN_SPLITS_FILTER_IGNORED, c.LOG_PREFIX_SETTINGS+': split filters have been configured but will have no effect if mode is not "%s", since synchronization is being deferred to an external tool.'],
30
30
  [c.WARN_SPLITS_FILTER_INVALID, c.LOG_PREFIX_SETTINGS+': split filter at position %s is invalid. It must be an object with a valid filter type ("byName" or "byPrefix") and a list of "values".'],
31
31
  [c.WARN_SPLITS_FILTER_EMPTY, c.LOG_PREFIX_SETTINGS+': splitFilters configuration must be a non-empty array of filter objects.'],
32
- [c.WARN_STORAGE_INVALID, c.LOG_PREFIX_SETTINGS+': The provided storage is invalid. Fallbacking into default MEMORY storage'],
33
32
  [c.WARN_API_KEY, c.LOG_PREFIX_SETTINGS+': You already have %s. We recommend keeping only one instance of the factory at all times (Singleton pattern) and reusing it throughout your application'],
34
33
 
35
34
  [c.STREAMING_PARSING_MY_SEGMENTS_UPDATE_V2, c.LOG_PREFIX_SYNC_STREAMING + 'Fetching MySegments due to an error processing %s notification: %s'],
@@ -1,6 +1,6 @@
1
1
  import objectAssign from 'object-assign';
2
2
  import { IStatusInterface, SplitIO } from '../types';
3
- import { CONSUMER_MODE } from '../utils/constants';
3
+ import { CONSUMER_MODE, CONSUMER_PARTIAL_MODE } from '../utils/constants';
4
4
  import { releaseApiKey } from '../utils/inputValidation/apiKey';
5
5
  import clientFactory from './client';
6
6
  import clientInputValidationDecorator from './clientInputValidation';
@@ -21,8 +21,8 @@ export function sdkClientFactory(params: ISdkClientFactoryParams): SplitIO.IClie
21
21
  settings.log,
22
22
  clientFactory(params),
23
23
  sdkReadinessManager.readinessManager,
24
- // @TODO isStorageSync could be extracted from the storage itself (e.g. `storage.isSync`) to simplify interfaces
25
- settings.mode === CONSUMER_MODE ? false : true,
24
+ // storage is async if and only if mode is consumer or partial consumer
25
+ [CONSUMER_MODE, CONSUMER_PARTIAL_MODE].indexOf(settings.mode) === -1 ? true : false,
26
26
  ),
27
27
 
28
28
  // Sdk destroy
@@ -37,11 +37,11 @@ export function sdkClientFactory(params: ISdkClientFactoryParams): SplitIO.IClie
37
37
  sdkReadinessManager.readinessManager.destroy();
38
38
  signalListener && signalListener.stop();
39
39
 
40
- // Cleanup storage
41
- storage.destroy();
42
-
43
40
  // Release the API Key if it is the main client
44
41
  if (!sharedClient) releaseApiKey(settings.core.authorizationKey);
42
+
43
+ // Cleanup storage
44
+ return storage.destroy();
45
45
  });
46
46
  }
47
47
  }
@@ -4,10 +4,10 @@ import { SplitIO } from '../types';
4
4
  import { validateKey } from '../utils/inputValidation/key';
5
5
  import { getMatching, keyParser } from '../utils/key';
6
6
  import { sdkClientFactory } from './sdkClient';
7
- import { IStorageSyncCS } from '../storages/types';
8
7
  import { ISyncManagerCS } from '../sync/types';
9
8
  import objectAssign from 'object-assign';
10
9
  import { RETRIEVE_CLIENT_DEFAULT, NEW_SHARED_CLIENT, RETRIEVE_CLIENT_EXISTING } from '../logger/constants';
10
+ import { SDK_SEGMENTS_ARRIVED } from '../readiness/constants';
11
11
 
12
12
  function buildInstanceId(key: SplitIO.SplitKey) {
13
13
  // @ts-ignore
@@ -58,15 +58,25 @@ export function sdkClientMethodCSFactory(params: ISdkClientFactoryParams): (key?
58
58
  const matchingKey = getMatching(validKey);
59
59
 
60
60
  const sharedSdkReadiness = sdkReadinessManager.shared(readyTimeout);
61
- const sharedStorage = (storage as IStorageSyncCS).shared(matchingKey);
62
- const sharedSyncManager = syncManager && (syncManager as ISyncManagerCS).shared(matchingKey, sharedSdkReadiness.readinessManager, sharedStorage);
61
+ const sharedStorage = storage.shared && storage.shared(matchingKey, (err) => {
62
+ if (err) return;
63
+ // Emit SDK_READY in consumer mode for shared clients
64
+ sharedSdkReadiness.readinessManager.segments.emit(SDK_SEGMENTS_ARRIVED);
65
+ });
66
+
67
+ // 3 possibilities:
68
+ // - Standalone mode: both syncManager and sharedSyncManager are defined
69
+ // - Consumer mode: both syncManager and sharedSyncManager are undefined
70
+ // - Consumer partial mode: syncManager is defined (only for submitters) but sharedSyncManager is undefined
71
+ // @ts-ignore
72
+ const sharedSyncManager = syncManager && sharedStorage && (syncManager as ISyncManagerCS).shared(matchingKey, sharedSdkReadiness.readinessManager, sharedStorage);
63
73
 
64
74
  // As shared clients reuse all the storage information, we don't need to check here if we
65
75
  // will use offline or online mode. We should stick with the original decision.
66
76
  clientInstances[instanceId] = clientCSDecorator(
67
77
  sdkClientFactory(objectAssign({}, params, {
68
78
  sdkReadinessManager: sharedSdkReadiness,
69
- storage: sharedStorage,
79
+ storage: sharedStorage || storage,
70
80
  syncManager: sharedSyncManager,
71
81
  signalListener: undefined, // only the main client "destroy" method stops the signal listener
72
82
  sharedClient: true
@@ -5,10 +5,10 @@ import { validateKey } from '../utils/inputValidation/key';
5
5
  import { validateTrafficType } from '../utils/inputValidation/trafficType';
6
6
  import { getMatching, keyParser } from '../utils/key';
7
7
  import { sdkClientFactory } from './sdkClient';
8
- import { IStorageSyncCS } from '../storages/types';
9
8
  import { ISyncManagerCS } from '../sync/types';
10
9
  import objectAssign from 'object-assign';
11
10
  import { RETRIEVE_CLIENT_DEFAULT, NEW_SHARED_CLIENT, RETRIEVE_CLIENT_EXISTING } from '../logger/constants';
11
+ import { SDK_SEGMENTS_ARRIVED } from '../readiness/constants';
12
12
 
13
13
  function buildInstanceId(key: SplitIO.SplitKey, trafficType?: string) {
14
14
  // @ts-ignore
@@ -72,15 +72,25 @@ export function sdkClientMethodCSFactory(params: ISdkClientFactoryParams): (key?
72
72
  const matchingKey = getMatching(validKey);
73
73
 
74
74
  const sharedSdkReadiness = sdkReadinessManager.shared(readyTimeout);
75
- const sharedStorage = (storage as IStorageSyncCS).shared(matchingKey);
76
- const sharedSyncManager = (syncManager as ISyncManagerCS).shared(matchingKey, sharedSdkReadiness.readinessManager, sharedStorage);
75
+ const sharedStorage = storage.shared && storage.shared(matchingKey, (err) => {
76
+ if (err) return;
77
+ // Emit SDK_READY in consumer mode for shared clients
78
+ sharedSdkReadiness.readinessManager.segments.emit(SDK_SEGMENTS_ARRIVED);
79
+ });
80
+
81
+ // 3 possibilities:
82
+ // - Standalone mode: both syncManager and sharedSyncManager are defined
83
+ // - Consumer mode: both syncManager and sharedSyncManager are undefined
84
+ // - Consumer partial mode: syncManager is defined (only for submitters) but sharedSyncManager is undefined
85
+ // @ts-ignore
86
+ const sharedSyncManager = syncManager && sharedStorage && (syncManager as ISyncManagerCS).shared(matchingKey, sharedSdkReadiness.readinessManager, sharedStorage);
77
87
 
78
88
  // As shared clients reuse all the storage information, we don't need to check here if we
79
89
  // will use offline or online mode. We should stick with the original decision.
80
90
  clientInstances[instanceId] = clientCSDecorator(
81
91
  sdkClientFactory(objectAssign({}, params, {
82
92
  sdkReadinessManager: sharedSdkReadiness,
83
- storage: sharedStorage,
93
+ storage: sharedStorage || storage,
84
94
  syncManager: sharedSyncManager,
85
95
  signalListener: undefined, // only the main client "destroy" method stops the signal listener
86
96
  sharedClient: true
@@ -89,7 +99,7 @@ export function sdkClientMethodCSFactory(params: ISdkClientFactoryParams): (key?
89
99
  validTrafficType
90
100
  );
91
101
 
92
- sharedSyncManager.start();
102
+ sharedSyncManager && sharedSyncManager.start();
93
103
 
94
104
  log.info(NEW_SHARED_CLIENT);
95
105
  } else {
@@ -40,12 +40,16 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ICsSDK | SplitIO.
40
40
  matchingKey: getMatching(settings.core.key),
41
41
  splitFiltersValidation: settings.sync.__splitFiltersValidation,
42
42
 
43
- // Callback used in consumer mode (`syncManagerFactory` is undefined) to emit SDK_READY
44
- onReadyCb: !syncManagerFactory ? (error) => {
43
+ // ATM, only used by PluggableStorage
44
+ mode: settings.mode,
45
+
46
+ // Callback used to emit SDK_READY in consumer mode, where `syncManagerFactory` is undefined
47
+ // or only instantiates submitters, and therefore it is not able to emit readiness events.
48
+ onReadyCb: (error) => {
45
49
  if (error) return; // don't emit SDK_READY if storage failed to connect.
46
50
  readinessManager.splits.emit(SDK_SPLITS_ARRIVED);
47
51
  readinessManager.segments.emit(SDK_SEGMENTS_ARRIVED);
48
- } : undefined,
52
+ },
49
53
  metadata: metadataBuilder(settings),
50
54
  log
51
55
  };
@@ -3,7 +3,7 @@ import { ISignalListener } from '../listeners/types';
3
3
  import { ILogger } from '../logger/types';
4
4
  import { ISdkReadinessManager } from '../readiness/types';
5
5
  import { ISdkClientFactoryParams } from '../sdkClient/types';
6
- import { IFetch, ISplitApi } from '../services/types';
6
+ import { IFetch, ISplitApi, IEventSourceConstructor } from '../services/types';
7
7
  import { IStorageAsync, IStorageSync, ISplitsCacheSync, ISplitsCacheAsync, IStorageFactoryParams } from '../storages/types';
8
8
  import { ISyncManager, ISyncManagerFactoryParams } from '../sync/types';
9
9
  import { IImpressionObserver } from '../trackers/impressionObserver/types';
@@ -16,7 +16,7 @@ import { SplitIO, ISettings, IEventEmitter } from '../types';
16
16
  export interface IPlatform {
17
17
  getOptions?: () => object
18
18
  getFetch?: () => (IFetch | undefined)
19
- getEventSource?: () => (typeof EventSource | undefined)
19
+ getEventSource?: () => (IEventSourceConstructor | undefined)
20
20
  EventEmitter: new () => IEventEmitter
21
21
  }
22
22
 
@@ -16,7 +16,10 @@ function userKeyToQueryParam(userKey: string) {
16
16
  * @param settings validated settings object
17
17
  * @param platform object containing environment-specific `getFetch` and `getOptions` dependencies
18
18
  */
19
- export function splitApiFactory(settings: ISettings, platform: Pick<IPlatform, 'getFetch' | 'getOptions'>): ISplitApi {
19
+ export function splitApiFactory(
20
+ settings: Pick<ISettings, 'urls' | 'sync' | 'log' | 'version' | 'runtime' | 'core'>,
21
+ platform: Pick<IPlatform, 'getFetch' | 'getOptions'>
22
+ ): ISplitApi {
20
23
 
21
24
  const urls = settings.urls;
22
25
  const filterQueryString = settings.sync.__splitFiltersValidation && settings.sync.__splitFiltersValidation.queryString;
@@ -50,8 +50,8 @@ export type IPostMetricsCounters = (body: string) => Promise<IResponse>
50
50
  export type IPostMetricsTimes = (body: string) => Promise<IResponse>
51
51
 
52
52
  export interface ISplitApi {
53
- getSdkAPIHealthCheck: IHealthCheckAPI
54
- getEventsAPIHealthCheck: IHealthCheckAPI
53
+ getSdkAPIHealthCheck: IHealthCheckAPI
54
+ getEventsAPIHealthCheck: IHealthCheckAPI
55
55
  fetchAuth: IFetchAuth
56
56
  fetchSplitChanges: IFetchSplitChanges
57
57
  fetchSegmentChanges: IFetchSegmentChanges
@@ -62,3 +62,17 @@ export interface ISplitApi {
62
62
  postMetricsCounters: IPostMetricsCounters
63
63
  postMetricsTimes: IPostMetricsTimes
64
64
  }
65
+
66
+ // Minimal version of EventSource API used by the SDK
67
+ interface EventSourceEventMap {
68
+ 'error': Event
69
+ 'message': MessageEvent
70
+ 'open': Event
71
+ }
72
+
73
+ interface IEventSource {
74
+ addEventListener<K extends keyof EventSourceEventMap>(type: K, listener: (this: IEventSource, ev: EventSourceEventMap[K]) => any): void
75
+ close(): void
76
+ }
77
+
78
+ export type IEventSourceConstructor = new (url: string, eventSourceInitDict?: { headers?: object }) => IEventSource
@@ -1,7 +1,7 @@
1
1
  import ImpressionsCacheInMemory from '../inMemory/ImpressionsCacheInMemory';
2
2
  import ImpressionCountsCacheInMemory from '../inMemory/ImpressionCountsCacheInMemory';
3
3
  import EventsCacheInMemory from '../inMemory/EventsCacheInMemory';
4
- import { IStorageFactoryParams, IStorageSyncCS, IStorageSyncFactory } from '../types';
4
+ import { IStorageFactoryParams, IStorageSync, IStorageSyncFactory } from '../types';
5
5
  import { validatePrefix } from '../KeyBuilder';
6
6
  import KeyBuilderCS from '../KeyBuilderCS';
7
7
  import { isLocalStorageAvailable } from '../../utils/env/isLocalStorageAvailable';
@@ -25,7 +25,7 @@ export function InLocalStorage(options: InLocalStorageOptions = {}): IStorageSyn
25
25
 
26
26
  const prefix = validatePrefix(options.prefix);
27
27
 
28
- function InLocalStorageCSFactory(params: IStorageFactoryParams): IStorageSyncCS {
28
+ function InLocalStorageCSFactory(params: IStorageFactoryParams): IStorageSync {
29
29
 
30
30
  // Fallback to InMemoryStorage if LocalStorage API is not available
31
31
  if (!isLocalStorageAvailable()) {
@@ -13,9 +13,6 @@ import { STORAGE_MEMORY } from '../../utils/constants';
13
13
  */
14
14
  export function InMemoryStorageFactory(params: IStorageFactoryParams): IStorageSync {
15
15
 
16
- // InMemory storage is always ready
17
- if (params.onReadyCb) setTimeout(params.onReadyCb);
18
-
19
16
  return {
20
17
  splits: new SplitsCacheInMemory(),
21
18
  segments: new SegmentsCacheInMemory(),
@@ -2,7 +2,7 @@ import SplitsCacheInMemory from './SplitsCacheInMemory';
2
2
  import MySegmentsCacheInMemory from './MySegmentsCacheInMemory';
3
3
  import ImpressionsCacheInMemory from './ImpressionsCacheInMemory';
4
4
  import EventsCacheInMemory from './EventsCacheInMemory';
5
- import { IStorageSyncCS, IStorageFactoryParams } from '../types';
5
+ import { IStorageSync, IStorageFactoryParams } from '../types';
6
6
  import ImpressionCountsCacheInMemory from './ImpressionCountsCacheInMemory';
7
7
  import { STORAGE_MEMORY } from '../../utils/constants';
8
8
 
@@ -11,7 +11,7 @@ import { STORAGE_MEMORY } from '../../utils/constants';
11
11
  *
12
12
  * @param params parameters required by EventsCacheSync
13
13
  */
14
- export function InMemoryStorageCSFactory(params: IStorageFactoryParams): IStorageSyncCS {
14
+ export function InMemoryStorageCSFactory(params: IStorageFactoryParams): IStorageSync {
15
15
 
16
16
  return {
17
17
  splits: new SplitsCacheInMemory(),
@@ -30,7 +30,7 @@ export function InRedisStorage(options: InRedisStorageOptions = {}): IStorageAsy
30
30
 
31
31
  // subscription to Redis connect event in order to emit SDK_READY event on consumer mode
32
32
  redisClient.on('connect', () => {
33
- if (onReadyCb) onReadyCb();
33
+ onReadyCb();
34
34
  });
35
35
 
36
36
  return {
@@ -1,4 +1,4 @@
1
- import { ICustomStorageWrapper, IEventsCacheAsync } from '../types';
1
+ import { IPluggableStorageWrapper, IEventsCacheAsync } from '../types';
2
2
  import { IMetadata } from '../../dtos/types';
3
3
  import { SplitIO } from '../../types';
4
4
  import { ILogger } from '../../logger/types';
@@ -8,11 +8,11 @@ import { StoredEventWithMetadata } from '../../sync/submitters/types';
8
8
  export class EventsCachePluggable implements IEventsCacheAsync {
9
9
 
10
10
  private readonly log: ILogger;
11
- private readonly wrapper: ICustomStorageWrapper;
11
+ private readonly wrapper: IPluggableStorageWrapper;
12
12
  private readonly key: string;
13
13
  private readonly metadata: IMetadata;
14
14
 
15
- constructor(log: ILogger, key: string, wrapper: ICustomStorageWrapper, metadata: IMetadata) {
15
+ constructor(log: ILogger, key: string, wrapper: IPluggableStorageWrapper, metadata: IMetadata) {
16
16
  this.log = log;
17
17
  this.key = key;
18
18
  this.wrapper = wrapper;
@@ -1,4 +1,4 @@
1
- import { ICustomStorageWrapper, IImpressionsCacheAsync } from '../types';
1
+ import { IPluggableStorageWrapper, IImpressionsCacheAsync } from '../types';
2
2
  import { IMetadata } from '../../dtos/types';
3
3
  import { ImpressionDTO } from '../../types';
4
4
  import { ILogger } from '../../logger/types';
@@ -8,10 +8,10 @@ export class ImpressionsCachePluggable implements IImpressionsCacheAsync {
8
8
 
9
9
  private readonly log: ILogger;
10
10
  private readonly key: string;
11
- private readonly wrapper: ICustomStorageWrapper;
11
+ private readonly wrapper: IPluggableStorageWrapper;
12
12
  private readonly metadata: IMetadata;
13
13
 
14
- constructor(log: ILogger, key: string, wrapper: ICustomStorageWrapper, metadata: IMetadata) {
14
+ constructor(log: ILogger, key: string, wrapper: IPluggableStorageWrapper, metadata: IMetadata) {
15
15
  this.log = log;
16
16
  this.key = key;
17
17
  this.wrapper = wrapper;
@@ -2,7 +2,7 @@
2
2
  /* eslint-disable no-unused-vars */
3
3
  import { isNaNNumber } from '../../utils/lang';
4
4
  import KeyBuilderSS from '../KeyBuilderSS';
5
- import { ICustomStorageWrapper, ISegmentsCacheAsync } from '../types';
5
+ import { IPluggableStorageWrapper, ISegmentsCacheAsync } from '../types';
6
6
  import { ILogger } from '../../logger/types';
7
7
  import { LOG_PREFIX } from './constants';
8
8
  import { _Set } from '../../utils/lang/sets';
@@ -14,9 +14,9 @@ export class SegmentsCachePluggable implements ISegmentsCacheAsync {
14
14
 
15
15
  private readonly log: ILogger;
16
16
  private readonly keys: KeyBuilderSS;
17
- private readonly wrapper: ICustomStorageWrapper;
17
+ private readonly wrapper: IPluggableStorageWrapper;
18
18
 
19
- constructor(log: ILogger, keys: KeyBuilderSS, wrapper: ICustomStorageWrapper) {
19
+ constructor(log: ILogger, keys: KeyBuilderSS, wrapper: IPluggableStorageWrapper) {
20
20
  this.log = log;
21
21
  this.keys = keys;
22
22
  this.wrapper = wrapper;
@@ -1,6 +1,6 @@
1
1
  import { isFiniteNumber, isNaNNumber } from '../../utils/lang';
2
2
  import KeyBuilder from '../KeyBuilder';
3
- import { ICustomStorageWrapper } from '../types';
3
+ import { IPluggableStorageWrapper } from '../types';
4
4
  import { ILogger } from '../../logger/types';
5
5
  import { ISplit } from '../../dtos/types';
6
6
  import { LOG_PREFIX } from './constants';
@@ -13,15 +13,15 @@ export class SplitsCachePluggable extends AbstractSplitsCacheAsync {
13
13
 
14
14
  private readonly log: ILogger;
15
15
  private readonly keys: KeyBuilder;
16
- private readonly wrapper: ICustomStorageWrapper;
16
+ private readonly wrapper: IPluggableStorageWrapper;
17
17
 
18
18
  /**
19
- * Create a SplitsCache that uses a custom storage wrapper.
19
+ * Create a SplitsCache that uses a storage wrapper.
20
20
  * @param log Logger instance.
21
21
  * @param keys Key builder.
22
22
  * @param wrapper Adapted wrapper storage.
23
23
  */
24
- constructor(log: ILogger, keys: KeyBuilder, wrapper: ICustomStorageWrapper) {
24
+ constructor(log: ILogger, keys: KeyBuilder, wrapper: IPluggableStorageWrapper) {
25
25
  super();
26
26
  this.log = log;
27
27
  this.keys = keys;
@@ -1 +1 @@
1
- export const LOG_PREFIX = 'storage:pluggable:';
1
+ export const LOG_PREFIX = 'storage:pluggable: ';
@@ -1,15 +1,18 @@
1
- import { ICustomStorageWrapper } from '../types';
1
+ import { IPluggableStorageWrapper } from '../types';
2
2
  import { startsWith, toNumber } from '../../utils/lang';
3
3
  import { ISet, setToArray, _Set } from '../../utils/lang/sets';
4
4
 
5
5
  /**
6
- * Creates a ICustomStorageWrapper implementation that stores items in memory.
6
+ * Creates a IPluggableStorageWrapper implementation that stores items in memory.
7
7
  * The `_cache` property is the object were items are stored.
8
8
  * Intended for testing purposes.
9
+ *
10
+ * @param connDelay delay in millis for `connect` resolve. If not provided, `connect` resolves inmediatelly.
9
11
  */
10
- export function inMemoryWrapperFactory(): ICustomStorageWrapper & { _cache: Record<string, string | string[] | ISet<string>> } {
12
+ export function inMemoryWrapperFactory(connDelay?: number): IPluggableStorageWrapper & { _cache: Record<string, string | string[] | ISet<string>>, _setConnDelay(connDelay: number): void } {
11
13
 
12
14
  let _cache: Record<string, string | string[] | ISet<string>> = {};
15
+ let _connDelay = connDelay;
13
16
 
14
17
  return {
15
18
  /** Holds items (for key-value operations), list of items (for list operations) and set of items (for set operations) */
@@ -109,8 +112,19 @@ export function inMemoryWrapperFactory(): ICustomStorageWrapper & { _cache: Reco
109
112
  return Promise.reject('key is not a set');
110
113
  },
111
114
 
112
- // always connects and close
113
- connect() { return Promise.resolve(); },
114
- close() { return Promise.resolve(); },
115
+ // always connects and disconnects
116
+ connect() {
117
+ if (typeof _connDelay === 'number') {
118
+ return new Promise(res => setTimeout(res, _connDelay));
119
+ } else {
120
+ return Promise.resolve();
121
+ }
122
+ },
123
+ disconnect() { return Promise.resolve(); },
124
+
125
+ // for testing
126
+ _setConnDelay(connDelay: number) {
127
+ _connDelay = connDelay;
128
+ }
115
129
  };
116
130
  }