@splitsoftware/splitio-commons 1.1.0 → 1.2.1-rc.1

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 (110) hide show
  1. package/CHANGES.txt +3 -0
  2. package/cjs/evaluator/matchers/ew.js +3 -3
  3. package/cjs/logger/messages/info.js +3 -3
  4. package/cjs/sdkClient/client.js +2 -1
  5. package/cjs/sdkClient/clientAttributesDecoration.js +108 -0
  6. package/cjs/sdkClient/clientCS.js +10 -7
  7. package/cjs/sdkClient/sdkClientMethodCS.js +2 -2
  8. package/cjs/sdkClient/sdkClientMethodCSWithTT.js +2 -2
  9. package/cjs/services/splitHttpClient.js +1 -1
  10. package/cjs/storages/inMemory/AttributesCacheInMemory.js +70 -0
  11. package/cjs/sync/polling/fetchers/mySegmentsFetcher.js +2 -2
  12. package/cjs/sync/polling/pollingManagerCS.js +2 -1
  13. package/cjs/sync/polling/pollingManagerSS.js +2 -1
  14. package/cjs/sync/polling/syncTasks/mySegmentsSyncTask.js +1 -1
  15. package/cjs/sync/polling/updaters/mySegmentsUpdater.js +2 -2
  16. package/cjs/sync/streaming/AuthClient/index.js +1 -2
  17. package/cjs/sync/streaming/UpdateWorkers/SegmentsUpdateWorker.js +1 -1
  18. package/cjs/sync/streaming/pushManager.js +27 -23
  19. package/cjs/sync/submitters/submitterManager.js +2 -1
  20. package/cjs/sync/syncManagerOnline.js +12 -12
  21. package/cjs/utils/inputValidation/attribute.js +20 -0
  22. package/cjs/utils/inputValidation/attributes.js +13 -1
  23. package/cjs/utils/inputValidation/eventProperties.js +3 -1
  24. package/cjs/utils/lang/index.js +1 -13
  25. package/cjs/utils/murmur3/legacy.js +44 -0
  26. package/cjs/utils/promise/timeout.js +1 -1
  27. package/esm/evaluator/matchers/ew.js +4 -4
  28. package/esm/logger/messages/info.js +3 -3
  29. package/esm/sdkClient/client.js +2 -1
  30. package/esm/sdkClient/clientAttributesDecoration.js +104 -0
  31. package/esm/sdkClient/clientCS.js +10 -7
  32. package/esm/sdkClient/sdkClientMethodCS.js +2 -2
  33. package/esm/sdkClient/sdkClientMethodCSWithTT.js +2 -2
  34. package/esm/services/splitHttpClient.js +1 -1
  35. package/esm/storages/inMemory/AttributesCacheInMemory.js +67 -0
  36. package/esm/sync/polling/fetchers/mySegmentsFetcher.js +2 -2
  37. package/esm/sync/polling/pollingManagerCS.js +2 -1
  38. package/esm/sync/polling/pollingManagerSS.js +2 -1
  39. package/esm/sync/polling/syncTasks/mySegmentsSyncTask.js +1 -1
  40. package/esm/sync/polling/updaters/mySegmentsUpdater.js +2 -2
  41. package/esm/sync/streaming/AuthClient/index.js +1 -2
  42. package/esm/sync/streaming/UpdateWorkers/SegmentsUpdateWorker.js +1 -1
  43. package/esm/sync/streaming/pushManager.js +27 -23
  44. package/esm/sync/submitters/submitterManager.js +2 -1
  45. package/esm/sync/syncManagerOnline.js +12 -12
  46. package/esm/utils/inputValidation/attribute.js +16 -0
  47. package/esm/utils/inputValidation/attributes.js +11 -0
  48. package/esm/utils/inputValidation/eventProperties.js +4 -2
  49. package/esm/utils/lang/index.js +0 -11
  50. package/esm/utils/murmur3/legacy.js +39 -0
  51. package/esm/utils/promise/timeout.js +1 -1
  52. package/package.json +4 -4
  53. package/src/evaluator/matchers/ew.ts +4 -4
  54. package/src/logger/messages/info.ts +3 -3
  55. package/src/sdkClient/client.ts +2 -1
  56. package/src/sdkClient/clientAttributesDecoration.ts +122 -0
  57. package/src/sdkClient/clientCS.ts +14 -7
  58. package/src/sdkClient/sdkClientMethodCS.ts +2 -0
  59. package/src/sdkClient/sdkClientMethodCSWithTT.ts +2 -0
  60. package/src/services/splitHttpClient.ts +1 -1
  61. package/src/storages/inMemory/AttributesCacheInMemory.ts +73 -0
  62. package/src/sync/polling/fetchers/mySegmentsFetcher.ts +2 -1
  63. package/src/sync/polling/fetchers/types.ts +1 -0
  64. package/src/sync/polling/pollingManagerCS.ts +3 -6
  65. package/src/sync/polling/pollingManagerSS.ts +3 -8
  66. package/src/sync/polling/syncTasks/mySegmentsSyncTask.ts +2 -1
  67. package/src/sync/polling/types.ts +0 -12
  68. package/src/sync/polling/updaters/mySegmentsUpdater.ts +2 -1
  69. package/src/sync/streaming/AuthClient/index.ts +1 -2
  70. package/src/sync/streaming/UpdateWorkers/SegmentsUpdateWorker.ts +1 -1
  71. package/src/sync/streaming/pushManager.ts +31 -38
  72. package/src/sync/streaming/types.ts +5 -25
  73. package/src/sync/submitters/submitterManager.ts +4 -8
  74. package/src/sync/syncManagerOnline.ts +16 -22
  75. package/src/types.ts +43 -0
  76. package/src/utils/inputValidation/attribute.ts +21 -0
  77. package/src/utils/inputValidation/attributes.ts +14 -0
  78. package/src/utils/inputValidation/eventProperties.ts +4 -2
  79. package/src/utils/lang/index.ts +0 -14
  80. package/src/utils/murmur3/legacy.ts +48 -0
  81. package/src/utils/promise/timeout.ts +1 -1
  82. package/types/sdkClient/clientAttributesDecoration.d.ts +51 -0
  83. package/types/sdkClient/clientCS.d.ts +2 -1
  84. package/types/storages/inMemory/AttributesCacheInMemory.d.ts +43 -0
  85. package/types/storages/inMemory/TelemetryCacheInMemory.d.ts +51 -0
  86. package/types/storages/inMemory/index.d.ts +10 -0
  87. package/types/storages/inRedis/TelemetryCacheInRedis.d.ts +0 -0
  88. package/types/storages/parseSegments.d.ts +6 -0
  89. package/types/storages/pluggable/TelemetryCachePluggable.d.ts +2 -0
  90. package/types/sync/polling/fetchers/mySegmentsFetcher.d.ts +1 -1
  91. package/types/sync/polling/fetchers/types.d.ts +1 -1
  92. package/types/sync/polling/pollingManagerCS.d.ts +2 -5
  93. package/types/sync/polling/pollingManagerSS.d.ts +2 -5
  94. package/types/sync/polling/types.d.ts +0 -11
  95. package/types/sync/polling/updaters/mySegmentsUpdater.d.ts +1 -1
  96. package/types/sync/streaming/UpdateWorkers/SegmentsUpdateWorker.d.ts +1 -1
  97. package/types/sync/streaming/pushManager.d.ts +3 -7
  98. package/types/sync/streaming/pushManagerCS.d.ts +1 -12
  99. package/types/sync/streaming/pushManagerSS.d.ts +1 -11
  100. package/types/sync/streaming/types.d.ts +3 -23
  101. package/types/sync/submitters/submitterManager.d.ts +2 -4
  102. package/types/sync/submitters/telemetrySyncTask.d.ts +17 -0
  103. package/types/sync/syncManagerOnline.d.ts +3 -3
  104. package/types/trackers/telemetryRecorder.d.ts +0 -0
  105. package/types/types.d.ts +40 -0
  106. package/types/utils/EventEmitter.d.ts +4 -0
  107. package/types/utils/inputValidation/attribute.d.ts +2 -0
  108. package/types/utils/inputValidation/attributes.d.ts +1 -0
  109. package/types/utils/lang/index.d.ts +0 -4
  110. package/types/utils/murmur3/legacy.d.ts +2 -0
