@splitsoftware/splitio-commons 1.5.0 → 1.5.1-rc.2

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 (129) hide show
  1. package/CHANGES.txt +5 -0
  2. package/cjs/listeners/browser.js +35 -15
  3. package/cjs/services/splitApi.js +4 -4
  4. package/cjs/sync/polling/fetchers/segmentChangesFetcher.js +5 -5
  5. package/cjs/sync/polling/fetchers/splitChangesFetcher.js +2 -2
  6. package/cjs/sync/polling/updaters/segmentChangesUpdater.js +34 -34
  7. package/cjs/sync/polling/updaters/splitChangesUpdater.js +4 -3
  8. package/cjs/sync/streaming/UpdateWorkers/MySegmentsUpdateWorker.js +46 -46
  9. package/cjs/sync/streaming/UpdateWorkers/SegmentsUpdateWorker.js +82 -64
  10. package/cjs/sync/streaming/UpdateWorkers/SplitsUpdateWorker.js +74 -58
  11. package/cjs/sync/streaming/UpdateWorkers/constants.js +6 -0
  12. package/cjs/sync/streaming/pushManager.js +6 -7
  13. package/cjs/sync/syncTask.js +13 -16
  14. package/cjs/utils/Backoff.js +3 -2
  15. package/esm/listeners/browser.js +35 -15
  16. package/esm/services/splitApi.js +4 -4
  17. package/esm/sync/polling/fetchers/segmentChangesFetcher.js +5 -5
  18. package/esm/sync/polling/fetchers/splitChangesFetcher.js +2 -2
  19. package/esm/sync/polling/updaters/segmentChangesUpdater.js +34 -34
  20. package/esm/sync/polling/updaters/splitChangesUpdater.js +4 -3
  21. package/esm/sync/streaming/UpdateWorkers/MySegmentsUpdateWorker.js +46 -47
  22. package/esm/sync/streaming/UpdateWorkers/SegmentsUpdateWorker.js +82 -65
  23. package/esm/sync/streaming/UpdateWorkers/SplitsUpdateWorker.js +74 -59
  24. package/esm/sync/streaming/UpdateWorkers/constants.js +3 -0
  25. package/esm/sync/streaming/pushManager.js +6 -7
  26. package/esm/sync/syncTask.js +13 -16
  27. package/esm/utils/Backoff.js +3 -2
  28. package/package.json +1 -5
  29. package/src/listeners/browser.ts +34 -14
  30. package/src/logger/.DS_Store +0 -0
  31. package/src/services/splitApi.ts +4 -4
  32. package/src/services/types.ts +2 -2
  33. package/src/sync/polling/fetchers/segmentChangesFetcher.ts +5 -4
  34. package/src/sync/polling/fetchers/splitChangesFetcher.ts +2 -1
  35. package/src/sync/polling/fetchers/types.ts +2 -0
  36. package/src/sync/polling/pollingManagerCS.ts +5 -5
  37. package/src/sync/polling/syncTasks/mySegmentsSyncTask.ts +2 -2
  38. package/src/sync/polling/types.ts +14 -6
  39. package/src/sync/polling/updaters/mySegmentsUpdater.ts +4 -4
  40. package/src/sync/polling/updaters/segmentChangesUpdater.ts +34 -32
  41. package/src/sync/polling/updaters/splitChangesUpdater.ts +5 -4
  42. package/src/sync/streaming/SSEHandler/types.ts +0 -7
  43. package/src/sync/streaming/UpdateWorkers/MySegmentsUpdateWorker.ts +45 -54
  44. package/src/sync/streaming/UpdateWorkers/SegmentsUpdateWorker.ts +78 -63
  45. package/src/sync/streaming/UpdateWorkers/SplitsUpdateWorker.ts +73 -61
  46. package/src/sync/streaming/UpdateWorkers/constants.ts +3 -0
  47. package/src/sync/streaming/UpdateWorkers/types.ts +2 -4
  48. package/src/sync/streaming/pushManager.ts +12 -12
  49. package/src/sync/streaming/types.ts +2 -2
  50. package/src/sync/syncTask.ts +16 -18
  51. package/src/utils/Backoff.ts +7 -2
  52. package/types/integrations/ga/GaToSplitPlugin.d.ts +3 -0
  53. package/types/integrations/ga/SplitToGaPlugin.d.ts +4 -0
  54. package/types/integrations/ga/autoRequire.d.ts +4 -0
  55. package/types/listeners/browser.d.ts +6 -6
  56. package/types/logger/browser/{DebugLogger.d.ts → debugLogger.d.ts} +0 -0
  57. package/types/logger/browser/{ErrorLogger.d.ts → errorLogger.d.ts} +0 -0
  58. package/types/logger/browser/{InfoLogger.d.ts → infoLogger.d.ts} +0 -0
  59. package/types/logger/browser/{WarnLogger.d.ts → warnLogger.d.ts} +0 -0
  60. package/types/logger/codes.d.ts +2 -0
  61. package/types/logger/codesConstants.d.ts +117 -0
  62. package/types/logger/codesConstantsBrowser.d.ts +2 -0
  63. package/types/logger/codesConstantsNode.d.ts +14 -0
  64. package/types/logger/codesDebug.d.ts +1 -0
  65. package/types/logger/codesDebugBrowser.d.ts +1 -0
  66. package/types/logger/codesDebugNode.d.ts +1 -0
  67. package/types/logger/codesError.d.ts +1 -0
  68. package/types/logger/codesErrorNode.d.ts +1 -0
  69. package/types/logger/codesInfo.d.ts +1 -0
  70. package/types/logger/codesWarn.d.ts +1 -0
  71. package/types/logger/codesWarnNode.d.ts +1 -0
  72. package/types/logger/debugLogger.d.ts +2 -0
  73. package/types/logger/errorLogger.d.ts +2 -0
  74. package/types/logger/infoLogger.d.ts +2 -0
  75. package/types/logger/messages/debugBrowser.d.ts +1 -0
  76. package/types/logger/messages/debugNode.d.ts +1 -0
  77. package/types/logger/messages/errorNode.d.ts +1 -0
  78. package/types/logger/messages/warnNode.d.ts +1 -0
  79. package/types/logger/noopLogger.d.ts +2 -0
  80. package/types/logger/warnLogger.d.ts +2 -0
  81. package/types/sdkFactory/userConsentProps.d.ts +6 -0
  82. package/types/sdkManager/sdkManagerMethod.d.ts +6 -0
  83. package/types/services/types.d.ts +2 -2
  84. package/types/storages/getRegisteredSegments.d.ts +10 -0
  85. package/types/storages/inMemory/index.d.ts +10 -0
  86. package/types/storages/parseSegments.d.ts +6 -0
  87. package/types/sync/polling/fetchers/types.d.ts +2 -2
  88. package/types/sync/polling/syncTasks/mySegmentsSyncTask.d.ts +2 -2
  89. package/types/sync/polling/syncTasks/splitsSyncTask.copy.d.ts +35 -0
  90. package/types/sync/polling/syncTasks/splitsSyncTask.morelikeoriginal.d.ts +35 -0
  91. package/types/sync/polling/types.d.ts +11 -6
  92. package/types/sync/polling/updaters/segmentChangesUpdater.d.ts +1 -1
  93. package/types/sync/polling/updaters/splitChangesUpdater.d.ts +1 -1
  94. package/types/sync/streaming/AuthClient/indexV1.d.ts +12 -0
  95. package/types/sync/streaming/AuthClient/indexV2.d.ts +8 -0
  96. package/types/sync/streaming/SSEHandler/types.d.ts +0 -4
  97. package/types/sync/streaming/UpdateWorkers/MySegmentsUpdateWorker.d.ts +3 -24
  98. package/types/sync/streaming/UpdateWorkers/SegmentsUpdateWorker.d.ts +3 -23
  99. package/types/sync/streaming/UpdateWorkers/SplitsUpdateWorker.d.ts +6 -33
  100. package/types/sync/streaming/UpdateWorkers/constants.d.ts +3 -0
  101. package/types/sync/streaming/UpdateWorkers/types.d.ts +1 -2
  102. package/types/sync/streaming/pushManagerCS.d.ts +1 -0
  103. package/types/sync/streaming/pushManagerNoUsers.d.ts +13 -0
  104. package/types/sync/streaming/pushManagerSS.d.ts +1 -0
  105. package/types/sync/streaming/types.d.ts +2 -2
  106. package/types/sync/submitters/telemetrySyncTask.d.ts +0 -0
  107. package/types/sync/syncManagerFromFile.d.ts +2 -0
  108. package/types/sync/syncManagerFromObject.d.ts +2 -0
  109. package/types/sync/syncManagerOffline.d.ts +9 -0
  110. package/types/sync/syncTask.d.ts +2 -3
  111. package/types/trackers/telemetryRecorder.d.ts +0 -0
  112. package/types/utils/Backoff.d.ts +2 -0
  113. package/types/utils/EventEmitter.d.ts +4 -0
  114. package/types/utils/consent.d.ts +2 -0
  115. package/types/utils/lang/errors.d.ts +10 -0
  116. package/types/utils/murmur3/commons.d.ts +12 -0
  117. package/types/utils/settingsValidation/buildMetadata.d.ts +3 -0
  118. package/types/utils/settingsValidation/localhost/index.d.ts +9 -0
  119. package/types/utils/settingsValidation/logger.d.ts +11 -0
  120. package/types/utils/settingsValidation/runtime/browser.d.ts +2 -0
  121. package/types/utils/settingsValidation/runtime/node.d.ts +2 -0
  122. package/types/utils/settingsValidation/userConsent.d.ts +5 -0
  123. package/types/utils/timeTracker/index.d.ts +1 -70
  124. package/cjs/sync/offline/LocalhostFromFile.js +0 -13
  125. package/cjs/sync/offline/splitsParser/splitsParserFromFile.js +0 -151
  126. package/esm/sync/offline/LocalhostFromFile.js +0 -9
  127. package/esm/sync/offline/splitsParser/splitsParserFromFile.js +0 -146
  128. package/src/sync/offline/LocalhostFromFile.ts +0 -12
  129. package/src/sync/offline/splitsParser/splitsParserFromFile.ts +0 -182
