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

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 (219) hide show
  1. package/cjs/consent/sdkUserConsent.js +1 -1
  2. package/cjs/listeners/browser.js +5 -4
  3. package/cjs/logger/constants.js +2 -1
  4. package/cjs/logger/messages/error.js +2 -1
  5. package/cjs/logger/messages/info.js +3 -3
  6. package/cjs/logger/messages/warn.js +2 -2
  7. package/cjs/sdkClient/client.js +17 -3
  8. package/cjs/sdkClient/sdkClient.js +4 -1
  9. package/cjs/sdkFactory/index.js +16 -19
  10. package/cjs/services/splitApi.js +15 -14
  11. package/cjs/services/splitHttpClient.js +4 -1
  12. package/cjs/storages/AbstractSegmentsCacheSync.js +0 -5
  13. package/cjs/storages/KeyBuilderSS.js +12 -19
  14. package/cjs/storages/findLatencyIndex.js +11 -6
  15. package/cjs/storages/inLocalStorage/MySegmentsCacheInLocal.js +13 -1
  16. package/cjs/storages/inLocalStorage/index.js +4 -1
  17. package/cjs/storages/inMemory/InMemoryStorage.js +2 -0
  18. package/cjs/storages/inMemory/InMemoryStorageCS.js +3 -0
  19. package/cjs/storages/inMemory/MySegmentsCacheInMemory.js +6 -0
  20. package/cjs/storages/inMemory/SegmentsCacheInMemory.js +6 -0
  21. package/cjs/storages/inMemory/TelemetryCacheInMemory.js +165 -0
  22. package/cjs/storages/inRedis/TelemetryCacheInRedis.js +29 -0
  23. package/cjs/storages/inRedis/index.js +2 -4
  24. package/cjs/storages/pluggable/TelemetryCachePluggable.js +27 -0
  25. package/cjs/storages/pluggable/index.js +2 -1
  26. package/cjs/sync/polling/pollingManagerCS.js +1 -1
  27. package/cjs/sync/polling/syncTasks/splitsSyncTask.js +2 -2
  28. package/cjs/sync/polling/updaters/mySegmentsUpdater.js +0 -3
  29. package/cjs/sync/polling/updaters/segmentChangesUpdater.js +1 -8
  30. package/cjs/sync/polling/updaters/splitChangesUpdater.js +3 -6
  31. package/cjs/sync/streaming/SSEHandler/NotificationKeeper.js +20 -13
  32. package/cjs/sync/streaming/SSEHandler/index.js +21 -15
  33. package/cjs/sync/streaming/pushManager.js +7 -4
  34. package/cjs/sync/submitters/eventsSubmitter.js +28 -0
  35. package/cjs/sync/submitters/{impressionCountsSyncTask.js → impressionCountsSubmitter.js} +10 -7
  36. package/cjs/sync/submitters/{impressionsSyncTask.js → impressionsSubmitter.js} +8 -8
  37. package/cjs/sync/submitters/submitter.js +66 -0
  38. package/cjs/sync/submitters/submitterManager.js +12 -10
  39. package/cjs/sync/submitters/telemetrySubmitter.js +128 -0
  40. package/cjs/sync/syncManagerOnline.js +6 -2
  41. package/cjs/trackers/eventTracker.js +5 -1
  42. package/cjs/trackers/impressionsTracker.js +9 -1
  43. package/cjs/trackers/telemetryTracker.js +65 -0
  44. package/cjs/utils/constants/index.js +40 -1
  45. package/cjs/utils/inputValidation/apiKey.js +12 -11
  46. package/cjs/utils/settingsValidation/index.js +35 -11
  47. package/cjs/utils/settingsValidation/url.js +4 -0
  48. package/cjs/utils/timeTracker/index.js +1 -0
  49. package/cjs/utils/timeTracker/timer.js +2 -2
  50. package/esm/consent/sdkUserConsent.js +1 -1
  51. package/esm/listeners/browser.js +3 -2
  52. package/esm/logger/constants.js +1 -0
  53. package/esm/logger/messages/error.js +2 -1
  54. package/esm/logger/messages/info.js +3 -3
  55. package/esm/logger/messages/warn.js +2 -2
  56. package/esm/sdkClient/client.js +18 -4
  57. package/esm/sdkClient/sdkClient.js +4 -1
  58. package/esm/sdkFactory/index.js +16 -19
  59. package/esm/services/splitApi.js +15 -14
  60. package/esm/services/splitHttpClient.js +4 -1
  61. package/esm/storages/AbstractSegmentsCacheSync.js +0 -5
  62. package/esm/storages/KeyBuilderSS.js +12 -19
  63. package/esm/storages/findLatencyIndex.js +11 -6
  64. package/esm/storages/inLocalStorage/MySegmentsCacheInLocal.js +13 -1
  65. package/esm/storages/inLocalStorage/index.js +5 -2
  66. package/esm/storages/inMemory/InMemoryStorage.js +3 -1
  67. package/esm/storages/inMemory/InMemoryStorageCS.js +4 -1
  68. package/esm/storages/inMemory/MySegmentsCacheInMemory.js +6 -0
  69. package/esm/storages/inMemory/SegmentsCacheInMemory.js +6 -0
  70. package/esm/storages/inMemory/TelemetryCacheInMemory.js +161 -0
  71. package/esm/storages/inRedis/TelemetryCacheInRedis.js +26 -0
  72. package/esm/storages/inRedis/index.js +2 -4
  73. package/esm/storages/pluggable/TelemetryCachePluggable.js +24 -0
  74. package/esm/storages/pluggable/index.js +2 -1
  75. package/esm/sync/polling/pollingManagerCS.js +1 -1
  76. package/esm/sync/polling/syncTasks/splitsSyncTask.js +2 -2
  77. package/esm/sync/polling/updaters/mySegmentsUpdater.js +0 -3
  78. package/esm/sync/polling/updaters/segmentChangesUpdater.js +1 -8
  79. package/esm/sync/polling/updaters/splitChangesUpdater.js +3 -6
  80. package/esm/sync/streaming/SSEHandler/NotificationKeeper.js +8 -1
  81. package/esm/sync/streaming/SSEHandler/index.js +21 -15
  82. package/esm/sync/streaming/pushManager.js +7 -4
  83. package/esm/sync/submitters/eventsSubmitter.js +24 -0
  84. package/esm/sync/submitters/{impressionCountsSyncTask.js → impressionCountsSubmitter.js} +8 -5
  85. package/esm/sync/submitters/{impressionsSyncTask.js → impressionsSubmitter.js} +6 -6
  86. package/esm/sync/submitters/submitter.js +61 -0
  87. package/esm/sync/submitters/submitterManager.js +12 -10
  88. package/esm/sync/submitters/telemetrySubmitter.js +122 -0
  89. package/esm/sync/syncManagerOnline.js +6 -2
  90. package/esm/trackers/eventTracker.js +6 -2
  91. package/esm/trackers/impressionsTracker.js +10 -2
  92. package/esm/trackers/telemetryTracker.js +61 -0
  93. package/esm/utils/constants/index.js +38 -0
  94. package/esm/utils/inputValidation/apiKey.js +2 -1
  95. package/esm/utils/settingsValidation/index.js +34 -10
  96. package/esm/utils/settingsValidation/url.js +4 -0
  97. package/esm/utils/timeTracker/index.js +1 -0
  98. package/esm/utils/timeTracker/timer.js +2 -2
  99. package/package.json +1 -1
  100. package/src/consent/sdkUserConsent.ts +1 -1
  101. package/src/listeners/browser.ts +3 -2
  102. package/src/logger/constants.ts +1 -0
  103. package/src/logger/messages/error.ts +2 -1
  104. package/src/logger/messages/info.ts +3 -3
  105. package/src/logger/messages/warn.ts +2 -2
  106. package/src/sdkClient/client.ts +23 -4
  107. package/src/sdkClient/sdkClient.ts +4 -1
  108. package/src/sdkFactory/index.ts +22 -24
  109. package/src/sdkFactory/types.ts +32 -15
  110. package/src/services/splitApi.ts +17 -14
  111. package/src/services/splitHttpClient.ts +6 -3
  112. package/src/services/types.ts +7 -5
  113. package/src/storages/AbstractSegmentsCacheSync.ts +8 -3
  114. package/src/storages/KeyBuilderSS.ts +13 -50
  115. package/src/storages/findLatencyIndex.ts +12 -3
  116. package/src/storages/inLocalStorage/MySegmentsCacheInLocal.ts +13 -1
  117. package/src/storages/inLocalStorage/index.ts +5 -2
  118. package/src/storages/inMemory/InMemoryStorage.ts +3 -1
  119. package/src/storages/inMemory/InMemoryStorageCS.ts +4 -1
  120. package/src/storages/inMemory/MySegmentsCacheInMemory.ts +8 -0
  121. package/src/storages/inMemory/SegmentsCacheInMemory.ts +6 -0
  122. package/src/storages/inMemory/TelemetryCacheInMemory.ts +210 -0
  123. package/src/storages/inRedis/TelemetryCacheInRedis.ts +29 -0
  124. package/src/storages/inRedis/index.ts +2 -4
  125. package/src/storages/pluggable/TelemetryCachePluggable.ts +26 -0
  126. package/src/storages/pluggable/index.ts +2 -1
  127. package/src/storages/types.ts +84 -32
  128. package/src/sync/offline/syncManagerOffline.ts +4 -3
  129. package/src/sync/polling/pollingManagerCS.ts +3 -3
  130. package/src/sync/polling/pollingManagerSS.ts +2 -2
  131. package/src/sync/polling/syncTasks/splitsSyncTask.ts +2 -0
  132. package/src/sync/polling/updaters/mySegmentsUpdater.ts +0 -4
  133. package/src/sync/polling/updaters/segmentChangesUpdater.ts +2 -10
  134. package/src/sync/polling/updaters/splitChangesUpdater.ts +3 -6
  135. package/src/sync/streaming/SSEHandler/NotificationKeeper.ts +11 -1
  136. package/src/sync/streaming/SSEHandler/index.ts +21 -14
  137. package/src/sync/streaming/pushManager.ts +11 -7
  138. package/src/sync/submitters/eventsSubmitter.ts +35 -0
  139. package/src/sync/submitters/{impressionCountsSyncTask.ts → impressionCountsSubmitter.ts} +15 -15
  140. package/src/sync/submitters/{impressionsSyncTask.ts → impressionsSubmitter.ts} +12 -16
  141. package/src/sync/submitters/{submitterSyncTask.ts → submitter.ts} +34 -16
  142. package/src/sync/submitters/submitterManager.ts +14 -11
  143. package/src/sync/submitters/telemetrySubmitter.ts +143 -0
  144. package/src/sync/submitters/types.ts +123 -0
  145. package/src/sync/syncManagerOnline.ts +13 -7
  146. package/src/sync/types.ts +0 -15
  147. package/src/trackers/eventTracker.ts +7 -3
  148. package/src/trackers/impressionsTracker.ts +11 -3
  149. package/src/trackers/telemetryTracker.ts +63 -0
  150. package/src/trackers/types.ts +24 -0
  151. package/src/types.ts +35 -6
  152. package/src/utils/constants/index.ts +45 -0
  153. package/src/utils/inputValidation/apiKey.ts +2 -1
  154. package/src/utils/settingsValidation/index.ts +35 -11
  155. package/src/utils/settingsValidation/url.ts +4 -0
  156. package/src/utils/timeTracker/index.ts +1 -1
  157. package/src/utils/timeTracker/timer.ts +3 -3
  158. package/types/logger/constants.d.ts +1 -0
  159. package/types/sdkFactory/types.d.ts +29 -14
  160. package/types/services/splitApi.d.ts +2 -1
  161. package/types/services/types.d.ts +8 -5
  162. package/types/storages/AbstractSegmentsCacheSync.d.ts +7 -3
  163. package/types/storages/KeyBuilderSS.d.ts +3 -3
  164. package/types/storages/findLatencyIndex.d.ts +7 -1
  165. package/types/storages/inLocalStorage/MySegmentsCacheInLocal.d.ts +2 -0
  166. package/types/storages/inMemory/MySegmentsCacheInMemory.d.ts +2 -0
  167. package/types/storages/inMemory/SegmentsCacheInMemory.d.ts +1 -0
  168. package/types/storages/inMemory/TelemetryCacheInMemory.d.ts +6 -2
  169. package/types/storages/inRedis/TelemetryCacheInRedis.d.ts +3 -3
  170. package/types/storages/pluggable/TelemetryCachePluggable.d.ts +2 -2
  171. package/types/storages/types.d.ts +71 -22
  172. package/types/sync/offline/syncManagerOffline.d.ts +3 -2
  173. package/types/sync/polling/pollingManagerCS.d.ts +2 -2
  174. package/types/sync/polling/pollingManagerSS.d.ts +2 -2
  175. package/types/sync/polling/syncTasks/splitsSyncTask.d.ts +1 -1
  176. package/types/sync/polling/updaters/splitChangesUpdater.d.ts +1 -1
  177. package/types/sync/streaming/SSEHandler/NotificationKeeper.d.ts +2 -1
  178. package/types/sync/streaming/SSEHandler/index.d.ts +2 -1
  179. package/types/sync/streaming/pushManager.d.ts +2 -2
  180. package/types/sync/submitters/eventsSubmitter.d.ts +5 -0
  181. package/types/sync/submitters/impressionCountsSubmitter.d.ts +10 -0
  182. package/types/sync/submitters/impressionsSubmitter.d.ts +11 -0
  183. package/types/sync/submitters/submitter.d.ts +12 -0
  184. package/types/sync/submitters/submitterManager.d.ts +2 -2
  185. package/types/sync/submitters/telemetrySubmitter.d.ts +24 -0
  186. package/types/sync/submitters/telemetrySyncTask.d.ts +0 -27
  187. package/types/sync/submitters/types.d.ts +107 -0
  188. package/types/sync/syncManagerOnline.d.ts +3 -2
  189. package/types/sync/types.d.ts +0 -13
  190. package/types/trackers/eventTracker.d.ts +2 -2
  191. package/types/trackers/impressionsTracker.d.ts +2 -2
  192. package/types/trackers/telemetryTracker.d.ts +2 -3
  193. package/types/trackers/types.d.ts +22 -0
  194. package/types/types.d.ts +33 -4
  195. package/types/utils/constants/index.d.ts +37 -0
  196. package/types/utils/inputValidation/apiKey.d.ts +1 -0
  197. package/types/utils/settingsValidation/index.d.ts +40 -0
  198. package/types/utils/timeTracker/index.d.ts +1 -1
  199. package/types/utils/timeTracker/timer.d.ts +1 -1
  200. package/cjs/storages/inMemory/CountsCacheInMemory.js +0 -38
  201. package/cjs/storages/inMemory/LatenciesCacheInMemory.js +0 -43
  202. package/cjs/storages/inRedis/CountsCacheInRedis.js +0 -16
  203. package/cjs/storages/inRedis/LatenciesCacheInRedis.js +0 -18
  204. package/cjs/sync/submitters/eventsSyncTask.js +0 -44
  205. package/cjs/sync/submitters/metricsSyncTask.js +0 -31
  206. package/cjs/sync/submitters/submitterSyncTask.js +0 -44
  207. package/esm/storages/inMemory/CountsCacheInMemory.js +0 -35
  208. package/esm/storages/inMemory/LatenciesCacheInMemory.js +0 -40
  209. package/esm/storages/inRedis/CountsCacheInRedis.js +0 -13
  210. package/esm/storages/inRedis/LatenciesCacheInRedis.js +0 -15
  211. package/esm/sync/submitters/eventsSyncTask.js +0 -40
  212. package/esm/sync/submitters/metricsSyncTask.js +0 -26
  213. package/esm/sync/submitters/submitterSyncTask.js +0 -40
  214. package/src/storages/inMemory/CountsCacheInMemory.ts +0 -37
  215. package/src/storages/inMemory/LatenciesCacheInMemory.ts +0 -45
  216. package/src/storages/inRedis/CountsCacheInRedis.ts +0 -20
  217. package/src/storages/inRedis/LatenciesCacheInRedis.ts +0 -23
  218. package/src/sync/submitters/eventsSyncTask.ts +0 -57
  219. package/src/sync/submitters/metricsSyncTask.ts +0 -49