@@ -1,7 +1,5 @@
1
- import { IPushEventEmitter, IPushManagerCS } from './types';
1
+ import { IPushEventEmitter, IPushManager } from './types';
2
2
  import { ISSEClient } from './SSEClient/types';
3
- import { IStorageSync } from '../../storages/types';
4
- import { IReadinessManager } from '../../readiness/types';
5
3
  import { ISegmentsSyncTask, IPollingManager } from '../polling/types';
6
4
  import { objectAssign } from '../../utils/lang/objectAssign';
7
5
  import { Backoff } from '../../utils/Backoff';
@@ -12,17 +10,15 @@ import { SplitsUpdateWorker } from './UpdateWorkers/SplitsUpdateWorker';
12
10
  import { authenticateFactory, hashUserKey } from './AuthClient';
13
11
  import { forOwn } from '../../utils/lang';
14
12
  import { SSEClient } from './SSEClient';
15
- import { IFetchAuth } from '../../services/types';
16
- import { ISettings } from '../../types';
17
13
  import { getMatching } from '../../utils/key';
18
14
  import { MY_SEGMENTS_UPDATE, MY_SEGMENTS_UPDATE_V2, PUSH_NONRETRYABLE_ERROR, PUSH_SUBSYSTEM_DOWN, SECONDS_BEFORE_EXPIRATION, SEGMENT_UPDATE, SPLIT_KILL, SPLIT_UPDATE, PUSH_RETRYABLE_ERROR, PUSH_SUBSYSTEM_UP, ControlType } from './constants';
19
- import { IPlatform } from '../../sdkFactory/types';
20
15
  import { STREAMING_FALLBACK, STREAMING_REFRESH_TOKEN, STREAMING_CONNECTING, STREAMING_DISABLED, ERROR_STREAMING_AUTH, STREAMING_DISCONNECTING, STREAMING_RECONNECT, STREAMING_PARSING_MY_SEGMENTS_UPDATE_V2 } from '../../logger/constants';
