@splitsoftware/splitio-commons 1.2.1-rc.3 → 1.2.1-rc.6

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 (104) hide show
  1. package/cjs/listeners/browser.js +14 -10
  2. package/cjs/logger/constants.js +7 -4
  3. package/cjs/logger/messages/debug.js +3 -3
  4. package/cjs/logger/messages/error.js +3 -2
  5. package/cjs/logger/messages/info.js +5 -3
  6. package/cjs/sdkClient/client.js +10 -4
  7. package/cjs/sdkFactory/index.js +6 -4
  8. package/cjs/sdkFactory/userConsentProps.js +37 -0
  9. package/cjs/storages/KeyBuilder.js +1 -5
  10. package/cjs/storages/KeyBuilderCS.js +11 -1
  11. package/cjs/storages/inLocalStorage/MySegmentsCacheInLocal.js +23 -3
  12. package/cjs/storages/inLocalStorage/index.js +1 -1
  13. package/cjs/storages/inMemory/ImpressionsCacheInMemory.js +15 -1
  14. package/cjs/storages/inMemory/InMemoryStorage.js +1 -1
  15. package/cjs/storages/inMemory/InMemoryStorageCS.js +1 -1
  16. package/cjs/storages/pluggable/index.js +2 -2
  17. package/cjs/sync/submitters/eventsSyncTask.js +17 -5
  18. package/cjs/sync/submitters/impressionsSyncTask.js +13 -1
  19. package/cjs/sync/syncManagerOnline.js +11 -7
  20. package/cjs/utils/consent.js +10 -0
  21. package/cjs/utils/constants/index.js +5 -1
  22. package/cjs/utils/lang/index.js +8 -1
  23. package/cjs/utils/settingsValidation/consent.js +16 -0
  24. package/cjs/utils/settingsValidation/impressionsMode.js +6 -6
  25. package/cjs/utils/settingsValidation/index.js +6 -1
  26. package/cjs/utils/settingsValidation/{runtime/browser.js → runtime.js} +1 -0
  27. package/esm/listeners/browser.js +14 -10
  28. package/esm/logger/constants.js +5 -2
  29. package/esm/logger/messages/debug.js +3 -3
  30. package/esm/logger/messages/error.js +3 -2
  31. package/esm/logger/messages/info.js +5 -3
  32. package/esm/sdkClient/client.js +11 -5
  33. package/esm/sdkFactory/index.js +6 -4
  34. package/esm/sdkFactory/userConsentProps.js +33 -0
  35. package/esm/storages/KeyBuilder.js +2 -6
  36. package/esm/storages/KeyBuilderCS.js +11 -1
  37. package/esm/storages/inLocalStorage/MySegmentsCacheInLocal.js +23 -3
  38. package/esm/storages/inLocalStorage/index.js +1 -1
  39. package/esm/storages/inMemory/ImpressionsCacheInMemory.js +15 -1
  40. package/esm/storages/inMemory/InMemoryStorage.js +1 -1
  41. package/esm/storages/inMemory/InMemoryStorageCS.js +1 -1
  42. package/esm/storages/pluggable/index.js +2 -2
  43. package/esm/sync/submitters/eventsSyncTask.js +18 -6
  44. package/esm/sync/submitters/impressionsSyncTask.js +13 -1
  45. package/esm/sync/syncManagerOnline.js +11 -7
  46. package/esm/utils/consent.js +6 -0
  47. package/esm/utils/constants/index.js +4 -0
  48. package/esm/utils/lang/index.js +6 -0
  49. package/esm/utils/settingsValidation/consent.js +12 -0
  50. package/esm/utils/settingsValidation/impressionsMode.js +7 -7
  51. package/esm/utils/settingsValidation/index.js +6 -1
  52. package/esm/utils/settingsValidation/{runtime/browser.js → runtime.js} +1 -0
  53. package/package.json +1 -2
  54. package/src/listeners/browser.ts +13 -9
  55. package/src/logger/constants.ts +5 -2
  56. package/src/logger/messages/debug.ts +3 -3
  57. package/src/logger/messages/error.ts +3 -2
  58. package/src/logger/messages/info.ts +5 -3
  59. package/src/sdkClient/client.ts +7 -5
  60. package/src/sdkFactory/index.ts +6 -4
  61. package/src/sdkFactory/types.ts +2 -0
  62. package/src/sdkFactory/userConsentProps.ts +40 -0
  63. package/src/storages/KeyBuilder.ts +2 -6
  64. package/src/storages/KeyBuilderCS.ts +13 -1
  65. package/src/storages/inLocalStorage/MySegmentsCacheInLocal.ts +23 -3
  66. package/src/storages/inLocalStorage/index.ts +1 -1
  67. package/src/storages/inMemory/ImpressionsCacheInMemory.ts +22 -1
  68. package/src/storages/inMemory/InMemoryStorage.ts +1 -1
  69. package/src/storages/inMemory/InMemoryStorageCS.ts +1 -1
  70. package/src/storages/pluggable/index.ts +2 -2
  71. package/src/storages/types.ts +5 -1
  72. package/src/sync/submitters/eventsSyncTask.ts +19 -6
  73. package/src/sync/submitters/impressionsSyncTask.ts +16 -1
  74. package/src/sync/syncManagerOnline.ts +13 -7
  75. package/src/sync/types.ts +4 -1
  76. package/src/types.ts +21 -0
  77. package/src/utils/consent.ts +8 -0
  78. package/src/utils/constants/index.ts +5 -0
  79. package/src/utils/lang/index.ts +8 -1
  80. package/src/utils/settingsValidation/consent.ts +15 -0
  81. package/src/utils/settingsValidation/impressionsMode.ts +8 -8
  82. package/src/utils/settingsValidation/index.ts +7 -1
  83. package/src/utils/settingsValidation/runtime.ts +9 -0
  84. package/src/utils/settingsValidation/types.ts +2 -0
  85. package/types/logger/constants.d.ts +5 -2
  86. package/types/sdkFactory/types.d.ts +1 -0
  87. package/types/sdkFactory/userConsentProps.d.ts +6 -0
  88. package/types/storages/KeyBuilderCS.d.ts +2 -0
  89. package/types/storages/inMemory/ImpressionsCacheInMemory.d.ts +9 -0
  90. package/types/storages/types.d.ts +2 -0
  91. package/types/sync/types.d.ts +3 -0
  92. package/types/types.d.ts +21 -0
  93. package/types/utils/consent.d.ts +2 -0
  94. package/types/utils/constants/index.d.ts +3 -0
  95. package/types/utils/lang/index.d.ts +4 -0
  96. package/types/utils/settingsValidation/consent.d.ts +5 -0
  97. package/types/utils/settingsValidation/impressionsMode.d.ts +1 -1
  98. package/types/utils/settingsValidation/runtime.d.ts +2 -0
  99. package/types/utils/settingsValidation/types.d.ts +2 -0
  100. package/types/utils/settingsValidation/userConsent.d.ts +5 -0
  101. package/cjs/utils/settingsValidation/runtime/node.js +0 -22
  102. package/esm/utils/settingsValidation/runtime/node.js +0 -17
  103. package/src/utils/settingsValidation/runtime/browser.ts +0 -8
  104. package/src/utils/settingsValidation/runtime/node.ts +0 -22
