@splitsoftware/splitio-commons 1.3.1-rc.0 → 1.3.2-rc.0

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 (212) hide show
  1. package/CHANGES.txt +2 -1
  2. package/cjs/consent/sdkUserConsent.js +1 -1
  3. package/cjs/listeners/browser.js +5 -4
  4. package/cjs/logger/constants.js +2 -1
  5. package/cjs/logger/messages/error.js +2 -1
  6. package/cjs/logger/messages/info.js +3 -3
  7. package/cjs/logger/messages/warn.js +2 -2
  8. package/cjs/sdkClient/client.js +17 -3
  9. package/cjs/sdkClient/sdkClient.js +4 -1
  10. package/cjs/sdkFactory/index.js +16 -19
  11. package/cjs/services/splitApi.js +15 -14
  12. package/cjs/services/splitHttpClient.js +15 -13
  13. package/cjs/storages/AbstractSegmentsCacheSync.js +0 -5
  14. package/cjs/storages/KeyBuilderSS.js +12 -19
  15. package/cjs/storages/findLatencyIndex.js +11 -6
  16. package/cjs/storages/inLocalStorage/MySegmentsCacheInLocal.js +13 -1
  17. package/cjs/storages/inLocalStorage/index.js +4 -1
  18. package/cjs/storages/inMemory/InMemoryStorage.js +2 -0
  19. package/cjs/storages/inMemory/InMemoryStorageCS.js +3 -0
  20. package/cjs/storages/inMemory/MySegmentsCacheInMemory.js +6 -0
  21. package/cjs/storages/inMemory/SegmentsCacheInMemory.js +6 -0
  22. package/cjs/storages/inMemory/TelemetryCacheInMemory.js +165 -0
  23. package/cjs/storages/inRedis/TelemetryCacheInRedis.js +29 -0
  24. package/cjs/storages/inRedis/index.js +2 -4
  25. package/cjs/storages/pluggable/TelemetryCachePluggable.js +27 -0
  26. package/cjs/storages/pluggable/index.js +2 -1
  27. package/cjs/sync/polling/updaters/mySegmentsUpdater.js +0 -3
  28. package/cjs/sync/polling/updaters/segmentChangesUpdater.js +1 -8
  29. package/cjs/sync/polling/updaters/splitChangesUpdater.js +1 -4
  30. package/cjs/sync/streaming/SSEHandler/NotificationKeeper.js +20 -13
  31. package/cjs/sync/streaming/SSEHandler/index.js +21 -15
  32. package/cjs/sync/streaming/pushManager.js +7 -4
  33. package/cjs/sync/submitters/eventsSubmitter.js +28 -0
  34. package/cjs/sync/submitters/{impressionCountsSyncTask.js → impressionCountsSubmitter.js} +10 -7
  35. package/cjs/sync/submitters/{impressionsSyncTask.js → impressionsSubmitter.js} +8 -8
  36. package/cjs/sync/submitters/{submitterSyncTask.js → submitter.js} +34 -13
  37. package/cjs/sync/submitters/submitterManager.js +12 -10
  38. package/cjs/sync/submitters/telemetrySubmitter.js +128 -0
  39. package/cjs/sync/syncManagerOnline.js +6 -2
  40. package/cjs/trackers/eventTracker.js +5 -1
  41. package/cjs/trackers/impressionsTracker.js +11 -3
  42. package/cjs/trackers/telemetryTracker.js +63 -0
  43. package/cjs/utils/constants/index.js +40 -1
  44. package/cjs/utils/inputValidation/apiKey.js +12 -11
  45. package/cjs/utils/settingsValidation/index.js +20 -6
  46. package/cjs/utils/settingsValidation/url.js +4 -0
  47. package/cjs/utils/timeTracker/index.js +1 -0
  48. package/cjs/utils/timeTracker/timer.js +2 -2
  49. package/esm/consent/sdkUserConsent.js +1 -1
  50. package/esm/listeners/browser.js +3 -2
  51. package/esm/logger/constants.js +1 -0
  52. package/esm/logger/messages/error.js +2 -1
  53. package/esm/logger/messages/info.js +3 -3
  54. package/esm/logger/messages/warn.js +2 -2
  55. package/esm/sdkClient/client.js +18 -4
  56. package/esm/sdkClient/sdkClient.js +4 -1
  57. package/esm/sdkFactory/index.js +16 -19
  58. package/esm/services/splitApi.js +15 -14
  59. package/esm/services/splitHttpClient.js +15 -13
  60. package/esm/storages/AbstractSegmentsCacheSync.js +0 -5
  61. package/esm/storages/KeyBuilderSS.js +12 -19
  62. package/esm/storages/findLatencyIndex.js +11 -6
  63. package/esm/storages/inLocalStorage/MySegmentsCacheInLocal.js +13 -1
  64. package/esm/storages/inLocalStorage/index.js +5 -2
  65. package/esm/storages/inMemory/InMemoryStorage.js +3 -1
  66. package/esm/storages/inMemory/InMemoryStorageCS.js +4 -1
  67. package/esm/storages/inMemory/MySegmentsCacheInMemory.js +6 -0
  68. package/esm/storages/inMemory/SegmentsCacheInMemory.js +6 -0
  69. package/esm/storages/inMemory/TelemetryCacheInMemory.js +161 -0
  70. package/esm/storages/inRedis/TelemetryCacheInRedis.js +26 -0
  71. package/esm/storages/inRedis/index.js +2 -4
  72. package/esm/storages/pluggable/TelemetryCachePluggable.js +24 -0
  73. package/esm/storages/pluggable/index.js +2 -1
  74. package/esm/sync/polling/updaters/mySegmentsUpdater.js +0 -3
  75. package/esm/sync/polling/updaters/segmentChangesUpdater.js +1 -8
  76. package/esm/sync/polling/updaters/splitChangesUpdater.js +1 -4
  77. package/esm/sync/streaming/SSEHandler/NotificationKeeper.js +8 -1
  78. package/esm/sync/streaming/SSEHandler/index.js +21 -15
  79. package/esm/sync/streaming/pushManager.js +7 -4
  80. package/esm/sync/submitters/eventsSubmitter.js +24 -0
  81. package/esm/sync/submitters/{impressionCountsSyncTask.js → impressionCountsSubmitter.js} +8 -5
  82. package/esm/sync/submitters/{impressionsSyncTask.js → impressionsSubmitter.js} +6 -6
  83. package/esm/sync/submitters/submitter.js +60 -0
  84. package/esm/sync/submitters/submitterManager.js +12 -10
  85. package/esm/sync/submitters/telemetrySubmitter.js +122 -0
  86. package/esm/sync/syncManagerOnline.js +6 -2
  87. package/esm/trackers/eventTracker.js +6 -2
  88. package/esm/trackers/impressionsTracker.js +12 -4
  89. package/esm/trackers/telemetryTracker.js +59 -0
  90. package/esm/utils/constants/index.js +38 -0
  91. package/esm/utils/inputValidation/apiKey.js +2 -1
  92. package/esm/utils/settingsValidation/index.js +18 -4
  93. package/esm/utils/settingsValidation/url.js +4 -0
  94. package/esm/utils/timeTracker/index.js +1 -0
  95. package/esm/utils/timeTracker/timer.js +2 -2
  96. package/package.json +1 -1
  97. package/src/consent/sdkUserConsent.ts +1 -1
  98. package/src/listeners/browser.ts +3 -2
  99. package/src/logger/constants.ts +1 -0
  100. package/src/logger/messages/error.ts +2 -1
  101. package/src/logger/messages/info.ts +3 -3
  102. package/src/logger/messages/warn.ts +2 -2
  103. package/src/sdkClient/client.ts +23 -4
  104. package/src/sdkClient/sdkClient.ts +4 -1
  105. package/src/sdkFactory/index.ts +22 -24
  106. package/src/sdkFactory/types.ts +31 -14
  107. package/src/services/splitApi.ts +17 -14
  108. package/src/services/splitHttpClient.ts +15 -14
  109. package/src/services/types.ts +7 -5
  110. package/src/storages/AbstractSegmentsCacheSync.ts +8 -3
  111. package/src/storages/KeyBuilderSS.ts +13 -50
  112. package/src/storages/findLatencyIndex.ts +12 -3
  113. package/src/storages/inLocalStorage/MySegmentsCacheInLocal.ts +13 -1
  114. package/src/storages/inLocalStorage/index.ts +5 -2
  115. package/src/storages/inMemory/InMemoryStorage.ts +3 -1
  116. package/src/storages/inMemory/InMemoryStorageCS.ts +4 -1
  117. package/src/storages/inMemory/MySegmentsCacheInMemory.ts +8 -0
  118. package/src/storages/inMemory/SegmentsCacheInMemory.ts +6 -0
  119. package/src/storages/inMemory/TelemetryCacheInMemory.ts +210 -0
  120. package/src/storages/inRedis/TelemetryCacheInRedis.ts +29 -0
  121. package/src/storages/inRedis/index.ts +2 -4
  122. package/src/storages/pluggable/TelemetryCachePluggable.ts +26 -0
  123. package/src/storages/pluggable/index.ts +2 -1
  124. package/src/storages/types.ts +84 -32
  125. package/src/sync/offline/syncManagerOffline.ts +4 -3
  126. package/src/sync/polling/pollingManagerCS.ts +2 -2
  127. package/src/sync/polling/pollingManagerSS.ts +2 -2
  128. package/src/sync/polling/updaters/mySegmentsUpdater.ts +0 -4
  129. package/src/sync/polling/updaters/segmentChangesUpdater.ts +2 -10
  130. package/src/sync/polling/updaters/splitChangesUpdater.ts +1 -5
  131. package/src/sync/streaming/SSEHandler/NotificationKeeper.ts +11 -1
  132. package/src/sync/streaming/SSEHandler/index.ts +21 -14
  133. package/src/sync/streaming/pushManager.ts +11 -7
  134. package/src/sync/submitters/eventsSubmitter.ts +35 -0
  135. package/src/sync/submitters/{impressionCountsSyncTask.ts → impressionCountsSubmitter.ts} +15 -15
  136. package/src/sync/submitters/{impressionsSyncTask.ts → impressionsSubmitter.ts} +12 -16
  137. package/src/sync/submitters/{submitterSyncTask.ts → submitter.ts} +33 -15
  138. package/src/sync/submitters/submitterManager.ts +14 -11
  139. package/src/sync/submitters/telemetrySubmitter.ts +143 -0
  140. package/src/sync/submitters/types.ts +123 -0
  141. package/src/sync/syncManagerOnline.ts +13 -7
  142. package/src/sync/types.ts +0 -15
  143. package/src/trackers/eventTracker.ts +7 -3
  144. package/src/trackers/impressionsTracker.ts +13 -5
  145. package/src/trackers/telemetryTracker.ts +63 -0
  146. package/src/trackers/types.ts +24 -0
  147. package/src/types.ts +35 -6
  148. package/src/utils/constants/index.ts +45 -0
  149. package/src/utils/inputValidation/apiKey.ts +2 -1
  150. package/src/utils/settingsValidation/index.ts +18 -4
  151. package/src/utils/settingsValidation/url.ts +4 -0
  152. package/src/utils/timeTracker/index.ts +1 -1
  153. package/src/utils/timeTracker/timer.ts +3 -3
  154. package/types/logger/constants.d.ts +1 -0
  155. package/types/sdkFactory/types.d.ts +28 -13
  156. package/types/services/splitApi.d.ts +2 -1
  157. package/types/services/types.d.ts +8 -5
  158. package/types/storages/AbstractSegmentsCacheSync.d.ts +7 -3
  159. package/types/storages/KeyBuilderSS.d.ts +3 -3
  160. package/types/storages/findLatencyIndex.d.ts +7 -1
  161. package/types/storages/inLocalStorage/MySegmentsCacheInLocal.d.ts +2 -0
  162. package/types/storages/inMemory/MySegmentsCacheInMemory.d.ts +2 -0
  163. package/types/storages/inMemory/SegmentsCacheInMemory.d.ts +1 -0
  164. package/types/storages/inMemory/TelemetryCacheInMemory.d.ts +6 -2
  165. package/types/storages/inRedis/TelemetryCacheInRedis.d.ts +6 -8
  166. package/types/storages/pluggable/TelemetryCachePluggable.d.ts +4 -6
  167. package/types/storages/types.d.ts +71 -22
  168. package/types/sync/offline/syncManagerOffline.d.ts +3 -2
  169. package/types/sync/polling/pollingManagerCS.d.ts +2 -2
  170. package/types/sync/polling/pollingManagerSS.d.ts +2 -2
  171. package/types/sync/streaming/SSEHandler/NotificationKeeper.d.ts +2 -1
  172. package/types/sync/streaming/SSEHandler/index.d.ts +2 -1
  173. package/types/sync/streaming/pushManager.d.ts +2 -2
  174. package/types/sync/submitters/eventsSubmitter.d.ts +5 -0
  175. package/types/sync/submitters/impressionCountsSubmitter.d.ts +10 -0
  176. package/types/sync/submitters/impressionsSubmitter.d.ts +11 -0
  177. package/types/sync/submitters/submitter.d.ts +12 -0
  178. package/types/sync/submitters/submitterManager.d.ts +2 -2
  179. package/types/sync/submitters/telemetrySubmitter.d.ts +24 -0
  180. package/types/sync/submitters/telemetrySyncTask.d.ts +0 -27
  181. package/types/sync/submitters/types.d.ts +107 -0
  182. package/types/sync/syncManagerOnline.d.ts +3 -2
  183. package/types/sync/types.d.ts +0 -13
  184. package/types/trackers/eventTracker.d.ts +2 -2
  185. package/types/trackers/impressionsTracker.d.ts +2 -2
  186. package/types/trackers/telemetryTracker.d.ts +3 -0
  187. package/types/trackers/types.d.ts +22 -0
  188. package/types/types.d.ts +33 -4
  189. package/types/utils/constants/index.d.ts +37 -0
  190. package/types/utils/inputValidation/apiKey.d.ts +1 -0
  191. package/types/utils/settingsValidation/index.d.ts +40 -0
  192. package/types/utils/timeTracker/index.d.ts +1 -1
  193. package/types/utils/timeTracker/timer.d.ts +1 -1
  194. package/cjs/storages/inMemory/CountsCacheInMemory.js +0 -38
  195. package/cjs/storages/inMemory/LatenciesCacheInMemory.js +0 -43
  196. package/cjs/storages/inRedis/CountsCacheInRedis.js +0 -16
  197. package/cjs/storages/inRedis/LatenciesCacheInRedis.js +0 -18
  198. package/cjs/sync/submitters/eventsSyncTask.js +0 -44
  199. package/cjs/sync/submitters/metricsSyncTask.js +0 -31
  200. package/esm/storages/inMemory/CountsCacheInMemory.js +0 -35
  201. package/esm/storages/inMemory/LatenciesCacheInMemory.js +0 -40
  202. package/esm/storages/inRedis/CountsCacheInRedis.js +0 -13
  203. package/esm/storages/inRedis/LatenciesCacheInRedis.js +0 -15
  204. package/esm/sync/submitters/eventsSyncTask.js +0 -40
  205. package/esm/sync/submitters/metricsSyncTask.js +0 -26
  206. package/esm/sync/submitters/submitterSyncTask.js +0 -40
  207. package/src/storages/inMemory/CountsCacheInMemory.ts +0 -37
  208. package/src/storages/inMemory/LatenciesCacheInMemory.ts +0 -45
  209. package/src/storages/inRedis/CountsCacheInRedis.ts +0 -20
  210. package/src/storages/inRedis/LatenciesCacheInRedis.ts +0 -23
  211. package/src/sync/submitters/eventsSyncTask.ts +0 -57
  212. package/src/sync/submitters/metricsSyncTask.ts +0 -49
