@splitsoftware/splitio-commons 1.4.2-rc.5 → 1.5.1-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/CHANGES.txt +3 -3
  2. package/cjs/integrations/ga/GaToSplit.js +11 -12
  3. package/cjs/listeners/browser.js +35 -15
  4. package/cjs/services/splitApi.js +4 -4
  5. package/cjs/sync/polling/fetchers/segmentChangesFetcher.js +5 -5
  6. package/cjs/sync/polling/fetchers/splitChangesFetcher.js +2 -2
  7. package/cjs/sync/polling/updaters/segmentChangesUpdater.js +34 -34
  8. package/cjs/sync/polling/updaters/splitChangesUpdater.js +4 -3
  9. package/cjs/sync/streaming/UpdateWorkers/MySegmentsUpdateWorker.js +46 -46
  10. package/cjs/sync/streaming/UpdateWorkers/SegmentsUpdateWorker.js +82 -64
  11. package/cjs/sync/streaming/UpdateWorkers/SplitsUpdateWorker.js +74 -58
  12. package/cjs/sync/streaming/UpdateWorkers/constants.js +6 -0
  13. package/cjs/sync/streaming/pushManager.js +6 -7
  14. package/cjs/sync/syncTask.js +13 -16
  15. package/cjs/utils/Backoff.js +3 -2
  16. package/esm/integrations/ga/GaToSplit.js +11 -12
  17. package/esm/listeners/browser.js +35 -15
  18. package/esm/services/splitApi.js +4 -4
  19. package/esm/sync/polling/fetchers/segmentChangesFetcher.js +5 -5
  20. package/esm/sync/polling/fetchers/splitChangesFetcher.js +2 -2
  21. package/esm/sync/polling/updaters/segmentChangesUpdater.js +34 -34
  22. package/esm/sync/polling/updaters/splitChangesUpdater.js +4 -3
  23. package/esm/sync/streaming/UpdateWorkers/MySegmentsUpdateWorker.js +46 -47
  24. package/esm/sync/streaming/UpdateWorkers/SegmentsUpdateWorker.js +82 -65
  25. package/esm/sync/streaming/UpdateWorkers/SplitsUpdateWorker.js +74 -59
  26. package/esm/sync/streaming/UpdateWorkers/constants.js +3 -0
  27. package/esm/sync/streaming/pushManager.js +6 -7
  28. package/esm/sync/syncTask.js +13 -16
  29. package/esm/utils/Backoff.js +3 -2
  30. package/package.json +1 -1
  31. package/src/integrations/ga/GaToSplit.ts +8 -14
  32. package/src/integrations/ga/types.ts +2 -13
  33. package/src/listeners/browser.ts +34 -14
  34. package/src/services/splitApi.ts +4 -4
  35. package/src/services/types.ts +2 -2
  36. package/src/sync/polling/fetchers/segmentChangesFetcher.ts +5 -4
  37. package/src/sync/polling/fetchers/splitChangesFetcher.ts +2 -1
  38. package/src/sync/polling/fetchers/types.ts +2 -0
  39. package/src/sync/polling/pollingManagerCS.ts +5 -5
  40. package/src/sync/polling/syncTasks/mySegmentsSyncTask.ts +2 -2
  41. package/src/sync/polling/types.ts +14 -6
  42. package/src/sync/polling/updaters/mySegmentsUpdater.ts +4 -4
  43. package/src/sync/polling/updaters/segmentChangesUpdater.ts +34 -32
  44. package/src/sync/polling/updaters/splitChangesUpdater.ts +5 -4
  45. package/src/sync/streaming/SSEHandler/types.ts +0 -7
  46. package/src/sync/streaming/UpdateWorkers/MySegmentsUpdateWorker.ts +45 -54
  47. package/src/sync/streaming/UpdateWorkers/SegmentsUpdateWorker.ts +78 -63
  48. package/src/sync/streaming/UpdateWorkers/SplitsUpdateWorker.ts +73 -61
  49. package/src/sync/streaming/UpdateWorkers/constants.ts +3 -0
  50. package/src/sync/streaming/UpdateWorkers/types.ts +2 -4
  51. package/src/sync/streaming/pushManager.ts +12 -12
  52. package/src/sync/streaming/types.ts +2 -2
  53. package/src/sync/syncTask.ts +16 -18
  54. package/src/utils/Backoff.ts +7 -2
  55. package/types/integrations/ga/types.d.ts +2 -13
  56. package/types/listeners/browser.d.ts +6 -6
  57. package/types/services/types.d.ts +2 -2
  58. package/types/sync/polling/fetchers/types.d.ts +2 -2
  59. package/types/sync/polling/syncTasks/mySegmentsSyncTask.d.ts +2 -2
  60. package/types/sync/polling/types.d.ts +11 -6
  61. package/types/sync/polling/updaters/segmentChangesUpdater.d.ts +1 -1
  62. package/types/sync/polling/updaters/splitChangesUpdater.d.ts +1 -1
  63. package/types/sync/streaming/SSEHandler/types.d.ts +0 -4
  64. package/types/sync/streaming/UpdateWorkers/MySegmentsUpdateWorker.d.ts +3 -24
  65. package/types/sync/streaming/UpdateWorkers/SegmentsUpdateWorker.d.ts +3 -23
  66. package/types/sync/streaming/UpdateWorkers/SplitsUpdateWorker.d.ts +6 -33
  67. package/types/sync/streaming/UpdateWorkers/constants.d.ts +3 -0
  68. package/types/sync/streaming/UpdateWorkers/types.d.ts +1 -2
  69. package/types/sync/streaming/types.d.ts +2 -2
  70. package/types/sync/syncTask.d.ts +2 -3
  71. package/types/utils/Backoff.d.ts +2 -0
  72. package/src/integrations/ga/autoRequire.js +0 -33