@@ -298,7 +298,7 @@ export interface IRecorderCacheProducerSync<T> {
298
298
  // @TODO names are inconsistent with spec
299
299
  /* Checks if cache is empty. Returns true if the cache was just created or cleared */
300
300
  isEmpty(): boolean
301
- /* clears cache data */
301
+ /* Clears cache data */
302
302
  clear(): void
303
303
  /* Gets cache data */
304
304
  state(): T
@@ -307,10 +307,13 @@ export interface IRecorderCacheProducerSync<T> {
307
307
 
308
308
  export interface IImpressionsCacheSync extends IImpressionsCacheBase, IRecorderCacheProducerSync<ImpressionDTO[]> {
309
309
  track(data: ImpressionDTO[]): void
310
+ /* Registers callback for full queue */
311
+ setOnFullQueueCb(cb: () => void): void
310
312
  }
311
313
 
312
314
  export interface IEventsCacheSync extends IEventsCacheBase, IRecorderCacheProducerSync<SplitIO.EventData[]> {
313
315
  track(data: SplitIO.EventData, size?: number): boolean
316
+ /* Registers callback for full queue */
314
317
  setOnFullQueueCb(cb: () => void): void
315
318
  }
316
319
 
@@ -423,6 +426,7 @@ export type DataLoader = (storage: IStorageSync, matchingKey: string) => void
423
426
 
424
427
  export interface IStorageFactoryParams {
425
428
  log: ILogger,
429
+ impressionsQueueSize?: number,
426
430
  eventsQueueSize?: number,
427
431
  optimize?: boolean /* whether create the `impressionCounts` cache (OPTIMIZED impression mode) or not (DEBUG impression mode) */,
428
432
 
@@ -3,7 +3,9 @@ import { IPostEventsBulk } from '../../services/types';
3
3
  import { ISyncTask, ITimeTracker } from '../types';
4
4
  import { submitterSyncTaskFactory } from './submitterSyncTask';
5
5
  import { ILogger } from '../../logger/types';
6
- import { SUBMITTERS_PUSH_FULL_EVENTS_QUEUE } from '../../logger/constants';
6
+ import { SUBMITTERS_PUSH_FULL_QUEUE } from '../../logger/constants';
7
+
8
+ const DATA_NAME = 'events';
7
9
 
8
10
  /**
9
11
  * Sync task that periodically posts tracked events
@@ -18,26 +20,37 @@ export function eventsSyncTaskFactory(
18
20
  ): ISyncTask {
19
21
 
20
22
  // don't retry events.
21
- const syncTask = submitterSyncTaskFactory(log, postEventsBulk, eventsCache, eventsPushRate, 'queued events', latencyTracker);
23
+ const syncTask = submitterSyncTaskFactory(log, postEventsBulk, eventsCache, eventsPushRate, DATA_NAME, latencyTracker);
22
24
 
23
- // Set a timer for the first push of events,
25
+ // Set a timer for the first push window of events.
26
+ // Not implemented in the base submitter or sync task, since this feature is only used by the events submitter.
24
27
  if (eventsFirstPushWindow > 0) {
28
+ let running = false;
25
29
  let stopEventPublisherTimeout: ReturnType<typeof setTimeout>;
26
30
  const originalStart = syncTask.start;
27
31
  syncTask.start = () => {
32
+ running = true;
28
33
  stopEventPublisherTimeout = setTimeout(originalStart, eventsFirstPushWindow);
29
34
  };
30
35
  const originalStop = syncTask.stop;
31
36
  syncTask.stop = () => {
37
+ running = false;
32
38
  clearTimeout(stopEventPublisherTimeout);
33
39
  originalStop();
34
40
  };
41
+ syncTask.isRunning = () => {
42
+ return running;
43
+ };
35
44
  }
36
45
 
37
- // register eventsSubmitter to be executed when events cache is full
46
+ // register events submitter to be executed when events cache is full
38
47
  eventsCache.setOnFullQueueCb(() => {
39
- log.info(SUBMITTERS_PUSH_FULL_EVENTS_QUEUE);
40
- syncTask.execute();
48
+ if (syncTask.isRunning()) {
49
+ log.info(SUBMITTERS_PUSH_FULL_QUEUE, [DATA_NAME]);
50
+ syncTask.execute();
51
+ }
52
+ // If submitter is stopped (e.g., user consent declined or unknown, or app state offline), we don't send the data.
53
+ // Data will be sent when submitter is resumed.
41
54
  });
42
55
 
43
56
  return syncTask;
@@ -6,6 +6,9 @@ import { ImpressionDTO } from '../../types';
6
6
  import { submitterSyncTaskFactory } from './submitterSyncTask';
7
7
  import { ImpressionsPayload } from './types';
8
8
  import { ILogger } from '../../logger/types';
9
+ import { SUBMITTERS_PUSH_FULL_QUEUE } from '../../logger/constants';
10
+
11
+ const DATA_NAME = 'impressions';
9
12
 
10
13
  /**
11
14
  * Converts `impressions` data from cache into request payload.
@@ -50,5 +53,17 @@ export function impressionsSyncTaskFactory(
50
53
  ): ISyncTask {
51
54
 
52
55
  // retry impressions only once.
53
- return submitterSyncTaskFactory(log, postTestImpressionsBulk, impressionsCache, impressionsRefreshRate, 'impressions', latencyTracker, fromImpressionsCollector.bind(undefined, sendLabels), 1);
56
+ const syncTask = submitterSyncTaskFactory(log, postTestImpressionsBulk, impressionsCache, impressionsRefreshRate, DATA_NAME, latencyTracker, fromImpressionsCollector.bind(undefined, sendLabels), 1);
57
+
58
+ // register impressions submitter to be executed when impressions cache is full
59
+ impressionsCache.setOnFullQueueCb(() => {
60
+ if (syncTask.isRunning()) {
61
+ log.info(SUBMITTERS_PUSH_FULL_QUEUE, [DATA_NAME]);
62
+ syncTask.execute();
63
+ }
64
+ // If submitter is stopped (e.g., user consent declined or unknown, or app state offline), we don't send the data.
65
+ // Data will be sent when submitter is resumed.
66
+ });
67
+
68
+ return syncTask;
54
69
  }
@@ -6,6 +6,7 @@ import { IPushManager } from './streaming/types';
6
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
+ import { isConsentGranted } from '../utils/consent';
9
10
 
10
11
  /**
11
12
  * Online SyncManager factory.
@@ -25,7 +26,7 @@ export function syncManagerOnlineFactory(
25
26
  */
26
27
  return function (params: ISyncManagerFactoryParams): ISyncManagerCS {
27
28
 
28
- const { log, streamingEnabled } = params.settings;
29
+ const { settings, settings: { log, streamingEnabled } } = params;
29
30
 
30
31
  /** Polling Manager */
31
32
  const pollingManager = pollingManagerFactory && pollingManagerFactory(params);
@@ -39,7 +40,6 @@ export function syncManagerOnlineFactory(
39
40
  // It is not inyected as push and polling managers, because at the moment it is required
40
41
  const submitter = submitterManagerFactory(params);
41
42
 
42
-
43
43
  /** Sync Manager logic */
44
44
 
45
45
  function startPolling() {
@@ -69,12 +69,18 @@ export function syncManagerOnlineFactory(
69
69
  let startFirstTime = true; // flag to distinguish calling the `start` method for the first time, to support pausing and resuming the synchronization
70
70
 
71
71
  return {
72
+ // Exposed for fine-grained control of synchronization.
73
+ // E.g.: user consent, app state changes (Page hide, Foreground/Background, Online/Offline).
74
+ pollingManager,
72
75
  pushManager,
76
+ submitter,
73
77
 
74
78
  /**
75
79
  * Method used to start the syncManager for the first time, or resume it after being stopped.
76
80
  */
77
81
  start() {
82
+ running = true;
83
+
78
84
  // start syncing splits and segments
79
85
  if (pollingManager) {
80
86
  if (pushManager) {
@@ -90,21 +96,21 @@ export function syncManagerOnlineFactory(
90
96
  }
91
97
 
92
98
  // start periodic data recording (events, impressions, telemetry).
93
- if (submitter) submitter.start();
94
- running = true;
99
+ if (isConsentGranted(settings)) submitter.start();
95
100
  },
96
101
 
97
102
  /**
98
103
  * Method used to stop/pause the syncManager.
99
104
  */
100
105
  stop() {
106
+ running = false;
107
+
101
108
  // stop syncing
102
109
  if (pushManager) pushManager.stop();
103
110
  if (pollingManager && pollingManager.isRunning()) pollingManager.stop();
104
111
 
105
112
  // stop periodic data recording (events, impressions, telemetry).
106
- if (submitter) submitter.stop();
107
- running = false;
113
+ submitter.stop();
108
114
  },
109
115
 
110
116
  isRunning() {
@@ -112,7 +118,7 @@ export function syncManagerOnlineFactory(
112
118
  },
113
119
 
114
120
  flush() {
115
- if (submitter) return submitter.execute();
121
+ if (isConsentGranted(settings)) return submitter.execute();
116
122
  else return Promise.resolve();
117
123
  },
118
124
 
package/src/sync/types.ts CHANGED
@@ -3,6 +3,7 @@ import { IPlatform } from '../sdkFactory/types';
3
3
  import { ISplitApi } from '../services/types';
4
4
  import { IStorageSync } from '../storages/types';
5
5
  import { ISettings } from '../types';
6
+ import { IPollingManager } from './polling/types';
6
7
  import { IPushManager } from './streaming/types';
7
8
 
8
9
  export interface ITask<Input extends any[] = []> {
@@ -43,7 +44,9 @@ export interface ITimeTracker {
43
44
 
44
45
  export interface ISyncManager extends ITask {
45
46
  flush(): Promise<any>,
46
- pushManager?: IPushManager
47
+ pushManager?: IPushManager,
48
+ pollingManager?: IPollingManager,
49
+ submitter?: ISyncTask
47
50
  }
48
51
 
49
52
  export interface ISyncManagerCS extends ISyncManager {
package/src/types.ts CHANGED
@@ -54,6 +54,11 @@ type EventConsts = {
54
54
  * @typedef {string} SDKMode
55
55
  */
56
56
  export type SDKMode = 'standalone' | 'consumer' | 'localhost' | 'consumer_partial';
57
+ /**
58
+ * User consent status.
59
+ * @typedef {string} ConsentStatus
60
+ */
61
+ export type ConsentStatus = 'GRANTED' | 'DECLINED' | 'UNKNOWN';
57
62
  /**
58
63
  * Settings interface. This is a representation of the settings the SDK expose, that's why
59
64
  * most of it's props are readonly. Only features should be rewritten when localhost mode is active.
@@ -73,6 +78,7 @@ export interface ISettings {
73
78
  readonly scheduler: {
74
79
  featuresRefreshRate: number,
75
80
  impressionsRefreshRate: number,
81
+ impressionsQueueSize: number,
76
82
  metricsRefreshRate: number,
77
83
  segmentsRefreshRate: number,
78
84
  offlineRefreshRate: number,
@@ -110,6 +116,7 @@ export interface ISettings {
110
116
  },
111
117
  readonly log: ILogger
112
118
  readonly impressionListener?: unknown
119
+ readonly userConsent?: ConsentStatus
113
120
  }
114
121
  /**
115
122
  * Log levels.
@@ -255,6 +262,13 @@ interface INodeBasicSettings extends ISharedSettings {
255
262
  * @default 300
256
263
  */
257
264
  impressionsRefreshRate?: number,
265
+ /**
266
+ * The maximum number of impression items we want to queue. If we queue more values, it will trigger a flush and reset the timer.
267
+ * If you use a 0 here, the queue will have no maximum size.
268
+ * @property {number} impressionsQueueSize
269
+ * @default 30000
270
+ */
271
+ impressionsQueueSize?: number,
258
272
  /**
259
273
  * The SDK sends diagnostic metrics to Split servers. This parameters controls this metric flush period in seconds.
260
274
  * @property {number} metricsRefreshRate
@@ -769,6 +783,13 @@ export namespace SplitIO {
769
783
  * @default 60
770
784
  */
771
785
  impressionsRefreshRate?: number,
786
+ /**
787
+ * The maximum number of impression items we want to queue. If we queue more values, it will trigger a flush and reset the timer.
788
+ * If you use a 0 here, the queue will have no maximum size.
789
+ * @property {number} impressionsQueueSize
790
+ * @default 30000
791
+ */
792
+ impressionsQueueSize?: number,
772
793
  /**
773
794
  * The SDK sends diagnostic metrics to Split servers. This parameters controls this metric flush period in seconds.
774
795
  * @property {number} metricsRefreshRate
@@ -0,0 +1,8 @@
1
+ import { ISettings } from '../types';
2
+ import { CONSENT_GRANTED } from './constants';
3
+
4
+ export function isConsentGranted(settings: ISettings) {
5
+ const userConsent = settings.userConsent;
6
+ // undefined userConsent is handled as granted (default)
7
+ return !userConsent || userConsent === CONSENT_GRANTED;
8
+ }
@@ -32,3 +32,8 @@ export const STORAGE_MEMORY: StorageType = 'MEMORY';
32
32
  export const STORAGE_LOCALSTORAGE: StorageType = 'LOCALSTORAGE';
33
33
  export const STORAGE_REDIS: StorageType = 'REDIS';
34
34
  export const STORAGE_PLUGGABLE: StorageType = 'PLUGGABLE';
35
+
36
+ // User consent
37
+ export const CONSENT_GRANTED = 'GRANTED'; // The user has granted consent for tracking events and impressions
38
+ export const CONSENT_DECLINED = 'DECLINED'; // The user has declined consent for tracking events and impressions
39
+ export const CONSENT_UNKNOWN = 'UNKNOWN'; // The user has neither granted nor declined consent for tracking events and impressions
@@ -89,7 +89,7 @@ export function get(obj: any, prop: any, val: any): any {
89
89
  /**
90
90
  * Parses an array into a map of different arrays, grouping by the specified prop value.
91
91
  */
92
- export function groupBy<T extends Record<string, any> >(source: T[], prop: string): Record<string, T[]> {
92
+ export function groupBy<T extends Record<string, any>>(source: T[], prop: string): Record<string, T[]> {
93
93
  const map: Record<string, any[]> = {};
94
94
 
95
95
  if (Array.isArray(source) && isString(prop)) {
@@ -164,6 +164,13 @@ export function isString(val: any): val is string {
164
164
  return typeof val === 'string' || val instanceof String;
165
165
  }
166
166
 
167
+ /**
168
+ * String sanitizer. Returns the provided value converted to uppercase if it is a string.
169
+ */
170
+ export function stringToUpperCase(val: any) {
171
+ return isString(val) ? val.toUpperCase() : val;
172
+ }
173
+
167
174
  /**
168
175
  * Deep copy version of Object.assign using recursion.
169
176
  * There are some assumptions here. It's for internal use and we don't need verbose errors
@@ -0,0 +1,15 @@
1
+ import { ERROR_INVALID_CONFIG_PARAM } from '../../logger/constants';
2
+ import { ILogger } from '../../logger/types';
3
+ import { CONSENT_DECLINED, CONSENT_GRANTED, CONSENT_UNKNOWN } from '../constants';
4
+ import { stringToUpperCase } from '../lang';
5
+
6
+ const userConsentValues = [CONSENT_DECLINED, CONSENT_GRANTED, CONSENT_UNKNOWN];
7
+
8
+ export function validateConsent({ userConsent, log }: { userConsent: any, log: ILogger }) {
9
+ userConsent = stringToUpperCase(userConsent);
10
+
11
+ if (userConsentValues.indexOf(userConsent) > -1) return userConsent;
12
+
13
+ log.error(ERROR_INVALID_CONFIG_PARAM, ['userConsent', userConsentValues, CONSENT_GRANTED]);
14
+ return CONSENT_GRANTED;
15
+ }
@@ -1,14 +1,14 @@
1
- import { ERROR_INVALID_IMPRESSIONS_MODE } from '../../logger/constants';
1
+ import { ERROR_INVALID_CONFIG_PARAM } from '../../logger/constants';
2
2
  import { ILogger } from '../../logger/types';
3
3
  import { SplitIO } from '../../types';
4
4
  import { DEBUG, OPTIMIZED } from '../constants';
5
+ import { stringToUpperCase } from '../lang';
5
6
 
6
- export function validImpressionsMode(log: ILogger, impressionsMode: string): SplitIO.ImpressionsMode {
7
- impressionsMode = impressionsMode.toUpperCase();
8
- if ([DEBUG, OPTIMIZED].indexOf(impressionsMode) === -1) {
9
- log.error(ERROR_INVALID_IMPRESSIONS_MODE, [[DEBUG, OPTIMIZED], OPTIMIZED]);
10
- impressionsMode = OPTIMIZED;
11
- }
7
+ export function validImpressionsMode(log: ILogger, impressionsMode: any): SplitIO.ImpressionsMode {
8
+ impressionsMode = stringToUpperCase(impressionsMode);
12
9
 
13
- return impressionsMode as SplitIO.ImpressionsMode;
10
+ if ([DEBUG, OPTIMIZED].indexOf(impressionsMode) > -1) return impressionsMode;
11
+
12
+ log.error(ERROR_INVALID_CONFIG_PARAM, ['impressionsMode', [DEBUG, OPTIMIZED], OPTIMIZED]);
13
+ return OPTIMIZED;
14
14
  }
@@ -38,6 +38,8 @@ const base = {
38
38
  eventsPushRate: 60,
39
39
  // how many events will be queued before flushing
40
40
  eventsQueueSize: 500,
41
+ // how many impressions will be queued before flushing
42
+ impressionsQueueSize: 30000,
41
43
  // backoff base seconds to wait before re attempting to connect to push notifications
42
44
  pushRetryBackoffBase: 1,
43
45
  },
@@ -95,7 +97,7 @@ function fromSecondsToMillis(n: number) {
95
97
  */
96
98
  export function settingsValidation(config: unknown, validationParams: ISettingsValidationParams) {
97
99
 
98
- const { defaults, runtime, storage, integrations, logger, localhost } = validationParams;
100
+ const { defaults, runtime, storage, integrations, logger, localhost, consent } = validationParams;
99
101
 
100
102
  // creates a settings object merging base, defaults and config objects.
101
103
  const withDefaults = merge({}, base, defaults, config) as ISettings;
@@ -159,5 +161,9 @@ export function settingsValidation(config: unknown, validationParams: ISettingsV
159
161
  // ensure a valid impressionsMode
160
162
  withDefaults.sync.impressionsMode = validImpressionsMode(log, withDefaults.sync.impressionsMode);
161
163
 
164
+ // ensure a valid user consent value
165
+ // @ts-ignore, modify readonly prop
166
+ withDefaults.userConsent = consent(withDefaults);
167
+
162
168
  return withDefaults;
163
169
  }
@@ -0,0 +1,9 @@
1
+ import { ISettings } from '../../types';
2
+
3
+ // For client-side SDKs, machine IP and Hostname are not captured and sent to Split backend.
4
+ export function validateRuntime(): ISettings['runtime'] {
5
+ return {
6
+ ip: false,
7
+ hostname: false
8
+ };
9
+ }
@@ -20,4 +20,6 @@ export interface ISettingsValidationParams {
20
20
  logger: (settings: ISettings) => ISettings['log'],
21
21
  /** Localhost mode validator (`settings.sync.localhostMode`) */
22
22
  localhost?: (settings: ISettings) => ISettings['sync']['localhostMode'],
23
+ /** User consent validator (`settings.userConsent`) */
24
+ consent: (settings: ISettings) => ISettings['userConsent'],
23
25
  }
@@ -60,13 +60,15 @@ export declare const STREAMING_RECONNECT = 111;
60
60
  export declare const STREAMING_CONNECTING = 112;
61
61
  export declare const STREAMING_DISABLED = 113;
62
62
  export declare const STREAMING_DISCONNECTING = 114;
63
- export declare const SUBMITTERS_PUSH_FULL_EVENTS_QUEUE = 115;
63
+ export declare const SUBMITTERS_PUSH_FULL_QUEUE = 115;
64
64
  export declare const SUBMITTERS_PUSH = 116;
65
65
  export declare const SYNC_START_POLLING = 117;
66
66
  export declare const SYNC_CONTINUE_POLLING = 118;
67
67
  export declare const SYNC_STOP_POLLING = 119;
68
68
  export declare const EVENTS_TRACKER_SUCCESS = 120;
69
69
  export declare const IMPRESSIONS_TRACKER_SUCCESS = 121;
70
+ export declare const USER_CONSENT_UPDATED = 122;
71
+ export declare const USER_CONSENT_NOT_UPDATED = 123;
70
72
  export declare const ENGINE_VALUE_INVALID = 200;
71
73
  export declare const ENGINE_VALUE_NO_ATTRIBUTES = 201;
72
74
  export declare const CLIENT_NO_LISTENER = 202;
@@ -112,10 +114,11 @@ export declare const ERROR_INVALID_KEY_OBJECT = 317;
112
114
  export declare const ERROR_INVALID = 318;
113
115
  export declare const ERROR_EMPTY = 319;
114
116
  export declare const ERROR_EMPTY_ARRAY = 320;
115
- export declare const ERROR_INVALID_IMPRESSIONS_MODE = 321;
117
+ export declare const ERROR_INVALID_CONFIG_PARAM = 321;
116
118
  export declare const ERROR_HTTP = 322;
117
119
  export declare const ERROR_LOCALHOST_MODULE_REQUIRED = 323;
118
120
  export declare const ERROR_STORAGE_INVALID = 324;
121
+ export declare const ERROR_NOT_BOOLEAN = 325;
119
122
  export declare const LOG_PREFIX_SETTINGS = "settings";
120
123
  export declare const LOG_PREFIX_INSTANTIATION = "Factory instantiation";
121
124
  export declare const LOG_PREFIX_ENGINE = "engine";
@@ -39,4 +39,5 @@ export interface ISdkFactoryParams {
39
39
  impressionListener?: SplitIO.IImpressionListener;
40
40
  integrationsManagerFactory?: (params: IIntegrationFactoryParams) => IIntegrationManager | undefined;
41
41
  impressionsObserverFactory?: () => IImpressionObserver;
42
+ extraProps?: (settings: ISettings, syncManager?: ISyncManager) => object;
42
43
  }
@@ -0,0 +1,6 @@
1
+ import { ISyncManager } from '../sync/types';
2
+ import { ISettings } from '../types';
3
+ export declare function userConsentProps(settings: ISettings, syncManager?: ISyncManager): {
4
+ setUserConsent(consent: unknown): boolean;
5
+ getUserConsent(): import("../types").ConsentStatus | undefined;
6
+ };
@@ -8,6 +8,8 @@ export declare class KeyBuilderCS extends KeyBuilder {
8
8
  */
9
9
  buildSegmentNameKey(segmentName: string): string;
10
10
  extractSegmentName(builtSegmentKeyName: string): string | undefined;
11
+ buildOldSegmentNameKey(segmentName: string): string;
12
+ extractOldSegmentKey(builtSegmentKeyName: string): string | undefined;
11
13
  buildLastUpdatedKey(): string;
12
14
  isSplitsCacheKey(key: string): boolean;
13
15
  buildSplitsFilterQueryKey(): string;
@@ -1,7 +1,16 @@
1
1
  import { IImpressionsCacheSync } from '../types';
2
2
  import { ImpressionDTO } from '../../types';
3
3
  export declare class ImpressionsCacheInMemory implements IImpressionsCacheSync {
4
+ private onFullQueue?;
5
+ private readonly maxQueue;
4
6
  private queue;
7
+ /**
8
+ *
9
+ * @param impressionsQueueSize number of queued impressions to call onFullQueueCb.
10
+ * Default value is 0, that means no maximum value, in case we want to avoid this being triggered.
11
+ */
12
+ constructor(impressionsQueueSize?: number);
13
+ setOnFullQueueCb(cb: () => void): void;
5
14
  /**
6
15
  * Store impressions in sequential order
7
16
  */
@@ -269,6 +269,7 @@ export interface IRecorderCacheProducerSync<T> {
269
269
  }
270
270
  export interface IImpressionsCacheSync extends IImpressionsCacheBase, IRecorderCacheProducerSync<ImpressionDTO[]> {
271
271
  track(data: ImpressionDTO[]): void;
272
+ setOnFullQueueCb(cb: () => void): void;
272
273
  }
273
274
  export interface IEventsCacheSync extends IEventsCacheBase, IRecorderCacheProducerSync<SplitIO.EventData[]> {
274
275
  track(data: SplitIO.EventData, size?: number): boolean;
@@ -335,6 +336,7 @@ export declare type IStorageAsync = IStorageBase<ISplitsCacheAsync, ISegmentsCac
335
336
  export declare type DataLoader = (storage: IStorageSync, matchingKey: string) => void;
336
337
  export interface IStorageFactoryParams {
337
338
  log: ILogger;
339
+ impressionsQueueSize?: number;
338
340
  eventsQueueSize?: number;
339
341
  optimize?: boolean;
340
342
  matchingKey?: string;
@@ -3,6 +3,7 @@ import { IPlatform } from '../sdkFactory/types';
3
3
  import { ISplitApi } from '../services/types';
4
4
  import { IStorageSync } from '../storages/types';
5
5
  import { ISettings } from '../types';
6
+ import { IPollingManager } from './polling/types';
6
7
  import { IPushManager } from './streaming/types';
7
8
  export interface ITask<Input extends any[] = []> {
8
9
  /**
@@ -39,6 +40,8 @@ export interface ITimeTracker {
39
40
  export interface ISyncManager extends ITask {
40
41
  flush(): Promise<any>;
41
42
  pushManager?: IPushManager;
43
+ pollingManager?: IPollingManager;
44
+ submitter?: ISyncTask;
42
45
  }
43
46
  export interface ISyncManagerCS extends ISyncManager {
44
47
  shared(matchingKey: string, readinessManager: IReadinessManager, storage: IStorageSync): ISyncManager | undefined;
package/types/types.d.ts CHANGED
@@ -48,6 +48,11 @@ declare type EventConsts = {
48
48
  * @typedef {string} SDKMode
49
49
  */
50
50
  export declare type SDKMode = 'standalone' | 'consumer' | 'localhost' | 'consumer_partial';
51
+ /**
52
+ * User consent status.
53
+ * @typedef {string} ConsentStatus
54
+ */
55
+ export declare type ConsentStatus = 'GRANTED' | 'DECLINED' | 'UNKNOWN';
51
56
  /**
52
57
  * Settings interface. This is a representation of the settings the SDK expose, that's why
53
58
  * most of it's props are readonly. Only features should be rewritten when localhost mode is active.
@@ -67,6 +72,7 @@ export interface ISettings {
67
72
  readonly scheduler: {
68
73
  featuresRefreshRate: number;
69
74
  impressionsRefreshRate: number;
75
+ impressionsQueueSize: number;
70
76
  metricsRefreshRate: number;
71
77
  segmentsRefreshRate: number;
72
78
  offlineRefreshRate: number;
@@ -104,6 +110,7 @@ export interface ISettings {
104
110
  };
105
111
  readonly log: ILogger;
106
112
  readonly impressionListener?: unknown;
113
+ readonly userConsent?: ConsentStatus;
107
114
  }
108
115
  /**
109
116
  * Log levels.
@@ -249,6 +256,13 @@ interface INodeBasicSettings extends ISharedSettings {
249
256
  * @default 300
250
257
  */
251
258
  impressionsRefreshRate?: number;
259
+ /**
260
+ * The maximum number of impression items we want to queue. If we queue more values, it will trigger a flush and reset the timer.
261
+ * If you use a 0 here, the queue will have no maximum size.
262
+ * @property {number} impressionsQueueSize
263
+ * @default 30000
264
+ */
265
+ impressionsQueueSize?: number;
252
266
  /**
253
267
  * The SDK sends diagnostic metrics to Split servers. This parameters controls this metric flush period in seconds.
254
268
  * @property {number} metricsRefreshRate
@@ -766,6 +780,13 @@ export declare namespace SplitIO {
766
780
  * @default 60
767
781
  */
768
782
  impressionsRefreshRate?: number;
783
+ /**
784
+ * The maximum number of impression items we want to queue. If we queue more values, it will trigger a flush and reset the timer.
785
+ * If you use a 0 here, the queue will have no maximum size.
786
+ * @property {number} impressionsQueueSize
787
+ * @default 30000
788
+ */
789
+ impressionsQueueSize?: number;
769
790
  /**
770
791
  * The SDK sends diagnostic metrics to Split servers. This parameters controls this metric flush period in seconds.
771
792
  * @property {number} metricsRefreshRate
@@ -0,0 +1,2 @@
1
+ import { ISettings } from '../types';
2
+ export declare function isConsentGranted(settings: ISettings): boolean;
@@ -20,3 +20,6 @@ export declare const STORAGE_MEMORY: StorageType;
20
20
  export declare const STORAGE_LOCALSTORAGE: StorageType;
21
21
  export declare const STORAGE_REDIS: StorageType;
22
22
  export declare const STORAGE_PLUGGABLE: StorageType;
23
+ export declare const CONSENT_GRANTED = "GRANTED";
24
+ export declare const CONSENT_DECLINED = "DECLINED";
25
+ export declare const CONSENT_UNKNOWN = "UNKNOWN";
@@ -62,6 +62,10 @@ export declare function isObject(obj: any): boolean;
62
62
  * Checks if a given value is a string.
63
63
  */
64
64
  export declare function isString(val: any): val is string;
65
+ /**
66
+ * String sanitizer. Returns the provided value converted to uppercase if it is a string.
67
+ */
68
+ export declare function stringToUpperCase(val: any): any;
65
69
  /**
66
70
  * Deep copy version of Object.assign using recursion.
67
71
  * There are some assumptions here. It's for internal use and we don't need verbose errors
@@ -0,0 +1,5 @@
1
+ import { ILogger } from '../../logger/types';
2
+ export declare function validateConsent({ userConsent, log }: {
3
+ userConsent: any;
4
+ log: ILogger;
5
+ }): any;
@@ -1,3 +1,3 @@
1
1
  import { ILogger } from '../../logger/types';
2
2
  import { SplitIO } from '../../types';
3
- export declare function validImpressionsMode(log: ILogger, impressionsMode: string): SplitIO.ImpressionsMode;
3
+ export declare function validImpressionsMode(log: ILogger, impressionsMode: any): SplitIO.ImpressionsMode;
@@ -0,0 +1,2 @@
1
+ import { ISettings } from '../../types';
2
+ export declare function validateRuntime(): ISettings['runtime'];
@@ -23,4 +23,6 @@ export interface ISettingsValidationParams {
23
23
  logger: (settings: ISettings) => ISettings['log'];
24
24
  /** Localhost mode validator (`settings.sync.localhostMode`) */
25
25
  localhost?: (settings: ISettings) => ISettings['sync']['localhostMode'];
26
+ /** User consent validator (`settings.userConsent`) */
27
+ consent: (settings: ISettings) => ISettings['userConsent'];
26
28
  }
@@ -0,0 +1,5 @@
1
+ import { ILogger } from '../../logger/types';
2
+ export declare function validateUserConsent({ userConsent, log }: {
3
+ userConsent: any;
4
+ log: ILogger;
5
+ }): any;
@@ -1,22 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.validateRuntime = void 0;
4
- var tslib_1 = require("tslib");
5
- var os_1 = (0, tslib_1.__importDefault)(require("os"));
6
- var ip_1 = (0, tslib_1.__importDefault)(require("ip"));
7
- var constants_1 = require("../../constants");
8
- function validateRuntime(settings) {
9
- var isIPAddressesEnabled = settings.core.IPAddressesEnabled === true;
10
- var isConsumerMode = settings.mode === constants_1.CONSUMER_MODE;
11
- // If the values are not available, default to false (for standalone) or "unknown" (for consumer mode, to be used on Redis keys)
12
- var ip = ip_1.default.address() || (isConsumerMode ? constants_1.UNKNOWN : false);
13
- var hostname = os_1.default.hostname() || (isConsumerMode ? constants_1.UNKNOWN : false);
14
- if (!isIPAddressesEnabled) { // If IPAddresses setting is not enabled, set as false (for standalone) or "NA" (for consumer mode, to be used on Redis keys)
15
- ip = hostname = isConsumerMode ? constants_1.NA : false;
16
- }
17
- return {
18
- ip: ip,
19
- hostname: hostname
20
- };
21
- }
22
- exports.validateRuntime = validateRuntime;