@@ -4,7 +4,8 @@ import { ImpressionsCacheInMemory } from './ImpressionsCacheInMemory';
4
4
  import { EventsCacheInMemory } from './EventsCacheInMemory';
5
5
  import { IStorageSync, IStorageFactoryParams } from '../types';
6
6
  import { ImpressionCountsCacheInMemory } from './ImpressionCountsCacheInMemory';
7
- import { STORAGE_MEMORY } from '../../utils/constants';
7
+ import { LOCALHOST_MODE, STORAGE_MEMORY } from '../../utils/constants';
8
+ import { shouldRecordTelemetry, TelemetryCacheInMemory } from './TelemetryCacheInMemory';
8
9
 
9
10
  /**
10
11
  * InMemory storage factory for standalone client-side SplitFactory
@@ -19,6 +20,7 @@ export function InMemoryStorageCSFactory(params: IStorageFactoryParams): IStorag
19
20
  impressions: new ImpressionsCacheInMemory(params.impressionsQueueSize),
20
21
  impressionCounts: params.optimize ? new ImpressionCountsCacheInMemory() : undefined,
21
22
  events: new EventsCacheInMemory(params.eventsQueueSize),
23
+ telemetry: params.mode !== LOCALHOST_MODE && shouldRecordTelemetry() ? new TelemetryCacheInMemory() : undefined,
22
24
 
23
25
  // When using MEMORY we should clean all the caches to leave them empty
24
26
  destroy() {
@@ -37,6 +39,7 @@ export function InMemoryStorageCSFactory(params: IStorageFactoryParams): IStorag
37
39
  impressions: this.impressions,
38
40
  impressionCounts: this.impressionCounts,
39
41
  events: this.events,
42
+ telemetry: this.telemetry,
40
43
 
41
44
  // Set a new splits cache to clean it for the client without affecting other clients
42
45
  destroy() {
@@ -72,4 +72,12 @@ export class MySegmentsCacheInMemory extends AbstractSegmentsCacheSync {
72
72
  return isDiff;
73
73
  }
74
74
 
75
+ getRegisteredSegments() {
76
+ return Object.keys(this.segmentCache);
77
+ }
78
+
79
+ getKeysCount() {
80
+ return 1;
81
+ }
82
+
75
83
  }
@@ -68,6 +68,12 @@ export class SegmentsCacheInMemory extends AbstractSegmentsCacheSync {
68
68
  return Object.keys(this.segmentCache);
69
69
  }
70
70
 
71
+ getKeysCount() {
72
+ return Object.keys(this.segmentCache).reduce((acum, segmentName) => {
73
+ return acum + this.segmentCache[segmentName].size;
74
+ }, 0);
75
+ }
76
+
71
77
  setChangeNumber(name: string, changeNumber: number) {
72
78
  this.segmentChangeNumber[name] = changeNumber;
73
79
 
@@ -0,0 +1,210 @@
1
+ import { ImpressionDataType, EventDataType, LastSync, HttpErrors, HttpLatencies, StreamingEvent, Method, OperationType, MethodExceptions, MethodLatencies } from '../../sync/submitters/types';
2
+ import { findLatencyIndex } from '../findLatencyIndex';
3
+ import { ITelemetryCacheSync } from '../types';
4
+
5
+ const MAX_STREAMING_EVENTS = 20;
6
+ const MAX_TAGS = 10;
7
+
8
+ function newBuckets() {
9
+ // MAX_LATENCY_BUCKET_COUNT (length) is 23;
10
+ return [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
11
+ }
12
+
13
+ const ACCEPTANCE_RANGE = 0.001;
14
+
15
+ /**
16
+ * Used on client-side. 0.1% of instances will track telemetry
17
+ */
18
+ export function shouldRecordTelemetry() {
19
+ return Math.random() <= ACCEPTANCE_RANGE;
20
+ }
21
+
22
+ export class TelemetryCacheInMemory implements ITelemetryCacheSync {
23
+
24
+ private timeUntilReady?: number;
25
+
26
+ getTimeUntilReady() {
27
+ return this.timeUntilReady;
28
+ }
29
+
30
+ recordTimeUntilReady(ms: number) {
31
+ this.timeUntilReady = ms;
32
+ }
33
+
34
+ private timeUntilReadyFromCache?: number;
35
+
36
+ getTimeUntilReadyFromCache() {
37
+ return this.timeUntilReadyFromCache;
38
+ }
39
+
40
+ recordTimeUntilReadyFromCache(ms: number) {
41
+ this.timeUntilReadyFromCache = ms;
42
+ }
43
+
44
+ private notReadyUsage = 0;
45
+
46
+ getNonReadyUsage() {
47
+ return this.notReadyUsage;
48
+ }
49
+
50
+ recordNonReadyUsage() {
51
+ this.notReadyUsage++;
52
+ }
53
+
54
+ private impressionStats = [0, 0, 0];
55
+
56
+ getImpressionStats(type: ImpressionDataType) {
57
+ return this.impressionStats[type];
58
+ }
59
+
60
+ recordImpressionStats(type: ImpressionDataType, count: number) {
61
+ this.impressionStats[type] += count;
62
+ }
63
+
64
+ private eventStats = [0, 0];
65
+
66
+ getEventStats(type: EventDataType) {
67
+ return this.eventStats[type];
68
+ }
69
+
70
+ recordEventStats(type: EventDataType, count: number) {
71
+ this.eventStats[type] += count;
72
+ }
73
+
74
+ // @ts-expect-error
75
+ private lastSync: LastSync = {};
76
+
77
+ getLastSynchronization() {
78
+ return this.lastSync;
79
+ }
80
+
81
+ recordSuccessfulSync(resource: OperationType, timeMs: number) {
82
+ this.lastSync[resource] = timeMs;
83
+ }
84
+
85
+ // @ts-expect-error
86
+ private httpErrors: HttpErrors = {};
87
+
88
+ popHttpErrors() {
89
+ const result = this.httpErrors; // @ts-expect-error
90
+ this.httpErrors = {};
91
+ return result;
92
+ }
93
+
94
+ recordHttpError(resource: OperationType, status: number) {
95
+ if (!this.httpErrors[resource]) this.httpErrors[resource] = {};
96
+ if (!this.httpErrors[resource][status]) {
97
+ this.httpErrors[resource][status] = 1;
98
+ } else {
99
+ this.httpErrors[resource][status]++;
100
+ }
101
+ }
102
+
103
+ // @ts-expect-error
104
+ private httpLatencies: HttpLatencies = {};
105
+
106
+ popHttpLatencies() {
107
+ const result = this.httpLatencies; // @ts-expect-error
108
+ this.httpLatencies = {};
109
+ return result;
110
+ }
111
+
112
+ recordHttpLatency(resource: OperationType, latencyMs: number) {
113
+ if (!this.httpLatencies[resource]) {
114
+ this.httpLatencies[resource] = newBuckets();
115
+ }
116
+ this.httpLatencies[resource][findLatencyIndex(latencyMs)]++;
117
+ }
118
+
119
+ private authRejections = 0;
120
+
121
+ popAuthRejections() {
122
+ const result = this.authRejections;
123
+ this.authRejections = 0;
124
+ return result;
125
+ }
126
+
127
+ recordAuthRejections() {
128
+ this.authRejections++;
129
+ }
130
+
131
+ private tokenRefreshes = 0;
132
+
133
+ popTokenRefreshes() {
134
+ const result = this.tokenRefreshes;
135
+ this.tokenRefreshes = 0;
136
+ return result;
137
+ }
138
+
139
+ recordTokenRefreshes() {
140
+ this.tokenRefreshes++;
141
+ }
142
+
143
+ private streamingEvents: StreamingEvent[] = []
144
+
145
+ popStreamingEvents() {
146
+ return this.streamingEvents.splice(0);
147
+ }
148
+
149
+ recordStreamingEvents(streamingEvent: StreamingEvent) {
150
+ if (this.streamingEvents.length < MAX_STREAMING_EVENTS) {
151
+ this.streamingEvents.push(streamingEvent);
152
+ }
153
+ }
154
+
155
+ private tags: string[] = [];
156
+
157
+ popTags() {
158
+ return this.tags.splice(0);
159
+ }
160
+
161
+ addTag(tag: string) {
162
+ if (this.tags.length < MAX_TAGS) {
163
+ this.tags.push(tag);
164
+ }
165
+ }
166
+
167
+ private sessionLength?: number;
168
+
169
+ getSessionLength() {
170
+ return this.sessionLength;
171
+ }
172
+
173
+ recordSessionLength(ms: number) {
174
+ this.sessionLength = ms;
175
+ }
176
+
177
+ // @ts-expect-error
178
+ private exceptions: MethodExceptions = {};
179
+
180
+ popExceptions() {
181
+ const result = this.exceptions; // @ts-expect-error
182
+ this.exceptions = {};
183
+ return result;
184
+ }
185
+
186
+ recordException(method: Method) {
187
+ if (!this.exceptions[method]) {
188
+ this.exceptions[method] = 1;
189
+ } else {
190
+ this.exceptions[method]++;
191
+ }
192
+ }
193
+
194
+ // @ts-expect-error
195
+ private latencies: MethodLatencies = {};
196
+
197
+ popLatencies() {
198
+ const result = this.latencies; // @ts-expect-error
199
+ this.latencies = {};
200
+ return result;
201
+ }
202
+
203
+ recordLatency(method: Method, latencyMs: number) {
204
+ if (!this.latencies[method]) {
205
+ this.latencies[method] = newBuckets();
206
+ }
207
+ this.latencies[method][findLatencyIndex(latencyMs)]++;
208
+ }
209
+
210
+ }
@@ -0,0 +1,29 @@
1
+ import { ILogger } from '../../logger/types';
2
+ import { Method } from '../../sync/submitters/types';
3
+ import { KeyBuilderSS } from '../KeyBuilderSS';
4
+ import { ITelemetryCacheAsync } from '../types';
5
+ import { findLatencyIndex } from '../findLatencyIndex';
6
+ import { Redis } from 'ioredis';
7
+
8
+ export class TelemetryCacheInRedis implements ITelemetryCacheAsync {
9
+
10
+ /**
11
+ * Create a Telemetry cache that uses Redis as storage.
12
+ * @param log Logger instance.
13
+ * @param keys Key builder.
14
+ * @param redis Redis client.
15
+ */
16
+ constructor(private readonly log: ILogger, private readonly keys: KeyBuilderSS, private readonly redis: Redis) { }
17
+
18
+ recordLatency(method: Method, latencyMs: number) {
19
+ const [key, field] = this.keys.buildLatencyKey(method, findLatencyIndex(latencyMs)).split('::');
20
+ return this.redis.hincrby(key, field, 1)
21
+ .catch(() => { /* Handle rejections for telemetry */ });
22
+ }
23
+ recordException(method: Method) {
24
+ const [key, field] = this.keys.buildExceptionKey(method).split('::');
25
+ return this.redis.hincrby(key, field, 1)
26
+ .catch(() => { /* Handle rejections for telemetry */ });
27
+ }
28
+
29
+ }
@@ -6,9 +6,8 @@ import { SplitsCacheInRedis } from './SplitsCacheInRedis';
6
6
  import { SegmentsCacheInRedis } from './SegmentsCacheInRedis';