@@ -6,18 +6,8 @@ import { ISseEventHandler } from '../SSEClient/types';
6
6
  import { INotificationError, INotificationMessage } from './types';
7
7
  import { ILogger } from '../../../logger/types';
8
8
  import { STREAMING_PARSING_ERROR_FAILS, ERROR_STREAMING_SSE, STREAMING_PARSING_MESSAGE_FAILS, STREAMING_NEW_MESSAGE } from '../../../logger/constants';
9
-
10
- function isRetryableError(error: INotificationError) {
11
- if (error.parsedData && error.parsedData.code) {
12
- const code = error.parsedData.code;
13
- // 401 errors due to invalid or expired token (e.g., if refresh token coudn't be executed)
14
- if (40140 <= code && code <= 40149) return true;
15
- // Others 4XX errors (e.g., bad request from the SDK)
16
- if (40000 <= code && code <= 49999) return false;
17
- }
18
- // network errors or 5XX HTTP errors
19
- return true;
20
- }
9
+ import { ABLY_ERROR, NON_REQUESTED, SSE_CONNECTION_ERROR } from '../../../utils/constants';
10
+ import { ITelemetryTracker } from '../../../trackers/types';
21
11
 
22
12
  /**
23
13
  * Factory for SSEHandler, which processes SSEClient messages and emits the corresponding push events.
@@ -25,9 +15,26 @@ function isRetryableError(error: INotificationError) {
25
15
  * @param log factory logger
26
16
  * @param pushEmitter emitter for events related to streaming support
27
17
  */
