@splitsoftware/splitio-commons 1.4.2-rc.2 → 1.4.2-rc.5
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 +3 -0
- package/cjs/integrations/ga/GaToSplit.js +12 -11
- package/cjs/sdkFactory/index.js +2 -20
- package/cjs/storages/AbstractSplitsCacheSync.js +1 -1
- package/cjs/storages/dataLoader.js +15 -23
- package/cjs/storages/inMemory/InMemoryStorage.js +1 -15
- package/cjs/storages/inMemory/InMemoryStorageCS.js +0 -12
- package/cjs/sync/syncManagerOnline.js +2 -2
- package/cjs/trackers/impressionObserver/impressionObserverCS.js +1 -1
- package/cjs/utils/settingsValidation/index.js +1 -8
- package/esm/integrations/ga/GaToSplit.js +12 -11
- package/esm/sdkFactory/index.js +3 -21
- package/esm/storages/AbstractSplitsCacheSync.js +1 -1
- package/esm/storages/dataLoader.js +13 -21
- package/esm/storages/inMemory/InMemoryStorage.js +1 -15
- package/esm/storages/inMemory/InMemoryStorageCS.js +0 -12
- package/esm/sync/syncManagerOnline.js +2 -2
- package/esm/trackers/impressionObserver/impressionObserverCS.js +1 -1
- package/esm/utils/settingsValidation/index.js +1 -8
- package/package.json +1 -1
- package/src/integrations/ga/GaToSplit.ts +14 -8
- package/src/integrations/ga/autoRequire.js +33 -0
- package/src/integrations/ga/types.ts +13 -2
- package/src/sdkFactory/index.ts +4 -23
- package/src/storages/AbstractSplitsCacheSync.ts +1 -1
- package/src/storages/dataLoader.ts +13 -20
- package/src/storages/inMemory/InMemoryStorage.ts +1 -16
- package/src/storages/inMemory/InMemoryStorageCS.ts +0 -13
- package/src/sync/syncManagerOnline.ts +2 -2
- package/src/trackers/impressionObserver/impressionObserverCS.ts +1 -1
- package/src/types.ts +3 -5
- package/src/utils/settingsValidation/index.ts +1 -9
- package/types/integrations/ga/types.d.ts +13 -2
- package/types/storages/dataLoader.d.ts +2 -2
- package/types/trackers/impressionObserver/impressionObserverCS.d.ts +2 -2
- package/types/types.d.ts +2 -4
package/CHANGES.txt
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
1.6.0 (June XX, 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).
|
|
3
|
+
|
|
1
4
|
1.5.0 (June 24, 2022)
|
|
2
5
|
- 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.
|
|
3
6
|
- Updated telemetry logic to track the anonymous config for user consent flag set to declined or unknown.
|
|
@@ -10,23 +10,24 @@ var logNameMapper = 'ga-to-split:mapper';
|
|
|
10
10
|
/**
|
|
11
11
|
* Provides a plugin to use with analytics.js, accounting for the possibility
|
|
12
12
|
* that the global command queue has been renamed or not yet defined.
|
|
13
|
-
* @param
|
|
14
|
-
* @param
|
|
13
|
+
* @param window Reference to global object.
|
|
14
|
+
* @param pluginName The plugin name identifier.
|
|
15
|
+
* @param pluginConstructor The plugin constructor function.
|
|
16
|
+
* @param log Logger instance.
|
|
17
|
+
* @param autoRequire If true, log error when auto-require script is not detected
|
|
15
18
|
*/
|
|
16
|
-
function providePlugin(pluginName, pluginConstructor) {
|
|
19
|
+
function providePlugin(window, pluginName, pluginConstructor, log, autoRequire) {
|
|
17
20
|
// get reference to global command queue. Init it if not defined yet.
|
|
18
|
-
// @ts-expect-error
|
|
19
21
|
var gaAlias = window.GoogleAnalyticsObject || 'ga';
|
|
20
22
|
window[gaAlias] = window[gaAlias] || function () {
|
|
21
|
-
|
|
22
|
-
for (var _i = 0; _i < arguments.length; _i++) {
|
|
23
|
-
args[_i] = arguments[_i];
|
|
24
|
-
}
|
|
25
|
-
(window[gaAlias].q = window[gaAlias].q || []).push(args);
|
|
23
|
+
(window[gaAlias].q = window[gaAlias].q || []).push(arguments);
|
|
26
24
|
};
|
|
27
25
|
// provides the plugin for use with analytics.js.
|
|
28
|
-
// @ts-expect-error
|
|
29
26
|
window[gaAlias]('provide', pluginName, pluginConstructor);
|
|
27
|
+
if (autoRequire && (!window[gaAlias].q || window[gaAlias].q.push === [].push)) {
|
|
28
|
+
// Expecting spy on ga.q push method but not found
|
|
29
|
+
log.error(logPrefix + 'integration is configured to autorequire the splitTracker plugin, but the necessary script does not seem to have run.');
|
|
30
|
+
}
|
|
30
31
|
}
|
|
31
32
|
// Default mapping: object used for building the default mapper from hits to Split events
|
|
32
33
|
var defaultMapping = {
|
|
@@ -246,6 +247,6 @@ function GaToSplit(sdkOptions, params) {
|
|
|
246
247
|
return SplitTracker;
|
|
247
248
|
}());
|
|
248
249
|
// Register the plugin, even if config is invalid, since, if not provided, it will block `ga` command queue.
|
|
249
|
-
providePlugin('splitTracker', SplitTracker);
|
|
250
|
+
providePlugin(window, 'splitTracker', SplitTracker, log, sdkOptions.autoRequire);
|
|
250
251
|
}
|
|
251
252
|
exports.GaToSplit = GaToSplit;
|
package/cjs/sdkFactory/index.js
CHANGED
|
@@ -25,14 +25,13 @@ function sdkFactory(params) {
|
|
|
25
25
|
(0, apiKey_1.validateAndTrackApiKey)(log, settings.core.authorizationKey);
|
|
26
26
|
var sdkReadinessManager = (0, sdkReadinessManager_1.sdkReadinessManagerFactory)(log, platform.EventEmitter, settings.startup.readyTimeout);
|
|
27
27
|
var readiness = sdkReadinessManager.readinessManager;
|
|
28
|
-
var matchingKey = (0, key_1.getMatching)(settings.core.key);
|
|
29
28
|
// @TODO consider passing the settings object, so that each storage access only what it needs
|
|
30
29
|
var storageFactoryParams = {
|
|
31
30
|
impressionsQueueSize: settings.scheduler.impressionsQueueSize,
|
|
32
31
|
eventsQueueSize: settings.scheduler.eventsQueueSize,
|
|
33
32
|
optimize: (0, utils_1.shouldBeOptimized)(settings),
|
|
34
33
|
// ATM, only used by InLocalStorage
|
|
35
|
-
matchingKey:
|
|
34
|
+
matchingKey: (0, key_1.getMatching)(settings.core.key),
|
|
36
35
|
splitFiltersValidation: settings.sync.__splitFiltersValidation,
|
|
37
36
|
// ATM, only used by PluggableStorage
|
|
38
37
|
mode: settings.mode,
|
|
@@ -48,21 +47,7 @@ function sdkFactory(params) {
|
|
|
48
47
|
log: log
|
|
49
48
|
};
|
|
50
49
|
var storage = storageFactory(storageFactoryParams);
|
|
51
|
-
// @TODO dataLoader
|
|
52
|
-
if (settings.dataLoader) {
|
|
53
|
-
settings.dataLoader(storage, matchingKey);
|
|
54
|
-
Promise.resolve(storage.splits.checkCache()).then(function (cacheReady) {
|
|
55
|
-
if (cacheReady) {
|
|
56
|
-
if (settings.sync.onlySubmitters) { // emit SDK_READY to not timeout when not synchronizing splits & segments
|
|
57
|
-
readiness.splits.emit(constants_2.SDK_SPLITS_ARRIVED);
|
|
58
|
-
readiness.segments.emit(constants_2.SDK_SEGMENTS_ARRIVED);
|
|
59
|
-
}
|
|
60
|
-
else { // emit SDK_READY_FROM_CACHE
|
|
61
|
-
readiness.splits.emit(constants_2.SDK_SPLITS_CACHE_LOADED);
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
});
|
|
65
|
-
}
|
|
50
|
+
// @TODO add support for dataloader: `if (params.dataLoader) params.dataLoader(storage);`
|
|
66
51
|
var integrationsManager = integrationsManagerFactory && integrationsManagerFactory({ settings: settings, storage: storage });
|
|
67
52
|
// trackers
|
|
68
53
|
var observer = impressionsObserverFactory && impressionsObserverFactory();
|
|
@@ -94,9 +79,6 @@ function sdkFactory(params) {
|
|
|
94
79
|
// Logger wrapper API
|
|
95
80
|
Logger: (0, sdkLogger_1.createLoggerAPI)(settings.log),
|
|
96
81
|
settings: settings,
|
|
97
|
-
// @TODO remove
|
|
98
|
-
__storage: storage,
|
|
99
|
-
__ctx: ctx
|
|
100
82
|
}, extraProps && extraProps(ctx));
|
|
101
83
|
}
|
|
102
84
|
exports.sdkFactory = sdkFactory;
|
|
@@ -33,7 +33,7 @@ var AbstractSplitsCacheSync = /** @class */ (function () {
|
|
|
33
33
|
* It is used as condition to emit SDK_SPLITS_CACHE_LOADED, and then SDK_READY_FROM_CACHE.
|
|
34
34
|
*/
|
|
35
35
|
AbstractSplitsCacheSync.prototype.checkCache = function () {
|
|
36
|
-
return
|
|
36
|
+
return false;
|
|
37
37
|
};
|
|
38
38
|
/**
|
|
39
39
|
* Kill `name` split and set `defaultTreatment` and `changeNumber`.
|
|
@@ -1,27 +1,26 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.dataLoaderFactory = void 0;
|
|
4
4
|
var browser_1 = require("../utils/constants/browser");
|
|
5
5
|
/**
|
|
6
|
-
* Factory of storage loader
|
|
6
|
+
* Factory of client-side 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
|
-
function
|
|
12
|
+
function dataLoaderFactory(preloadedData) {
|
|
13
13
|
/**
|
|
14
14
|
* Storage-agnostic adaptation of `loadDataIntoLocalStorage` function
|
|
15
15
|
* (https://github.com/godaddy/split-javascript-data-loader/blob/master/src/load-data.js)
|
|
16
16
|
*
|
|
17
17
|
* @param storage object containing `splits` and `segments` cache (client-side variant)
|
|
18
|
-
* @param
|
|
18
|
+
* @param userId user key string of the provided MySegmentsCache
|
|
19
19
|
*
|
|
20
|
+
* @TODO extend to support SegmentsCache (server-side variant) by making `userId` optional and adding the corresponding logic.
|
|
20
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.
|
|
21
|
-
* @TODO add logs, and input validation in this module, in favor of size reduction.
|
|
22
|
-
* @TODO unit tests
|
|
23
22
|
*/
|
|
24
|
-
return function loadData(storage,
|
|
23
|
+
return function loadData(storage, userId) {
|
|
25
24
|
// Do not load data if current preloadedData is empty
|
|
26
25
|
if (Object.keys(preloadedData).length === 0)
|
|
27
26
|
return;
|
|
@@ -37,23 +36,16 @@ function DataLoaderFactory(preloadedData) {
|
|
|
37
36
|
storage.splits.setChangeNumber(since);
|
|
38
37
|
// splitsData in an object where the property is the split name and the pertaining value is a stringified json of its data
|
|
39
38
|
storage.splits.addSplits(Object.keys(splitsData).map(function (splitName) { return [splitName, splitsData[splitName]]; }));
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
});
|
|
48
|
-
}
|
|
49
|
-
storage.segments.resetSegments(mySegmentsData);
|
|
50
|
-
}
|
|
51
|
-
else { // add segments data (server-side)
|
|
52
|
-
Object.keys(segmentsData).filter(function (segmentName) {
|
|
53
|
-
var userKeys = segmentsData[segmentName];
|
|
54
|
-
storage.segments.addToSegment(segmentName, userKeys);
|
|
39
|
+
// add mySegments data
|
|
40
|
+
var mySegmentsData = preloadedData.mySegmentsData && preloadedData.mySegmentsData[userId];
|
|
41
|
+
if (!mySegmentsData) {
|
|
42
|
+
// 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
|
|
43
|
+
mySegmentsData = Object.keys(segmentsData).filter(function (segmentName) {
|
|
44
|
+
var userIds = JSON.parse(segmentsData[segmentName]).added;
|
|
45
|
+
return Array.isArray(userIds) && userIds.indexOf(userId) > -1;
|
|
55
46
|
});
|
|
56
47
|
}
|
|
48
|
+
storage.segments.resetSegments(mySegmentsData);
|
|
57
49
|
};
|
|
58
50
|
}
|
|
59
|
-
exports.
|
|
51
|
+
exports.dataLoaderFactory = dataLoaderFactory;
|
|
@@ -8,7 +8,6 @@ var EventsCacheInMemory_1 = require("./EventsCacheInMemory");
|
|
|
8
8
|
var ImpressionCountsCacheInMemory_1 = require("./ImpressionCountsCacheInMemory");
|
|
9
9
|
var constants_1 = require("../../utils/constants");
|
|
10
10
|
var TelemetryCacheInMemory_1 = require("./TelemetryCacheInMemory");
|
|
11
|
-
var sets_1 = require("../../utils/lang/sets");
|
|
12
11
|
/**
|
|
13
12
|
* InMemory storage factory for standalone server-side SplitFactory
|
|
14
13
|
*
|
|
@@ -29,20 +28,7 @@ function InMemoryStorageFactory(params) {
|
|
|
29
28
|
this.impressions.clear();
|
|
30
29
|
this.impressionCounts && this.impressionCounts.clear();
|
|
31
30
|
this.events.clear();
|
|
32
|
-
}
|
|
33
|
-
// @ts-ignore, private method, for POC
|
|
34
|
-
getSnapshot: function () {
|
|
35
|
-
var _this = this;
|
|
36
|
-
return {
|
|
37
|
-
lastUpdated: Date.now(),
|
|
38
|
-
since: this.splits.changeNumber,
|
|
39
|
-
splitsData: this.splits.splitsCache,
|
|
40
|
-
segmentsData: Object.keys(this.segments.segmentCache).reduce(function (prev, cur) {
|
|
41
|
-
prev[cur] = (0, sets_1.setToArray)(_this.segments.segmentCache[cur]);
|
|
42
|
-
return prev;
|
|
43
|
-
}, {})
|
|
44
|
-
};
|
|
45
|
-
},
|
|
31
|
+
}
|
|
46
32
|
};
|
|
47
33
|
}
|
|
48
34
|
exports.InMemoryStorageFactory = InMemoryStorageFactory;
|
|
@@ -29,18 +29,6 @@ function InMemoryStorageCSFactory(params) {
|
|
|
29
29
|
this.impressionCounts && this.impressionCounts.clear();
|
|
30
30
|
this.events.clear();
|
|
31
31
|
},
|
|
32
|
-
// @ts-ignore, private method, for POC
|
|
33
|
-
getSnapshot: function () {
|
|
34
|
-
var _a;
|
|
35
|
-
return {
|
|
36
|
-
lastUpdated: Date.now(),
|
|
37
|
-
since: this.splits.changeNumber,
|
|
38
|
-
splitsData: this.splits.splitsCache,
|
|
39
|
-
mySegmentsData: (_a = {},
|
|
40
|
-
_a[params.matchingKey] = Object.keys(this.segments.segmentCache),
|
|
41
|
-
_a)
|
|
42
|
-
};
|
|
43
|
-
},
|
|
44
32
|
// When using shared instanciation with MEMORY we reuse everything but segments (they are unique per key)
|
|
45
33
|
shared: function () {
|
|
46
34
|
return {
|
|
@@ -19,9 +19,9 @@ function syncManagerOnlineFactory(pollingManagerFactory, pushManagerFactory) {
|
|
|
19
19
|
* SyncManager factory for modular SDK
|
|
20
20
|
*/
|
|
21
21
|
return function (params) {
|
|
22
|
-
var settings = params.settings, _a = params.settings, log = _a.log, streamingEnabled = _a.streamingEnabled,
|
|
22
|
+
var settings = params.settings, _a = params.settings, log = _a.log, streamingEnabled = _a.streamingEnabled, syncEnabled = _a.sync.enabled, telemetryTracker = params.telemetryTracker;
|
|
23
23
|
/** Polling Manager */
|
|
24
|
-
var pollingManager =
|
|
24
|
+
var pollingManager = pollingManagerFactory && pollingManagerFactory(params);
|
|
25
25
|
/** Push Manager */
|
|
26
26
|
var pushManager = syncEnabled && streamingEnabled && pollingManager && pushManagerFactory ?
|
|
27
27
|
pushManagerFactory(params, pollingManager) :
|
|
@@ -5,7 +5,7 @@ var ImpressionObserver_1 = require("./ImpressionObserver");
|
|
|
5
5
|
var murmur3_1 = require("../../utils/murmur3/murmur3");
|
|
6
6
|
var buildKey_1 = require("./buildKey");
|
|
7
7
|
function hashImpression32(impression) {
|
|
8
|
-
return (0, murmur3_1.hash)((0, buildKey_1.buildKey)(impression))
|
|
8
|
+
return (0, murmur3_1.hash)((0, buildKey_1.buildKey)(impression));
|
|
9
9
|
}
|
|
10
10
|
exports.hashImpression32 = hashImpression32;
|
|
11
11
|
var LAST_SEEN_CACHE_SIZE = 500; // cache up to 500 impression hashes
|
|
@@ -127,9 +127,6 @@ function settingsValidation(config, validationParams) {
|
|
|
127
127
|
// ensure a valid SDK mode
|
|
128
128
|
// @ts-ignore, modify readonly prop
|
|
129
129
|
withDefaults.mode = (0, mode_1.mode)(withDefaults.core.authorizationKey, withDefaults.mode);
|
|
130
|
-
if (withDefaults.sync.onlySubmitters && withDefaults.mode === constants_1.STANDALONE_MODE && !withDefaults.dataLoader) {
|
|
131
|
-
throw new Error('To use `onlySubmitters` param in standalone mode, DataLoader is required to preload data into the storage');
|
|
132
|
-
}
|
|
133
130
|
// ensure a valid Storage based on mode defined.
|
|
134
131
|
// @ts-ignore, modify readonly prop
|
|
135
132
|
if (storage)
|
|
@@ -172,13 +169,9 @@ function settingsValidation(config, validationParams) {
|
|
|
172
169
|
scheduler.pushRetryBackoffBase = fromSecondsToMillis(scheduler.pushRetryBackoffBase);
|
|
173
170
|
}
|
|
174
171
|
// validate sync enabled
|
|
175
|
-
if (withDefaults.sync.enabled !== false) {
|
|
172
|
+
if (withDefaults.sync.enabled !== false) { // @ts-ignore, modify readonly prop
|
|
176
173
|
withDefaults.sync.enabled = true;
|
|
177
174
|
}
|
|
178
|
-
// validate sync onlySubmitters
|
|
179
|
-
if (withDefaults.sync.onlySubmitters !== true) {
|
|
180
|
-
withDefaults.sync.onlySubmitters = false;
|
|
181
|
-
}
|
|
182
175
|
// validate the `splitFilters` settings and parse splits query
|
|
183
176
|
var splitFiltersValidation = (0, splitFilters_1.validateSplitFilters)(log, withDefaults.sync.splitFilters, withDefaults.mode);
|
|
184
177
|
withDefaults.sync.splitFilters = splitFiltersValidation.validFilters;
|
|
@@ -7,23 +7,24 @@ var logNameMapper = 'ga-to-split:mapper';
|
|
|
7
7
|
/**
|
|
8
8
|
* Provides a plugin to use with analytics.js, accounting for the possibility
|
|
9
9
|
* that the global command queue has been renamed or not yet defined.
|
|
10
|
-
* @param
|
|
11
|
-
* @param
|
|
10
|
+
* @param window Reference to global object.
|
|
11
|
+
* @param pluginName The plugin name identifier.
|
|
12
|
+
* @param pluginConstructor The plugin constructor function.
|
|
13
|
+
* @param log Logger instance.
|
|
14
|
+
* @param autoRequire If true, log error when auto-require script is not detected
|
|
12
15
|
*/
|
|
13
|
-
function providePlugin(pluginName, pluginConstructor) {
|
|
16
|
+
function providePlugin(window, pluginName, pluginConstructor, log, autoRequire) {
|
|
14
17
|
// get reference to global command queue. Init it if not defined yet.
|
|
15
|
-
// @ts-expect-error
|
|
16
18
|
var gaAlias = window.GoogleAnalyticsObject || 'ga';
|
|
17
19
|
window[gaAlias] = window[gaAlias] || function () {
|
|
18
|
-
|
|
19
|
-
for (var _i = 0; _i < arguments.length; _i++) {
|
|
20
|
-
args[_i] = arguments[_i];
|
|
21
|
-
}
|
|
22
|
-
(window[gaAlias].q = window[gaAlias].q || []).push(args);
|
|
20
|
+
(window[gaAlias].q = window[gaAlias].q || []).push(arguments);
|
|
23
21
|
};
|
|
24
22
|
// provides the plugin for use with analytics.js.
|
|
25
|
-
// @ts-expect-error
|
|
26
23
|
window[gaAlias]('provide', pluginName, pluginConstructor);
|
|
24
|
+
if (autoRequire && (!window[gaAlias].q || window[gaAlias].q.push === [].push)) {
|
|
25
|
+
// Expecting spy on ga.q push method but not found
|
|
26
|
+
log.error(logPrefix + 'integration is configured to autorequire the splitTracker plugin, but the necessary script does not seem to have run.');
|
|
27
|
+
}
|
|
27
28
|
}
|
|
28
29
|
// Default mapping: object used for building the default mapper from hits to Split events
|
|
29
30
|
var defaultMapping = {
|
|
@@ -240,5 +241,5 @@ export function GaToSplit(sdkOptions, params) {
|
|
|
240
241
|
return SplitTracker;
|
|
241
242
|
}());
|
|
242
243
|
// Register the plugin, even if config is invalid, since, if not provided, it will block `ga` command queue.
|
|
243
|
-
providePlugin('splitTracker', SplitTracker);
|
|
244
|
+
providePlugin(window, 'splitTracker', SplitTracker, log, sdkOptions.autoRequire);
|
|
244
245
|
}
|
package/esm/sdkFactory/index.js
CHANGED
|
@@ -8,7 +8,7 @@ import { validateAndTrackApiKey } from '../utils/inputValidation/apiKey';
|
|
|
8
8
|
import { createLoggerAPI } from '../logger/sdkLogger';
|
|
9
9
|
import { NEW_FACTORY, RETRIEVE_MANAGER } from '../logger/constants';
|
|
10
10
|
import { metadataBuilder } from '../storages/metadataBuilder';
|
|
11
|
-
import { SDK_SPLITS_ARRIVED, SDK_SEGMENTS_ARRIVED
|
|
11
|
+
import { SDK_SPLITS_ARRIVED, SDK_SEGMENTS_ARRIVED } from '../readiness/constants';
|
|
12
12
|
import { objectAssign } from '../utils/lang/objectAssign';
|
|
13
13
|
/**
|
|
14
14
|
* Modular SDK factory
|
|
@@ -22,14 +22,13 @@ export function sdkFactory(params) {
|
|
|
22
22
|
validateAndTrackApiKey(log, settings.core.authorizationKey);
|
|
23
23
|
var sdkReadinessManager = sdkReadinessManagerFactory(log, platform.EventEmitter, settings.startup.readyTimeout);
|
|
24
24
|
var readiness = sdkReadinessManager.readinessManager;
|
|
25
|
-
var matchingKey = getMatching(settings.core.key);
|
|
26
25
|
// @TODO consider passing the settings object, so that each storage access only what it needs
|
|
27
26
|
var storageFactoryParams = {
|
|
28
27
|
impressionsQueueSize: settings.scheduler.impressionsQueueSize,
|
|
29
28
|
eventsQueueSize: settings.scheduler.eventsQueueSize,
|
|
30
29
|
optimize: shouldBeOptimized(settings),
|
|
31
30
|
// ATM, only used by InLocalStorage
|
|
32
|
-
matchingKey:
|
|
31
|
+
matchingKey: getMatching(settings.core.key),
|
|
33
32
|
splitFiltersValidation: settings.sync.__splitFiltersValidation,
|
|
34
33
|
// ATM, only used by PluggableStorage
|
|
35
34
|
mode: settings.mode,
|
|
@@ -45,21 +44,7 @@ export function sdkFactory(params) {
|
|
|
45
44
|
log: log
|
|
46
45
|
};
|
|
47
46
|
var storage = storageFactory(storageFactoryParams);
|
|
48
|
-
// @TODO dataLoader
|
|
49
|
-
if (settings.dataLoader) {
|
|
50
|
-
settings.dataLoader(storage, matchingKey);
|
|
51
|
-
Promise.resolve(storage.splits.checkCache()).then(function (cacheReady) {
|
|
52
|
-
if (cacheReady) {
|
|
53
|
-
if (settings.sync.onlySubmitters) { // emit SDK_READY to not timeout when not synchronizing splits & segments
|
|
54
|
-
readiness.splits.emit(SDK_SPLITS_ARRIVED);
|
|
55
|
-
readiness.segments.emit(SDK_SEGMENTS_ARRIVED);
|
|
56
|
-
}
|
|
57
|
-
else { // emit SDK_READY_FROM_CACHE
|
|
58
|
-
readiness.splits.emit(SDK_SPLITS_CACHE_LOADED);
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
});
|
|
62
|
-
}
|
|
47
|
+
// @TODO add support for dataloader: `if (params.dataLoader) params.dataLoader(storage);`
|
|
63
48
|
var integrationsManager = integrationsManagerFactory && integrationsManagerFactory({ settings: settings, storage: storage });
|
|
64
49
|
// trackers
|
|
65
50
|
var observer = impressionsObserverFactory && impressionsObserverFactory();
|
|
@@ -91,8 +76,5 @@ export function sdkFactory(params) {
|
|
|
91
76
|
// Logger wrapper API
|
|
92
77
|
Logger: createLoggerAPI(settings.log),
|
|
93
78
|
settings: settings,
|
|
94
|
-
// @TODO remove
|
|
95
|
-
__storage: storage,
|
|
96
|
-
__ctx: ctx
|
|
97
79
|
}, extraProps && extraProps(ctx));
|
|
98
80
|
}
|
|
@@ -30,7 +30,7 @@ var AbstractSplitsCacheSync = /** @class */ (function () {
|
|
|
30
30
|
* It is used as condition to emit SDK_SPLITS_CACHE_LOADED, and then SDK_READY_FROM_CACHE.
|
|
31
31
|
*/
|
|
32
32
|
AbstractSplitsCacheSync.prototype.checkCache = function () {
|
|
33
|
-
return
|
|
33
|
+
return false;
|
|
34
34
|
};
|
|
35
35
|
/**
|
|
36
36
|
* Kill `name` split and set `defaultTreatment` and `changeNumber`.
|
|
@@ -1,24 +1,23 @@
|
|
|
1
1
|
import { DEFAULT_CACHE_EXPIRATION_IN_MILLIS } from '../utils/constants/browser';
|
|
2
2
|
/**
|
|
3
|
-
* Factory of storage loader
|
|
3
|
+
* Factory of client-side 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 userId user key string of the provided MySegmentsCache
|
|
16
16
|
*
|
|
17
|
+
* @TODO extend to support SegmentsCache (server-side variant) by making `userId` optional and adding the corresponding logic.
|
|
17
18
|
* @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
|
|
20
19
|
*/
|
|
21
|
-
return function loadData(storage,
|
|
20
|
+
return function loadData(storage, userId) {
|
|
22
21
|
// Do not load data if current preloadedData is empty
|
|
23
22
|
if (Object.keys(preloadedData).length === 0)
|
|
24
23
|
return;
|
|
@@ -34,22 +33,15 @@ export function DataLoaderFactory(preloadedData) {
|
|
|
34
33
|
storage.splits.setChangeNumber(since);
|
|
35
34
|
// splitsData in an object where the property is the split name and the pertaining value is a stringified json of its data
|
|
36
35
|
storage.splits.addSplits(Object.keys(splitsData).map(function (splitName) { return [splitName, splitsData[splitName]]; }));
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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);
|
|
36
|
+
// add mySegments data
|
|
37
|
+
var mySegmentsData = preloadedData.mySegmentsData && preloadedData.mySegmentsData[userId];
|
|
38
|
+
if (!mySegmentsData) {
|
|
39
|
+
// 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
|
|
40
|
+
mySegmentsData = Object.keys(segmentsData).filter(function (segmentName) {
|
|
41
|
+
var userIds = JSON.parse(segmentsData[segmentName]).added;
|
|
42
|
+
return Array.isArray(userIds) && userIds.indexOf(userId) > -1;
|
|
52
43
|
});
|
|
53
44
|
}
|
|
45
|
+
storage.segments.resetSegments(mySegmentsData);
|
|
54
46
|
};
|
|
55
47
|
}
|
|
@@ -5,7 +5,6 @@ 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';
|
|
9
8
|
/**
|
|
10
9
|
* InMemory storage factory for standalone server-side SplitFactory
|
|
11
10
|
*
|
|
@@ -26,20 +25,7 @@ export function InMemoryStorageFactory(params) {
|
|
|
26
25
|
this.impressions.clear();
|
|
27
26
|
this.impressionCounts && this.impressionCounts.clear();
|
|
28
27
|
this.events.clear();
|
|
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
|
-
},
|
|
28
|
+
}
|
|
43
29
|
};
|
|
44
30
|
}
|
|
45
31
|
InMemoryStorageFactory.type = STORAGE_MEMORY;
|
|
@@ -26,18 +26,6 @@ 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
|
-
},
|
|
41
29
|
// When using shared instanciation with MEMORY we reuse everything but segments (they are unique per key)
|
|
42
30
|
shared: function () {
|
|
43
31
|
return {
|
|
@@ -16,9 +16,9 @@ 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,
|
|
19
|
+
var settings = params.settings, _a = params.settings, log = _a.log, streamingEnabled = _a.streamingEnabled, syncEnabled = _a.sync.enabled, telemetryTracker = params.telemetryTracker;
|
|
20
20
|
/** Polling Manager */
|
|
21
|
-
var pollingManager =
|
|
21
|
+
var pollingManager = pollingManagerFactory && pollingManagerFactory(params);
|
|
22
22
|
/** Push Manager */
|
|
23
23
|
var pushManager = syncEnabled && streamingEnabled && pollingManager && pushManagerFactory ?
|
|
24
24
|
pushManagerFactory(params, pollingManager) :
|
|
@@ -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));
|
|
6
6
|
}
|
|
7
7
|
var LAST_SEEN_CACHE_SIZE = 500; // cache up to 500 impression hashes
|
|
8
8
|
export function impressionObserverCSFactory() {
|
|
@@ -124,9 +124,6 @@ export function settingsValidation(config, validationParams) {
|
|
|
124
124
|
// ensure a valid SDK mode
|
|
125
125
|
// @ts-ignore, modify readonly prop
|
|
126
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
|
-
}
|
|
130
127
|
// ensure a valid Storage based on mode defined.
|
|
131
128
|
// @ts-ignore, modify readonly prop
|
|
132
129
|
if (storage)
|
|
@@ -169,13 +166,9 @@ export function settingsValidation(config, validationParams) {
|
|
|
169
166
|
scheduler.pushRetryBackoffBase = fromSecondsToMillis(scheduler.pushRetryBackoffBase);
|
|
170
167
|
}
|
|
171
168
|
// validate sync enabled
|
|
172
|
-
if (withDefaults.sync.enabled !== false) {
|
|
169
|
+
if (withDefaults.sync.enabled !== false) { // @ts-ignore, modify readonly prop
|
|
173
170
|
withDefaults.sync.enabled = true;
|
|
174
171
|
}
|
|
175
|
-
// validate sync onlySubmitters
|
|
176
|
-
if (withDefaults.sync.onlySubmitters !== true) {
|
|
177
|
-
withDefaults.sync.onlySubmitters = false;
|
|
178
|
-
}
|
|
179
172
|
// validate the `splitFilters` settings and parse splits query
|
|
180
173
|
var splitFiltersValidation = validateSplitFilters(log, withDefaults.sync.splitFilters, withDefaults.mode);
|
|
181
174
|
withDefaults.sync.splitFilters = splitFiltersValidation.validFilters;
|
package/package.json
CHANGED
|
@@ -19,20 +19,26 @@ const logNameMapper = 'ga-to-split:mapper';
|
|
|
19
19
|
/**
|
|
20
20
|
* Provides a plugin to use with analytics.js, accounting for the possibility
|
|
21
21
|
* that the global command queue has been renamed or not yet defined.
|
|
22
|
-
* @param
|
|
23
|
-
* @param
|
|
22
|
+
* @param window Reference to global object.
|
|
23
|
+
* @param pluginName The plugin name identifier.
|
|
24
|
+
* @param pluginConstructor The plugin constructor function.
|
|
25
|
+
* @param log Logger instance.
|
|
26
|
+
* @param autoRequire If true, log error when auto-require script is not detected
|
|
24
27
|
*/
|
|
25
|
-
function providePlugin(pluginName: string, pluginConstructor: Function) {
|
|
28
|
+
function providePlugin(window: any, pluginName: string, pluginConstructor: Function, log: ILogger, autoRequire?: boolean) {
|
|
26
29
|
// get reference to global command queue. Init it if not defined yet.
|
|
27
|
-
// @ts-expect-error
|
|
28
30
|
const gaAlias = window.GoogleAnalyticsObject || 'ga';
|
|
29
|
-
window[gaAlias] = window[gaAlias] || function (
|
|
30
|
-
(window[gaAlias].q = window[gaAlias].q || []).push(
|
|
31
|
+
window[gaAlias] = window[gaAlias] || function () {
|
|
32
|
+
(window[gaAlias].q = window[gaAlias].q || []).push(arguments);
|
|
31
33
|
};
|
|
32
34
|
|
|
33
35
|
// provides the plugin for use with analytics.js.
|
|
34
|
-
// @ts-expect-error
|
|
35
36
|
window[gaAlias]('provide', pluginName, pluginConstructor);
|
|
37
|
+
|
|
38
|
+
if (autoRequire && (!window[gaAlias].q || window[gaAlias].q.push === [].push)) {
|
|
39
|
+
// Expecting spy on ga.q push method but not found
|
|
40
|
+
log.error(logPrefix + 'integration is configured to autorequire the splitTracker plugin, but the necessary script does not seem to have run.');
|
|
41
|
+
}
|
|
36
42
|
}
|
|
37
43
|
|
|
38
44
|
// Default mapping: object used for building the default mapper from hits to Split events
|
|
@@ -284,5 +290,5 @@ export function GaToSplit(sdkOptions: GoogleAnalyticsToSplitOptions, params: IIn
|
|
|
284
290
|
}
|
|
285
291
|
|
|
286
292
|
// Register the plugin, even if config is invalid, since, if not provided, it will block `ga` command queue.
|
|
287
|
-
providePlugin('splitTracker', SplitTracker);
|
|
293
|
+
providePlugin(window, 'splitTracker', SplitTracker, log, sdkOptions.autoRequire);
|
|
288
294
|
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/* eslint-disable no-undef */
|
|
2
|
+
/**
|
|
3
|
+
* Auto-require script to use with GoogleAnalyticsToSplit integration
|
|
4
|
+
*/
|
|
5
|
+
(function (i, r, s) {
|
|
6
|
+
i[s] = i[s] || r;
|
|
7
|
+
i[r] = i[r] || function () { i[r].q.push(arguments); };
|
|
8
|
+
i[r].q = i[r].q || [];
|
|
9
|
+
|
|
10
|
+
var ts = {}; // Tracker names
|
|
11
|
+
function n(arg) { return typeof arg === 'object' && typeof arg.name === 'string' && arg.name; }
|
|
12
|
+
|
|
13
|
+
function p(v) { // Queue a `require` command if v is a `create` command
|
|
14
|
+
if (v && v[0] === 'create') {
|
|
15
|
+
var t = n(v[1]) || n(v[2]) || n(v[3]) || (typeof v[3] === 'string' ? v[3] : undefined); // Get tracker name
|
|
16
|
+
|
|
17
|
+
if (!ts[t]) {
|
|
18
|
+
ts[t] = true;
|
|
19
|
+
i[r]((t ? t + '.' : '') + 'require', 'splitTracker'); // Auto-require
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
i[r].q.forEach(function (v) { p(v); }); // Process already queued commands
|
|
25
|
+
|
|
26
|
+
var o = i[r].q.push;
|
|
27
|
+
i[r].q.push = function (v) { // Spy new queued commands
|
|
28
|
+
var result = o.apply(this, arguments);
|
|
29
|
+
p(v);
|
|
30
|
+
return result;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
})(window, 'ga', 'GoogleAnalyticsObject');
|
|
@@ -53,13 +53,24 @@ export interface GoogleAnalyticsToSplitOptions {
|
|
|
53
53
|
* If not provided, events are sent using the key and traffic type provided at SDK config
|
|
54
54
|
*/
|
|
55
55
|
identities?: Identity[],
|
|
56
|
+
/**
|
|
57
|
+
* Optional flag to log an error if the `auto-require` script is not detected.
|
|
58
|
+
* The auto-require script automatically requires the `splitTracker` plugin for created trackers,
|
|
59
|
+
* and should be placed right after your Google Analytics, GTM or gtag.js script tag.
|
|
60
|
+
*
|
|
61
|
+
* @see {@link https://help.split.io/hc/en-us/articles/360040838752#google-tag-manager}
|
|
62
|
+
*
|
|
63
|
+
* @property {boolean} autoRequire
|
|
64
|
+
* @default false
|
|
65
|
+
*/
|
|
66
|
+
autoRequire?: boolean,
|
|
56
67
|
}
|
|
57
68
|
|
|
58
69
|
/**
|
|
59
70
|
* Enable 'Google Analytics to Split' integration, to track Google Analytics hits as Split events.
|
|
60
71
|
* Used by the browser variant of the isomorphic JS SDK.
|
|
61
72
|
*
|
|
62
|
-
* @see {@link https://help.split.io/hc/en-us/articles/
|
|
73
|
+
* @see {@link https://help.split.io/hc/en-us/articles/360040838752#google-analytics-to-split}
|
|
63
74
|
*/
|
|
64
75
|
export interface IGoogleAnalyticsToSplitConfig extends GoogleAnalyticsToSplitOptions {
|
|
65
76
|
type: 'GOOGLE_ANALYTICS_TO_SPLIT'
|
|
@@ -129,7 +140,7 @@ export interface SplitToGoogleAnalyticsOptions {
|
|
|
129
140
|
* Enable 'Split to Google Analytics' integration, to track Split impressions and events as Google Analytics hits.
|
|
130
141
|
* Used by the browser variant of the isomorphic JS SDK.
|
|
131
142
|
*
|
|
132
|
-
* @see {@link https://help.split.io/hc/en-us/articles/
|
|
143
|
+
* @see {@link https://help.split.io/hc/en-us/articles/360040838752#split-to-google-analytics}
|
|
133
144
|
*/
|
|
134
145
|
export interface ISplitToGoogleAnalyticsConfig extends SplitToGoogleAnalyticsOptions {
|
|
135
146
|
type: 'SPLIT_TO_GOOGLE_ANALYTICS'
|
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
|
|
6
|
+
import { IStorageFactoryParams } 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
|
|
14
|
+
import { SDK_SPLITS_ARRIVED, SDK_SEGMENTS_ARRIVED } from '../readiness/constants';
|
|
15
15
|
import { objectAssign } from '../utils/lang/objectAssign';
|
|
16
16
|
|
|
17
17
|
/**
|
|
@@ -32,7 +32,6 @@ 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);
|
|
36
35
|
|
|
37
36
|
// @TODO consider passing the settings object, so that each storage access only what it needs
|
|
38
37
|
const storageFactoryParams: IStorageFactoryParams = {
|
|
@@ -41,7 +40,7 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ICsSDK | SplitIO.
|
|
|
41
40
|
optimize: shouldBeOptimized(settings),
|
|
42
41
|
|
|
43
42
|
// ATM, only used by InLocalStorage
|
|
44
|
-
matchingKey,
|
|
43
|
+
matchingKey: getMatching(settings.core.key),
|
|
45
44
|
splitFiltersValidation: settings.sync.__splitFiltersValidation,
|
|
46
45
|
|
|
47
46
|
// ATM, only used by PluggableStorage
|
|
@@ -59,21 +58,7 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ICsSDK | SplitIO.
|
|
|
59
58
|
};
|
|
60
59
|
|
|
61
60
|
const storage = storageFactory(storageFactoryParams);
|
|
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
|
-
}
|
|
61
|
+
// @TODO add support for dataloader: `if (params.dataLoader) params.dataLoader(storage);`
|
|
77
62
|
|
|
78
63
|
const integrationsManager = integrationsManagerFactory && integrationsManagerFactory({ settings, storage });
|
|
79
64
|
|
|
@@ -118,9 +103,5 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ICsSDK | SplitIO.
|
|
|
118
103
|
Logger: createLoggerAPI(settings.log),
|
|
119
104
|
|
|
120
105
|
settings,
|
|
121
|
-
|
|
122
|
-
// @TODO remove
|
|
123
|
-
__storage: storage,
|
|
124
|
-
__ctx: ctx
|
|
125
106
|
}, extraProps && extraProps(ctx));
|
|
126
107
|
}
|
|
@@ -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 false;
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
/**
|
|
@@ -3,26 +3,25 @@ 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 storage loader
|
|
6
|
+
* Factory of client-side 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 userId user key string of the provided MySegmentsCache
|
|
20
20
|
*
|
|
21
|
+
* @TODO extend to support SegmentsCache (server-side variant) by making `userId` optional and adding the corresponding logic.
|
|
21
22
|
* @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
|
|
24
23
|
*/
|
|
25
|
-
return function loadData(storage: { splits: ISplitsCacheSync, segments: ISegmentsCacheSync },
|
|
24
|
+
return function loadData(storage: { splits: ISplitsCacheSync, segments: ISegmentsCacheSync }, userId: string) {
|
|
26
25
|
// Do not load data if current preloadedData is empty
|
|
27
26
|
if (Object.keys(preloadedData).length === 0) return;
|
|
28
27
|
|
|
@@ -42,21 +41,15 @@ export function DataLoaderFactory(preloadedData: SplitIO.PreloadedData): DataLoa
|
|
|
42
41
|
// splitsData in an object where the property is the split name and the pertaining value is a stringified json of its data
|
|
43
42
|
storage.splits.addSplits(Object.keys(splitsData).map(splitName => [splitName, splitsData[splitName]]));
|
|
44
43
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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);
|
|
44
|
+
// add mySegments data
|
|
45
|
+
let mySegmentsData = preloadedData.mySegmentsData && preloadedData.mySegmentsData[userId];
|
|
46
|
+
if (!mySegmentsData) {
|
|
47
|
+
// 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
|
|
48
|
+
mySegmentsData = Object.keys(segmentsData).filter(segmentName => {
|
|
49
|
+
const userIds = JSON.parse(segmentsData[segmentName]).added;
|
|
50
|
+
return Array.isArray(userIds) && userIds.indexOf(userId) > -1;
|
|
59
51
|
});
|
|
60
52
|
}
|
|
53
|
+
storage.segments.resetSegments(mySegmentsData);
|
|
61
54
|
};
|
|
62
55
|
}
|
|
@@ -6,8 +6,6 @@ import { IStorageFactoryParams, IStorageSync } from '../types';
|
|
|
6
6
|
import { ImpressionCountsCacheInMemory } from './ImpressionCountsCacheInMemory';
|
|
7
7
|
import { LOCALHOST_MODE, STORAGE_MEMORY } from '../../utils/constants';
|
|
8
8
|
import { TelemetryCacheInMemory } from './TelemetryCacheInMemory';
|
|
9
|
-
import { SplitIO } from '../../types';
|
|
10
|
-
import { setToArray, ISet } from '../../utils/lang/sets';
|
|
11
9
|
|
|
12
10
|
/**
|
|
13
11
|
* InMemory storage factory for standalone server-side SplitFactory
|
|
@@ -31,20 +29,7 @@ export function InMemoryStorageFactory(params: IStorageFactoryParams): IStorageS
|
|
|
31
29
|
this.impressions.clear();
|
|
32
30
|
this.impressionCounts && this.impressionCounts.clear();
|
|
33
31
|
this.events.clear();
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// @ts-ignore, private method, for POC
|
|
37
|
-
getSnapshot(): SplitIO.PreloadedData {
|
|
38
|
-
return {
|
|
39
|
-
lastUpdated: Date.now(), // @ts-ignore accessing private prop
|
|
40
|
-
since: this.splits.changeNumber, // @ts-ignore accessing private prop
|
|
41
|
-
splitsData: this.splits.splitsCache, // @ts-ignore accessing private prop
|
|
42
|
-
segmentsData: Object.keys(this.segments.segmentCache).reduce((prev, cur) => { // @ts-ignore accessing private prop
|
|
43
|
-
prev[cur] = setToArray(this.segments.segmentCache[cur] as ISet<string>);
|
|
44
|
-
return prev;
|
|
45
|
-
}, {})
|
|
46
|
-
};
|
|
47
|
-
},
|
|
32
|
+
}
|
|
48
33
|
};
|
|
49
34
|
}
|
|
50
35
|
|
|
@@ -6,7 +6,6 @@ import { IStorageSync, IStorageFactoryParams } from '../types';
|
|
|
6
6
|
import { ImpressionCountsCacheInMemory } from './ImpressionCountsCacheInMemory';
|
|
7
7
|
import { LOCALHOST_MODE, STORAGE_MEMORY } from '../../utils/constants';
|
|
8
8
|
import { shouldRecordTelemetry, TelemetryCacheInMemory } from './TelemetryCacheInMemory';
|
|
9
|
-
import { SplitIO } from '../../types';
|
|
10
9
|
|
|
11
10
|
/**
|
|
12
11
|
* InMemory storage factory for standalone client-side SplitFactory
|
|
@@ -32,18 +31,6 @@ export function InMemoryStorageCSFactory(params: IStorageFactoryParams): IStorag
|
|
|
32
31
|
this.events.clear();
|
|
33
32
|
},
|
|
34
33
|
|
|
35
|
-
// @ts-ignore, private method, for POC
|
|
36
|
-
getSnapshot(): SplitIO.PreloadedData {
|
|
37
|
-
return {
|
|
38
|
-
lastUpdated: Date.now(), // @ts-ignore accessing private prop
|
|
39
|
-
since: this.splits.changeNumber, // @ts-ignore accessing private prop
|
|
40
|
-
splitsData: this.splits.splitsCache,
|
|
41
|
-
mySegmentsData: { // @ts-ignore accessing private prop
|
|
42
|
-
[params.matchingKey as string]: Object.keys(this.segments.segmentCache)
|
|
43
|
-
}
|
|
44
|
-
};
|
|
45
|
-
},
|
|
46
|
-
|
|
47
34
|
// When using shared instanciation with MEMORY we reuse everything but segments (they are unique per key)
|
|
48
35
|
shared() {
|
|
49
36
|
return {
|
|
@@ -28,10 +28,10 @@ export function syncManagerOnlineFactory(
|
|
|
28
28
|
*/
|
|
29
29
|
return function (params: ISdkFactoryContextSync): ISyncManagerCS {
|
|
30
30
|
|
|
31
|
-
const { settings, settings: { log, streamingEnabled, sync: { enabled: syncEnabled
|
|
31
|
+
const { settings, settings: { log, streamingEnabled, sync: { enabled: syncEnabled } }, telemetryTracker } = params;
|
|
32
32
|
|
|
33
33
|
/** Polling Manager */
|
|
34
|
-
const pollingManager =
|
|
34
|
+
const pollingManager = pollingManagerFactory && pollingManagerFactory(params);
|
|
35
35
|
|
|
36
36
|
/** Push Manager */
|
|
37
37
|
const pushManager = syncEnabled && streamingEnabled && pollingManager && pushManagerFactory ?
|
|
@@ -4,7 +4,7 @@ import { buildKey } from './buildKey';
|
|
|
4
4
|
import { ImpressionDTO } from '../../types';
|
|
5
5
|
|
|
6
6
|
export function hashImpression32(impression: ImpressionDTO) {
|
|
7
|
-
return hash(buildKey(impression))
|
|
7
|
+
return hash(buildKey(impression));
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
const LAST_SEEN_CACHE_SIZE = 500; // cache up to 500 impression hashes
|
package/src/types.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { ILogger } from './logger/types';
|
|
|
4
4
|
import { ISdkFactoryContext } from './sdkFactory/types';
|
|
5
5
|
/* eslint-disable no-use-before-define */
|
|
6
6
|
|
|
7
|
-
import { IStorageFactoryParams, IStorageSync, IStorageAsync, IStorageSyncFactory, IStorageAsyncFactory
|
|
7
|
+
import { IStorageFactoryParams, IStorageSync, IStorageAsync, IStorageSyncFactory, IStorageAsyncFactory } from './storages/types';
|
|
8
8
|
import { ISyncManagerCS } from './sync/types';
|
|
9
9
|
|
|
10
10
|
/**
|
|
@@ -98,7 +98,6 @@ export interface ISettings {
|
|
|
98
98
|
eventsFirstPushWindow: number
|
|
99
99
|
},
|
|
100
100
|
readonly storage: IStorageSyncFactory | IStorageAsyncFactory,
|
|
101
|
-
readonly dataLoader?: DataLoader,
|
|
102
101
|
readonly integrations: Array<{
|
|
103
102
|
readonly type: string,
|
|
104
103
|
(params: IIntegrationFactoryParams): IIntegration | void
|
|
@@ -119,8 +118,7 @@ export interface ISettings {
|
|
|
119
118
|
impressionsMode: SplitIO.ImpressionsMode,
|
|
120
119
|
__splitFiltersValidation: ISplitFiltersValidation,
|
|
121
120
|
localhostMode?: SplitIO.LocalhostFactory,
|
|
122
|
-
enabled: boolean
|
|
123
|
-
onlySubmitters: boolean
|
|
121
|
+
enabled: boolean
|
|
124
122
|
},
|
|
125
123
|
readonly runtime: {
|
|
126
124
|
ip: string | false
|
|
@@ -754,7 +752,7 @@ export namespace SplitIO {
|
|
|
754
752
|
* This property is ignored if `mySegmentsData` was provided.
|
|
755
753
|
*/
|
|
756
754
|
segmentsData?: {
|
|
757
|
-
[segmentName: string]: string
|
|
755
|
+
[segmentName: string]: string
|
|
758
756
|
},
|
|
759
757
|
}
|
|
760
758
|
/**
|
|
@@ -147,9 +147,6 @@ export function settingsValidation(config: unknown, validationParams: ISettingsV
|
|
|
147
147
|
// ensure a valid SDK mode
|
|
148
148
|
// @ts-ignore, modify readonly prop
|
|
149
149
|
withDefaults.mode = mode(withDefaults.core.authorizationKey, withDefaults.mode);
|
|
150
|
-
if (withDefaults.sync.onlySubmitters && withDefaults.mode === STANDALONE_MODE && !withDefaults.dataLoader) {
|
|
151
|
-
throw new Error('To use `onlySubmitters` param in standalone mode, DataLoader is required to preload data into the storage');
|
|
152
|
-
}
|
|
153
150
|
|
|
154
151
|
// ensure a valid Storage based on mode defined.
|
|
155
152
|
// @ts-ignore, modify readonly prop
|
|
@@ -196,15 +193,10 @@ export function settingsValidation(config: unknown, validationParams: ISettingsV
|
|
|
196
193
|
}
|
|
197
194
|
|
|
198
195
|
// validate sync enabled
|
|
199
|
-
if (withDefaults.sync.enabled !== false) {
|
|
196
|
+
if (withDefaults.sync.enabled !== false) { // @ts-ignore, modify readonly prop
|
|
200
197
|
withDefaults.sync.enabled = true;
|
|
201
198
|
}
|
|
202
199
|
|
|
203
|
-
// validate sync onlySubmitters
|
|
204
|
-
if (withDefaults.sync.onlySubmitters !== true) {
|
|
205
|
-
withDefaults.sync.onlySubmitters = false;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
200
|
// validate the `splitFilters` settings and parse splits query
|
|
209
201
|
const splitFiltersValidation = validateSplitFilters(log, withDefaults.sync.splitFilters, withDefaults.mode);
|
|
210
202
|
withDefaults.sync.splitFilters = splitFiltersValidation.validFilters;
|
|
@@ -52,12 +52,23 @@ export interface GoogleAnalyticsToSplitOptions {
|
|
|
52
52
|
* If not provided, events are sent using the key and traffic type provided at SDK config
|
|
53
53
|
*/
|
|
54
54
|
identities?: Identity[];
|
|
55
|
+
/**
|
|
56
|
+
* Optional flag to log an error if the `auto-require` script is not detected.
|
|
57
|
+
* The auto-require script automatically requires the `splitTracker` plugin for created trackers,
|
|
58
|
+
* and should be placed right after your Google Analytics, GTM or gtag.js script tag.
|
|
59
|
+
*
|
|
60
|
+
* @see {@link https://help.split.io/hc/en-us/articles/360040838752#google-tag-manager}
|
|
61
|
+
*
|
|
62
|
+
* @property {boolean} autoRequire
|
|
63
|
+
* @default false
|
|
64
|
+
*/
|
|
65
|
+
autoRequire?: boolean;
|
|
55
66
|
}
|
|
56
67
|
/**
|
|
57
68
|
* Enable 'Google Analytics to Split' integration, to track Google Analytics hits as Split events.
|
|
58
69
|
* Used by the browser variant of the isomorphic JS SDK.
|
|
59
70
|
*
|
|
60
|
-
* @see {@link https://help.split.io/hc/en-us/articles/
|
|
71
|
+
* @see {@link https://help.split.io/hc/en-us/articles/360040838752#google-analytics-to-split}
|
|
61
72
|
*/
|
|
62
73
|
export interface IGoogleAnalyticsToSplitConfig extends GoogleAnalyticsToSplitOptions {
|
|
63
74
|
type: 'GOOGLE_ANALYTICS_TO_SPLIT';
|
|
@@ -125,7 +136,7 @@ export interface SplitToGoogleAnalyticsOptions {
|
|
|
125
136
|
* Enable 'Split to Google Analytics' integration, to track Split impressions and events as Google Analytics hits.
|
|
126
137
|
* Used by the browser variant of the isomorphic JS SDK.
|
|
127
138
|
*
|
|
128
|
-
* @see {@link https://help.split.io/hc/en-us/articles/
|
|
139
|
+
* @see {@link https://help.split.io/hc/en-us/articles/360040838752#split-to-google-analytics}
|
|
129
140
|
*/
|
|
130
141
|
export interface ISplitToGoogleAnalyticsConfig extends SplitToGoogleAnalyticsOptions {
|
|
131
142
|
type: 'SPLIT_TO_GOOGLE_ANALYTICS';
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { SplitIO } from '../types';
|
|
2
2
|
import { DataLoader } from './types';
|
|
3
3
|
/**
|
|
4
|
-
* Factory of storage loader
|
|
4
|
+
* Factory of client-side storage loader
|
|
5
5
|
*
|
|
6
6
|
* @param preloadedData validated data following the format proposed in https://github.com/godaddy/split-javascript-data-loader
|
|
7
7
|
* and extended with a `mySegmentsData` property.
|
|
8
8
|
* @returns function to preload the storage
|
|
9
9
|
*/
|
|
10
|
-
export declare function
|
|
10
|
+
export declare function dataLoaderFactory(preloadedData: SplitIO.PreloadedData): DataLoader;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import { ImpressionObserver } from './ImpressionObserver';
|
|
2
2
|
import { ImpressionDTO } from '../../types';
|
|
3
|
-
export declare function hashImpression32(impression: ImpressionDTO):
|
|
4
|
-
export declare function impressionObserverCSFactory(): ImpressionObserver<
|
|
3
|
+
export declare function hashImpression32(impression: ImpressionDTO): number;
|
|
4
|
+
export declare function impressionObserverCSFactory(): ImpressionObserver<number>;
|
package/types/types.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { ISplitFiltersValidation } from './dtos/types';
|
|
|
2
2
|
import { IIntegration, IIntegrationFactoryParams } from './integrations/types';
|
|
3
3
|
import { ILogger } from './logger/types';
|
|
4
4
|
import { ISdkFactoryContext } from './sdkFactory/types';
|
|
5
|
-
import { IStorageFactoryParams, IStorageSync, IStorageAsync, IStorageSyncFactory, IStorageAsyncFactory
|
|
5
|
+
import { IStorageFactoryParams, IStorageSync, IStorageAsync, IStorageSyncFactory, IStorageAsyncFactory } from './storages/types';
|
|
6
6
|
import { ISyncManagerCS } from './sync/types';
|
|
7
7
|
/**
|
|
8
8
|
* Reduced version of NodeJS.EventEmitter interface with the minimal methods used by the SDK
|
|
@@ -92,7 +92,6 @@ export interface ISettings {
|
|
|
92
92
|
eventsFirstPushWindow: number;
|
|
93
93
|
};
|
|
94
94
|
readonly storage: IStorageSyncFactory | IStorageAsyncFactory;
|
|
95
|
-
readonly dataLoader?: DataLoader;
|
|
96
95
|
readonly integrations: Array<{
|
|
97
96
|
readonly type: string;
|
|
98
97
|
(params: IIntegrationFactoryParams): IIntegration | void;
|
|
@@ -114,7 +113,6 @@ export interface ISettings {
|
|
|
114
113
|
__splitFiltersValidation: ISplitFiltersValidation;
|
|
115
114
|
localhostMode?: SplitIO.LocalhostFactory;
|
|
116
115
|
enabled: boolean;
|
|
117
|
-
onlySubmitters: boolean;
|
|
118
116
|
};
|
|
119
117
|
readonly runtime: {
|
|
120
118
|
ip: string | false;
|
|
@@ -751,7 +749,7 @@ export declare namespace SplitIO {
|
|
|
751
749
|
* This property is ignored if `mySegmentsData` was provided.
|
|
752
750
|
*/
|
|
753
751
|
segmentsData?: {
|
|
754
|
-
[segmentName: string]: string
|
|
752
|
+
[segmentName: string]: string;
|
|
755
753
|
};
|
|
756
754
|
}
|
|
757
755
|
/**
|