@splitsoftware/splitio-commons 1.3.2-rc.1 → 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.
- package/cjs/listeners/browser.js +6 -1
- package/cjs/readiness/sdkReadinessManager.js +7 -7
- package/cjs/services/splitApi.js +2 -2
- package/cjs/storages/KeyBuilderSS.js +9 -5
- package/cjs/storages/inRedis/RedisAdapter.js +1 -1
- package/cjs/storages/inRedis/TelemetryCacheInRedis.js +7 -0
- package/cjs/storages/inRedis/index.js +4 -1
- package/cjs/storages/pluggable/index.js +2 -1
- package/cjs/sync/submitters/submitter.js +5 -4
- package/cjs/sync/submitters/telemetrySubmitter.js +22 -9
- package/cjs/utils/settingsValidation/index.js +16 -6
- package/cjs/utils/settingsValidation/url.js +1 -1
- package/esm/listeners/browser.js +6 -1
- package/esm/readiness/sdkReadinessManager.js +7 -7
- package/esm/services/splitApi.js +2 -2
- package/esm/storages/KeyBuilderSS.js +9 -5
- package/esm/storages/inRedis/RedisAdapter.js +1 -1
- package/esm/storages/inRedis/TelemetryCacheInRedis.js +7 -0
- package/esm/storages/inRedis/index.js +4 -1
- package/esm/storages/pluggable/index.js +2 -1
- package/esm/sync/submitters/submitter.js +5 -4
- package/esm/sync/submitters/telemetrySubmitter.js +21 -9
- package/esm/utils/settingsValidation/index.js +17 -7
- package/esm/utils/settingsValidation/url.js +1 -1
- package/package.json +1 -1
- package/src/listeners/browser.ts +6 -1
- package/src/readiness/sdkReadinessManager.ts +7 -5
- package/src/readiness/types.ts +7 -1
- package/src/services/splitApi.ts +2 -2
- package/src/storages/KeyBuilderSS.ts +12 -6
- package/src/storages/inRedis/RedisAdapter.ts +1 -1
- package/src/storages/inRedis/TelemetryCacheInRedis.ts +7 -0
- package/src/storages/inRedis/index.ts +5 -1
- package/src/storages/pluggable/index.ts +2 -1
- package/src/sync/submitters/submitter.ts +4 -4
- package/src/sync/submitters/telemetrySubmitter.ts +24 -10
- package/src/sync/submitters/types.ts +11 -6
- package/src/types.ts +1 -1
- package/src/utils/murmur3/utfx.ts +1 -2
- package/src/utils/settingsValidation/index.ts +18 -8
- package/src/utils/settingsValidation/url.ts +1 -1
- package/types/readiness/sdkReadinessManager.d.ts +1 -3
- package/types/readiness/types.d.ts +6 -1
- package/types/storages/KeyBuilderSS.d.ts +3 -2
- package/types/storages/inRedis/TelemetryCacheInRedis.d.ts +1 -0
- package/types/sync/submitters/telemetrySubmitter.d.ts +3 -2
- package/types/sync/submitters/types.d.ts +8 -5
- package/types/types.d.ts +1 -1
|
@@ -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
|
}
|
|
@@ -104,12 +115,13 @@ export function telemetrySubmitterFactory(params) {
|
|
|
104
115
|
var _a = params.storage, splits = _a.splits, segments = _a.segments, telemetry = _a.telemetry;
|
|
105
116
|
if (!telemetry)
|
|
106
117
|
return; // No submitter created if telemetry cache is not defined
|
|
107
|
-
var settings = params.settings, _b = params.settings, log = _b.log, telemetryRefreshRate = _b.scheduler.telemetryRefreshRate, splitApi = params.splitApi, now = params.platform.now, readiness = params.readiness;
|
|
118
|
+
var settings = params.settings, _b = params.settings, log = _b.log, telemetryRefreshRate = _b.scheduler.telemetryRefreshRate, splitApi = params.splitApi, now = params.platform.now, readiness = params.readiness, sdkReadinessManager = params.sdkReadinessManager;
|
|
108
119
|
var startTime = timer(now || Date.now);
|
|
109
120
|
var submitter = firstPushWindowDecorator(submitterFactory(log, splitApi.postMetricsUsage, telemetryCacheStatsAdapter(telemetry, splits, segments), telemetryRefreshRate, 'telemetry stats', undefined, 0, true), telemetryRefreshRate);
|
|
110
121
|
readiness.gate.once(SDK_READY_FROM_CACHE, function () {
|
|
111
122
|
telemetry.recordTimeUntilReadyFromCache(startTime());
|
|
112
123
|
});
|
|
124
|
+
sdkReadinessManager.incInternalReadyCbCount();
|
|
113
125
|
readiness.gate.once(SDK_READY, function () {
|
|
114
126
|
telemetry.recordTimeUntilReady(startTime());
|
|
115
127
|
// Post config data when the SDK is ready and if the telemetry submitter was started
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { merge } from '../lang';
|
|
2
2
|
import { mode } from './mode';
|
|
3
3
|
import { validateSplitFilters } from './splitFilters';
|
|
4
|
-
import { STANDALONE_MODE, OPTIMIZED, LOCALHOST_MODE } from '../constants';
|
|
4
|
+
import { STANDALONE_MODE, OPTIMIZED, LOCALHOST_MODE, DEBUG } from '../constants';
|
|
5
5
|
import { validImpressionsMode } from './impressionsMode';
|
|
6
6
|
import { validateKey } from '../inputValidation/key';
|
|
7
7
|
import { validateTrafficType } from '../inputValidation/trafficType';
|
|
@@ -29,8 +29,8 @@ export var base = {
|
|
|
29
29
|
segmentsRefreshRate: 60,
|
|
30
30
|
// publish telemetry stats each 3600 secs (1 hour)
|
|
31
31
|
telemetryRefreshRate: 3600,
|
|
32
|
-
// publish evaluations each
|
|
33
|
-
impressionsRefreshRate:
|
|
32
|
+
// publish evaluations each 300 sec (default value for OPTIMIZED impressions mode)
|
|
33
|
+
impressionsRefreshRate: 300,
|
|
34
34
|
// fetch offline changes each 15 sec
|
|
35
35
|
offlineRefreshRate: 15,
|
|
36
36
|
// publish events every 60 seconds after the first flush
|
|
@@ -52,7 +52,7 @@ export var base = {
|
|
|
52
52
|
// Streaming Server
|
|
53
53
|
streaming: 'https://streaming.split.io',
|
|
54
54
|
// Telemetry Server
|
|
55
|
-
telemetry: 'https://telemetry.split.io',
|
|
55
|
+
telemetry: 'https://telemetry.split.io/api',
|
|
56
56
|
},
|
|
57
57
|
// Defines which kind of storage we should instanciate.
|
|
58
58
|
storage: undefined,
|
|
@@ -93,6 +93,8 @@ export function settingsValidation(config, validationParams) {
|
|
|
93
93
|
// First thing to validate, since other validators might use the logger.
|
|
94
94
|
var log = logger(withDefaults); // @ts-ignore, modify readonly prop
|
|
95
95
|
withDefaults.log = log;
|
|
96
|
+
// ensure a valid impressionsMode
|
|
97
|
+
withDefaults.sync.impressionsMode = validImpressionsMode(log, withDefaults.sync.impressionsMode);
|
|
96
98
|
function validateMinValue(paramName, actualValue, minValue) {
|
|
97
99
|
if (actualValue >= minValue)
|
|
98
100
|
return actualValue;
|
|
@@ -104,10 +106,20 @@ export function settingsValidation(config, validationParams) {
|
|
|
104
106
|
var scheduler = withDefaults.scheduler, startup = withDefaults.startup;
|
|
105
107
|
scheduler.featuresRefreshRate = fromSecondsToMillis(scheduler.featuresRefreshRate);
|
|
106
108
|
scheduler.segmentsRefreshRate = fromSecondsToMillis(scheduler.segmentsRefreshRate);
|
|
107
|
-
scheduler.impressionsRefreshRate = fromSecondsToMillis(scheduler.impressionsRefreshRate);
|
|
108
109
|
scheduler.offlineRefreshRate = fromSecondsToMillis(scheduler.offlineRefreshRate);
|
|
109
110
|
scheduler.eventsPushRate = fromSecondsToMillis(scheduler.eventsPushRate);
|
|
110
111
|
scheduler.telemetryRefreshRate = fromSecondsToMillis(validateMinValue('telemetryRefreshRate', scheduler.telemetryRefreshRate, 60));
|
|
112
|
+
if (scheduler.impressionsRefreshRate !== base.scheduler.impressionsRefreshRate) {
|
|
113
|
+
// Validate impressionsRefreshRate defined by user
|
|
114
|
+
scheduler.impressionsRefreshRate = validateMinValue('impressionsRefreshRate', scheduler.impressionsRefreshRate, withDefaults.sync.impressionsMode === DEBUG ? 1 : 60 // Min is 1 sec for DEBUG and 60 secs for OPTIMIZED
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
// Default impressionsRefreshRate for DEBUG mode is 60 secs
|
|
119
|
+
if (withDefaults.sync.impressionsMode === DEBUG)
|
|
120
|
+
scheduler.impressionsRefreshRate = 60;
|
|
121
|
+
}
|
|
122
|
+
scheduler.impressionsRefreshRate = fromSecondsToMillis(scheduler.impressionsRefreshRate);
|
|
111
123
|
// Log deprecation for old telemetry param
|
|
112
124
|
if (scheduler.metricsRefreshRate)
|
|
113
125
|
log.warn('`metricsRefreshRate` will be deprecated soon. For configuring telemetry rates, update `telemetryRefreshRate` value in configs');
|
|
@@ -163,8 +175,6 @@ export function settingsValidation(config, validationParams) {
|
|
|
163
175
|
var splitFiltersValidation = validateSplitFilters(log, withDefaults.sync.splitFilters, withDefaults.mode);
|
|
164
176
|
withDefaults.sync.splitFilters = splitFiltersValidation.validFilters;
|
|
165
177
|
withDefaults.sync.__splitFiltersValidation = splitFiltersValidation;
|
|
166
|
-
// ensure a valid impressionsMode
|
|
167
|
-
withDefaults.sync.impressionsMode = validImpressionsMode(log, withDefaults.sync.impressionsMode);
|
|
168
178
|
// ensure a valid user consent value
|
|
169
179
|
// @ts-ignore, modify readonly prop
|
|
170
180
|
withDefaults.userConsent = consent(withDefaults);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
var telemetryEndpointMatcher = /^\/metrics\/(config|usage)/;
|
|
1
|
+
var telemetryEndpointMatcher = /^\/v1\/metrics\/(config|usage)/;
|
|
2
2
|
var eventsEndpointMatcher = /^\/(testImpressions|metrics|events)/;
|
|
3
3
|
var authEndpointMatcher = /^\/v2\/auth/;
|
|
4
4
|
var streamingEndpointMatcher = /^\/(sse|event-stream)/;
|
package/package.json
CHANGED
package/src/listeners/browser.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
|
@@ -15,18 +15,16 @@ const REMOVE_LISTENER_EVENT = 'removeListener';
|
|
|
15
15
|
* It also updates logs related warnings and errors.
|
|
16
16
|
*
|
|
17
17
|
* @param readyTimeout time in millis to emit SDK_READY_TIME_OUT event
|
|
18
|
-
* @param internalReadyCbCount offset value of SDK_READY listeners that are added/removed internally
|
|
19
|
-
* by the SDK. It is required to properly log the warning 'No listeners for SDK Readiness detected'
|
|
20
18
|
* @param readinessManager optional readinessManager to use. only used internally for `shared` method
|
|
21
19
|
*/
|
|
22
20
|
export function sdkReadinessManagerFactory(
|
|
23
21
|
log: ILogger,
|
|
24
22
|
EventEmitter: new () => IEventEmitter,
|
|
25
23
|
readyTimeout = 0,
|
|
26
|
-
internalReadyCbCount = 0,
|
|
27
24
|
readinessManager = readinessManagerFactory(EventEmitter, readyTimeout)): ISdkReadinessManager {
|
|
28
25
|
|
|
29
26
|
/** Ready callback warning */
|
|
27
|
+
let internalReadyCbCount = 0;
|
|
30
28
|
let readyCbCount = 0;
|
|
31
29
|
readinessManager.gate.on(REMOVE_LISTENER_EVENT, (event: any) => {
|
|
32
30
|
if (event === SDK_READY) readyCbCount--;
|
|
@@ -74,8 +72,12 @@ export function sdkReadinessManagerFactory(
|
|
|
74
72
|
return {
|
|
75
73
|
readinessManager,
|
|
76
74
|
|
|
77
|
-
shared(readyTimeout = 0
|
|
78
|
-
return sdkReadinessManagerFactory(log, EventEmitter, readyTimeout,
|
|
75
|
+
shared(readyTimeout = 0) {
|
|
76
|
+
return sdkReadinessManagerFactory(log, EventEmitter, readyTimeout, readinessManager.shared(readyTimeout));
|
|
77
|
+
},
|
|
78
|
+
|
|
79
|
+
incInternalReadyCbCount() {
|
|
80
|
+
internalReadyCbCount++;
|
|
79
81
|
},
|
|
80
82
|
|
|
81
83
|
sdkStatus: objectAssign(
|
package/src/readiness/types.ts
CHANGED
|
@@ -66,6 +66,12 @@ export interface ISdkReadinessManager {
|
|
|
66
66
|
readinessManager: IReadinessManager
|
|
67
67
|
sdkStatus: IStatusInterface
|
|
68
68
|
|
|
69
|
+
/**
|
|
70
|
+
* Increment internalReadyCbCount, an offset value of SDK_READY listeners that are added/removed internally
|
|
71
|
+
* by the SDK. It is required to properly log the warning 'No listeners for SDK Readiness detected'
|
|
72
|
+
*/
|
|
73
|
+
incInternalReadyCbCount(): void
|
|
74
|
+
|
|
69
75
|
/** for client-side */
|
|
70
|
-
shared(readyTimeout?: number
|
|
76
|
+
shared(readyTimeout?: number): ISdkReadinessManager
|
|
71
77
|
}
|
package/src/services/splitApi.ts
CHANGED
|
@@ -108,12 +108,12 @@ export function splitApiFactory(
|
|
|
108
108
|
},
|
|
109
109
|
|
|
110
110
|
postMetricsConfig(body: string) {
|
|
111
|
-
const url = `${urls.telemetry}/metrics/config`;
|
|
111
|
+
const url = `${urls.telemetry}/v1/metrics/config`;
|
|
112
112
|
return splitHttpClient(url, { method: 'POST', body }, telemetryTracker.trackHttp(TELEMETRY), true);
|
|
113
113
|
},
|
|
114
114
|
|
|
115
115
|
postMetricsUsage(body: string) {
|
|
116
|
-
const url = `${urls.telemetry}/metrics/usage`;
|
|
116
|
+
const url = `${urls.telemetry}/v1/metrics/usage`;
|
|
117
117
|
return splitHttpClient(url, { method: 'POST', body }, telemetryTracker.trackHttp(TELEMETRY), true);
|
|
118
118
|
}
|
|
119
119
|
};
|
|
@@ -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
|
-
|
|
47
|
-
return `${this.
|
|
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
|
|
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
|
|
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
|
|
@@ -16,7 +16,7 @@ export function submitterFactory<TState>(
|
|
|
16
16
|
dataName: string,
|
|
17
17
|
fromCacheToPayload?: (cacheData: TState) => any,
|
|
18
18
|
maxRetries: number = 0,
|
|
19
|
-
debugLogs?: boolean
|
|
19
|
+
debugLogs?: boolean // true for telemetry submitters
|
|
20
20
|
): ISyncTask<[], void> {
|
|
21
21
|
|
|
22
22
|
let retries = 0;
|
|
@@ -37,14 +37,14 @@ export function submitterFactory<TState>(
|
|
|
37
37
|
sourceCache.clear(); // we clear the queue if request successes.
|
|
38
38
|
}).catch(err => {
|
|
39
39
|
if (!maxRetries) {
|
|
40
|
-
log
|
|
40
|
+
log[debugLogs ? 'debug' : 'warn'](SUBMITTERS_PUSH_FAILS, [dataCountMessage, err]);
|
|
41
41
|
} else if (retries === maxRetries) {
|
|
42
42
|
retries = 0;
|
|
43
43
|
sourceCache.clear(); // we clear the queue if request fails after retries.
|
|
44
|
-
log
|
|
44
|
+
log[debugLogs ? 'debug' : 'warn'](SUBMITTERS_PUSH_FAILS, [dataCountMessage, err]);
|
|
45
45
|
} else {
|
|
46
46
|
retries++;
|
|
47
|
-
log
|
|
47
|
+
log[debugLogs ? 'debug' : 'warn'](SUBMITTERS_PUSH_RETRY, [dataCountMessage, err]);
|
|
48
48
|
}
|
|
49
49
|
});
|
|
50
50
|
}
|
|
@@ -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
|
}
|
|
@@ -117,7 +130,7 @@ export function telemetrySubmitterFactory(params: ISdkFactoryContextSync) {
|
|
|
117
130
|
const { storage: { splits, segments, telemetry } } = params;
|
|
118
131
|
if (!telemetry) return; // No submitter created if telemetry cache is not defined
|
|
119
132
|
|
|
120
|
-
const { settings, settings: { log, scheduler: { telemetryRefreshRate } }, splitApi, platform: { now }, readiness } = params;
|
|
133
|
+
const { settings, settings: { log, scheduler: { telemetryRefreshRate } }, splitApi, platform: { now }, readiness, sdkReadinessManager } = params;
|
|
121
134
|
const startTime = timer(now || Date.now);
|
|
122
135
|
|
|
123
136
|
const submitter = firstPushWindowDecorator(
|
|
@@ -129,6 +142,7 @@ export function telemetrySubmitterFactory(params: ISdkFactoryContextSync) {
|
|
|
129
142
|
telemetry.recordTimeUntilReadyFromCache(startTime());
|
|
130
143
|
});
|
|
131
144
|
|
|
145
|
+
sdkReadinessManager.incInternalReadyCbCount();
|
|
132
146
|
readiness.gate.once(SDK_READY, () => {
|
|
133
147
|
telemetry.recordTimeUntilReady(startTime());
|
|
134
148
|
|
|
@@ -165,10 +165,17 @@ export type UrlOverrides = {
|
|
|
165
165
|
t: boolean, // telemetry
|
|
166
166
|
}
|
|
167
167
|
|
|
168
|
-
// '
|
|
169
|
-
export type
|
|
170
|
-
oM
|
|
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
|
}
|
package/src/types.ts
CHANGED
|
@@ -684,7 +684,7 @@ export namespace SplitIO {
|
|
|
684
684
|
/**
|
|
685
685
|
* String property to override the base URL where the SDK will post telemetry data.
|
|
686
686
|
* @property {string} telemetry
|
|
687
|
-
* @default 'https://telemetry.split.io'
|
|
687
|
+
* @default 'https://telemetry.split.io/api'
|
|
688
688
|
*/
|
|
689
689
|
telemetry?: string
|
|
690
690
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { merge } from '../lang';
|
|
2
2
|
import { mode } from './mode';
|
|
3
3
|
import { validateSplitFilters } from './splitFilters';
|
|
4
|
-
import { STANDALONE_MODE, OPTIMIZED, LOCALHOST_MODE } from '../constants';
|
|
4
|
+
import { STANDALONE_MODE, OPTIMIZED, LOCALHOST_MODE, DEBUG } from '../constants';
|
|
5
5
|
import { validImpressionsMode } from './impressionsMode';
|
|
6
6
|
import { ISettingsValidationParams } from './types';
|
|
7
7
|
import { ISettings } from '../../types';
|
|
@@ -34,8 +34,8 @@ export const base = {
|
|
|
34
34
|
segmentsRefreshRate: 60,
|
|
35
35
|
// publish telemetry stats each 3600 secs (1 hour)
|
|
36
36
|
telemetryRefreshRate: 3600,
|
|
37
|
-
// publish evaluations each
|
|
38
|
-
impressionsRefreshRate:
|
|
37
|
+
// publish evaluations each 300 sec (default value for OPTIMIZED impressions mode)
|
|
38
|
+
impressionsRefreshRate: 300,
|
|
39
39
|
// fetch offline changes each 15 sec
|
|
40
40
|
offlineRefreshRate: 15,
|
|
41
41
|
// publish events every 60 seconds after the first flush
|
|
@@ -58,7 +58,7 @@ export const base = {
|
|
|
58
58
|
// Streaming Server
|
|
59
59
|
streaming: 'https://streaming.split.io',
|
|
60
60
|
// Telemetry Server
|
|
61
|
-
telemetry: 'https://telemetry.split.io',
|
|
61
|
+
telemetry: 'https://telemetry.split.io/api',
|
|
62
62
|
},
|
|
63
63
|
|
|
64
64
|
// Defines which kind of storage we should instanciate.
|
|
@@ -113,6 +113,9 @@ export function settingsValidation(config: unknown, validationParams: ISettingsV
|
|
|
113
113
|
const log = logger(withDefaults); // @ts-ignore, modify readonly prop
|
|
114
114
|
withDefaults.log = log;
|
|
115
115
|
|
|
116
|
+
// ensure a valid impressionsMode
|
|
117
|
+
withDefaults.sync.impressionsMode = validImpressionsMode(log, withDefaults.sync.impressionsMode);
|
|
118
|
+
|
|
116
119
|
function validateMinValue(paramName: string, actualValue: number, minValue: number) {
|
|
117
120
|
if (actualValue >= minValue) return actualValue;
|
|
118
121
|
// actualValue is not a number or is lower than minValue
|
|
@@ -124,11 +127,21 @@ export function settingsValidation(config: unknown, validationParams: ISettingsV
|
|
|
124
127
|
const { scheduler, startup } = withDefaults;
|
|
125
128
|
scheduler.featuresRefreshRate = fromSecondsToMillis(scheduler.featuresRefreshRate);
|
|
126
129
|
scheduler.segmentsRefreshRate = fromSecondsToMillis(scheduler.segmentsRefreshRate);
|
|
127
|
-
scheduler.impressionsRefreshRate = fromSecondsToMillis(scheduler.impressionsRefreshRate);
|
|
128
130
|
scheduler.offlineRefreshRate = fromSecondsToMillis(scheduler.offlineRefreshRate);
|
|
129
131
|
scheduler.eventsPushRate = fromSecondsToMillis(scheduler.eventsPushRate);
|
|
130
132
|
scheduler.telemetryRefreshRate = fromSecondsToMillis(validateMinValue('telemetryRefreshRate', scheduler.telemetryRefreshRate, 60));
|
|
131
133
|
|
|
134
|
+
if (scheduler.impressionsRefreshRate !== base.scheduler.impressionsRefreshRate) {
|
|
135
|
+
// Validate impressionsRefreshRate defined by user
|
|
136
|
+
scheduler.impressionsRefreshRate = validateMinValue('impressionsRefreshRate', scheduler.impressionsRefreshRate,
|
|
137
|
+
withDefaults.sync.impressionsMode === DEBUG ? 1 : 60 // Min is 1 sec for DEBUG and 60 secs for OPTIMIZED
|
|
138
|
+
);
|
|
139
|
+
} else {
|
|
140
|
+
// Default impressionsRefreshRate for DEBUG mode is 60 secs
|
|
141
|
+
if (withDefaults.sync.impressionsMode === DEBUG) scheduler.impressionsRefreshRate = 60;
|
|
142
|
+
}
|
|
143
|
+
scheduler.impressionsRefreshRate = fromSecondsToMillis(scheduler.impressionsRefreshRate);
|
|
144
|
+
|
|
132
145
|
// Log deprecation for old telemetry param
|
|
133
146
|
if (scheduler.metricsRefreshRate) log.warn('`metricsRefreshRate` will be deprecated soon. For configuring telemetry rates, update `telemetryRefreshRate` value in configs');
|
|
134
147
|
|
|
@@ -190,9 +203,6 @@ export function settingsValidation(config: unknown, validationParams: ISettingsV
|
|
|
190
203
|
withDefaults.sync.splitFilters = splitFiltersValidation.validFilters;
|
|
191
204
|
withDefaults.sync.__splitFiltersValidation = splitFiltersValidation;
|
|
192
205
|
|
|
193
|
-
// ensure a valid impressionsMode
|
|
194
|
-
withDefaults.sync.impressionsMode = validImpressionsMode(log, withDefaults.sync.impressionsMode);
|
|
195
|
-
|
|
196
206
|
// ensure a valid user consent value
|
|
197
207
|
// @ts-ignore, modify readonly prop
|
|
198
208
|
withDefaults.userConsent = consent(withDefaults);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ISettings } from '../../types';
|
|
2
2
|
|
|
3
|
-
const telemetryEndpointMatcher = /^\/metrics\/(config|usage)/;
|
|
3
|
+
const telemetryEndpointMatcher = /^\/v1\/metrics\/(config|usage)/;
|
|
4
4
|
const eventsEndpointMatcher = /^\/(testImpressions|metrics|events)/;
|
|
5
5
|
const authEndpointMatcher = /^\/v2\/auth/;
|
|
6
6
|
const streamingEndpointMatcher = /^\/(sse|event-stream)/;
|
|
@@ -6,8 +6,6 @@ import { ILogger } from '../logger/types';
|
|
|
6
6
|
* It also updates logs related warnings and errors.
|
|
7
7
|
*
|
|
8
8
|
* @param readyTimeout time in millis to emit SDK_READY_TIME_OUT event
|
|
9
|
-
* @param internalReadyCbCount offset value of SDK_READY listeners that are added/removed internally
|
|
10
|
-
* by the SDK. It is required to properly log the warning 'No listeners for SDK Readiness detected'
|
|
11
9
|
* @param readinessManager optional readinessManager to use. only used internally for `shared` method
|
|
12
10
|
*/
|
|
13
|
-
export declare function sdkReadinessManagerFactory(log: ILogger, EventEmitter: new () => IEventEmitter, readyTimeout?: number,
|
|
11
|
+
export declare function sdkReadinessManagerFactory(log: ILogger, EventEmitter: new () => IEventEmitter, readyTimeout?: number, readinessManager?: import("./types").IReadinessManager): ISdkReadinessManager;
|
|
@@ -49,7 +49,12 @@ export interface IReadinessManager {
|
|
|
49
49
|
export interface ISdkReadinessManager {
|
|
50
50
|
readinessManager: IReadinessManager;
|
|
51
51
|
sdkStatus: IStatusInterface;
|
|
52
|
+
/**
|
|
53
|
+
* Increment internalReadyCbCount, an offset value of SDK_READY listeners that are added/removed internally
|
|
54
|
+
* by the SDK. It is required to properly log the warning 'No listeners for SDK Readiness detected'
|
|
55
|
+
*/
|
|
56
|
+
incInternalReadyCbCount(): void;
|
|
52
57
|
/** for client-side */
|
|
53
|
-
shared(readyTimeout?: number
|
|
58
|
+
shared(readyTimeout?: number): ISdkReadinessManager;
|
|
54
59
|
}
|
|
55
60
|
export {};
|