@splitsoftware/splitio-commons 1.4.1-rc.1 → 1.4.2-rc.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGES.txt +4 -0
- package/cjs/consent/sdkUserConsent.js +3 -3
- package/cjs/integrations/ga/GaToSplit.js +12 -11
- package/cjs/integrations/ga/autoRequire.js +34 -0
- package/cjs/listeners/browser.js +7 -6
- package/cjs/storages/inMemory/EventsCacheInMemory.js +2 -2
- package/cjs/storages/inMemory/ImpressionCountsCacheInMemory.js +10 -1
- package/cjs/storages/inMemory/ImpressionsCacheInMemory.js +2 -2
- package/cjs/sync/submitters/submitter.js +3 -7
- package/cjs/sync/submitters/submitterManager.js +28 -4
- package/cjs/sync/syncManagerOnline.js +5 -9
- package/esm/consent/sdkUserConsent.js +3 -3
- package/esm/integrations/ga/GaToSplit.js +12 -11
- package/esm/integrations/ga/autoRequire.js +30 -0
- package/esm/listeners/browser.js +7 -6
- package/esm/storages/inMemory/EventsCacheInMemory.js +2 -2
- package/esm/storages/inMemory/ImpressionCountsCacheInMemory.js +10 -1
- package/esm/storages/inMemory/ImpressionsCacheInMemory.js +2 -2
- package/esm/sync/submitters/submitter.js +3 -7
- package/esm/sync/submitters/submitterManager.js +28 -4
- package/esm/sync/syncManagerOnline.js +5 -9
- package/package.json +1 -1
- package/src/consent/sdkUserConsent.ts +4 -3
- package/src/integrations/ga/GaToSplit.ts +14 -8
- package/src/integrations/ga/autoRequire.ts +35 -0
- package/src/integrations/ga/types.ts +13 -2
- package/src/listeners/browser.ts +9 -7
- package/src/storages/inMemory/EventsCacheInMemory.ts +2 -2
- package/src/storages/inMemory/ImpressionCountsCacheInMemory.ts +8 -1
- package/src/storages/inMemory/ImpressionsCacheInMemory.ts +2 -2
- package/src/storages/types.ts +2 -2
- package/src/sync/submitters/submitter.ts +6 -10
- package/src/sync/submitters/submitterManager.ts +30 -4
- package/src/sync/submitters/types.ts +7 -0
- package/src/sync/syncManagerOnline.ts +5 -6
- package/src/sync/types.ts +2 -1
- package/types/integrations/ga/autoRequire.d.ts +4 -0
- package/types/integrations/ga/types.d.ts +13 -2
- package/types/storages/inMemory/EventsCacheInMemory.d.ts +1 -1
- package/types/storages/inMemory/ImpressionCountsCacheInMemory.d.ts +1 -1
- package/types/storages/inMemory/ImpressionsCacheInMemory.d.ts +1 -1
- package/types/storages/types.d.ts +2 -2
- package/types/sync/submitters/submitter.d.ts +1 -1
- package/types/sync/submitters/submitterManager.d.ts +2 -1
- package/types/sync/submitters/types.d.ts +6 -0
- package/types/sync/types.d.ts +2 -1
- 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,7 @@
|
|
|
1
|
+
1.4.2 (June XX, 2022)
|
|
2
|
+
- Added `autoRequire` configuration for GoogleAnalyticsToSplit integration (See https://help.split.io/hc/en-us/articles/360040838752#google-tag-manager).
|
|
3
|
+
- Updated telemetry to submit data even if user consent is not granted.
|
|
4
|
+
|
|
1
5
|
1.4.1 (June 13, 2022)
|
|
2
6
|
- Bugfixing - Updated submitters logic, to avoid dropping impressions and events that are being tracked while POST request is pending.
|
|
3
7
|
|
|
@@ -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
|
|
@@ -10,23 +10,24 @@ var logNameMapper = 'ga-to-split:mapper';
|
|
|
10
10
|
/**
|
|
11
11
|
* Provides a plugin to use with analytics.js, accounting for the possibility
|
|
12
12
|
* that the global command queue has been renamed or not yet defined.
|
|
13
|
-
* @param
|
|
14
|
-
* @param
|
|
13
|
+
* @param window Reference to global object.
|
|
14
|
+
* @param pluginName The plugin name identifier.
|
|
15
|
+
* @param pluginConstructor The plugin constructor function.
|
|
16
|
+
* @param log Logger instance.
|
|
17
|
+
* @param autoRequire If true, log error when auto-require script is not detected
|
|
15
18
|
*/
|
|
16
|
-
function providePlugin(pluginName, pluginConstructor) {
|
|
19
|
+
function providePlugin(window, pluginName, pluginConstructor, log, autoRequire) {
|
|
17
20
|
// get reference to global command queue. Init it if not defined yet.
|
|
18
|
-
// @ts-expect-error
|
|
19
21
|
var gaAlias = window.GoogleAnalyticsObject || 'ga';
|
|
20
22
|
window[gaAlias] = window[gaAlias] || function () {
|
|
21
|
-
|
|
22
|
-
for (var _i = 0; _i < arguments.length; _i++) {
|
|
23
|
-
args[_i] = arguments[_i];
|
|
24
|
-
}
|
|
25
|
-
(window[gaAlias].q = window[gaAlias].q || []).push(args);
|
|
23
|
+
(window[gaAlias].q = window[gaAlias].q || []).push(arguments);
|
|
26
24
|
};
|
|
27
25
|
// provides the plugin for use with analytics.js.
|
|
28
|
-
// @ts-expect-error
|
|
29
26
|
window[gaAlias]('provide', pluginName, pluginConstructor);
|
|
27
|
+
if (autoRequire && (!window[gaAlias].q || window[gaAlias].q.push === [].push)) {
|
|
28
|
+
// Expecting spy on ga.q push method but not found
|
|
29
|
+
log.error('Auto-require script was expected but not provided.');
|
|
30
|
+
}
|
|
30
31
|
}
|
|
31
32
|
// Default mapping: object used for building the default mapper from hits to Split events
|
|
32
33
|
var defaultMapping = {
|
|
@@ -246,6 +247,6 @@ function GaToSplit(sdkOptions, params) {
|
|
|
246
247
|
return SplitTracker;
|
|
247
248
|
}());
|
|
248
249
|
// Register the plugin, even if config is invalid, since, if not provided, it will block `ga` command queue.
|
|
249
|
-
providePlugin('splitTracker', SplitTracker);
|
|
250
|
+
providePlugin(window, 'splitTracker', SplitTracker, log, sdkOptions.autoRequire);
|
|
250
251
|
}
|
|
251
252
|
exports.GaToSplit = GaToSplit;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.autoRequire = void 0;
|
|
4
|
+
/* eslint-disable no-undef */
|
|
5
|
+
/**
|
|
6
|
+
* Auto-require script to use with GaToSplit integration
|
|
7
|
+
*/
|
|
8
|
+
function autoRequire() {
|
|
9
|
+
(function (i, r, s) {
|
|
10
|
+
i[s] = i[s] || r;
|
|
11
|
+
i[r] = i[r] || function () { i[r].q.push(arguments); };
|
|
12
|
+
i[r].q = i[r].q || [];
|
|
13
|
+
var ts = {}; // Tracker names
|
|
14
|
+
var o = i[r].q.push;
|
|
15
|
+
i[r].q.push = function (v) {
|
|
16
|
+
var result = o.apply(this, arguments);
|
|
17
|
+
if (v && v[0] === 'create') {
|
|
18
|
+
var t = typeof v[2] === 'object' && typeof v[2].name === 'string' ?
|
|
19
|
+
v[2].name : // `ga('create', 'UA-ID', { name: 'trackerName', ... })`
|
|
20
|
+
typeof v[3] === 'object' && typeof v[3].name === 'string' ?
|
|
21
|
+
v[3].name : // `ga('create', 'UA-ID', 'auto', { name: 'trackerName', ... })`
|
|
22
|
+
typeof v[3] === 'string' ?
|
|
23
|
+
v[3] : // `ga('create', 'UA-ID', 'auto', 'trackerName')`
|
|
24
|
+
undefined; // No name tracker, e.g.: `ga('create', 'UA-ID', 'auto')`
|
|
25
|
+
if (!ts[t]) {
|
|
26
|
+
ts[t] = true;
|
|
27
|
+
i[r](t ? t + '.require' : 'require', 'splitTracker'); // Auto-require
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return result;
|
|
31
|
+
};
|
|
32
|
+
})(window, 'ga', 'GoogleAnalyticsObject');
|
|
33
|
+
}
|
|
34
|
+
exports.autoRequire = autoRequire;
|
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)
|
|
@@ -37,10 +37,10 @@ var EventsCacheInMemory = /** @class */ (function () {
|
|
|
37
37
|
/**
|
|
38
38
|
* Pop the collected data, used as payload for posting.
|
|
39
39
|
*/
|
|
40
|
-
EventsCacheInMemory.prototype.pop = function () {
|
|
40
|
+
EventsCacheInMemory.prototype.pop = function (toMerge) {
|
|
41
41
|
var data = this.queue;
|
|
42
42
|
this.clear();
|
|
43
|
-
return data;
|
|
43
|
+
return toMerge ? toMerge.concat(data) : data;
|
|
44
44
|
};
|
|
45
45
|
/**
|
|
46
46
|
* Check if the cache is empty.
|
|
@@ -23,9 +23,18 @@ var ImpressionCountsCacheInMemory = /** @class */ (function () {
|
|
|
23
23
|
/**
|
|
24
24
|
* Pop the collected data, used as payload for posting.
|
|
25
25
|
*/
|
|
26
|
-
ImpressionCountsCacheInMemory.prototype.pop = function () {
|
|
26
|
+
ImpressionCountsCacheInMemory.prototype.pop = function (toMerge) {
|
|
27
27
|
var data = this.cache;
|
|
28
28
|
this.clear();
|
|
29
|
+
if (toMerge) {
|
|
30
|
+
Object.keys(data).forEach(function (key) {
|
|
31
|
+
if (toMerge[key])
|
|
32
|
+
toMerge[key] += data[key];
|
|
33
|
+
else
|
|
34
|
+
toMerge[key] = data[key];
|
|
35
|
+
});
|
|
36
|
+
return toMerge;
|
|
37
|
+
}
|
|
29
38
|
return data;
|
|
30
39
|
};
|
|
31
40
|
/**
|
|
@@ -35,10 +35,10 @@ var ImpressionsCacheInMemory = /** @class */ (function () {
|
|
|
35
35
|
/**
|
|
36
36
|
* Pop the collected data, used as payload for posting.
|
|
37
37
|
*/
|
|
38
|
-
ImpressionsCacheInMemory.prototype.pop = function () {
|
|
38
|
+
ImpressionsCacheInMemory.prototype.pop = function (toMerge) {
|
|
39
39
|
var data = this.queue;
|
|
40
40
|
this.clear();
|
|
41
|
-
return data;
|
|
41
|
+
return toMerge ? toMerge.concat(data) : data;
|
|
42
42
|
};
|
|
43
43
|
/**
|
|
44
44
|
* Check if the cache is empty.
|
|
@@ -12,13 +12,9 @@ function submitterFactory(log, postClient, sourceCache, postRate, dataName, from
|
|
|
12
12
|
var retries = 0;
|
|
13
13
|
var data;
|
|
14
14
|
function postData() {
|
|
15
|
-
if (!data)
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
// we clear the cache to track new items, while `data` is used for retries
|
|
19
|
-
data = sourceCache.pop();
|
|
20
|
-
}
|
|
21
|
-
// @ts-ignore
|
|
15
|
+
if (sourceCache.isEmpty() && !data)
|
|
16
|
+
return Promise.resolve();
|
|
17
|
+
data = sourceCache.pop(data);
|
|
22
18
|
var dataCountMessage = typeof data.length === 'number' ? data.length + " " + dataName : dataName;
|
|
23
19
|
log[debugLogs ? 'debug' : 'info'](constants_1.SUBMITTERS_PUSH, [dataCountMessage]);
|
|
24
20
|
var jsonPayload = JSON.stringify(fromCacheToPayload ? fromCacheToPayload(data) : data);
|
|
@@ -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;
|
|
@@ -28,7 +28,7 @@ function syncManagerOnlineFactory(pollingManagerFactory, pushManagerFactory) {
|
|
|
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
|
*/
|
|
@@ -82,8 +82,7 @@ function syncManagerOnlineFactory(pollingManagerFactory, pushManagerFactory) {
|
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
84
|
// start periodic data recording (events, impressions, telemetry).
|
|
85
|
-
|
|
86
|
-
submitter.start();
|
|
85
|
+
submitterManager.start(!(0, consent_1.isConsentGranted)(settings));
|
|
87
86
|
},
|
|
88
87
|
/**
|
|
89
88
|
* Method used to stop/pause the syncManager.
|
|
@@ -96,16 +95,13 @@ function syncManagerOnlineFactory(pollingManagerFactory, pushManagerFactory) {
|
|
|
96
95
|
if (pollingManager && pollingManager.isRunning())
|
|
97
96
|
pollingManager.stop();
|
|
98
97
|
// stop periodic data recording (events, impressions, telemetry).
|
|
99
|
-
|
|
98
|
+
submitterManager.stop();
|
|
100
99
|
},
|
|
101
100
|
isRunning: function () {
|
|
102
101
|
return running;
|
|
103
102
|
},
|
|
104
103
|
flush: function () {
|
|
105
|
-
|
|
106
|
-
return submitter.execute();
|
|
107
|
-
else
|
|
108
|
-
return Promise.resolve();
|
|
104
|
+
return submitterManager.execute(!(0, consent_1.isConsentGranted)(settings));
|
|
109
105
|
},
|
|
110
106
|
// [Only used for client-side]
|
|
111
107
|
// If polling and push managers are defined (standalone mode), they implement the interfaces for client-side
|
|
@@ -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
|
|
@@ -7,23 +7,24 @@ var logNameMapper = 'ga-to-split:mapper';
|
|
|
7
7
|
/**
|
|
8
8
|
* Provides a plugin to use with analytics.js, accounting for the possibility
|
|
9
9
|
* that the global command queue has been renamed or not yet defined.
|
|
10
|
-
* @param
|
|
11
|
-
* @param
|
|
10
|
+
* @param window Reference to global object.
|
|
11
|
+
* @param pluginName The plugin name identifier.
|
|
12
|
+
* @param pluginConstructor The plugin constructor function.
|
|
13
|
+
* @param log Logger instance.
|
|
14
|
+
* @param autoRequire If true, log error when auto-require script is not detected
|
|
12
15
|
*/
|
|
13
|
-
function providePlugin(pluginName, pluginConstructor) {
|
|
16
|
+
function providePlugin(window, pluginName, pluginConstructor, log, autoRequire) {
|
|
14
17
|
// get reference to global command queue. Init it if not defined yet.
|
|
15
|
-
// @ts-expect-error
|
|
16
18
|
var gaAlias = window.GoogleAnalyticsObject || 'ga';
|
|
17
19
|
window[gaAlias] = window[gaAlias] || function () {
|
|
18
|
-
|
|
19
|
-
for (var _i = 0; _i < arguments.length; _i++) {
|
|
20
|
-
args[_i] = arguments[_i];
|
|
21
|
-
}
|
|
22
|
-
(window[gaAlias].q = window[gaAlias].q || []).push(args);
|
|
20
|
+
(window[gaAlias].q = window[gaAlias].q || []).push(arguments);
|
|
23
21
|
};
|
|
24
22
|
// provides the plugin for use with analytics.js.
|
|
25
|
-
// @ts-expect-error
|
|
26
23
|
window[gaAlias]('provide', pluginName, pluginConstructor);
|
|
24
|
+
if (autoRequire && (!window[gaAlias].q || window[gaAlias].q.push === [].push)) {
|
|
25
|
+
// Expecting spy on ga.q push method but not found
|
|
26
|
+
log.error('Auto-require script was expected but not provided.');
|
|
27
|
+
}
|
|
27
28
|
}
|
|
28
29
|
// Default mapping: object used for building the default mapper from hits to Split events
|
|
29
30
|
var defaultMapping = {
|
|
@@ -240,5 +241,5 @@ export function GaToSplit(sdkOptions, params) {
|
|
|
240
241
|
return SplitTracker;
|
|
241
242
|
}());
|
|
242
243
|
// Register the plugin, even if config is invalid, since, if not provided, it will block `ga` command queue.
|
|
243
|
-
providePlugin('splitTracker', SplitTracker);
|
|
244
|
+
providePlugin(window, 'splitTracker', SplitTracker, log, sdkOptions.autoRequire);
|
|
244
245
|
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/* eslint-disable no-undef */
|
|
2
|
+
/**
|
|
3
|
+
* Auto-require script to use with GaToSplit integration
|
|
4
|
+
*/
|
|
5
|
+
export function autoRequire() {
|
|
6
|
+
(function (i, r, s) {
|
|
7
|
+
i[s] = i[s] || r;
|
|
8
|
+
i[r] = i[r] || function () { i[r].q.push(arguments); };
|
|
9
|
+
i[r].q = i[r].q || [];
|
|
10
|
+
var ts = {}; // Tracker names
|
|
11
|
+
var o = i[r].q.push;
|
|
12
|
+
i[r].q.push = function (v) {
|
|
13
|
+
var result = o.apply(this, arguments);
|
|
14
|
+
if (v && v[0] === 'create') {
|
|
15
|
+
var t = typeof v[2] === 'object' && typeof v[2].name === 'string' ?
|
|
16
|
+
v[2].name : // `ga('create', 'UA-ID', { name: 'trackerName', ... })`
|
|
17
|
+
typeof v[3] === 'object' && typeof v[3].name === 'string' ?
|
|
18
|
+
v[3].name : // `ga('create', 'UA-ID', 'auto', { name: 'trackerName', ... })`
|
|
19
|
+
typeof v[3] === 'string' ?
|
|
20
|
+
v[3] : // `ga('create', 'UA-ID', 'auto', 'trackerName')`
|
|
21
|
+
undefined; // No name tracker, e.g.: `ga('create', 'UA-ID', 'auto')`
|
|
22
|
+
if (!ts[t]) {
|
|
23
|
+
ts[t] = true;
|
|
24
|
+
i[r](t ? t + '.require' : 'require', 'splitTracker'); // Auto-require
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return result;
|
|
28
|
+
};
|
|
29
|
+
})(window, 'ga', 'GoogleAnalyticsObject');
|
|
30
|
+
}
|
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)
|
|
@@ -34,10 +34,10 @@ var EventsCacheInMemory = /** @class */ (function () {
|
|
|
34
34
|
/**
|
|
35
35
|
* Pop the collected data, used as payload for posting.
|
|
36
36
|
*/
|
|
37
|
-
EventsCacheInMemory.prototype.pop = function () {
|
|
37
|
+
EventsCacheInMemory.prototype.pop = function (toMerge) {
|
|
38
38
|
var data = this.queue;
|
|
39
39
|
this.clear();
|
|
40
|
-
return data;
|
|
40
|
+
return toMerge ? toMerge.concat(data) : data;
|
|
41
41
|
};
|
|
42
42
|
/**
|
|
43
43
|
* Check if the cache is empty.
|
|
@@ -20,9 +20,18 @@ var ImpressionCountsCacheInMemory = /** @class */ (function () {
|
|
|
20
20
|
/**
|
|
21
21
|
* Pop the collected data, used as payload for posting.
|
|
22
22
|
*/
|
|
23
|
-
ImpressionCountsCacheInMemory.prototype.pop = function () {
|
|
23
|
+
ImpressionCountsCacheInMemory.prototype.pop = function (toMerge) {
|
|
24
24
|
var data = this.cache;
|
|
25
25
|
this.clear();
|
|
26
|
+
if (toMerge) {
|
|
27
|
+
Object.keys(data).forEach(function (key) {
|
|
28
|
+
if (toMerge[key])
|
|
29
|
+
toMerge[key] += data[key];
|
|
30
|
+
else
|
|
31
|
+
toMerge[key] = data[key];
|
|
32
|
+
});
|
|
33
|
+
return toMerge;
|
|
34
|
+
}
|
|
26
35
|
return data;
|
|
27
36
|
};
|
|
28
37
|
/**
|
|
@@ -32,10 +32,10 @@ var ImpressionsCacheInMemory = /** @class */ (function () {
|
|
|
32
32
|
/**
|
|
33
33
|
* Pop the collected data, used as payload for posting.
|
|
34
34
|
*/
|
|
35
|
-
ImpressionsCacheInMemory.prototype.pop = function () {
|
|
35
|
+
ImpressionsCacheInMemory.prototype.pop = function (toMerge) {
|
|
36
36
|
var data = this.queue;
|
|
37
37
|
this.clear();
|
|
38
|
-
return data;
|
|
38
|
+
return toMerge ? toMerge.concat(data) : data;
|
|
39
39
|
};
|
|
40
40
|
/**
|
|
41
41
|
* Check if the cache is empty.
|
|
@@ -9,13 +9,9 @@ export function submitterFactory(log, postClient, sourceCache, postRate, dataNam
|
|
|
9
9
|
var retries = 0;
|
|
10
10
|
var data;
|
|
11
11
|
function postData() {
|
|
12
|
-
if (!data)
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
// we clear the cache to track new items, while `data` is used for retries
|
|
16
|
-
data = sourceCache.pop();
|
|
17
|
-
}
|
|
18
|
-
// @ts-ignore
|
|
12
|
+
if (sourceCache.isEmpty() && !data)
|
|
13
|
+
return Promise.resolve();
|
|
14
|
+
data = sourceCache.pop(data);
|
|
19
15
|
var dataCountMessage = typeof data.length === 'number' ? data.length + " " + dataName : dataName;
|
|
20
16
|
log[debugLogs ? 'debug' : 'info'](SUBMITTERS_PUSH, [dataCountMessage]);
|
|
21
17
|
var jsonPayload = JSON.stringify(fromCacheToPayload ? fromCacheToPayload(data) : data);
|
|
@@ -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
|
}
|
|
@@ -25,7 +25,7 @@ export function syncManagerOnlineFactory(pollingManagerFactory, pushManagerFacto
|
|
|
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
|
*/
|
|
@@ -79,8 +79,7 @@ export function syncManagerOnlineFactory(pollingManagerFactory, pushManagerFacto
|
|
|
79
79
|
}
|
|
80
80
|
}
|
|
81
81
|
// start periodic data recording (events, impressions, telemetry).
|
|
82
|
-
|
|
83
|
-
submitter.start();
|
|
82
|
+
submitterManager.start(!isConsentGranted(settings));
|
|
84
83
|
},
|
|
85
84
|
/**
|
|
86
85
|
* Method used to stop/pause the syncManager.
|
|
@@ -93,16 +92,13 @@ export function syncManagerOnlineFactory(pollingManagerFactory, pushManagerFacto
|
|
|
93
92
|
if (pollingManager && pollingManager.isRunning())
|
|
94
93
|
pollingManager.stop();
|
|
95
94
|
// stop periodic data recording (events, impressions, telemetry).
|
|
96
|
-
|
|
95
|
+
submitterManager.stop();
|
|
97
96
|
},
|
|
98
97
|
isRunning: function () {
|
|
99
98
|
return running;
|
|
100
99
|
},
|
|
101
100
|
flush: function () {
|
|
102
|
-
|
|
103
|
-
return submitter.execute();
|
|
104
|
-
else
|
|
105
|
-
return Promise.resolve();
|
|
101
|
+
return submitterManager.execute(!isConsentGranted(settings));
|
|
106
102
|
},
|
|
107
103
|
// [Only used for client-side]
|
|
108
104
|
// If polling and push managers are defined (standalone mode), they implement the interfaces for client-side
|
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();
|
|
@@ -19,20 +19,26 @@ const logNameMapper = 'ga-to-split:mapper';
|
|
|
19
19
|
/**
|
|
20
20
|
* Provides a plugin to use with analytics.js, accounting for the possibility
|
|
21
21
|
* that the global command queue has been renamed or not yet defined.
|
|
22
|
-
* @param
|
|
23
|
-
* @param
|
|
22
|
+
* @param window Reference to global object.
|
|
23
|
+
* @param pluginName The plugin name identifier.
|
|
24
|
+
* @param pluginConstructor The plugin constructor function.
|
|
25
|
+
* @param log Logger instance.
|
|
26
|
+
* @param autoRequire If true, log error when auto-require script is not detected
|
|
24
27
|
*/
|
|
25
|
-
function providePlugin(pluginName: string, pluginConstructor: Function) {
|
|
28
|
+
function providePlugin(window: any, pluginName: string, pluginConstructor: Function, log: ILogger, autoRequire?: boolean) {
|
|
26
29
|
// get reference to global command queue. Init it if not defined yet.
|
|
27
|
-
// @ts-expect-error
|
|
28
30
|
const gaAlias = window.GoogleAnalyticsObject || 'ga';
|
|
29
|
-
window[gaAlias] = window[gaAlias] || function (
|
|
30
|
-
(window[gaAlias].q = window[gaAlias].q || []).push(
|
|
31
|
+
window[gaAlias] = window[gaAlias] || function () {
|
|
32
|
+
(window[gaAlias].q = window[gaAlias].q || []).push(arguments);
|
|
31
33
|
};
|
|
32
34
|
|
|
33
35
|
// provides the plugin for use with analytics.js.
|
|
34
|
-
// @ts-expect-error
|
|
35
36
|
window[gaAlias]('provide', pluginName, pluginConstructor);
|
|
37
|
+
|
|
38
|
+
if (autoRequire && (!window[gaAlias].q || window[gaAlias].q.push === [].push)) {
|
|
39
|
+
// Expecting spy on ga.q push method but not found
|
|
40
|
+
log.error('Auto-require script was expected but not provided.');
|
|
41
|
+
}
|
|
36
42
|
}
|
|
37
43
|
|
|
38
44
|
// Default mapping: object used for building the default mapper from hits to Split events
|
|
@@ -284,5 +290,5 @@ export function GaToSplit(sdkOptions: GoogleAnalyticsToSplitOptions, params: IIn
|
|
|
284
290
|
}
|
|
285
291
|
|
|
286
292
|
// Register the plugin, even if config is invalid, since, if not provided, it will block `ga` command queue.
|
|
287
|
-
providePlugin('splitTracker', SplitTracker);
|
|
293
|
+
providePlugin(window, 'splitTracker', SplitTracker, log, sdkOptions.autoRequire);
|
|
288
294
|
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/* eslint-disable no-undef */
|
|
2
|
+
/**
|
|
3
|
+
* Auto-require script to use with GaToSplit integration
|
|
4
|
+
*/
|
|
5
|
+
export function autoRequire() {
|
|
6
|
+
(function (i: any, r: string, s: string) {
|
|
7
|
+
i[s] = i[s] || r;
|
|
8
|
+
i[r] = i[r] || function () { i[r].q.push(arguments); };
|
|
9
|
+
i[r].q = i[r].q || [];
|
|
10
|
+
|
|
11
|
+
var ts: any = {}; // Tracker names
|
|
12
|
+
var o = i[r].q.push;
|
|
13
|
+
i[r].q.push = function (v: any) {
|
|
14
|
+
var result = o.apply(this, arguments);
|
|
15
|
+
|
|
16
|
+
if (v && v[0] === 'create') {
|
|
17
|
+
var t = typeof v[2] === 'object' && typeof v[2].name === 'string' ?
|
|
18
|
+
v[2].name : // `ga('create', 'UA-ID', { name: 'trackerName', ... })`
|
|
19
|
+
typeof v[3] === 'object' && typeof v[3].name === 'string' ?
|
|
20
|
+
v[3].name : // `ga('create', 'UA-ID', 'auto', { name: 'trackerName', ... })`
|
|
21
|
+
typeof v[3] === 'string' ?
|
|
22
|
+
v[3] : // `ga('create', 'UA-ID', 'auto', 'trackerName')`
|
|
23
|
+
undefined; // No name tracker, e.g.: `ga('create', 'UA-ID', 'auto')`
|
|
24
|
+
|
|
25
|
+
if (!ts[t]) {
|
|
26
|
+
ts[t] = true;
|
|
27
|
+
i[r](t ? t + '.require' : 'require', 'splitTracker'); // Auto-require
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return result;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
})(window, 'ga', 'GoogleAnalyticsObject');
|
|
35
|
+
}
|
|
@@ -53,13 +53,24 @@ export interface GoogleAnalyticsToSplitOptions {
|
|
|
53
53
|
* If not provided, events are sent using the key and traffic type provided at SDK config
|
|
54
54
|
*/
|
|
55
55
|
identities?: Identity[],
|
|
56
|
+
/**
|
|
57
|
+
* Optional flag to log an error if the `auto-require` script is not detected.
|
|
58
|
+
* The auto-require script automatically requires the `splitTracker` plugin for created trackers,
|
|
59
|
+
* and should be placed right after your Google Analytics, GTM or gtag.js script tag.
|
|
60
|
+
*
|
|
61
|
+
* @see {@link https://help.split.io/hc/en-us/articles/360040838752#google-tag-manager}
|
|
62
|
+
*
|
|
63
|
+
* @property {boolean} autoRequire
|
|
64
|
+
* @default false
|
|
65
|
+
*/
|
|
66
|
+
autoRequire?: boolean,
|
|
56
67
|
}
|
|
57
68
|
|
|
58
69
|
/**
|
|
59
70
|
* Enable 'Google Analytics to Split' integration, to track Google Analytics hits as Split events.
|
|
60
71
|
* Used by the browser variant of the isomorphic JS SDK.
|
|
61
72
|
*
|
|
62
|
-
* @see {@link https://help.split.io/hc/en-us/articles/
|
|
73
|
+
* @see {@link https://help.split.io/hc/en-us/articles/360040838752#google-analytics-to-split}
|
|
63
74
|
*/
|
|
64
75
|
export interface IGoogleAnalyticsToSplitConfig extends GoogleAnalyticsToSplitOptions {
|
|
65
76
|
type: 'GOOGLE_ANALYTICS_TO_SPLIT'
|
|
@@ -129,7 +140,7 @@ export interface SplitToGoogleAnalyticsOptions {
|
|
|
129
140
|
* Enable 'Split to Google Analytics' integration, to track Split impressions and events as Google Analytics hits.
|
|
130
141
|
* Used by the browser variant of the isomorphic JS SDK.
|
|
131
142
|
*
|
|
132
|
-
* @see {@link https://help.split.io/hc/en-us/articles/
|
|
143
|
+
* @see {@link https://help.split.io/hc/en-us/articles/360040838752#split-to-google-analytics}
|
|
133
144
|
*/
|
|
134
145
|
export interface ISplitToGoogleAnalyticsConfig extends SplitToGoogleAnalyticsOptions {
|
|
135
146
|
type: 'SPLIT_TO_GOOGLE_ANALYTICS'
|
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,18 +78,20 @@ 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
|
|
89
91
|
if (this.syncManager.pushManager) this.syncManager.pushManager.stop();
|
|
90
92
|
}
|
|
91
93
|
|
|
92
|
-
private _flushData<
|
|
94
|
+
private _flushData<T>(url: string, cache: IRecorderCacheProducerSync<T>, postService: (body: string) => Promise<IResponse>, fromCacheToPayload?: (cacheData: T) => any, extraMetadata?: {}) {
|
|
93
95
|
// if there is data in cache, send it to backend
|
|
94
96
|
if (!cache.isEmpty()) {
|
|
95
97
|
const dataPayload = fromCacheToPayload ? fromCacheToPayload(cache.pop()) : cache.pop();
|
|
@@ -48,10 +48,10 @@ export class EventsCacheInMemory implements IEventsCacheSync {
|
|
|
48
48
|
/**
|
|
49
49
|
* Pop the collected data, used as payload for posting.
|
|
50
50
|
*/
|
|
51
|
-
pop() {
|
|
51
|
+
pop(toMerge?: SplitIO.EventData[]) {
|
|
52
52
|
const data = this.queue;
|
|
53
53
|
this.clear();
|
|
54
|
-
return data;
|
|
54
|
+
return toMerge ? toMerge.concat(data) : data;
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
/**
|
|
@@ -25,9 +25,16 @@ export class ImpressionCountsCacheInMemory implements IImpressionCountsCacheSync
|
|
|
25
25
|
/**
|
|
26
26
|
* Pop the collected data, used as payload for posting.
|
|
27
27
|
*/
|
|
28
|
-
pop() {
|
|
28
|
+
pop(toMerge?: Record<string, number>) {
|
|
29
29
|
const data = this.cache;
|
|
30
30
|
this.clear();
|
|
31
|
+
if (toMerge) {
|
|
32
|
+
Object.keys(data).forEach((key) => {
|
|
33
|
+
if (toMerge[key]) toMerge[key] += data[key];
|
|
34
|
+
else toMerge[key] = data[key];
|
|
35
|
+
});
|
|
36
|
+
return toMerge;
|
|
37
|
+
}
|
|
31
38
|
return data;
|
|
32
39
|
}
|
|
33
40
|
|
|
@@ -43,10 +43,10 @@ export class ImpressionsCacheInMemory implements IImpressionsCacheSync {
|
|
|
43
43
|
/**
|
|
44
44
|
* Pop the collected data, used as payload for posting.
|
|
45
45
|
*/
|
|
46
|
-
pop() {
|
|
46
|
+
pop(toMerge?: ImpressionDTO[]) {
|
|
47
47
|
const data = this.queue;
|
|
48
48
|
this.clear();
|
|
49
|
-
return data;
|
|
49
|
+
return toMerge ? toMerge.concat(data) : data;
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
/**
|
package/src/storages/types.ts
CHANGED
|
@@ -302,7 +302,7 @@ export interface IRecorderCacheProducerSync<T> {
|
|
|
302
302
|
/* Clears cache data */
|
|
303
303
|
clear(): void
|
|
304
304
|
/* Pops cache data */
|
|
305
|
-
pop(): T
|
|
305
|
+
pop(toMerge?: T): T
|
|
306
306
|
}
|
|
307
307
|
|
|
308
308
|
|
|
@@ -352,7 +352,7 @@ export interface IImpressionCountsCacheSync extends IRecorderCacheProducerSync<R
|
|
|
352
352
|
|
|
353
353
|
// Used by impressions count submitter in standalone and producer mode
|
|
354
354
|
isEmpty(): boolean // check if cache is empty. Return true if the cache was just created or cleared.
|
|
355
|
-
pop(): Record<string, number> // pop cache data
|
|
355
|
+
pop(toMerge?: Record<string, number> ): Record<string, number> // pop cache data
|
|
356
356
|
}
|
|
357
357
|
|
|
358
358
|
|
|
@@ -8,28 +8,24 @@ import { IResponse } from '../../services/types';
|
|
|
8
8
|
/**
|
|
9
9
|
* Base function to create submitters, such as ImpressionsSubmitter and EventsSubmitter
|
|
10
10
|
*/
|
|
11
|
-
export function submitterFactory<
|
|
11
|
+
export function submitterFactory<T>(
|
|
12
12
|
log: ILogger,
|
|
13
13
|
postClient: (body: string) => Promise<IResponse>,
|
|
14
|
-
sourceCache: IRecorderCacheProducerSync<
|
|
14
|
+
sourceCache: IRecorderCacheProducerSync<T>,
|
|
15
15
|
postRate: number,
|
|
16
16
|
dataName: string,
|
|
17
|
-
fromCacheToPayload?: (cacheData:
|
|
17
|
+
fromCacheToPayload?: (cacheData: T) => any,
|
|
18
18
|
maxRetries: number = 0,
|
|
19
19
|
debugLogs?: boolean // true for telemetry submitters
|
|
20
20
|
): ISyncTask<[], void> {
|
|
21
21
|
|
|
22
22
|
let retries = 0;
|
|
23
|
-
let data:
|
|
23
|
+
let data: any;
|
|
24
24
|
|
|
25
25
|
function postData(): Promise<any> {
|
|
26
|
-
if (!data)
|
|
27
|
-
|
|
28
|
-
// we clear the cache to track new items, while `data` is used for retries
|
|
29
|
-
data = sourceCache.pop();
|
|
30
|
-
}
|
|
26
|
+
if (sourceCache.isEmpty() && !data) return Promise.resolve();
|
|
27
|
+
data = sourceCache.pop(data);
|
|
31
28
|
|
|
32
|
-
// @ts-ignore
|
|
33
29
|
const dataCountMessage = typeof data.length === 'number' ? `${data.length} ${dataName}` : dataName;
|
|
34
30
|
log[debugLogs ? 'debug' : 'info'](SUBMITTERS_PUSH, [dataCountMessage]);
|
|
35
31
|
|
|
@@ -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
|
+
}
|
|
@@ -40,7 +40,7 @@ export function syncManagerOnlineFactory(
|
|
|
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.
|
|
@@ -102,7 +102,7 @@ export function syncManagerOnlineFactory(
|
|
|
102
102
|
}
|
|
103
103
|
|
|
104
104
|
// start periodic data recording (events, impressions, telemetry).
|
|
105
|
-
|
|
105
|
+
submitterManager.start(!isConsentGranted(settings));
|
|
106
106
|
},
|
|
107
107
|
|
|
108
108
|
/**
|
|
@@ -116,7 +116,7 @@ export function syncManagerOnlineFactory(
|
|
|
116
116
|
if (pollingManager && pollingManager.isRunning()) pollingManager.stop();
|
|
117
117
|
|
|
118
118
|
// stop periodic data recording (events, impressions, telemetry).
|
|
119
|
-
|
|
119
|
+
submitterManager.stop();
|
|
120
120
|
},
|
|
121
121
|
|
|
122
122
|
isRunning() {
|
|
@@ -124,8 +124,7 @@ export function syncManagerOnlineFactory(
|
|
|
124
124
|
},
|
|
125
125
|
|
|
126
126
|
flush() {
|
|
127
|
-
|
|
128
|
-
else return Promise.resolve();
|
|
127
|
+
return submitterManager.execute(!isConsentGranted(settings));
|
|
129
128
|
},
|
|
130
129
|
|
|
131
130
|
// [Only used for client-side]
|
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 {
|
|
@@ -52,12 +52,23 @@ export interface GoogleAnalyticsToSplitOptions {
|
|
|
52
52
|
* If not provided, events are sent using the key and traffic type provided at SDK config
|
|
53
53
|
*/
|
|
54
54
|
identities?: Identity[];
|
|
55
|
+
/**
|
|
56
|
+
* Optional flag to log an error if the `auto-require` script is not detected.
|
|
57
|
+
* The auto-require script automatically requires the `splitTracker` plugin for created trackers,
|
|
58
|
+
* and should be placed right after your Google Analytics, GTM or gtag.js script tag.
|
|
59
|
+
*
|
|
60
|
+
* @see {@link https://help.split.io/hc/en-us/articles/360040838752#google-tag-manager}
|
|
61
|
+
*
|
|
62
|
+
* @property {boolean} autoRequire
|
|
63
|
+
* @default false
|
|
64
|
+
*/
|
|
65
|
+
autoRequire?: boolean;
|
|
55
66
|
}
|
|
56
67
|
/**
|
|
57
68
|
* Enable 'Google Analytics to Split' integration, to track Google Analytics hits as Split events.
|
|
58
69
|
* Used by the browser variant of the isomorphic JS SDK.
|
|
59
70
|
*
|
|
60
|
-
* @see {@link https://help.split.io/hc/en-us/articles/
|
|
71
|
+
* @see {@link https://help.split.io/hc/en-us/articles/360040838752#google-analytics-to-split}
|
|
61
72
|
*/
|
|
62
73
|
export interface IGoogleAnalyticsToSplitConfig extends GoogleAnalyticsToSplitOptions {
|
|
63
74
|
type: 'GOOGLE_ANALYTICS_TO_SPLIT';
|
|
@@ -125,7 +136,7 @@ export interface SplitToGoogleAnalyticsOptions {
|
|
|
125
136
|
* Enable 'Split to Google Analytics' integration, to track Split impressions and events as Google Analytics hits.
|
|
126
137
|
* Used by the browser variant of the isomorphic JS SDK.
|
|
127
138
|
*
|
|
128
|
-
* @see {@link https://help.split.io/hc/en-us/articles/
|
|
139
|
+
* @see {@link https://help.split.io/hc/en-us/articles/360040838752#split-to-google-analytics}
|
|
129
140
|
*/
|
|
130
141
|
export interface ISplitToGoogleAnalyticsConfig extends SplitToGoogleAnalyticsOptions {
|
|
131
142
|
type: 'SPLIT_TO_GOOGLE_ANALYTICS';
|
|
@@ -23,7 +23,7 @@ export declare class EventsCacheInMemory implements IEventsCacheSync {
|
|
|
23
23
|
/**
|
|
24
24
|
* Pop the collected data, used as payload for posting.
|
|
25
25
|
*/
|
|
26
|
-
pop(): SplitIO.EventData[];
|
|
26
|
+
pop(toMerge?: SplitIO.EventData[]): SplitIO.EventData[];
|
|
27
27
|
/**
|
|
28
28
|
* Check if the cache is empty.
|
|
29
29
|
*/
|
|
@@ -12,7 +12,7 @@ export declare class ImpressionCountsCacheInMemory implements IImpressionCountsC
|
|
|
12
12
|
/**
|
|
13
13
|
* Pop the collected data, used as payload for posting.
|
|
14
14
|
*/
|
|
15
|
-
pop(): Record<string, number>;
|
|
15
|
+
pop(toMerge?: Record<string, number>): Record<string, number>;
|
|
16
16
|
/**
|
|
17
17
|
* Clear the data stored on the cache.
|
|
18
18
|
*/
|
|
@@ -22,7 +22,7 @@ export declare class ImpressionsCacheInMemory implements IImpressionsCacheSync {
|
|
|
22
22
|
/**
|
|
23
23
|
* Pop the collected data, used as payload for posting.
|
|
24
24
|
*/
|
|
25
|
-
pop(): ImpressionDTO[];
|
|
25
|
+
pop(toMerge?: ImpressionDTO[]): ImpressionDTO[];
|
|
26
26
|
/**
|
|
27
27
|
* Check if the cache is empty.
|
|
28
28
|
*/
|
|
@@ -266,7 +266,7 @@ export interface IEventsCacheBase {
|
|
|
266
266
|
export interface IRecorderCacheProducerSync<T> {
|
|
267
267
|
isEmpty(): boolean;
|
|
268
268
|
clear(): void;
|
|
269
|
-
pop(): T;
|
|
269
|
+
pop(toMerge?: T): T;
|
|
270
270
|
}
|
|
271
271
|
export interface IImpressionsCacheSync extends IImpressionsCacheBase, IRecorderCacheProducerSync<ImpressionDTO[]> {
|
|
272
272
|
track(data: ImpressionDTO[]): void;
|
|
@@ -295,7 +295,7 @@ export interface IEventsCacheAsync extends IEventsCacheBase, IRecorderCacheProdu
|
|
|
295
295
|
export interface IImpressionCountsCacheSync extends IRecorderCacheProducerSync<Record<string, number>> {
|
|
296
296
|
track(featureName: string, timeFrame: number, amount: number): void;
|
|
297
297
|
isEmpty(): boolean;
|
|
298
|
-
pop(): Record<string, number>;
|
|
298
|
+
pop(toMerge?: Record<string, number>): Record<string, number>;
|
|
299
299
|
}
|
|
300
300
|
/**
|
|
301
301
|
* Telemetry storage interface for standalone and partial consumer modes.
|
|
@@ -5,7 +5,7 @@ import { IResponse } from '../../services/types';
|
|
|
5
5
|
/**
|
|
6
6
|
* Base function to create submitters, such as ImpressionsSubmitter and EventsSubmitter
|
|
7
7
|
*/
|
|
8
|
-
export declare function submitterFactory<
|
|
8
|
+
export declare function submitterFactory<T>(log: ILogger, postClient: (body: string) => Promise<IResponse>, sourceCache: IRecorderCacheProducerSync<T>, postRate: number, dataName: string, fromCacheToPayload?: (cacheData: T) => any, maxRetries?: number, debugLogs?: boolean): ISyncTask<[], void>;
|
|
9
9
|
/**
|
|
10
10
|
* Decorates a provided submitter with a first execution window
|
|
11
11
|
*/
|
|
@@ -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/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;
|
|
@@ -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
|
-
}
|