@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.
Files changed (49) hide show
  1. package/CHANGES.txt +4 -0
  2. package/cjs/consent/sdkUserConsent.js +3 -3
  3. package/cjs/integrations/ga/GaToSplit.js +12 -11
  4. package/cjs/integrations/ga/autoRequire.js +34 -0
  5. package/cjs/listeners/browser.js +7 -6
  6. package/cjs/storages/inMemory/EventsCacheInMemory.js +2 -2
  7. package/cjs/storages/inMemory/ImpressionCountsCacheInMemory.js +10 -1
  8. package/cjs/storages/inMemory/ImpressionsCacheInMemory.js +2 -2
  9. package/cjs/sync/submitters/submitter.js +3 -7
  10. package/cjs/sync/submitters/submitterManager.js +28 -4
  11. package/cjs/sync/syncManagerOnline.js +5 -9
  12. package/esm/consent/sdkUserConsent.js +3 -3
  13. package/esm/integrations/ga/GaToSplit.js +12 -11
  14. package/esm/integrations/ga/autoRequire.js +30 -0
  15. package/esm/listeners/browser.js +7 -6
  16. package/esm/storages/inMemory/EventsCacheInMemory.js +2 -2
  17. package/esm/storages/inMemory/ImpressionCountsCacheInMemory.js +10 -1
  18. package/esm/storages/inMemory/ImpressionsCacheInMemory.js +2 -2
  19. package/esm/sync/submitters/submitter.js +3 -7
  20. package/esm/sync/submitters/submitterManager.js +28 -4
  21. package/esm/sync/syncManagerOnline.js +5 -9
  22. package/package.json +1 -1
  23. package/src/consent/sdkUserConsent.ts +4 -3
  24. package/src/integrations/ga/GaToSplit.ts +14 -8
  25. package/src/integrations/ga/autoRequire.ts +35 -0
  26. package/src/integrations/ga/types.ts +13 -2
  27. package/src/listeners/browser.ts +9 -7
  28. package/src/storages/inMemory/EventsCacheInMemory.ts +2 -2
  29. package/src/storages/inMemory/ImpressionCountsCacheInMemory.ts +8 -1
  30. package/src/storages/inMemory/ImpressionsCacheInMemory.ts +2 -2
  31. package/src/storages/types.ts +2 -2
  32. package/src/sync/submitters/submitter.ts +6 -10
  33. package/src/sync/submitters/submitterManager.ts +30 -4
  34. package/src/sync/submitters/types.ts +7 -0
  35. package/src/sync/syncManagerOnline.ts +5 -6
  36. package/src/sync/types.ts +2 -1
  37. package/types/integrations/ga/autoRequire.d.ts +4 -0
  38. package/types/integrations/ga/types.d.ts +13 -2
  39. package/types/storages/inMemory/EventsCacheInMemory.d.ts +1 -1
  40. package/types/storages/inMemory/ImpressionCountsCacheInMemory.d.ts +1 -1
  41. package/types/storages/inMemory/ImpressionsCacheInMemory.d.ts +1 -1
  42. package/types/storages/types.d.ts +2 -2
  43. package/types/sync/submitters/submitter.d.ts +1 -1
  44. package/types/sync/submitters/submitterManager.d.ts +2 -1
  45. package/types/sync/submitters/types.d.ts +6 -0
  46. package/types/sync/types.d.ts +2 -1
  47. package/cjs/sync/syncTaskComposite.js +0 -26
  48. package/esm/sync/syncTaskComposite.js +0 -22
  49. 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.submitter) === null || _a === void 0 ? void 0 : _a.start();
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.submitter) === null || _b === void 0 ? void 0 : _b.stop();
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 {string} pluginName The plugin name identifier.
14
- * @param {Function} pluginConstructor The plugin constructor function.
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
- var args = [];
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;
@@ -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
- if (this.storage.telemetry) {
68
- var telemetryUrl = this.settings.urls.telemetry;
69
- var telemetryCacheAdapter = (0, telemetrySubmitter_1.telemetryCacheStatsAdapter)(this.storage.telemetry, this.storage.splits, this.storage.segments);
70
- this._flushData(telemetryUrl + '/v1/metrics/usage/beacon', telemetryCacheAdapter, this.serviceApi.postMetricsUsage);
71
- }
67
+ }
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
- if (sourceCache.isEmpty())
17
- return Promise.resolve();
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
- if (telemetrySubmitter)
19
- submitters.push(telemetrySubmitter);
20
- return (0, syncTaskComposite_1.syncTaskComposite)(submitters);
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 submitter = (0, submitterManager_1.submitterManagerFactory)(params);
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
- submitter: submitter,
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
- if ((0, consent_1.isConsentGranted)(settings))
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
- submitter.stop();
98
+ submitterManager.stop();
100
99
  },
101
100
  isRunning: function () {
102
101
  return running;
103
102
  },
