@splitsoftware/splitio-commons 1.14.0 → 1.15.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 +4 -1
- package/cjs/services/splitApi.js +3 -2
- package/cjs/storages/KeyBuilder.js +1 -2
- package/cjs/sync/streaming/SSEClient/index.js +2 -2
- package/cjs/utils/constants/index.js +2 -2
- package/cjs/utils/settingsValidation/index.js +14 -11
- package/esm/services/splitApi.js +4 -3
- package/esm/storages/KeyBuilder.js +1 -2
- package/esm/sync/streaming/SSEClient/index.js +2 -2
- package/esm/utils/constants/index.js +1 -1
- package/esm/utils/settingsValidation/index.js +15 -12
- package/package.json +1 -1
- package/src/services/splitApi.ts +4 -3
- package/src/storages/KeyBuilder.ts +1 -2
- package/src/sync/streaming/SSEClient/index.ts +2 -2
- package/src/types.ts +2 -1
- package/src/utils/constants/index.ts +1 -1
- package/src/utils/settingsValidation/index.ts +15 -12
- package/src/utils/settingsValidation/types.ts +2 -0
- package/types/types.d.ts +1 -0
- package/types/utils/constants/index.d.ts +1 -1
- package/types/utils/settingsValidation/index.d.ts +1 -0
- package/types/utils/settingsValidation/types.d.ts +2 -0
package/CHANGES.txt
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
1.15.0 (May 13, 2024)
|
|
2
|
+
- Added an optional settings validation parameter to let overwrite the default flag spec version, used by the JS Synchronizer.
|
|
3
|
+
|
|
1
4
|
1.14.0 (May 6, 2024)
|
|
2
5
|
- Added support for targeting rules based on semantic versions (https://semver.org/).
|
|
3
6
|
- Added special impression label "targeting rule type unsupported by sdk" when the matcher type is not supported by the SDK, which returns 'control' treatment.
|
|
@@ -86,7 +89,7 @@
|
|
|
86
89
|
- Bugfixing - Removed js-yaml dependency 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).
|
|
87
90
|
|
|
88
91
|
1.5.0 (June 29, 2022)
|
|
89
|
-
- 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
|
|
92
|
+
- 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 SDK 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.
|
|
90
93
|
- Updated telemetry logic to track the anonymous config for user consent flag set to declined or unknown.
|
|
91
94
|
- Updated submitters logic, to avoid duplicating the post of impressions to Split cloud when the SDK is destroyed while its periodic post of impressions is running.
|
|
92
95
|
|
package/cjs/services/splitApi.js
CHANGED
|
@@ -20,6 +20,7 @@ function splitApiFactory(settings, platform, telemetryTracker) {
|
|
|
20
20
|
var urls = settings.urls;
|
|
21
21
|
var filterQueryString = settings.sync.__splitFiltersValidation && settings.sync.__splitFiltersValidation.queryString;
|
|
22
22
|
var SplitSDKImpressionsMode = settings.sync.impressionsMode;
|
|
23
|
+
var flagSpecVersion = settings.sync.flagSpecVersion;
|
|
23
24
|
var splitHttpClient = (0, splitHttpClient_1.splitHttpClientFactory)(settings, platform.getFetch);
|
|
24
25
|
return {
|
|
25
26
|
// @TODO throw errors if health check requests fail, to log them in the Synchronizer
|
|
@@ -32,7 +33,7 @@ function splitApiFactory(settings, platform, telemetryTracker) {
|
|
|
32
33
|
return splitHttpClient(url).then(function () { return true; }).catch(function () { return false; });
|
|
33
34
|
},
|
|
34
35
|
fetchAuth: function (userMatchingKeys) {
|
|
35
|
-
var url = urls.auth + "/v2/auth?s=" +
|
|
36
|
+
var url = urls.auth + "/v2/auth?s=" + flagSpecVersion;
|
|
36
37
|
if (userMatchingKeys) { // `userMatchingKeys` is undefined in server-side
|
|
37
38
|
var queryParams = userMatchingKeys.map(userKeyToQueryParam).join('&');
|
|
38
39
|
if (queryParams)
|
|
@@ -41,7 +42,7 @@ function splitApiFactory(settings, platform, telemetryTracker) {
|
|
|
41
42
|
return splitHttpClient(url, undefined, telemetryTracker.trackHttp(constants_1.TOKEN));
|
|
42
43
|
},
|
|
43
44
|
fetchSplitChanges: function (since, noCache, till) {
|
|
44
|
-
var url = urls.sdk + "/splitChanges?s=" +
|
|
45
|
+
var url = urls.sdk + "/splitChanges?s=" + flagSpecVersion + "&since=" + since + (filterQueryString || '') + (till ? '&till=' + till : '');
|
|
45
46
|
return splitHttpClient(url, noCache ? noCacheHeaderOptions : undefined, telemetryTracker.trackHttp(constants_1.SPLITS))
|
|
46
47
|
.catch(function (err) {
|
|
47
48
|
if (err.statusCode === 414)
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.getStorageHash = exports.KeyBuilder = exports.validatePrefix = void 0;
|
|
4
|
-
var constants_1 = require("../utils/constants");
|
|
5
4
|
var lang_1 = require("../utils/lang");
|
|
6
5
|
var murmur3_1 = require("../utils/murmur3/murmur3");
|
|
7
6
|
var everythingAtTheEnd = /[^.]+$/;
|
|
@@ -71,6 +70,6 @@ exports.KeyBuilder = KeyBuilder;
|
|
|
71
70
|
* The hash is in hexadecimal format (8 characters max, 32 bits).
|
|
72
71
|
*/
|
|
73
72
|
function getStorageHash(settings) {
|
|
74
|
-
return (0, murmur3_1.hash)(settings.core.authorizationKey + "::" + settings.sync.__splitFiltersValidation.queryString + "::" +
|
|
73
|
+
return (0, murmur3_1.hash)(settings.core.authorizationKey + "::" + settings.sync.__splitFiltersValidation.queryString + "::" + settings.sync.flagSpecVersion).toString(16);
|
|
75
74
|
}
|
|
76
75
|
exports.getStorageHash = getStorageHash;
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.SSEClient = void 0;
|
|
4
4
|
var lang_1 = require("../../../utils/lang");
|
|
5
|
-
var
|
|
5
|
+
var ABLY_API_VERSION = '1.1';
|
|
6
6
|
var CONTROL_CHANNEL_REGEX = /^control_/;
|
|
7
7
|
/**
|
|
8
8
|
* Build metadata headers for SSE connection.
|
|
@@ -60,7 +60,7 @@ var SSEClient = /** @class */ (function () {
|
|
|
60
60
|
var params = CONTROL_CHANNEL_REGEX.test(channel) ? '[?occupancy=metrics.publishers]' : '';
|
|
61
61
|
return encodeURIComponent(params + channel);
|
|
62
62
|
}).join(',');
|
|
63
|
-
var url = this.streamingUrl + "?channels=" + channelsQueryParam + "&accessToken=" + authToken.token + "&v=" +
|
|
63
|
+
var url = this.streamingUrl + "?channels=" + channelsQueryParam + "&accessToken=" + authToken.token + "&v=" + ABLY_API_VERSION + "&heartbeats=true"; // same results using `&heartbeats=false`
|
|
64
64
|
this.connection = new this.eventSource(
|
|
65
65
|
// For client-side SDKs, SplitSDKClientKey and SplitSDKClientKey metadata is passed as query params,
|
|
66
66
|
// because native EventSource implementations for browser doesn't support headers.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.MY_SEGMENT = exports.SEGMENT = exports.TOKEN = exports.TELEMETRY = exports.EVENTS = exports.IMPRESSIONS_COUNT = exports.IMPRESSIONS = exports.SPLITS = exports.NONE_ENUM = exports.DEBUG_ENUM = exports.OPTIMIZED_ENUM = exports.CONSUMER_PARTIAL_ENUM = exports.CONSUMER_ENUM = exports.STANDALONE_ENUM = exports.DEDUPED = exports.DROPPED = exports.QUEUED = exports.NAMES_FN_LABEL = exports.SPLITS_FN_LABEL = exports.SPLIT_FN_LABEL = exports.TRACK_FN_LABEL = exports.GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SETS = exports.GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SET = exports.GET_TREATMENTS_BY_FLAG_SETS = exports.GET_TREATMENTS_BY_FLAG_SET = exports.GET_TREATMENTS_WITH_CONFIG = exports.GET_TREATMENT_WITH_CONFIG = exports.GET_TREATMENTS = exports.GET_TREATMENT = exports.CONSENT_UNKNOWN = exports.CONSENT_DECLINED = exports.CONSENT_GRANTED = exports.STORAGE_PLUGGABLE = exports.STORAGE_REDIS = exports.STORAGE_LOCALSTORAGE = exports.STORAGE_MEMORY = exports.CONSUMER_PARTIAL_MODE = exports.CONSUMER_MODE = exports.PRODUCER_MODE = exports.STANDALONE_MODE = exports.LOCALHOST_MODE = exports.NONE = exports.OPTIMIZED = exports.DEBUG = exports.SPLIT_EVENT = exports.SPLIT_IMPRESSION = exports.NA = exports.UNKNOWN = exports.CONTROL_WITH_CONFIG = exports.CONTROL = void 0;
|
|
4
|
-
exports.
|
|
4
|
+
exports.FLAG_SPEC_VERSION = exports.PAUSED = exports.ENABLED = exports.DISABLED = exports.NON_REQUESTED = exports.REQUESTED = exports.POLLING = exports.STREAMING = exports.AUTH_REJECTION = exports.SYNC_MODE_UPDATE = exports.ABLY_ERROR = exports.TOKEN_REFRESH = exports.SSE_CONNECTION_ERROR = exports.STREAMING_STATUS = exports.OCCUPANCY_SEC = exports.OCCUPANCY_PRI = exports.CONNECTION_ESTABLISHED = exports.TRACK = exports.TREATMENTS_WITH_CONFIG_BY_FLAGSETS = exports.TREATMENTS_WITH_CONFIG_BY_FLAGSET = exports.TREATMENTS_BY_FLAGSETS = exports.TREATMENTS_BY_FLAGSET = exports.TREATMENTS_WITH_CONFIG = exports.TREATMENT_WITH_CONFIG = exports.TREATMENTS = exports.TREATMENT = void 0;
|
|
5
5
|
// Special treatments
|
|
6
6
|
exports.CONTROL = 'control';
|
|
7
7
|
exports.CONTROL_WITH_CONFIG = {
|
|
@@ -90,4 +90,4 @@ exports.NON_REQUESTED = 1;
|
|
|
90
90
|
exports.DISABLED = 0;
|
|
91
91
|
exports.ENABLED = 1;
|
|
92
92
|
exports.PAUSED = 2;
|
|
93
|
-
exports.
|
|
93
|
+
exports.FLAG_SPEC_VERSION = '1.1';
|
|
@@ -74,7 +74,8 @@ exports.base = {
|
|
|
74
74
|
// impressions collection mode
|
|
75
75
|
impressionsMode: constants_1.OPTIMIZED,
|
|
76
76
|
localhostMode: undefined,
|
|
77
|
-
enabled: true
|
|
77
|
+
enabled: true,
|
|
78
|
+
flagSpecVersion: constants_1.FLAG_SPEC_VERSION
|
|
78
79
|
},
|
|
79
80
|
// Logger
|
|
80
81
|
log: undefined
|
|
@@ -90,7 +91,7 @@ function fromSecondsToMillis(n) {
|
|
|
90
91
|
* @param validationParams defaults and fields validators used to validate and creates a settings object from a given config
|
|
91
92
|
*/
|
|
92
93
|
function settingsValidation(config, validationParams) {
|
|
93
|
-
var defaults = validationParams.defaults, runtime = validationParams.runtime, storage = validationParams.storage, integrations = validationParams.integrations, logger = validationParams.logger, localhost = validationParams.localhost, consent = validationParams.consent;
|
|
94
|
+
var defaults = validationParams.defaults, runtime = validationParams.runtime, storage = validationParams.storage, integrations = validationParams.integrations, logger = validationParams.logger, localhost = validationParams.localhost, consent = validationParams.consent, flagSpec = validationParams.flagSpec;
|
|
94
95
|
// creates a settings object merging base, defaults and config objects.
|
|
95
96
|
var withDefaults = (0, lang_1.merge)({}, exports.base, defaults, config);
|
|
96
97
|
// ensure a valid logger.
|
|
@@ -98,7 +99,8 @@ function settingsValidation(config, validationParams) {
|
|
|
98
99
|
var log = logger(withDefaults); // @ts-ignore, modify readonly prop
|
|
99
100
|
withDefaults.log = log;
|
|
100
101
|
// ensure a valid impressionsMode
|
|
101
|
-
|
|
102
|
+
var sync = withDefaults.sync;
|
|
103
|
+
sync.impressionsMode = (0, impressionsMode_1.validImpressionsMode)(log, sync.impressionsMode);
|
|
102
104
|
function validateMinValue(paramName, actualValue, minValue) {
|
|
103
105
|
if (actualValue >= minValue)
|
|
104
106
|
return actualValue;
|
|
@@ -114,7 +116,7 @@ function settingsValidation(config, validationParams) {
|
|
|
114
116
|
scheduler.eventsPushRate = fromSecondsToMillis(scheduler.eventsPushRate);
|
|
115
117
|
scheduler.telemetryRefreshRate = fromSecondsToMillis(validateMinValue('telemetryRefreshRate', scheduler.telemetryRefreshRate, 60));
|
|
116
118
|
// Default impressionsRefreshRate for DEBUG mode is 60 secs
|
|
117
|
-
if ((0, lang_1.get)(config, 'scheduler.impressionsRefreshRate') === undefined &&
|
|
119
|
+
if ((0, lang_1.get)(config, 'scheduler.impressionsRefreshRate') === undefined && sync.impressionsMode === constants_1.DEBUG)
|
|
118
120
|
scheduler.impressionsRefreshRate = 60;
|
|
119
121
|
scheduler.impressionsRefreshRate = fromSecondsToMillis(scheduler.impressionsRefreshRate);
|
|
120
122
|
// Log deprecation for old telemetry param
|
|
@@ -166,22 +168,23 @@ function settingsValidation(config, validationParams) {
|
|
|
166
168
|
if (integrations)
|
|
167
169
|
withDefaults.integrations = integrations(withDefaults);
|
|
168
170
|
if (localhost)
|
|
169
|
-
|
|
171
|
+
sync.localhostMode = localhost(withDefaults);
|
|
170
172
|
// validate push options
|
|
171
173
|
if (withDefaults.streamingEnabled !== false) { // @ts-ignore, modify readonly prop
|
|
172
174
|
withDefaults.streamingEnabled = true;
|
|
173
175
|
// Backoff bases.
|
|
174
|
-
// We are not checking if bases are positive numbers. Thus, we might be
|
|
176
|
+
// We are not checking if bases are positive numbers. Thus, we might be re-authenticating immediately (`setTimeout` with NaN or negative number)
|
|
175
177
|
scheduler.pushRetryBackoffBase = fromSecondsToMillis(scheduler.pushRetryBackoffBase);
|
|
176
178
|
}
|
|
177
179
|
// validate sync enabled
|
|
178
|
-
if (
|
|
179
|
-
|
|
180
|
+
if (sync.enabled !== false) {
|
|
181
|
+
sync.enabled = true;
|
|
180
182
|
}
|
|
181
183
|
// validate the `splitFilters` settings and parse splits query
|
|
182
|
-
var splitFiltersValidation = (0, splitFilters_1.validateSplitFilters)(log,
|
|
183
|
-
|
|
184
|
-
|
|
184
|
+
var splitFiltersValidation = (0, splitFilters_1.validateSplitFilters)(log, sync.splitFilters, withDefaults.mode);
|
|
185
|
+
sync.splitFilters = splitFiltersValidation.validFilters;
|
|
186
|
+
sync.__splitFiltersValidation = splitFiltersValidation;
|
|
187
|
+
sync.flagSpecVersion = flagSpec ? flagSpec(withDefaults) : constants_1.FLAG_SPEC_VERSION;
|
|
185
188
|
// ensure a valid user consent value
|
|
186
189
|
// @ts-ignore, modify readonly prop
|
|
187
190
|
withDefaults.userConsent = consent(withDefaults);
|
package/esm/services/splitApi.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { splitHttpClientFactory } from './splitHttpClient';
|
|
2
2
|
import { objectAssign } from '../utils/lang/objectAssign';
|
|
3
|
-
import { SPLITS, IMPRESSIONS, IMPRESSIONS_COUNT, EVENTS, TELEMETRY, TOKEN, SEGMENT, MY_SEGMENT
|
|
3
|
+
import { SPLITS, IMPRESSIONS, IMPRESSIONS_COUNT, EVENTS, TELEMETRY, TOKEN, SEGMENT, MY_SEGMENT } from '../utils/constants';
|
|
4
4
|
import { ERROR_TOO_MANY_SETS } from '../logger/constants';
|
|
5
5
|
var noCacheHeaderOptions = { headers: { 'Cache-Control': 'no-cache' } };
|
|
6
6
|
function userKeyToQueryParam(userKey) {
|
|
@@ -17,6 +17,7 @@ export function splitApiFactory(settings, platform, telemetryTracker) {
|
|
|
17
17
|
var urls = settings.urls;
|
|
18
18
|
var filterQueryString = settings.sync.__splitFiltersValidation && settings.sync.__splitFiltersValidation.queryString;
|
|
19
19
|
var SplitSDKImpressionsMode = settings.sync.impressionsMode;
|
|
20
|
+
var flagSpecVersion = settings.sync.flagSpecVersion;
|
|
20
21
|
var splitHttpClient = splitHttpClientFactory(settings, platform.getFetch);
|
|
21
22
|
return {
|
|
22
23
|
// @TODO throw errors if health check requests fail, to log them in the Synchronizer
|
|
@@ -29,7 +30,7 @@ export function splitApiFactory(settings, platform, telemetryTracker) {
|
|
|
29
30
|
return splitHttpClient(url).then(function () { return true; }).catch(function () { return false; });
|
|
30
31
|
},
|
|
31
32
|
fetchAuth: function (userMatchingKeys) {
|
|
32
|
-
var url = urls.auth + "/v2/auth?s=" +
|
|
33
|
+
var url = urls.auth + "/v2/auth?s=" + flagSpecVersion;
|
|
33
34
|
if (userMatchingKeys) { // `userMatchingKeys` is undefined in server-side
|
|
34
35
|
var queryParams = userMatchingKeys.map(userKeyToQueryParam).join('&');
|
|
35
36
|
if (queryParams)
|
|
@@ -38,7 +39,7 @@ export function splitApiFactory(settings, platform, telemetryTracker) {
|
|
|
38
39
|
return splitHttpClient(url, undefined, telemetryTracker.trackHttp(TOKEN));
|
|
39
40
|
},
|
|
40
41
|
fetchSplitChanges: function (since, noCache, till) {
|
|
41
|
-
var url = urls.sdk + "/splitChanges?s=" +
|
|
42
|
+
var url = urls.sdk + "/splitChanges?s=" + flagSpecVersion + "&since=" + since + (filterQueryString || '') + (till ? '&till=' + till : '');
|
|
42
43
|
return splitHttpClient(url, noCache ? noCacheHeaderOptions : undefined, telemetryTracker.trackHttp(SPLITS))
|
|
43
44
|
.catch(function (err) {
|
|
44
45
|
if (err.statusCode === 414)
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { FLAGS_SPEC } from '../utils/constants';
|
|
2
1
|
import { startsWith } from '../utils/lang';
|
|
3
2
|
import { hash } from '../utils/murmur3/murmur3';
|
|
4
3
|
var everythingAtTheEnd = /[^.]+$/;
|
|
@@ -67,5 +66,5 @@ export { KeyBuilder };
|
|
|
67
66
|
* The hash is in hexadecimal format (8 characters max, 32 bits).
|
|
68
67
|
*/
|
|
69
68
|
export function getStorageHash(settings) {
|
|
70
|
-
return hash(settings.core.authorizationKey + "::" + settings.sync.__splitFiltersValidation.queryString + "::" +
|
|
69
|
+
return hash(settings.core.authorizationKey + "::" + settings.sync.__splitFiltersValidation.queryString + "::" + settings.sync.flagSpecVersion).toString(16);
|
|
71
70
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { isString } from '../../../utils/lang';
|
|
2
|
-
var
|
|
2
|
+
var ABLY_API_VERSION = '1.1';
|
|
3
3
|
var CONTROL_CHANNEL_REGEX = /^control_/;
|
|
4
4
|
/**
|
|
5
5
|
* Build metadata headers for SSE connection.
|
|
@@ -57,7 +57,7 @@ var SSEClient = /** @class */ (function () {
|
|
|
57
57
|
var params = CONTROL_CHANNEL_REGEX.test(channel) ? '[?occupancy=metrics.publishers]' : '';
|
|
58
58
|
return encodeURIComponent(params + channel);
|
|
59
59
|
}).join(',');
|
|
60
|
-
var url = this.streamingUrl + "?channels=" + channelsQueryParam + "&accessToken=" + authToken.token + "&v=" +
|
|
60
|
+
var url = this.streamingUrl + "?channels=" + channelsQueryParam + "&accessToken=" + authToken.token + "&v=" + ABLY_API_VERSION + "&heartbeats=true"; // same results using `&heartbeats=false`
|
|
61
61
|
this.connection = new this.eventSource(
|
|
62
62
|
// For client-side SDKs, SplitSDKClientKey and SplitSDKClientKey metadata is passed as query params,
|
|
63
63
|
// because native EventSource implementations for browser doesn't support headers.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { merge, get } from '../lang';
|
|
2
2
|
import { validateMode } from './mode';
|
|
3
3
|
import { validateSplitFilters } from './splitFilters';
|
|
4
|
-
import { STANDALONE_MODE, OPTIMIZED, LOCALHOST_MODE, DEBUG } from '../constants';
|
|
4
|
+
import { STANDALONE_MODE, OPTIMIZED, LOCALHOST_MODE, DEBUG, FLAG_SPEC_VERSION } from '../constants';
|
|
5
5
|
import { validImpressionsMode } from './impressionsMode';
|
|
6
6
|
import { validateKey } from '../inputValidation/key';
|
|
7
7
|
import { validateTrafficType } from '../inputValidation/trafficType';
|
|
@@ -71,7 +71,8 @@ export var base = {
|
|
|
71
71
|
// impressions collection mode
|
|
72
72
|
impressionsMode: OPTIMIZED,
|
|
73
73
|
localhostMode: undefined,
|
|
74
|
-
enabled: true
|
|
74
|
+
enabled: true,
|
|
75
|
+
flagSpecVersion: FLAG_SPEC_VERSION
|
|
75
76
|
},
|
|
76
77
|
// Logger
|
|
77
78
|
log: undefined
|
|
@@ -87,7 +88,7 @@ function fromSecondsToMillis(n) {
|
|
|
87
88
|
* @param validationParams defaults and fields validators used to validate and creates a settings object from a given config
|
|
88
89
|
*/
|
|
89
90
|
export function settingsValidation(config, validationParams) {
|
|
90
|
-
var defaults = validationParams.defaults, runtime = validationParams.runtime, storage = validationParams.storage, integrations = validationParams.integrations, logger = validationParams.logger, localhost = validationParams.localhost, consent = validationParams.consent;
|
|
91
|
+
var defaults = validationParams.defaults, runtime = validationParams.runtime, storage = validationParams.storage, integrations = validationParams.integrations, logger = validationParams.logger, localhost = validationParams.localhost, consent = validationParams.consent, flagSpec = validationParams.flagSpec;
|
|
91
92
|
// creates a settings object merging base, defaults and config objects.
|
|
92
93
|
var withDefaults = merge({}, base, defaults, config);
|
|
93
94
|
// ensure a valid logger.
|
|
@@ -95,7 +96,8 @@ export function settingsValidation(config, validationParams) {
|
|
|
95
96
|
var log = logger(withDefaults); // @ts-ignore, modify readonly prop
|
|
96
97
|
withDefaults.log = log;
|
|
97
98
|
// ensure a valid impressionsMode
|
|
98
|
-
|
|
99
|
+
var sync = withDefaults.sync;
|
|
100
|
+
sync.impressionsMode = validImpressionsMode(log, sync.impressionsMode);
|
|
99
101
|
function validateMinValue(paramName, actualValue, minValue) {
|
|
100
102
|
if (actualValue >= minValue)
|
|
101
103
|
return actualValue;
|
|
@@ -111,7 +113,7 @@ export function settingsValidation(config, validationParams) {
|
|
|
111
113
|
scheduler.eventsPushRate = fromSecondsToMillis(scheduler.eventsPushRate);
|
|
112
114
|
scheduler.telemetryRefreshRate = fromSecondsToMillis(validateMinValue('telemetryRefreshRate', scheduler.telemetryRefreshRate, 60));
|
|
113
115
|
// Default impressionsRefreshRate for DEBUG mode is 60 secs
|
|
114
|
-
if (get(config, 'scheduler.impressionsRefreshRate') === undefined &&
|
|
116
|
+
if (get(config, 'scheduler.impressionsRefreshRate') === undefined && sync.impressionsMode === DEBUG)
|
|
115
117
|
scheduler.impressionsRefreshRate = 60;
|
|
116
118
|
scheduler.impressionsRefreshRate = fromSecondsToMillis(scheduler.impressionsRefreshRate);
|
|
117
119
|
// Log deprecation for old telemetry param
|
|
@@ -163,22 +165,23 @@ export function settingsValidation(config, validationParams) {
|
|
|
163
165
|
if (integrations)
|
|
164
166
|
withDefaults.integrations = integrations(withDefaults);
|
|
165
167
|
if (localhost)
|
|
166
|
-
|
|
168
|
+
sync.localhostMode = localhost(withDefaults);
|
|
167
169
|
// validate push options
|
|
168
170
|
if (withDefaults.streamingEnabled !== false) { // @ts-ignore, modify readonly prop
|
|
169
171
|
withDefaults.streamingEnabled = true;
|
|
170
172
|
// Backoff bases.
|
|
171
|
-
// We are not checking if bases are positive numbers. Thus, we might be
|
|
173
|
+
// We are not checking if bases are positive numbers. Thus, we might be re-authenticating immediately (`setTimeout` with NaN or negative number)
|
|
172
174
|
scheduler.pushRetryBackoffBase = fromSecondsToMillis(scheduler.pushRetryBackoffBase);
|
|
173
175
|
}
|
|
174
176
|
// validate sync enabled
|
|
175
|
-
if (
|
|
176
|
-
|
|
177
|
+
if (sync.enabled !== false) {
|
|
178
|
+
sync.enabled = true;
|
|
177
179
|
}
|
|
178
180
|
// validate the `splitFilters` settings and parse splits query
|
|
179
|
-
var splitFiltersValidation = validateSplitFilters(log,
|
|
180
|
-
|
|
181
|
-
|
|
181
|
+
var splitFiltersValidation = validateSplitFilters(log, sync.splitFilters, withDefaults.mode);
|
|
182
|
+
sync.splitFilters = splitFiltersValidation.validFilters;
|
|
183
|
+
sync.__splitFiltersValidation = splitFiltersValidation;
|
|
184
|
+
sync.flagSpecVersion = flagSpec ? flagSpec(withDefaults) : FLAG_SPEC_VERSION;
|
|
182
185
|
// ensure a valid user consent value
|
|
183
186
|
// @ts-ignore, modify readonly prop
|
|
184
187
|
withDefaults.userConsent = consent(withDefaults);
|
package/package.json
CHANGED
package/src/services/splitApi.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { splitHttpClientFactory } from './splitHttpClient';
|
|
|
4
4
|
import { ISplitApi } from './types';
|
|
5
5
|
import { objectAssign } from '../utils/lang/objectAssign';
|
|
6
6
|
import { ITelemetryTracker } from '../trackers/types';
|
|
7
|
-
import { SPLITS, IMPRESSIONS, IMPRESSIONS_COUNT, EVENTS, TELEMETRY, TOKEN, SEGMENT, MY_SEGMENT
|
|
7
|
+
import { SPLITS, IMPRESSIONS, IMPRESSIONS_COUNT, EVENTS, TELEMETRY, TOKEN, SEGMENT, MY_SEGMENT } from '../utils/constants';
|
|
8
8
|
import { ERROR_TOO_MANY_SETS } from '../logger/constants';
|
|
9
9
|
|
|
10
10
|
const noCacheHeaderOptions = { headers: { 'Cache-Control': 'no-cache' } };
|
|
@@ -29,6 +29,7 @@ export function splitApiFactory(
|
|
|
29
29
|
const urls = settings.urls;
|
|
30
30
|
const filterQueryString = settings.sync.__splitFiltersValidation && settings.sync.__splitFiltersValidation.queryString;
|
|
31
31
|
const SplitSDKImpressionsMode = settings.sync.impressionsMode;
|
|
32
|
+
const flagSpecVersion = settings.sync.flagSpecVersion;
|
|
32
33
|
const splitHttpClient = splitHttpClientFactory(settings, platform.getFetch);
|
|
33
34
|
|
|
34
35
|
return {
|
|
@@ -44,7 +45,7 @@ export function splitApiFactory(
|
|
|
44
45
|
},
|
|
45
46
|
|
|
46
47
|
fetchAuth(userMatchingKeys?: string[]) {
|
|
47
|
-
let url = `${urls.auth}/v2/auth?s=${
|
|
48
|
+
let url = `${urls.auth}/v2/auth?s=${flagSpecVersion}`;
|
|
48
49
|
if (userMatchingKeys) { // `userMatchingKeys` is undefined in server-side
|
|
49
50
|
const queryParams = userMatchingKeys.map(userKeyToQueryParam).join('&');
|
|
50
51
|
if (queryParams) url += '&' + queryParams;
|
|
@@ -53,7 +54,7 @@ export function splitApiFactory(
|
|
|
53
54
|
},
|
|
54
55
|
|
|
55
56
|
fetchSplitChanges(since: number, noCache?: boolean, till?: number) {
|
|
56
|
-
const url = `${urls.sdk}/splitChanges?s=${
|
|
57
|
+
const url = `${urls.sdk}/splitChanges?s=${flagSpecVersion}&since=${since}${filterQueryString || ''}${till ? '&till=' + till : ''}`;
|
|
57
58
|
return splitHttpClient(url, noCache ? noCacheHeaderOptions : undefined, telemetryTracker.trackHttp(SPLITS))
|
|
58
59
|
.catch((err) => {
|
|
59
60
|
if (err.statusCode === 414) settings.log.error(ERROR_TOO_MANY_SETS);
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { ISettings } from '../types';
|
|
2
|
-
import { FLAGS_SPEC } from '../utils/constants';
|
|
3
2
|
import { startsWith } from '../utils/lang';
|
|
4
3
|
import { hash } from '../utils/murmur3/murmur3';
|
|
5
4
|
|
|
@@ -86,5 +85,5 @@ export class KeyBuilder {
|
|
|
86
85
|
* The hash is in hexadecimal format (8 characters max, 32 bits).
|
|
87
86
|
*/
|
|
88
87
|
export function getStorageHash(settings: ISettings) {
|
|
89
|
-
return hash(`${settings.core.authorizationKey}::${settings.sync.__splitFiltersValidation.queryString}::${
|
|
88
|
+
return hash(`${settings.core.authorizationKey}::${settings.sync.__splitFiltersValidation.queryString}::${settings.sync.flagSpecVersion}`).toString(16);
|
|
90
89
|
}
|
|
@@ -4,7 +4,7 @@ import { isString } from '../../../utils/lang';
|
|
|
4
4
|
import { IAuthTokenPushEnabled } from '../AuthClient/types';
|
|
5
5
|
import { ISSEClient, ISseEventHandler } from './types';
|
|
6
6
|
|
|
7
|
-
const
|
|
7
|
+
const ABLY_API_VERSION = '1.1';
|
|
8
8
|
|
|
9
9
|
const CONTROL_CHANNEL_REGEX = /^control_/;
|
|
10
10
|
|
|
@@ -78,7 +78,7 @@ export class SSEClient implements ISSEClient {
|
|
|
78
78
|
return encodeURIComponent(params + channel);
|
|
79
79
|
}
|
|
80
80
|
).join(',');
|
|
81
|
-
const url = `${this.streamingUrl}?channels=${channelsQueryParam}&accessToken=${authToken.token}&v=${
|
|
81
|
+
const url = `${this.streamingUrl}?channels=${channelsQueryParam}&accessToken=${authToken.token}&v=${ABLY_API_VERSION}&heartbeats=true`; // same results using `&heartbeats=false`
|
|
82
82
|
|
|
83
83
|
this.connection = new this.eventSource!(
|
|
84
84
|
// For client-side SDKs, SplitSDKClientKey and SplitSDKClientKey metadata is passed as query params,
|
package/src/types.ts
CHANGED
|
@@ -118,7 +118,8 @@ export interface ISettings {
|
|
|
118
118
|
impressionsMode: SplitIO.ImpressionsMode,
|
|
119
119
|
__splitFiltersValidation: ISplitFiltersValidation,
|
|
120
120
|
localhostMode?: SplitIO.LocalhostFactory,
|
|
121
|
-
enabled: boolean
|
|
121
|
+
enabled: boolean,
|
|
122
|
+
flagSpecVersion: string
|
|
122
123
|
},
|
|
123
124
|
readonly runtime: {
|
|
124
125
|
ip: string | false
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { merge, get } from '../lang';
|
|
2
2
|
import { validateMode } from './mode';
|
|
3
3
|
import { validateSplitFilters } from './splitFilters';
|
|
4
|
-
import { STANDALONE_MODE, OPTIMIZED, LOCALHOST_MODE, DEBUG } from '../constants';
|
|
4
|
+
import { STANDALONE_MODE, OPTIMIZED, LOCALHOST_MODE, DEBUG, FLAG_SPEC_VERSION } from '../constants';
|
|
5
5
|
import { validImpressionsMode } from './impressionsMode';
|
|
6
6
|
import { ISettingsValidationParams } from './types';
|
|
7
7
|
import { ISettings } from '../../types';
|
|
@@ -84,7 +84,8 @@ export const base = {
|
|
|
84
84
|
// impressions collection mode
|
|
85
85
|
impressionsMode: OPTIMIZED,
|
|
86
86
|
localhostMode: undefined,
|
|
87
|
-
enabled: true
|
|
87
|
+
enabled: true,
|
|
88
|
+
flagSpecVersion: FLAG_SPEC_VERSION
|
|
88
89
|
},
|
|
89
90
|
|
|
90
91
|
// Logger
|
|
@@ -104,7 +105,7 @@ function fromSecondsToMillis(n: number) {
|
|
|
104
105
|
*/
|
|
105
106
|
export function settingsValidation(config: unknown, validationParams: ISettingsValidationParams) {
|
|
106
107
|
|
|
107
|
-
const { defaults, runtime, storage, integrations, logger, localhost, consent } = validationParams;
|
|
108
|
+
const { defaults, runtime, storage, integrations, logger, localhost, consent, flagSpec } = validationParams;
|
|
108
109
|
|
|
109
110
|
// creates a settings object merging base, defaults and config objects.
|
|
110
111
|
const withDefaults = merge({}, base, defaults, config) as ISettings;
|
|
@@ -115,7 +116,8 @@ export function settingsValidation(config: unknown, validationParams: ISettingsV
|
|
|
115
116
|
withDefaults.log = log;
|
|
116
117
|
|
|
117
118
|
// ensure a valid impressionsMode
|
|
118
|
-
|
|
119
|
+
const sync = withDefaults.sync;
|
|
120
|
+
sync.impressionsMode = validImpressionsMode(log, sync.impressionsMode);
|
|
119
121
|
|
|
120
122
|
function validateMinValue(paramName: string, actualValue: number, minValue: number) {
|
|
121
123
|
if (actualValue >= minValue) return actualValue;
|
|
@@ -133,7 +135,7 @@ export function settingsValidation(config: unknown, validationParams: ISettingsV
|
|
|
133
135
|
scheduler.telemetryRefreshRate = fromSecondsToMillis(validateMinValue('telemetryRefreshRate', scheduler.telemetryRefreshRate, 60));
|
|
134
136
|
|
|
135
137
|
// Default impressionsRefreshRate for DEBUG mode is 60 secs
|
|
136
|
-
if (get(config, 'scheduler.impressionsRefreshRate') === undefined &&
|
|
138
|
+
if (get(config, 'scheduler.impressionsRefreshRate') === undefined && sync.impressionsMode === DEBUG) scheduler.impressionsRefreshRate = 60;
|
|
137
139
|
scheduler.impressionsRefreshRate = fromSecondsToMillis(scheduler.impressionsRefreshRate);
|
|
138
140
|
|
|
139
141
|
// Log deprecation for old telemetry param
|
|
@@ -186,25 +188,26 @@ export function settingsValidation(config: unknown, validationParams: ISettingsV
|
|
|
186
188
|
// @ts-ignore, modify readonly prop
|
|
187
189
|
if (integrations) withDefaults.integrations = integrations(withDefaults);
|
|
188
190
|
|
|
189
|
-
if (localhost)
|
|
191
|
+
if (localhost) sync.localhostMode = localhost(withDefaults);
|
|
190
192
|
|
|
191
193
|
// validate push options
|
|
192
194
|
if (withDefaults.streamingEnabled !== false) { // @ts-ignore, modify readonly prop
|
|
193
195
|
withDefaults.streamingEnabled = true;
|
|
194
196
|
// Backoff bases.
|
|
195
|
-
// We are not checking if bases are positive numbers. Thus, we might be
|
|
197
|
+
// We are not checking if bases are positive numbers. Thus, we might be re-authenticating immediately (`setTimeout` with NaN or negative number)
|
|
196
198
|
scheduler.pushRetryBackoffBase = fromSecondsToMillis(scheduler.pushRetryBackoffBase);
|
|
197
199
|
}
|
|
198
200
|
|
|
199
201
|
// validate sync enabled
|
|
200
|
-
if (
|
|
201
|
-
|
|
202
|
+
if (sync.enabled !== false) {
|
|
203
|
+
sync.enabled = true;
|
|
202
204
|
}
|
|
203
205
|
|
|
204
206
|
// validate the `splitFilters` settings and parse splits query
|
|
205
|
-
const splitFiltersValidation = validateSplitFilters(log,
|
|
206
|
-
|
|
207
|
-
|
|
207
|
+
const splitFiltersValidation = validateSplitFilters(log, sync.splitFilters, withDefaults.mode);
|
|
208
|
+
sync.splitFilters = splitFiltersValidation.validFilters;
|
|
209
|
+
sync.__splitFiltersValidation = splitFiltersValidation;
|
|
210
|
+
sync.flagSpecVersion = flagSpec ? flagSpec(withDefaults) : FLAG_SPEC_VERSION;
|
|
208
211
|
|
|
209
212
|
// ensure a valid user consent value
|
|
210
213
|
// @ts-ignore, modify readonly prop
|
|
@@ -26,4 +26,6 @@ export interface ISettingsValidationParams {
|
|
|
26
26
|
localhost?: (settings: ISettings) => ISettings['sync']['localhostMode'],
|
|
27
27
|
/** User consent validator (`settings.userConsent`) */
|
|
28
28
|
consent: (settings: ISettings) => ISettings['userConsent'],
|
|
29
|
+
/** Flag spec version validation. Configurable by the JS Synchronizer but not by the SDKs */
|
|
30
|
+
flagSpec?: (settings: ISettings) => ISettings['sync']['flagSpecVersion']
|
|
29
31
|
}
|
package/types/types.d.ts
CHANGED
|
@@ -29,4 +29,6 @@ export interface ISettingsValidationParams {
|
|
|
29
29
|
localhost?: (settings: ISettings) => ISettings['sync']['localhostMode'];
|
|
30
30
|
/** User consent validator (`settings.userConsent`) */
|
|
31
31
|
consent: (settings: ISettings) => ISettings['userConsent'];
|
|
32
|
+
/** Flag spec version validation. Configurable by the JS Synchronizer but not by the SDKs */
|
|
33
|
+
flagSpec?: (settings: ISettings) => ISettings['sync']['flagSpecVersion'];
|
|
32
34
|
}
|