21
16
  import { KeyList, UpdateStrategy } from './SSEHandler/types';
22
17
  import { isInBitmap, parseBitmap, parseKeyList } from './mySegmentsV2utils';
23
18
  import { ISet, _Set } from '../../utils/lang/sets';
24
19
  import { Hash64, hash64 } from '../../utils/murmur3/murmur3_64';
25
20
  import { IAuthTokenPushEnabled } from './AuthClient/types';
21
+ import { ISyncManagerFactoryParams } from '../types';
26
22
 
27
23
  /**
28
24
  * PushManager factory:
@@ -30,17 +26,15 @@ import { IAuthTokenPushEnabled } from './AuthClient/types';
30
26
  * - for client-side, with support for multiple clients, if key is provided in settings
31
27
  */
32
28
  export function pushManagerFactory(
29
+ params: ISyncManagerFactoryParams,
33
30
  pollingManager: IPollingManager,
34
- storage: IStorageSync,
35
- readiness: IReadinessManager,
36
- fetchAuth: IFetchAuth,
37
- platform: IPlatform,
38
- settings: ISettings,
39
- ): IPushManagerCS | undefined {
31
+ ): IPushManager | undefined {
32
+
33
+ const { settings, storage, splitApi, readiness, platform } = params;
40
34
 
41
35
  // `userKey` is the matching key of main client in client-side SDK.
42
36
  // It can be used to check if running on client-side or server-side SDK.
43
- const userKey = settings.core.key ? getMatching(settings.core.key) : undefined; //
37
+ const userKey = settings.core.key ? getMatching(settings.core.key) : undefined;
44
38
  const log = settings.log;
45
39
 
46
40
  let sseClient: ISSEClient;
@@ -51,7 +45,7 @@ export function pushManagerFactory(
51
45
  log.warn(STREAMING_FALLBACK, [e]);
52
46
  return;
53
47
  }
54
- const authenticate = authenticateFactory(fetchAuth);
48
+ const authenticate = authenticateFactory(splitApi.fetchAuth);
55
49
 
56
50
  // init feedback loop
57
51
  const pushEmitter = new platform.EventEmitter() as IPushEventEmitter;
@@ -59,7 +53,8 @@ export function pushManagerFactory(
59
53
  sseClient.setEventHandler(sseHandler);
60
54
 
61
55
  // init workers
62
- const segmentsUpdateWorker = userKey ? new MySegmentsUpdateWorker(pollingManager.segmentsSyncTask) : new SegmentsUpdateWorker(storage.segments, pollingManager.segmentsSyncTask);
56
+ // MySegmentsUpdateWorker (client-side) are initiated in `add` method
57
+ const segmentsUpdateWorker = userKey ? undefined : new SegmentsUpdateWorker(pollingManager.segmentsSyncTask, storage.segments);
63
58
  // For server-side we pass the segmentsSyncTask, used by SplitsUpdateWorker to fetch new segments
64
59
  const splitsUpdateWorker = new SplitsUpdateWorker(storage.splits, pollingManager.splitsSyncTask, readiness.splits, userKey ? undefined : pollingManager.segmentsSyncTask);
65
60
 
@@ -68,11 +63,6 @@ export function pushManagerFactory(
68
63
  // [Only for client-side] map of user keys to their corresponding hash64 and MySegmentsUpdateWorkers.
69
64
  // Hash64 is used to process MY_SEGMENTS_UPDATE_V2 events and dispatch actions to the corresponding MySegmentsUpdateWorker.
70
65
  const clients: Record<string, { hash64: Hash64, worker: MySegmentsUpdateWorker }> = {};
71
- if (userKey) {
72
- const hash = hashUserKey(userKey);
73
- userKeyHashes[hash] = userKey;
74
- clients[userKey] = { hash64: hash64(userKey), worker: segmentsUpdateWorker as MySegmentsUpdateWorker };
75
- }
76
66
 
77
67
  // [Only for client-side] variable to flag that a new client was added. It is needed to reconnect streaming.
78
68
  let connectForNewClient = false;
@@ -176,7 +166,7 @@ export function pushManagerFactory(
176
166
  function stopWorkers() {
177
167
  splitsUpdateWorker.backoff.reset();
178
168
  if (userKey) forOwn(clients, ({ worker }) => worker.backoff.reset());
179
- else segmentsUpdateWorker.backoff.reset();
169
+ else (segmentsUpdateWorker as SegmentsUpdateWorker).backoff.reset();
180
170
  }
181
171
 
182
172
  pushEmitter.on(PUSH_SUBSYSTEM_DOWN, stopWorkers);
@@ -306,37 +296,40 @@ export function pushManagerFactory(
306
296
  // Expose Event Emitter functionality and Event constants
307
297
  Object.create(pushEmitter),
308
298
  {
309
- // Expose functionality for starting and stoping push mode:
310
- stop: disconnectPush, // `handleNonRetryableError` cannot be used as `stop`, because it emits PUSH_SUBSYSTEM_DOWN event, which starts polling.
311
-
299
+ // Stop/pause push mode
300
+ stop() {
301
+ disconnectPush(); // `handleNonRetryableError` cannot be used as `stop`, because it emits PUSH_SUBSYSTEM_DOWN event, which starts polling.
302
+ if (userKey) this.remove(userKey); // Necessary to properly resume streaming in client-side (e.g., RN SDK transition to foreground).
303
+ },
304
+ // Start/resume push mode
312
305
  start() {
313
306
  // Guard condition to avoid calling `connectPush` again if the `start` method is called multiple times or if push has been disabled.
314
307
  if (disabled || disconnected === false) return;
315
308
  disconnected = false;
316
- // Run in next event-loop cycle for optimization on client-side: if multiple clients are created in the same cycle than the factory, only one authentication is performed.
317
- setTimeout(connectPush);
309
+
310
+ if (userKey) this.add(userKey, pollingManager.segmentsSyncTask); // client-side
311
+ else setTimeout(connectPush); // server-side runs in next cycle as in client-side, for consistency with client-side
318
312
  },
319
313
 
320
314
  // [Only for client-side]
321
315
  add(userKey: string, mySegmentsSyncTask: ISegmentsSyncTask) {
322
- clients[userKey] = { hash64: hash64(userKey), worker: new MySegmentsUpdateWorker(mySegmentsSyncTask) };
323
-
324
316
  const hash = hashUserKey(userKey);
325
317
 
326
318
  if (!userKeyHashes[hash]) {
327
319
  userKeyHashes[hash] = userKey;
320
+ clients[userKey] = { hash64: hash64(userKey), worker: new MySegmentsUpdateWorker(mySegmentsSyncTask) };
328
321
  connectForNewClient = true; // we must reconnect on start, to listen the channel for the new user key
329
- }
330
322
 
331
- // Reconnects in case of a new client.
332
- // Run in next event-loop cycle to save authentication calls
333
- // in case the user is creating several clients in the current cycle.
334
- setTimeout(function checkForReconnect() {
335
- if (connectForNewClient) {
336
- connectForNewClient = false;
337
- connectPush();
338
- }
339
- }, 0);
323
+ // Reconnects in case of a new client.
324
+ // Run in next event-loop cycle to save authentication calls
325
+ // in case multiple clients are created in the current cycle.
326
+ setTimeout(function checkForReconnect() {
327
+ if (connectForNewClient) {
328
+ connectForNewClient = false;
329
+ connectPush();
330
+ }
331
+ }, 0);
332
+ }
340
333
  },
341
334
  // [Only for client-side]
342
335
  remove(userKey: string) {
@@ -1,11 +1,7 @@
1
1
  import { IMySegmentsUpdateData, IMySegmentsUpdateV2Data, ISegmentUpdateData, ISplitUpdateData, ISplitKillData } from './SSEHandler/types';
2
2
  import { ITask } from '../types';
3
- import { IPollingManager, ISegmentsSyncTask } from '../polling/types';
4
- import { IReadinessManager } from '../../readiness/types';
5
- import { IFetchAuth } from '../../services/types';
6
- import { IStorageSync } from '../../storages/types';
7
- import { IEventEmitter, ISettings } from '../../types';
8
- import { IPlatform } from '../../sdkFactory/types';
3
+ import { ISegmentsSyncTask } from '../polling/types';
4
+ import { IEventEmitter } from '../../types';
9
5
  import { ControlType } from './constants';
10
6
 
11
7
  // Internal SDK events, subscribed by SyncManager and PushManager
@@ -45,26 +41,10 @@ export interface IPushEventEmitter extends IEventEmitter {
45
41
  }
46
42
 
47
43
  /**
48
- * PushManager for server-side
44
+ * PushManager
49
45
  */
50
- export interface IPushManager extends ITask, IPushEventEmitter { }
51
-
52
- /**
53
- * PushManager for client-side with support for multiple clients
54
- */
55
- export interface IPushManagerCS extends IPushManager {
46
+ export interface IPushManager extends ITask, IPushEventEmitter {
47
+ // Methods used in client-side, to support multiple clients
56
48
  add(userKey: string, mySegmentsSyncTask: ISegmentsSyncTask): void,
57
49
  remove(userKey: string): void
58
50
  }
59
-
60
- /**
61
- * Signature of push manager factory/constructor
62
- */
63
- export type IPushManagerFactoryParams = [
64
- pollingManager: IPollingManager,
65
- storage: IStorageSync,
66
- readiness: IReadinessManager,
67
- fetchAuth: IFetchAuth,
68
- platform: IPlatform,
69
- settings: ISettings
70
- ]
@@ -2,15 +2,11 @@ import { syncTaskComposite } from '../syncTaskComposite';
2
2
  import { eventsSyncTaskFactory } from './eventsSyncTask';
3
3
  import { impressionsSyncTaskFactory } from './impressionsSyncTask';
4
4
  import { impressionCountsSyncTaskFactory } from './impressionCountsSyncTask';
5
- import { ISplitApi } from '../../services/types';
6
- import { IStorageSync } from '../../storages/types';
7
- import { ISettings } from '../../types';
5
+ import { ISyncManagerFactoryParams } from '../types';
8
6
 
9
- export function submitterManagerFactory(
10
- settings: ISettings,
11
- storage: IStorageSync,
12
- splitApi: ISplitApi,
13
- ) {
7
+ export function submitterManagerFactory(params: ISyncManagerFactoryParams) {
8
+
9
+ const { settings, storage, splitApi } = params;
14
10
  const log = settings.log;
15
11
  const submitters = [
16
12
  impressionsSyncTaskFactory(log, splitApi.postTestImpressionsBulk, storage.impressions, settings.scheduler.impressionsRefreshRate, settings.core.labelsEnabled),
@@ -2,8 +2,8 @@ import { ISyncManagerCS, ISyncManagerFactoryParams } from './types';
2
2
  import { submitterManagerFactory } from './submitters/submitterManager';
3
3
  import { IReadinessManager } from '../readiness/types';
4
4
  import { IStorageSync } from '../storages/types';
5
- import { IPushManagerFactoryParams, IPushManager, IPushManagerCS } from './streaming/types';
6
- import { IPollingManager, IPollingManagerCS, IPollingManagerFactoryParams } from './polling/types';
5
+ import { IPushManager } from './streaming/types';
6
+ import { IPollingManager, IPollingManagerCS } from './polling/types';
7
7
  import { PUSH_SUBSYSTEM_UP, PUSH_SUBSYSTEM_DOWN } from './streaming/constants';
8
8
  import { SYNC_START_POLLING, SYNC_CONTINUE_POLLING, SYNC_STOP_POLLING } from '../logger/constants';
9
9
 
@@ -16,44 +16,38 @@ import { SYNC_START_POLLING, SYNC_CONTINUE_POLLING, SYNC_STOP_POLLING } from '..
16
16
  * @param pushManagerFactory optional to build a SyncManager with or without streaming support
17
17
  */
18
18
  export function syncManagerOnlineFactory(
19
- pollingManagerFactory?: (...args: IPollingManagerFactoryParams) => IPollingManager,
20
- pushManagerFactory?: (...args: IPushManagerFactoryParams) => IPushManager | undefined
19
+ pollingManagerFactory?: (params: ISyncManagerFactoryParams) => IPollingManager,
20
+ pushManagerFactory?: (params: ISyncManagerFactoryParams, pollingManager: IPollingManager) => IPushManager | undefined,
21
21
  ): (params: ISyncManagerFactoryParams) => ISyncManagerCS {
22
22
 
23
23
  /**
24
24
  * SyncManager factory for modular SDK
25
25
  */
26
- return function ({
27
- settings,
28
- platform,
29
- splitApi,
30
- storage,
31
- readiness
32
- }: ISyncManagerFactoryParams): ISyncManagerCS {
26
+ return function (params: ISyncManagerFactoryParams): ISyncManagerCS {
33
27
 
34
- const log = settings.log;
28
+ const { log, streamingEnabled } = params.settings;
35
29
 
36
30
  /** Polling Manager */
37
- const pollingManager = pollingManagerFactory && pollingManagerFactory(splitApi, storage, readiness, settings);
31
+ const pollingManager = pollingManagerFactory && pollingManagerFactory(params);
38
32
 
39
33
  /** Push Manager */
40
- const pushManager = settings.streamingEnabled && pollingManager && pushManagerFactory ?
41
- pushManagerFactory(pollingManager, storage, readiness, splitApi.fetchAuth, platform, settings) :
34
+ const pushManager = streamingEnabled && pollingManager && pushManagerFactory ?
35
+ pushManagerFactory(params, pollingManager) :
42
36
  undefined;
43
37
 
44
38
  /** Submitter Manager */
45
39
  // It is not inyected as push and polling managers, because at the moment it is required
46
- const submitter = submitterManagerFactory(settings, storage, splitApi);
40
+ const submitter = submitterManagerFactory(params);
47
41
 
48
42
 
49
43
  /** Sync Manager logic */
50
44
 
51
45
  function startPolling() {
52
- if (!pollingManager!.isRunning()) {
46
+ if (pollingManager!.isRunning()) {
47
+ log.info(SYNC_CONTINUE_POLLING);
48
+ } else {
53
49
  log.info(SYNC_START_POLLING);
54
50
  pollingManager!.start();
55
- } else {
56
- log.info(SYNC_CONTINUE_POLLING);
57
51
  }
58
52
  }
59
53
 
@@ -96,7 +90,7 @@ export function syncManagerOnlineFactory(
96
90
  }
97
91
 
98
92
  // start periodic data recording (events, impressions, telemetry).
99
- submitter && submitter.start();
93
+ if (submitter) submitter.start();
100
94
  running = true;
101
95
  },
102
96
 
@@ -141,7 +135,7 @@ export function syncManagerOnlineFactory(
141
135
  // of segments since `syncAll` was already executed when starting the main client
142
136
  mySegmentsSyncTask.execute();
143
137
  }
144
- (pushManager as IPushManagerCS).add(matchingKey, mySegmentsSyncTask);
138
+ pushManager.add(matchingKey, mySegmentsSyncTask);
145
139
  } else {
146
140
  if (storage.splits.usesSegments()) mySegmentsSyncTask.start();
147
141
  }
@@ -151,7 +145,7 @@ export function syncManagerOnlineFactory(
151
145
  const mySegmentsSyncTask = (pollingManager as IPollingManagerCS).get(matchingKey);
152
146
  if (mySegmentsSyncTask) {
153
147
  // stop syncing
154
- if (pushManager) (pushManager as IPushManagerCS).remove(matchingKey);
148
+ if (pushManager) pushManager.remove(matchingKey);
155
149
  if (mySegmentsSyncTask.isRunning()) mySegmentsSyncTask.stop();
156
150
 
157
151
  (pollingManager as IPollingManagerCS).remove(matchingKey);
package/src/types.ts CHANGED
@@ -381,6 +381,10 @@ interface IBasicClient extends IStatusInterface {
381
381
  * @returns {Promise<void>}
382
382
  */
383
383
  destroy(): Promise<void>
384
+
385
+ // Whether the client implements the client-side API, i.e, with bound key, (true), or the server-side API (false).
386
+ // Exposed for internal purposes only. Not considered part of the public API, and might be renamed eventually.
387
+ isBrowserClient: boolean
384
388
  }
385
389
  /**
386
390
  * Common definitions between SDK instances for different environments interface.
@@ -1108,6 +1112,45 @@ export namespace SplitIO {
1108
1112
  * @returns {boolean} Whether the event was added to the queue succesfully or not.
1109
1113
  */
1110
1114
  track(...args: [trafficType: string, eventType: string, value?: number, properties?: Properties] | [eventType: string, value?: number, properties?: Properties]): boolean,
1115
+ /**
1116
+ * Add an attribute to client's in memory attributes storage
1117
+ * @function setAttribute
1118
+ * @param {string} attributeName Attrinute name
1119
+ * @param {string, number, boolean, list} attributeValue Attribute value
1120
+ * @returns {boolean} true if the attribute was stored and false otherways
1121
+ */
1122
+ setAttribute(attributeName: string, attributeValue: Object): boolean,
1123
+ /**
1124
+ * Returns the attribute with the given key
1125
+ * @function getAttribute
1126
+ * @param {string} attributeName Attribute name
1127
+ * @returns {Object} Attribute with the given key
1128
+ */
1129
+ getAttribute(attributeName: string): Object,
1130
+ /**
1131
+ * Add to client's in memory attributes storage the attributes in 'attributes'
1132
+ * @function setAttributes
1133
+ * @param {Object} attributes Object with attributes to store
1134
+ * @returns true if attributes were stored an false otherways
1135
+ */
1136
+ setAttributes(attributes: Record<string, Object>): boolean,
1137
+ /**
1138
+ * Return all the attributes stored in client's in memory attributes storage
1139
+ * @function getAttributes
1140
+ * @returns {Object} returns all the stored attributes
1141
+ */
1142
+ getAttributes(): Record<string, Object>,
1143
+ /**
1144
+ * Removes from client's in memory attributes storage the attribute with the given key
1145
+ * @function removeAttribute
1146
+ * @param {string} attributeName
1147
+ * @returns {boolean} true if attribute was removed and false otherways
1148
+ */
1149
+ removeAttribute(attributeName: string): boolean,
1150
+ /**
1151
+ * Remove all the stored attributes in the client's in memory attribute storage
1152
+ */
1153
+ clearAttributes(): boolean
1111
1154
  }
1112
1155
  /**
1113
1156
  * Representation of a manager instance with synchronous storage of the SDK.
@@ -0,0 +1,21 @@
1
+ import { isString, isFiniteNumber, isBoolean } from '../../utils/lang';
2
+ import { ILogger } from '../../logger/types';
3
+
4
+ export function validateAttribute(log: ILogger, attributeKey: string, attributeValue: Object, method: string): boolean {
5
+ if (!isString(attributeKey) || attributeKey.length === 0) {
6
+ log.warn(`${method}: you passed an invalid attribute name, attribute name must be a non-empty string.`);
7
+ return false;
8
+ }
9
+
10
+ const isStringVal = isString(attributeValue);
11
+ const isFiniteVal = isFiniteNumber(attributeValue);
12
+ const isBoolVal = isBoolean(attributeValue);
13
+ const isArrayVal = Array.isArray(attributeValue);
14
+
15
+ if (!(isStringVal || isFiniteVal || isBoolVal || isArrayVal)) { // If it's not of valid type.
16
+ log.warn(`${method}: you passed an invalid attribute value for ${attributeKey}. Acceptable types are: string, number, boolean and array of strings.`);
17
+ return false;
18
+ }
19
+
20
+ return true;
21
+ }
@@ -1,6 +1,7 @@
1
1
  import { isObject } from '../lang';
2
2
  import { SplitIO } from '../../types';
3
3
  import { ILogger } from '../../logger/types';
4
+ import { validateAttribute } from './attribute';
4
5
  import { ERROR_NOT_PLAIN_OBJECT } from '../../logger/constants';
5
6
 
6
7
  export function validateAttributes(log: ILogger, maybeAttrs: any, method: string): SplitIO.Attributes | undefined | false {
@@ -11,3 +12,16 @@ export function validateAttributes(log: ILogger, maybeAttrs: any, method: string
11
12
  log.error(ERROR_NOT_PLAIN_OBJECT, [method, 'attributes']);
12
13
  return false;
13
14
  }
15
+
16
+ export function validateAttributesDeep(log: ILogger, maybeAttributes: Record<string, Object>, method: string): boolean {
17
+ if (!validateAttributes(log, maybeAttributes, method)) return false;
18
+
19
+ let result = true;
20
+ Object.keys(maybeAttributes).forEach(attributeKey => {
21
+ if (!validateAttribute(log, attributeKey, maybeAttributes[attributeKey], method))
22
+ result = false;
23
+ });
24
+
25
+ return result;
26
+
27
+ }
@@ -1,4 +1,5 @@
1
- import { isObject, shallowClone, isString, isFiniteNumber, isBoolean } from '../lang';
1
+ import { isObject, isString, isFiniteNumber, isBoolean } from '../lang';
2
+ import { objectAssign } from '../lang/objectAssign';
2
3
  import { SplitIO } from '../../types';
3
4
  import { ILogger } from '../../logger/types';
4
5
  import { ERROR_NOT_PLAIN_OBJECT, ERROR_SIZE_EXCEEDED, WARN_SETTING_NULL, WARN_TRIMMING_PROPERTIES } from '../../logger/constants';
@@ -22,7 +23,8 @@ export function validateEventProperties(log: ILogger, maybeProperties: any, meth
22
23
  }
23
24
 
24
25
  const keys = Object.keys(maybeProperties);
25
- const clone = shallowClone(maybeProperties);
26
+ // Shallow clone
27
+ const clone = objectAssign({}, maybeProperties);
26
28
  // To avoid calculating the size twice we'll return it from here.
27
29
  const output = {
28
30
  properties: clone,
@@ -194,20 +194,6 @@ export function merge(target: { [key: string]: any }, source: { [key: string]: a
194
194
  return res;
195
195
  }
196
196
 
197
- /**
198
- * Shallow clone an object
199
- */
200
- export function shallowClone(obj: any): any {
201
- const keys = Object.keys(obj);
202
- const output: Record<string, any> = {};
203
-
204
- for (let i = 0; i < keys.length; i++) {
205
- output[keys[i]] = obj[keys[i]];
206
- }
207
-
208
- return output;
209
- }
210
-
211
197
  /**
212
198
  * Checks if the target string starts with the sub string.
213
199
  */
@@ -0,0 +1,48 @@
1
+ // Deprecated hashing function, used for split bucketing. Replaced by murmur3
2
+
3
+ //
4
+ // JAVA reference implementation for the hashing function.
5
+ //
6
+ // int h = 0;
7
+ // for (int i = 0; i < key.length(); i++) {
8
+ // h = 31 * h + key.charAt(i);
9
+ // }
10
+ // return h ^ seed; // XOR the hash and seed
11
+ //
12
+
13
+ function ToInteger(x: number) {
14
+ x = Number(x);
15
+ return x < 0 ? Math.ceil(x) : Math.floor(x);
16
+ }
17
+
18
+ function modulo(a: number, b: number) {
19
+ return a - Math.floor(a / b) * b;
20
+ }
21
+
22
+ function ToUint32(x: number) {
23
+ return modulo(ToInteger(x), Math.pow(2, 32));
24
+ }
25
+
26
+ function ToInt32(x: number) {
27
+ let uint32 = ToUint32(x);
28
+
29
+ if (uint32 >= Math.pow(2, 31)) {
30
+ return uint32 - Math.pow(2, 32);
31
+ } else {
32
+ return uint32;
33
+ }
34
+ }
35
+
36
+ export function hash(str: string, seed: number): number {
37
+ let h = 0;
38
+
39
+ for (let i = 0; i < str.length; i++) {
40
+ h = ToInt32(ToInt32(31 * h) + str.charCodeAt(i));
41
+ }
42
+
43
+ return ToInt32(h ^ seed);
44
+ }
45
+
46
+ export function bucket(str: string, seed: number): number {
47
+ return Math.abs(hash(str, seed) % 100) + 1;
48
+ }
@@ -3,7 +3,7 @@ export function timeout<T>(ms: number, promise: Promise<T>): Promise<T> {
3
3
 
4
4
  return new Promise((resolve, reject) => {
5
5
  const tid = setTimeout(() => {
6
- reject(new Error(`Operation timed out because it exceeded the configured time limit of ${ms}ms.`));
6
+ reject(new Error(`Operation timed out because it exceeded the configured time limit of ${ms} ms.`));
7
7
  }, ms);
8
8
 
9
9
  promise.then((res) => {
@@ -0,0 +1,51 @@
1
+ import { SplitIO } from '../types';
2
+ import { ILogger } from '../logger/types';
3
+ /**
4
+ * Add in memory attributes storage methods and combine them with any attribute received from the getTreatment/s call
5
+ */
6
+ export declare function clientAttributesDecoration<TClient extends SplitIO.IClient | SplitIO.IAsyncClient>(log: ILogger, client: TClient): TClient & {
7
+ getTreatment: (maybeKey: SplitIO.SplitKey, maybeSplit: string, maybeAttributes?: SplitIO.Attributes | undefined) => string | SplitIO.AsyncTreatment;
8
+ getTreatmentWithConfig: (maybeKey: SplitIO.SplitKey, maybeSplit: string, maybeAttributes?: SplitIO.Attributes | undefined) => SplitIO.TreatmentWithConfig | SplitIO.AsyncTreatmentWithConfig;
9
+ getTreatments: (maybeKey: SplitIO.SplitKey, maybeSplits: string[], maybeAttributes?: SplitIO.Attributes | undefined) => SplitIO.Treatments | SplitIO.AsyncTreatments;
10
+ getTreatmentsWithConfig: (maybeKey: SplitIO.SplitKey, maybeSplits: string[], maybeAttributes?: SplitIO.Attributes | undefined) => SplitIO.TreatmentsWithConfig | SplitIO.AsyncTreatmentsWithConfig;
11
+ track: (maybeKey: SplitIO.SplitKey, maybeTT: string, maybeEvent: string, maybeEventValue?: number | undefined, maybeProperties?: SplitIO.Properties | undefined) => import("../dtos/types").MaybeThenable<boolean>;
12
+ /**
13
+ * Add an attribute to client's in memory attributes storage
14
+ *
15
+ * @param {string} attributeName Attrinute name
16
+ * @param {string, number, boolean, list} attributeValue Attribute value
17
+ * @returns {boolean} true if the attribute was stored and false otherways
18
+ */
19
+ setAttribute(attributeName: string, attributeValue: Object): boolean;
20
+ /**
21
+ * Returns the attribute with the given key
22
+ *
23
+ * @param {string} attributeName Attribute name
24
+ * @returns {Object} Attribute with the given key
25
+ */
26
+ getAttribute(attributeName: string): Object;
27
+ /**
28
+ * Add to client's in memory attributes storage the attributes in 'attributes'
29
+ *
30
+ * @param {Object} attributes Object with attributes to store
31
+ * @returns true if attributes were stored an false otherways
32
+ */
33
+ setAttributes(attributes: Record<string, Object>): boolean;
34
+ /**
35
+ * Return all the attributes stored in client's in memory attributes storage
36
+ *
37
+ * @returns {Object} returns all the stored attributes
38
+ */
39
+ getAttributes(): Record<string, Object>;
40
+ /**
41
+ * Removes from client's in memory attributes storage the attribute with the given key
42
+ *
43
+ * @param {string} attributeName
44
+ * @returns {boolean} true if attribute was removed and false otherways
45
+ */
46
+ removeAttribute(attributeName: string): boolean;
47
+ /**
48
+ * Remove all the stored attributes in the client's in memory attribute storage
49
+ */
50
+ clearAttributes(): boolean;
51
+ };
@@ -1,3 +1,4 @@
1
+ import { ILogger } from '../logger/types';
1
2
  import { SplitIO } from '../types';
2
3
  /**
3
4
  * Decorator that binds a key and (optionally) a traffic type to client methods
@@ -6,4 +7,4 @@ import { SplitIO } from '../types';
6
7
  * @param key validated split key
7
8
  * @param trafficType validated traffic type
8
9
  */
9
- export declare function clientCSDecorator(client: SplitIO.IClient, key: SplitIO.SplitKey, trafficType?: string): SplitIO.ICsClient;
10
+ export declare function clientCSDecorator(log: ILogger, client: SplitIO.IClient, key: SplitIO.SplitKey, trafficType?: string): SplitIO.ICsClient;
@@ -0,0 +1,43 @@
1
+ export declare class AttributesCacheInMemory {
2
+ private attributesCache;
3
+ /**
4
+ * Create or update the value for the given attribute
5
+ *
6
+ * @param {string} attributeName attribute name
7
+ * @param {Object} attributeValue attribute value
8
+ * @returns {boolean} the attribute was stored
9
+ */
10
+ setAttribute(attributeName: string, attributeValue: Object): boolean;
11
+ /**
12
+ * Retrieves the value of a given attribute
13
+ *
14
+ * @param {string} attributeName attribute name
15
+ * @returns {Object?} stored attribute value
16
+ */
17
+ getAttribute(attributeName: string): Object;
18
+ /**
19
+ * Create or update all the given attributes
20
+ *
21
+ * @param {[string, Object]} attributes attributes to create or update
22
+ * @returns {boolean} attributes were stored
23
+ */
24
+ setAttributes(attributes: Record<string, Object>): boolean;
25
+ /**
26
+ * Retrieve the full attributes map
27
+ *
28
+ * @returns {Map<string, Object>} stored attributes
29
+ */
30
+ getAll(): Record<string, Object>;
31
+ /**
32
+ * Removes a given attribute from the map
33
+ *
34
+ * @param {string} attributeName attribute to remove
35
+ * @returns {boolean} attribute removed
36
+ */
37
+ removeAttribute(attributeName: string): boolean;
38
+ /**
39
+ * Clears all attributes stored in the SDK
40
+ *
41
+ */
42
+ clear(): boolean;
43
+ }