@splitsoftware/splitio-commons 1.5.1-rc.0 → 1.5.1-rc.3

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 (71) hide show
  1. package/CHANGES.txt +4 -2
  2. package/cjs/integrations/ga/GaToSplit.js +1 -1
  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/integrations/ga/GaToSplit.js +1 -1
  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/integrations/ga/GaToSplit.ts +1 -1
  30. package/src/integrations/ga/autoRequire.js +16 -16
  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/services/types.d.ts +2 -2
  53. package/types/sync/polling/fetchers/types.d.ts +2 -2
  54. package/types/sync/polling/syncTasks/mySegmentsSyncTask.d.ts +2 -2
  55. package/types/sync/polling/types.d.ts +11 -6
  56. package/types/sync/polling/updaters/segmentChangesUpdater.d.ts +1 -1
  57. package/types/sync/polling/updaters/splitChangesUpdater.d.ts +1 -1
  58. package/types/sync/streaming/SSEHandler/types.d.ts +0 -4
  59. package/types/sync/streaming/UpdateWorkers/MySegmentsUpdateWorker.d.ts +3 -24
  60. package/types/sync/streaming/UpdateWorkers/SegmentsUpdateWorker.d.ts +3 -23
  61. package/types/sync/streaming/UpdateWorkers/SplitsUpdateWorker.d.ts +6 -33
  62. package/types/sync/streaming/UpdateWorkers/types.d.ts +1 -2
  63. package/types/sync/streaming/types.d.ts +2 -2
  64. package/types/sync/syncTask.d.ts +2 -3
  65. package/types/utils/Backoff.d.ts +2 -0
  66. package/cjs/sync/offline/LocalhostFromFile.js +0 -13
  67. package/cjs/sync/offline/splitsParser/splitsParserFromFile.js +0 -151
  68. package/esm/sync/offline/LocalhostFromFile.js +0 -9
  69. package/esm/sync/offline/splitsParser/splitsParserFromFile.js +0 -146
  70. package/src/sync/offline/LocalhostFromFile.ts +0 -12
  71. package/src/sync/offline/splitsParser/splitsParserFromFile.ts +0 -182