28
- export function SSEHandlerFactory(log: ILogger, pushEmitter: IPushEventEmitter): ISseEventHandler {
18
+ export function SSEHandlerFactory(log: ILogger, pushEmitter: IPushEventEmitter, telemetryTracker: ITelemetryTracker): ISseEventHandler {
29
19
 
30
- const notificationKeeper = notificationKeeperFactory(pushEmitter);
20
+ const notificationKeeper = notificationKeeperFactory(pushEmitter, telemetryTracker);
21
+
22
+ function isRetryableError(error: INotificationError): boolean {
23
+ if (error.parsedData && error.parsedData.code) {
24
+ // Ably error
25
+ const code = error.parsedData.code;
26
+ telemetryTracker.streamingEvent(ABLY_ERROR, code);
27
+
28
+ // 401 errors due to invalid or expired token (e.g., if refresh token coudn't be executed)
29
+ if (40140 <= code && code <= 40149) return true;
30
+ // Others 4XX errors (e.g., bad request from the SDK)
31
+ if (40000 <= code && code <= 49999) return false;
32
+ } else {
33
+ // network errors or 5XX HTTP errors
34
+ telemetryTracker.streamingEvent(SSE_CONNECTION_ERROR, NON_REQUESTED);
35
+ }
36
+ return true;
37
+ }
31
38
 
32
39
  return {
33
40
  handleOpen() {
@@ -18,7 +18,8 @@ import { isInBitmap, parseBitmap, parseKeyList } from './mySegmentsV2utils';
18
18
  import { ISet, _Set } from '../../utils/lang/sets';
19
19
  import { Hash64, hash64 } from '../../utils/murmur3/murmur3_64';
20
20
  import { IAuthTokenPushEnabled } from './AuthClient/types';
21
- import { ISyncManagerFactoryParams } from '../types';
21
+ import { TOKEN_REFRESH, AUTH_REJECTION } from '../../utils/constants';
22
+ import { ISdkFactoryContextSync } from '../../sdkFactory/types';
22
23
 
23
24
  /**
24
25
  * PushManager factory:
@@ -26,11 +27,11 @@ import { ISyncManagerFactoryParams } from '../types';
26
27
  * - for client-side, with support for multiple clients, if key is provided in settings
27
28
  */
28
29
  export function pushManagerFactory(
29
- params: ISyncManagerFactoryParams,
30
+ params: ISdkFactoryContextSync,
30
31
  pollingManager: IPollingManager,
31
32
  ): IPushManager | undefined {
32
33
 
33
- const { settings, storage, splitApi, readiness, platform } = params;
34
+ const { settings, storage, splitApi, readiness, platform, telemetryTracker } = params;
34
35
 
35
36
  // `userKey` is the matching key of main client in client-side SDK.
36
37
  // It can be used to check if running on client-side or server-side SDK.
@@ -49,7 +50,7 @@ export function pushManagerFactory(
49
50
 
50
51
  // init feedback loop
51
52
  const pushEmitter = new platform.EventEmitter() as IPushEventEmitter;
52
- const sseHandler = SSEHandlerFactory(log, pushEmitter);
53
+ const sseHandler = SSEHandlerFactory(log, pushEmitter, telemetryTracker);
53
54
  sseClient.setEventHandler(sseHandler);
54
55
 
55
56
  // init workers
@@ -101,6 +102,8 @@ export function pushManagerFactory(
101
102
  if (disconnected) return;
102
103
  sseClient.open(authData);
103
104
  }, connDelay * 1000);
105
+
106
+ telemetryTracker.streamingEvent(TOKEN_REFRESH, decodedToken.exp);
104
107
  }
105
108
 
106
109
  function connectPush() {
@@ -137,6 +140,7 @@ export function pushManagerFactory(
137
140
 
138
141
  // Handle 4XX HTTP errors: 401 (invalid API Key) or 400 (using incorrect API Key, i.e., client-side API Key on server-side)
139
142
  if (error.statusCode >= 400 && error.statusCode < 500) {
143
+ telemetryTracker.streamingEvent(AUTH_REJECTION);
140
144
  pushEmitter.emit(PUSH_NONRETRYABLE_ERROR);
141
145
  return;
142
146
  }
@@ -179,7 +183,7 @@ export function pushManagerFactory(
179
183
  stopWorkers();
180
184
  });
181
185
 
182
- /** Fallbacking without retry due to: STREAMING_DISABLED control event, or 'pushEnabled: false', or non-recoverable SSE and Authentication errors */
186
+ /** Fallback to polling without retry due to: STREAMING_DISABLED control event, or 'pushEnabled: false', or non-recoverable SSE and Authentication errors */
183
187
 
184
188
  pushEmitter.on(PUSH_NONRETRYABLE_ERROR, function handleNonRetryableError() {
185
189
  disabled = true;
@@ -188,7 +192,7 @@ export function pushManagerFactory(
188
192
  pushEmitter.emit(PUSH_SUBSYSTEM_DOWN); // no harm if polling already
189
193
  });
190
194
 
191
- /** Fallbacking with retry due to recoverable SSE and Authentication errors */
195
+ /** Fallback to polling with retry due to recoverable SSE and Authentication errors */
192
196
 
193
197
  pushEmitter.on(PUSH_RETRYABLE_ERROR, function handleRetryableError() { // HTTP or network error in SSE connection
194
198
  // SSE connection is closed to avoid repeated errors due to retries
@@ -316,7 +320,7 @@ export function pushManagerFactory(
316
320
  },
317
321
 
318
322
  // true/false if start or stop was called last respectively
319
- isRunning(){
323
+ isRunning() {
320
324
  return disconnected === false;
321
325
  },
322
326
 
@@ -0,0 +1,35 @@
1
+ import { submitterFactory, firstPushWindowDecorator } from './submitter';
2
+ import { SUBMITTERS_PUSH_FULL_QUEUE } from '../../logger/constants';
3
+ import { ISdkFactoryContextSync } from '../../sdkFactory/types';
4
+
5
+ const DATA_NAME = 'events';
6
+
7
+ /**
8
+ * Submitter that periodically posts tracked events
9
+ */
10
+ export function eventsSubmitterFactory(params: ISdkFactoryContextSync) {
11
+
12
+ const {
13
+ settings: { log, scheduler: { eventsPushRate }, startup: { eventsFirstPushWindow } },
14
+ splitApi: { postEventsBulk },
15
+ storage: { events },
16
+ } = params;
17
+
18
+ // don't retry events.
19
+ let submitter = submitterFactory(log, postEventsBulk, events, eventsPushRate, DATA_NAME);
20
+
21
+ // Set a timer for the first push window of events.
22
+ if (eventsFirstPushWindow > 0) submitter = firstPushWindowDecorator(submitter, eventsFirstPushWindow);
23
+
24
+ // register events submitter to be executed when events cache is full
25
+ events.setOnFullQueueCb(() => {
26
+ if (submitter.isRunning()) {
27
+ log.info(SUBMITTERS_PUSH_FULL_QUEUE, [DATA_NAME]);
28
+ submitter.execute();
29
+ }
30
+ // If submitter is stopped (e.g., user consent declined or unknown, or app state offline), we don't send the data.
31
+ // Data will be sent when submitter is resumed.
32
+ });
33
+
34
+ return submitter;
35
+ }
@@ -1,9 +1,6 @@
1
- import { ISyncTask, ITimeTracker } from '../types';
2
- import { IPostTestImpressionsCount } from '../../services/types';
3
- import { IImpressionCountsCacheSync } from '../../storages/types';
4
- import { submitterSyncTaskFactory } from './submitterSyncTask';
1
+ import { ISdkFactoryContextSync } from '../../sdkFactory/types';
2
+ import { submitterFactory } from './submitter';
5
3
  import { ImpressionCountsPayload } from './types';
6
- import { ILogger } from '../../logger/types';
7
4
 
8
5
  /**
9
6
  * Converts `impressionCounts` data from cache into request payload.
@@ -32,15 +29,18 @@ export function fromImpressionCountsCollector(impressionsCount: Record<string, n
32
29
  const IMPRESSIONS_COUNT_RATE = 1800000; // 30 minutes
33
30
 
34
31
  /**
35
- * Sync task that periodically posts impression counts
32
+ * Submitter that periodically posts impression counts
36
33
  */
37
- export function impressionCountsSyncTaskFactory(
38
- log: ILogger,
39
- postTestImpressionsCount: IPostTestImpressionsCount,
40
- impressionCountsCache: IImpressionCountsCacheSync,
41
- latencyTracker?: ITimeTracker
42
- ): ISyncTask {
43
-
44
- // retry impressions counts only once.
45
- return submitterSyncTaskFactory(log, postTestImpressionsCount, impressionCountsCache, IMPRESSIONS_COUNT_RATE, 'impression counts', latencyTracker, fromImpressionCountsCollector, 1);
34
+ export function impressionCountsSubmitterFactory(params: ISdkFactoryContextSync) {
35
+
36
+ const {
37
+ settings: { log },
38
+ splitApi: { postTestImpressionsCount },
39
+ storage: { impressionCounts }
40
+ } = params;
41
+
42
+ if (impressionCounts) {
43
+ // retry impressions counts only once.
44
+ return submitterFactory(log, postTestImpressionsCount, impressionCounts, IMPRESSIONS_COUNT_RATE, 'impression counts', fromImpressionCountsCollector, 1);
45
+ }
46
46
  }
@@ -1,12 +1,9 @@
1
1
  import { groupBy, forOwn } from '../../utils/lang';
2
- import { ISyncTask, ITimeTracker } from '../types';
3
- import { IPostTestImpressionsBulk } from '../../services/types';
4
- import { IImpressionsCacheSync } from '../../storages/types';
5
2
  import { ImpressionDTO } from '../../types';
6
- import { submitterSyncTaskFactory } from './submitterSyncTask';
3
+ import { submitterFactory } from './submitter';
7
4
  import { ImpressionsPayload } from './types';
8
- import { ILogger } from '../../logger/types';
9
5
  import { SUBMITTERS_PUSH_FULL_QUEUE } from '../../logger/constants';
6
+ import { ISdkFactoryContextSync } from '../../sdkFactory/types';
10
7
 
11
8
  const DATA_NAME = 'impressions';
12
9
 
@@ -41,22 +38,21 @@ export function fromImpressionsCollector(sendLabels: boolean, data: ImpressionDT
41
38
  }
42
39
 
43
40
  /**
44
- * Sync task that periodically posts impressions data
41
+ * Submitter that periodically posts impressions data
45
42
  */
46
- export function impressionsSyncTaskFactory(
47
- log: ILogger,
48
- postTestImpressionsBulk: IPostTestImpressionsBulk,
49
- impressionsCache: IImpressionsCacheSync,
50
- impressionsRefreshRate: number,
51
- sendLabels = false,
52
- latencyTracker?: ITimeTracker,
53
- ): ISyncTask {
43
+ export function impressionsSubmitterFactory(params: ISdkFactoryContextSync) {
44
+
45
+ const {
46
+ settings: { log, scheduler: { impressionsRefreshRate }, core: { labelsEnabled } },
47
+ splitApi: { postTestImpressionsBulk },
48
+ storage: { impressions }
49
+ } = params;
54
50
 
55
51
  // retry impressions only once.
56
- const syncTask = submitterSyncTaskFactory(log, postTestImpressionsBulk, impressionsCache, impressionsRefreshRate, DATA_NAME, latencyTracker, fromImpressionsCollector.bind(undefined, sendLabels), 1);
52
+ const syncTask = submitterFactory(log, postTestImpressionsBulk, impressions, impressionsRefreshRate, DATA_NAME, fromImpressionsCollector.bind(undefined, labelsEnabled), 1);
57
53
 
58
54
  // register impressions submitter to be executed when impressions cache is full
59
- impressionsCache.setOnFullQueueCb(() => {
55
+ impressions.setOnFullQueueCb(() => {
60
56
  if (syncTask.isRunning()) {
61
57
  log.info(SUBMITTERS_PUSH_FULL_QUEUE, [DATA_NAME]);
62
58
  syncTask.execute();
@@ -1,20 +1,19 @@
1
1
  import { syncTaskFactory } from '../syncTask';
2
- import { ISyncTask, ITimeTracker } from '../types';
2
+ import { ISyncTask } from '../types';
3
3
  import { IRecorderCacheProducerSync } from '../../storages/types';
4
4
  import { ILogger } from '../../logger/types';
5
5
  import { SUBMITTERS_PUSH, SUBMITTERS_PUSH_FAILS, SUBMITTERS_PUSH_RETRY } from '../../logger/constants';
6
6
  import { IResponse } from '../../services/types';
7
7
 
8
8
  /**
9
- * Base function to create submitter sync tasks, such as ImpressionsSyncTask and EventsSyncTask
9
+ * Base function to create submitters, such as ImpressionsSubmitter and EventsSubmitter
10
10
  */
11
- export function submitterSyncTaskFactory<TState extends { length?: number }>(
11
+ export function submitterFactory<TState>(
12
12
  log: ILogger,
13
13
  postClient: (body: string) => Promise<IResponse>,
14
14
  sourceCache: IRecorderCacheProducerSync<TState>,
15
15
  postRate: number,
16
16
  dataName: string,
17
- latencyTracker?: ITimeTracker,
18
17
  fromCacheToPayload?: (cacheData: TState) => any,
19
18
  maxRetries: number = 0,
20
19
  debugLogs?: boolean
@@ -26,33 +25,52 @@ export function submitterSyncTaskFactory<TState extends { length?: number }>(
26
25
  if (sourceCache.isEmpty()) return Promise.resolve();
27
26
 
28
27
  const data = sourceCache.state();
29
-
30
- const dataCount: number | '' = typeof data.length === 'number' ? data.length : '';
31
- log[debugLogs ? 'debug' : 'info'](SUBMITTERS_PUSH, [dataCount, dataName]);
32
- const latencyTrackerStop = latencyTracker && latencyTracker.start();
28
+ // @ts-ignore
29
+ const dataCountMessage = typeof data.length === 'number' ? `${data.length} ${dataName}` : dataName;
30
+ log[debugLogs ? 'debug' : 'info'](SUBMITTERS_PUSH, [dataCountMessage]);
33
31
 
34
32
  const jsonPayload = JSON.stringify(fromCacheToPayload ? fromCacheToPayload(data) : data);
35
33
  if (!maxRetries) sourceCache.clear();
36
34
 
37
- const postPromise = postClient(jsonPayload).then(() => {
35
+ return postClient(jsonPayload).then(() => {
38
36
  retries = 0;
39
37
  sourceCache.clear(); // we clear the queue if request successes.
40
38
  }).catch(err => {
41
39
  if (!maxRetries) {
42
- log.warn(SUBMITTERS_PUSH_FAILS, [dataCount, dataName, err]);
40
+ log.warn(SUBMITTERS_PUSH_FAILS, [dataCountMessage, err]);
43
41
  } else if (retries === maxRetries) {
44
42
  retries = 0;
45
43
  sourceCache.clear(); // we clear the queue if request fails after retries.
46
- log.warn(SUBMITTERS_PUSH_FAILS, [dataCount, dataName, err]);
44
+ log.warn(SUBMITTERS_PUSH_FAILS, [dataCountMessage, err]);
47
45
  } else {
48
46
  retries++;
49
- log.warn(SUBMITTERS_PUSH_RETRY, [dataCount, dataName, err]);
47
+ log.warn(SUBMITTERS_PUSH_RETRY, [dataCountMessage, err]);
50
48
  }
51
49
  });
52
-
53
- // if latencyTracker provided, attach stop callback to postEventsPromise
54
- return latencyTrackerStop ? postPromise.then(latencyTrackerStop).catch(latencyTrackerStop) : postPromise;
55
50
  }
56
51
 
57
52
  return syncTaskFactory(log, postData, postRate, dataName + ' submitter');
58
53
  }
54
+
55
+ /**
56
+ * Decorates a provided submitter with a first execution window
57
+ */
58
+ export function firstPushWindowDecorator(submitter: ISyncTask, firstPushWindow: number) {
59
+ let running = false;
60
+ let stopEventPublisherTimeout: ReturnType<typeof setTimeout>;
61
+ const originalStart = submitter.start;
62
+ submitter.start = () => {
63
+ running = true;
64
+ stopEventPublisherTimeout = setTimeout(originalStart, firstPushWindow);
65
+ };
66
+ const originalStop = submitter.stop;
67
+ submitter.stop = () => {
68
+ running = false;
69
+ clearTimeout(stopEventPublisherTimeout);
70
+ originalStop();
71
+ };
72
+ submitter.isRunning = () => {
73
+ return running;
74
+ };
75
+ return submitter;
76
+ }
@@ -1,18 +1,21 @@
1
1
  import { syncTaskComposite } from '../syncTaskComposite';
2
- import { eventsSyncTaskFactory } from './eventsSyncTask';
3
- import { impressionsSyncTaskFactory } from './impressionsSyncTask';
4
- import { impressionCountsSyncTaskFactory } from './impressionCountsSyncTask';
5
- import { ISyncManagerFactoryParams } from '../types';
2
+ import { eventsSubmitterFactory } from './eventsSubmitter';
3
+ import { impressionsSubmitterFactory } from './impressionsSubmitter';
4
+ import { impressionCountsSubmitterFactory } from './impressionCountsSubmitter';
5
+ import { telemetrySubmitterFactory } from './telemetrySubmitter';
6
+ import { ISdkFactoryContextSync } from '../../sdkFactory/types';
6
7
 
7
- export function submitterManagerFactory(params: ISyncManagerFactoryParams) {
8
+ export function submitterManagerFactory(params: ISdkFactoryContextSync) {
8
9
 
9
- const { settings, storage, splitApi } = params;
10
- const log = settings.log;
11
10
  const submitters = [
12
- impressionsSyncTaskFactory(log, splitApi.postTestImpressionsBulk, storage.impressions, settings.scheduler.impressionsRefreshRate, settings.core.labelsEnabled),
13
- eventsSyncTaskFactory(log, splitApi.postEventsBulk, storage.events, settings.scheduler.eventsPushRate, settings.startup.eventsFirstPushWindow)
14
- // @TODO add telemetry submitter
11
+ impressionsSubmitterFactory(params),
12
+ eventsSubmitterFactory(params)
15
13
  ];
16
- if (storage.impressionCounts) submitters.push(impressionCountsSyncTaskFactory(log, splitApi.postTestImpressionsCount, storage.impressionCounts));
14
+
15
+ const impressionCountsSubmitter = impressionCountsSubmitterFactory(params);
16
+ if (impressionCountsSubmitter) submitters.push(impressionCountsSubmitter);
17
+ const telemetrySubmitter = telemetrySubmitterFactory(params);
18
+ if (telemetrySubmitter) submitters.push(telemetrySubmitter);
19
+
17
20
  return syncTaskComposite(submitters);
18
21
  }
@@ -0,0 +1,143 @@
1
+ import { ISegmentsCacheSync, ISplitsCacheSync, ITelemetryCacheSync } from '../../storages/types';
2
+ import { submitterFactory, firstPushWindowDecorator } from './submitter';
3
+ import { TelemetryUsageStatsPayload, TelemetryConfigStatsPayload } from './types';
4
+ import { QUEUED, DEDUPED, DROPPED, CONSUMER_MODE, CONSUMER_ENUM, STANDALONE_MODE, CONSUMER_PARTIAL_MODE, STANDALONE_ENUM, CONSUMER_PARTIAL_ENUM, OPTIMIZED, DEBUG, DEBUG_ENUM, OPTIMIZED_ENUM } from '../../utils/constants';
5
+ import { SDK_READY, SDK_READY_FROM_CACHE } from '../../readiness/constants';
6
+ import { ISettings } from '../../types';
7
+ import { base } from '../../utils/settingsValidation';
8
+ import { usedKeysMap } from '../../utils/inputValidation/apiKey';
9
+ import { timer } from '../../utils/timeTracker/timer';
10
+ import { ISdkFactoryContextSync } from '../../sdkFactory/types';
11
+
12
+ /**
13
+ * Converts data from telemetry cache into /metrics/usage request payload.
14
+ */
15
+ export function telemetryCacheStatsAdapter(telemetry: ITelemetryCacheSync, splits: ISplitsCacheSync, segments: ISegmentsCacheSync) {
16
+ return {
17
+ isEmpty() { return false; }, // There is always data in telemetry cache
18
+ clear() { }, // No-op
19
+
20
+ // @TODO consider moving inside telemetry cache for code size reduction
21
+ state(): TelemetryUsageStatsPayload {
22
+ return {
23
+ lS: telemetry.getLastSynchronization(),
24
+ mL: telemetry.popLatencies(),
25
+ mE: telemetry.popExceptions(),
26
+ hE: telemetry.popHttpErrors(),
27
+ hL: telemetry.popHttpLatencies(),
28
+ tR: telemetry.popTokenRefreshes(),
29
+ aR: telemetry.popAuthRejections(),
30
+ iQ: telemetry.getImpressionStats(QUEUED),
31
+ iDe: telemetry.getImpressionStats(DEDUPED),
32
+ iDr: telemetry.getImpressionStats(DROPPED),
33
+ spC: splits.getSplitNames().length,
34
+ seC: segments.getRegisteredSegments().length,
35
+ skC: segments.getKeysCount(),
36
+ sL: telemetry.getSessionLength(),
37
+ eQ: telemetry.getEventStats(QUEUED),
38
+ eD: telemetry.getEventStats(DROPPED),
39
+ sE: telemetry.popStreamingEvents(),
40
+ t: telemetry.popTags(),
41
+ };
42
+ }
43
+ };
44
+ }
45
+
46
+ const OPERATION_MODE_MAP = {
47
+ [STANDALONE_MODE]: STANDALONE_ENUM,
48
+ [CONSUMER_MODE]: CONSUMER_ENUM,
49
+ [CONSUMER_PARTIAL_MODE]: CONSUMER_PARTIAL_ENUM
50
+ } as Record<ISettings['mode'], (0 | 1 | 2)>;
51
+
52
+ const IMPRESSIONS_MODE_MAP = {
53
+ [OPTIMIZED]: OPTIMIZED_ENUM,
54
+ [DEBUG]: DEBUG_ENUM
55
+ } as Record<ISettings['sync']['impressionsMode'], (0 | 1)>;
56
+
57
+ function getActiveFactories() {
58
+ return Object.keys(usedKeysMap).length;
59
+ }
60
+
61
+ function getRedundantActiveFactories() {
62
+ return Object.keys(usedKeysMap).reduce((acum, apiKey) => {
63
+ return acum + usedKeysMap[apiKey] - 1;
64
+ }, 0);
65
+ }
66
+
67
+ /**
68
+ * Converts data from telemetry cache and settings into /metrics/config request payload.
69
+ */
70
+ export function telemetryCacheConfigAdapter(telemetry: ITelemetryCacheSync, settings: ISettings) {
71
+ return {
72
+ isEmpty() { return false; },
73
+ clear() { },
74
+
75
+ state(): TelemetryConfigStatsPayload {
76
+ const { urls, scheduler } = settings;
77
+
78
+ return {
79
+ oM: OPERATION_MODE_MAP[settings.mode], // @ts-ignore lower case of storage type
80
+ st: settings.storage.type.toLowerCase(),
81
+ sE: settings.streamingEnabled,
82
+ rR: {
83
+ sp: scheduler.featuresRefreshRate,
84
+ se: scheduler.segmentsRefreshRate,
85
+ im: scheduler.impressionsRefreshRate,
86
+ ev: scheduler.eventsPushRate,
87
+ te: scheduler.telemetryRefreshRate,
88
+ }, // refreshRates
89
+ uO: {
90
+ s: urls.sdk !== base.urls.sdk,
91
+ e: urls.events !== base.urls.events,
92
+ a: urls.auth !== base.urls.auth,
93
+ st: urls.streaming !== base.urls.streaming,
94
+ t: urls.telemetry !== base.urls.telemetry,
95
+ }, // urlOverrides
96
+ iQ: scheduler.impressionsQueueSize,
97
+ eQ: scheduler.eventsQueueSize,
98
+ iM: IMPRESSIONS_MODE_MAP[settings.sync.impressionsMode],
99
+ iL: settings.impressionListener ? true : false,
100
+ hP: false, // @TODO proxy not supported
101
+ aF: getActiveFactories(),
102
+ rF: getRedundantActiveFactories(),
103
+ tR: telemetry.getTimeUntilReady() as number,
104
+ tC: telemetry.getTimeUntilReadyFromCache(),
105
+ nR: telemetry.getNonReadyUsage(),
106
+ t: telemetry.popTags(),
107
+ i: settings.integrations && settings.integrations.map(int => int.type),
108
+ };
109
+ }
110
+ };
111
+ }
112
+
113
+ /**
114
+ * Submitter that periodically posts telemetry data
115
+ */
116
+ export function telemetrySubmitterFactory(params: ISdkFactoryContextSync) {
117
+ const { storage: { splits, segments, telemetry } } = params;
118
+ if (!telemetry) return; // No submitter created if telemetry cache is not defined
119
+
120
+ const { settings, settings: { log, scheduler: { telemetryRefreshRate } }, splitApi, platform: { now }, readiness } = params;
121
+ const startTime = timer(now || Date.now);
122
+
123
+ const submitter = firstPushWindowDecorator(
124
+ submitterFactory(log, splitApi.postMetricsUsage, telemetryCacheStatsAdapter(telemetry, splits, segments), telemetryRefreshRate, 'telemetry stats', undefined, 0, true),
125
+ telemetryRefreshRate
126
+ );
127
+
128
+ readiness.gate.once(SDK_READY_FROM_CACHE, () => {
129
+ telemetry.recordTimeUntilReadyFromCache(startTime());
130
+ });
131
+
132
+ readiness.gate.once(SDK_READY, () => {
133
+ telemetry.recordTimeUntilReady(startTime());
134
+
135
+ // Post config data when the SDK is ready and if the telemetry submitter was started
136
+ if (submitter.isRunning()) {
137
+ const postMetricsConfigTask = submitterFactory(log, splitApi.postMetricsConfig, telemetryCacheConfigAdapter(telemetry, settings), 0, 'telemetry config', undefined, 0, true);
138
+ postMetricsConfigTask.execute();
139
+ }
140
+ });
141
+
142
+ return submitter;
143
+ }
@@ -62,3 +62,126 @@ export type StoredEventWithMetadata = {
62
62
  /** Stored event */
63
63
  e: SplitIO.EventData
64
64
  }
65
+
66
+ /**
67
+ * Telemetry usage stats
68
+ */
69
+
70
+ export type QUEUED = 0;
71
+ export type DROPPED = 1;
72
+ export type DEDUPED = 2;
73
+ export type ImpressionDataType = QUEUED | DROPPED | DEDUPED
74
+ export type EventDataType = QUEUED | DROPPED;
75
+
76
+ export type SPLITS = 'sp';
77
+ export type IMPRESSIONS = 'im';
78
+ export type IMPRESSIONS_COUNT = 'ic';
79
+ export type EVENTS = 'ev';
80
+ export type TELEMETRY = 'te';
81
+ export type TOKEN = 'to';
82
+ export type SEGMENT = 'se';
83
+ export type MY_SEGMENT = 'ms';
84
+ export type OperationType = SPLITS | IMPRESSIONS | IMPRESSIONS_COUNT | EVENTS | TELEMETRY | TOKEN | SEGMENT | MY_SEGMENT;
85
+
86
+ export type LastSync = Record<OperationType, number | undefined>
87
+ export type HttpErrors = Record<OperationType, { [statusCode: string]: number }>
88
+ export type HttpLatencies = Record<OperationType, Array<number>>
89
+
90
+ export type TREATMENT = 't';
91
+ export type TREATMENTS = 'ts';
92
+ export type TREATMENT_WITH_CONFIG = 'tc';
93
+ export type TREATMENTS_WITH_CONFIG = 'tcs';
94
+ export type TRACK = 'tr';
95
+ export type Method = TREATMENT | TREATMENTS | TREATMENT_WITH_CONFIG | TREATMENTS_WITH_CONFIG | TRACK;
96
+
97
+ export type MethodLatencies = Record<Method, Array<number>>;
98
+
99
+ export type MethodExceptions = Record<Method, number>;
100
+
101
+ export type CONNECTION_ESTABLISHED = 0;
102
+ export type OCCUPANCY_PRI = 10;
103
+ export type OCCUPANCY_SEC = 20;
104
+ export type STREAMING_STATUS = 30;
105
+ export type SSE_CONNECTION_ERROR = 40;
106
+ export type TOKEN_REFRESH = 50;
107
+ export type ABLY_ERROR = 60;
108
+ export type SYNC_MODE_UPDATE = 70;
109
+ export type StreamingEventType = CONNECTION_ESTABLISHED | OCCUPANCY_PRI | OCCUPANCY_SEC | STREAMING_STATUS | SSE_CONNECTION_ERROR | TOKEN_REFRESH | ABLY_ERROR | SYNC_MODE_UPDATE;
110
+
111
+ export type StreamingEvent = {
112
+ e: StreamingEventType, // eventType
113
+ d?: number, // eventData
114
+ t: number, // timestamp
115
+ }
116
+
117
+ // 'metrics/usage' JSON request body
118
+ export type TelemetryUsageStatsPayload = {
119
+ lS: LastSync, // lastSynchronization
120
+ mL: MethodLatencies, // clientMethodLatencies
121
+ mE: MethodExceptions, // methodExceptions
122
+ hE: HttpErrors, // httpErrors
123
+ hL: HttpLatencies, // httpLatencies
124
+ tR: number, // tokenRefreshes
125
+ aR: number, // authRejections
126
+ iQ: number, // impressionsQueued
127
+ iDe: number, // impressionsDeduped
128
+ iDr: number, // impressionsDropped
129
+ spC: number, // splitCount
130
+ seC: number, // segmentCount
131
+ skC: number, // segmentKeyCount
132
+ sL?: number, // sessionLengthMs
133
+ eQ: number, // eventsQueued
134
+ eD: number, // eventsDropped
135
+ sE: Array<StreamingEvent>, // streamingEvents
136
+ t?: Array<string>, // tags
137
+ }
138
+
139
+ /**
140
+ * Telemetry config stats
141
+ */
142
+
143
+ export type STANDALONE_ENUM = 0;
144
+ export type CONSUMER_ENUM = 1;
145
+ export type CONSUMER_PARTIAL_ENUM = 2;
146
+ export type OperationMode = STANDALONE_ENUM | CONSUMER_ENUM | CONSUMER_PARTIAL_ENUM
147
+
148
+ export type OPTIMIZED_ENUM = 0;
149
+ export type DEBUG_ENUM = 1;
150
+ export type ImpressionsMode = OPTIMIZED_ENUM | DEBUG_ENUM;
151
+
152
+ export type RefreshRates = {
153
+ sp: number, // splits
154
+ se: number, // mySegments
155
+ im: number, // impressions
156
+ ev: number, // events
157
+ te: number, // telemetry
158
+ }
159
+
160
+ export type UrlOverrides = {
161
+ s: boolean, // sdkUrl
162
+ e: boolean, // events
163
+ a: boolean, // auth
164
+ st: boolean, // stream
165
+ t: boolean, // telemetry
166
+ }
167
+
168
+ // 'metrics/config' JSON request body
169
+ export type TelemetryConfigStatsPayload = {
170
+ oM?: OperationMode, // operationMode
171
+ st: 'memory' | 'redis' | 'pluggable' | 'localstorage', // storage
172
+ sE: boolean, // streamingEnabled
173
+ rR: RefreshRates, // refreshRates
174
+ uO: UrlOverrides, // urlOverrides
175
+ iQ: number, // impressionsQueueSize
176
+ eQ: number, // eventsQueueSize
177
+ iM: ImpressionsMode, // impressionsMode
178
+ iL: boolean, // impressionsListenerEnabled
179
+ hP: boolean, // httpProxyDetected
180
+ aF: number, // activeFactories
181
+ rF: number, // redundantActiveFactories
182
+ tR: number, // timeUntilSDKReady
183
+ tC?: number, // timeUntilSDKReadyFromCache
184
+ nR: number, // SDKNotReadyUsage
185
+ t?: Array<string>, // tags
186
+ i?: Array<string>, // integrations
187
+ }