@splitsoftware/splitio-commons 1.4.1 → 1.4.2-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.
- package/CHANGES.txt +5 -0
- package/cjs/consent/sdkUserConsent.js +3 -3
- package/cjs/listeners/browser.js +7 -6
- package/cjs/sdkFactory/index.js +20 -2
- package/cjs/storages/AbstractSplitsCacheSync.js +1 -1
- package/cjs/storages/dataLoader.js +23 -15
- package/cjs/storages/inMemory/InMemoryStorage.js +15 -1
- package/cjs/storages/inMemory/InMemoryStorageCS.js +12 -0
- package/cjs/sync/submitters/submitterManager.js +28 -4
- package/cjs/sync/syncManagerOnline.js +40 -29
- package/cjs/sync/syncTask.js +15 -10
- package/cjs/trackers/impressionObserver/impressionObserverCS.js +1 -1
- package/cjs/utils/settingsValidation/index.js +13 -1
- package/esm/consent/sdkUserConsent.js +3 -3
- package/esm/listeners/browser.js +7 -6
- package/esm/sdkFactory/index.js +21 -3
- package/esm/storages/AbstractSplitsCacheSync.js +1 -1
- package/esm/storages/dataLoader.js +21 -13
- package/esm/storages/inMemory/InMemoryStorage.js +15 -1
- package/esm/storages/inMemory/InMemoryStorageCS.js +12 -0
- package/esm/sync/submitters/submitterManager.js +28 -4
- package/esm/sync/syncManagerOnline.js +40 -29
- package/esm/sync/syncTask.js +15 -10
- package/esm/trackers/impressionObserver/impressionObserverCS.js +1 -1
- package/esm/utils/settingsValidation/index.js +13 -1
- package/package.json +1 -1
- package/src/consent/sdkUserConsent.ts +4 -3
- package/src/listeners/browser.ts +8 -6
- package/src/sdkClient/clientAttributesDecoration.ts +9 -9
- package/src/sdkFactory/index.ts +23 -4
- package/src/storages/AbstractSplitsCacheSync.ts +1 -1
- package/src/storages/dataLoader.ts +20 -13
- package/src/storages/inMemory/InMemoryStorage.ts +16 -1
- package/src/storages/inMemory/InMemoryStorageCS.ts +13 -0
- package/src/sync/submitters/submitterManager.ts +30 -4
- package/src/sync/submitters/types.ts +7 -0
- package/src/sync/syncManagerOnline.ts +35 -23
- package/src/sync/syncTask.ts +17 -11
- package/src/sync/types.ts +2 -1
- package/src/trackers/impressionObserver/impressionObserverCS.ts +1 -1
- package/src/types.ts +11 -3
- package/src/utils/settingsValidation/index.ts +15 -1
- package/types/integrations/ga/autoRequire.d.ts +4 -0
- package/types/storages/dataLoader.d.ts +2 -2
- package/types/sync/submitters/submitterManager.d.ts +2 -1
- package/types/sync/submitters/types.d.ts +6 -0
- package/types/sync/syncTask.d.ts +2 -2
- package/types/sync/types.d.ts +2 -1
- package/types/trackers/impressionObserver/impressionObserverCS.d.ts +2 -2
- package/types/types.d.ts +10 -2
- package/types/utils/settingsValidation/index.d.ts +1 -0
- package/cjs/sync/syncTaskComposite.js +0 -26
- package/esm/sync/syncTaskComposite.js +0 -22
- package/src/sync/syncTaskComposite.ts +0 -26
|
@@ -1,23 +1,24 @@
|
|
|
1
1
|
import { DEFAULT_CACHE_EXPIRATION_IN_MILLIS } from '../utils/constants/browser';
|
|
2
2
|
/**
|
|
3
|
-
* Factory of
|
|
3
|
+
* Factory of storage loader
|
|
4
4
|
*
|
|
5
5
|
* @param preloadedData validated data following the format proposed in https://github.com/godaddy/split-javascript-data-loader
|
|
6
6
|
* and extended with a `mySegmentsData` property.
|
|
7
7
|
* @returns function to preload the storage
|
|
8
8
|
*/
|
|
9
|
-
export function
|
|
9
|
+
export function DataLoaderFactory(preloadedData) {
|
|
10
10
|
/**
|
|
11
11
|
* Storage-agnostic adaptation of `loadDataIntoLocalStorage` function
|
|
12
12
|
* (https://github.com/godaddy/split-javascript-data-loader/blob/master/src/load-data.js)
|
|
13
13
|
*
|
|
14
14
|
* @param storage object containing `splits` and `segments` cache (client-side variant)
|
|
15
|
-
* @param
|
|
15
|
+
* @param userKey user key (matching key) of the provided MySegmentsCache
|
|
16
16
|
*
|
|
17
|
-
* @TODO extend to support SegmentsCache (server-side variant) by making `userId` optional and adding the corresponding logic.
|
|
18
17
|
* @TODO extend to load data on shared mySegments storages. Be specific when emitting SDK_READY_FROM_CACHE on shared clients. Maybe the serializer should provide the `useSegments` flag.
|
|
18
|
+
* @TODO add logs, and input validation in this module, in favor of size reduction.
|
|
19
|
+
* @TODO unit tests
|
|
19
20
|
*/
|
|
20
|
-
return function loadData(storage,
|
|
21
|
+
return function loadData(storage, userKey) {
|
|
21
22
|
// Do not load data if current preloadedData is empty
|
|
22
23
|
if (Object.keys(preloadedData).length === 0)
|
|
23
24
|
return;
|
|
@@ -33,15 +34,22 @@ export function dataLoaderFactory(preloadedData) {
|
|
|
33
34
|
storage.splits.setChangeNumber(since);
|
|
34
35
|
// splitsData in an object where the property is the split name and the pertaining value is a stringified json of its data
|
|
35
36
|
storage.splits.addSplits(Object.keys(splitsData).map(function (splitName) { return [splitName, splitsData[splitName]]; }));
|
|
36
|
-
// add mySegments data
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
37
|
+
if (userKey) { // add mySegments data (client-side)
|
|
38
|
+
var mySegmentsData = preloadedData.mySegmentsData && preloadedData.mySegmentsData[userKey];
|
|
39
|
+
if (!mySegmentsData) {
|
|
40
|
+
// segmentsData in an object where the property is the segment name and the pertaining value is a stringified object that contains the `added` array of userIds
|
|
41
|
+
mySegmentsData = Object.keys(segmentsData).filter(function (segmentName) {
|
|
42
|
+
var userKeys = segmentsData[segmentName];
|
|
43
|
+
return userKeys.indexOf(userKey) > -1;
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
storage.segments.resetSegments(mySegmentsData);
|
|
47
|
+
}
|
|
48
|
+
else { // add segments data (server-side)
|
|
49
|
+
Object.keys(segmentsData).filter(function (segmentName) {
|
|
50
|
+
var userKeys = segmentsData[segmentName];
|
|
51
|
+
storage.segments.addToSegment(segmentName, userKeys);
|
|
43
52
|
});
|
|
44
53
|
}
|
|
45
|
-
storage.segments.resetSegments(mySegmentsData);
|
|
46
54
|
};
|
|
47
55
|
}
|
|
@@ -5,6 +5,7 @@ import { EventsCacheInMemory } from './EventsCacheInMemory';
|
|
|
5
5
|
import { ImpressionCountsCacheInMemory } from './ImpressionCountsCacheInMemory';
|
|
6
6
|
import { LOCALHOST_MODE, STORAGE_MEMORY } from '../../utils/constants';
|
|
7
7
|
import { TelemetryCacheInMemory } from './TelemetryCacheInMemory';
|
|
8
|
+
import { setToArray } from '../../utils/lang/sets';
|
|
8
9
|
/**
|
|
9
10
|
* InMemory storage factory for standalone server-side SplitFactory
|
|
10
11
|
*
|
|
@@ -25,7 +26,20 @@ export function InMemoryStorageFactory(params) {
|
|
|
25
26
|
this.impressions.clear();
|
|
26
27
|
this.impressionCounts && this.impressionCounts.clear();
|
|
27
28
|
this.events.clear();
|
|
28
|
-
}
|
|
29
|
+
},
|
|
30
|
+
// @ts-ignore, private method, for POC
|
|
31
|
+
getSnapshot: function () {
|
|
32
|
+
var _this = this;
|
|
33
|
+
return {
|
|
34
|
+
lastUpdated: Date.now(),
|
|
35
|
+
since: this.splits.changeNumber,
|
|
36
|
+
splitsData: this.splits.splitsCache,
|
|
37
|
+
segmentsData: Object.keys(this.segments.segmentCache).reduce(function (prev, cur) {
|
|
38
|
+
prev[cur] = setToArray(_this.segments.segmentCache[cur]);
|
|
39
|
+
return prev;
|
|
40
|
+
}, {})
|
|
41
|
+
};
|
|
42
|
+
},
|
|
29
43
|
};
|
|
30
44
|
}
|
|
31
45
|
InMemoryStorageFactory.type = STORAGE_MEMORY;
|
|
@@ -26,6 +26,18 @@ export function InMemoryStorageCSFactory(params) {
|
|
|
26
26
|
this.impressionCounts && this.impressionCounts.clear();
|
|
27
27
|
this.events.clear();
|
|
28
28
|
},
|
|
29
|
+
// @ts-ignore, private method, for POC
|
|
30
|
+
getSnapshot: function () {
|
|
31
|
+
var _a;
|
|
32
|
+
return {
|
|
33
|
+
lastUpdated: Date.now(),
|
|
34
|
+
since: this.splits.changeNumber,
|
|
35
|
+
splitsData: this.splits.splitsCache,
|
|
36
|
+
mySegmentsData: (_a = {},
|
|
37
|
+
_a[params.matchingKey] = Object.keys(this.segments.segmentCache),
|
|
38
|
+
_a)
|
|
39
|
+
};
|
|
40
|
+
},
|
|
29
41
|
// When using shared instanciation with MEMORY we reuse everything but segments (they are unique per key)
|
|
30
42
|
shared: function () {
|
|
31
43
|
return {
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { syncTaskComposite } from '../syncTaskComposite';
|
|
2
1
|
import { eventsSubmitterFactory } from './eventsSubmitter';
|
|
3
2
|
import { impressionsSubmitterFactory } from './impressionsSubmitter';
|
|
4
3
|
import { impressionCountsSubmitterFactory } from './impressionCountsSubmitter';
|
|
@@ -12,7 +11,32 @@ export function submitterManagerFactory(params) {
|
|
|
12
11
|
if (impressionCountsSubmitter)
|
|
13
12
|
submitters.push(impressionCountsSubmitter);
|
|
14
13
|
var telemetrySubmitter = telemetrySubmitterFactory(params);
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
14
|
+
return {
|
|
15
|
+
// `onlyTelemetry` true if SDK is created with userConsent not GRANTED
|
|
16
|
+
start: function (onlyTelemetry) {
|
|
17
|
+
if (!onlyTelemetry)
|
|
18
|
+
submitters.forEach(function (submitter) { return submitter.start(); });
|
|
19
|
+
if (telemetrySubmitter)
|
|
20
|
+
telemetrySubmitter.start();
|
|
21
|
+
},
|
|
22
|
+
// `allExceptTelemetry` true if userConsent is changed to DECLINED
|
|
23
|
+
stop: function (allExceptTelemetry) {
|
|
24
|
+
submitters.forEach(function (submitter) { return submitter.stop(); });
|
|
25
|
+
if (!allExceptTelemetry && telemetrySubmitter)
|
|
26
|
+
telemetrySubmitter.stop();
|
|
27
|
+
},
|
|
28
|
+
isRunning: function () {
|
|
29
|
+
return submitters.some(function (submitter) { return submitter.isRunning(); });
|
|
30
|
+
},
|
|
31
|
+
// Flush data. Called with `onlyTelemetry` true if SDK is destroyed with userConsent not GRANTED
|
|
32
|
+
execute: function (onlyTelemetry) {
|
|
33
|
+
var promises = onlyTelemetry ? [] : submitters.map(function (submitter) { return submitter.execute(); });
|
|
34
|
+
if (telemetrySubmitter)
|
|
35
|
+
promises.push(telemetrySubmitter.execute());
|
|
36
|
+
return Promise.all(promises);
|
|
37
|
+
},
|
|
38
|
+
isExecuting: function () {
|
|
39
|
+
return submitters.some(function (submitter) { return submitter.isExecuting(); });
|
|
40
|
+
}
|
|
41
|
+
};
|
|
18
42
|
}
|
|
@@ -16,16 +16,16 @@ export function syncManagerOnlineFactory(pollingManagerFactory, pushManagerFacto
|
|
|
16
16
|
* SyncManager factory for modular SDK
|
|
17
17
|
*/
|
|
18
18
|
return function (params) {
|
|
19
|
-
var settings = params.settings, _a = params.settings, log = _a.log, streamingEnabled = _a.streamingEnabled, telemetryTracker = params.telemetryTracker;
|
|
19
|
+
var settings = params.settings, _a = params.settings, log = _a.log, streamingEnabled = _a.streamingEnabled, _b = _a.sync, syncEnabled = _b.enabled, onlySubmitters = _b.onlySubmitters, telemetryTracker = params.telemetryTracker;
|
|
20
20
|
/** Polling Manager */
|
|
21
|
-
var pollingManager = pollingManagerFactory && pollingManagerFactory(params);
|
|
21
|
+
var pollingManager = onlySubmitters ? undefined : pollingManagerFactory && pollingManagerFactory(params);
|
|
22
22
|
/** Push Manager */
|
|
23
|
-
var pushManager = streamingEnabled && pollingManager && pushManagerFactory ?
|
|
23
|
+
var pushManager = syncEnabled && streamingEnabled && pollingManager && pushManagerFactory ?
|
|
24
24
|
pushManagerFactory(params, pollingManager) :
|
|
25
25
|
undefined;
|
|
26
26
|
/** Submitter Manager */
|
|
27
27
|
// It is not inyected as push and polling managers, because at the moment it is required
|
|
28
|
-
var
|
|
28
|
+
var submitterManager = submitterManagerFactory(params);
|
|
29
29
|
/** Sync Manager logic */
|
|
30
30
|
function startPolling() {
|
|
31
31
|
if (pollingManager.isRunning()) {
|
|
@@ -58,7 +58,7 @@ export function syncManagerOnlineFactory(pollingManagerFactory, pushManagerFacto
|
|
|
58
58
|
// E.g.: user consent, app state changes (Page hide, Foreground/Background, Online/Offline).
|
|
59
59
|
pollingManager: pollingManager,
|
|
60
60
|
pushManager: pushManager,
|
|
61
|
-
|
|
61
|
+
submitterManager: submitterManager,
|
|
62
62
|
/**
|
|
63
63
|
* Method used to start the syncManager for the first time, or resume it after being stopped.
|
|
64
64
|
*/
|
|
@@ -66,21 +66,29 @@ export function syncManagerOnlineFactory(pollingManagerFactory, pushManagerFacto
|
|
|
66
66
|
running = true;
|
|
67
67
|
// start syncing splits and segments
|
|
68
68
|
if (pollingManager) {
|
|
69
|
-
|
|
70
|
-
|
|
69
|
+
// If synchronization is disabled pushManager and pollingManager should not start
|
|
70
|
+
if (syncEnabled) {
|
|
71
|
+
if (pushManager) {
|
|
72
|
+
// Doesn't call `syncAll` when the syncManager is resuming
|
|
73
|
+
if (startFirstTime) {
|
|
74
|
+
pollingManager.syncAll();
|
|
75
|
+
startFirstTime = false;
|
|
76
|
+
}
|
|
77
|
+
pushManager.start();
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
pollingManager.start();
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
71
84
|
if (startFirstTime) {
|
|
72
85
|
pollingManager.syncAll();
|
|
73
86
|
startFirstTime = false;
|
|
74
87
|
}
|
|
75
|
-
pushManager.start();
|
|
76
|
-
}
|
|
77
|
-
else {
|
|
78
|
-
pollingManager.start();
|
|
79
88
|
}
|
|
80
89
|
}
|
|
81
90
|
// start periodic data recording (events, impressions, telemetry).
|
|
82
|
-
|
|
83
|
-
submitter.start();
|
|
91
|
+
submitterManager.start(!isConsentGranted(settings));
|
|
84
92
|
},
|
|
85
93
|
/**
|
|
86
94
|
* Method used to stop/pause the syncManager.
|
|
@@ -93,16 +101,13 @@ export function syncManagerOnlineFactory(pollingManagerFactory, pushManagerFacto
|
|
|
93
101
|
if (pollingManager && pollingManager.isRunning())
|
|
94
102
|
pollingManager.stop();
|
|
95
103
|
// stop periodic data recording (events, impressions, telemetry).
|
|
96
|
-
|
|
104
|
+
submitterManager.stop();
|
|
97
105
|
},
|
|
98
106
|
isRunning: function () {
|
|
99
107
|
return running;
|
|
100
108
|
},
|
|
101
109
|
flush: function () {
|
|
102
|
-
|
|
103
|
-
return submitter.execute();
|
|
104
|
-
else
|
|
105
|
-
return Promise.resolve();
|
|
110
|
+
return submitterManager.execute(!isConsentGranted(settings));
|
|
106
111
|
},
|
|
107
112
|
// [Only used for client-side]
|
|
108
113
|
// If polling and push managers are defined (standalone mode), they implement the interfaces for client-side
|
|
@@ -113,22 +118,28 @@ export function syncManagerOnlineFactory(pollingManagerFactory, pushManagerFacto
|
|
|
113
118
|
return {
|
|
114
119
|
isRunning: mySegmentsSyncTask.isRunning,
|
|
115
120
|
start: function () {
|
|
116
|
-
if (
|
|
117
|
-
if (
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
+
if (syncEnabled) {
|
|
122
|
+
if (pushManager) {
|
|
123
|
+
if (pollingManager.isRunning()) {
|
|
124
|
+
// if doing polling, we must start the periodic fetch of data
|
|
125
|
+
if (storage.splits.usesSegments())
|
|
126
|
+
mySegmentsSyncTask.start();
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
// if not polling, we must execute the sync task for the initial fetch
|
|
130
|
+
// of segments since `syncAll` was already executed when starting the main client
|
|
131
|
+
mySegmentsSyncTask.execute();
|
|
132
|
+
}
|
|
133
|
+
pushManager.add(matchingKey, mySegmentsSyncTask);
|
|
121
134
|
}
|
|
122
135
|
else {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
mySegmentsSyncTask.execute();
|
|
136
|
+
if (storage.splits.usesSegments())
|
|
137
|
+
mySegmentsSyncTask.start();
|
|
126
138
|
}
|
|
127
|
-
pushManager.add(matchingKey, mySegmentsSyncTask);
|
|
128
139
|
}
|
|
129
140
|
else {
|
|
130
|
-
if (
|
|
131
|
-
mySegmentsSyncTask.
|
|
141
|
+
if (!readinessManager.isReady())
|
|
142
|
+
mySegmentsSyncTask.execute();
|
|
132
143
|
}
|
|
133
144
|
},
|
|
134
145
|
stop: function () {
|
package/esm/sync/syncTask.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { SYNC_TASK_EXECUTE, SYNC_TASK_START, SYNC_TASK_STOP } from '../logger/constants';
|
|
2
2
|
/**
|
|
3
3
|
* Creates a syncTask that handles the periodic execution of a given task ("start" and "stop" methods).
|
|
4
|
-
* The task can be executed
|
|
5
|
-
*
|
|
4
|
+
* The task can be also executed by calling the "execute" method. Multiple execute calls are chained to run secuentially and avoid race conditions.
|
|
5
|
+
* For example, submitters executed on SDK destroy or full queue, while periodic execution is pending.
|
|
6
6
|
*
|
|
7
7
|
* @param log Logger instance.
|
|
8
8
|
* @param task Task to execute that returns a promise that NEVER REJECTS. Otherwise, periodic execution can result in Unhandled Promise Rejections.
|
|
@@ -12,8 +12,8 @@ import { SYNC_TASK_EXECUTE, SYNC_TASK_START, SYNC_TASK_STOP } from '../logger/co
|
|
|
12
12
|
*/
|
|
13
13
|
export function syncTaskFactory(log, task, period, taskName) {
|
|
14
14
|
if (taskName === void 0) { taskName = 'task'; }
|
|
15
|
-
//
|
|
16
|
-
var
|
|
15
|
+
// Task promise while it is pending. Undefined once the promise is resolved
|
|
16
|
+
var pendingTask;
|
|
17
17
|
// flag that indicates if the task periodic execution has been started/stopped.
|
|
18
18
|
var running = false;
|
|
19
19
|
// Auxiliar counter used to avoid race condition when calling `start` & `stop` intermittently
|
|
@@ -27,13 +27,19 @@ export function syncTaskFactory(log, task, period, taskName) {
|
|
|
27
27
|
for (var _i = 0; _i < arguments.length; _i++) {
|
|
28
28
|
args[_i] = arguments[_i];
|
|
29
29
|
}
|
|
30
|
-
executing
|
|
30
|
+
// If task is executing, chain the new execution
|
|
31
|
+
if (pendingTask) {
|
|
32
|
+
return pendingTask.then(function () {
|
|
33
|
+
return execute.apply(void 0, args);
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
// Execute task
|
|
31
37
|
log.debug(SYNC_TASK_EXECUTE, [taskName]);
|
|
32
|
-
|
|
33
|
-
|
|
38
|
+
pendingTask = task.apply(void 0, args).then(function (result) {
|
|
39
|
+
pendingTask = undefined;
|
|
34
40
|
return result;
|
|
35
41
|
});
|
|
36
|
-
|
|
42
|
+
return pendingTask;
|
|
37
43
|
}
|
|
38
44
|
function periodicExecute(currentRunningId) {
|
|
39
45
|
return execute.apply(void 0, runningArgs).then(function (result) {
|
|
@@ -45,10 +51,9 @@ export function syncTaskFactory(log, task, period, taskName) {
|
|
|
45
51
|
});
|
|
46
52
|
}
|
|
47
53
|
return {
|
|
48
|
-
// @TODO check if we need to queued `execute` calls, to avoid possible race conditions on submitters and updaters with streaming.
|
|
49
54
|
execute: execute,
|
|
50
55
|
isExecuting: function () {
|
|
51
|
-
return
|
|
56
|
+
return pendingTask !== undefined;
|
|
52
57
|
},
|
|
53
58
|
start: function () {
|
|
54
59
|
var args = [];
|
|
@@ -2,7 +2,7 @@ import { ImpressionObserver } from './ImpressionObserver';
|
|
|
2
2
|
import { hash } from '../../utils/murmur3/murmur3';
|
|
3
3
|
import { buildKey } from './buildKey';
|
|
4
4
|
export function hashImpression32(impression) {
|
|
5
|
-
return hash(buildKey(impression));
|
|
5
|
+
return hash(buildKey(impression)).toString();
|
|
6
6
|
}
|
|
7
7
|
var LAST_SEEN_CACHE_SIZE = 500; // cache up to 500 impression hashes
|
|
8
8
|
export function impressionObserverCSFactory() {
|
|
@@ -70,7 +70,8 @@ export var base = {
|
|
|
70
70
|
splitFilters: undefined,
|
|
71
71
|
// impressions collection mode
|
|
72
72
|
impressionsMode: OPTIMIZED,
|
|
73
|
-
localhostMode: undefined
|
|
73
|
+
localhostMode: undefined,
|
|
74
|
+
enabled: true
|
|
74
75
|
},
|
|
75
76
|
// Logger
|
|
76
77
|
log: undefined
|
|
@@ -123,6 +124,9 @@ export function settingsValidation(config, validationParams) {
|
|
|
123
124
|
// ensure a valid SDK mode
|
|
124
125
|
// @ts-ignore, modify readonly prop
|
|
125
126
|
withDefaults.mode = mode(withDefaults.core.authorizationKey, withDefaults.mode);
|
|
127
|
+
if (withDefaults.sync.onlySubmitters && withDefaults.mode === STANDALONE_MODE && !withDefaults.dataLoader) {
|
|
128
|
+
throw new Error('To use `onlySubmitters` param in standalone mode, DataLoader is required to preload data into the storage');
|
|
129
|
+
}
|
|
126
130
|
// ensure a valid Storage based on mode defined.
|
|
127
131
|
// @ts-ignore, modify readonly prop
|
|
128
132
|
if (storage)
|
|
@@ -164,6 +168,14 @@ export function settingsValidation(config, validationParams) {
|
|
|
164
168
|
// We are not checking if bases are positive numbers. Thus, we might be reauthenticating immediately (`setTimeout` with NaN or negative number)
|
|
165
169
|
scheduler.pushRetryBackoffBase = fromSecondsToMillis(scheduler.pushRetryBackoffBase);
|
|
166
170
|
}
|
|
171
|
+
// validate sync enabled
|
|
172
|
+
if (withDefaults.sync.enabled !== false) {
|
|
173
|
+
withDefaults.sync.enabled = true;
|
|
174
|
+
}
|
|
175
|
+
// validate sync onlySubmitters
|
|
176
|
+
if (withDefaults.sync.onlySubmitters !== true) {
|
|
177
|
+
withDefaults.sync.onlySubmitters = false;
|
|
178
|
+
}
|
|
167
179
|
// validate the `splitFilters` settings and parse splits query
|
|
168
180
|
var splitFiltersValidation = validateSplitFilters(log, withDefaults.sync.splitFilters, withDefaults.mode);
|
|
169
181
|
withDefaults.sync.splitFilters = splitFiltersValidation.validFilters;
|
package/package.json
CHANGED
|
@@ -34,9 +34,10 @@ export function createUserConsentAPI(params: ISdkFactoryContext) {
|
|
|
34
34
|
settings.userConsent = newConsentStatus;
|
|
35
35
|
|
|
36
36
|
if (consent) { // resumes submitters if transitioning to GRANTED
|
|
37
|
-
syncManager?.
|
|
38
|
-
} else { // pauses submitters and drops tracked data if transitioning to DECLINED
|
|
39
|
-
syncManager?.
|
|
37
|
+
syncManager?.submitterManager?.start();
|
|
38
|
+
} else { // pauses submitters (except telemetry), and drops tracked data if transitioning to DECLINED
|
|
39
|
+
syncManager?.submitterManager?.stop(true);
|
|
40
|
+
|
|
40
41
|
// @ts-ignore, clear method is present in storage for standalone and partial consumer mode
|
|
41
42
|
if (events.clear) events.clear(); // @ts-ignore
|
|
42
43
|
if (impressions.clear) impressions.clear();
|
package/src/listeners/browser.ts
CHANGED
|
@@ -67,7 +67,7 @@ export class BrowserSignalListener implements ISignalListener {
|
|
|
67
67
|
flushData() {
|
|
68
68
|
if (!this.syncManager) return; // In consumer mode there is not sync manager and data to flush
|
|
69
69
|
|
|
70
|
-
// Flush data if there is user consent
|
|
70
|
+
// Flush impressions & events data if there is user consent
|
|
71
71
|
if (isConsentGranted(this.settings)) {
|
|
72
72
|
const eventsUrl = this.settings.urls.events;
|
|
73
73
|
const extraMetadata = {
|
|
@@ -78,11 +78,13 @@ export class BrowserSignalListener implements ISignalListener {
|
|
|
78
78
|
this._flushData(eventsUrl + '/testImpressions/beacon', this.storage.impressions, this.serviceApi.postTestImpressionsBulk, this.fromImpressionsCollector, extraMetadata);
|
|
79
79
|
this._flushData(eventsUrl + '/events/beacon', this.storage.events, this.serviceApi.postEventsBulk);
|
|
80
80
|
if (this.storage.impressionCounts) this._flushData(eventsUrl + '/testImpressions/count/beacon', this.storage.impressionCounts, this.serviceApi.postTestImpressionsCount, fromImpressionCountsCollector);
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Flush telemetry data
|
|
84
|
+
if (this.storage.telemetry) {
|
|
85
|
+
const telemetryUrl = this.settings.urls.telemetry;
|
|
86
|
+
const telemetryCacheAdapter = telemetryCacheStatsAdapter(this.storage.telemetry, this.storage.splits, this.storage.segments);
|
|
87
|
+
this._flushData(telemetryUrl + '/v1/metrics/usage/beacon', telemetryCacheAdapter, this.serviceApi.postMetricsUsage);
|
|
86
88
|
}
|
|
87
89
|
|
|
88
90
|
// Close streaming connection
|
|
@@ -5,7 +5,7 @@ import { ILogger } from '../logger/types';
|
|
|
5
5
|
import { objectAssign } from '../utils/lang/objectAssign';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
|
-
* Add in memory attributes storage methods and combine them with any attribute received from the getTreatment/s call
|
|
8
|
+
* Add in memory attributes storage methods and combine them with any attribute received from the getTreatment/s call
|
|
9
9
|
*/
|
|
10
10
|
export function clientAttributesDecoration<TClient extends SplitIO.IClient | SplitIO.IAsyncClient>(log: ILogger, client: TClient) {
|
|
11
11
|
|
|
@@ -52,10 +52,10 @@ export function clientAttributesDecoration<TClient extends SplitIO.IClient | Spl
|
|
|
52
52
|
getTreatments: getTreatments,
|
|
53
53
|
getTreatmentsWithConfig: getTreatmentsWithConfig,
|
|
54
54
|
track: track,
|
|
55
|
-
|
|
55
|
+
|
|
56
56
|
/**
|
|
57
57
|
* Add an attribute to client's in memory attributes storage
|
|
58
|
-
*
|
|
58
|
+
*
|
|
59
59
|
* @param {string} attributeName Attrinute name
|
|
60
60
|
* @param {string, number, boolean, list} attributeValue Attribute value
|
|
61
61
|
* @returns {boolean} true if the attribute was stored and false otherways
|
|
@@ -70,7 +70,7 @@ export function clientAttributesDecoration<TClient extends SplitIO.IClient | Spl
|
|
|
70
70
|
|
|
71
71
|
/**
|
|
72
72
|
* Returns the attribute with the given key
|
|
73
|
-
*
|
|
73
|
+
*
|
|
74
74
|
* @param {string} attributeName Attribute name
|
|
75
75
|
* @returns {Object} Attribute with the given key
|
|
76
76
|
*/
|
|
@@ -81,7 +81,7 @@ export function clientAttributesDecoration<TClient extends SplitIO.IClient | Spl
|
|
|
81
81
|
|
|
82
82
|
/**
|
|
83
83
|
* Add to client's in memory attributes storage the attributes in 'attributes'
|
|
84
|
-
*
|
|
84
|
+
*
|
|
85
85
|
* @param {Object} attributes Object with attributes to store
|
|
86
86
|
* @returns true if attributes were stored an false otherways
|
|
87
87
|
*/
|
|
@@ -92,7 +92,7 @@ export function clientAttributesDecoration<TClient extends SplitIO.IClient | Spl
|
|
|
92
92
|
|
|
93
93
|
/**
|
|
94
94
|
* Return all the attributes stored in client's in memory attributes storage
|
|
95
|
-
*
|
|
95
|
+
*
|
|
96
96
|
* @returns {Object} returns all the stored attributes
|
|
97
97
|
*/
|
|
98
98
|
getAttributes(): Record<string, Object> {
|
|
@@ -101,8 +101,8 @@ export function clientAttributesDecoration<TClient extends SplitIO.IClient | Spl
|
|
|
101
101
|
|
|
102
102
|
/**
|
|
103
103
|
* Removes from client's in memory attributes storage the attribute with the given key
|
|
104
|
-
*
|
|
105
|
-
* @param {string} attributeName
|
|
104
|
+
*
|
|
105
|
+
* @param {string} attributeName
|
|
106
106
|
* @returns {boolean} true if attribute was removed and false otherways
|
|
107
107
|
*/
|
|
108
108
|
removeAttribute(attributeName: string) {
|
|
@@ -116,7 +116,7 @@ export function clientAttributesDecoration<TClient extends SplitIO.IClient | Spl
|
|
|
116
116
|
clearAttributes() {
|
|
117
117
|
return attributeStorage.clear();
|
|
118
118
|
}
|
|
119
|
-
|
|
119
|
+
|
|
120
120
|
});
|
|
121
121
|
|
|
122
122
|
}
|
package/src/sdkFactory/index.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { sdkReadinessManagerFactory } from '../readiness/sdkReadinessManager';
|
|
|
3
3
|
import { impressionsTrackerFactory } from '../trackers/impressionsTracker';
|
|
4
4
|
import { eventTrackerFactory } from '../trackers/eventTracker';
|
|
5
5
|
import { telemetryTrackerFactory } from '../trackers/telemetryTracker';
|
|
6
|
-
import { IStorageFactoryParams } from '../storages/types';
|
|
6
|
+
import { IStorageFactoryParams, IStorageSync } from '../storages/types';
|
|
7
7
|
import { SplitIO } from '../types';
|
|
8
8
|
import { getMatching } from '../utils/key';
|
|
9
9
|
import { shouldBeOptimized } from '../trackers/impressionObserver/utils';
|
|
@@ -11,7 +11,7 @@ import { validateAndTrackApiKey } from '../utils/inputValidation/apiKey';
|
|
|
11
11
|
import { createLoggerAPI } from '../logger/sdkLogger';
|
|
12
12
|
import { NEW_FACTORY, RETRIEVE_MANAGER } from '../logger/constants';
|
|
13
13
|
import { metadataBuilder } from '../storages/metadataBuilder';
|
|
14
|
-
import { SDK_SPLITS_ARRIVED, SDK_SEGMENTS_ARRIVED } from '../readiness/constants';
|
|
14
|
+
import { SDK_SPLITS_ARRIVED, SDK_SEGMENTS_ARRIVED, SDK_SPLITS_CACHE_LOADED } from '../readiness/constants';
|
|
15
15
|
import { objectAssign } from '../utils/lang/objectAssign';
|
|
16
16
|
|
|
17
17
|
/**
|
|
@@ -32,6 +32,7 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ICsSDK | SplitIO.
|
|
|
32
32
|
|
|
33
33
|
const sdkReadinessManager = sdkReadinessManagerFactory(log, platform.EventEmitter, settings.startup.readyTimeout);
|
|
34
34
|
const readiness = sdkReadinessManager.readinessManager;
|
|
35
|
+
const matchingKey = getMatching(settings.core.key);
|
|
35
36
|
|
|
36
37
|
// @TODO consider passing the settings object, so that each storage access only what it needs
|
|
37
38
|
const storageFactoryParams: IStorageFactoryParams = {
|
|
@@ -40,7 +41,7 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ICsSDK | SplitIO.
|
|
|
40
41
|
optimize: shouldBeOptimized(settings),
|
|
41
42
|
|
|
42
43
|
// ATM, only used by InLocalStorage
|
|
43
|
-
matchingKey
|
|
44
|
+
matchingKey,
|
|
44
45
|
splitFiltersValidation: settings.sync.__splitFiltersValidation,
|
|
45
46
|
|
|
46
47
|
// ATM, only used by PluggableStorage
|
|
@@ -58,7 +59,21 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ICsSDK | SplitIO.
|
|
|
58
59
|
};
|
|
59
60
|
|
|
60
61
|
const storage = storageFactory(storageFactoryParams);
|
|
61
|
-
|
|
62
|
+
|
|
63
|
+
// @TODO dataLoader requires validation
|
|
64
|
+
if (settings.dataLoader) {
|
|
65
|
+
settings.dataLoader(storage as IStorageSync, matchingKey);
|
|
66
|
+
Promise.resolve(storage.splits.checkCache()).then(cacheReady => {
|
|
67
|
+
if (cacheReady) {
|
|
68
|
+
if (settings.sync.onlySubmitters) { // emit SDK_READY to not timeout when not synchronizing splits & segments
|
|
69
|
+
readiness.splits.emit(SDK_SPLITS_ARRIVED);
|
|
70
|
+
readiness.segments.emit(SDK_SEGMENTS_ARRIVED);
|
|
71
|
+
} else { // emit SDK_READY_FROM_CACHE
|
|
72
|
+
readiness.splits.emit(SDK_SPLITS_CACHE_LOADED);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
}
|
|
62
77
|
|
|
63
78
|
const integrationsManager = integrationsManagerFactory && integrationsManagerFactory({ settings, storage });
|
|
64
79
|
|
|
@@ -103,5 +118,9 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ICsSDK | SplitIO.
|
|
|
103
118
|
Logger: createLoggerAPI(settings.log),
|
|
104
119
|
|
|
105
120
|
settings,
|
|
121
|
+
|
|
122
|
+
// @TODO remove
|
|
123
|
+
__storage: storage,
|
|
124
|
+
__ctx: ctx
|
|
106
125
|
}, extraProps && extraProps(ctx));
|
|
107
126
|
}
|
|
@@ -50,7 +50,7 @@ export abstract class AbstractSplitsCacheSync implements ISplitsCacheSync {
|
|
|
50
50
|
* It is used as condition to emit SDK_SPLITS_CACHE_LOADED, and then SDK_READY_FROM_CACHE.
|
|
51
51
|
*/
|
|
52
52
|
checkCache(): boolean {
|
|
53
|
-
return
|
|
53
|
+
return this.getChangeNumber() > -1;
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
/**
|
|
@@ -3,25 +3,26 @@ import { DEFAULT_CACHE_EXPIRATION_IN_MILLIS } from '../utils/constants/browser';
|
|
|
3
3
|
import { DataLoader, ISegmentsCacheSync, ISplitsCacheSync } from './types';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
* Factory of
|
|
6
|
+
* Factory of storage loader
|
|
7
7
|
*
|
|
8
8
|
* @param preloadedData validated data following the format proposed in https://github.com/godaddy/split-javascript-data-loader
|
|
9
9
|
* and extended with a `mySegmentsData` property.
|
|
10
10
|
* @returns function to preload the storage
|
|
11
11
|
*/
|
|
12
|
-
export function
|
|
12
|
+
export function DataLoaderFactory(preloadedData: SplitIO.PreloadedData): DataLoader {
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
15
|
* Storage-agnostic adaptation of `loadDataIntoLocalStorage` function
|
|
16
16
|
* (https://github.com/godaddy/split-javascript-data-loader/blob/master/src/load-data.js)
|
|
17
17
|
*
|
|
18
18
|
* @param storage object containing `splits` and `segments` cache (client-side variant)
|
|
19
|
-
* @param
|
|
19
|
+
* @param userKey user key (matching key) of the provided MySegmentsCache
|
|
20
20
|
*
|
|
21
|
-
* @TODO extend to support SegmentsCache (server-side variant) by making `userId` optional and adding the corresponding logic.
|
|
22
21
|
* @TODO extend to load data on shared mySegments storages. Be specific when emitting SDK_READY_FROM_CACHE on shared clients. Maybe the serializer should provide the `useSegments` flag.
|
|
22
|
+
* @TODO add logs, and input validation in this module, in favor of size reduction.
|
|
23
|
+
* @TODO unit tests
|
|
23
24
|
*/
|
|
24
|
-
return function loadData(storage: { splits: ISplitsCacheSync, segments: ISegmentsCacheSync },
|
|
25
|
+
return function loadData(storage: { splits: ISplitsCacheSync, segments: ISegmentsCacheSync }, userKey?: string) {
|
|
25
26
|
// Do not load data if current preloadedData is empty
|
|
26
27
|
if (Object.keys(preloadedData).length === 0) return;
|
|
27
28
|
|
|
@@ -41,15 +42,21 @@ export function dataLoaderFactory(preloadedData: SplitIO.PreloadedData): DataLoa
|
|
|
41
42
|
// splitsData in an object where the property is the split name and the pertaining value is a stringified json of its data
|
|
42
43
|
storage.splits.addSplits(Object.keys(splitsData).map(splitName => [splitName, splitsData[splitName]]));
|
|
43
44
|
|
|
44
|
-
// add mySegments data
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
45
|
+
if (userKey) { // add mySegments data (client-side)
|
|
46
|
+
let mySegmentsData = preloadedData.mySegmentsData && preloadedData.mySegmentsData[userKey];
|
|
47
|
+
if (!mySegmentsData) {
|
|
48
|
+
// segmentsData in an object where the property is the segment name and the pertaining value is a stringified object that contains the `added` array of userIds
|
|
49
|
+
mySegmentsData = Object.keys(segmentsData).filter(segmentName => {
|
|
50
|
+
const userKeys = segmentsData[segmentName];
|
|
51
|
+
return userKeys.indexOf(userKey) > -1;
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
storage.segments.resetSegments(mySegmentsData);
|
|
55
|
+
} else { // add segments data (server-side)
|
|
56
|
+
Object.keys(segmentsData).filter(segmentName => {
|
|
57
|
+
const userKeys = segmentsData[segmentName];
|
|
58
|
+
storage.segments.addToSegment(segmentName, userKeys);
|
|
51
59
|
});
|
|
52
60
|
}
|
|
53
|
-
storage.segments.resetSegments(mySegmentsData);
|
|
54
61
|
};
|
|
55
62
|
}
|