7
7
  import { ImpressionsCacheInRedis } from './ImpressionsCacheInRedis';
8
8
  import { EventsCacheInRedis } from './EventsCacheInRedis';
9
- import { LatenciesCacheInRedis } from './LatenciesCacheInRedis';
10
- import { CountsCacheInRedis } from './CountsCacheInRedis';
11
9
  import { STORAGE_REDIS } from '../../utils/constants';
10
+ import { TelemetryCacheInRedis } from './TelemetryCacheInRedis';
12
11
 
13
12
  export interface InRedisStorageOptions {
14
13
  prefix?: string
@@ -38,8 +37,7 @@ export function InRedisStorage(options: InRedisStorageOptions = {}): IStorageAsy
38
37
  segments: new SegmentsCacheInRedis(log, keys, redisClient),
39
38
  impressions: new ImpressionsCacheInRedis(log, keys.buildImpressionsKey(), redisClient, metadata),
40
39
  events: new EventsCacheInRedis(log, keys.buildEventsKey(), redisClient, metadata),
41
- latencies: new LatenciesCacheInRedis(keys, redisClient),
42
- counts: new CountsCacheInRedis(keys, redisClient),
40
+ telemetry: new TelemetryCacheInRedis(log, keys, redisClient),
43
41
 
44
42
  // When using REDIS we should:
45
43
  // 1- Disconnect from the storage
@@ -0,0 +1,26 @@
1
+ import { ILogger } from '../../logger/types';
2
+ import { Method } from '../../sync/submitters/types';
3
+ import { KeyBuilderSS } from '../KeyBuilderSS';
4
+ import { IPluggableStorageWrapper, ITelemetryCacheAsync } from '../types';
5
+ import { findLatencyIndex } from '../findLatencyIndex';
6
+
7
+ export class TelemetryCachePluggable implements ITelemetryCacheAsync {
8
+
9
+ /**
10
+ * Create a Telemetry cache that uses a storage wrapper.
11
+ * @param log Logger instance.
12
+ * @param keys Key builder.
13
+ * @param wrapper Adapted wrapper storage.
14
+ */
15
+ constructor(private readonly log: ILogger, private readonly keys: KeyBuilderSS, private readonly wrapper: IPluggableStorageWrapper) { }
16
+
17
+ recordLatency(method: Method, latencyMs: number) {
18
+ return this.wrapper.incr(this.keys.buildLatencyKey(method, findLatencyIndex(latencyMs)))
19
+ .catch(() => { /* Handle rejections for telemetry */ });
20
+ }
21
+ recordException(method: Method) {
22
+ return this.wrapper.incr(this.keys.buildExceptionKey(method))
23
+ .catch(() => { /* Handle rejections for telemetry */ });
24
+ }
25
+
26
+ }
@@ -77,7 +77,8 @@ export function PluggableStorage(options: PluggableStorageOptions): IStorageAsyn
77
77
  impressions: isPartialConsumer ? new ImpressionsCacheInMemory(impressionsQueueSize) : new ImpressionsCachePluggable(log, keys.buildImpressionsKey(), wrapper, metadata),
78
78
  impressionCounts: optimize ? new ImpressionCountsCacheInMemory() : undefined,
79
79
  events: isPartialConsumer ? promisifyEventsTrack(new EventsCacheInMemory(eventsQueueSize)) : new EventsCachePluggable(log, keys.buildEventsKey(), wrapper, metadata),
80
- // @TODO add telemetry cache when required
80
+ // @TODO Not using TelemetryCachePluggable yet, because it is not supported by the Split Synchronizer
81
+ // telemetry: isPartialConsumer ? new TelemetryCacheInMemory() : new TelemetryCachePluggable(log, keys, wrapper),
81
82
 
82
83
  // Disconnect the underlying storage
83
84
  destroy() {
@@ -1,6 +1,6 @@
1
1
  import { MaybeThenable, IMetadata, ISplitFiltersValidation } from '../dtos/types';
2
2
  import { ILogger } from '../logger/types';
3
- import { StoredEventWithMetadata, StoredImpressionWithMetadata } from '../sync/submitters/types';
3
+ import { EventDataType, HttpErrors, HttpLatencies, ImpressionDataType, LastSync, Method, MethodExceptions, MethodLatencies, OperationType, StoredEventWithMetadata, StoredImpressionWithMetadata, StreamingEvent } from '../sync/submitters/types';
4
4
  import { SplitIO, ImpressionDTO, SDKMode } from '../types';
5
5
 
6
6
  /**
@@ -262,6 +262,7 @@ export interface ISegmentsCacheSync extends ISegmentsCacheBase {
262
262
  isInSegment(name: string, key?: string): boolean
263
263
  registerSegments(names: string[]): boolean
264
264
  getRegisteredSegments(): string[]
265
+ getKeysCount(): number // only used for telemetry
265
266
  setChangeNumber(name: string, changeNumber: number): boolean
266
267
  getChangeNumber(name: string): number
267
268
  resetSegments(names: string[]): boolean // only for Sync Client-Side
@@ -331,11 +332,13 @@ export interface IRecorderCacheProducerAsync<T> {
331
332
 
332
333
  export interface IImpressionsCacheAsync extends IImpressionsCacheBase, IRecorderCacheProducerAsync<StoredImpressionWithMetadata[]> {
333
334
  // Consumer API method, used by impressions tracker (in standalone and consumer modes) to push data into.
335
+ // The result promise can reject.
334
336
  track(data: ImpressionDTO[]): Promise<void>
335
337
  }
336
338
 
337
339
  export interface IEventsCacheAsync extends IEventsCacheBase, IRecorderCacheProducerAsync<StoredEventWithMetadata[]> {
338
340
  // Consumer API method, used by events tracker (in standalone and consumer modes) to push data into.
341
+ // The result promise cannot reject.
339
342
  track(data: SplitIO.EventData, size?: number): Promise<boolean>
340
343
  }
341
344
 
@@ -354,31 +357,86 @@ export interface IImpressionCountsCacheSync extends IRecorderCacheProducerSync<R
354
357
  }
355
358
 
356
359
 
357
- /** Latencies and metric counts cache */
358
- // @TODO remove. They are deprecated.
360
+ /**
361
+ * Telemetry storage interface for standalone and partial consumer modes.
362
+ * Methods are sync because data is stored in memory.
363
+ */
359
364
 
360
- export interface ILatenciesCacheSync extends IRecorderCacheProducerSync<Record<string, number[]>> {
361
- track(metricName: string, latency: number): boolean
362
- isEmpty(): boolean
363
- clear(): void
364
- state(): Record<string, number[]>
365
+ export interface ITelemetryInitConsumerSync {
366
+ getTimeUntilReady(): number | undefined;
367
+ getTimeUntilReadyFromCache(): number | undefined;
368
+ getNonReadyUsage(): number;
369
+ // 'active factories' and 'redundant factories' are not tracked in the storage. They are derived from `usedKeysMap`
365
370
  }
366
371
 
367
- export interface ILatenciesCacheAsync {
368
- track(metricName: string, latency: number): Promise<boolean>
372
+ export interface ITelemetryRuntimeConsumerSync {
373
+ getImpressionStats(type: ImpressionDataType): number;
374
+ getEventStats(type: EventDataType): number;
375
+ getLastSynchronization(): LastSync;
376
+ popHttpErrors(): HttpErrors;
377
+ popHttpLatencies(): HttpLatencies;
378
+ popAuthRejections(): number;
379
+ popTokenRefreshes(): number;
380
+ popStreamingEvents(): Array<StreamingEvent>;
381
+ popTags(): Array<string> | undefined;
382
+ getSessionLength(): number | undefined;
369
383
  }
370
384
 
371
- export interface ICountsCacheSync extends IRecorderCacheProducerSync<Record<string, number>> {
372
- track(metricName: string): boolean
373
- isEmpty(): boolean
374
- clear(): void
375
- state(): Record<string, number>
385
+ export interface ITelemetryEvaluationConsumerSync {
386
+ popExceptions(): MethodExceptions;
387
+ popLatencies(): MethodLatencies;
388
+ }
389
+
390
+ export interface ITelemetryStorageConsumerSync extends ITelemetryInitConsumerSync, ITelemetryRuntimeConsumerSync, ITelemetryEvaluationConsumerSync { }
391
+
392
+ export interface ITelemetryInitProducerSync {
393
+ recordTimeUntilReady(ms: number): void;
394
+ recordTimeUntilReadyFromCache(ms: number): void;
395
+ recordNonReadyUsage(): void;
396
+ // 'active factories' and 'redundant factories' are not tracked in the storage. They are derived from `usedKeysMap`
397
+ }
398
+
399
+ export interface ITelemetryRuntimeProducerSync {
400
+ addTag(tag: string): void;
401
+ recordImpressionStats(type: ImpressionDataType, count: number): void;
402
+ recordEventStats(type: EventDataType, count: number): void;
403
+ recordSuccessfulSync(resource: OperationType, timeMs: number): void;
404
+ recordHttpError(resource: OperationType, status: number): void;
405
+ recordHttpLatency(resource: OperationType, latencyMs: number): void;
406
+ recordAuthRejections(): void;
407
+ recordTokenRefreshes(): void;
408
+ recordStreamingEvents(streamingEvent: StreamingEvent): void;
409
+ recordSessionLength(ms: number): void;
376
410
  }
377
411
 
378
- export interface ICountsCacheAsync {
379
- track(metricName: string): Promise<boolean>
412
+ export interface ITelemetryEvaluationProducerSync {
413
+ recordLatency(method: Method, latencyMs: number): void;
414
+ recordException(method: Method): void;
380
415
  }
381
416
 
417
+ export interface ITelemetryStorageProducerSync extends ITelemetryInitProducerSync, ITelemetryRuntimeProducerSync, ITelemetryEvaluationProducerSync { }
418
+
419
+ export interface ITelemetryCacheSync extends ITelemetryStorageConsumerSync, ITelemetryStorageProducerSync { }
420
+
421
+ /**
422
+ * Telemetry storage interface for consumer mode.
423
+ * Methods are async because data is stored in Redis or a pluggable storage.
424
+ */
425
+
426
+ export interface ITelemetryEvaluationConsumerAsync {
427
+ popExceptions(): Promise<MethodExceptions>;
428
+ popLatencies(): Promise<MethodLatencies>;
429
+ }
430
+
431
+ export interface ITelemetryEvaluationProducerAsync {
432
+ recordLatency(method: Method, latencyMs: number): Promise<any>;
433
+ recordException(method: Method): Promise<any>;
434
+ }
435
+
436
+ // ATM it only implements the producer API, used by the SDK in consumer mode.
437
+ // @TODO implement consumer API for JS Synchronizer.
438
+ export interface ITelemetryCacheAsync extends ITelemetryEvaluationProducerAsync { }
439
+
382
440
  /**
383
441
  * Storages
384
442
  */
@@ -388,37 +446,33 @@ export interface IStorageBase<
388
446
  TSegmentsCache extends ISegmentsCacheBase,
389
447
  TImpressionsCache extends IImpressionsCacheBase,
390
448
  TEventsCache extends IEventsCacheBase,
391
- TLatenciesCache extends ILatenciesCacheSync | ILatenciesCacheAsync,
392
- TCountsCache extends ICountsCacheSync | ICountsCacheAsync,
449
+ TTelemetryCache extends ITelemetryCacheSync | ITelemetryCacheAsync
393
450
  > {
394
451
  splits: TSplitsCache,
395
452
  segments: TSegmentsCache,
396
453
  impressions: TImpressionsCache,
397
454
  impressionCounts?: IImpressionCountsCacheSync,
398
455
  events: TEventsCache,
399
- latencies?: TLatenciesCache,
400
- counts?: TCountsCache,
456
+ telemetry?: TTelemetryCache
401
457
  destroy(): void | Promise<void>,
402
458
  shared?: (matchingKey: string, onReadyCb: (error?: any) => void) => this
403
459
  }
404
460
 
405
- export type IStorageSync = IStorageBase<
461
+ export interface IStorageSync extends IStorageBase<
406
462
  ISplitsCacheSync,
407
463
  ISegmentsCacheSync,
408
464
  IImpressionsCacheSync,
409
465
  IEventsCacheSync,
410
- ILatenciesCacheSync,
411
- ICountsCacheSync
412
- >
466
+ ITelemetryCacheSync
467
+ > { }
413
468
 
414
- export type IStorageAsync = IStorageBase<
469
+ export interface IStorageAsync extends IStorageBase<
415
470
  ISplitsCacheAsync,
416
471
  ISegmentsCacheAsync,
417
472
  IImpressionsCacheAsync | IImpressionsCacheSync,
418
473
  IEventsCacheAsync | IEventsCacheSync,
419
- ILatenciesCacheAsync,
420
- ICountsCacheAsync
421
- >
474
+ ITelemetryCacheAsync
475
+ > { }
422
476
 
423
477
  /** StorageFactory */
424
478
 
@@ -429,14 +483,12 @@ export interface IStorageFactoryParams {
429
483
  impressionsQueueSize?: number,
430
484
  eventsQueueSize?: number,
431
485
  optimize?: boolean /* whether create the `impressionCounts` cache (OPTIMIZED impression mode) or not (DEBUG impression mode) */,
486
+ mode: SDKMode,
432
487
 
433
488
  // ATM, only used by InLocalStorage
434
489
  matchingKey?: string, /* undefined on server-side SDKs */
435
490
  splitFiltersValidation?: ISplitFiltersValidation,
436
491
 
437
- // ATM, only used by PluggableStorage
438
- mode?: SDKMode,
439
-
440
492
  // This callback is invoked when the storage is ready to be used. Error-first callback style: if an error is passed,
441
493
  // it means that the storge fail to connect and shouldn't be used.
442
494
  // It is meant for emitting SDK_READY event in consumer mode, and for synchronizer to wait before using the storage.
@@ -1,9 +1,10 @@
1
- import { ISyncManager, ISyncManagerCS, ISyncManagerFactoryParams } from '../types';
1
+ import { ISyncManager, ISyncManagerCS } from '../types';
2
2
  import { fromObjectSyncTaskFactory } from './syncTasks/fromObjectSyncTask';
3
3
  import { objectAssign } from '../../utils/lang/objectAssign';
4
4
  import { ISplitsParser } from './splitsParser/types';
5
5
  import { IReadinessManager } from '../../readiness/types';
6
6
  import { SDK_SEGMENTS_ARRIVED } from '../../readiness/constants';
7
+ import { ISdkFactoryContextSync } from '../../sdkFactory/types';
7
8
 
8
9
  function flush() {
9
10
  return Promise.resolve();
@@ -17,7 +18,7 @@ function flush() {
17
18
  */
18
19
  export function syncManagerOfflineFactory(
19
20
  splitsParserFactory: () => ISplitsParser
20
- ): (params: ISyncManagerFactoryParams) => ISyncManagerCS {
21
+ ): (params: ISdkFactoryContextSync) => ISyncManagerCS {
21
22
 
22
23
  /**
23
24
  * SyncManager factory for modular SDK
@@ -26,7 +27,7 @@ export function syncManagerOfflineFactory(
26
27
  settings,
27
28
  readiness,
28
29
  storage,
29
- }: ISyncManagerFactoryParams): ISyncManagerCS {
30
+ }: ISdkFactoryContextSync): ISyncManagerCS {
30
31
 
31
32
  return objectAssign(
32
33
  fromObjectSyncTaskFactory(splitsParserFactory(), storage, readiness, settings),
@@ -7,20 +7,20 @@ import { splitsSyncTaskFactory } from './syncTasks/splitsSyncTask';
7
7
  import { getMatching } from '../../utils/key';
8
8
  import { SDK_SPLITS_ARRIVED, SDK_SEGMENTS_ARRIVED } from '../../readiness/constants';
9
9
  import { POLLING_SMART_PAUSING, POLLING_START, POLLING_STOP } from '../../logger/constants';
10
- import { ISyncManagerFactoryParams } from '../types';
10
+ import { ISdkFactoryContextSync } from '../../sdkFactory/types';
11
11
 
12
12
  /**
13
13
  * Expose start / stop mechanism for polling data from services.
14
14
  * For client-side API with multiple clients.
15
15
  */
16
16
  export function pollingManagerCSFactory(
17
- params: ISyncManagerFactoryParams
17
+ params: ISdkFactoryContextSync
18
18
  ): IPollingManagerCS {
19
19
 
20
20
  const { splitApi, storage, readiness, settings } = params;
21
21
  const log = settings.log;
22
22
 
23
- const splitsSyncTask: ISplitsSyncTask = splitsSyncTaskFactory(splitApi.fetchSplitChanges, storage, readiness, settings);
23
+ const splitsSyncTask: ISplitsSyncTask = splitsSyncTaskFactory(splitApi.fetchSplitChanges, storage, readiness, settings, true);
24
24
 
25
25
  // Map of matching keys to their corresponding MySegmentsSyncTask.
26
26
  const mySegmentsSyncTasks: Record<string, ISegmentsSyncTask> = {};
@@ -3,13 +3,13 @@ import { segmentsSyncTaskFactory } from './syncTasks/segmentsSyncTask';
3
3
  import { IPollingManager, ISegmentsSyncTask, ISplitsSyncTask } from './types';
4
4
  import { thenable } from '../../utils/promise/thenable';
5
5
  import { POLLING_START, POLLING_STOP, LOG_PREFIX_SYNC_POLLING } from '../../logger/constants';
6
- import { ISyncManagerFactoryParams } from '../types';
6
+ import { ISdkFactoryContextSync } from '../../sdkFactory/types';
7
7
 
8
8
  /**
9
9
  * Expose start / stop mechanism for pulling data from services.
10
10
  */
11
11
  export function pollingManagerSSFactory(
12
- params: ISyncManagerFactoryParams
12
+ params: ISdkFactoryContextSync
13
13
  ): IPollingManager {
14
14
 
15
15
  const { splitApi, storage, readiness, settings } = params;
@@ -15,6 +15,7 @@ export function splitsSyncTaskFactory(
15
15
  storage: IStorageSync,
16
16
  readiness: IReadinessManager,
17
17
  settings: ISettings,
18
+ isClientSide?: boolean
18
19
  ): ISplitsSyncTask {
19
20
  return syncTaskFactory(
20
21
  settings.log,
@@ -26,6 +27,7 @@ export function splitsSyncTaskFactory(
26
27
  readiness.splits,
27
28
  settings.startup.requestTimeoutBeforeReady,
28
29
  settings.startup.retriesOnFailureBeforeReady,
30
+ isClientSide
29
31
  ),
30
32
  settings.scheduler.featuresRefreshRate,
31
33
  'splitChangesUpdater',
@@ -33,10 +33,6 @@ export function mySegmentsUpdaterFactory(
33
33
  function _promiseDecorator<T>(promise: Promise<T>) {
34
34
  if (startingUp) promise = timeout(requestTimeoutBeforeReady, promise);
35
35
  return promise;
36
-
37
- // @TODO telemetry
38
- // NOTE: We only collect metrics on startup.
39
- // mySegmentsPromise = tracker.start(tracker.TaskNames.MY_SEGMENTS_FETCH, startingUp ? metricCollectors : false, mySegmentsPromise);
40
36
  }
41
37
 
42
38
  // @TODO if allowing pluggable storages, handle async execution