@splitsoftware/splitio-commons 1.3.2-rc.3 → 1.3.2-rc.4

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.
@@ -7,6 +7,7 @@ var constants_1 = require("../utils/constants");
7
7
  var objectAssign_1 = require("../utils/lang/objectAssign");
8
8
  var constants_2 = require("../logger/constants");
9
9
  var consent_1 = require("../consent");
10
+ var telemetrySubmitter_1 = require("../sync/submitters/telemetrySubmitter");
10
11
  // 'unload' event is used instead of 'beforeunload', since 'unload' is not a cancelable event, so no other listeners can stop the event from occurring.
11
12
  var UNLOAD_DOM_EVENT = 'unload';
12
13
  var EVENT_NAME = 'for unload page event.';
@@ -63,7 +64,11 @@ var BrowserSignalListener = /** @class */ (function () {
63
64
  this._flushData(eventsUrl + '/events/beacon', this.storage.events, this.serviceApi.postEventsBulk);
64
65
  if (this.storage.impressionCounts)
65
66
  this._flushData(eventsUrl + '/testImpressions/count/beacon', this.storage.impressionCounts, this.serviceApi.postTestImpressionsCount, impressionCountsSubmitter_1.fromImpressionCountsCollector);
66
- // No beacon endpoint for `/metrics/usage`
67
+ if (this.storage.telemetry) {
68
+ var telemetryUrl = this.settings.urls.telemetry;
69
+ var telemetryCacheAdapter = (0, telemetrySubmitter_1.telemetryCacheStatsAdapter)(this.storage.telemetry, this.storage.splits, this.storage.segments);
70
+ this._flushData(telemetryUrl + '/v1/metrics/usage/beacon', telemetryCacheAdapter, this.serviceApi.postMetricsUsage);
71
+ }
67
72
  }
68
73
  // Close streaming connection
69
74
  if (this.syncManager.pushManager)
@@ -20,23 +20,27 @@ var KeyBuilderSS = /** @class */ (function (_super) {
20
20
  KeyBuilderSS.prototype.buildRegisteredSegmentsKey = function () {
21
21
  return this.prefix + ".segments.registered";
22
22
  };
23
- KeyBuilderSS.prototype.buildVersionablePrefix = function () {
24
- return this.metadata.s + "/" + this.metadata.n + "/" + this.metadata.i;
25
- };
26
23
  KeyBuilderSS.prototype.buildImpressionsKey = function () {
27
24
  return this.prefix + ".impressions";
28
25
  };
29
26
  KeyBuilderSS.prototype.buildEventsKey = function () {
30
27
  return this.prefix + ".events";
31
28
  };
29
+ KeyBuilderSS.prototype.searchPatternForSplitKeys = function () {
30
+ return this.buildSplitKeyPrefix() + "*";
31
+ };
32
+ /* Telemetry keys */
32
33
  KeyBuilderSS.prototype.buildLatencyKey = function (method, bucket) {
33
34
  return this.prefix + ".telemetry.latencies::" + this.buildVersionablePrefix() + "/" + methodNames[method] + "/" + bucket;
34
35
  };
35
36
  KeyBuilderSS.prototype.buildExceptionKey = function (method) {
36
37
  return this.prefix + ".telemetry.exceptions::" + this.buildVersionablePrefix() + "/" + methodNames[method];
37
38
  };
38
- KeyBuilderSS.prototype.searchPatternForSplitKeys = function () {
39
- return this.buildSplitKeyPrefix() + "*";
39
+ KeyBuilderSS.prototype.buildInitKey = function () {
40
+ return this.prefix + ".telemetry.init::" + this.buildVersionablePrefix();
41
+ };
42
+ KeyBuilderSS.prototype.buildVersionablePrefix = function () {
43
+ return this.metadata.s + "/" + this.metadata.n + "/" + this.metadata.i;
40
44
  };
41
45
  return KeyBuilderSS;
42
46
  }(KeyBuilder_1.KeyBuilder));
@@ -9,7 +9,7 @@ var thenable_1 = require("../../utils/promise/thenable");
9
9
  var timeout_1 = require("../../utils/promise/timeout");
10
10
  var LOG_PREFIX = 'storage:redis-adapter: ';
11
11
  // If we ever decide to fully wrap every method, there's a Commander.getBuiltinCommands from ioredis.
12
- var METHODS_TO_PROMISE_WRAP = ['set', 'exec', 'del', 'get', 'keys', 'sadd', 'srem', 'sismember', 'smembers', 'incr', 'rpush', 'pipeline', 'expire', 'mget', 'lrange', 'ltrim'];
12
+ var METHODS_TO_PROMISE_WRAP = ['set', 'exec', 'del', 'get', 'keys', 'sadd', 'srem', 'sismember', 'smembers', 'incr', 'rpush', 'pipeline', 'expire', 'mget', 'lrange', 'ltrim', 'hset'];
13
13
  // Not part of the settings since it'll vary on each storage. We should be removing storage specific logic from elsewhere.
14
14
  var DEFAULT_OPTIONS = {
15
15
  connectionTimeout: 10000,
@@ -2,6 +2,8 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.TelemetryCacheInRedis = void 0;
4
4
  var findLatencyIndex_1 = require("../findLatencyIndex");
5
+ var telemetrySubmitter_1 = require("../../sync/submitters/telemetrySubmitter");
6
+ var constants_1 = require("../../utils/constants");
5
7
  var TelemetryCacheInRedis = /** @class */ (function () {
6
8
  /**
7
9
  * Create a Telemetry cache that uses Redis as storage.
@@ -24,6 +26,11 @@ var TelemetryCacheInRedis = /** @class */ (function () {
24
26
  return this.redis.hincrby(key, field, 1)
25
27
  .catch(function () { });
26
28
  };
29
+ TelemetryCacheInRedis.prototype.recordConfig = function () {
30
+ var _a = this.keys.buildInitKey().split('::'), key = _a[0], field = _a[1];
31
+ var value = JSON.stringify((0, telemetrySubmitter_1.getTelemetryConfigStats)(constants_1.CONSUMER_MODE, constants_1.STORAGE_REDIS));
32
+ return this.redis.hset(key, field, value).catch(function () { });
33
+ };
27
34
  return TelemetryCacheInRedis;
28
35
  }());
29
36
  exports.TelemetryCacheInRedis = TelemetryCacheInRedis;
@@ -21,16 +21,19 @@ function InRedisStorage(options) {
21
21
  var log = _a.log, metadata = _a.metadata, onReadyCb = _a.onReadyCb;
22
22
  var keys = new KeyBuilderSS_1.KeyBuilderSS(prefix, metadata);
23
23
  var redisClient = new RedisAdapter_1.RedisAdapter(log, options.options || {});
24
+ var telemetry = new TelemetryCacheInRedis_1.TelemetryCacheInRedis(log, keys, redisClient);
24
25
  // subscription to Redis connect event in order to emit SDK_READY event on consumer mode
25
26
  redisClient.on('connect', function () {
26
27
  onReadyCb();
28
+ // Synchronize config
29
+ telemetry.recordConfig();
27
30
  });
28
31
  return {
29
32
  splits: new SplitsCacheInRedis_1.SplitsCacheInRedis(log, keys, redisClient),
30
33
  segments: new SegmentsCacheInRedis_1.SegmentsCacheInRedis(log, keys, redisClient),
31
34
  impressions: new ImpressionsCacheInRedis_1.ImpressionsCacheInRedis(log, keys.buildImpressionsKey(), redisClient, metadata),
32
35
  events: new EventsCacheInRedis_1.EventsCacheInRedis(log, keys.buildEventsKey(), redisClient, metadata),
33
- telemetry: new TelemetryCacheInRedis_1.TelemetryCacheInRedis(log, keys, redisClient),
36
+ telemetry: telemetry,
34
37
  // When using REDIS we should:
35
38
  // 1- Disconnect from the storage
36
39
  destroy: function () {
@@ -34,6 +34,7 @@ function validatePluggableStorageOptions(options) {
34
34
  function wrapperConnect(wrapper, onReadyCb) {
35
35
  wrapper.connect().then(function () {
36
36
  onReadyCb();
37
+ // At the moment, we don't synchronize config with pluggable storage
37
38
  }).catch(function (e) {
38
39
  onReadyCb(e || new Error('Error connecting wrapper'));
39
40
  });
@@ -66,7 +67,7 @@ function PluggableStorage(options) {
66
67
  impressions: isPartialConsumer ? new ImpressionsCacheInMemory_1.ImpressionsCacheInMemory(impressionsQueueSize) : new ImpressionsCachePluggable_1.ImpressionsCachePluggable(log, keys.buildImpressionsKey(), wrapper, metadata),
67
68
  impressionCounts: optimize ? new ImpressionCountsCacheInMemory_1.ImpressionCountsCacheInMemory() : undefined,
68
69
  events: isPartialConsumer ? promisifyEventsTrack(new EventsCacheInMemory_1.EventsCacheInMemory(eventsQueueSize)) : new EventsCachePluggable_1.EventsCachePluggable(log, keys.buildEventsKey(), wrapper, metadata),
69
- // @TODO Not using TelemetryCachePluggable yet, because it is not supported by the Split Synchronizer
70
+ // @TODO Not using TelemetryCachePluggable yet because it's not supported by the Split Synchronizer, and needs to drop or queue operations while the wrapper is not ready
70
71
  // telemetry: isPartialConsumer ? new TelemetryCacheInMemory() : new TelemetryCachePluggable(log, keys, wrapper),
71
72
  // Disconnect the underlying storage
72
73
  destroy: function () {
@@ -1,13 +1,14 @@
1
1
  "use strict";
2
- var _a, _b;
2
+ var _a, _b, _c;
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
- exports.telemetrySubmitterFactory = exports.telemetryCacheConfigAdapter = exports.telemetryCacheStatsAdapter = void 0;
4
+ exports.telemetrySubmitterFactory = exports.telemetryCacheConfigAdapter = exports.getTelemetryConfigStats = exports.telemetryCacheStatsAdapter = void 0;
5
5
  var submitter_1 = require("./submitter");
6
6
  var constants_1 = require("../../utils/constants");
7
7
  var constants_2 = require("../../readiness/constants");
8
8
  var settingsValidation_1 = require("../../utils/settingsValidation");
9
9
  var apiKey_1 = require("../../utils/inputValidation/apiKey");
10
10
  var timer_1 = require("../../utils/timeTracker/timer");
11
+ var objectAssign_1 = require("../../utils/lang/objectAssign");
11
12
  /**
12
13
  * Converts data from telemetry cache into /metrics/usage request payload.
13
14
  */
@@ -50,6 +51,11 @@ var IMPRESSIONS_MODE_MAP = (_b = {},
50
51
  _b[constants_1.OPTIMIZED] = constants_1.OPTIMIZED_ENUM,
51
52
  _b[constants_1.DEBUG] = constants_1.DEBUG_ENUM,
52
53
  _b);
54
+ var USER_CONSENT_MAP = (_c = {},
55
+ _c[constants_1.CONSENT_UNKNOWN] = 1,
56
+ _c[constants_1.CONSENT_GRANTED] = 2,
57
+ _c[constants_1.CONSENT_DECLINED] = 3,
58
+ _c);
53
59
  function getActiveFactories() {
54
60
  return Object.keys(apiKey_1.usedKeysMap).length;
55
61
  }
@@ -58,6 +64,15 @@ function getRedundantActiveFactories() {
58
64
  return acum + apiKey_1.usedKeysMap[apiKey] - 1;
59
65
  }, 0);
60
66
  }
67
+ function getTelemetryConfigStats(mode, storageType) {
68
+ return {
69
+ oM: OPERATION_MODE_MAP[mode],
70
+ st: storageType.toLowerCase(),
71
+ aF: getActiveFactories(),
72
+ rF: getRedundantActiveFactories(),
73
+ };
74
+ }
75
+ exports.getTelemetryConfigStats = getTelemetryConfigStats;
61
76
  /**
62
77
  * Converts data from telemetry cache and settings into /metrics/config request payload.
63
78
  */
@@ -67,9 +82,7 @@ function telemetryCacheConfigAdapter(telemetry, settings) {
67
82
  clear: function () { },
68
83
  state: function () {
69
84
  var urls = settings.urls, scheduler = settings.scheduler;
70
- return {
71
- oM: OPERATION_MODE_MAP[settings.mode],
72
- st: settings.storage.type.toLowerCase(),
85
+ return (0, objectAssign_1.objectAssign)(getTelemetryConfigStats(settings.mode, settings.storage.type), {
73
86
  sE: settings.streamingEnabled,
74
87
  rR: {
75
88
  sp: scheduler.featuresRefreshRate,
@@ -90,14 +103,13 @@ function telemetryCacheConfigAdapter(telemetry, settings) {
90
103
  iM: IMPRESSIONS_MODE_MAP[settings.sync.impressionsMode],
91
104
  iL: settings.impressionListener ? true : false,
92
105
  hP: false,
93
- aF: getActiveFactories(),
94
- rF: getRedundantActiveFactories(),
95
106
  tR: telemetry.getTimeUntilReady(),
96
107
  tC: telemetry.getTimeUntilReadyFromCache(),
97
108
  nR: telemetry.getNonReadyUsage(),
98
109
  t: telemetry.popTags(),
99
110
  i: settings.integrations && settings.integrations.map(function (int) { return int.type; }),
100
- };
111
+ uC: settings.userConsent ? USER_CONSENT_MAP[settings.userConsent] : 0
112
+ });
101
113
  }
102
114
  };
103
115
  }
@@ -4,6 +4,7 @@ import { OPTIMIZED, DEBUG } from '../utils/constants';
4
4
  import { objectAssign } from '../utils/lang/objectAssign';
5
5
  import { CLEANUP_REGISTERING, CLEANUP_DEREGISTERING } from '../logger/constants';
6
6
  import { isConsentGranted } from '../consent';
7
+ import { telemetryCacheStatsAdapter } from '../sync/submitters/telemetrySubmitter';
7
8
  // 'unload' event is used instead of 'beforeunload', since 'unload' is not a cancelable event, so no other listeners can stop the event from occurring.
8
9
  var UNLOAD_DOM_EVENT = 'unload';
9
10
  var EVENT_NAME = 'for unload page event.';
@@ -60,7 +61,11 @@ var BrowserSignalListener = /** @class */ (function () {
60
61
  this._flushData(eventsUrl + '/events/beacon', this.storage.events, this.serviceApi.postEventsBulk);
61
62
  if (this.storage.impressionCounts)
62
63
  this._flushData(eventsUrl + '/testImpressions/count/beacon', this.storage.impressionCounts, this.serviceApi.postTestImpressionsCount, fromImpressionCountsCollector);
63
- // No beacon endpoint for `/metrics/usage`
64
+ if (this.storage.telemetry) {
65
+ var telemetryUrl = this.settings.urls.telemetry;
66
+ var telemetryCacheAdapter = telemetryCacheStatsAdapter(this.storage.telemetry, this.storage.splits, this.storage.segments);
67
+ this._flushData(telemetryUrl + '/v1/metrics/usage/beacon', telemetryCacheAdapter, this.serviceApi.postMetricsUsage);
68
+ }
64
69
  }
65
70
  // Close streaming connection
66
71
  if (this.syncManager.pushManager)
@@ -17,23 +17,27 @@ var KeyBuilderSS = /** @class */ (function (_super) {
17
17
  KeyBuilderSS.prototype.buildRegisteredSegmentsKey = function () {
18
18
  return this.prefix + ".segments.registered";
19
19
  };
20
- KeyBuilderSS.prototype.buildVersionablePrefix = function () {
21
- return this.metadata.s + "/" + this.metadata.n + "/" + this.metadata.i;
22
- };
23
20
  KeyBuilderSS.prototype.buildImpressionsKey = function () {
24
21
  return this.prefix + ".impressions";
25
22
  };
26
23
  KeyBuilderSS.prototype.buildEventsKey = function () {
27
24
  return this.prefix + ".events";
28
25
  };
26
+ KeyBuilderSS.prototype.searchPatternForSplitKeys = function () {
27
+ return this.buildSplitKeyPrefix() + "*";
28
+ };
29
+ /* Telemetry keys */
29
30
  KeyBuilderSS.prototype.buildLatencyKey = function (method, bucket) {
30
31
  return this.prefix + ".telemetry.latencies::" + this.buildVersionablePrefix() + "/" + methodNames[method] + "/" + bucket;
31
32
  };
32
33
  KeyBuilderSS.prototype.buildExceptionKey = function (method) {
33
34
  return this.prefix + ".telemetry.exceptions::" + this.buildVersionablePrefix() + "/" + methodNames[method];
34
35
  };
35
- KeyBuilderSS.prototype.searchPatternForSplitKeys = function () {
36
- return this.buildSplitKeyPrefix() + "*";
36
+ KeyBuilderSS.prototype.buildInitKey = function () {
37
+ return this.prefix + ".telemetry.init::" + this.buildVersionablePrefix();
38
+ };
39
+ KeyBuilderSS.prototype.buildVersionablePrefix = function () {
40
+ return this.metadata.s + "/" + this.metadata.n + "/" + this.metadata.i;
37
41
  };
38
42
  return KeyBuilderSS;
39
43
  }(KeyBuilder));
@@ -6,7 +6,7 @@ import { thenable } from '../../utils/promise/thenable';
6
6
  import { timeout } from '../../utils/promise/timeout';
7
7
  var LOG_PREFIX = 'storage:redis-adapter: ';
8
8
  // If we ever decide to fully wrap every method, there's a Commander.getBuiltinCommands from ioredis.
9
- var METHODS_TO_PROMISE_WRAP = ['set', 'exec', 'del', 'get', 'keys', 'sadd', 'srem', 'sismember', 'smembers', 'incr', 'rpush', 'pipeline', 'expire', 'mget', 'lrange', 'ltrim'];
9
+ var METHODS_TO_PROMISE_WRAP = ['set', 'exec', 'del', 'get', 'keys', 'sadd', 'srem', 'sismember', 'smembers', 'incr', 'rpush', 'pipeline', 'expire', 'mget', 'lrange', 'ltrim', 'hset'];
10
10
  // Not part of the settings since it'll vary on each storage. We should be removing storage specific logic from elsewhere.
11
11
  var DEFAULT_OPTIONS = {
12
12
  connectionTimeout: 10000,
@@ -1,4 +1,6 @@
1
1
  import { findLatencyIndex } from '../findLatencyIndex';
2
+ import { getTelemetryConfigStats } from '../../sync/submitters/telemetrySubmitter';
3
+ import { CONSUMER_MODE, STORAGE_REDIS } from '../../utils/constants';
2
4
  var TelemetryCacheInRedis = /** @class */ (function () {
3
5
  /**
4
6
  * Create a Telemetry cache that uses Redis as storage.
@@ -21,6 +23,11 @@ var TelemetryCacheInRedis = /** @class */ (function () {
21
23
  return this.redis.hincrby(key, field, 1)
22
24
  .catch(function () { });
23
25
  };
26
+ TelemetryCacheInRedis.prototype.recordConfig = function () {
27
+ var _a = this.keys.buildInitKey().split('::'), key = _a[0], field = _a[1];
28
+ var value = JSON.stringify(getTelemetryConfigStats(CONSUMER_MODE, STORAGE_REDIS));
29
+ return this.redis.hset(key, field, value).catch(function () { });
30
+ };
24
31
  return TelemetryCacheInRedis;
25
32
  }());
26
33
  export { TelemetryCacheInRedis };
@@ -18,16 +18,19 @@ export function InRedisStorage(options) {
18
18
  var log = _a.log, metadata = _a.metadata, onReadyCb = _a.onReadyCb;
19
19
  var keys = new KeyBuilderSS(prefix, metadata);
20
20
  var redisClient = new RedisAdapter(log, options.options || {});
21
+ var telemetry = new TelemetryCacheInRedis(log, keys, redisClient);
21
22
  // subscription to Redis connect event in order to emit SDK_READY event on consumer mode
22
23
  redisClient.on('connect', function () {
23
24
  onReadyCb();
25
+ // Synchronize config
26
+ telemetry.recordConfig();
24
27
  });
25
28
  return {
26
29
  splits: new SplitsCacheInRedis(log, keys, redisClient),
27
30
  segments: new SegmentsCacheInRedis(log, keys, redisClient),
28
31
  impressions: new ImpressionsCacheInRedis(log, keys.buildImpressionsKey(), redisClient, metadata),
29
32
  events: new EventsCacheInRedis(log, keys.buildEventsKey(), redisClient, metadata),
30
- telemetry: new TelemetryCacheInRedis(log, keys, redisClient),
33
+ telemetry: telemetry,
31
34
  // When using REDIS we should:
32
35
  // 1- Disconnect from the storage
33
36
  destroy: function () {
@@ -31,6 +31,7 @@ function validatePluggableStorageOptions(options) {
31
31
  function wrapperConnect(wrapper, onReadyCb) {
32
32
  wrapper.connect().then(function () {
33
33
  onReadyCb();
34
+ // At the moment, we don't synchronize config with pluggable storage
34
35
  }).catch(function (e) {
35
36
  onReadyCb(e || new Error('Error connecting wrapper'));
36
37
  });
@@ -63,7 +64,7 @@ export function PluggableStorage(options) {
63
64
  impressions: isPartialConsumer ? new ImpressionsCacheInMemory(impressionsQueueSize) : new ImpressionsCachePluggable(log, keys.buildImpressionsKey(), wrapper, metadata),
64
65
  impressionCounts: optimize ? new ImpressionCountsCacheInMemory() : undefined,
65
66
  events: isPartialConsumer ? promisifyEventsTrack(new EventsCacheInMemory(eventsQueueSize)) : new EventsCachePluggable(log, keys.buildEventsKey(), wrapper, metadata),
66
- // @TODO Not using TelemetryCachePluggable yet, because it is not supported by the Split Synchronizer
67
+ // @TODO Not using TelemetryCachePluggable yet because it's not supported by the Split Synchronizer, and needs to drop or queue operations while the wrapper is not ready
67
68
  // telemetry: isPartialConsumer ? new TelemetryCacheInMemory() : new TelemetryCachePluggable(log, keys, wrapper),
68
69
  // Disconnect the underlying storage
69
70
  destroy: function () {
@@ -1,10 +1,11 @@
1
- var _a, _b;
1
+ var _a, _b, _c;
2
2
  import { submitterFactory, firstPushWindowDecorator } from './submitter';
3
- 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';
3
+ import { QUEUED, DEDUPED, DROPPED, CONSUMER_MODE, CONSUMER_ENUM, STANDALONE_MODE, CONSUMER_PARTIAL_MODE, STANDALONE_ENUM, CONSUMER_PARTIAL_ENUM, OPTIMIZED, DEBUG, DEBUG_ENUM, OPTIMIZED_ENUM, CONSENT_GRANTED, CONSENT_DECLINED, CONSENT_UNKNOWN } from '../../utils/constants';
4
4
  import { SDK_READY, SDK_READY_FROM_CACHE } from '../../readiness/constants';
5
5
  import { base } from '../../utils/settingsValidation';
6
6
  import { usedKeysMap } from '../../utils/inputValidation/apiKey';
7
7
  import { timer } from '../../utils/timeTracker/timer';
8
+ import { objectAssign } from '../../utils/lang/objectAssign';
8
9
  /**
9
10
  * Converts data from telemetry cache into /metrics/usage request payload.
10
11
  */
@@ -46,6 +47,11 @@ var IMPRESSIONS_MODE_MAP = (_b = {},
46
47
  _b[OPTIMIZED] = OPTIMIZED_ENUM,
47
48
  _b[DEBUG] = DEBUG_ENUM,
48
49
  _b);
50
+ var USER_CONSENT_MAP = (_c = {},
51
+ _c[CONSENT_UNKNOWN] = 1,
52
+ _c[CONSENT_GRANTED] = 2,
53
+ _c[CONSENT_DECLINED] = 3,
54
+ _c);
49
55
  function getActiveFactories() {
50
56
  return Object.keys(usedKeysMap).length;
51
57
  }
@@ -54,6 +60,14 @@ function getRedundantActiveFactories() {
54
60
  return acum + usedKeysMap[apiKey] - 1;
55
61
  }, 0);
56
62
  }
63
+ export function getTelemetryConfigStats(mode, storageType) {
64
+ return {
65
+ oM: OPERATION_MODE_MAP[mode],
66
+ st: storageType.toLowerCase(),
67
+ aF: getActiveFactories(),
68
+ rF: getRedundantActiveFactories(),
69
+ };
70
+ }
57
71
  /**
58
72
  * Converts data from telemetry cache and settings into /metrics/config request payload.
59
73
  */
@@ -63,9 +77,7 @@ export function telemetryCacheConfigAdapter(telemetry, settings) {
63
77
  clear: function () { },
64
78
  state: function () {
65
79
  var urls = settings.urls, scheduler = settings.scheduler;
66
- return {
67
- oM: OPERATION_MODE_MAP[settings.mode],
68
- st: settings.storage.type.toLowerCase(),
80
+ return objectAssign(getTelemetryConfigStats(settings.mode, settings.storage.type), {
69
81
  sE: settings.streamingEnabled,
70
82
  rR: {
71
83
  sp: scheduler.featuresRefreshRate,
@@ -86,14 +98,13 @@ export function telemetryCacheConfigAdapter(telemetry, settings) {
86
98
  iM: IMPRESSIONS_MODE_MAP[settings.sync.impressionsMode],
87
99
  iL: settings.impressionListener ? true : false,
88
100
  hP: false,
89
- aF: getActiveFactories(),
90
- rF: getRedundantActiveFactories(),
91
101
  tR: telemetry.getTimeUntilReady(),
92
102
  tC: telemetry.getTimeUntilReadyFromCache(),
93
103
  nR: telemetry.getNonReadyUsage(),
94
104
  t: telemetry.popTags(),
95
105
  i: settings.integrations && settings.integrations.map(function (int) { return int.type; }),
96
- };
106
+ uC: settings.userConsent ? USER_CONSENT_MAP[settings.userConsent] : 0
107
+ });
97
108
  }
98
109
  };
99
110
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@splitsoftware/splitio-commons",
3
- "version": "1.3.2-rc.3",
3
+ "version": "1.3.2-rc.4",
4
4
  "description": "Split Javascript SDK common components",
5
5
  "main": "cjs/index.js",
6
6
  "module": "esm/index.js",
@@ -12,6 +12,7 @@ import { objectAssign } from '../utils/lang/objectAssign';
12
12
  import { CLEANUP_REGISTERING, CLEANUP_DEREGISTERING } from '../logger/constants';
13
13
  import { ISyncManager } from '../sync/types';
14
14
  import { isConsentGranted } from '../consent';
15
+ import { telemetryCacheStatsAdapter } from '../sync/submitters/telemetrySubmitter';
15
16
 
16
17
  // 'unload' event is used instead of 'beforeunload', since 'unload' is not a cancelable event, so no other listeners can stop the event from occurring.
17
18
  const UNLOAD_DOM_EVENT = 'unload';
@@ -77,7 +78,11 @@ export class BrowserSignalListener implements ISignalListener {
77
78
  this._flushData(eventsUrl + '/testImpressions/beacon', this.storage.impressions, this.serviceApi.postTestImpressionsBulk, this.fromImpressionsCollector, extraMetadata);
78
79
  this._flushData(eventsUrl + '/events/beacon', this.storage.events, this.serviceApi.postEventsBulk);
79
80
  if (this.storage.impressionCounts) this._flushData(eventsUrl + '/testImpressions/count/beacon', this.storage.impressionCounts, this.serviceApi.postTestImpressionsCount, fromImpressionCountsCollector);
80
- // No beacon endpoint for `/metrics/usage`
81
+ if (this.storage.telemetry) {
82
+ const telemetryUrl = this.settings.urls.telemetry;
83
+ const telemetryCacheAdapter = telemetryCacheStatsAdapter(this.storage.telemetry, this.storage.splits, this.storage.segments);
84
+ this._flushData(telemetryUrl + '/v1/metrics/usage/beacon', telemetryCacheAdapter, this.serviceApi.postMetricsUsage);
85
+ }
81
86
  }
82
87
 
83
88
  // Close streaming connection
@@ -23,10 +23,6 @@ export class KeyBuilderSS extends KeyBuilder {
23
23
  return `${this.prefix}.segments.registered`;
24
24
  }
25
25
 
26
- private buildVersionablePrefix() {
27
- return `${this.metadata.s}/${this.metadata.n}/${this.metadata.i}`;
28
- }
29
-
30
26
  buildImpressionsKey() {
31
27
  return `${this.prefix}.impressions`;
32
28
  }
@@ -35,6 +31,12 @@ export class KeyBuilderSS extends KeyBuilder {
35
31
  return `${this.prefix}.events`;
36
32
  }
37
33
 
34
+ searchPatternForSplitKeys() {
35
+ return `${this.buildSplitKeyPrefix()}*`;
36
+ }
37
+
38
+ /* Telemetry keys */
39
+
38
40
  buildLatencyKey(method: Method, bucket: number) {
39
41
  return `${this.prefix}.telemetry.latencies::${this.buildVersionablePrefix()}/${methodNames[method]}/${bucket}`;
40
42
  }
@@ -43,8 +45,12 @@ export class KeyBuilderSS extends KeyBuilder {
43
45
  return `${this.prefix}.telemetry.exceptions::${this.buildVersionablePrefix()}/${methodNames[method]}`;
44
46
  }
45
47
 
46
- searchPatternForSplitKeys() {
47
- return `${this.buildSplitKeyPrefix()}*`;
48
+ buildInitKey() {
49
+ return `${this.prefix}.telemetry.init::${this.buildVersionablePrefix()}`;
50
+ }
51
+
52
+ private buildVersionablePrefix() {
53
+ return `${this.metadata.s}/${this.metadata.n}/${this.metadata.i}`;
48
54
  }
49
55
 
50
56
  }
@@ -8,7 +8,7 @@ import { timeout } from '../../utils/promise/timeout';
8
8
  const LOG_PREFIX = 'storage:redis-adapter: ';
9
9
 
10
10
  // If we ever decide to fully wrap every method, there's a Commander.getBuiltinCommands from ioredis.
11
- const METHODS_TO_PROMISE_WRAP = ['set', 'exec', 'del', 'get', 'keys', 'sadd', 'srem', 'sismember', 'smembers', 'incr', 'rpush', 'pipeline', 'expire', 'mget', 'lrange', 'ltrim'];
11
+ const METHODS_TO_PROMISE_WRAP = ['set', 'exec', 'del', 'get', 'keys', 'sadd', 'srem', 'sismember', 'smembers', 'incr', 'rpush', 'pipeline', 'expire', 'mget', 'lrange', 'ltrim', 'hset'];
12
12
 
13
13
  // Not part of the settings since it'll vary on each storage. We should be removing storage specific logic from elsewhere.
14
14
  const DEFAULT_OPTIONS = {
@@ -4,6 +4,8 @@ import { KeyBuilderSS } from '../KeyBuilderSS';
4
4
  import { ITelemetryCacheAsync } from '../types';
5
5
  import { findLatencyIndex } from '../findLatencyIndex';
6
6
  import { Redis } from 'ioredis';
7
+ import { getTelemetryConfigStats } from '../../sync/submitters/telemetrySubmitter';
8
+ import { CONSUMER_MODE, STORAGE_REDIS } from '../../utils/constants';
7
9
 
8
10
  export class TelemetryCacheInRedis implements ITelemetryCacheAsync {
9
11
 
@@ -26,4 +28,9 @@ export class TelemetryCacheInRedis implements ITelemetryCacheAsync {
26
28
  .catch(() => { /* Handle rejections for telemetry */ });
27
29
  }
28
30
 
31
+ recordConfig() {
32
+ const [key, field] = this.keys.buildInitKey().split('::');
33
+ const value = JSON.stringify(getTelemetryConfigStats(CONSUMER_MODE, STORAGE_REDIS));
34
+ return this.redis.hset(key, field, value).catch(() => { /* Handle rejections for telemetry */ });
35
+ }
29
36
  }
@@ -26,10 +26,14 @@ export function InRedisStorage(options: InRedisStorageOptions = {}): IStorageAsy
26
26
 
27
27
  const keys = new KeyBuilderSS(prefix, metadata);
28
28
  const redisClient = new RedisAdapter(log, options.options || {});
29
+ const telemetry = new TelemetryCacheInRedis(log, keys, redisClient);
29
30
 
30
31
  // subscription to Redis connect event in order to emit SDK_READY event on consumer mode
31
32
  redisClient.on('connect', () => {
32
33
  onReadyCb();
34
+
35
+ // Synchronize config
36
+ telemetry.recordConfig();
33
37
  });
34
38
 
35
39
  return {
@@ -37,7 +41,7 @@ export function InRedisStorage(options: InRedisStorageOptions = {}): IStorageAsy
37
41
  segments: new SegmentsCacheInRedis(log, keys, redisClient),
38
42
  impressions: new ImpressionsCacheInRedis(log, keys.buildImpressionsKey(), redisClient, metadata),
39
43
  events: new EventsCacheInRedis(log, keys.buildEventsKey(), redisClient, metadata),
40
- telemetry: new TelemetryCacheInRedis(log, keys, redisClient),
44
+ telemetry,
41
45
 
42
46
  // When using REDIS we should:
43
47
  // 1- Disconnect from the storage
@@ -39,6 +39,7 @@ function validatePluggableStorageOptions(options: any) {
39
39
  function wrapperConnect(wrapper: IPluggableStorageWrapper, onReadyCb: (error?: any) => void) {
40
40
  wrapper.connect().then(() => {
41
41
  onReadyCb();
42
+ // At the moment, we don't synchronize config with pluggable storage
42
43
  }).catch((e) => {
43
44
  onReadyCb(e || new Error('Error connecting wrapper'));
44
45
  });
@@ -77,7 +78,7 @@ export function PluggableStorage(options: PluggableStorageOptions): IStorageAsyn
77
78
  impressions: isPartialConsumer ? new ImpressionsCacheInMemory(impressionsQueueSize) : new ImpressionsCachePluggable(log, keys.buildImpressionsKey(), wrapper, metadata),
78
79
  impressionCounts: optimize ? new ImpressionCountsCacheInMemory() : undefined,
79
80
  events: isPartialConsumer ? promisifyEventsTrack(new EventsCacheInMemory(eventsQueueSize)) : new EventsCachePluggable(log, keys.buildEventsKey(), wrapper, metadata),
80
- // @TODO Not using TelemetryCachePluggable yet, because it is not supported by the Split Synchronizer
81
+ // @TODO Not using TelemetryCachePluggable yet because it's not supported by the Split Synchronizer, and needs to drop or queue operations while the wrapper is not ready
81
82
  // telemetry: isPartialConsumer ? new TelemetryCacheInMemory() : new TelemetryCachePluggable(log, keys, wrapper),
82
83
 
83
84
  // Disconnect the underlying storage
@@ -1,13 +1,14 @@
1
1
  import { ISegmentsCacheSync, ISplitsCacheSync, ITelemetryCacheSync } from '../../storages/types';
2
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';
3
+ import { TelemetryUsageStatsPayload, TelemetryConfigStatsPayload, TelemetryConfigStats } 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, CONSENT_GRANTED, CONSENT_DECLINED, CONSENT_UNKNOWN } from '../../utils/constants';
5
5
  import { SDK_READY, SDK_READY_FROM_CACHE } from '../../readiness/constants';
6
- import { ISettings } from '../../types';
6
+ import { ConsentStatus, ISettings, SDKMode } from '../../types';
7
7
  import { base } from '../../utils/settingsValidation';
8
8
  import { usedKeysMap } from '../../utils/inputValidation/apiKey';
9
9
  import { timer } from '../../utils/timeTracker/timer';
10
10
  import { ISdkFactoryContextSync } from '../../sdkFactory/types';
11
+ import { objectAssign } from '../../utils/lang/objectAssign';
11
12
 
12
13
  /**
13
14
  * Converts data from telemetry cache into /metrics/usage request payload.
@@ -54,6 +55,12 @@ const IMPRESSIONS_MODE_MAP = {
54
55
  [DEBUG]: DEBUG_ENUM
55
56
  } as Record<ISettings['sync']['impressionsMode'], (0 | 1)>;
56
57
 
58
+ const USER_CONSENT_MAP = {
59
+ [CONSENT_UNKNOWN]: 1,
60
+ [CONSENT_GRANTED]: 2,
61
+ [CONSENT_DECLINED]: 3
62
+ } as Record<ConsentStatus, (0 | 1 | 2 | 3)>;
63
+
57
64
  function getActiveFactories() {
58
65
  return Object.keys(usedKeysMap).length;
59
66
  }
@@ -64,6 +71,15 @@ function getRedundantActiveFactories() {
64
71
  }, 0);
65
72
  }
66
73
 
74
+ export function getTelemetryConfigStats(mode: SDKMode, storageType: string): TelemetryConfigStats {
75
+ return {
76
+ oM: OPERATION_MODE_MAP[mode], // @ts-ignore lower case of storage type
77
+ st: storageType.toLowerCase(),
78
+ aF: getActiveFactories(),
79
+ rF: getRedundantActiveFactories(),
80
+ };
81
+ }
82
+
67
83
  /**
68
84
  * Converts data from telemetry cache and settings into /metrics/config request payload.
69
85
  */
@@ -75,9 +91,7 @@ export function telemetryCacheConfigAdapter(telemetry: ITelemetryCacheSync, sett
75
91
  state(): TelemetryConfigStatsPayload {
76
92
  const { urls, scheduler } = settings;
77
93
 
78
- return {
79
- oM: OPERATION_MODE_MAP[settings.mode], // @ts-ignore lower case of storage type
80
- st: settings.storage.type.toLowerCase(),
94
+ return objectAssign(getTelemetryConfigStats(settings.mode, settings.storage.type), {
81
95
  sE: settings.streamingEnabled,
82
96
  rR: {
83
97
  sp: scheduler.featuresRefreshRate,
@@ -98,14 +112,13 @@ export function telemetryCacheConfigAdapter(telemetry: ITelemetryCacheSync, sett
98
112
  iM: IMPRESSIONS_MODE_MAP[settings.sync.impressionsMode],
99
113
  iL: settings.impressionListener ? true : false,
100
114
  hP: false, // @TODO proxy not supported
101
- aF: getActiveFactories(),
102
- rF: getRedundantActiveFactories(),
103
115
  tR: telemetry.getTimeUntilReady() as number,
104
116
  tC: telemetry.getTimeUntilReadyFromCache(),
105
117
  nR: telemetry.getNonReadyUsage(),
106
118
  t: telemetry.popTags(),
107
119
  i: settings.integrations && settings.integrations.map(int => int.type),
108
- };
120
+ uC: settings.userConsent ? USER_CONSENT_MAP[settings.userConsent] : 0
121
+ });
109
122
  }
110
123
  };
111
124
  }
@@ -165,10 +165,17 @@ export type UrlOverrides = {
165
165
  t: boolean, // telemetry
166
166
  }
167
167
 
168
- // 'metrics/config' JSON request body
169
- export type TelemetryConfigStatsPayload = {
170
- oM?: OperationMode, // operationMode
168
+ // 'telemetry.init' Redis/Pluggable key
169
+ export type TelemetryConfigStats = {
170
+ oM: OperationMode, // operationMode
171
171
  st: 'memory' | 'redis' | 'pluggable' | 'localstorage', // storage
172
+ aF: number, // activeFactories
173
+ rF: number, // redundantActiveFactories
174
+ t?: Array<string>, // tags
175
+ }
176
+
177
+ // 'metrics/config' JSON request body
178
+ export type TelemetryConfigStatsPayload = TelemetryConfigStats & {
172
179
  sE: boolean, // streamingEnabled
173
180
  rR: RefreshRates, // refreshRates
174
181
  uO: UrlOverrides, // urlOverrides
@@ -177,11 +184,9 @@ export type TelemetryConfigStatsPayload = {
177
184
  iM: ImpressionsMode, // impressionsMode
178
185
  iL: boolean, // impressionsListenerEnabled
179
186
  hP: boolean, // httpProxyDetected
180
- aF: number, // activeFactories
181
- rF: number, // redundantActiveFactories
182
187
  tR: number, // timeUntilSDKReady
183
188
  tC?: number, // timeUntilSDKReadyFromCache
184
189
  nR: number, // SDKNotReadyUsage
185
- t?: Array<string>, // tags
186
190
  i?: Array<string>, // integrations
191
+ uC: number, // userConsent
187
192
  }
@@ -8,8 +8,7 @@
8
8
  */
9
9
 
10
10
  export interface utfx {
11
- encodeUTF16toUTF8(src: () => number | null, dst: (...args: number[]) => string | undefined): void,
12
-
11
+ encodeUTF16toUTF8(src: () => number | null, dst: (...args: number[]) => string | undefined): void
13
12
  }
14
13
 
15
14
 
@@ -5,10 +5,11 @@ export declare class KeyBuilderSS extends KeyBuilder {
5
5
  protected readonly metadata: IMetadata;
6
6
  constructor(prefix: string, metadata: IMetadata);
7
7
  buildRegisteredSegmentsKey(): string;
8
- private buildVersionablePrefix;
9
8
  buildImpressionsKey(): string;
10
9
  buildEventsKey(): string;
10
+ searchPatternForSplitKeys(): string;
11
11
  buildLatencyKey(method: Method, bucket: number): string;
12
12
  buildExceptionKey(method: Method): string;
13
- searchPatternForSplitKeys(): string;
13
+ buildInitKey(): string;
14
+ private buildVersionablePrefix;
14
15
  }
@@ -16,4 +16,5 @@ export declare class TelemetryCacheInRedis implements ITelemetryCacheAsync {
16
16
  constructor(log: ILogger, keys: KeyBuilderSS, redis: Redis);
17
17
  recordLatency(method: Method, latencyMs: number): Promise<number | void>;
18
18
  recordException(method: Method): Promise<number | void>;
19
+ recordConfig(): Promise<number | void>;
19
20
  }
@@ -1,6 +1,6 @@
1
1
  import { ISegmentsCacheSync, ISplitsCacheSync, ITelemetryCacheSync } from '../../storages/types';
2
- import { TelemetryUsageStatsPayload, TelemetryConfigStatsPayload } from './types';
3
- import { ISettings } from '../../types';
2
+ import { TelemetryUsageStatsPayload, TelemetryConfigStatsPayload, TelemetryConfigStats } from './types';
3
+ import { ISettings, SDKMode } from '../../types';
4
4
  import { ISdkFactoryContextSync } from '../../sdkFactory/types';
5
5
  /**
6
6
  * Converts data from telemetry cache into /metrics/usage request payload.
@@ -10,6 +10,7 @@ export declare function telemetryCacheStatsAdapter(telemetry: ITelemetryCacheSyn
10
10
  clear(): void;
11
11
  state(): TelemetryUsageStatsPayload;
12
12
  };
13
+ export declare function getTelemetryConfigStats(mode: SDKMode, storageType: string): TelemetryConfigStats;
13
14
  /**
14
15
  * Converts data from telemetry cache and settings into /metrics/config request payload.
15
16
  */
@@ -146,9 +146,14 @@ export declare type UrlOverrides = {
146
146
  st: boolean;
147
147
  t: boolean;
148
148
  };
149
- export declare type TelemetryConfigStatsPayload = {
150
- oM?: OperationMode;
149
+ export declare type TelemetryConfigStats = {
150
+ oM: OperationMode;
151
151
  st: 'memory' | 'redis' | 'pluggable' | 'localstorage';
152
+ aF: number;
153
+ rF: number;
154
+ t?: Array<string>;
155
+ };
156
+ export declare type TelemetryConfigStatsPayload = TelemetryConfigStats & {
152
157
  sE: boolean;
153
158
  rR: RefreshRates;
154
159
  uO: UrlOverrides;
@@ -157,11 +162,9 @@ export declare type TelemetryConfigStatsPayload = {
157
162
  iM: ImpressionsMode;
158
163
  iL: boolean;
159
164
  hP: boolean;
160
- aF: number;
161
- rF: number;
162
165
  tR: number;
163
166
  tC?: number;
164
167
  nR: number;
165
- t?: Array<string>;
166
168
  i?: Array<string>;
169
+ uC: number;
167
170
  };