@@ -14,12 +14,13 @@ import { ISyncManager } from '../sync/types';
14
14
  import { isConsentGranted } from '../consent';
15
15
  import { telemetryCacheStatsAdapter } from '../sync/submitters/telemetrySubmitter';
16
16
 
17
- // 'unload' event is used instead of 'beforeunload', since 'unload' is not a cancelable event, so no other listeners can stop the event from occurring.
18
- const UNLOAD_DOM_EVENT = 'unload';
17
+ const VISIBILITYCHANGE_EVENT = 'visibilitychange';
18
+ const PAGEHIDE_EVENT = 'pagehide';
19
+ const UNLOAD_EVENT = 'unload';
19
20
  const EVENT_NAME = 'for unload page event.';
20
21
 
21
22
  /**
22
- * We'll listen for 'unload' event over the window object, since it's the standard way to listen page reload and close.
23
+ * We'll listen for events over the window object.
23
24
  */
24
25
  export class BrowserSignalListener implements ISignalListener {
25
26
 
@@ -32,36 +33,53 @@ export class BrowserSignalListener implements ISignalListener {
32
33
  private serviceApi: ISplitApi,
33
34
  ) {
34
35
  this.flushData = this.flushData.bind(this);
36
+ this.flushDataIfHidden = this.flushDataIfHidden.bind(this);
37
+ this.stopSync = this.stopSync.bind(this);
35
38
  this.fromImpressionsCollector = fromImpressionsCollector.bind(undefined, settings.core.labelsEnabled);
36
39
  }
37
40
 
38
41
  /**
39
42
  * start method.
40
- * Called when SplitFactory is initialized.
41
- * We add a handler on unload events. The handler flushes remaining impressions and events to the backend.
43
+ * Called when SplitFactory is initialized, it adds event listeners to close streaming and flush impressions and events.
42
44
  */
43
45
  start() {
46
+ this.settings.log.debug(CLEANUP_REGISTERING, [EVENT_NAME]);
47
+ if (typeof document !== 'undefined' && document.addEventListener) {
48
+ // Flush data whenever the page is hidden or unloaded.
49
+ document.addEventListener(VISIBILITYCHANGE_EVENT, this.flushDataIfHidden);
50
+ }
44
51
  if (typeof window !== 'undefined' && window.addEventListener) {
45
- this.settings.log.debug(CLEANUP_REGISTERING, [EVENT_NAME]);
46
- window.addEventListener(UNLOAD_DOM_EVENT, this.flushData);
52
+ // 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.
53
+ // If both events are triggered, the last one will find the storage empty, so no duplicated data will be submitted.
54
+ window.addEventListener(PAGEHIDE_EVENT, this.flushData);
55
+ // 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.
56
+ window.addEventListener(UNLOAD_EVENT, this.stopSync);
47
57
  }
48
58
  }
49
59
 
50
60
  /**
51
61
  * stop method.
52
- * Called when client is destroyed.
53
- * We need to remove the handler for unload events, since it can break if called when Split context was destroyed.
62
+ * Called when client is destroyed, it removes event listeners.
54
63
  */
55
64
  stop() {
65
+ this.settings.log.debug(CLEANUP_DEREGISTERING, [EVENT_NAME]);
66
+ if (typeof document !== 'undefined' && document.removeEventListener) {
67
+ document.removeEventListener(VISIBILITYCHANGE_EVENT, this.flushDataIfHidden);
68
+ }
56
69
  if (typeof window !== 'undefined' && window.removeEventListener) {
57
- this.settings.log.debug(CLEANUP_DEREGISTERING, [EVENT_NAME]);
58
- window.removeEventListener(UNLOAD_DOM_EVENT, this.flushData);
70
+ window.removeEventListener(PAGEHIDE_EVENT, this.flushData);
71
+ window.removeEventListener(UNLOAD_EVENT, this.stopSync);
59
72
  }
60
73
  }
61
74
 
75
+ stopSync() {
76
+ // Close streaming connection
77
+ if (this.syncManager && this.syncManager.pushManager) this.syncManager.pushManager.stop();
78
+ }
79
+
62
80
  /**
63
81
  * flushData method.
64
- * Called when unload event is triggered. It flushed remaining impressions and events to the backend,
82
+ * Called when pagehide event is triggered. It flushed remaining impressions and events to the backend,
65
83
  * using beacon API if possible, or falling back to regular post transport.
66
84
  */
67
85
  flushData() {
@@ -86,9 +104,11 @@ export class BrowserSignalListener implements ISignalListener {
86
104
  const telemetryCacheAdapter = telemetryCacheStatsAdapter(this.storage.telemetry, this.storage.splits, this.storage.segments);
87
105
  this._flushData(telemetryUrl + '/v1/metrics/usage/beacon', telemetryCacheAdapter, this.serviceApi.postMetricsUsage);
88
106
  }
107
+ }
89
108
 
90
- // Close streaming connection
91
- if (this.syncManager.pushManager) this.syncManager.pushManager.stop();
109
+ flushDataIfHidden() {
110
+ // Precondition: document defined
111
+ if (document.visibilityState === 'hidden') this.flushData(); // On a 'visibilitychange' event, flush data if state is hidden
92
112
  }
93
113
 
94
114
  private _flushData<T>(url: string, cache: IRecorderCacheProducerSync<T>, postService: (body: string) => Promise<IResponse>, fromCacheToPayload?: (cacheData: T) => any, extraMetadata?: {}) {
@@ -50,13 +50,13 @@ export function splitApiFactory(
50
50
  return splitHttpClient(url, undefined, telemetryTracker.trackHttp(TOKEN));
51
51
  },
52
52
 
53
- fetchSplitChanges(since: number, noCache?: boolean) {
54
- const url = `${urls.sdk}/splitChanges?since=${since}${filterQueryString || ''}`;
53
+ fetchSplitChanges(since: number, noCache?: boolean, till?: number) {
54
+ const url = `${urls.sdk}/splitChanges?since=${since}${till ? '&till=' + till : ''}${filterQueryString || ''}`;
55
55
  return splitHttpClient(url, noCache ? noCacheHeaderOptions : undefined, telemetryTracker.trackHttp(SPLITS));
56
56
  },
57
57
 
58
- fetchSegmentChanges(since: number, segmentName: string, noCache?: boolean) {
59
- const url = `${urls.sdk}/segmentChanges/${segmentName}?since=${since}`;
58
+ fetchSegmentChanges(since: number, segmentName: string, noCache?: boolean, till?: number) {
59
+ const url = `${urls.sdk}/segmentChanges/${segmentName}?since=${since}${till ? '&till=' + till : ''}`;
60
60
  return splitHttpClient(url, noCache ? noCacheHeaderOptions : undefined, telemetryTracker.trackHttp(SEGMENT));
61
61
  },
62
62
 
@@ -35,9 +35,9 @@ export type ISplitHttpClient = (url: string, options?: IRequestOptions, latencyT
35
35
 
36
36
  export type IFetchAuth = (userKeys?: string[]) => Promise<IResponse>
37
37
 
38
- export type IFetchSplitChanges = (since: number, noCache?: boolean) => Promise<IResponse>
38
+ export type IFetchSplitChanges = (since: number, noCache?: boolean, till?: number) => Promise<IResponse>
39
39
 
40
- export type IFetchSegmentChanges = (since: number, segmentName: string, noCache?: boolean) => Promise<IResponse>
40
+ export type IFetchSegmentChanges = (since: number, segmentName: string, noCache?: boolean, till?: number) => Promise<IResponse>
41
41
 
42
42
  export type IFetchMySegments = (userMatchingKey: string, noCache?: boolean) => Promise<IResponse>
43
43
 
@@ -2,15 +2,15 @@ import { IFetchSegmentChanges } from '../../../services/types';
2
2
  import { ISegmentChangesResponse } from '../../../dtos/types';
3
3
  import { ISegmentChangesFetcher } from './types';
4
4
 
5
- function greedyFetch(fetchSegmentChanges: IFetchSegmentChanges, since: number, segmentName: string, noCache?: boolean): Promise<ISegmentChangesResponse[]> {
6
- return fetchSegmentChanges(since, segmentName, noCache)
5
+ function greedyFetch(fetchSegmentChanges: IFetchSegmentChanges, since: number, segmentName: string, noCache?: boolean, targetTill?: number): Promise<ISegmentChangesResponse[]> {
6
+ return fetchSegmentChanges(since, segmentName, noCache, targetTill)
7
7
  .then(resp => resp.json())
8
8
  .then((json: ISegmentChangesResponse) => {
9
9
  let { since, till } = json;
10
10
  if (since === till) {
11
11
  return [json];
12
12
  } else {
13
- return Promise.all([json, greedyFetch(fetchSegmentChanges, till, segmentName, noCache)]).then(flatMe => {
13
+ return Promise.all([json, greedyFetch(fetchSegmentChanges, till, segmentName, noCache, targetTill)]).then(flatMe => {
14
14
  return [flatMe[0], ...flatMe[1]];
15
15
  });
16
16
  }
@@ -27,11 +27,12 @@ export function segmentChangesFetcherFactory(fetchSegmentChanges: IFetchSegmentC
27
27
  since: number,
28
28
  segmentName: string,
29
29
  noCache?: boolean,
30
+ till?: number,
30
31
  // Optional decorator for `fetchMySegments` promise, such as timeout or time tracker
31
32
  decorator?: (promise: Promise<ISegmentChangesResponse[]>) => Promise<ISegmentChangesResponse[]>
32
33
  ): Promise<ISegmentChangesResponse[]> {
33
34
 
34
- let segmentsPromise = greedyFetch(fetchSegmentChanges, since, segmentName, noCache);
35
+ let segmentsPromise = greedyFetch(fetchSegmentChanges, since, segmentName, noCache, till);
35
36
  if (decorator) segmentsPromise = decorator(segmentsPromise);
36
37
 
37
38
  return segmentsPromise;
@@ -10,11 +10,12 @@ export function splitChangesFetcherFactory(fetchSplitChanges: IFetchSplitChanges
10
10
  return function splitChangesFetcher(
11
11
  since: number,
12
12
  noCache?: boolean,
13
+ till?: number,
13
14
  // Optional decorator for `fetchSplitChanges` promise, such as timeout or time tracker
14
15
  decorator?: (promise: Promise<IResponse>) => Promise<IResponse>
15
16
  ) {
16
17
 
17
- let splitsPromise = fetchSplitChanges(since, noCache);
18
+ let splitsPromise = fetchSplitChanges(since, noCache, till);
18
19
  if (decorator) splitsPromise = decorator(splitsPromise);
19
20
 
20
21
  return splitsPromise.then(resp => resp.json());
@@ -4,6 +4,7 @@ import { IResponse } from '../../../services/types';
4
4
  export type ISplitChangesFetcher = (
5
5
  since: number,
6
6
  noCache?: boolean,
7
+ till?: number,
7
8
  decorator?: (promise: Promise<IResponse>) => Promise<IResponse>
8
9
  ) => Promise<ISplitChangesResponse>
9
10
 
@@ -11,6 +12,7 @@ export type ISegmentChangesFetcher = (
11
12
  since: number,
12
13
  segmentName: string,
13
14
  noCache?: boolean,
15
+ till?: number,
14
16
  decorator?: (promise: Promise<ISegmentChangesResponse[]>) => Promise<ISegmentChangesResponse[]>
15
17
  ) => Promise<ISegmentChangesResponse[]>
16
18
 
@@ -1,4 +1,4 @@
1
- import { ISegmentsSyncTask, ISplitsSyncTask, IPollingManagerCS } from './types';
1
+ import { IMySegmentsSyncTask, IPollingManagerCS } from './types';
2
2
  import { forOwn } from '../../utils/lang';
3
3
  import { IReadinessManager } from '../../readiness/types';
4
4
  import { IStorageSync } from '../../storages/types';
@@ -20,13 +20,13 @@ export function pollingManagerCSFactory(
20
20
  const { splitApi, storage, readiness, settings } = params;
21
21
  const log = settings.log;
22
22
 
23
- const splitsSyncTask: ISplitsSyncTask = splitsSyncTaskFactory(splitApi.fetchSplitChanges, storage, readiness, settings, true);
23
+ const splitsSyncTask = splitsSyncTaskFactory(splitApi.fetchSplitChanges, storage, readiness, settings, true);
24
24
 
25
25
  // Map of matching keys to their corresponding MySegmentsSyncTask.
26
- const mySegmentsSyncTasks: Record<string, ISegmentsSyncTask> = {};
26
+ const mySegmentsSyncTasks: Record<string, IMySegmentsSyncTask> = {};
27
27
 
28
28
  const matchingKey = getMatching(settings.core.key);
29
- const mySegmentsSyncTask: ISegmentsSyncTask = add(matchingKey, readiness, storage);
29
+ const mySegmentsSyncTask = add(matchingKey, readiness, storage);
30
30
 
31
31
  function startMySegmentsSyncTasks() {
32
32
  forOwn(mySegmentsSyncTasks, function (mySegmentsSyncTask) {
@@ -54,7 +54,7 @@ export function pollingManagerCSFactory(
54
54
  }
55
55
  });
56
56
 
57
- function add(matchingKey: string, readiness: IReadinessManager, storage: IStorageSync): ISegmentsSyncTask {
57
+ function add(matchingKey: string, readiness: IReadinessManager, storage: IStorageSync) {
58
58
  const mySegmentsSyncTask = mySegmentsSyncTaskFactory(splitApi.fetchMySegments, storage, readiness, settings, matchingKey);
59
59
 
60
60
  // smart ready
@@ -1,7 +1,7 @@
1
1
  import { IStorageSync } from '../../../storages/types';
2
2
  import { IReadinessManager } from '../../../readiness/types';
3
3
  import { syncTaskFactory } from '../../syncTask';
4
- import { ISegmentsSyncTask } from '../types';
4
+ import { IMySegmentsSyncTask } from '../types';
5
5
  import { IFetchMySegments } from '../../../services/types';
6
6
  import { mySegmentsFetcherFactory } from '../fetchers/mySegmentsFetcher';
7
7
  import { ISettings } from '../../../types';
@@ -16,7 +16,7 @@ export function mySegmentsSyncTaskFactory(
16
16
  readiness: IReadinessManager,
17
17
  settings: ISettings,
18
18
  matchingKey: string
19
- ): ISegmentsSyncTask {
19
+ ): IMySegmentsSyncTask {
20
20
  return syncTaskFactory(
21
21
  settings.log,
22
22
  mySegmentsUpdaterFactory(
@@ -1,23 +1,31 @@
1
1
  import { IReadinessManager } from '../../readiness/types';
2
2
  import { IStorageSync } from '../../storages/types';
3
- import { SegmentsData } from '../streaming/SSEHandler/types';
4
3
  import { ITask, ISyncTask } from '../types';
5
4
 
6
- export interface ISplitsSyncTask extends ISyncTask<[noCache?: boolean], boolean> { }
5
+ export interface ISplitsSyncTask extends ISyncTask<[noCache?: boolean, till?: number], boolean> { }
7
6
 
8
- export interface ISegmentsSyncTask extends ISyncTask<[segmentNames?: SegmentsData, noCache?: boolean, fetchOnlyNew?: boolean], boolean> { }
7
+ export interface ISegmentsSyncTask extends ISyncTask<[fetchOnlyNew?: boolean, segmentName?: string, noCache?: boolean, till?: number], boolean> { }
8
+
9
+ export type MySegmentsData = string[] | {
10
+ /* segment name */
11
+ name: string,
12
+ /* action: `true` for add, and `false` for delete */
13
+ add: boolean
14
+ }
15
+
16
+ export interface IMySegmentsSyncTask extends ISyncTask<[segmentsData?: MySegmentsData, noCache?: boolean], boolean> { }
9
17
 
10
18
  export interface IPollingManager extends ITask {
11
19
  syncAll(): Promise<any>
12
20
  splitsSyncTask: ISplitsSyncTask
13
- segmentsSyncTask: ISegmentsSyncTask
21
+ segmentsSyncTask: ISyncTask
14
22
  }
15
23
 
16
24
  /**
17
25
  * PollingManager for client-side with support for multiple clients
18
26
  */
19
27
  export interface IPollingManagerCS extends IPollingManager {
20
- add(matchingKey: string, readiness: IReadinessManager, storage: IStorageSync): ISegmentsSyncTask
28
+ add(matchingKey: string, readiness: IReadinessManager, storage: IStorageSync): IMySegmentsSyncTask
21
29
  remove(matchingKey: string): void;
22
- get(matchingKey: string): ISegmentsSyncTask | undefined
30
+ get(matchingKey: string): IMySegmentsSyncTask | undefined
23
31
  }
@@ -5,7 +5,7 @@ import { timeout } from '../../../utils/promise/timeout';
5
5
  import { SDK_SEGMENTS_ARRIVED } from '../../../readiness/constants';
6
6
  import { ILogger } from '../../../logger/types';
7
7
  import { SYNC_MYSEGMENTS_FETCH_RETRY } from '../../../logger/constants';
8
- import { SegmentsData } from '../../streaming/SSEHandler/types';
8
+ import { MySegmentsData } from '../types';
9
9
 
10
10
  type IMySegmentsUpdater = (segmentList?: string[], noCache?: boolean) => Promise<boolean>
11
11
 
@@ -36,7 +36,7 @@ export function mySegmentsUpdaterFactory(
36
36
  }
37
37
 
38
38
  // @TODO if allowing pluggable storages, handle async execution
39
- function updateSegments(segmentsData: SegmentsData) {
39
+ function updateSegments(segmentsData: MySegmentsData) {
40
40
 
41
41
  let shouldNotifyUpdate;
42
42
  if (Array.isArray(segmentsData)) {
@@ -61,7 +61,7 @@ export function mySegmentsUpdaterFactory(
61
61
  }
62
62
  }
63
63
 
64
- function _mySegmentsUpdater(retry: number, segmentsData?: SegmentsData, noCache?: boolean): Promise<boolean> {
64
+ function _mySegmentsUpdater(retry: number, segmentsData?: MySegmentsData, noCache?: boolean): Promise<boolean> {
65
65
  const updaterPromise: Promise<boolean> = segmentsData ?
66
66
  // If segmentsData is provided, there is no need to fetch mySegments
67
67
  new Promise((res) => { updateSegments(segmentsData); res(true); }) :
@@ -97,7 +97,7 @@ export function mySegmentsUpdaterFactory(
97
97
  * (3) or `undefined`, for which the updater will fetch mySegments in order to sync the storage.
98
98
  * @param {boolean | undefined} noCache true to revalidate data to fetch
99
99
  */
100
- return function mySegmentsUpdater(segmentsData?: SegmentsData, noCache?: boolean) {
100
+ return function mySegmentsUpdater(segmentsData?: MySegmentsData, noCache?: boolean) {
101
101
  return _mySegmentsUpdater(0, segmentsData, noCache);
102
102
  };
103
103
 
@@ -8,7 +8,7 @@ import { ILogger } from '../../../logger/types';
8
8
  import { LOG_PREFIX_INSTANTIATION, LOG_PREFIX_SYNC_SEGMENTS } from '../../../logger/constants';
9
9
  import { thenable } from '../../../utils/promise/thenable';
10
10
 
11
- type ISegmentChangesUpdater = (segmentNames?: string[], noCache?: boolean, fetchOnlyNew?: boolean) => Promise<boolean>
11
+ type ISegmentChangesUpdater = (fetchOnlyNew?: boolean, segmentName?: string, noCache?: boolean, till?: number) => Promise<boolean>
12
12
 
13
13
  /**
14
14
  * Factory of SegmentChanges updater, a task that:
@@ -30,53 +30,56 @@ export function segmentChangesUpdaterFactory(
30
30
 
31
31
  let readyOnAlreadyExistentState = true;
32
32
 
33
+ function updateSegment(segmentName: string, noCache?: boolean, till?: number, fetchOnlyNew?: boolean) {
34
+ log.debug(`${LOG_PREFIX_SYNC_SEGMENTS}Processing segment ${segmentName}`);
35
+ let sincePromise = Promise.resolve(segments.getChangeNumber(segmentName));
36
+
37
+ return sincePromise.then(since => {
38
+ // if fetchOnlyNew flag, avoid processing already fetched segments
39
+ if (fetchOnlyNew && since !== -1) return -1;
40
+
41
+ return segmentChangesFetcher(since, segmentName, noCache, till).then(function (changes) {
42
+ let changeNumber = -1;
43
+ const results: MaybeThenable<boolean | void>[] = [];
44
+ changes.forEach(x => {
45
+ if (x.added.length > 0) results.push(segments.addToSegment(segmentName, x.added));
46
+ if (x.removed.length > 0) results.push(segments.removeFromSegment(segmentName, x.removed));
47
+ if (x.added.length > 0 || x.removed.length > 0) {
48
+ results.push(segments.setChangeNumber(segmentName, x.till));
49
+ changeNumber = x.till;
50
+ }
51
+
52
+ log.debug(`${LOG_PREFIX_SYNC_SEGMENTS}Processed ${segmentName} with till = ${x.till}. Added: ${x.added.length}. Removed: ${x.removed.length}`);
53
+ });
54
+ // If at least one storage operation result is a promise, join all in a single promise.
55
+ if (results.some(result => thenable(result))) return Promise.all(results).then(() => changeNumber);
56
+ return changeNumber;
57
+ });
58
+ });
59
+ }
33
60
  /**
34
61
  * 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.
35
62
  * Thus, a false result doesn't imply that SDK_SEGMENTS_ARRIVED was not emitted.
36
63
  * Returned promise will not be rejected.
37
64
  *
38
- * @param {string[] | undefined} segmentNames list of segment names to fetch. By passing `undefined` it fetches the list of segments registered at the storage
39
- * @param {boolean | undefined} noCache true to revalidate data to fetch on a SEGMENT_UPDATE notifications.
40
65
  * @param {boolean | undefined} fetchOnlyNew if true, only fetch the segments that not exists, i.e., which `changeNumber` is equal to -1.
41
66
  * This param is used by SplitUpdateWorker on server-side SDK, to fetch new registered segments on SPLIT_UPDATE notifications.
67
+ * @param {string | undefined} segmentName segment name to fetch. By passing `undefined` it fetches the list of segments registered at the storage
68
+ * @param {boolean | undefined} noCache true to revalidate data to fetch on a SEGMENT_UPDATE notifications.
69
+ * @param {number | undefined} till till target for the provided segmentName, for CDN bypass.
42
70
  */
43
- return function segmentChangesUpdater(segmentNames?: string[], noCache?: boolean, fetchOnlyNew?: boolean) {
71
+ return function segmentChangesUpdater(fetchOnlyNew?: boolean, segmentName?: string, noCache?: boolean, till?: number) {
44
72
  log.debug(`${LOG_PREFIX_SYNC_SEGMENTS}Started segments update`);
45
73
 
46
74
  // If not a segment name provided, read list of available segments names to be updated.
47
- let segmentsPromise = Promise.resolve(segmentNames ? segmentNames : segments.getRegisteredSegments());
75
+ let segmentsPromise = Promise.resolve(segmentName ? [segmentName] : segments.getRegisteredSegments());
48
76
 
49
77
  return segmentsPromise.then(segmentNames => {
50
78
  // Async fetchers are collected here.
51
79
  const updaters: Promise<number>[] = [];
52
80
 
53
81
  for (let index = 0; index < segmentNames.length; index++) {
54
- const segmentName = segmentNames[index];
55
- log.debug(`${LOG_PREFIX_SYNC_SEGMENTS}Processing segment ${segmentName}`);
56
- let sincePromise = Promise.resolve(segments.getChangeNumber(segmentName));
57
-
58
- updaters.push(sincePromise.then(since => {
59
- // if fetchOnlyNew flag, avoid processing already fetched segments
60
- if (fetchOnlyNew && since !== -1) return -1;
61
-
62
- return segmentChangesFetcher(since, segmentName, noCache).then(function (changes) {
63
- let changeNumber = -1;
64
- const results: MaybeThenable<boolean | void>[] = [];
65
- changes.forEach(x => {
66
- if (x.added.length > 0) results.push(segments.addToSegment(segmentName, x.added));
67
- if (x.removed.length > 0) results.push(segments.removeFromSegment(segmentName, x.removed));
68
- if (x.added.length > 0 || x.removed.length > 0) {
69
- results.push(segments.setChangeNumber(segmentName, x.till));
70
- changeNumber = x.till;
71
- }
72
-
73
- log.debug(`${LOG_PREFIX_SYNC_SEGMENTS}Processed ${segmentName} with till = ${x.till}. Added: ${x.added.length}. Removed: ${x.removed.length}`);
74
- });
75
- // If at least one storage operation result is a promise, join all in a single promise.
76
- if (results.some(result => thenable(result))) return Promise.all(results).then(() => changeNumber);
77
- return changeNumber;
78
- });
79
- }));
82
+ updaters.push(updateSegment(segmentNames[index], noCache, till, fetchOnlyNew));
80
83
 
81
84
  }
82
85
 
@@ -103,5 +106,4 @@ export function segmentChangesUpdaterFactory(
103
106
  return false;
104
107
  });
105
108
  };
106
-
107
109
  }
@@ -8,7 +8,7 @@ import { SDK_SPLITS_ARRIVED, SDK_SPLITS_CACHE_LOADED } from '../../../readiness/
8
8
  import { ILogger } from '../../../logger/types';
9
9
  import { SYNC_SPLITS_FETCH, SYNC_SPLITS_NEW, SYNC_SPLITS_REMOVED, SYNC_SPLITS_SEGMENTS, SYNC_SPLITS_FETCH_FAILS, SYNC_SPLITS_FETCH_RETRY } from '../../../logger/constants';
10
10
 
11
- type ISplitChangesUpdater = (noCache?: boolean) => Promise<boolean>
11
+ type ISplitChangesUpdater = (noCache?: boolean, till?: number) => Promise<boolean>
12
12
 
13
13
  // Checks that all registered segments have been fetched (changeNumber !== -1 for every segment).
14
14
  // Returns a promise that could be rejected.
@@ -109,17 +109,18 @@ export function splitChangesUpdaterFactory(
109
109
  * Returned promise will not be rejected.
110
110
  *
111
111
  * @param {boolean | undefined} noCache true to revalidate data to fetch
112
+ * @param {boolean | undefined} till query param to bypass CDN requests
112
113
  */
113
- return function splitChangesUpdater(noCache?: boolean) {
114
+ return function splitChangesUpdater(noCache?: boolean, till?: number) {
114
115
 
115
116
  /**
116
117
  * @param {number} since current changeNumber at splitsCache
117
- * @param {number} retry current number of retry attemps
118
+ * @param {number} retry current number of retry attempts
118
119
  */
119
120
  function _splitChangesUpdater(since: number, retry = 0): Promise<boolean> {
120
121
  log.debug(SYNC_SPLITS_FETCH, [since]);
121
122
 
122
- const fetcherPromise = splitChangesFetcher(since, noCache, _promiseDecorator)
123
+ const fetcherPromise = splitChangesFetcher(since, noCache, till, _promiseDecorator)
123
124
  .then((splitChanges: ISplitChangesResponse) => {
124
125
  startingUp = false;
125
126
 
@@ -68,10 +68,3 @@ export interface IOccupancyData {
68
68
  export type INotificationData = IMySegmentsUpdateData | IMySegmentsUpdateV2Data | ISegmentUpdateData | ISplitUpdateData | ISplitKillData | IControlData | IOccupancyData
69
69
  export type INotificationMessage = { parsedData: INotificationData, channel: string, timestamp: number, data: string }
70
70
  export type INotificationError = Event & { parsedData?: any, message?: string }
71
-
72
- export type SegmentsData = string[] | {
73
- /* segment name */
74
- name: string,
75
- /* action: `true` for add, and `false` for delete */
76
- add: boolean
77
- }
@@ -1,71 +1,62 @@
1
- import { ISegmentsSyncTask } from '../../polling/types';
1
+ import { IMySegmentsSyncTask, MySegmentsData } from '../../polling/types';
2
2
  import { Backoff } from '../../../utils/Backoff';
3
3
  import { IUpdateWorker } from './types';
4
- import { SegmentsData } from '../SSEHandler/types';
5
4
 
6
5
  /**
7
- * MySegmentsUpdateWorker class
6
+ * MySegmentsUpdateWorker factory
8
7
  */
9
- export class MySegmentsUpdateWorker implements IUpdateWorker {
8
+ export function MySegmentsUpdateWorker(mySegmentsSyncTask: IMySegmentsSyncTask): IUpdateWorker {
10
9
 
11
- private readonly mySegmentsSyncTask: ISegmentsSyncTask;
12
- private maxChangeNumber: number;
13
- private handleNewEvent: boolean;
14
- private segmentsData?: SegmentsData;
15
- private currentChangeNumber: number;
16
- readonly backoff: Backoff;
10
+ let maxChangeNumber = 0; // keeps the maximum changeNumber among queued events
11
+ let currentChangeNumber = -1;
12
+ let handleNewEvent = false;
13
+ let isHandlingEvent: boolean;
14
+ let _segmentsData: MySegmentsData | undefined; // keeps the segmentsData (if included in notification payload) from the queued event with maximum changeNumber
15
+ const backoff = new Backoff(__handleMySegmentsUpdateCall);
17
16
 
18
- /**
19
- * @param {Object} mySegmentsSyncTask task for syncing mySegments data
20
- */
21
- constructor(mySegmentsSyncTask: ISegmentsSyncTask) {
22
- this.mySegmentsSyncTask = mySegmentsSyncTask;
23
- this.maxChangeNumber = 0; // keeps the maximum changeNumber among queued events
24
- this.handleNewEvent = false;
25
- this.segmentsData = undefined; // keeps the segmentsData (if included in notification payload) from the queued event with maximum changeNumber
26
- this.currentChangeNumber = -1; // @TODO: remove once `/mySegments` endpoint provides the changeNumber
27
- this.put = this.put.bind(this);
28
- this.__handleMySegmentsUpdateCall = this.__handleMySegmentsUpdateCall.bind(this);
29
- this.backoff = new Backoff(this.__handleMySegmentsUpdateCall);
30
- }
31
-
32
- // Private method
33
- // Precondition: this.mySegmentsSyncTask.isSynchronizingMySegments === false
34
- __handleMySegmentsUpdateCall() {
35
- if (this.maxChangeNumber > this.currentChangeNumber) {
36
- this.handleNewEvent = false;
37
- const currentMaxChangeNumber = this.maxChangeNumber;
17
+ function __handleMySegmentsUpdateCall() {
18
+ isHandlingEvent = true;
19
+ if (maxChangeNumber > currentChangeNumber) {
20
+ handleNewEvent = false;
21
+ const currentMaxChangeNumber = maxChangeNumber;
38
22
 
39
23
  // fetch mySegments revalidating data if cached
40
- this.mySegmentsSyncTask.execute(this.segmentsData, true).then((result) => {
24
+ mySegmentsSyncTask.execute(_segmentsData, true).then((result) => {
25
+ if (!isHandlingEvent) return; // halt if `stop` has been called
41
26
  if (result !== false) // Unlike `Splits|SegmentsUpdateWorker`, we cannot use `mySegmentsCache.getChangeNumber` since `/mySegments` endpoint doesn't provide this value.
42
- this.currentChangeNumber = Math.max(this.currentChangeNumber, currentMaxChangeNumber); // use `currentMaxChangeNumber`, in case that `this.maxChangeNumber` was updated during fetch.
43
- if (this.handleNewEvent) {
44
- this.__handleMySegmentsUpdateCall();
27
+ currentChangeNumber = Math.max(currentChangeNumber, currentMaxChangeNumber); // use `currentMaxChangeNumber`, in case that `maxChangeNumber` was updated during fetch.
28
+ if (handleNewEvent) {
29
+ __handleMySegmentsUpdateCall();
45
30
  } else {
46
- this.backoff.scheduleCall();
31
+ backoff.scheduleCall();
47
32
  }
48
33
  });
34
+ } else {
35
+ isHandlingEvent = false;
49
36
  }
50
37
  }
51
38
 
52
- /**
53
- * Invoked by NotificationProcessor on MY_SEGMENTS_UPDATE event
54
- *
55
- * @param {number} changeNumber change number of the MY_SEGMENTS_UPDATE notification
56
- * @param {SegmentsData | undefined} segmentsData might be undefined
57
- */
58
- put(changeNumber: number, segmentsData?: SegmentsData) {
59
- if (changeNumber <= this.currentChangeNumber || changeNumber <= this.maxChangeNumber) return;
60
-
61
- this.maxChangeNumber = changeNumber;
62
- this.handleNewEvent = true;
63
- this.backoff.reset();
64
- this.segmentsData = segmentsData;
65
-
66
- if (this.mySegmentsSyncTask.isExecuting()) return;
67
-
68
- this.__handleMySegmentsUpdateCall();
69
- }
70
-
39
+ return {
40
+ /**
41
+ * Invoked by NotificationProcessor on MY_SEGMENTS_UPDATE event
42
+ *
43
+ * @param {number} changeNumber change number of the MY_SEGMENTS_UPDATE notification
44
+ * @param {SegmentsData | undefined} segmentsData might be undefined
45
+ */
46
+ put(changeNumber: number, segmentsData?: MySegmentsData) {
47
+ if (changeNumber <= currentChangeNumber || changeNumber <= maxChangeNumber) return;
48
+
49
+ maxChangeNumber = changeNumber;
50
+ handleNewEvent = true;
51
+ _segmentsData = segmentsData;
52
+
53
+ if (backoff.timeoutID || !isHandlingEvent) __handleMySegmentsUpdateCall();
54
+ backoff.reset();
55
+ },
56
+
57
+ stop() {
58
+ isHandlingEvent = false;
59
+ backoff.reset();
60
+ }
61
+ };
71
62
  }