@splitsoftware/splitio 10.20.1-rc.1 → 10.21.1-rc.0
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 +10 -0
- package/es/factory/browser.js +1 -2
- package/es/factory/node.js +3 -2
- package/es/platform/filter/bloomFilter.js +25 -0
- package/es/settings/defaults/version.js +1 -1
- package/es/settings/node.js +1 -1
- package/es/sync/offline/LocalhostFromFile.js +9 -0
- package/es/sync/offline/splitsParserFromFile.js +142 -0
- package/lib/factory/browser.js +1 -2
- package/lib/factory/node.js +3 -2
- package/lib/platform/filter/bloomFilter.js +29 -0
- package/lib/settings/defaults/version.js +1 -1
- package/lib/settings/node.js +1 -1
- package/lib/sync/offline/LocalhostFromFile.js +13 -0
- package/lib/sync/offline/splitsParserFromFile.js +147 -0
- package/package.json +24 -21
- package/scripts/ga-to-split-autorequire.js +1 -0
- package/src/.DS_Store +0 -0
- package/src/factory/browser.js +1 -2
- package/src/factory/node.js +4 -2
- package/src/platform/filter/bloomFilter.js +30 -0
- package/src/settings/defaults/version.js +1 -1
- package/src/settings/node.js +1 -1
- package/src/{settings → storage}/.DS_Store +0 -0
- package/src/sync/offline/LocalhostFromFile.js +11 -0
- package/src/sync/offline/splitsParserFromFile.js +172 -0
- package/types/splitio.d.ts +25 -3
- package/src/platform/.DS_Store +0 -0
package/CHANGES.txt
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
10.21.1 (July 25, 2022)
|
|
2
|
+
- Bugfixing - Added missed type definitions `enabled` from `sync`.
|
|
3
|
+
|
|
4
|
+
10.21.0 (July 22, 2022)
|
|
5
|
+
- Added `autoRequire` configuration option to the Google Analytics to Split integration, which takes care of requiring the splitTracker plugin on trackers dynamically created by Google tag managers (See https://help.split.io/hc/en-us/articles/360040838752#set-up-with-gtm-and-gtag.js).
|
|
6
|
+
- Updated browser listener to push remaining impressions and events on 'visibilitychange' and 'pagehide' DOM events, instead of 'unload', which is not reliable in modern mobile and desktop Web browsers.
|
|
7
|
+
- Updated the synchronization flow to be more reliable in the event of an edge case generating delay in cache purge propagation, keeping the SDK cache properly synced.
|
|
8
|
+
- Updated some dependencies for vulnerability fixes.
|
|
9
|
+
- Bugfixing - Moved js-yaml dependency from @splitsoftware/splitio-commons to avoid resolution to an incompatible version on certain npm versions when installing third-party dependencies that also define js-yaml as transitive dependency (Related to issue https://github.com/splitio/javascript-client/issues/662).
|
|
10
|
+
|
|
1
11
|
10.20.0 (June 29, 2022)
|
|
2
12
|
- 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
13
|
- Updated telemetry logic to track the anonymous config for user consent flag set to declined or unknown.
|
package/es/factory/browser.js
CHANGED
|
@@ -11,7 +11,6 @@ import { integrationsManagerFactory } from '@splitsoftware/splitio-commons/esm/i
|
|
|
11
11
|
import { __InLocalStorageMockFactory } from '@splitsoftware/splitio-commons/esm/utils/settingsValidation/storage/storageCS';
|
|
12
12
|
import { sdkFactory } from '@splitsoftware/splitio-commons/esm/sdkFactory';
|
|
13
13
|
import { LOCALHOST_MODE, STORAGE_LOCALSTORAGE } from '@splitsoftware/splitio-commons/esm/utils/constants';
|
|
14
|
-
import { shouldAddPt } from '@splitsoftware/splitio-commons/esm/trackers/impressionObserver/utils';
|
|
15
14
|
import { createUserConsentAPI } from '@splitsoftware/splitio-commons/esm/consent/sdkUserConsent';
|
|
16
15
|
import { settingsFactory } from '../settings/browser';
|
|
17
16
|
import { platform, SignalListener } from '../platform';
|
|
@@ -38,7 +37,7 @@ function getModules(settings) {
|
|
|
38
37
|
sdkClientMethodFactory: sdkClientMethodCSFactory,
|
|
39
38
|
SignalListener: SignalListener,
|
|
40
39
|
integrationsManagerFactory: settings.integrations && settings.integrations.length > 0 ? integrationsManagerFactory.bind(null, settings.integrations) : undefined,
|
|
41
|
-
impressionsObserverFactory:
|
|
40
|
+
impressionsObserverFactory: impressionObserverCSFactory,
|
|
42
41
|
extraProps: function (params) {
|
|
43
42
|
return {
|
|
44
43
|
UserConsent: createUserConsentAPI(params)
|
package/es/factory/node.js
CHANGED
|
@@ -9,9 +9,9 @@ import { sdkClientMethodFactory } from '@splitsoftware/splitio-commons/esm/sdkCl
|
|
|
9
9
|
import { impressionObserverSSFactory } from '@splitsoftware/splitio-commons/esm/trackers/impressionObserver/impressionObserverSS';
|
|
10
10
|
import { sdkFactory } from '@splitsoftware/splitio-commons/esm/sdkFactory';
|
|
11
11
|
import { CONSUMER_MODE, LOCALHOST_MODE } from '@splitsoftware/splitio-commons/esm/utils/constants';
|
|
12
|
-
import { shouldAddPt } from '@splitsoftware/splitio-commons/esm/trackers/impressionObserver/utils';
|
|
13
12
|
import { settingsFactory } from '../settings/node';
|
|
14
13
|
import { platform, SignalListener } from '../platform';
|
|
14
|
+
import { bloomFilterFactory } from '../platform/filter/bloomFilter';
|
|
15
15
|
var syncManagerOnlineSSFactory = syncManagerOnlineFactory(pollingManagerSSFactory, pushManagerFactory);
|
|
16
16
|
function getStorage(settings) {
|
|
17
17
|
return settings.storage.type === 'REDIS' ?
|
|
@@ -32,7 +32,8 @@ function getModules(settings) {
|
|
|
32
32
|
sdkManagerFactory: sdkManagerFactory,
|
|
33
33
|
sdkClientMethodFactory: sdkClientMethodFactory,
|
|
34
34
|
SignalListener: SignalListener,
|
|
35
|
-
impressionsObserverFactory:
|
|
35
|
+
impressionsObserverFactory: impressionObserverSSFactory,
|
|
36
|
+
filterAdapterFactory: bloomFilterFactory
|
|
36
37
|
};
|
|
37
38
|
switch (settings.mode) {
|
|
38
39
|
case LOCALHOST_MODE:
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { BloomFilter } from '@ably/bloomit';
|
|
2
|
+
var EXPECTED_INSERTIONS = 10000000;
|
|
3
|
+
var ERROR_RATE = 0.01;
|
|
4
|
+
export function bloomFilterFactory(expectedInsertions, errorRate) {
|
|
5
|
+
if (expectedInsertions === void 0) { expectedInsertions = EXPECTED_INSERTIONS; }
|
|
6
|
+
if (errorRate === void 0) { errorRate = ERROR_RATE; }
|
|
7
|
+
var filter = BloomFilter.create(expectedInsertions, errorRate);
|
|
8
|
+
return {
|
|
9
|
+
add: function (key, value) {
|
|
10
|
+
var data = key + ":" + value;
|
|
11
|
+
if (filter.has(data)) {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
filter.add(data);
|
|
15
|
+
return true;
|
|
16
|
+
},
|
|
17
|
+
contains: function (key, value) {
|
|
18
|
+
var data = key + ":" + value;
|
|
19
|
+
return filter.has(data);
|
|
20
|
+
},
|
|
21
|
+
clear: function () {
|
|
22
|
+
filter = BloomFilter.create(expectedInsertions, errorRate);
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export var packageVersion = '10.
|
|
1
|
+
export var packageVersion = '10.21.1-rc.0';
|
package/es/settings/node.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { settingsValidation } from '@splitsoftware/splitio-commons/esm/utils/settingsValidation';
|
|
2
2
|
import { validateLogger } from '@splitsoftware/splitio-commons/esm/utils/settingsValidation/logger/builtinLogger';
|
|
3
|
-
import { LocalhostFromFile } from '
|
|
3
|
+
import { LocalhostFromFile } from '../sync/offline/LocalhostFromFile';
|
|
4
4
|
import { defaults } from './defaults/node';
|
|
5
5
|
import { validateStorage } from './storage/node';
|
|
6
6
|
import { validateRuntime } from './runtime/node';
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { splitsParserFromFileFactory } from './splitsParserFromFile';
|
|
2
|
+
import { syncManagerOfflineFactory } from '@splitsoftware/splitio-commons/esm/sync/offline/syncManagerOffline';
|
|
3
|
+
// Singleton instance of the factory function for offline SyncManager from YAML file (a.k.a. localhostFromFile)
|
|
4
|
+
// It uses NodeJS APIs.
|
|
5
|
+
var localhostFromFile = syncManagerOfflineFactory(splitsParserFromFileFactory);
|
|
6
|
+
localhostFromFile.type = 'LocalhostFromFile';
|
|
7
|
+
export function LocalhostFromFile() {
|
|
8
|
+
return localhostFromFile;
|
|
9
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import yaml from 'js-yaml';
|
|
4
|
+
import { isString, endsWith, find, forOwn, uniq, } from '@splitsoftware/splitio-commons/esm/utils/lang';
|
|
5
|
+
import { parseCondition } from '@splitsoftware/splitio-commons/esm/sync/offline/splitsParser/parseCondition';
|
|
6
|
+
var logPrefix = 'sync:offline:splits-fetcher: ';
|
|
7
|
+
var DEFAULT_FILENAME = '.split';
|
|
8
|
+
function configFilesPath(configFilePath) {
|
|
9
|
+
if (configFilePath === DEFAULT_FILENAME || !isString(configFilePath)) {
|
|
10
|
+
var root = process.env.HOME;
|
|
11
|
+
if (process.env.SPLIT_CONFIG_ROOT)
|
|
12
|
+
root = process.env.SPLIT_CONFIG_ROOT;
|
|
13
|
+
if (!root)
|
|
14
|
+
throw new Error('Missing split mock configuration root.');
|
|
15
|
+
configFilePath = path.join(root, DEFAULT_FILENAME);
|
|
16
|
+
}
|
|
17
|
+
// Validate the extensions
|
|
18
|
+
if (!(endsWith(configFilePath, '.yaml', true) || endsWith(configFilePath, '.yml', true) || endsWith(configFilePath, '.split', true)))
|
|
19
|
+
throw new Error("Invalid extension specified for Splits mock file. Accepted extensions are \".yml\" and \".yaml\". Your specified file is " + configFilePath);
|
|
20
|
+
if (!fs.existsSync(configFilePath))
|
|
21
|
+
throw new Error("Split configuration not found in " + configFilePath + " - Please review your Split file location.");
|
|
22
|
+
return configFilePath;
|
|
23
|
+
}
|
|
24
|
+
// This function is not pure nor meant to be. Here we apply modifications to cover
|
|
25
|
+
// for behaviour that's ensured by the BE.
|
|
26
|
+
function arrangeConditions(mocksData) {
|
|
27
|
+
// Iterate through each Split data
|
|
28
|
+
forOwn(mocksData, function (data) {
|
|
29
|
+
var conditions = data.conditions;
|
|
30
|
+
// On the manager, as the split jsons come with all treatments on the partitions prop,
|
|
31
|
+
// we'll add all the treatments to the first condition.
|
|
32
|
+
var firstRolloutCondition = find(conditions, function (cond) { return cond.conditionType === 'ROLLOUT'; });
|
|
33
|
+
// Malformed mocks may have
|
|
34
|
+
var treatments = uniq(data.treatments);
|
|
35
|
+
// If they're only specifying a whitelist we add the treatments there.
|
|
36
|
+
var allTreatmentsCondition = firstRolloutCondition ? firstRolloutCondition : conditions[0];
|
|
37
|
+
var fullyAllocatedTreatment = allTreatmentsCondition.partitions[0].treatment;
|
|
38
|
+
treatments.forEach(function (treatment) {
|
|
39
|
+
if (treatment !== fullyAllocatedTreatment) {
|
|
40
|
+
allTreatmentsCondition.partitions.push({
|
|
41
|
+
treatment: treatment,
|
|
42
|
+
size: 0
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
// Don't need these anymore
|
|
47
|
+
delete data.treatments;
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
export function splitsParserFromFileFactory() {
|
|
51
|
+
var previousMock = 'NO_MOCK_LOADED';
|
|
52
|
+
// Parse `.split` configuration file and return a map of "Split Objects"
|
|
53
|
+
function readSplitConfigFile(log, filePath) {
|
|
54
|
+
var SPLIT_POSITION = 0;
|
|
55
|
+
var TREATMENT_POSITION = 1;
|
|
56
|
+
var data;
|
|
57
|
+
try {
|
|
58
|
+
data = fs.readFileSync(filePath, 'utf-8');
|
|
59
|
+
}
|
|
60
|
+
catch (e) {
|
|
61
|
+
log.error(e && e.message);
|
|
62
|
+
return {};
|
|
63
|
+
}
|
|
64
|
+
if (data === previousMock)
|
|
65
|
+
return false;
|
|
66
|
+
previousMock = data;
|
|
67
|
+
var splitObjects = data.split(/\r?\n/).reduce(function (accum, line, index) {
|
|
68
|
+
var tuple = line.trim();
|
|
69
|
+
if (tuple === '' || tuple.charAt(0) === '#') {
|
|
70
|
+
log.debug(logPrefix + ("Ignoring empty line or comment at #" + index));
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
tuple = tuple.split(/\s+/);
|
|
74
|
+
if (tuple.length !== 2) {
|
|
75
|
+
log.debug(logPrefix + ("Ignoring line since it does not have exactly two columns #" + index));
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
var splitName = tuple[SPLIT_POSITION];
|
|
79
|
+
var condition = parseCondition({ treatment: tuple[TREATMENT_POSITION] });
|
|
80
|
+
accum[splitName] = { conditions: [condition], configurations: {}, trafficTypeName: 'localhost' };
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return accum;
|
|
84
|
+
}, {});
|
|
85
|
+
return splitObjects;
|
|
86
|
+
}
|
|
87
|
+
// Parse `.yml` or `.yaml` configuration files and return a map of "Split Objects"
|
|
88
|
+
function readYAMLConfigFile(log, filePath) {
|
|
89
|
+
var data = '';
|
|
90
|
+
var yamldoc = null;
|
|
91
|
+
try {
|
|
92
|
+
data = fs.readFileSync(filePath, 'utf8');
|
|
93
|
+
if (data === previousMock)
|
|
94
|
+
return false;
|
|
95
|
+
previousMock = data;
|
|
96
|
+
yamldoc = yaml.safeLoad(data);
|
|
97
|
+
}
|
|
98
|
+
catch (e) {
|
|
99
|
+
log.error(e);
|
|
100
|
+
return {};
|
|
101
|
+
}
|
|
102
|
+
// Each entry will be mapped to a condition, but we'll also keep the configurations map.
|
|
103
|
+
var mocksData = (yamldoc).reduce(function (accum, splitEntry) {
|
|
104
|
+
var splitName = Object.keys(splitEntry)[0];
|
|
105
|
+
if (!splitName || !isString(splitEntry[splitName].treatment))
|
|
106
|
+
log.error(logPrefix + 'Ignoring entry on YAML since the format is incorrect.');
|
|
107
|
+
var mockData = splitEntry[splitName];
|
|
108
|
+
// "Template" for each split accumulated data
|
|
109
|
+
if (!accum[splitName]) {
|
|
110
|
+
accum[splitName] = {
|
|
111
|
+
configurations: {}, conditions: [], treatments: [], trafficTypeName: 'localhost'
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
// Assign the config if there is one on the mock
|
|
115
|
+
if (mockData.config)
|
|
116
|
+
accum[splitName].configurations[mockData.treatment] = mockData.config;
|
|
117
|
+
// Parse the condition from the entry.
|
|
118
|
+
var condition = parseCondition(mockData);
|
|
119
|
+
accum[splitName].conditions[condition.conditionType === 'ROLLOUT' ? 'push' : 'unshift'](condition);
|
|
120
|
+
// Also keep track of the treatments, will be useful for manager functionality.
|
|
121
|
+
accum[splitName].treatments.push(mockData.treatment);
|
|
122
|
+
return accum;
|
|
123
|
+
}, {});
|
|
124
|
+
arrangeConditions(mocksData);
|
|
125
|
+
return mocksData;
|
|
126
|
+
}
|
|
127
|
+
// Load the content of a configuration file into an Object
|
|
128
|
+
return function splitsParserFromFile(_a) {
|
|
129
|
+
var features = _a.features, log = _a.log;
|
|
130
|
+
var filePath = configFilesPath(features);
|
|
131
|
+
var mockData;
|
|
132
|
+
// If we have a filePath, it means the extension is correct, choose the parser.
|
|
133
|
+
if (endsWith(filePath, '.split')) {
|
|
134
|
+
log.warn(logPrefix + '.split mocks will be deprecated soon in favor of YAML files, which provide more targeting power. Take a look in our documentation.');
|
|
135
|
+
mockData = readSplitConfigFile(log, filePath);
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
mockData = readYAMLConfigFile(log, filePath);
|
|
139
|
+
}
|
|
140
|
+
return mockData;
|
|
141
|
+
};
|
|
142
|
+
}
|
package/lib/factory/browser.js
CHANGED
|
@@ -14,7 +14,6 @@ var browser_1 = require("@splitsoftware/splitio-commons/cjs/integrations/browser
|
|
|
14
14
|
var storageCS_1 = require("@splitsoftware/splitio-commons/cjs/utils/settingsValidation/storage/storageCS");
|
|
15
15
|
var sdkFactory_1 = require("@splitsoftware/splitio-commons/cjs/sdkFactory");
|
|
16
16
|
var constants_1 = require("@splitsoftware/splitio-commons/cjs/utils/constants");
|
|
17
|
-
var utils_1 = require("@splitsoftware/splitio-commons/cjs/trackers/impressionObserver/utils");
|
|
18
17
|
var sdkUserConsent_1 = require("@splitsoftware/splitio-commons/cjs/consent/sdkUserConsent");
|
|
19
18
|
var browser_2 = require("../settings/browser");
|
|
20
19
|
var platform_1 = require("../platform");
|
|
@@ -41,7 +40,7 @@ function getModules(settings) {
|
|
|
41
40
|
sdkClientMethodFactory: sdkClientMethodCSWithTT_1.sdkClientMethodCSFactory,
|
|
42
41
|
SignalListener: platform_1.SignalListener,
|
|
43
42
|
integrationsManagerFactory: settings.integrations && settings.integrations.length > 0 ? browser_1.integrationsManagerFactory.bind(null, settings.integrations) : undefined,
|
|
44
|
-
impressionsObserverFactory:
|
|
43
|
+
impressionsObserverFactory: impressionObserverCS_1.impressionObserverCSFactory,
|
|
45
44
|
extraProps: function (params) {
|
|
46
45
|
return {
|
|
47
46
|
UserConsent: (0, sdkUserConsent_1.createUserConsentAPI)(params)
|
package/lib/factory/node.js
CHANGED
|
@@ -12,9 +12,9 @@ var sdkClientMethod_1 = require("@splitsoftware/splitio-commons/cjs/sdkClient/sd
|
|
|
12
12
|
var impressionObserverSS_1 = require("@splitsoftware/splitio-commons/cjs/trackers/impressionObserver/impressionObserverSS");
|
|
13
13
|
var sdkFactory_1 = require("@splitsoftware/splitio-commons/cjs/sdkFactory");
|
|
14
14
|
var constants_1 = require("@splitsoftware/splitio-commons/cjs/utils/constants");
|
|
15
|
-
var utils_1 = require("@splitsoftware/splitio-commons/cjs/trackers/impressionObserver/utils");
|
|
16
15
|
var node_1 = require("../settings/node");
|
|
17
16
|
var platform_1 = require("../platform");
|
|
17
|
+
var bloomFilter_1 = require("../platform/filter/bloomFilter");
|
|
18
18
|
var syncManagerOnlineSSFactory = (0, syncManagerOnline_1.syncManagerOnlineFactory)(pollingManagerSS_1.pollingManagerSSFactory, pushManager_1.pushManagerFactory);
|
|
19
19
|
function getStorage(settings) {
|
|
20
20
|
return settings.storage.type === 'REDIS' ?
|
|
@@ -35,7 +35,8 @@ function getModules(settings) {
|
|
|
35
35
|
sdkManagerFactory: sdkManager_1.sdkManagerFactory,
|
|
36
36
|
sdkClientMethodFactory: sdkClientMethod_1.sdkClientMethodFactory,
|
|
37
37
|
SignalListener: platform_1.SignalListener,
|
|
38
|
-
impressionsObserverFactory:
|
|
38
|
+
impressionsObserverFactory: impressionObserverSS_1.impressionObserverSSFactory,
|
|
39
|
+
filterAdapterFactory: bloomFilter_1.bloomFilterFactory
|
|
39
40
|
};
|
|
40
41
|
switch (settings.mode) {
|
|
41
42
|
case constants_1.LOCALHOST_MODE:
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.bloomFilterFactory = void 0;
|
|
4
|
+
var bloomit_1 = require("@ably/bloomit");
|
|
5
|
+
var EXPECTED_INSERTIONS = 10000000;
|
|
6
|
+
var ERROR_RATE = 0.01;
|
|
7
|
+
function bloomFilterFactory(expectedInsertions, errorRate) {
|
|
8
|
+
if (expectedInsertions === void 0) { expectedInsertions = EXPECTED_INSERTIONS; }
|
|
9
|
+
if (errorRate === void 0) { errorRate = ERROR_RATE; }
|
|
10
|
+
var filter = bloomit_1.BloomFilter.create(expectedInsertions, errorRate);
|
|
11
|
+
return {
|
|
12
|
+
add: function (key, value) {
|
|
13
|
+
var data = key + ":" + value;
|
|
14
|
+
if (filter.has(data)) {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
filter.add(data);
|
|
18
|
+
return true;
|
|
19
|
+
},
|
|
20
|
+
contains: function (key, value) {
|
|
21
|
+
var data = key + ":" + value;
|
|
22
|
+
return filter.has(data);
|
|
23
|
+
},
|
|
24
|
+
clear: function () {
|
|
25
|
+
filter = bloomit_1.BloomFilter.create(expectedInsertions, errorRate);
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
exports.bloomFilterFactory = bloomFilterFactory;
|
package/lib/settings/node.js
CHANGED
|
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.settingsFactory = void 0;
|
|
4
4
|
var settingsValidation_1 = require("@splitsoftware/splitio-commons/cjs/utils/settingsValidation");
|
|
5
5
|
var builtinLogger_1 = require("@splitsoftware/splitio-commons/cjs/utils/settingsValidation/logger/builtinLogger");
|
|
6
|
-
var LocalhostFromFile_1 = require("
|
|
6
|
+
var LocalhostFromFile_1 = require("../sync/offline/LocalhostFromFile");
|
|
7
7
|
var node_1 = require("./defaults/node");
|
|
8
8
|
var node_2 = require("./storage/node");
|
|
9
9
|
var node_3 = require("./runtime/node");
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.LocalhostFromFile = void 0;
|
|
4
|
+
var splitsParserFromFile_1 = require("./splitsParserFromFile");
|
|
5
|
+
var syncManagerOffline_1 = require("@splitsoftware/splitio-commons/cjs/sync/offline/syncManagerOffline");
|
|
6
|
+
// Singleton instance of the factory function for offline SyncManager from YAML file (a.k.a. localhostFromFile)
|
|
7
|
+
// It uses NodeJS APIs.
|
|
8
|
+
var localhostFromFile = (0, syncManagerOffline_1.syncManagerOfflineFactory)(splitsParserFromFile_1.splitsParserFromFileFactory);
|
|
9
|
+
localhostFromFile.type = 'LocalhostFromFile';
|
|
10
|
+
function LocalhostFromFile() {
|
|
11
|
+
return localhostFromFile;
|
|
12
|
+
}
|
|
13
|
+
exports.LocalhostFromFile = LocalhostFromFile;
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.splitsParserFromFileFactory = void 0;
|
|
4
|
+
var tslib_1 = require("tslib");
|
|
5
|
+
var fs_1 = (0, tslib_1.__importDefault)(require("fs"));
|
|
6
|
+
var path_1 = (0, tslib_1.__importDefault)(require("path"));
|
|
7
|
+
var js_yaml_1 = (0, tslib_1.__importDefault)(require("js-yaml"));
|
|
8
|
+
var lang_1 = require("@splitsoftware/splitio-commons/cjs/utils/lang");
|
|
9
|
+
var parseCondition_1 = require("@splitsoftware/splitio-commons/cjs/sync/offline/splitsParser/parseCondition");
|
|
10
|
+
var logPrefix = 'sync:offline:splits-fetcher: ';
|
|
11
|
+
var DEFAULT_FILENAME = '.split';
|
|
12
|
+
function configFilesPath(configFilePath) {
|
|
13
|
+
if (configFilePath === DEFAULT_FILENAME || !(0, lang_1.isString)(configFilePath)) {
|
|
14
|
+
var root = process.env.HOME;
|
|
15
|
+
if (process.env.SPLIT_CONFIG_ROOT)
|
|
16
|
+
root = process.env.SPLIT_CONFIG_ROOT;
|
|
17
|
+
if (!root)
|
|
18
|
+
throw new Error('Missing split mock configuration root.');
|
|
19
|
+
configFilePath = path_1.default.join(root, DEFAULT_FILENAME);
|
|
20
|
+
}
|
|
21
|
+
// Validate the extensions
|
|
22
|
+
if (!((0, lang_1.endsWith)(configFilePath, '.yaml', true) || (0, lang_1.endsWith)(configFilePath, '.yml', true) || (0, lang_1.endsWith)(configFilePath, '.split', true)))
|
|
23
|
+
throw new Error("Invalid extension specified for Splits mock file. Accepted extensions are \".yml\" and \".yaml\". Your specified file is " + configFilePath);
|
|
24
|
+
if (!fs_1.default.existsSync(configFilePath))
|
|
25
|
+
throw new Error("Split configuration not found in " + configFilePath + " - Please review your Split file location.");
|
|
26
|
+
return configFilePath;
|
|
27
|
+
}
|
|
28
|
+
// This function is not pure nor meant to be. Here we apply modifications to cover
|
|
29
|
+
// for behaviour that's ensured by the BE.
|
|
30
|
+
function arrangeConditions(mocksData) {
|
|
31
|
+
// Iterate through each Split data
|
|
32
|
+
(0, lang_1.forOwn)(mocksData, function (data) {
|
|
33
|
+
var conditions = data.conditions;
|
|
34
|
+
// On the manager, as the split jsons come with all treatments on the partitions prop,
|
|
35
|
+
// we'll add all the treatments to the first condition.
|
|
36
|
+
var firstRolloutCondition = (0, lang_1.find)(conditions, function (cond) { return cond.conditionType === 'ROLLOUT'; });
|
|
37
|
+
// Malformed mocks may have
|
|
38
|
+
var treatments = (0, lang_1.uniq)(data.treatments);
|
|
39
|
+
// If they're only specifying a whitelist we add the treatments there.
|
|
40
|
+
var allTreatmentsCondition = firstRolloutCondition ? firstRolloutCondition : conditions[0];
|
|
41
|
+
var fullyAllocatedTreatment = allTreatmentsCondition.partitions[0].treatment;
|
|
42
|
+
treatments.forEach(function (treatment) {
|
|
43
|
+
if (treatment !== fullyAllocatedTreatment) {
|
|
44
|
+
allTreatmentsCondition.partitions.push({
|
|
45
|
+
treatment: treatment,
|
|
46
|
+
size: 0
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
// Don't need these anymore
|
|
51
|
+
delete data.treatments;
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
function splitsParserFromFileFactory() {
|
|
55
|
+
var previousMock = 'NO_MOCK_LOADED';
|
|
56
|
+
// Parse `.split` configuration file and return a map of "Split Objects"
|
|
57
|
+
function readSplitConfigFile(log, filePath) {
|
|
58
|
+
var SPLIT_POSITION = 0;
|
|
59
|
+
var TREATMENT_POSITION = 1;
|
|
60
|
+
var data;
|
|
61
|
+
try {
|
|
62
|
+
data = fs_1.default.readFileSync(filePath, 'utf-8');
|
|
63
|
+
}
|
|
64
|
+
catch (e) {
|
|
65
|
+
log.error(e && e.message);
|
|
66
|
+
return {};
|
|
67
|
+
}
|
|
68
|
+
if (data === previousMock)
|
|
69
|
+
return false;
|
|
70
|
+
previousMock = data;
|
|
71
|
+
var splitObjects = data.split(/\r?\n/).reduce(function (accum, line, index) {
|
|
72
|
+
var tuple = line.trim();
|
|
73
|
+
if (tuple === '' || tuple.charAt(0) === '#') {
|
|
74
|
+
log.debug(logPrefix + ("Ignoring empty line or comment at #" + index));
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
tuple = tuple.split(/\s+/);
|
|
78
|
+
if (tuple.length !== 2) {
|
|
79
|
+
log.debug(logPrefix + ("Ignoring line since it does not have exactly two columns #" + index));
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
var splitName = tuple[SPLIT_POSITION];
|
|
83
|
+
var condition = (0, parseCondition_1.parseCondition)({ treatment: tuple[TREATMENT_POSITION] });
|
|
84
|
+
accum[splitName] = { conditions: [condition], configurations: {}, trafficTypeName: 'localhost' };
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return accum;
|
|
88
|
+
}, {});
|
|
89
|
+
return splitObjects;
|
|
90
|
+
}
|
|
91
|
+
// Parse `.yml` or `.yaml` configuration files and return a map of "Split Objects"
|
|
92
|
+
function readYAMLConfigFile(log, filePath) {
|
|
93
|
+
var data = '';
|
|
94
|
+
var yamldoc = null;
|
|
95
|
+
try {
|
|
96
|
+
data = fs_1.default.readFileSync(filePath, 'utf8');
|
|
97
|
+
if (data === previousMock)
|
|
98
|
+
return false;
|
|
99
|
+
previousMock = data;
|
|
100
|
+
yamldoc = js_yaml_1.default.safeLoad(data);
|
|
101
|
+
}
|
|
102
|
+
catch (e) {
|
|
103
|
+
log.error(e);
|
|
104
|
+
return {};
|
|
105
|
+
}
|
|
106
|
+
// Each entry will be mapped to a condition, but we'll also keep the configurations map.
|
|
107
|
+
var mocksData = (yamldoc).reduce(function (accum, splitEntry) {
|
|
108
|
+
var splitName = Object.keys(splitEntry)[0];
|
|
109
|
+
if (!splitName || !(0, lang_1.isString)(splitEntry[splitName].treatment))
|
|
110
|
+
log.error(logPrefix + 'Ignoring entry on YAML since the format is incorrect.');
|
|
111
|
+
var mockData = splitEntry[splitName];
|
|
112
|
+
// "Template" for each split accumulated data
|
|
113
|
+
if (!accum[splitName]) {
|
|
114
|
+
accum[splitName] = {
|
|
115
|
+
configurations: {}, conditions: [], treatments: [], trafficTypeName: 'localhost'
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
// Assign the config if there is one on the mock
|
|
119
|
+
if (mockData.config)
|
|
120
|
+
accum[splitName].configurations[mockData.treatment] = mockData.config;
|
|
121
|
+
// Parse the condition from the entry.
|
|
122
|
+
var condition = (0, parseCondition_1.parseCondition)(mockData);
|
|
123
|
+
accum[splitName].conditions[condition.conditionType === 'ROLLOUT' ? 'push' : 'unshift'](condition);
|
|
124
|
+
// Also keep track of the treatments, will be useful for manager functionality.
|
|
125
|
+
accum[splitName].treatments.push(mockData.treatment);
|
|
126
|
+
return accum;
|
|
127
|
+
}, {});
|
|
128
|
+
arrangeConditions(mocksData);
|
|
129
|
+
return mocksData;
|
|
130
|
+
}
|
|
131
|
+
// Load the content of a configuration file into an Object
|
|
132
|
+
return function splitsParserFromFile(_a) {
|
|
133
|
+
var features = _a.features, log = _a.log;
|
|
134
|
+
var filePath = configFilesPath(features);
|
|
135
|
+
var mockData;
|
|
136
|
+
// If we have a filePath, it means the extension is correct, choose the parser.
|
|
137
|
+
if ((0, lang_1.endsWith)(filePath, '.split')) {
|
|
138
|
+
log.warn(logPrefix + '.split mocks will be deprecated soon in favor of YAML files, which provide more targeting power. Take a look in our documentation.');
|
|
139
|
+
mockData = readSplitConfigFile(log, filePath);
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
mockData = readYAMLConfigFile(log, filePath);
|
|
143
|
+
}
|
|
144
|
+
return mockData;
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
exports.splitsParserFromFileFactory = splitsParserFromFileFactory;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@splitsoftware/splitio",
|
|
3
|
-
"version": "10.
|
|
3
|
+
"version": "10.21.1-rc.0",
|
|
4
4
|
"description": "Split SDK",
|
|
5
5
|
"files": [
|
|
6
6
|
"README.md",
|
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
"lib",
|
|
11
11
|
"types",
|
|
12
12
|
"es",
|
|
13
|
-
"src"
|
|
13
|
+
"src",
|
|
14
|
+
"scripts/ga-to-split-autorequire.js"
|
|
14
15
|
],
|
|
15
16
|
"repository": "splitio/javascript-client",
|
|
16
17
|
"homepage": "https://github.com/splitio/javascript-client#readme",
|
|
@@ -32,11 +33,12 @@
|
|
|
32
33
|
"node": ">=6"
|
|
33
34
|
},
|
|
34
35
|
"dependencies": {
|
|
35
|
-
"@
|
|
36
|
+
"@ably/bloomit": "^1.4.2",
|
|
37
|
+
"@splitsoftware/splitio-commons": "1.6.2-rc.2",
|
|
36
38
|
"@types/google.analytics": "0.0.40",
|
|
37
39
|
"@types/ioredis": "^4.28.0",
|
|
38
40
|
"ioredis": "^4.28.0",
|
|
39
|
-
"js-yaml": "3.13.1",
|
|
41
|
+
"js-yaml": "^3.13.1",
|
|
40
42
|
"node-fetch": "^2.6.7",
|
|
41
43
|
"unfetch": "^4.2.0"
|
|
42
44
|
},
|
|
@@ -83,28 +85,29 @@
|
|
|
83
85
|
"postbuild-cjs": "cross-env NODE_ENV=cjs node scripts/copy.packages.json.js && ./scripts/build_cjs_replace_imports.sh",
|
|
84
86
|
"build-umd": "webpack --config webpack.dev.js --env branch=$BUILD_BRANCH --env commit_hash=$BUILD_COMMIT && webpack --config webpack.prod.js --env branch=$BUILD_BRANCH --env commit_hash=$BUILD_COMMIT && ./scripts/clean_umd_build.sh",
|
|
85
87
|
"build:npm": "rimraf lib es && npm run build-cjs && npm run build-esm",
|
|
88
|
+
"build:ga-to-split-autorequire": "terser ./node_modules/@splitsoftware/splitio-commons/src/integrations/ga/autoRequire.js --mangle --output ./scripts/ga-to-split-autorequire.js && cp ./scripts/ga-to-split-autorequire.js umd/ga-to-split-autorequire.js",
|
|
86
89
|
"build": "rimraf lib umd es && npm run build-cjs && npm run build-esm && npm run build-umd",
|
|
87
90
|
"check": "npm run check:lint && npm run check:version",
|
|
88
91
|
"check:lint": "eslint src",
|
|
89
92
|
"check:version": "cross-env NODE_ENV=test tape -r ./ts-node.register src/settings/__tests__/defaults.spec.js",
|
|
90
|
-
"test-browser
|
|
91
|
-
"test-browser-
|
|
92
|
-
"test-browser": "npm run test-browser-
|
|
93
|
-
"test-browser-
|
|
94
|
-
"test-browser-
|
|
95
|
-
"test-browser-
|
|
96
|
-
"test-browser-
|
|
97
|
-
"test-browser-
|
|
98
|
-
"test-browser-
|
|
99
|
-
"test-
|
|
100
|
-
"test-node": "npm run test-node-unit && npm run test-node-online && npm run test-node-redis && npm run test-node-offline && npm run test-node-destroy && npm run test-node-errors && npm run test-node-push",
|
|
93
|
+
"test-browser": "npm run test-browser-unit && npm run test-browser-e2e",
|
|
94
|
+
"test-browser-unit": "cross-env NODE_ENV=test karma start karma/unit.karma.conf.js",
|
|
95
|
+
"test-browser-e2e": "npm run test-browser-e2e-online && npm run test-browser-e2e-offline && npm run test-browser-e2e-destroy && npm run test-browser-e2e-errorCatching && npm run test-browser-e2e-push && npm run test-browser-e2e-gaIntegration",
|
|
96
|
+
"test-browser-e2e-online": "cross-env NODE_ENV=test karma start karma/e2e.online.karma.conf.js",
|
|
97
|
+
"test-browser-e2e-offline": "cross-env NODE_ENV=test karma start karma/e2e.offline.karma.conf.js",
|
|
98
|
+
"test-browser-e2e-destroy": "cross-env NODE_ENV=test karma start karma/e2e.destroy.karma.conf.js",
|
|
99
|
+
"test-browser-e2e-errorCatching": "cross-env NODE_ENV=test karma start karma/e2e.errorCatching.karma.conf.js",
|
|
100
|
+
"test-browser-e2e-push": "cross-env NODE_ENV=test karma start karma/e2e.push.karma.conf.js",
|
|
101
|
+
"test-browser-e2e-gaIntegration": "cross-env NODE_ENV=test karma start karma/e2e.gaIntegration.karma.conf.js",
|
|
102
|
+
"test-node": "npm run test-node-unit && npm run test-node-e2e",
|
|
101
103
|
"test-node-unit": "cross-env NODE_ENV=test tape -r ./ts-node.register \"src/*/**/__tests__/**/!(browser).spec.js\" | tap-min",
|
|
102
|
-
"test-node-
|
|
103
|
-
"test-node-
|
|
104
|
-
"test-node-
|
|
105
|
-
"test-node-
|
|
106
|
-
"test-node-
|
|
107
|
-
"test-node-push": "cross-env NODE_ENV=test tape -r ./ts-node.register src/__tests__/push/node.spec.js | tap-min",
|
|
104
|
+
"test-node-e2e": "npm run test-node-e2e-online && npm run test-node-e2e-offline && npm run test-node-e2e-destroy && npm run test-node-e2e-errorCatching && npm run test-node-e2e-push && npm run test-node-e2e-redis",
|
|
105
|
+
"test-node-e2e-online": "cross-env NODE_ENV=test tape -r ./ts-node.register src/__tests__/online/node.spec.js | tap-min",
|
|
106
|
+
"test-node-e2e-offline": "cross-env NODE_ENV=test tape -r ./ts-node.register src/__tests__/offline/node.spec.js | tap-min",
|
|
107
|
+
"test-node-e2e-destroy": "cross-env NODE_ENV=test tape -r ./ts-node.register src/__tests__/destroy/node.spec.js | tap-min",
|
|
108
|
+
"test-node-e2e-errorCatching": "cross-env NODE_ENV=test tape -r ./ts-node.register src/__tests__/errorCatching/node.spec.js | tap-min",
|
|
109
|
+
"test-node-e2e-push": "cross-env NODE_ENV=test tape -r ./ts-node.register src/__tests__/push/node.spec.js | tap-min",
|
|
110
|
+
"test-node-e2e-redis": "cross-env NODE_ENV=test tape -r ./ts-node.register src/__tests__/consumer/node_redis.spec.js | tap-min",
|
|
108
111
|
"pretest-ts-decls": "npm run build-esm && npm run build-cjs && npm link",
|
|
109
112
|
"test-ts-decls": "./scripts/ts-tests.sh",
|
|
110
113
|
"posttest-ts-decls": "npm unlink && npm install",
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
(function(n,t,e){n[e]=n[e]||t;n[t]=n[t]||function(){n[t].q.push(arguments)};n[t].q=n[t].q||[];var r={};function i(n){return typeof n==="object"&&typeof n.name==="string"&&n.name}function o(e){if(e&&e[0]==="create"){var o=i(e[1])||i(e[2])||i(e[3])||(typeof e[3]==="string"?e[3]:undefined);if(!r[o]){r[o]=true;n[t]((o?o+".":"")+"require","splitTracker")}}}n[t].q.forEach(o);var u=n[t].q.push;n[t].q.push=function(n){var t=u.apply(this,arguments);o(n);return t}})(window,"ga","GoogleAnalyticsObject");
|
package/src/.DS_Store
CHANGED
|
Binary file
|
package/src/factory/browser.js
CHANGED
|
@@ -11,7 +11,6 @@ import { integrationsManagerFactory } from '@splitsoftware/splitio-commons/src/i
|
|
|
11
11
|
import { __InLocalStorageMockFactory } from '@splitsoftware/splitio-commons/src/utils/settingsValidation/storage/storageCS';
|
|
12
12
|
import { sdkFactory } from '@splitsoftware/splitio-commons/src/sdkFactory';
|
|
13
13
|
import { LOCALHOST_MODE, STORAGE_LOCALSTORAGE } from '@splitsoftware/splitio-commons/src/utils/constants';
|
|
14
|
-
import { shouldAddPt } from '@splitsoftware/splitio-commons/src/trackers/impressionObserver/utils';
|
|
15
14
|
import { createUserConsentAPI } from '@splitsoftware/splitio-commons/src/consent/sdkUserConsent';
|
|
16
15
|
|
|
17
16
|
import { settingsFactory } from '../settings/browser';
|
|
@@ -52,7 +51,7 @@ function getModules(settings) {
|
|
|
52
51
|
|
|
53
52
|
integrationsManagerFactory: settings.integrations && settings.integrations.length > 0 ? integrationsManagerFactory.bind(null, settings.integrations) : undefined,
|
|
54
53
|
|
|
55
|
-
impressionsObserverFactory:
|
|
54
|
+
impressionsObserverFactory: impressionObserverCSFactory,
|
|
56
55
|
|
|
57
56
|
extraProps: (params) => {
|
|
58
57
|
return {
|
package/src/factory/node.js
CHANGED
|
@@ -9,10 +9,10 @@ import { sdkClientMethodFactory } from '@splitsoftware/splitio-commons/src/sdkCl
|
|
|
9
9
|
import { impressionObserverSSFactory } from '@splitsoftware/splitio-commons/src/trackers/impressionObserver/impressionObserverSS';
|
|
10
10
|
import { sdkFactory } from '@splitsoftware/splitio-commons/src/sdkFactory';
|
|
11
11
|
import { CONSUMER_MODE, LOCALHOST_MODE } from '@splitsoftware/splitio-commons/src/utils/constants';
|
|
12
|
-
import { shouldAddPt } from '@splitsoftware/splitio-commons/src/trackers/impressionObserver/utils';
|
|
13
12
|
|
|
14
13
|
import { settingsFactory } from '../settings/node';
|
|
15
14
|
import { platform, SignalListener } from '../platform';
|
|
15
|
+
import { bloomFilterFactory } from '../platform/filter/bloomFilter';
|
|
16
16
|
|
|
17
17
|
const syncManagerOnlineSSFactory = syncManagerOnlineFactory(pollingManagerSSFactory, pushManagerFactory);
|
|
18
18
|
|
|
@@ -45,7 +45,9 @@ function getModules(settings) {
|
|
|
45
45
|
|
|
46
46
|
SignalListener,
|
|
47
47
|
|
|
48
|
-
impressionsObserverFactory:
|
|
48
|
+
impressionsObserverFactory: impressionObserverSSFactory,
|
|
49
|
+
|
|
50
|
+
filterAdapterFactory: bloomFilterFactory
|
|
49
51
|
};
|
|
50
52
|
|
|
51
53
|
switch (settings.mode) {
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { BloomFilter } from '@ably/bloomit';
|
|
2
|
+
|
|
3
|
+
const EXPECTED_INSERTIONS = 10000000;
|
|
4
|
+
const ERROR_RATE = 0.01;
|
|
5
|
+
|
|
6
|
+
export function bloomFilterFactory(expectedInsertions = EXPECTED_INSERTIONS, errorRate = ERROR_RATE) {
|
|
7
|
+
let filter = BloomFilter.create(expectedInsertions, errorRate);
|
|
8
|
+
|
|
9
|
+
return {
|
|
10
|
+
|
|
11
|
+
add(key, value) {
|
|
12
|
+
const data = `${key}:${value}`;
|
|
13
|
+
if (filter.has(data)) {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
filter.add(data);
|
|
17
|
+
return true;
|
|
18
|
+
},
|
|
19
|
+
|
|
20
|
+
contains(key, value) {
|
|
21
|
+
const data = `${key}:${value}`;
|
|
22
|
+
return filter.has(data);
|
|
23
|
+
},
|
|
24
|
+
|
|
25
|
+
clear() {
|
|
26
|
+
filter = BloomFilter.create(expectedInsertions, errorRate);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
};
|
|
30
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export const packageVersion = '10.
|
|
1
|
+
export const packageVersion = '10.21.1-rc.0';
|
package/src/settings/node.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { settingsValidation } from '@splitsoftware/splitio-commons/src/utils/settingsValidation';
|
|
2
2
|
import { validateLogger } from '@splitsoftware/splitio-commons/src/utils/settingsValidation/logger/builtinLogger';
|
|
3
|
-
import { LocalhostFromFile } from '
|
|
3
|
+
import { LocalhostFromFile } from '../sync/offline/LocalhostFromFile';
|
|
4
4
|
|
|
5
5
|
import { defaults } from './defaults/node';
|
|
6
6
|
import { validateStorage } from './storage/node';
|
|
Binary file
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { splitsParserFromFileFactory } from './splitsParserFromFile';
|
|
2
|
+
import { syncManagerOfflineFactory } from '@splitsoftware/splitio-commons/src/sync/offline/syncManagerOffline';
|
|
3
|
+
|
|
4
|
+
// Singleton instance of the factory function for offline SyncManager from YAML file (a.k.a. localhostFromFile)
|
|
5
|
+
// It uses NodeJS APIs.
|
|
6
|
+
const localhostFromFile = syncManagerOfflineFactory(splitsParserFromFileFactory);
|
|
7
|
+
localhostFromFile.type = 'LocalhostFromFile';
|
|
8
|
+
|
|
9
|
+
export function LocalhostFromFile() {
|
|
10
|
+
return localhostFromFile;
|
|
11
|
+
}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import yaml from 'js-yaml';
|
|
4
|
+
import { isString, endsWith, find, forOwn, uniq, } from '@splitsoftware/splitio-commons/src/utils/lang';
|
|
5
|
+
import { parseCondition } from '@splitsoftware/splitio-commons/src/sync/offline/splitsParser/parseCondition';
|
|
6
|
+
|
|
7
|
+
const logPrefix = 'sync:offline:splits-fetcher: ';
|
|
8
|
+
|
|
9
|
+
const DEFAULT_FILENAME = '.split';
|
|
10
|
+
|
|
11
|
+
function configFilesPath(configFilePath) {
|
|
12
|
+
if (configFilePath === DEFAULT_FILENAME || !isString(configFilePath)) {
|
|
13
|
+
let root = process.env.HOME;
|
|
14
|
+
|
|
15
|
+
if (process.env.SPLIT_CONFIG_ROOT) root = process.env.SPLIT_CONFIG_ROOT;
|
|
16
|
+
|
|
17
|
+
if (!root) throw new Error('Missing split mock configuration root.');
|
|
18
|
+
|
|
19
|
+
configFilePath = path.join(root, DEFAULT_FILENAME);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Validate the extensions
|
|
23
|
+
if (!(endsWith(configFilePath, '.yaml', true) || endsWith(configFilePath, '.yml', true) || endsWith(configFilePath, '.split', true)))
|
|
24
|
+
throw new Error(`Invalid extension specified for Splits mock file. Accepted extensions are ".yml" and ".yaml". Your specified file is ${configFilePath}`);
|
|
25
|
+
|
|
26
|
+
if (!fs.existsSync(configFilePath))
|
|
27
|
+
throw new Error(`Split configuration not found in ${configFilePath} - Please review your Split file location.`);
|
|
28
|
+
|
|
29
|
+
return configFilePath;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// This function is not pure nor meant to be. Here we apply modifications to cover
|
|
33
|
+
// for behaviour that's ensured by the BE.
|
|
34
|
+
function arrangeConditions(mocksData) {
|
|
35
|
+
// Iterate through each Split data
|
|
36
|
+
forOwn(mocksData, data => {
|
|
37
|
+
const conditions = data.conditions;
|
|
38
|
+
|
|
39
|
+
// On the manager, as the split jsons come with all treatments on the partitions prop,
|
|
40
|
+
// we'll add all the treatments to the first condition.
|
|
41
|
+
const firstRolloutCondition = find(conditions, cond => cond.conditionType === 'ROLLOUT');
|
|
42
|
+
// Malformed mocks may have
|
|
43
|
+
const treatments = uniq(data.treatments);
|
|
44
|
+
// If they're only specifying a whitelist we add the treatments there.
|
|
45
|
+
const allTreatmentsCondition = firstRolloutCondition ? firstRolloutCondition : conditions[0];
|
|
46
|
+
|
|
47
|
+
const fullyAllocatedTreatment = allTreatmentsCondition.partitions[0].treatment;
|
|
48
|
+
|
|
49
|
+
treatments.forEach(treatment => {
|
|
50
|
+
if (treatment !== fullyAllocatedTreatment) {
|
|
51
|
+
allTreatmentsCondition.partitions.push({
|
|
52
|
+
treatment, size: 0
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
// Don't need these anymore
|
|
58
|
+
delete data.treatments;
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function splitsParserFromFileFactory() {
|
|
63
|
+
|
|
64
|
+
let previousMock = 'NO_MOCK_LOADED';
|
|
65
|
+
|
|
66
|
+
// Parse `.split` configuration file and return a map of "Split Objects"
|
|
67
|
+
function readSplitConfigFile(log, filePath) {
|
|
68
|
+
const SPLIT_POSITION = 0;
|
|
69
|
+
const TREATMENT_POSITION = 1;
|
|
70
|
+
let data;
|
|
71
|
+
|
|
72
|
+
try {
|
|
73
|
+
data = fs.readFileSync(filePath, 'utf-8');
|
|
74
|
+
} catch (e) {
|
|
75
|
+
log.error(e && e.message);
|
|
76
|
+
|
|
77
|
+
return {};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (data === previousMock) return false;
|
|
81
|
+
previousMock = data;
|
|
82
|
+
|
|
83
|
+
const splitObjects = data.split(/\r?\n/).reduce((accum, line, index) => {
|
|
84
|
+
let tuple = line.trim();
|
|
85
|
+
|
|
86
|
+
if (tuple === '' || tuple.charAt(0) === '#') {
|
|
87
|
+
log.debug(logPrefix + `Ignoring empty line or comment at #${index}`);
|
|
88
|
+
} else {
|
|
89
|
+
tuple = tuple.split(/\s+/);
|
|
90
|
+
|
|
91
|
+
if (tuple.length !== 2) {
|
|
92
|
+
log.debug(logPrefix + `Ignoring line since it does not have exactly two columns #${index}`);
|
|
93
|
+
} else {
|
|
94
|
+
const splitName = tuple[SPLIT_POSITION];
|
|
95
|
+
const condition = parseCondition({ treatment: tuple[TREATMENT_POSITION] });
|
|
96
|
+
accum[splitName] = { conditions: [condition], configurations: {}, trafficTypeName: 'localhost' };
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return accum;
|
|
101
|
+
}, {});
|
|
102
|
+
|
|
103
|
+
return splitObjects;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Parse `.yml` or `.yaml` configuration files and return a map of "Split Objects"
|
|
107
|
+
function readYAMLConfigFile(log, filePath) {
|
|
108
|
+
let data = '';
|
|
109
|
+
let yamldoc = null;
|
|
110
|
+
|
|
111
|
+
try {
|
|
112
|
+
data = fs.readFileSync(filePath, 'utf8');
|
|
113
|
+
|
|
114
|
+
if (data === previousMock) return false;
|
|
115
|
+
previousMock = data;
|
|
116
|
+
|
|
117
|
+
yamldoc = yaml.safeLoad(data);
|
|
118
|
+
} catch (e) {
|
|
119
|
+
log.error(e);
|
|
120
|
+
|
|
121
|
+
return {};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Each entry will be mapped to a condition, but we'll also keep the configurations map.
|
|
125
|
+
const mocksData = (yamldoc).reduce((accum, splitEntry) => {
|
|
126
|
+
const splitName = Object.keys(splitEntry)[0];
|
|
127
|
+
|
|
128
|
+
if (!splitName || !isString(splitEntry[splitName].treatment))
|
|
129
|
+
log.error(logPrefix + 'Ignoring entry on YAML since the format is incorrect.');
|
|
130
|
+
|
|
131
|
+
const mockData = splitEntry[splitName];
|
|
132
|
+
|
|
133
|
+
// "Template" for each split accumulated data
|
|
134
|
+
if (!accum[splitName]) {
|
|
135
|
+
accum[splitName] = {
|
|
136
|
+
configurations: {}, conditions: [], treatments: [], trafficTypeName: 'localhost'
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Assign the config if there is one on the mock
|
|
141
|
+
if (mockData.config) accum[splitName].configurations[mockData.treatment] = mockData.config;
|
|
142
|
+
// Parse the condition from the entry.
|
|
143
|
+
const condition = parseCondition(mockData);
|
|
144
|
+
accum[splitName].conditions[condition.conditionType === 'ROLLOUT' ? 'push' : 'unshift'](condition);
|
|
145
|
+
// Also keep track of the treatments, will be useful for manager functionality.
|
|
146
|
+
accum[splitName].treatments.push(mockData.treatment);
|
|
147
|
+
|
|
148
|
+
return accum;
|
|
149
|
+
}, {});
|
|
150
|
+
|
|
151
|
+
arrangeConditions(mocksData);
|
|
152
|
+
|
|
153
|
+
return mocksData;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Load the content of a configuration file into an Object
|
|
157
|
+
return function splitsParserFromFile({ features, log }) {
|
|
158
|
+
const filePath = configFilesPath(features);
|
|
159
|
+
let mockData;
|
|
160
|
+
|
|
161
|
+
// If we have a filePath, it means the extension is correct, choose the parser.
|
|
162
|
+
if (endsWith(filePath, '.split')) {
|
|
163
|
+
log.warn(logPrefix + '.split mocks will be deprecated soon in favor of YAML files, which provide more targeting power. Take a look in our documentation.');
|
|
164
|
+
mockData = readSplitConfigFile(log, filePath);
|
|
165
|
+
} else {
|
|
166
|
+
mockData = readYAMLConfigFile(log, filePath);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return mockData;
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
}
|
package/types/splitio.d.ts
CHANGED
|
@@ -110,6 +110,7 @@ interface ISettings {
|
|
|
110
110
|
readonly sync: {
|
|
111
111
|
splitFilters: SplitIO.SplitFilter[],
|
|
112
112
|
impressionsMode: SplitIO.ImpressionsMode,
|
|
113
|
+
enabled: boolean
|
|
113
114
|
}
|
|
114
115
|
/**
|
|
115
116
|
* User consent status if using in browser. Undefined if using in NodeJS.
|
|
@@ -234,6 +235,16 @@ interface ISharedSettings {
|
|
|
234
235
|
* @default 'OPTIMIZED'
|
|
235
236
|
*/
|
|
236
237
|
impressionsMode?: SplitIO.ImpressionsMode,
|
|
238
|
+
/**
|
|
239
|
+
* Controls the SDK continuous synchronization flags.
|
|
240
|
+
*
|
|
241
|
+
* When `true` a running SDK will process rollout plan updates performed on the UI (default).
|
|
242
|
+
* When false it'll just fetch all data upon init
|
|
243
|
+
*
|
|
244
|
+
* @property {boolean} enabled
|
|
245
|
+
* @default true
|
|
246
|
+
*/
|
|
247
|
+
enabled?: boolean
|
|
237
248
|
}
|
|
238
249
|
}
|
|
239
250
|
/**
|
|
@@ -716,7 +727,7 @@ declare namespace SplitIO {
|
|
|
716
727
|
/**
|
|
717
728
|
* Enable 'Google Analytics to Split' integration, to track Google Analytics hits as Split events.
|
|
718
729
|
*
|
|
719
|
-
* @see {@link https://help.split.io/hc/en-us/articles/
|
|
730
|
+
* @see {@link https://help.split.io/hc/en-us/articles/360040838752#google-analytics-to-split}
|
|
720
731
|
*/
|
|
721
732
|
interface IGoogleAnalyticsToSplitConfig {
|
|
722
733
|
type: 'GOOGLE_ANALYTICS_TO_SPLIT',
|
|
@@ -758,6 +769,17 @@ declare namespace SplitIO {
|
|
|
758
769
|
* If not provided, events are sent using the key and traffic type provided at SDK config
|
|
759
770
|
*/
|
|
760
771
|
identities?: Identity[],
|
|
772
|
+
/**
|
|
773
|
+
* Optional flag to log an error if the `auto-require` script is not detected.
|
|
774
|
+
* The auto-require script automatically requires the `splitTracker` plugin for created trackers,
|
|
775
|
+
* and should be placed right after your Google Analytics, Google Tag Manager or gtag.js script tag.
|
|
776
|
+
*
|
|
777
|
+
* @see {@link https://help.split.io/hc/en-us/articles/360040838752#set-up-with-gtm-and-gtag.js}
|
|
778
|
+
*
|
|
779
|
+
* @property {boolean} autoRequire
|
|
780
|
+
* @default false
|
|
781
|
+
*/
|
|
782
|
+
autoRequire?: boolean,
|
|
761
783
|
}
|
|
762
784
|
/**
|
|
763
785
|
* Object representing the data sent by Split (events and impressions).
|
|
@@ -769,7 +791,7 @@ declare namespace SplitIO {
|
|
|
769
791
|
/**
|
|
770
792
|
* Enable 'Split to Google Analytics' integration, to track Split impressions and events as Google Analytics hits.
|
|
771
793
|
*
|
|
772
|
-
* @see {@link https://help.split.io/hc/en-us/articles/
|
|
794
|
+
* @see {@link https://help.split.io/hc/en-us/articles/360040838752#split-to-google-analytics}
|
|
773
795
|
*/
|
|
774
796
|
interface ISplitToGoogleAnalyticsConfig {
|
|
775
797
|
type: 'SPLIT_TO_GOOGLE_ANALYTICS',
|
|
@@ -892,7 +914,7 @@ declare namespace SplitIO {
|
|
|
892
914
|
* ImpressionsMode type
|
|
893
915
|
* @typedef {string} ImpressionsMode
|
|
894
916
|
*/
|
|
895
|
-
type ImpressionsMode = 'OPTIMIZED' | 'DEBUG';
|
|
917
|
+
type ImpressionsMode = 'OPTIMIZED' | 'DEBUG' | 'NONE';
|
|
896
918
|
/**
|
|
897
919
|
* User consent status.
|
|
898
920
|
* @typedef {string} ConsentStatus
|
package/src/platform/.DS_Store
DELETED
|
Binary file
|