package/CHANGES.txt CHANGED
@@ -1,6 +1,8 @@
1
- 1.6.0 (July 7, 2022)
2
- - Added `autoRequire` configuration option to the Google Analytics to Split integration (See https://help.split.io/hc/en-us/articles/360040838752#set-up-with-gtm-and-gtag.js).
1
+ 1.6.0 (July 21, 2022)
2
+ - Added `autoRequire` configuration option to the Google Analytics to Split integration, which takes care of requiring the splitTracker plugin on trackers dynamically created by Google tag managers (See https://help.split.io/hc/en-us/articles/360040838752#set-up-with-gtm-and-gtag.js).
3
3
  - Updated browser listener to push remaining impressions and events on 'visibilitychange' and 'pagehide' DOM events, instead of 'unload', which is not reliable in mobile and modern Web browsers (See https://developer.chrome.com/blog/page-lifecycle-api/#legacy-lifecycle-apis-to-avoid).
4
+ - Updated the synchronization flow to be more reliable in the event of an edge case generating delay in cache purge propagation, keeping the SDK cache properly synced.
5
+ - Bugfixing - Removed js-yaml dependency to avoid issues in Node v14 when installing third-party dependencies that also uses js-yaml as a transitive dependency (Related to issue https://github.com/splitio/javascript-client/issues/662)
4
6
 
5
7
  1.5.0 (June 29, 2022)
6
8
  - Added a new config option to control the tasks that listen or poll for updates on feature flags and segments, via the new config sync.enabled . Running online Split will always pull the most recent updates upon initialization, this only affects updates fetching on a running instance. Useful when a consistent session experience is a must or to save resources when updates are not being used.
@@ -26,7 +26,7 @@ function providePlugin(window, pluginName, pluginConstructor, log, autoRequire)
26
26
  window[gaAlias]('provide', pluginName, pluginConstructor);
27
27
  if (autoRequire && (!window[gaAlias].q || window[gaAlias].q.push === [].push)) {
28
28
  // Expecting spy on ga.q push method but not found
29
- log.error(logPrefix + 'integration is configured to autorequire the splitTracker plugin, but the necessary script does not seem to have run.');
29
+ log.error(logPrefix + 'integration is configured to autorequire the splitTracker plugin, but the necessary script does not seem to have run. Please check the docs.');
30
30
  }
31
31
  }
32
32
  // Default mapping: object used for building the default mapper from hits to Split events
@@ -37,12 +37,12 @@ function splitApiFactory(settings, platform, telemetryTracker) {
37
37
  }
38
38
  return splitHttpClient(url, undefined, telemetryTracker.trackHttp(constants_1.TOKEN));
39
39
  },
40
- fetchSplitChanges: function (since, noCache) {
41
- var url = urls.sdk + "/splitChanges?since=" + since + (filterQueryString || '');
40
+ fetchSplitChanges: function (since, noCache, till) {
41
+ var url = urls.sdk + "/splitChanges?since=" + since + (till ? '&till=' + till : '') + (filterQueryString || '');
42
42
  return splitHttpClient(url, noCache ? noCacheHeaderOptions : undefined, telemetryTracker.trackHttp(constants_1.SPLITS));
43
43
  },
44
- fetchSegmentChanges: function (since, segmentName, noCache) {
45
- var url = urls.sdk + "/segmentChanges/" + segmentName + "?since=" + since;
44
+ fetchSegmentChanges: function (since, segmentName, noCache, till) {
45
+ var url = urls.sdk + "/segmentChanges/" + segmentName + "?since=" + since + (till ? '&till=' + till : '');
46
46
  return splitHttpClient(url, noCache ? noCacheHeaderOptions : undefined, telemetryTracker.trackHttp(constants_1.SEGMENT));
47
47
  },
48
48
  fetchMySegments: function (userMatchingKey, noCache) {
@@ -2,8 +2,8 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.segmentChangesFetcherFactory = void 0;
4
4
  var tslib_1 = require("tslib");
5
- function greedyFetch(fetchSegmentChanges, since, segmentName, noCache) {
6
- return fetchSegmentChanges(since, segmentName, noCache)
5
+ function greedyFetch(fetchSegmentChanges, since, segmentName, noCache, targetTill) {
6
+ return fetchSegmentChanges(since, segmentName, noCache, targetTill)
7
7
  .then(function (resp) { return resp.json(); })
8
8
  .then(function (json) {
9
9
  var since = json.since, till = json.till;
@@ -11,7 +11,7 @@ function greedyFetch(fetchSegmentChanges, since, segmentName, noCache) {
11
11
  return [json];
12
12
  }
13
13
  else {
14
- return Promise.all([json, greedyFetch(fetchSegmentChanges, till, segmentName, noCache)]).then(function (flatMe) {
14
+ return Promise.all([json, greedyFetch(fetchSegmentChanges, till, segmentName, noCache, targetTill)]).then(function (flatMe) {
15
15
  return (0, tslib_1.__spreadArray)([flatMe[0]], flatMe[1], true);
16
16
  });
17
17
  }
@@ -22,10 +22,10 @@ function greedyFetch(fetchSegmentChanges, since, segmentName, noCache) {
22
22
  * SegmentChanges fetcher is a wrapper around `segmentChanges` API service that parses the response and handle errors and retries.
23
23
  */
24
24
  function segmentChangesFetcherFactory(fetchSegmentChanges) {
25
- return function segmentChangesFetcher(since, segmentName, noCache,
25
+ return function segmentChangesFetcher(since, segmentName, noCache, till,
26
26
  // Optional decorator for `fetchMySegments` promise, such as timeout or time tracker
27
27
  decorator) {
28
- var segmentsPromise = greedyFetch(fetchSegmentChanges, since, segmentName, noCache);
28
+ var segmentsPromise = greedyFetch(fetchSegmentChanges, since, segmentName, noCache, till);
29
29
  if (decorator)
30
30
  segmentsPromise = decorator(segmentsPromise);
31
31
  return segmentsPromise;
@@ -6,10 +6,10 @@ exports.splitChangesFetcherFactory = void 0;
6
6
  * SplitChanges fetcher is a wrapper around `splitChanges` API service that parses the response and handle errors.
7
7
  */
8
8
  function splitChangesFetcherFactory(fetchSplitChanges) {
9
- return function splitChangesFetcher(since, noCache,
9
+ return function splitChangesFetcher(since, noCache, till,
10
10
  // Optional decorator for `fetchSplitChanges` promise, such as timeout or time tracker
11
11
  decorator) {
12
- var splitsPromise = fetchSplitChanges(since, noCache);
12
+ var splitsPromise = fetchSplitChanges(since, noCache, till);
13
13
  if (decorator)
14
14
  splitsPromise = decorator(splitsPromise);
15
15
  return splitsPromise.then(function (resp) { return resp.json(); });
@@ -18,54 +18,54 @@ var thenable_1 = require("../../../utils/promise/thenable");
18
18
  */
19
19
  function segmentChangesUpdaterFactory(log, segmentChangesFetcher, segments, readiness) {
20
20
  var readyOnAlreadyExistentState = true;
21
+ function updateSegment(segmentName, noCache, till, fetchOnlyNew) {
22
+ log.debug(constants_2.LOG_PREFIX_SYNC_SEGMENTS + "Processing segment " + segmentName);
23
+ var sincePromise = Promise.resolve(segments.getChangeNumber(segmentName));
24
+ return sincePromise.then(function (since) {
25
+ // if fetchOnlyNew flag, avoid processing already fetched segments
26
+ if (fetchOnlyNew && since !== -1)
27
+ return -1;
28
+ return segmentChangesFetcher(since, segmentName, noCache, till).then(function (changes) {
29
+ var changeNumber = -1;
30
+ var results = [];
31
+ changes.forEach(function (x) {
32
+ if (x.added.length > 0)
33
+ results.push(segments.addToSegment(segmentName, x.added));
34
+ if (x.removed.length > 0)
35
+ results.push(segments.removeFromSegment(segmentName, x.removed));
36
+ if (x.added.length > 0 || x.removed.length > 0) {
37
+ results.push(segments.setChangeNumber(segmentName, x.till));
38
+ changeNumber = x.till;
39
+ }
40
+ log.debug(constants_2.LOG_PREFIX_SYNC_SEGMENTS + "Processed " + segmentName + " with till = " + x.till + ". Added: " + x.added.length + ". Removed: " + x.removed.length);
41
+ });
42
+ // If at least one storage operation result is a promise, join all in a single promise.
43
+ if (results.some(function (result) { return (0, thenable_1.thenable)(result); }))
44
+ return Promise.all(results).then(function () { return changeNumber; });
45
+ return changeNumber;
46
+ });
47
+ });
48
+ }
21
49
  /**
22
50
  * 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.
23
51
  * Thus, a false result doesn't imply that SDK_SEGMENTS_ARRIVED was not emitted.
24
52
  * Returned promise will not be rejected.
25
53
  *
26
- * @param {string[] | undefined} segmentNames list of segment names to fetch. By passing `undefined` it fetches the list of segments registered at the storage
27
- * @param {boolean | undefined} noCache true to revalidate data to fetch on a SEGMENT_UPDATE notifications.
28
54
  * @param {boolean | undefined} fetchOnlyNew if true, only fetch the segments that not exists, i.e., which `changeNumber` is equal to -1.
29
55
  * This param is used by SplitUpdateWorker on server-side SDK, to fetch new registered segments on SPLIT_UPDATE notifications.
56
+ * @param {string | undefined} segmentName segment name to fetch. By passing `undefined` it fetches the list of segments registered at the storage
57
+ * @param {boolean | undefined} noCache true to revalidate data to fetch on a SEGMENT_UPDATE notifications.
58
+ * @param {number | undefined} till till target for the provided segmentName, for CDN bypass.
30
59
  */
31
- return function segmentChangesUpdater(segmentNames, noCache, fetchOnlyNew) {
60
+ return function segmentChangesUpdater(fetchOnlyNew, segmentName, noCache, till) {
32
61
  log.debug(constants_2.LOG_PREFIX_SYNC_SEGMENTS + "Started segments update");
33
62
  // If not a segment name provided, read list of available segments names to be updated.
34
- var segmentsPromise = Promise.resolve(segmentNames ? segmentNames : segments.getRegisteredSegments());
63
+ var segmentsPromise = Promise.resolve(segmentName ? [segmentName] : segments.getRegisteredSegments());
35
64
  return segmentsPromise.then(function (segmentNames) {
36
65
  // Async fetchers are collected here.
37
66
  var updaters = [];
38
- var _loop_1 = function (index) {
39
- var segmentName = segmentNames[index];
40
- log.debug(constants_2.LOG_PREFIX_SYNC_SEGMENTS + "Processing segment " + segmentName);
41
- var sincePromise = Promise.resolve(segments.getChangeNumber(segmentName));
42
- updaters.push(sincePromise.then(function (since) {
43
- // if fetchOnlyNew flag, avoid processing already fetched segments
44
- if (fetchOnlyNew && since !== -1)
45
- return -1;
46
- return segmentChangesFetcher(since, segmentName, noCache).then(function (changes) {
47
- var changeNumber = -1;
48
- var results = [];
49
- changes.forEach(function (x) {
50
- if (x.added.length > 0)
51
- results.push(segments.addToSegment(segmentName, x.added));
52
- if (x.removed.length > 0)
53
- results.push(segments.removeFromSegment(segmentName, x.removed));
54
- if (x.added.length > 0 || x.removed.length > 0) {
55
- results.push(segments.setChangeNumber(segmentName, x.till));
56
- changeNumber = x.till;
57
- }
58
- log.debug(constants_2.LOG_PREFIX_SYNC_SEGMENTS + "Processed " + segmentName + " with till = " + x.till + ". Added: " + x.added.length + ". Removed: " + x.removed.length);
59
- });
60
- // If at least one storage operation result is a promise, join all in a single promise.
61
- if (results.some(function (result) { return (0, thenable_1.thenable)(result); }))
62
- return Promise.all(results).then(function () { return changeNumber; });
63
- return changeNumber;
64
- });
65
- }));
66
- };
67
67
  for (var index = 0; index < segmentNames.length; index++) {
68
- _loop_1(index);
68
+ updaters.push(updateSegment(segmentNames[index], noCache, till, fetchOnlyNew));
69
69
  }
70
70
  return Promise.all(updaters).then(function (shouldUpdateFlags) {
71
71
  // if at least one segment fetch succeeded, mark segments ready
@@ -84,16 +84,17 @@ function splitChangesUpdaterFactory(log, splitChangesFetcher, splits, segments,
84
84
  * Returned promise will not be rejected.
85
85
  *
86
86
  * @param {boolean | undefined} noCache true to revalidate data to fetch
87
+ * @param {boolean | undefined} till query param to bypass CDN requests
87
88
  */
88
- return function splitChangesUpdater(noCache) {
89
+ return function splitChangesUpdater(noCache, till) {
89
90
  /**
90
91
  * @param {number} since current changeNumber at splitsCache
91
- * @param {number} retry current number of retry attemps
92
+ * @param {number} retry current number of retry attempts
92
93
  */
93
94
  function _splitChangesUpdater(since, retry) {
94
95
  if (retry === void 0) { retry = 0; }
95
96
  log.debug(constants_2.SYNC_SPLITS_FETCH, [since]);
96
- var fetcherPromise = splitChangesFetcher(since, noCache, _promiseDecorator)
97
+ var fetcherPromise = splitChangesFetcher(since, noCache, till, _promiseDecorator)
97
98
  .then(function (splitChanges) {
98
99
  startingUp = false;
99
100
  var mutation = computeSplitsMutation(splitChanges.splits);
@@ -3,59 +3,59 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.MySegmentsUpdateWorker = void 0;
4
4
  var Backoff_1 = require("../../../utils/Backoff");
5
5
  /**
6
- * MySegmentsUpdateWorker class
6
+ * MySegmentsUpdateWorker factory
7
7
  */
8
- var MySegmentsUpdateWorker = /** @class */ (function () {
9
- /**
10
- * @param {Object} mySegmentsSyncTask task for syncing mySegments data
11
- */
12
- function MySegmentsUpdateWorker(mySegmentsSyncTask) {
13
- this.mySegmentsSyncTask = mySegmentsSyncTask;
14
- this.maxChangeNumber = 0; // keeps the maximum changeNumber among queued events
15
- this.handleNewEvent = false;
16
- this.segmentsData = undefined; // keeps the segmentsData (if included in notification payload) from the queued event with maximum changeNumber
17
- this.currentChangeNumber = -1; // @TODO: remove once `/mySegments` endpoint provides the changeNumber
18
- this.put = this.put.bind(this);
19
- this.__handleMySegmentsUpdateCall = this.__handleMySegmentsUpdateCall.bind(this);
20
- this.backoff = new Backoff_1.Backoff(this.__handleMySegmentsUpdateCall);
21
- }
22
- // Private method
23
- // Precondition: this.mySegmentsSyncTask.isSynchronizingMySegments === false
24
- MySegmentsUpdateWorker.prototype.__handleMySegmentsUpdateCall = function () {
25
- var _this = this;
26
- if (this.maxChangeNumber > this.currentChangeNumber) {
27
- this.handleNewEvent = false;
28
- var currentMaxChangeNumber_1 = this.maxChangeNumber;
8
+ function MySegmentsUpdateWorker(mySegmentsSyncTask) {
9
+ var maxChangeNumber = 0; // keeps the maximum changeNumber among queued events
10
+ var currentChangeNumber = -1;
11
+ var handleNewEvent = false;
12
+ var isHandlingEvent;
13
+ var _segmentsData; // keeps the segmentsData (if included in notification payload) from the queued event with maximum changeNumber
14
+ var backoff = new Backoff_1.Backoff(__handleMySegmentsUpdateCall);
15
+ function __handleMySegmentsUpdateCall() {
16
+ isHandlingEvent = true;
17
+ if (maxChangeNumber > currentChangeNumber) {
18
+ handleNewEvent = false;
19
+ var currentMaxChangeNumber_1 = maxChangeNumber;
29
20
  // fetch mySegments revalidating data if cached
30
- this.mySegmentsSyncTask.execute(this.segmentsData, true).then(function (result) {
21
+ mySegmentsSyncTask.execute(_segmentsData, true).then(function (result) {
22
+ if (!isHandlingEvent)
23
+ return; // halt if `stop` has been called
31
24
  if (result !== false) // Unlike `Splits|SegmentsUpdateWorker`, we cannot use `mySegmentsCache.getChangeNumber` since `/mySegments` endpoint doesn't provide this value.
32
- _this.currentChangeNumber = Math.max(_this.currentChangeNumber, currentMaxChangeNumber_1); // use `currentMaxChangeNumber`, in case that `this.maxChangeNumber` was updated during fetch.
33
- if (_this.handleNewEvent) {
34
- _this.__handleMySegmentsUpdateCall();
25
+ currentChangeNumber = Math.max(currentChangeNumber, currentMaxChangeNumber_1); // use `currentMaxChangeNumber`, in case that `maxChangeNumber` was updated during fetch.
26
+ if (handleNewEvent) {
27
+ __handleMySegmentsUpdateCall();
35
28
  }
36
29
  else {
37
- _this.backoff.scheduleCall();
30
+ backoff.scheduleCall();
38
31
  }
39
32
  });
40
33
  }
34
+ else {
35
+ isHandlingEvent = false;
36
+ }
37
+ }
38
+ return {
39
+ /**
40
+ * Invoked by NotificationProcessor on MY_SEGMENTS_UPDATE event
41
+ *
42
+ * @param {number} changeNumber change number of the MY_SEGMENTS_UPDATE notification
43
+ * @param {SegmentsData | undefined} segmentsData might be undefined
44
+ */
45
+ put: function (changeNumber, segmentsData) {
46
+ if (changeNumber <= currentChangeNumber || changeNumber <= maxChangeNumber)
47
+ return;
48
+ maxChangeNumber = changeNumber;
49
+ handleNewEvent = true;
50
+ _segmentsData = segmentsData;
51
+ if (backoff.timeoutID || !isHandlingEvent)
52
+ __handleMySegmentsUpdateCall();
53
+ backoff.reset();
54
+ },
55
+ stop: function () {
56
+ isHandlingEvent = false;
57
+ backoff.reset();
58
+ }
41
59
  };
42
- /**
43
- * Invoked by NotificationProcessor on MY_SEGMENTS_UPDATE event
44
- *
45
- * @param {number} changeNumber change number of the MY_SEGMENTS_UPDATE notification
46
- * @param {SegmentsData | undefined} segmentsData might be undefined
47
- */
48
- MySegmentsUpdateWorker.prototype.put = function (changeNumber, segmentsData) {
49
- if (changeNumber <= this.currentChangeNumber || changeNumber <= this.maxChangeNumber)
50
- return;
51
- this.maxChangeNumber = changeNumber;
52
- this.handleNewEvent = true;
53
- this.backoff.reset();
54
- this.segmentsData = segmentsData;
55
- if (this.mySegmentsSyncTask.isExecuting())
56
- return;
57
- this.__handleMySegmentsUpdateCall();
58
- };
59
- return MySegmentsUpdateWorker;
60
- }());
60
+ }
61
61
  exports.MySegmentsUpdateWorker = MySegmentsUpdateWorker;
@@ -2,73 +2,91 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.SegmentsUpdateWorker = void 0;
4
4
  var Backoff_1 = require("../../../utils/Backoff");
5
+ var constants_1 = require("./constants");
5
6
  /**
6
- * SegmentUpdateWorker class
7
+ * SegmentsUpdateWorker factory
7
8
  */
8
- var SegmentsUpdateWorker = /** @class */ (function () {
9
- /**
10
- * @param {Object} segmentsCache segments data cache
11
- * @param {Object} segmentsSyncTask task for syncing segments data
12
- */
13
- function SegmentsUpdateWorker(segmentsSyncTask, segmentsCache) {
14
- this.segmentsCache = segmentsCache;
15
- this.segmentsSyncTask = segmentsSyncTask;
16
- this.maxChangeNumbers = {};
17
- this.handleNewEvent = false;
18
- this.put = this.put.bind(this);
19
- this.__handleSegmentUpdateCall = this.__handleSegmentUpdateCall.bind(this);
20
- this.backoff = new Backoff_1.Backoff(this.__handleSegmentUpdateCall);
9
+ function SegmentsUpdateWorker(log, segmentsSyncTask, segmentsCache) {
10
+ // Handles retries with CDN bypass per segment name
11
+ function SegmentUpdateWorker(segment) {
12
+ var maxChangeNumber = 0;
13
+ var handleNewEvent = false;
14
+ var isHandlingEvent;
15
+ var cdnBypass;
16
+ var backoff = new Backoff_1.Backoff(__handleSegmentUpdateCall, constants_1.FETCH_BACKOFF_BASE, constants_1.FETCH_BACKOFF_MAX_WAIT);
17
+ function __handleSegmentUpdateCall() {
18
+ isHandlingEvent = true;
19
+ if (maxChangeNumber > segmentsCache.getChangeNumber(segment)) {
20
+ handleNewEvent = false;
21
+ // fetch segments revalidating data if cached
22
+ segmentsSyncTask.execute(false, segment, true, cdnBypass ? maxChangeNumber : undefined).then(function () {
23
+ if (!isHandlingEvent)
24
+ return; // halt if `stop` has been called
25
+ if (handleNewEvent) {
26
+ __handleSegmentUpdateCall();
27
+ }
28
+ else {
29
+ var attempts = backoff.attempts + 1;
30
+ if (maxChangeNumber <= segmentsCache.getChangeNumber(segment)) {
31
+ log.debug("Refresh completed" + (cdnBypass ? ' bypassing the CDN' : '') + " in " + attempts + " attempts.");
32
+ isHandlingEvent = false;
33
+ return;
34
+ }
35
+ if (attempts < constants_1.FETCH_BACKOFF_MAX_RETRIES) {
36
+ backoff.scheduleCall();
37
+ return;
38
+ }
39
+ if (cdnBypass) {
40
+ log.debug("No changes fetched after " + attempts + " attempts with CDN bypassed.");
41
+ isHandlingEvent = false;
42
+ }
43
+ else {
44
+ backoff.reset();
45
+ cdnBypass = true;
46
+ __handleSegmentUpdateCall();
47
+ }
48
+ }
49
+ });
50
+ }
51
+ else {
52
+ isHandlingEvent = false;
53
+ }
54
+ }
55
+ return {
56
+ put: function (changeNumber) {
57
+ var currentChangeNumber = segmentsCache.getChangeNumber(segment);
58
+ if (changeNumber <= currentChangeNumber || changeNumber <= maxChangeNumber)
59
+ return;
60
+ maxChangeNumber = changeNumber;
61
+ handleNewEvent = true;
62
+ cdnBypass = false;
63
+ if (backoff.timeoutID || !isHandlingEvent)
64
+ __handleSegmentUpdateCall();
65
+ backoff.reset();
66
+ },
67
+ stop: function () {
68
+ isHandlingEvent = false;
69
+ backoff.reset();
70
+ }
71
+ };
21
72
  }
22
- // Private method
23
- // Precondition: this.segmentsSyncTask.isSynchronizingSegments === false
24
- // Approach similar to MySegmentsUpdateWorker due to differences on Segments notifications and endpoint changeNumbers
25
- SegmentsUpdateWorker.prototype.__handleSegmentUpdateCall = function () {
26
- var _this = this;
27
- var segmentsToFetch = Object.keys(this.maxChangeNumbers).filter(function (segmentName) {
28
- return _this.maxChangeNumbers[segmentName] > _this.segmentsCache.getChangeNumber(segmentName);
29
- });
30
- if (segmentsToFetch.length > 0) {
31
- this.handleNewEvent = false;
32
- var currentMaxChangeNumbers_1 = segmentsToFetch.map(function (segmentToFetch) { return _this.maxChangeNumbers[segmentToFetch]; });
33
- // fetch segments revalidating data if cached
34
- this.segmentsSyncTask.execute(segmentsToFetch, true).then(function (result) {
35
- // Unlike `SplitUpdateWorker` where changeNumber is consistent between notification and endpoint, if there is no error,
36
- // we must clean the `maxChangeNumbers` of those segments that didn't receive a new update notification during the fetch.
37
- if (result !== false) {
38
- segmentsToFetch.forEach(function (fetchedSegment, index) {
39
- if (_this.maxChangeNumbers[fetchedSegment] === currentMaxChangeNumbers_1[index])
40
- _this.maxChangeNumbers[fetchedSegment] = -1;
41
- });
42
- }
43
- else {
44
- // recursive invocation with backoff if there was some error
45
- _this.backoff.scheduleCall();
46
- }
47
- // immediate recursive invocation if a new notification was queued during fetch
48
- if (_this.handleNewEvent) {
49
- _this.__handleSegmentUpdateCall();
50
- }
51
- });
73
+ var segments = {};
74
+ return {
75
+ /**
76
+ * Invoked by NotificationProcessor on SEGMENT_UPDATE event
77
+ *
78
+ * @param {number} changeNumber change number of the SEGMENT_UPDATE notification
79
+ * @param {string} segmentName segment name of the SEGMENT_UPDATE notification
80
+ */
81
+ put: function (_a) {
82
+ var changeNumber = _a.changeNumber, segmentName = _a.segmentName;
83
+ if (!segments[segmentName])
84
+ segments[segmentName] = SegmentUpdateWorker(segmentName);
85
+ segments[segmentName].put(changeNumber);
86
+ },
87
+ stop: function () {
88
+ Object.keys(segments).forEach(function (segmentName) { return segments[segmentName].stop(); });
52
89
  }
53
90
  };
54
- /**
55
- * Invoked by NotificationProcessor on SEGMENT_UPDATE event
56
- *
57
- * @param {number} changeNumber change number of the SEGMENT_UPDATE notification
58
- * @param {string} segmentName segment name of the SEGMENT_UPDATE notification
59
- */
60
- SegmentsUpdateWorker.prototype.put = function (_a) {
61
- var changeNumber = _a.changeNumber, segmentName = _a.segmentName;
62
- var currentChangeNumber = this.segmentsCache.getChangeNumber(segmentName);
63
- if (changeNumber <= currentChangeNumber || changeNumber <= this.maxChangeNumbers[segmentName])
64
- return;
65
- this.maxChangeNumbers[segmentName] = changeNumber;
66
- this.handleNewEvent = true;
67
- this.backoff.reset();
68
- if (this.segmentsSyncTask.isExecuting())
69
- return;
70
- this.__handleSegmentUpdateCall();
71
- };
72
- return SegmentsUpdateWorker;
73
- }());
91
+ }
74
92
  exports.SegmentsUpdateWorker = SegmentsUpdateWorker;
@@ -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