@splitsoftware/splitio-commons 1.4.1-rc.2 → 1.4.2-rc.1
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/CHANGES.txt +5 -0
- package/cjs/consent/sdkUserConsent.js +3 -3
- package/cjs/listeners/browser.js +7 -6
- package/cjs/sync/submitters/submitterManager.js +28 -4
- package/cjs/sync/syncManagerOnline.js +39 -28
- package/cjs/sync/syncTask.js +15 -10
- package/cjs/utils/settingsValidation/index.js +6 -1
- package/esm/consent/sdkUserConsent.js +3 -3
- package/esm/listeners/browser.js +7 -6
- package/esm/sync/submitters/submitterManager.js +28 -4
- package/esm/sync/syncManagerOnline.js +39 -28
- package/esm/sync/syncTask.js +15 -10
- package/esm/utils/settingsValidation/index.js +6 -1
- package/package.json +1 -1
- package/src/consent/sdkUserConsent.ts +4 -3
- package/src/listeners/browser.ts +8 -6
- package/src/sdkClient/clientAttributesDecoration.ts +9 -9
- package/src/sync/submitters/submitterManager.ts +30 -4
- package/src/sync/submitters/types.ts +7 -0
- package/src/sync/syncManagerOnline.ts +34 -22
- package/src/sync/syncTask.ts +17 -11
- package/src/sync/types.ts +2 -1
- package/src/types.ts +7 -1
- package/src/utils/settingsValidation/index.ts +7 -1
- package/types/integrations/ga/autoRequire.d.ts +4 -0
- package/types/sync/submitters/submitterManager.d.ts +2 -1
- package/types/sync/submitters/types.d.ts +6 -0
- package/types/sync/syncTask.d.ts +2 -2
- package/types/sync/types.d.ts +2 -1
- package/types/types.d.ts +6 -0
- package/types/utils/settingsValidation/index.d.ts +1 -0
- package/cjs/sync/syncTaskComposite.js +0 -26
- package/esm/sync/syncTaskComposite.js +0 -22
- package/src/sync/syncTaskComposite.ts +0 -26
package/CHANGES.txt
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
1.4.2 (June XX, 2022)
|
|
2
|
+
- Added `sync.enabled` property to SDK configuration to allow synchronize splits and segments only once.
|
|
3
|
+
- Updated telemetry to submit data even if user consent is not granted.
|
|
4
|
+
- Updated submitters logic, to avoid duplicating the post of impressions to Split cloud when the SDK is destroyed while performing its periodic post of impressions.
|
|
5
|
+
|
|
1
6
|
1.4.1 (June 13, 2022)
|
|
2
7
|
- Bugfixing - Updated submitters logic, to avoid dropping impressions and events that are being tracked while POST request is pending.
|
|
3
8
|
|
|
@@ -31,10 +31,10 @@ function createUserConsentAPI(params) {
|
|
|
31
31
|
log.info(constants_1.USER_CONSENT_UPDATED, [settings.userConsent, newConsentStatus]); // @ts-ignore, modify readonly prop
|
|
32
32
|
settings.userConsent = newConsentStatus;
|
|
33
33
|
if (consent) { // resumes submitters if transitioning to GRANTED
|
|
34
|
-
(_a = syncManager === null || syncManager === void 0 ? void 0 : syncManager.
|
|
34
|
+
(_a = syncManager === null || syncManager === void 0 ? void 0 : syncManager.submitterManager) === null || _a === void 0 ? void 0 : _a.start();
|
|
35
35
|
}
|
|
36
|
-
else { // pauses submitters and drops tracked data if transitioning to DECLINED
|
|
37
|
-
(_b = syncManager === null || syncManager === void 0 ? void 0 : syncManager.
|
|
36
|
+
else { // pauses submitters (except telemetry), and drops tracked data if transitioning to DECLINED
|
|
37
|
+
(_b = syncManager === null || syncManager === void 0 ? void 0 : syncManager.submitterManager) === null || _b === void 0 ? void 0 : _b.stop(true);
|
|
38
38
|
// @ts-ignore, clear method is present in storage for standalone and partial consumer mode
|
|
39
39
|
if (events.clear)
|
|
40
40
|
events.clear(); // @ts-ignore
|
package/cjs/listeners/browser.js
CHANGED
|
@@ -53,7 +53,7 @@ var BrowserSignalListener = /** @class */ (function () {
|
|
|
53
53
|
BrowserSignalListener.prototype.flushData = function () {
|
|
54
54
|
if (!this.syncManager)
|
|
55
55
|
return; // In consumer mode there is not sync manager and data to flush
|
|
56
|
-
// Flush data if there is user consent
|
|
56
|
+
// Flush impressions & events data if there is user consent
|
|
57
57
|
if ((0, consent_1.isConsentGranted)(this.settings)) {
|
|
58
58
|
var eventsUrl = this.settings.urls.events;
|
|
59
59
|
var extraMetadata = {
|
|
@@ -64,11 +64,12 @@ var BrowserSignalListener = /** @class */ (function () {
|
|
|
64
64
|
this._flushData(eventsUrl + '/events/beacon', this.storage.events, this.serviceApi.postEventsBulk);
|
|
65
65
|
if (this.storage.impressionCounts)
|
|
66
66
|
this._flushData(eventsUrl + '/testImpressions/count/beacon', this.storage.impressionCounts, this.serviceApi.postTestImpressionsCount, impressionCountsSubmitter_1.fromImpressionCountsCollector);
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
67
|
+
}
|
|
68
|
+
// Flush telemetry data
|
|
69
|
+
if (this.storage.telemetry) {
|
|
70
|
+
var telemetryUrl = this.settings.urls.telemetry;
|
|
71
|
+
var telemetryCacheAdapter = (0, telemetrySubmitter_1.telemetryCacheStatsAdapter)(this.storage.telemetry, this.storage.splits, this.storage.segments);
|
|
72
|
+
this._flushData(telemetryUrl + '/v1/metrics/usage/beacon', telemetryCacheAdapter, this.serviceApi.postMetricsUsage);
|
|
72
73
|
}
|
|
73
74
|
// Close streaming connection
|
|
74
75
|
if (this.syncManager.pushManager)
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.submitterManagerFactory = void 0;
|
|
4
|
-
var syncTaskComposite_1 = require("../syncTaskComposite");
|
|
5
4
|
var eventsSubmitter_1 = require("./eventsSubmitter");
|
|
6
5
|
var impressionsSubmitter_1 = require("./impressionsSubmitter");
|
|
7
6
|
var impressionCountsSubmitter_1 = require("./impressionCountsSubmitter");
|
|
@@ -15,8 +14,33 @@ function submitterManagerFactory(params) {
|
|
|
15
14
|
if (impressionCountsSubmitter)
|
|
16
15
|
submitters.push(impressionCountsSubmitter);
|
|
17
16
|
var telemetrySubmitter = (0, telemetrySubmitter_1.telemetrySubmitterFactory)(params);
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
return {
|
|
18
|
+
// `onlyTelemetry` true if SDK is created with userConsent not GRANTED
|
|
19
|
+
start: function (onlyTelemetry) {
|
|
20
|
+
if (!onlyTelemetry)
|
|
21
|
+
submitters.forEach(function (submitter) { return submitter.start(); });
|
|
22
|
+
if (telemetrySubmitter)
|
|
23
|
+
telemetrySubmitter.start();
|
|
24
|
+
},
|
|
25
|
+
// `allExceptTelemetry` true if userConsent is changed to DECLINED
|
|
26
|
+
stop: function (allExceptTelemetry) {
|
|
27
|
+
submitters.forEach(function (submitter) { return submitter.stop(); });
|
|
28
|
+
if (!allExceptTelemetry && telemetrySubmitter)
|
|
29
|
+
telemetrySubmitter.stop();
|
|
30
|
+
},
|
|
31
|
+
isRunning: function () {
|
|
32
|
+
return submitters.some(function (submitter) { return submitter.isRunning(); });
|
|
33
|
+
},
|
|
34
|
+
// Flush data. Called with `onlyTelemetry` true if SDK is destroyed with userConsent not GRANTED
|
|
35
|
+
execute: function (onlyTelemetry) {
|
|
36
|
+
var promises = onlyTelemetry ? [] : submitters.map(function (submitter) { return submitter.execute(); });
|
|
37
|
+
if (telemetrySubmitter)
|
|
38
|
+
promises.push(telemetrySubmitter.execute());
|
|
39
|
+
return Promise.all(promises);
|
|
40
|
+
},
|
|
41
|
+
isExecuting: function () {
|
|
42
|
+
return submitters.some(function (submitter) { return submitter.isExecuting(); });
|
|
43
|
+
}
|
|
44
|
+
};
|
|
21
45
|
}
|
|
22
46
|
exports.submitterManagerFactory = submitterManagerFactory;
|
|
@@ -19,16 +19,16 @@ function syncManagerOnlineFactory(pollingManagerFactory, pushManagerFactory) {
|
|
|
19
19
|
* SyncManager factory for modular SDK
|
|
20
20
|
*/
|
|
21
21
|
return function (params) {
|
|
22
|
-
var settings = params.settings, _a = params.settings, log = _a.log, streamingEnabled = _a.streamingEnabled, telemetryTracker = params.telemetryTracker;
|
|
22
|
+
var settings = params.settings, _a = params.settings, log = _a.log, streamingEnabled = _a.streamingEnabled, syncEnabled = _a.sync.enabled, telemetryTracker = params.telemetryTracker;
|
|
23
23
|
/** Polling Manager */
|
|
24
24
|
var pollingManager = pollingManagerFactory && pollingManagerFactory(params);
|
|
25
25
|
/** Push Manager */
|
|
26
|
-
var pushManager = streamingEnabled && pollingManager && pushManagerFactory ?
|
|
26
|
+
var pushManager = syncEnabled && streamingEnabled && pollingManager && pushManagerFactory ?
|
|
27
27
|
pushManagerFactory(params, pollingManager) :
|
|
28
28
|
undefined;
|
|
29
29
|
/** Submitter Manager */
|
|
30
30
|
// It is not inyected as push and polling managers, because at the moment it is required
|
|
31
|
-
var
|
|
31
|
+
var submitterManager = (0, submitterManager_1.submitterManagerFactory)(params);
|
|
32
32
|
/** Sync Manager logic */
|
|
33
33
|
function startPolling() {
|
|
34
34
|
if (pollingManager.isRunning()) {
|
|
@@ -61,7 +61,7 @@ function syncManagerOnlineFactory(pollingManagerFactory, pushManagerFactory) {
|
|
|
61
61
|
// E.g.: user consent, app state changes (Page hide, Foreground/Background, Online/Offline).
|
|
62
62
|
pollingManager: pollingManager,
|
|
63
63
|
pushManager: pushManager,
|
|
64
|
-
|
|
64
|
+
submitterManager: submitterManager,
|
|
65
65
|
/**
|
|
66
66
|
* Method used to start the syncManager for the first time, or resume it after being stopped.
|
|
67
67
|
*/
|
|
@@ -69,21 +69,29 @@ function syncManagerOnlineFactory(pollingManagerFactory, pushManagerFactory) {
|
|
|
69
69
|
running = true;
|
|
70
70
|
// start syncing splits and segments
|
|
71
71
|
if (pollingManager) {
|
|
72
|
-
|
|
73
|
-
|
|
72
|
+
// If synchronization is disabled pushManager and pollingManager should not start
|
|
73
|
+
if (syncEnabled) {
|
|
74
|
+
if (pushManager) {
|
|
75
|
+
// Doesn't call `syncAll` when the syncManager is resuming
|
|
76
|
+
if (startFirstTime) {
|
|
77
|
+
pollingManager.syncAll();
|
|
78
|
+
startFirstTime = false;
|
|
79
|
+
}
|
|
80
|
+
pushManager.start();
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
pollingManager.start();
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
74
87
|
if (startFirstTime) {
|
|
75
88
|
pollingManager.syncAll();
|
|
76
89
|
startFirstTime = false;
|
|
77
90
|
}
|
|
78
|
-
pushManager.start();
|
|
79
|
-
}
|
|
80
|
-
else {
|
|
81
|
-
pollingManager.start();
|
|
82
91
|
}
|
|
83
92
|
}
|
|
84
93
|
// start periodic data recording (events, impressions, telemetry).
|
|
85
|
-
|
|
86
|
-
submitter.start();
|
|
94
|
+
submitterManager.start(!(0, consent_1.isConsentGranted)(settings));
|
|
87
95
|
},
|
|
88
96
|
/**
|
|
89
97
|
* Method used to stop/pause the syncManager.
|
|
@@ -96,16 +104,13 @@ function syncManagerOnlineFactory(pollingManagerFactory, pushManagerFactory) {
|
|
|
96
104
|
if (pollingManager && pollingManager.isRunning())
|
|
97
105
|
pollingManager.stop();
|
|
98
106
|
// stop periodic data recording (events, impressions, telemetry).
|
|
99
|
-
|
|
107
|
+
submitterManager.stop();
|
|
100
108
|
},
|
|
101
109
|
isRunning: function () {
|
|
102
110
|
return running;
|
|
103
111
|
},
|
|
104
112
|
flush: function () {
|
|
105
|
-
|
|
106
|
-
return submitter.execute();
|
|
107
|
-
else
|
|
108
|
-
return Promise.resolve();
|
|
113
|
+
return submitterManager.execute(!(0, consent_1.isConsentGranted)(settings));
|
|
109
114
|
},
|
|
110
115
|
// [Only used for client-side]
|
|
111
116
|
// If polling and push managers are defined (standalone mode), they implement the interfaces for client-side
|
|
@@ -116,22 +121,28 @@ function syncManagerOnlineFactory(pollingManagerFactory, pushManagerFactory) {
|
|
|
116
121
|
return {
|
|
117
122
|
isRunning: mySegmentsSyncTask.isRunning,
|
|
118
123
|
start: function () {
|
|
119
|
-
if (
|
|
120
|
-
if (
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
+
if (syncEnabled) {
|
|
125
|
+
if (pushManager) {
|
|
126
|
+
if (pollingManager.isRunning()) {
|
|
127
|
+
// if doing polling, we must start the periodic fetch of data
|
|
128
|
+
if (storage.splits.usesSegments())
|
|
129
|
+
mySegmentsSyncTask.start();
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
// if not polling, we must execute the sync task for the initial fetch
|
|
133
|
+
// of segments since `syncAll` was already executed when starting the main client
|
|
134
|
+
mySegmentsSyncTask.execute();
|
|
135
|
+
}
|
|
136
|
+
pushManager.add(matchingKey, mySegmentsSyncTask);
|
|
124
137
|
}
|
|
125
138
|
else {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
mySegmentsSyncTask.execute();
|
|
139
|
+
if (storage.splits.usesSegments())
|
|
140
|
+
mySegmentsSyncTask.start();
|
|
129
141
|
}
|
|
130
|
-
pushManager.add(matchingKey, mySegmentsSyncTask);
|
|
131
142
|
}
|
|
132
143
|
else {
|
|
133
|
-
if (
|
|
134
|
-
mySegmentsSyncTask.
|
|
144
|
+
if (!readinessManager.isReady())
|
|
145
|
+
mySegmentsSyncTask.execute();
|
|
135
146
|
}
|
|
136
147
|
},
|
|
137
148
|
stop: function () {
|
package/cjs/sync/syncTask.js
CHANGED
|
@@ -4,8 +4,8 @@ exports.syncTaskFactory = void 0;
|
|
|
4
4
|
var constants_1 = require("../logger/constants");
|
|
5
5
|
/**
|
|
6
6
|
* Creates a syncTask that handles the periodic execution of a given task ("start" and "stop" methods).
|
|
7
|
-
* The task can be executed
|
|
8
|
-
*
|
|
7
|
+
* The task can be also executed by calling the "execute" method. Multiple execute calls are chained to run secuentially and avoid race conditions.
|
|
8
|
+
* For example, submitters executed on SDK destroy or full queue, while periodic execution is pending.
|
|
9
9
|
*
|
|
10
10
|
* @param log Logger instance.
|
|
11
11
|
* @param task Task to execute that returns a promise that NEVER REJECTS. Otherwise, periodic execution can result in Unhandled Promise Rejections.
|
|
@@ -15,8 +15,8 @@ var constants_1 = require("../logger/constants");
|
|
|
15
15
|
*/
|
|
16
16
|
function syncTaskFactory(log, task, period, taskName) {
|
|
17
17
|
if (taskName === void 0) { taskName = 'task'; }
|
|
18
|
-
//
|
|
19
|
-
var
|
|
18
|
+
// Task promise while it is pending. Undefined once the promise is resolved
|
|
19
|
+
var pendingTask;
|
|
20
20
|
// flag that indicates if the task periodic execution has been started/stopped.
|
|
21
21
|
var running = false;
|
|
22
22
|
// Auxiliar counter used to avoid race condition when calling `start` & `stop` intermittently
|
|
@@ -30,13 +30,19 @@ function syncTaskFactory(log, task, period, taskName) {
|
|
|
30
30
|
for (var _i = 0; _i < arguments.length; _i++) {
|
|
31
31
|
args[_i] = arguments[_i];
|
|
32
32
|
}
|
|
33
|
-
executing
|
|
33
|
+
// If task is executing, chain the new execution
|
|
34
|
+
if (pendingTask) {
|
|
35
|
+
return pendingTask.then(function () {
|
|
36
|
+
return execute.apply(void 0, args);
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
// Execute task
|
|
34
40
|
log.debug(constants_1.SYNC_TASK_EXECUTE, [taskName]);
|
|
35
|
-
|
|
36
|
-
|
|
41
|
+
pendingTask = task.apply(void 0, args).then(function (result) {
|
|
42
|
+
pendingTask = undefined;
|
|
37
43
|
return result;
|
|
38
44
|
});
|
|
39
|
-
|
|
45
|
+
return pendingTask;
|
|
40
46
|
}
|
|
41
47
|
function periodicExecute(currentRunningId) {
|
|
42
48
|
return execute.apply(void 0, runningArgs).then(function (result) {
|
|
@@ -48,10 +54,9 @@ function syncTaskFactory(log, task, period, taskName) {
|
|
|
48
54
|
});
|
|
49
55
|
}
|
|
50
56
|
return {
|
|
51
|
-
// @TODO check if we need to queued `execute` calls, to avoid possible race conditions on submitters and updaters with streaming.
|
|
52
57
|
execute: execute,
|
|
53
58
|
isExecuting: function () {
|
|
54
|
-
return
|
|
59
|
+
return pendingTask !== undefined;
|
|
55
60
|
},
|
|
56
61
|
start: function () {
|
|
57
62
|
var args = [];
|
|
@@ -73,7 +73,8 @@ exports.base = {
|
|
|
73
73
|
splitFilters: undefined,
|
|
74
74
|
// impressions collection mode
|
|
75
75
|
impressionsMode: constants_1.OPTIMIZED,
|
|
76
|
-
localhostMode: undefined
|
|
76
|
+
localhostMode: undefined,
|
|
77
|
+
enabled: true
|
|
77
78
|
},
|
|
78
79
|
// Logger
|
|
79
80
|
log: undefined
|
|
@@ -167,6 +168,10 @@ function settingsValidation(config, validationParams) {
|
|
|
167
168
|
// We are not checking if bases are positive numbers. Thus, we might be reauthenticating immediately (`setTimeout` with NaN or negative number)
|
|
168
169
|
scheduler.pushRetryBackoffBase = fromSecondsToMillis(scheduler.pushRetryBackoffBase);
|
|
169
170
|
}
|
|
171
|
+
// validate sync enabled
|
|
172
|
+
if (withDefaults.sync.enabled !== false) { // @ts-ignore, modify readonly prop
|
|
173
|
+
withDefaults.sync.enabled = true;
|
|
174
|
+
}
|
|
170
175
|
// validate the `splitFilters` settings and parse splits query
|
|
171
176
|
var splitFiltersValidation = (0, splitFilters_1.validateSplitFilters)(log, withDefaults.sync.splitFilters, withDefaults.mode);
|
|
172
177
|
withDefaults.sync.splitFilters = splitFiltersValidation.validFilters;
|
|
@@ -28,10 +28,10 @@ export function createUserConsentAPI(params) {
|
|
|
28
28
|
log.info(USER_CONSENT_UPDATED, [settings.userConsent, newConsentStatus]); // @ts-ignore, modify readonly prop
|
|
29
29
|
settings.userConsent = newConsentStatus;
|
|
30
30
|
if (consent) { // resumes submitters if transitioning to GRANTED
|
|
31
|
-
(_a = syncManager === null || syncManager === void 0 ? void 0 : syncManager.
|
|
31
|
+
(_a = syncManager === null || syncManager === void 0 ? void 0 : syncManager.submitterManager) === null || _a === void 0 ? void 0 : _a.start();
|
|
32
32
|
}
|
|
33
|
-
else { // pauses submitters and drops tracked data if transitioning to DECLINED
|
|
34
|
-
(_b = syncManager === null || syncManager === void 0 ? void 0 : syncManager.
|
|
33
|
+
else { // pauses submitters (except telemetry), and drops tracked data if transitioning to DECLINED
|
|
34
|
+
(_b = syncManager === null || syncManager === void 0 ? void 0 : syncManager.submitterManager) === null || _b === void 0 ? void 0 : _b.stop(true);
|
|
35
35
|
// @ts-ignore, clear method is present in storage for standalone and partial consumer mode
|
|
36
36
|
if (events.clear)
|
|
37
37
|
events.clear(); // @ts-ignore
|
package/esm/listeners/browser.js
CHANGED
|
@@ -50,7 +50,7 @@ var BrowserSignalListener = /** @class */ (function () {
|
|
|
50
50
|
BrowserSignalListener.prototype.flushData = function () {
|
|
51
51
|
if (!this.syncManager)
|
|
52
52
|
return; // In consumer mode there is not sync manager and data to flush
|
|
53
|
-
// Flush data if there is user consent
|
|
53
|
+
// Flush impressions & events data if there is user consent
|
|
54
54
|
if (isConsentGranted(this.settings)) {
|
|
55
55
|
var eventsUrl = this.settings.urls.events;
|
|
56
56
|
var extraMetadata = {
|
|
@@ -61,11 +61,12 @@ var BrowserSignalListener = /** @class */ (function () {
|
|
|
61
61
|
this._flushData(eventsUrl + '/events/beacon', this.storage.events, this.serviceApi.postEventsBulk);
|
|
62
62
|
if (this.storage.impressionCounts)
|
|
63
63
|
this._flushData(eventsUrl + '/testImpressions/count/beacon', this.storage.impressionCounts, this.serviceApi.postTestImpressionsCount, fromImpressionCountsCollector);
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
64
|
+
}
|
|
65
|
+
// Flush telemetry data
|
|
66
|
+
if (this.storage.telemetry) {
|
|
67
|
+
var telemetryUrl = this.settings.urls.telemetry;
|
|
68
|
+
var telemetryCacheAdapter = telemetryCacheStatsAdapter(this.storage.telemetry, this.storage.splits, this.storage.segments);
|
|
69
|
+
this._flushData(telemetryUrl + '/v1/metrics/usage/beacon', telemetryCacheAdapter, this.serviceApi.postMetricsUsage);
|
|
69
70
|
}
|
|
70
71
|
// Close streaming connection
|
|
71
72
|
if (this.syncManager.pushManager)
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { syncTaskComposite } from '../syncTaskComposite';
|
|
2
1
|
import { eventsSubmitterFactory } from './eventsSubmitter';
|
|
3
2
|
import { impressionsSubmitterFactory } from './impressionsSubmitter';
|
|
4
3
|
import { impressionCountsSubmitterFactory } from './impressionCountsSubmitter';
|
|
@@ -12,7 +11,32 @@ export function submitterManagerFactory(params) {
|
|
|
12
11
|
if (impressionCountsSubmitter)
|
|
13
12
|
submitters.push(impressionCountsSubmitter);
|
|
14
13
|
var telemetrySubmitter = telemetrySubmitterFactory(params);
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
14
|
+
return {
|
|
15
|
+
// `onlyTelemetry` true if SDK is created with userConsent not GRANTED
|
|
16
|
+
start: function (onlyTelemetry) {
|
|
17
|
+
if (!onlyTelemetry)
|
|
18
|
+
submitters.forEach(function (submitter) { return submitter.start(); });
|
|
19
|
+
if (telemetrySubmitter)
|
|
20
|
+
telemetrySubmitter.start();
|
|
21
|
+
},
|
|
22
|
+
// `allExceptTelemetry` true if userConsent is changed to DECLINED
|
|
23
|
+
stop: function (allExceptTelemetry) {
|
|
24
|
+
submitters.forEach(function (submitter) { return submitter.stop(); });
|
|
25
|
+
if (!allExceptTelemetry && telemetrySubmitter)
|
|
26
|
+
telemetrySubmitter.stop();
|
|
27
|
+
},
|
|
28
|
+
isRunning: function () {
|
|
29
|
+
return submitters.some(function (submitter) { return submitter.isRunning(); });
|
|
30
|
+
},
|
|
31
|
+
// Flush data. Called with `onlyTelemetry` true if SDK is destroyed with userConsent not GRANTED
|
|
32
|
+
execute: function (onlyTelemetry) {
|
|
33
|
+
var promises = onlyTelemetry ? [] : submitters.map(function (submitter) { return submitter.execute(); });
|
|
34
|
+
if (telemetrySubmitter)
|
|
35
|
+
promises.push(telemetrySubmitter.execute());
|
|
36
|
+
return Promise.all(promises);
|
|
37
|
+
},
|
|
38
|
+
isExecuting: function () {
|
|
39
|
+
return submitters.some(function (submitter) { return submitter.isExecuting(); });
|
|
40
|
+
}
|
|
41
|
+
};
|
|
18
42
|
}
|
|
@@ -16,16 +16,16 @@ export function syncManagerOnlineFactory(pollingManagerFactory, pushManagerFacto
|
|
|
16
16
|
* SyncManager factory for modular SDK
|
|
17
17
|
*/
|
|
18
18
|
return function (params) {
|
|
19
|
-
var settings = params.settings, _a = params.settings, log = _a.log, streamingEnabled = _a.streamingEnabled, telemetryTracker = params.telemetryTracker;
|
|
19
|
+
var settings = params.settings, _a = params.settings, log = _a.log, streamingEnabled = _a.streamingEnabled, syncEnabled = _a.sync.enabled, telemetryTracker = params.telemetryTracker;
|
|
20
20
|
/** Polling Manager */
|
|
21
21
|
var pollingManager = pollingManagerFactory && pollingManagerFactory(params);
|
|
22
22
|
/** Push Manager */
|
|
23
|
-
var pushManager = streamingEnabled && pollingManager && pushManagerFactory ?
|
|
23
|
+
var pushManager = syncEnabled && streamingEnabled && pollingManager && pushManagerFactory ?
|
|
24
24
|
pushManagerFactory(params, pollingManager) :
|
|
25
25
|
undefined;
|
|
26
26
|
/** Submitter Manager */
|
|
27
27
|
// It is not inyected as push and polling managers, because at the moment it is required
|
|
28
|
-
var
|
|
28
|
+
var submitterManager = submitterManagerFactory(params);
|
|
29
29
|
/** Sync Manager logic */
|
|
30
30
|
function startPolling() {
|
|
31
31
|
if (pollingManager.isRunning()) {
|
|
@@ -58,7 +58,7 @@ export function syncManagerOnlineFactory(pollingManagerFactory, pushManagerFacto
|
|
|
58
58
|
// E.g.: user consent, app state changes (Page hide, Foreground/Background, Online/Offline).
|
|
59
59
|
pollingManager: pollingManager,
|
|
60
60
|
pushManager: pushManager,
|
|
61
|
-
|
|
61
|
+
submitterManager: submitterManager,
|
|
62
62
|
/**
|
|
63
63
|
* Method used to start the syncManager for the first time, or resume it after being stopped.
|
|
64
64
|
*/
|
|
@@ -66,21 +66,29 @@ export function syncManagerOnlineFactory(pollingManagerFactory, pushManagerFacto
|
|
|
66
66
|
running = true;
|
|
67
67
|
// start syncing splits and segments
|
|
68
68
|
if (pollingManager) {
|
|
69
|
-
|
|
70
|
-
|
|
69
|
+
// If synchronization is disabled pushManager and pollingManager should not start
|
|
70
|
+
if (syncEnabled) {
|
|
71
|
+
if (pushManager) {
|
|
72
|
+
// Doesn't call `syncAll` when the syncManager is resuming
|
|
73
|
+
if (startFirstTime) {
|
|
74
|
+
pollingManager.syncAll();
|
|
75
|
+
startFirstTime = false;
|
|
76
|
+
}
|
|
77
|
+
pushManager.start();
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
pollingManager.start();
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
71
84
|
if (startFirstTime) {
|
|
72
85
|
pollingManager.syncAll();
|
|
73
86
|
startFirstTime = false;
|
|
74
87
|
}
|
|
75
|
-
pushManager.start();
|
|
76
|
-
}
|
|
77
|
-
else {
|
|
78
|
-
pollingManager.start();
|
|
79
88
|
}
|
|
80
89
|
}
|
|
81
90
|
// start periodic data recording (events, impressions, telemetry).
|
|
82
|
-
|
|
83
|
-
submitter.start();
|
|
91
|
+
submitterManager.start(!isConsentGranted(settings));
|
|
84
92
|
},
|
|
85
93
|
/**
|
|
86
94
|
* Method used to stop/pause the syncManager.
|
|
@@ -93,16 +101,13 @@ export function syncManagerOnlineFactory(pollingManagerFactory, pushManagerFacto
|
|
|
93
101
|
if (pollingManager && pollingManager.isRunning())
|
|
94
102
|
pollingManager.stop();
|
|
95
103
|
// stop periodic data recording (events, impressions, telemetry).
|
|
96
|
-
|
|
104
|
+
submitterManager.stop();
|
|
97
105
|
},
|
|
98
106
|
isRunning: function () {
|
|
99
107
|
return running;
|
|
100
108
|
},
|
|
101
109
|
flush: function () {
|
|
102
|
-
|
|
103
|
-
return submitter.execute();
|
|
104
|
-
else
|
|
105
|
-
return Promise.resolve();
|
|
110
|
+
return submitterManager.execute(!isConsentGranted(settings));
|
|
106
111
|
},
|
|
107
112
|
// [Only used for client-side]
|
|
108
113
|
// If polling and push managers are defined (standalone mode), they implement the interfaces for client-side
|
|
@@ -113,22 +118,28 @@ export function syncManagerOnlineFactory(pollingManagerFactory, pushManagerFacto
|
|
|
113
118
|
return {
|
|
114
119
|
isRunning: mySegmentsSyncTask.isRunning,
|
|
115
120
|
start: function () {
|
|
116
|
-
if (
|
|
117
|
-
if (
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
+
if (syncEnabled) {
|
|
122
|
+
if (pushManager) {
|
|
123
|
+
if (pollingManager.isRunning()) {
|
|
124
|
+
// if doing polling, we must start the periodic fetch of data
|
|
125
|
+
if (storage.splits.usesSegments())
|
|
126
|
+
mySegmentsSyncTask.start();
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
// if not polling, we must execute the sync task for the initial fetch
|
|
130
|
+
// of segments since `syncAll` was already executed when starting the main client
|
|
131
|
+
mySegmentsSyncTask.execute();
|
|
132
|
+
}
|
|
133
|
+
pushManager.add(matchingKey, mySegmentsSyncTask);
|
|
121
134
|
}
|
|
122
135
|
else {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
mySegmentsSyncTask.execute();
|
|
136
|
+
if (storage.splits.usesSegments())
|
|
137
|
+
mySegmentsSyncTask.start();
|
|
126
138
|
}
|
|
127
|
-
pushManager.add(matchingKey, mySegmentsSyncTask);
|
|
128
139
|
}
|
|
129
140
|
else {
|
|
130
|
-
if (
|
|
131
|
-
mySegmentsSyncTask.
|
|
141
|
+
if (!readinessManager.isReady())
|
|
142
|
+
mySegmentsSyncTask.execute();
|
|
132
143
|
}
|
|
133
144
|
},
|
|
134
145
|
stop: function () {
|
package/esm/sync/syncTask.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { SYNC_TASK_EXECUTE, SYNC_TASK_START, SYNC_TASK_STOP } from '../logger/constants';
|
|
2
2
|
/**
|
|
3
3
|
* Creates a syncTask that handles the periodic execution of a given task ("start" and "stop" methods).
|
|
4
|
-
* The task can be executed
|
|
5
|
-
*
|
|
4
|
+
* The task can be also executed by calling the "execute" method. Multiple execute calls are chained to run secuentially and avoid race conditions.
|
|
5
|
+
* For example, submitters executed on SDK destroy or full queue, while periodic execution is pending.
|
|
6
6
|
*
|
|
7
7
|
* @param log Logger instance.
|
|
8
8
|
* @param task Task to execute that returns a promise that NEVER REJECTS. Otherwise, periodic execution can result in Unhandled Promise Rejections.
|
|
@@ -12,8 +12,8 @@ import { SYNC_TASK_EXECUTE, SYNC_TASK_START, SYNC_TASK_STOP } from '../logger/co
|
|
|
12
12
|
*/
|
|
13
13
|
export function syncTaskFactory(log, task, period, taskName) {
|
|
14
14
|
if (taskName === void 0) { taskName = 'task'; }
|
|
15
|
-
//
|
|
16
|
-
var
|
|
15
|
+
// Task promise while it is pending. Undefined once the promise is resolved
|
|
16
|
+
var pendingTask;
|
|
17
17
|
// flag that indicates if the task periodic execution has been started/stopped.
|
|
18
18
|
var running = false;
|
|
19
19
|
// Auxiliar counter used to avoid race condition when calling `start` & `stop` intermittently
|
|
@@ -27,13 +27,19 @@ export function syncTaskFactory(log, task, period, taskName) {
|
|
|
27
27
|
for (var _i = 0; _i < arguments.length; _i++) {
|
|
28
28
|
args[_i] = arguments[_i];
|
|
29
29
|
}
|
|
30
|
-
executing
|
|
30
|
+
// If task is executing, chain the new execution
|
|
31
|
+
if (pendingTask) {
|
|
32
|
+
return pendingTask.then(function () {
|
|
33
|
+
return execute.apply(void 0, args);
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
// Execute task
|
|
31
37
|
log.debug(SYNC_TASK_EXECUTE, [taskName]);
|
|
32
|
-
|
|
33
|
-
|
|
38
|
+
pendingTask = task.apply(void 0, args).then(function (result) {
|
|
39
|
+
pendingTask = undefined;
|
|
34
40
|
return result;
|
|
35
41
|
});
|
|
36
|
-
|
|
42
|
+
return pendingTask;
|
|
37
43
|
}
|
|
38
44
|
function periodicExecute(currentRunningId) {
|
|
39
45
|
return execute.apply(void 0, runningArgs).then(function (result) {
|
|
@@ -45,10 +51,9 @@ export function syncTaskFactory(log, task, period, taskName) {
|
|
|
45
51
|
});
|
|
46
52
|
}
|
|
47
53
|
return {
|
|
48
|
-
// @TODO check if we need to queued `execute` calls, to avoid possible race conditions on submitters and updaters with streaming.
|
|
49
54
|
execute: execute,
|
|
50
55
|
isExecuting: function () {
|
|
51
|
-
return
|
|
56
|
+
return pendingTask !== undefined;
|
|
52
57
|
},
|
|
53
58
|
start: function () {
|
|
54
59
|
var args = [];
|
|
@@ -70,7 +70,8 @@ export var base = {
|
|
|
70
70
|
splitFilters: undefined,
|
|
71
71
|
// impressions collection mode
|
|
72
72
|
impressionsMode: OPTIMIZED,
|
|
73
|
-
localhostMode: undefined
|
|
73
|
+
localhostMode: undefined,
|
|
74
|
+
enabled: true
|
|
74
75
|
},
|
|
75
76
|
// Logger
|
|
76
77
|
log: undefined
|
|
@@ -164,6 +165,10 @@ export function settingsValidation(config, validationParams) {
|
|
|
164
165
|
// We are not checking if bases are positive numbers. Thus, we might be reauthenticating immediately (`setTimeout` with NaN or negative number)
|
|
165
166
|
scheduler.pushRetryBackoffBase = fromSecondsToMillis(scheduler.pushRetryBackoffBase);
|
|
166
167
|
}
|
|
168
|
+
// validate sync enabled
|
|
169
|
+
if (withDefaults.sync.enabled !== false) { // @ts-ignore, modify readonly prop
|
|
170
|
+
withDefaults.sync.enabled = true;
|
|
171
|
+
}
|
|
167
172
|
// validate the `splitFilters` settings and parse splits query
|
|
168
173
|
var splitFiltersValidation = validateSplitFilters(log, withDefaults.sync.splitFilters, withDefaults.mode);
|
|
169
174
|
withDefaults.sync.splitFilters = splitFiltersValidation.validFilters;
|
package/package.json
CHANGED
|
@@ -34,9 +34,10 @@ export function createUserConsentAPI(params: ISdkFactoryContext) {
|
|
|
34
34
|
settings.userConsent = newConsentStatus;
|
|
35
35
|
|
|
36
36
|
if (consent) { // resumes submitters if transitioning to GRANTED
|
|
37
|
-
syncManager?.
|
|
38
|
-
} else { // pauses submitters and drops tracked data if transitioning to DECLINED
|
|
39
|
-
syncManager?.
|
|
37
|
+
syncManager?.submitterManager?.start();
|
|
38
|
+
} else { // pauses submitters (except telemetry), and drops tracked data if transitioning to DECLINED
|
|
39
|
+
syncManager?.submitterManager?.stop(true);
|
|
40
|
+
|
|
40
41
|
// @ts-ignore, clear method is present in storage for standalone and partial consumer mode
|
|
41
42
|
if (events.clear) events.clear(); // @ts-ignore
|
|
42
43
|
if (impressions.clear) impressions.clear();
|
package/src/listeners/browser.ts
CHANGED
|
@@ -67,7 +67,7 @@ export class BrowserSignalListener implements ISignalListener {
|
|
|
67
67
|
flushData() {
|
|
68
68
|
if (!this.syncManager) return; // In consumer mode there is not sync manager and data to flush
|
|
69
69
|
|
|
70
|
-
// Flush data if there is user consent
|
|
70
|
+
// Flush impressions & events data if there is user consent
|
|
71
71
|
if (isConsentGranted(this.settings)) {
|
|
72
72
|
const eventsUrl = this.settings.urls.events;
|
|
73
73
|
const extraMetadata = {
|
|
@@ -78,11 +78,13 @@ export class BrowserSignalListener implements ISignalListener {
|
|
|
78
78
|
this._flushData(eventsUrl + '/testImpressions/beacon', this.storage.impressions, this.serviceApi.postTestImpressionsBulk, this.fromImpressionsCollector, extraMetadata);
|
|
79
79
|
this._flushData(eventsUrl + '/events/beacon', this.storage.events, this.serviceApi.postEventsBulk);
|
|
80
80
|
if (this.storage.impressionCounts) this._flushData(eventsUrl + '/testImpressions/count/beacon', this.storage.impressionCounts, this.serviceApi.postTestImpressionsCount, fromImpressionCountsCollector);
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Flush telemetry data
|
|
84
|
+
if (this.storage.telemetry) {
|
|
85
|
+
const telemetryUrl = this.settings.urls.telemetry;
|
|
86
|
+
const telemetryCacheAdapter = telemetryCacheStatsAdapter(this.storage.telemetry, this.storage.splits, this.storage.segments);
|
|
87
|
+
this._flushData(telemetryUrl + '/v1/metrics/usage/beacon', telemetryCacheAdapter, this.serviceApi.postMetricsUsage);
|
|
86
88
|
}
|
|
87
89
|
|
|
88
90
|
// Close streaming connection
|
|
@@ -5,7 +5,7 @@ import { ILogger } from '../logger/types';
|
|
|
5
5
|
import { objectAssign } from '../utils/lang/objectAssign';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
|
-
* Add in memory attributes storage methods and combine them with any attribute received from the getTreatment/s call
|
|
8
|
+
* Add in memory attributes storage methods and combine them with any attribute received from the getTreatment/s call
|
|
9
9
|
*/
|
|
10
10
|
export function clientAttributesDecoration<TClient extends SplitIO.IClient | SplitIO.IAsyncClient>(log: ILogger, client: TClient) {
|
|
11
11
|
|
|
@@ -52,10 +52,10 @@ export function clientAttributesDecoration<TClient extends SplitIO.IClient | Spl
|
|
|
52
52
|
getTreatments: getTreatments,
|
|
53
53
|
getTreatmentsWithConfig: getTreatmentsWithConfig,
|
|
54
54
|
track: track,
|
|
55
|
-
|
|
55
|
+
|
|
56
56
|
/**
|
|
57
57
|
* Add an attribute to client's in memory attributes storage
|
|
58
|
-
*
|
|
58
|
+
*
|
|
59
59
|
* @param {string} attributeName Attrinute name
|
|
60
60
|
* @param {string, number, boolean, list} attributeValue Attribute value
|
|
61
61
|
* @returns {boolean} true if the attribute was stored and false otherways
|
|
@@ -70,7 +70,7 @@ export function clientAttributesDecoration<TClient extends SplitIO.IClient | Spl
|
|
|
70
70
|
|
|
71
71
|
/**
|
|
72
72
|
* Returns the attribute with the given key
|
|
73
|
-
*
|
|
73
|
+
*
|
|
74
74
|
* @param {string} attributeName Attribute name
|
|
75
75
|
* @returns {Object} Attribute with the given key
|
|
76
76
|
*/
|
|
@@ -81,7 +81,7 @@ export function clientAttributesDecoration<TClient extends SplitIO.IClient | Spl
|
|
|
81
81
|
|
|
82
82
|
/**
|
|
83
83
|
* Add to client's in memory attributes storage the attributes in 'attributes'
|
|
84
|
-
*
|
|
84
|
+
*
|
|
85
85
|
* @param {Object} attributes Object with attributes to store
|
|
86
86
|
* @returns true if attributes were stored an false otherways
|
|
87
87
|
*/
|
|
@@ -92,7 +92,7 @@ export function clientAttributesDecoration<TClient extends SplitIO.IClient | Spl
|
|
|
92
92
|
|
|
93
93
|
/**
|
|
94
94
|
* Return all the attributes stored in client's in memory attributes storage
|
|
95
|
-
*
|
|
95
|
+
*
|
|
96
96
|
* @returns {Object} returns all the stored attributes
|
|
97
97
|
*/
|
|
98
98
|
getAttributes(): Record<string, Object> {
|
|
@@ -101,8 +101,8 @@ export function clientAttributesDecoration<TClient extends SplitIO.IClient | Spl
|
|
|
101
101
|
|
|
102
102
|
/**
|
|
103
103
|
* Removes from client's in memory attributes storage the attribute with the given key
|
|
104
|
-
*
|
|
105
|
-
* @param {string} attributeName
|
|
104
|
+
*
|
|
105
|
+
* @param {string} attributeName
|
|
106
106
|
* @returns {boolean} true if attribute was removed and false otherways
|
|
107
107
|
*/
|
|
108
108
|
removeAttribute(attributeName: string) {
|
|
@@ -116,7 +116,7 @@ export function clientAttributesDecoration<TClient extends SplitIO.IClient | Spl
|
|
|
116
116
|
clearAttributes() {
|
|
117
117
|
return attributeStorage.clear();
|
|
118
118
|
}
|
|
119
|
-
|
|
119
|
+
|
|
120
120
|
});
|
|
121
121
|
|
|
122
122
|
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { syncTaskComposite } from '../syncTaskComposite';
|
|
2
1
|
import { eventsSubmitterFactory } from './eventsSubmitter';
|
|
3
2
|
import { impressionsSubmitterFactory } from './impressionsSubmitter';
|
|
4
3
|
import { impressionCountsSubmitterFactory } from './impressionCountsSubmitter';
|
|
5
4
|
import { telemetrySubmitterFactory } from './telemetrySubmitter';
|
|
6
5
|
import { ISdkFactoryContextSync } from '../../sdkFactory/types';
|
|
6
|
+
import { ISubmitterManager } from './types';
|
|
7
7
|
|
|
8
|
-
export function submitterManagerFactory(params: ISdkFactoryContextSync) {
|
|
8
|
+
export function submitterManagerFactory(params: ISdkFactoryContextSync): ISubmitterManager {
|
|
9
9
|
|
|
10
10
|
const submitters = [
|
|
11
11
|
impressionsSubmitterFactory(params),
|
|
@@ -15,7 +15,33 @@ export function submitterManagerFactory(params: ISdkFactoryContextSync) {
|
|
|
15
15
|
const impressionCountsSubmitter = impressionCountsSubmitterFactory(params);
|
|
16
16
|
if (impressionCountsSubmitter) submitters.push(impressionCountsSubmitter);
|
|
17
17
|
const telemetrySubmitter = telemetrySubmitterFactory(params);
|
|
18
|
-
if (telemetrySubmitter) submitters.push(telemetrySubmitter);
|
|
19
18
|
|
|
20
|
-
return
|
|
19
|
+
return {
|
|
20
|
+
// `onlyTelemetry` true if SDK is created with userConsent not GRANTED
|
|
21
|
+
start(onlyTelemetry?: boolean) {
|
|
22
|
+
if (!onlyTelemetry) submitters.forEach(submitter => submitter.start());
|
|
23
|
+
if (telemetrySubmitter) telemetrySubmitter.start();
|
|
24
|
+
},
|
|
25
|
+
|
|
26
|
+
// `allExceptTelemetry` true if userConsent is changed to DECLINED
|
|
27
|
+
stop(allExceptTelemetry?: boolean) {
|
|
28
|
+
submitters.forEach(submitter => submitter.stop());
|
|
29
|
+
if (!allExceptTelemetry && telemetrySubmitter) telemetrySubmitter.stop();
|
|
30
|
+
},
|
|
31
|
+
|
|
32
|
+
isRunning() {
|
|
33
|
+
return submitters.some(submitter => submitter.isRunning());
|
|
34
|
+
},
|
|
35
|
+
|
|
36
|
+
// Flush data. Called with `onlyTelemetry` true if SDK is destroyed with userConsent not GRANTED
|
|
37
|
+
execute(onlyTelemetry?: boolean) {
|
|
38
|
+
const promises = onlyTelemetry ? [] : submitters.map(submitter => submitter.execute());
|
|
39
|
+
if (telemetrySubmitter) promises.push(telemetrySubmitter.execute());
|
|
40
|
+
return Promise.all(promises);
|
|
41
|
+
},
|
|
42
|
+
|
|
43
|
+
isExecuting() {
|
|
44
|
+
return submitters.some(submitter => submitter.isExecuting());
|
|
45
|
+
}
|
|
46
|
+
};
|
|
21
47
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { IMetadata } from '../../dtos/types';
|
|
2
2
|
import { SplitIO } from '../../types';
|
|
3
|
+
import { ISyncTask } from '../types';
|
|
3
4
|
|
|
4
5
|
export type ImpressionsPayload = {
|
|
5
6
|
/** Split name */
|
|
@@ -191,3 +192,9 @@ export type TelemetryConfigStatsPayload = TelemetryConfigStats & {
|
|
|
191
192
|
i?: Array<string>, // integrations
|
|
192
193
|
uC: number, // userConsent
|
|
193
194
|
}
|
|
195
|
+
|
|
196
|
+
export interface ISubmitterManager extends ISyncTask {
|
|
197
|
+
start(onlyTelemetry?: boolean): void,
|
|
198
|
+
stop(allExceptTelemetry?: boolean): void,
|
|
199
|
+
execute(onlyTelemetry?: boolean): Promise<any>
|
|
200
|
+
}
|
|
@@ -28,19 +28,19 @@ export function syncManagerOnlineFactory(
|
|
|
28
28
|
*/
|
|
29
29
|
return function (params: ISdkFactoryContextSync): ISyncManagerCS {
|
|
30
30
|
|
|
31
|
-
const { settings, settings: { log, streamingEnabled },
|
|
31
|
+
const { settings, settings: { log, streamingEnabled, sync: { enabled: syncEnabled } }, telemetryTracker } = params;
|
|
32
32
|
|
|
33
33
|
/** Polling Manager */
|
|
34
34
|
const pollingManager = pollingManagerFactory && pollingManagerFactory(params);
|
|
35
35
|
|
|
36
36
|
/** Push Manager */
|
|
37
|
-
const pushManager = streamingEnabled && pollingManager && pushManagerFactory ?
|
|
37
|
+
const pushManager = syncEnabled && streamingEnabled && pollingManager && pushManagerFactory ?
|
|
38
38
|
pushManagerFactory(params, pollingManager) :
|
|
39
39
|
undefined;
|
|
40
40
|
|
|
41
41
|
/** Submitter Manager */
|
|
42
42
|
// It is not inyected as push and polling managers, because at the moment it is required
|
|
43
|
-
const
|
|
43
|
+
const submitterManager = submitterManagerFactory(params);
|
|
44
44
|
|
|
45
45
|
/** Sync Manager logic */
|
|
46
46
|
|
|
@@ -79,7 +79,7 @@ export function syncManagerOnlineFactory(
|
|
|
79
79
|
// E.g.: user consent, app state changes (Page hide, Foreground/Background, Online/Offline).
|
|
80
80
|
pollingManager,
|
|
81
81
|
pushManager,
|
|
82
|
-
|
|
82
|
+
submitterManager,
|
|
83
83
|
|
|
84
84
|
/**
|
|
85
85
|
* Method used to start the syncManager for the first time, or resume it after being stopped.
|
|
@@ -89,20 +89,29 @@ export function syncManagerOnlineFactory(
|
|
|
89
89
|
|
|
90
90
|
// start syncing splits and segments
|
|
91
91
|
if (pollingManager) {
|
|
92
|
-
|
|
93
|
-
|
|
92
|
+
|
|
93
|
+
// If synchronization is disabled pushManager and pollingManager should not start
|
|
94
|
+
if (syncEnabled) {
|
|
95
|
+
if (pushManager) {
|
|
96
|
+
// Doesn't call `syncAll` when the syncManager is resuming
|
|
97
|
+
if (startFirstTime) {
|
|
98
|
+
pollingManager.syncAll();
|
|
99
|
+
startFirstTime = false;
|
|
100
|
+
}
|
|
101
|
+
pushManager.start();
|
|
102
|
+
} else {
|
|
103
|
+
pollingManager.start();
|
|
104
|
+
}
|
|
105
|
+
} else {
|
|
94
106
|
if (startFirstTime) {
|
|
95
107
|
pollingManager.syncAll();
|
|
96
108
|
startFirstTime = false;
|
|
97
109
|
}
|
|
98
|
-
pushManager.start();
|
|
99
|
-
} else {
|
|
100
|
-
pollingManager.start();
|
|
101
110
|
}
|
|
102
111
|
}
|
|
103
112
|
|
|
104
113
|
// start periodic data recording (events, impressions, telemetry).
|
|
105
|
-
|
|
114
|
+
submitterManager.start(!isConsentGranted(settings));
|
|
106
115
|
},
|
|
107
116
|
|
|
108
117
|
/**
|
|
@@ -116,7 +125,7 @@ export function syncManagerOnlineFactory(
|
|
|
116
125
|
if (pollingManager && pollingManager.isRunning()) pollingManager.stop();
|
|
117
126
|
|
|
118
127
|
// stop periodic data recording (events, impressions, telemetry).
|
|
119
|
-
|
|
128
|
+
submitterManager.stop();
|
|
120
129
|
},
|
|
121
130
|
|
|
122
131
|
isRunning() {
|
|
@@ -124,8 +133,7 @@ export function syncManagerOnlineFactory(
|
|
|
124
133
|
},
|
|
125
134
|
|
|
126
135
|
flush() {
|
|
127
|
-
|
|
128
|
-
else return Promise.resolve();
|
|
136
|
+
return submitterManager.execute(!isConsentGranted(settings));
|
|
129
137
|
},
|
|
130
138
|
|
|
131
139
|
// [Only used for client-side]
|
|
@@ -138,18 +146,22 @@ export function syncManagerOnlineFactory(
|
|
|
138
146
|
return {
|
|
139
147
|
isRunning: mySegmentsSyncTask.isRunning,
|
|
140
148
|
start() {
|
|
141
|
-
if (
|
|
142
|
-
if (
|
|
143
|
-
|
|
144
|
-
|
|
149
|
+
if (syncEnabled) {
|
|
150
|
+
if (pushManager) {
|
|
151
|
+
if (pollingManager!.isRunning()) {
|
|
152
|
+
// if doing polling, we must start the periodic fetch of data
|
|
153
|
+
if (storage.splits.usesSegments()) mySegmentsSyncTask.start();
|
|
154
|
+
} else {
|
|
155
|
+
// if not polling, we must execute the sync task for the initial fetch
|
|
156
|
+
// of segments since `syncAll` was already executed when starting the main client
|
|
157
|
+
mySegmentsSyncTask.execute();
|
|
158
|
+
}
|
|
159
|
+
pushManager.add(matchingKey, mySegmentsSyncTask);
|
|
145
160
|
} else {
|
|
146
|
-
|
|
147
|
-
// of segments since `syncAll` was already executed when starting the main client
|
|
148
|
-
mySegmentsSyncTask.execute();
|
|
161
|
+
if (storage.splits.usesSegments()) mySegmentsSyncTask.start();
|
|
149
162
|
}
|
|
150
|
-
pushManager.add(matchingKey, mySegmentsSyncTask);
|
|
151
163
|
} else {
|
|
152
|
-
if (
|
|
164
|
+
if (!readinessManager.isReady()) mySegmentsSyncTask.execute();
|
|
153
165
|
}
|
|
154
166
|
},
|
|
155
167
|
stop() {
|
package/src/sync/syncTask.ts
CHANGED
|
@@ -4,8 +4,8 @@ import { ISyncTask } from './types';
|
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Creates a syncTask that handles the periodic execution of a given task ("start" and "stop" methods).
|
|
7
|
-
* The task can be executed
|
|
8
|
-
*
|
|
7
|
+
* The task can be also executed by calling the "execute" method. Multiple execute calls are chained to run secuentially and avoid race conditions.
|
|
8
|
+
* For example, submitters executed on SDK destroy or full queue, while periodic execution is pending.
|
|
9
9
|
*
|
|
10
10
|
* @param log Logger instance.
|
|
11
11
|
* @param task Task to execute that returns a promise that NEVER REJECTS. Otherwise, periodic execution can result in Unhandled Promise Rejections.
|
|
@@ -15,8 +15,8 @@ import { ISyncTask } from './types';
|
|
|
15
15
|
*/
|
|
16
16
|
export function syncTaskFactory<Input extends any[], Output = any>(log: ILogger, task: (...args: Input) => Promise<Output>, period: number, taskName = 'task'): ISyncTask<Input, Output> {
|
|
17
17
|
|
|
18
|
-
//
|
|
19
|
-
let
|
|
18
|
+
// Task promise while it is pending. Undefined once the promise is resolved
|
|
19
|
+
let pendingTask: Promise<Output> | undefined;
|
|
20
20
|
// flag that indicates if the task periodic execution has been started/stopped.
|
|
21
21
|
let running = false;
|
|
22
22
|
// Auxiliar counter used to avoid race condition when calling `start` & `stop` intermittently
|
|
@@ -26,14 +26,21 @@ export function syncTaskFactory<Input extends any[], Output = any>(log: ILogger,
|
|
|
26
26
|
// Id of the periodic call timeout
|
|
27
27
|
let timeoutID: any;
|
|
28
28
|
|
|
29
|
-
function execute(...args: Input) {
|
|
30
|
-
executing
|
|
29
|
+
function execute(...args: Input): Promise<Output> {
|
|
30
|
+
// If task is executing, chain the new execution
|
|
31
|
+
if (pendingTask) {
|
|
32
|
+
return pendingTask.then(() => {
|
|
33
|
+
return execute(...args);
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Execute task
|
|
31
38
|
log.debug(SYNC_TASK_EXECUTE, [taskName]);
|
|
32
|
-
|
|
33
|
-
|
|
39
|
+
pendingTask = task(...args).then(result => {
|
|
40
|
+
pendingTask = undefined;
|
|
34
41
|
return result;
|
|
35
42
|
});
|
|
36
|
-
|
|
43
|
+
return pendingTask;
|
|
37
44
|
}
|
|
38
45
|
|
|
39
46
|
function periodicExecute(currentRunningId: number) {
|
|
@@ -46,11 +53,10 @@ export function syncTaskFactory<Input extends any[], Output = any>(log: ILogger,
|
|
|
46
53
|
}
|
|
47
54
|
|
|
48
55
|
return {
|
|
49
|
-
// @TODO check if we need to queued `execute` calls, to avoid possible race conditions on submitters and updaters with streaming.
|
|
50
56
|
execute,
|
|
51
57
|
|
|
52
58
|
isExecuting() {
|
|
53
|
-
return
|
|
59
|
+
return pendingTask !== undefined;
|
|
54
60
|
},
|
|
55
61
|
|
|
56
62
|
start(...args: Input) {
|
package/src/sync/types.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { IReadinessManager } from '../readiness/types';
|
|
|
2
2
|
import { IStorageSync } from '../storages/types';
|
|
3
3
|
import { IPollingManager } from './polling/types';
|
|
4
4
|
import { IPushManager } from './streaming/types';
|
|
5
|
+
import { ISubmitterManager } from './submitters/types';
|
|
5
6
|
|
|
6
7
|
export interface ITask<Input extends any[] = []> {
|
|
7
8
|
/**
|
|
@@ -39,7 +40,7 @@ export interface ISyncManager extends ITask {
|
|
|
39
40
|
flush(): Promise<any>,
|
|
40
41
|
pushManager?: IPushManager,
|
|
41
42
|
pollingManager?: IPollingManager,
|
|
42
|
-
|
|
43
|
+
submitterManager?: ISubmitterManager
|
|
43
44
|
}
|
|
44
45
|
|
|
45
46
|
export interface ISyncManagerCS extends ISyncManager {
|
package/src/types.ts
CHANGED
|
@@ -117,7 +117,8 @@ export interface ISettings {
|
|
|
117
117
|
splitFilters: SplitIO.SplitFilter[],
|
|
118
118
|
impressionsMode: SplitIO.ImpressionsMode,
|
|
119
119
|
__splitFiltersValidation: ISplitFiltersValidation,
|
|
120
|
-
localhostMode?: SplitIO.LocalhostFactory
|
|
120
|
+
localhostMode?: SplitIO.LocalhostFactory,
|
|
121
|
+
enabled: boolean
|
|
121
122
|
},
|
|
122
123
|
readonly runtime: {
|
|
123
124
|
ip: string | false
|
|
@@ -214,6 +215,11 @@ interface ISharedSettings {
|
|
|
214
215
|
* @default 'OPTIMIZED'
|
|
215
216
|
*/
|
|
216
217
|
impressionsMode?: SplitIO.ImpressionsMode,
|
|
218
|
+
/**
|
|
219
|
+
* Enables synchronization.
|
|
220
|
+
* @property {boolean} enabled
|
|
221
|
+
*/
|
|
222
|
+
enabled: boolean
|
|
217
223
|
}
|
|
218
224
|
}
|
|
219
225
|
/**
|
|
@@ -83,7 +83,8 @@ export const base = {
|
|
|
83
83
|
splitFilters: undefined,
|
|
84
84
|
// impressions collection mode
|
|
85
85
|
impressionsMode: OPTIMIZED,
|
|
86
|
-
localhostMode: undefined
|
|
86
|
+
localhostMode: undefined,
|
|
87
|
+
enabled: true
|
|
87
88
|
},
|
|
88
89
|
|
|
89
90
|
// Logger
|
|
@@ -191,6 +192,11 @@ export function settingsValidation(config: unknown, validationParams: ISettingsV
|
|
|
191
192
|
scheduler.pushRetryBackoffBase = fromSecondsToMillis(scheduler.pushRetryBackoffBase);
|
|
192
193
|
}
|
|
193
194
|
|
|
195
|
+
// validate sync enabled
|
|
196
|
+
if (withDefaults.sync.enabled !== false) { // @ts-ignore, modify readonly prop
|
|
197
|
+
withDefaults.sync.enabled = true;
|
|
198
|
+
}
|
|
199
|
+
|
|
194
200
|
// validate the `splitFilters` settings and parse splits query
|
|
195
201
|
const splitFiltersValidation = validateSplitFilters(log, withDefaults.sync.splitFilters, withDefaults.mode);
|
|
196
202
|
withDefaults.sync.splitFilters = splitFiltersValidation.validFilters;
|
|
@@ -1,2 +1,3 @@
|
|
|
1
1
|
import { ISdkFactoryContextSync } from '../../sdkFactory/types';
|
|
2
|
-
|
|
2
|
+
import { ISubmitterManager } from './types';
|
|
3
|
+
export declare function submitterManagerFactory(params: ISdkFactoryContextSync): ISubmitterManager;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { IMetadata } from '../../dtos/types';
|
|
2
2
|
import { SplitIO } from '../../types';
|
|
3
|
+
import { ISyncTask } from '../types';
|
|
3
4
|
export declare type ImpressionsPayload = {
|
|
4
5
|
/** Split name */
|
|
5
6
|
f: string;
|
|
@@ -169,3 +170,8 @@ export declare type TelemetryConfigStatsPayload = TelemetryConfigStats & {
|
|
|
169
170
|
i?: Array<string>;
|
|
170
171
|
uC: number;
|
|
171
172
|
};
|
|
173
|
+
export interface ISubmitterManager extends ISyncTask {
|
|
174
|
+
start(onlyTelemetry?: boolean): void;
|
|
175
|
+
stop(allExceptTelemetry?: boolean): void;
|
|
176
|
+
execute(onlyTelemetry?: boolean): Promise<any>;
|
|
177
|
+
}
|
package/types/sync/syncTask.d.ts
CHANGED
|
@@ -2,8 +2,8 @@ import { ILogger } from '../logger/types';
|
|
|
2
2
|
import { ISyncTask } from './types';
|
|
3
3
|
/**
|
|
4
4
|
* Creates a syncTask that handles the periodic execution of a given task ("start" and "stop" methods).
|
|
5
|
-
* The task can be executed
|
|
6
|
-
*
|
|
5
|
+
* The task can be also executed by calling the "execute" method. Multiple execute calls are chained to run secuentially and avoid race conditions.
|
|
6
|
+
* For example, submitters executed on SDK destroy or full queue, while periodic execution is pending.
|
|
7
7
|
*
|
|
8
8
|
* @param log Logger instance.
|
|
9
9
|
* @param task Task to execute that returns a promise that NEVER REJECTS. Otherwise, periodic execution can result in Unhandled Promise Rejections.
|
package/types/sync/types.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { IReadinessManager } from '../readiness/types';
|
|
|
2
2
|
import { IStorageSync } from '../storages/types';
|
|
3
3
|
import { IPollingManager } from './polling/types';
|
|
4
4
|
import { IPushManager } from './streaming/types';
|
|
5
|
+
import { ISubmitterManager } from './submitters/types';
|
|
5
6
|
export interface ITask<Input extends any[] = []> {
|
|
6
7
|
/**
|
|
7
8
|
* Start periodic execution of the task
|
|
@@ -35,7 +36,7 @@ export interface ISyncManager extends ITask {
|
|
|
35
36
|
flush(): Promise<any>;
|
|
36
37
|
pushManager?: IPushManager;
|
|
37
38
|
pollingManager?: IPollingManager;
|
|
38
|
-
|
|
39
|
+
submitterManager?: ISubmitterManager;
|
|
39
40
|
}
|
|
40
41
|
export interface ISyncManagerCS extends ISyncManager {
|
|
41
42
|
shared(matchingKey: string, readinessManager: IReadinessManager, storage: IStorageSync): ISyncManager | undefined;
|
package/types/types.d.ts
CHANGED
|
@@ -112,6 +112,7 @@ export interface ISettings {
|
|
|
112
112
|
impressionsMode: SplitIO.ImpressionsMode;
|
|
113
113
|
__splitFiltersValidation: ISplitFiltersValidation;
|
|
114
114
|
localhostMode?: SplitIO.LocalhostFactory;
|
|
115
|
+
enabled: boolean;
|
|
115
116
|
};
|
|
116
117
|
readonly runtime: {
|
|
117
118
|
ip: string | false;
|
|
@@ -208,6 +209,11 @@ interface ISharedSettings {
|
|
|
208
209
|
* @default 'OPTIMIZED'
|
|
209
210
|
*/
|
|
210
211
|
impressionsMode?: SplitIO.ImpressionsMode;
|
|
212
|
+
/**
|
|
213
|
+
* Enables synchronization.
|
|
214
|
+
* @property {boolean} enabled
|
|
215
|
+
*/
|
|
216
|
+
enabled: boolean;
|
|
211
217
|
};
|
|
212
218
|
}
|
|
213
219
|
/**
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.syncTaskComposite = void 0;
|
|
4
|
-
/**
|
|
5
|
-
* Composite Sync Task: group of sync tasks that are treated as a single one.
|
|
6
|
-
*/
|
|
7
|
-
function syncTaskComposite(syncTasks) {
|
|
8
|
-
return {
|
|
9
|
-
start: function () {
|
|
10
|
-
syncTasks.forEach(function (syncTask) { return syncTask.start(); });
|
|
11
|
-
},
|
|
12
|
-
stop: function () {
|
|
13
|
-
syncTasks.forEach(function (syncTask) { return syncTask.stop(); });
|
|
14
|
-
},
|
|
15
|
-
isRunning: function () {
|
|
16
|
-
return syncTasks.some(function (syncTask) { return syncTask.isRunning(); });
|
|
17
|
-
},
|
|
18
|
-
execute: function () {
|
|
19
|
-
return Promise.all(syncTasks.map(function (syncTask) { return syncTask.execute(); }));
|
|
20
|
-
},
|
|
21
|
-
isExecuting: function () {
|
|
22
|
-
return syncTasks.some(function (syncTask) { return syncTask.isExecuting(); });
|
|
23
|
-
}
|
|
24
|
-
};
|
|
25
|
-
}
|
|
26
|
-
exports.syncTaskComposite = syncTaskComposite;
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Composite Sync Task: group of sync tasks that are treated as a single one.
|
|
3
|
-
*/
|
|
4
|
-
export function syncTaskComposite(syncTasks) {
|
|
5
|
-
return {
|
|
6
|
-
start: function () {
|
|
7
|
-
syncTasks.forEach(function (syncTask) { return syncTask.start(); });
|
|
8
|
-
},
|
|
9
|
-
stop: function () {
|
|
10
|
-
syncTasks.forEach(function (syncTask) { return syncTask.stop(); });
|
|
11
|
-
},
|
|
12
|
-
isRunning: function () {
|
|
13
|
-
return syncTasks.some(function (syncTask) { return syncTask.isRunning(); });
|
|
14
|
-
},
|
|
15
|
-
execute: function () {
|
|
16
|
-
return Promise.all(syncTasks.map(function (syncTask) { return syncTask.execute(); }));
|
|
17
|
-
},
|
|
18
|
-
isExecuting: function () {
|
|
19
|
-
return syncTasks.some(function (syncTask) { return syncTask.isExecuting(); });
|
|
20
|
-
}
|
|
21
|
-
};
|
|
22
|
-
}
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import { ISyncTask } from './types';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Composite Sync Task: group of sync tasks that are treated as a single one.
|
|
5
|
-
*/
|
|
6
|
-
export function syncTaskComposite(syncTasks: ISyncTask[]): ISyncTask {
|
|
7
|
-
|
|
8
|
-
return {
|
|
9
|
-
start() {
|
|
10
|
-
syncTasks.forEach(syncTask => syncTask.start());
|
|
11
|
-
},
|
|
12
|
-
stop() {
|
|
13
|
-
syncTasks.forEach(syncTask => syncTask.stop());
|
|
14
|
-
},
|
|
15
|
-
isRunning() {
|
|
16
|
-
return syncTasks.some(syncTask => syncTask.isRunning());
|
|
17
|
-
},
|
|
18
|
-
execute() {
|
|
19
|
-
return Promise.all(syncTasks.map(syncTask => syncTask.execute()));
|
|
20
|
-
},
|
|
21
|
-
isExecuting() {
|
|
22
|
-
return syncTasks.some(syncTask => syncTask.isExecuting());
|
|
23
|
-
}
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
}
|