104
103
  flush: function () {
105
- if ((0, consent_1.isConsentGranted)(settings))
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.submitter) === null || _a === void 0 ? void 0 : _a.start();
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.submitter) === null || _b === void 0 ? void 0 : _b.stop();
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 {string} pluginName The plugin name identifier.
11
- * @param {Function} pluginConstructor The plugin constructor function.
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
- var args = [];
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
+ }
@@ -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
- if (this.storage.telemetry) {
65
- var telemetryUrl = this.settings.urls.telemetry;
66
- var telemetryCacheAdapter = telemetryCacheStatsAdapter(this.storage.telemetry, this.storage.splits, this.storage.segments);
67
- this._flushData(telemetryUrl + '/v1/metrics/usage/beacon', telemetryCacheAdapter, this.serviceApi.postMetricsUsage);
68
- }
64
+ }
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
- if (sourceCache.isEmpty())
14
- return Promise.resolve();
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
- if (telemetrySubmitter)
16
- submitters.push(telemetrySubmitter);
17
- return syncTaskComposite(submitters);
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 submitter = submitterManagerFactory(params);
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
- submitter: submitter,
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
- if (isConsentGranted(settings))
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
- submitter.stop();
95
+ submitterManager.stop();
97
96
  },
98
97
  isRunning: function () {
99
98
  return running;
100
99
  },
101
100
  flush: function () {
102
- if (isConsentGranted(settings))
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@splitsoftware/splitio-commons",
3
- "version": "1.4.1-rc.1",
3
+ "version": "1.4.2-rc.0",
4
4
  "description": "Split Javascript SDK common components",
5
5
  "main": "cjs/index.js",
6
6
  "module": "esm/index.js",
@@ -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?.submitter?.start();
38
- } else { // pauses submitters and drops tracked data if transitioning to DECLINED
39
- syncManager?.submitter?.stop();
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 {string} pluginName The plugin name identifier.
23
- * @param {Function} pluginConstructor The plugin constructor function.
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 (...args: any[]) { // @ts-expect-error
30
- (window[gaAlias].q = window[gaAlias].q || []).push(args);
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/360020448791-JavaScript-SDK#google-analytics-to-split}
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/360020448791-JavaScript-SDK#split-to-google-analytics}
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'
@@ -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
- if (this.storage.telemetry) {
82
- const telemetryUrl = this.settings.urls.telemetry;
83
- const telemetryCacheAdapter = telemetryCacheStatsAdapter(this.storage.telemetry, this.storage.splits, this.storage.segments);
84
- this._flushData(telemetryUrl + '/v1/metrics/usage/beacon', telemetryCacheAdapter, this.serviceApi.postMetricsUsage);
85
- }
81
+ }
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<TState>(url: string, cache: IRecorderCacheProducerSync<TState>, postService: (body: string) => Promise<IResponse>, fromCacheToPayload?: (cacheData: TState) => any, extraMetadata?: {}) {
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
  /**
@@ -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<TState>(
11
+ export function submitterFactory<T>(
12
12
  log: ILogger,
13
13
  postClient: (body: string) => Promise<IResponse>,
14
- sourceCache: IRecorderCacheProducerSync<TState>,
14
+ sourceCache: IRecorderCacheProducerSync<T>,
15
15
  postRate: number,
16
16
  dataName: string,
17
- fromCacheToPayload?: (cacheData: TState) => any,
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: TState | undefined;
23
+ let data: any;
24
24
 
25
25
  function postData(): Promise<any> {
26
- if (!data) {
27
- if (sourceCache.isEmpty()) return Promise.resolve();
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 syncTaskComposite(submitters);
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 submitter = submitterManagerFactory(params);
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
- submitter,
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
- if (isConsentGranted(settings)) submitter.start();
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
- submitter.stop();
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
- if (isConsentGranted(settings)) return submitter.execute();
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
- submitter?: ISyncTask
43
+ submitterManager?: ISubmitterManager
43
44
  }
44
45
 
45
46
  export interface ISyncManagerCS extends ISyncManager {
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Auto-require script to use with GaToSplit integration
3
+ */
4
+ export declare function autoRequire(): void;
@@ -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/360020448791-JavaScript-SDK#google-analytics-to-split}
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/360020448791-JavaScript-SDK#split-to-google-analytics}
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<TState>(log: ILogger, postClient: (body: string) => Promise<IResponse>, sourceCache: IRecorderCacheProducerSync<TState>, postRate: number, dataName: string, fromCacheToPayload?: (cacheData: TState) => any, maxRetries?: number, debugLogs?: boolean): ISyncTask<[], void>;
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
- export declare function submitterManagerFactory(params: ISdkFactoryContextSync): import("../types").ISyncTask<[], any>;
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
+ }
@@ -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
- submitter?: ISyncTask;
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
- }