@@ -3,80 +3,96 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.SplitsUpdateWorker = void 0;
4
4
  var constants_1 = require("../../../readiness/constants");
5
5
  var Backoff_1 = require("../../../utils/Backoff");
6
+ var constants_2 = require("./constants");
6
7
  /**
7
- * SplitsUpdateWorker class
8
+ * SplitsUpdateWorker factory
8
9
  */
9
- var SplitsUpdateWorker = /** @class */ (function () {
10
- /**
11
- * @param {Object} splitsCache splits data cache
12
- * @param {Object} splitsSyncTask task for syncing splits data
13
- * @param {Object} splitsEventEmitter emitter for splits data events
14
- */
15
- function SplitsUpdateWorker(splitsCache, splitsSyncTask, splitsEventEmitter, segmentsSyncTask) {
16
- this.splitsCache = splitsCache;
17
- this.splitsSyncTask = splitsSyncTask;
18
- this.splitsEventEmitter = splitsEventEmitter;
19
- this.segmentsSyncTask = segmentsSyncTask;
20
- this.maxChangeNumber = 0;
21
- this.handleNewEvent = false;
22
- this.put = this.put.bind(this);
23
- this.killSplit = this.killSplit.bind(this);
24
- this.__handleSplitUpdateCall = this.__handleSplitUpdateCall.bind(this);
25
- this.backoff = new Backoff_1.Backoff(this.__handleSplitUpdateCall);
26
- }
27
- // Private method
28
- // Preconditions: this.splitsSyncTask.isSynchronizingSplits === false
29
- SplitsUpdateWorker.prototype.__handleSplitUpdateCall = function () {
30
- var _this = this;
31
- if (this.maxChangeNumber > this.splitsCache.getChangeNumber()) {
32
- this.handleNewEvent = false;
10
+ function SplitsUpdateWorker(log, splitsCache, splitsSyncTask, splitsEventEmitter, segmentsSyncTask) {
11
+ var maxChangeNumber = 0;
12
+ var handleNewEvent = false;
13
+ var isHandlingEvent;
14
+ var cdnBypass;
15
+ var backoff = new Backoff_1.Backoff(__handleSplitUpdateCall, constants_2.FETCH_BACKOFF_BASE, constants_2.FETCH_BACKOFF_MAX_WAIT);
16
+ function __handleSplitUpdateCall() {
17
+ isHandlingEvent = true;
18
+ if (maxChangeNumber > splitsCache.getChangeNumber()) {
19
+ handleNewEvent = false;
33
20
  // fetch splits revalidating data if cached
34
- this.splitsSyncTask.execute(true).then(function () {
35
- if (_this.handleNewEvent) {
36
- _this.__handleSplitUpdateCall();
21
+ splitsSyncTask.execute(true, cdnBypass ? maxChangeNumber : undefined).then(function () {
22
+ if (!isHandlingEvent)
23
+ return; // halt if `stop` has been called
24
+ if (handleNewEvent) {
25
+ __handleSplitUpdateCall();
37
26
  }
38
27
  else {
39
28
  // fetch new registered segments for server-side API. Not retrying on error
40
- if (_this.segmentsSyncTask)
41
- _this.segmentsSyncTask.execute(undefined, false, true);
42
- _this.backoff.scheduleCall();
29
+ if (segmentsSyncTask)
30
+ segmentsSyncTask.execute(true);
31
+ var attempts = backoff.attempts + 1;
32
+ if (maxChangeNumber <= splitsCache.getChangeNumber()) {
33
+ log.debug("Refresh completed" + (cdnBypass ? ' bypassing the CDN' : '') + " in " + attempts + " attempts.");
34
+ isHandlingEvent = false;
35
+ return;
36
+ }
37
+ if (attempts < constants_2.FETCH_BACKOFF_MAX_RETRIES) {
38
+ backoff.scheduleCall();
39
+ return;
40
+ }
41
+ if (cdnBypass) {
42
+ log.debug("No changes fetched after " + attempts + " attempts with CDN bypassed.");
43
+ isHandlingEvent = false;
44
+ }
45
+ else {
46
+ backoff.reset();
47
+ cdnBypass = true;
48
+ __handleSplitUpdateCall();
49
+ }
43
50
  }
44
51
  });
45
52
  }
46
- };
53
+ else {
54
+ isHandlingEvent = false;
55
+ }
56
+ }
47
57
  /**
48
58
  * Invoked by NotificationProcessor on SPLIT_UPDATE event
49
59
  *
50
60
  * @param {number} changeNumber change number of the SPLIT_UPDATE notification
51
61
  */
52
- SplitsUpdateWorker.prototype.put = function (_a) {
62
+ function put(_a) {
53
63
  var changeNumber = _a.changeNumber;
54
- var currentChangeNumber = this.splitsCache.getChangeNumber();
55
- if (changeNumber <= currentChangeNumber || changeNumber <= this.maxChangeNumber)
56
- return;
57
- this.maxChangeNumber = changeNumber;
58
- this.handleNewEvent = true;
59
- this.backoff.reset();
60
- if (this.splitsSyncTask.isExecuting())
64
+ var currentChangeNumber = splitsCache.getChangeNumber();
65
+ if (changeNumber <= currentChangeNumber || changeNumber <= maxChangeNumber)
61
66
  return;
62
- this.__handleSplitUpdateCall();
63
- };
64
- /**
65
- * Invoked by NotificationProcessor on SPLIT_KILL event
66
- *
67
- * @param {number} changeNumber change number of the SPLIT_UPDATE notification
68
- * @param {string} splitName name of split to kill
69
- * @param {string} defaultTreatment default treatment value
70
- */
71
- SplitsUpdateWorker.prototype.killSplit = function (_a) {
72
- var changeNumber = _a.changeNumber, splitName = _a.splitName, defaultTreatment = _a.defaultTreatment;
73
- if (this.splitsCache.killLocally(splitName, defaultTreatment, changeNumber)) {
74
- // trigger an SDK_UPDATE if Split was killed locally
75
- this.splitsEventEmitter.emit(constants_1.SDK_SPLITS_ARRIVED, true);
67
+ maxChangeNumber = changeNumber;
68
+ handleNewEvent = true;
69
+ cdnBypass = false;
70
+ if (backoff.timeoutID || !isHandlingEvent)
71
+ __handleSplitUpdateCall();
72
+ backoff.reset();
73
+ }
74
+ return {
75
+ put: put,
76
+ /**
77
+ * Invoked by NotificationProcessor on SPLIT_KILL event
78
+ *
79
+ * @param {number} changeNumber change number of the SPLIT_UPDATE notification
80
+ * @param {string} splitName name of split to kill
81
+ * @param {string} defaultTreatment default treatment value
82
+ */
83
+ killSplit: function (_a) {
84
+ var changeNumber = _a.changeNumber, splitName = _a.splitName, defaultTreatment = _a.defaultTreatment;
85
+ if (splitsCache.killLocally(splitName, defaultTreatment, changeNumber)) {
86
+ // trigger an SDK_UPDATE if Split was killed locally
87
+ splitsEventEmitter.emit(constants_1.SDK_SPLITS_ARRIVED, true);
88
+ }
89
+ // queues the SplitChanges fetch (only if changeNumber is newer)
90
+ put({ changeNumber: changeNumber });
91
+ },
92
+ stop: function () {
93
+ isHandlingEvent = false;
94
+ backoff.reset();
76
95
  }
77
- // queues the SplitChanges fetch (only if changeNumber is newer)
78
- this.put({ changeNumber: changeNumber });
79
96
  };
80
- return SplitsUpdateWorker;
81
- }());
97
+ }
82
98
  exports.SplitsUpdateWorker = SplitsUpdateWorker;
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FETCH_BACKOFF_MAX_RETRIES = exports.FETCH_BACKOFF_MAX_WAIT = exports.FETCH_BACKOFF_BASE = void 0;
4
+ exports.FETCH_BACKOFF_BASE = 10000; // backoff base starting at 10 seconds
5
+ exports.FETCH_BACKOFF_MAX_WAIT = 60000; // don't wait for more than 1 minute
6
+ exports.FETCH_BACKOFF_MAX_RETRIES = 10; // max retries
@@ -45,9 +45,9 @@ function pushManagerFactory(params, pollingManager) {
45
45
  sseClient.setEventHandler(sseHandler);
46
46
  // init workers
47
47
  // MySegmentsUpdateWorker (client-side) are initiated in `add` method
48
- var segmentsUpdateWorker = userKey ? undefined : new SegmentsUpdateWorker_1.SegmentsUpdateWorker(pollingManager.segmentsSyncTask, storage.segments);
48
+ var segmentsUpdateWorker = userKey ? undefined : (0, SegmentsUpdateWorker_1.SegmentsUpdateWorker)(log, pollingManager.segmentsSyncTask, storage.segments);
49
49
  // For server-side we pass the segmentsSyncTask, used by SplitsUpdateWorker to fetch new segments
50
- var splitsUpdateWorker = new SplitsUpdateWorker_1.SplitsUpdateWorker(storage.splits, pollingManager.splitsSyncTask, readiness.splits, userKey ? undefined : pollingManager.segmentsSyncTask);
50
+ var splitsUpdateWorker = (0, SplitsUpdateWorker_1.SplitsUpdateWorker)(log, storage.splits, pollingManager.splitsSyncTask, readiness.splits, userKey ? undefined : pollingManager.segmentsSyncTask);
51
51
  // [Only for client-side] map of hashes to user keys, to dispatch MY_SEGMENTS_UPDATE events to the corresponding MySegmentsUpdateWorker
52
52
  var userKeyHashes = {};
53
53
  // [Only for client-side] map of user keys to their corresponding hash64 and MySegmentsUpdateWorkers.
@@ -140,21 +140,20 @@ function pushManagerFactory(params, pollingManager) {
140
140
  }
141
141
  // cancel scheduled fetch retries of Splits, Segments, and MySegments Update Workers
142
142
  function stopWorkers() {
143
- splitsUpdateWorker.backoff.reset();
143
+ splitsUpdateWorker.stop();
144
144
  if (userKey)
145
145
  (0, lang_1.forOwn)(clients, function (_a) {
146
146
  var worker = _a.worker;
147
- return worker.backoff.reset();
147
+ return worker.stop();
148
148
  });
149
149
  else
150
- segmentsUpdateWorker.backoff.reset();
150
+ segmentsUpdateWorker.stop();
151
151
  }
152
152
  pushEmitter.on(constants_1.PUSH_SUBSYSTEM_DOWN, stopWorkers);
153
153
  // Only required when streaming connects after a PUSH_RETRYABLE_ERROR.
154
154
  // Otherwise it is unnecessary (e.g, STREAMING_RESUMED).
155
155
  pushEmitter.on(constants_1.PUSH_SUBSYSTEM_UP, function () {
156
156
  connectPushRetryBackoff.reset();
157
- stopWorkers();
158
157
  });
159
158
  /** Fallback to polling without retry due to: STREAMING_DISABLED control event, or 'pushEnabled: false', or non-recoverable SSE and Authentication errors */
160
159
  pushEmitter.on(constants_1.PUSH_NONRETRYABLE_ERROR, function handleNonRetryableError() {
@@ -290,7 +289,7 @@ function pushManagerFactory(params, pollingManager) {
290
289
  var hash = (0, AuthClient_1.hashUserKey)(userKey);
291
290
  if (!userKeyHashes[hash]) {
292
291
  userKeyHashes[hash] = userKey;
293
- clients[userKey] = { hash64: (0, murmur3_64_1.hash64)(userKey), worker: new MySegmentsUpdateWorker_1.MySegmentsUpdateWorker(mySegmentsSyncTask) };
292
+ clients[userKey] = { hash64: (0, murmur3_64_1.hash64)(userKey), worker: (0, MySegmentsUpdateWorker_1.MySegmentsUpdateWorker)(mySegmentsSyncTask) };
294
293
  connectForNewClient = true; // we must reconnect on start, to listen the channel for the new user key
295
294
  // Reconnects in case of a new client.
296
295
  // Run in next event-loop cycle to save authentication calls
@@ -3,9 +3,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.syncTaskFactory = void 0;
4
4
  var constants_1 = require("../logger/constants");
5
5
  /**
6
- * Creates a syncTask that handles the periodic execution of a given task ("start" and "stop" methods).
7
- * The task can be also executed by calling the "execute" method. Multiple execute calls are chained to run secuentially and avoid race conditions.
8
- * For example, submitters executed on SDK destroy or full queue, while periodic execution is pending.
6
+ * Creates an object that handles the periodic execution of a given task via "start" and "stop" methods.
7
+ * The task can be also executed by calling the "execute" method. Multiple calls run sequentially to avoid race conditions (e.g., submitters executed on SDK destroy or full queue, while periodic execution is pending).
9
8
  *
10
9
  * @param log Logger instance.
11
10
  * @param task Task to execute that returns a promise that NEVER REJECTS. Otherwise, periodic execution can result in Unhandled Promise Rejections.
@@ -15,8 +14,10 @@ var constants_1 = require("../logger/constants");
15
14
  */
16
15
  function syncTaskFactory(log, task, period, taskName) {
17
16
  if (taskName === void 0) { taskName = 'task'; }
18
- // Task promise while it is pending. Undefined once the promise is resolved
19
- var pendingTask;
17
+ // Flag that indicates if the task is executing
18
+ var executing = 0;
19
+ // Promise chain to resolve tasks sequentially
20
+ var promiseChain;
20
21
  // flag that indicates if the task periodic execution has been started/stopped.
21
22
  var running = false;
22
23
  // Auxiliar counter used to avoid race condition when calling `start` & `stop` intermittently
@@ -30,19 +31,15 @@ function syncTaskFactory(log, task, period, taskName) {
30
31
  for (var _i = 0; _i < arguments.length; _i++) {
31
32
  args[_i] = arguments[_i];
32
33
  }
33
- // If task is executing, chain the new execution
34
- if (pendingTask) {
35
- return pendingTask.then(function () {
36
- return execute.apply(void 0, args);
37
- });
38
- }
39
- // Execute task
34
+ executing++;
40
35
  log.debug(constants_1.SYNC_TASK_EXECUTE, [taskName]);
41
- pendingTask = task.apply(void 0, args).then(function (result) {
42
- pendingTask = undefined;
36
+ // Update `promiseChain` with last promise, to run tasks serially
37
+ promiseChain = (promiseChain ? promiseChain.then(function () { return task.apply(void 0, args); }) : task.apply(void 0, args))
38
+ .then(function (result) {
39
+ executing--;
43
40
  return result;
44
41
  });
45
- return pendingTask;
42
+ return promiseChain;
46
43
  }
47
44
  function periodicExecute(currentRunningId) {
48
45
  return execute.apply(void 0, runningArgs).then(function (result) {
@@ -56,7 +53,7 @@ function syncTaskFactory(log, task, period, taskName) {
56
53
  return {
57
54
  execute: execute,
58
55
  isExecuting: function () {
59
- return pendingTask !== undefined;
56
+ return executing > 0;
60
57
  },
61
58
  start: function () {
62
59
  var args = [];
@@ -10,8 +10,8 @@ var Backoff = /** @class */ (function () {
10
10
  * @param {number} maxMillis
11
11
  */
12
12
  function Backoff(cb, baseMillis, maxMillis) {
13
- this.baseMillis = baseMillis || Backoff.DEFAULT_BASE_MILLIS;
14
- this.maxMillis = maxMillis || Backoff.DEFAULT_MAX_MILLIS;
13
+ this.baseMillis = Backoff.__TEST__BASE_MILLIS || baseMillis || Backoff.DEFAULT_BASE_MILLIS;
14
+ this.maxMillis = Backoff.__TEST__MAX_MILLIS || maxMillis || Backoff.DEFAULT_MAX_MILLIS;
15
15
  this.attempts = 0;
16
16
  this.cb = cb;
17
17
  }
@@ -25,6 +25,7 @@ var Backoff = /** @class */ (function () {
25
25
  if (this.timeoutID)
26
26
  clearTimeout(this.timeoutID);
27
27
  this.timeoutID = setTimeout(function () {
28
+ _this.timeoutID = undefined;
28
29
  _this.cb();
29
30
  }, delayInMillis);
30
31
  this.attempts++;
@@ -5,11 +5,12 @@ import { objectAssign } from '../utils/lang/objectAssign';
5
5
  import { CLEANUP_REGISTERING, CLEANUP_DEREGISTERING } from '../logger/constants';
6
6
  import { isConsentGranted } from '../consent';
7
7
  import { telemetryCacheStatsAdapter } from '../sync/submitters/telemetrySubmitter';
8
- // 'unload' event is used instead of 'beforeunload', since 'unload' is not a cancelable event, so no other listeners can stop the event from occurring.
9
- var UNLOAD_DOM_EVENT = 'unload';
8
+ var VISIBILITYCHANGE_EVENT = 'visibilitychange';
9
+ var PAGEHIDE_EVENT = 'pagehide';
10
+ var UNLOAD_EVENT = 'unload';
10
11
  var EVENT_NAME = 'for unload page event.';
11
12
  /**
12
- * We'll listen for 'unload' event over the window object, since it's the standard way to listen page reload and close.
13
+ * We'll listen for events over the window object.
13
14
  */
14
15
  var BrowserSignalListener = /** @class */ (function () {
15
16
  function BrowserSignalListener(syncManager, settings, storage, serviceApi) {
@@ -18,33 +19,50 @@ var BrowserSignalListener = /** @class */ (function () {
18
19
  this.storage = storage;
19
20
  this.serviceApi = serviceApi;
20
21
  this.flushData = this.flushData.bind(this);
22
+ this.flushDataIfHidden = this.flushDataIfHidden.bind(this);
23
+ this.stopSync = this.stopSync.bind(this);
21
24
  this.fromImpressionsCollector = fromImpressionsCollector.bind(undefined, settings.core.labelsEnabled);
22
25
  }
23
26
  /**
24
27
  * start method.
25
- * Called when SplitFactory is initialized.
26
- * We add a handler on unload events. The handler flushes remaining impressions and events to the backend.
28
+ * Called when SplitFactory is initialized, it adds event listeners to close streaming and flush impressions and events.
27
29
  */
28
30
  BrowserSignalListener.prototype.start = function () {
31
+ this.settings.log.debug(CLEANUP_REGISTERING, [EVENT_NAME]);
32
+ if (typeof document !== 'undefined' && document.addEventListener) {
33
+ // Flush data whenever the page is hidden or unloaded.
34
+ document.addEventListener(VISIBILITYCHANGE_EVENT, this.flushDataIfHidden);
35
+ }
29
36
  if (typeof window !== 'undefined' && window.addEventListener) {
30
- this.settings.log.debug(CLEANUP_REGISTERING, [EVENT_NAME]);
31
- window.addEventListener(UNLOAD_DOM_EVENT, this.flushData);
37
+ // Some browsers like Safari does not fire the `visibilitychange` event when the page is being unloaded. So we also flush data in the `pagehide` event.
38
+ // If both events are triggered, the last one will find the storage empty, so no duplicated data will be submitted.
39
+ window.addEventListener(PAGEHIDE_EVENT, this.flushData);
40
+ // Stop streaming on 'unload' event. Used instead of 'beforeunload', because 'unload' is not a cancelable event, so no other listeners can stop the event from occurring.
41
+ window.addEventListener(UNLOAD_EVENT, this.stopSync);
32
42
  }
33
43
  };
34
44
  /**
35
45
  * stop method.
36
- * Called when client is destroyed.
37
- * We need to remove the handler for unload events, since it can break if called when Split context was destroyed.
46
+ * Called when client is destroyed, it removes event listeners.
38
47
  */
39
48
  BrowserSignalListener.prototype.stop = function () {
49
+ this.settings.log.debug(CLEANUP_DEREGISTERING, [EVENT_NAME]);
50
+ if (typeof document !== 'undefined' && document.removeEventListener) {
51
+ document.removeEventListener(VISIBILITYCHANGE_EVENT, this.flushDataIfHidden);
52
+ }
40
53
  if (typeof window !== 'undefined' && window.removeEventListener) {
41
- this.settings.log.debug(CLEANUP_DEREGISTERING, [EVENT_NAME]);
42
- window.removeEventListener(UNLOAD_DOM_EVENT, this.flushData);
54
+ window.removeEventListener(PAGEHIDE_EVENT, this.flushData);
55
+ window.removeEventListener(UNLOAD_EVENT, this.stopSync);
43
56
  }
44
57
  };
58
+ BrowserSignalListener.prototype.stopSync = function () {
59
+ // Close streaming connection
60
+ if (this.syncManager && this.syncManager.pushManager)
61
+ this.syncManager.pushManager.stop();
62
+ };
45
63
  /**
46
64
  * flushData method.
47
- * Called when unload event is triggered. It flushed remaining impressions and events to the backend,
65
+ * Called when pagehide event is triggered. It flushed remaining impressions and events to the backend,
48
66
  * using beacon API if possible, or falling back to regular post transport.
49
67
  */
50
68
  BrowserSignalListener.prototype.flushData = function () {
@@ -68,9 +86,11 @@ var BrowserSignalListener = /** @class */ (function () {
68
86
  var telemetryCacheAdapter = telemetryCacheStatsAdapter(this.storage.telemetry, this.storage.splits, this.storage.segments);
69
87
  this._flushData(telemetryUrl + '/v1/metrics/usage/beacon', telemetryCacheAdapter, this.serviceApi.postMetricsUsage);
70
88
  }
71
- // Close streaming connection
72
- if (this.syncManager.pushManager)
73
- this.syncManager.pushManager.stop();
89
+ };
90
+ BrowserSignalListener.prototype.flushDataIfHidden = function () {
91
+ // Precondition: document defined
92
+ if (document.visibilityState === 'hidden')
93
+ this.flushData(); // On a 'visibilitychange' event, flush data if state is hidden
74
94
  };
75
95
  BrowserSignalListener.prototype._flushData = function (url, cache, postService, fromCacheToPayload, extraMetadata) {
76
96
  // if there is data in cache, send it to backend
@@ -34,12 +34,12 @@ export function splitApiFactory(settings, platform, telemetryTracker) {
34
34
  }
35
35
  return splitHttpClient(url, undefined, telemetryTracker.trackHttp(TOKEN));
36
36
  },
37
- fetchSplitChanges: function (since, noCache) {
38
- var url = urls.sdk + "/splitChanges?since=" + since + (filterQueryString || '');
37
+ fetchSplitChanges: function (since, noCache, till) {
38
+ var url = urls.sdk + "/splitChanges?since=" + since + (till ? '&till=' + till : '') + (filterQueryString || '');
39
39
  return splitHttpClient(url, noCache ? noCacheHeaderOptions : undefined, telemetryTracker.trackHttp(SPLITS));
40
40
  },
41
- fetchSegmentChanges: function (since, segmentName, noCache) {
42
- var url = urls.sdk + "/segmentChanges/" + segmentName + "?since=" + since;
41
+ fetchSegmentChanges: function (since, segmentName, noCache, till) {
42
+ var url = urls.sdk + "/segmentChanges/" + segmentName + "?since=" + since + (till ? '&till=' + till : '');
43
43
  return splitHttpClient(url, noCache ? noCacheHeaderOptions : undefined, telemetryTracker.trackHttp(SEGMENT));
44
44
  },
45
45
  fetchMySegments: function (userMatchingKey, noCache) {
@@ -1,6 +1,6 @@
1
1
  import { __spreadArray } from "tslib";
2
- function greedyFetch(fetchSegmentChanges, since, segmentName, noCache) {
3
- return fetchSegmentChanges(since, segmentName, noCache)
2
+ function greedyFetch(fetchSegmentChanges, since, segmentName, noCache, targetTill) {
3
+ return fetchSegmentChanges(since, segmentName, noCache, targetTill)
4
4
  .then(function (resp) { return resp.json(); })
5
5
  .then(function (json) {
6
6
  var since = json.since, till = json.till;
@@ -8,7 +8,7 @@ function greedyFetch(fetchSegmentChanges, since, segmentName, noCache) {
8
8
  return [json];
9
9
  }
10
10
  else {
11
- return Promise.all([json, greedyFetch(fetchSegmentChanges, till, segmentName, noCache)]).then(function (flatMe) {
11
+ return Promise.all([json, greedyFetch(fetchSegmentChanges, till, segmentName, noCache, targetTill)]).then(function (flatMe) {
12
12
  return __spreadArray([flatMe[0]], flatMe[1], true);
13
13
  });
14
14
  }
@@ -19,10 +19,10 @@ function greedyFetch(fetchSegmentChanges, since, segmentName, noCache) {
19
19
  * SegmentChanges fetcher is a wrapper around `segmentChanges` API service that parses the response and handle errors and retries.
20
20
  */
21
21
  export function segmentChangesFetcherFactory(fetchSegmentChanges) {
22
- return function segmentChangesFetcher(since, segmentName, noCache,
22
+ return function segmentChangesFetcher(since, segmentName, noCache, till,
23
23
  // Optional decorator for `fetchMySegments` promise, such as timeout or time tracker
24
24
  decorator) {
25
- var segmentsPromise = greedyFetch(fetchSegmentChanges, since, segmentName, noCache);
25
+ var segmentsPromise = greedyFetch(fetchSegmentChanges, since, segmentName, noCache, till);
26
26
  if (decorator)
27
27
  segmentsPromise = decorator(segmentsPromise);
28
28
  return segmentsPromise;
@@ -3,10 +3,10 @@
3
3
  * SplitChanges fetcher is a wrapper around `splitChanges` API service that parses the response and handle errors.
4
4
  */
5
5
  export function splitChangesFetcherFactory(fetchSplitChanges) {
6
- return function splitChangesFetcher(since, noCache,
6
+ return function splitChangesFetcher(since, noCache, till,
7
7
  // Optional decorator for `fetchSplitChanges` promise, such as timeout or time tracker
8
8
  decorator) {
9
- var splitsPromise = fetchSplitChanges(since, noCache);
9
+ var splitsPromise = fetchSplitChanges(since, noCache, till);
10
10
  if (decorator)
11
11
  splitsPromise = decorator(splitsPromise);
12
12
  return splitsPromise.then(function (resp) { return resp.json(); });
@@ -15,54 +15,54 @@ import { thenable } from '../../../utils/promise/thenable';
15
15
  */
16
16
  export function segmentChangesUpdaterFactory(log, segmentChangesFetcher, segments, readiness) {
17
17
  var readyOnAlreadyExistentState = true;
18
+ function updateSegment(segmentName, noCache, till, fetchOnlyNew) {
19
+ log.debug(LOG_PREFIX_SYNC_SEGMENTS + "Processing segment " + segmentName);
20
+ var sincePromise = Promise.resolve(segments.getChangeNumber(segmentName));
21
+ return sincePromise.then(function (since) {
22
+ // if fetchOnlyNew flag, avoid processing already fetched segments
23
+ if (fetchOnlyNew && since !== -1)
24
+ return -1;
25
+ return segmentChangesFetcher(since, segmentName, noCache, till).then(function (changes) {
26
+ var changeNumber = -1;
27
+ var results = [];
28
+ changes.forEach(function (x) {
29
+ if (x.added.length > 0)
30
+ results.push(segments.addToSegment(segmentName, x.added));
31
+ if (x.removed.length > 0)
32
+ results.push(segments.removeFromSegment(segmentName, x.removed));
33
+ if (x.added.length > 0 || x.removed.length > 0) {
34
+ results.push(segments.setChangeNumber(segmentName, x.till));
35
+ changeNumber = x.till;
36
+ }
37
+ log.debug(LOG_PREFIX_SYNC_SEGMENTS + "Processed " + segmentName + " with till = " + x.till + ". Added: " + x.added.length + ". Removed: " + x.removed.length);
38
+ });
39
+ // If at least one storage operation result is a promise, join all in a single promise.
40
+ if (results.some(function (result) { return thenable(result); }))
41
+ return Promise.all(results).then(function () { return changeNumber; });
42
+ return changeNumber;
43
+ });
44
+ });
45
+ }
18
46
  /**
19
47
  * Segments updater returns a promise that resolves with a `false` boolean value if it fails at least to fetch a segment or synchronize it with the storage.
20
48
  * Thus, a false result doesn't imply that SDK_SEGMENTS_ARRIVED was not emitted.
21
49
  * Returned promise will not be rejected.
22
50
  *
23
- * @param {string[] | undefined} segmentNames list of segment names to fetch. By passing `undefined` it fetches the list of segments registered at the storage
24
- * @param {boolean | undefined} noCache true to revalidate data to fetch on a SEGMENT_UPDATE notifications.
25
51
  * @param {boolean | undefined} fetchOnlyNew if true, only fetch the segments that not exists, i.e., which `changeNumber` is equal to -1.
26
52
  * This param is used by SplitUpdateWorker on server-side SDK, to fetch new registered segments on SPLIT_UPDATE notifications.
53
+ * @param {string | undefined} segmentName segment name to fetch. By passing `undefined` it fetches the list of segments registered at the storage
54
+ * @param {boolean | undefined} noCache true to revalidate data to fetch on a SEGMENT_UPDATE notifications.
55
+ * @param {number | undefined} till till target for the provided segmentName, for CDN bypass.
27
56
  */
28
- return function segmentChangesUpdater(segmentNames, noCache, fetchOnlyNew) {
57
+ return function segmentChangesUpdater(fetchOnlyNew, segmentName, noCache, till) {
29
58
  log.debug(LOG_PREFIX_SYNC_SEGMENTS + "Started segments update");
30
59
  // If not a segment name provided, read list of available segments names to be updated.
31
- var segmentsPromise = Promise.resolve(segmentNames ? segmentNames : segments.getRegisteredSegments());
60
+ var segmentsPromise = Promise.resolve(segmentName ? [segmentName] : segments.getRegisteredSegments());
32
61
  return segmentsPromise.then(function (segmentNames) {
33
62
  // Async fetchers are collected here.
34
63
  var updaters = [];
35
- var _loop_1 = function (index) {
36
- var segmentName = segmentNames[index];
37
- log.debug(LOG_PREFIX_SYNC_SEGMENTS + "Processing segment " + segmentName);
38
- var sincePromise = Promise.resolve(segments.getChangeNumber(segmentName));
39
- updaters.push(sincePromise.then(function (since) {
40
- // if fetchOnlyNew flag, avoid processing already fetched segments
41
- if (fetchOnlyNew && since !== -1)
42
- return -1;
43
- return segmentChangesFetcher(since, segmentName, noCache).then(function (changes) {
44
- var changeNumber = -1;
45
- var results = [];
46
- changes.forEach(function (x) {
47
- if (x.added.length > 0)
48
- results.push(segments.addToSegment(segmentName, x.added));
49
- if (x.removed.length > 0)
50
- results.push(segments.removeFromSegment(segmentName, x.removed));
51
- if (x.added.length > 0 || x.removed.length > 0) {
52
- results.push(segments.setChangeNumber(segmentName, x.till));
53
- changeNumber = x.till;
54
- }
55
- log.debug(LOG_PREFIX_SYNC_SEGMENTS + "Processed " + segmentName + " with till = " + x.till + ". Added: " + x.added.length + ". Removed: " + x.removed.length);
56
- });
57
- // If at least one storage operation result is a promise, join all in a single promise.
58
- if (results.some(function (result) { return thenable(result); }))
59
- return Promise.all(results).then(function () { return changeNumber; });
60
- return changeNumber;
61
- });
62
- }));
63
- };
64
64
  for (var index = 0; index < segmentNames.length; index++) {
65
- _loop_1(index);
65
+ updaters.push(updateSegment(segmentNames[index], noCache, till, fetchOnlyNew));
66
66
  }
67
67
  return Promise.all(updaters).then(function (shouldUpdateFlags) {
68
68
  // if at least one segment fetch succeeded, mark segments ready
@@ -79,16 +79,17 @@ export function splitChangesUpdaterFactory(log, splitChangesFetcher, splits, seg
79
79
  * Returned promise will not be rejected.
80
80
  *
81
81
  * @param {boolean | undefined} noCache true to revalidate data to fetch
82
+ * @param {boolean | undefined} till query param to bypass CDN requests
82
83
  */
83
- return function splitChangesUpdater(noCache) {
84
+ return function splitChangesUpdater(noCache, till) {
84
85
  /**
85
86
  * @param {number} since current changeNumber at splitsCache
86
- * @param {number} retry current number of retry attemps
87
+ * @param {number} retry current number of retry attempts
87
88
  */
88
89
  function _splitChangesUpdater(since, retry) {
89
90
  if (retry === void 0) { retry = 0; }
90
91
  log.debug(SYNC_SPLITS_FETCH, [since]);
91
- var fetcherPromise = splitChangesFetcher(since, noCache, _promiseDecorator)
92
+ var fetcherPromise = splitChangesFetcher(since, noCache, till, _promiseDecorator)
92
93
  .then(function (splitChanges) {
93
94
  startingUp = false;
94
95
  var mutation = computeSplitsMutation(splitChanges.splits);