@splitsoftware/splitio-commons 1.12.1-rc.3 → 1.12.1-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 +1 -1
- package/cjs/logger/constants.js +3 -3
- package/cjs/logger/messages/warn.js +2 -2
- package/cjs/sdkClient/client.js +27 -21
- package/cjs/sdkClient/clientInputValidation.js +21 -21
- package/cjs/sdkManager/index.js +13 -15
- package/cjs/storages/KeyBuilder.js +13 -1
- package/cjs/storages/KeyBuilderCS.js +1 -4
- package/cjs/storages/inLocalStorage/SplitsCacheInLocal.js +12 -16
- package/cjs/storages/inLocalStorage/index.js +1 -1
- package/cjs/storages/inRedis/SplitsCacheInRedis.js +2 -2
- package/cjs/storages/inRedis/index.js +1 -1
- package/cjs/storages/pluggable/SplitsCachePluggable.js +2 -2
- package/cjs/storages/pluggable/index.js +14 -3
- package/cjs/trackers/eventTracker.js +4 -4
- package/cjs/utils/constants/index.js +16 -2
- package/cjs/utils/lang/sets.js +3 -3
- package/cjs/utils/settingsValidation/index.js +1 -1
- package/cjs/utils/settingsValidation/mode.js +10 -3
- package/cjs/utils/settingsValidation/splitFilters.js +20 -19
- package/esm/logger/constants.js +2 -2
- package/esm/logger/messages/warn.js +2 -2
- package/esm/sdkClient/client.js +28 -22
- package/esm/sdkClient/clientInputValidation.js +23 -23
- package/esm/sdkManager/index.js +7 -9
- package/esm/storages/KeyBuilder.js +11 -0
- package/esm/storages/KeyBuilderCS.js +1 -4
- package/esm/storages/inLocalStorage/SplitsCacheInLocal.js +12 -16
- package/esm/storages/inLocalStorage/index.js +1 -1
- package/esm/storages/inRedis/SplitsCacheInRedis.js +3 -3
- package/esm/storages/inRedis/index.js +1 -1
- package/esm/storages/pluggable/SplitsCachePluggable.js +3 -3
- package/esm/storages/pluggable/index.js +15 -4
- package/esm/trackers/eventTracker.js +4 -4
- package/esm/utils/constants/index.js +14 -0
- package/esm/utils/lang/sets.js +1 -1
- package/esm/utils/settingsValidation/index.js +2 -2
- package/esm/utils/settingsValidation/mode.js +7 -1
- package/esm/utils/settingsValidation/splitFilters.js +11 -10
- package/package.json +1 -1
- package/src/logger/constants.ts +2 -2
- package/src/logger/messages/warn.ts +2 -2
- package/src/sdkClient/client.ts +26 -23
- package/src/sdkClient/clientInputValidation.ts +23 -23
- package/src/sdkManager/index.ts +7 -10
- package/src/storages/KeyBuilder.ts +14 -1
- package/src/storages/KeyBuilderCS.ts +1 -5
- package/src/storages/KeyBuilderSS.ts +4 -4
- package/src/storages/inLocalStorage/SplitsCacheInLocal.ts +16 -14
- package/src/storages/inLocalStorage/index.ts +1 -1
- package/src/storages/inRedis/SplitsCacheInRedis.ts +3 -3
- package/src/storages/inRedis/index.ts +1 -1
- package/src/storages/pluggable/SplitsCachePluggable.ts +3 -3
- package/src/storages/pluggable/index.ts +15 -5
- package/src/storages/types.ts +3 -3
- package/src/trackers/eventTracker.ts +4 -4
- package/src/utils/constants/index.ts +16 -0
- package/src/utils/lang/sets.ts +1 -1
- package/src/utils/murmur3/murmur3.ts +0 -1
- package/src/utils/settingsValidation/index.ts +2 -2
- package/src/utils/settingsValidation/mode.ts +8 -1
- package/src/utils/settingsValidation/splitFilters.ts +11 -10
- package/types/logger/constants.d.ts +2 -2
- package/types/storages/AbstractSplitsCache.d.ts +46 -0
- package/types/storages/KeyBuilder.d.ts +8 -1
- package/types/storages/KeyBuilderCS.d.ts +0 -1
- package/types/storages/KeyBuilderSS.d.ts +4 -4
- package/types/storages/inLocalStorage/SplitsCacheInLocal.d.ts +5 -5
- package/types/utils/constants/index.d.ts +12 -0
- package/types/utils/lang/sets.d.ts +1 -1
- package/types/utils/settingsValidation/mode.d.ts +5 -1
- package/types/utils/settingsValidation/splitFilters.d.ts +1 -1
- package/cjs/trackers/impressionObserver/utils.js +0 -11
- package/cjs/utils/redis/RedisMock.js +0 -31
- package/esm/trackers/impressionObserver/utils.js +0 -7
- package/esm/utils/redis/RedisMock.js +0 -28
- package/src/trackers/impressionObserver/utils.ts +0 -9
- package/src/utils/redis/RedisMock.ts +0 -31
|
@@ -12,12 +12,12 @@ import {
|
|
|
12
12
|
validateIfOperational
|
|
13
13
|
} from '../utils/inputValidation';
|
|
14
14
|
import { startsWith } from '../utils/lang';
|
|
15
|
-
import { CONTROL, CONTROL_WITH_CONFIG } from '../utils/constants';
|
|
15
|
+
import { CONTROL, CONTROL_WITH_CONFIG, GET_TREATMENT, GET_TREATMENTS, GET_TREATMENTS_BY_FLAG_SET, GET_TREATMENTS_BY_FLAG_SETS, GET_TREATMENTS_WITH_CONFIG, GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SET, GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SETS, GET_TREATMENT_WITH_CONFIG, TRACK_FN_LABEL } from '../utils/constants';
|
|
16
16
|
import { IReadinessManager } from '../readiness/types';
|
|
17
17
|
import { MaybeThenable } from '../dtos/types';
|
|
18
18
|
import { ISettings, SplitIO } from '../types';
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
19
|
+
import { isConsumerMode } from '../utils/settingsValidation/mode';
|
|
20
|
+
import { validateFlagSets } from '../utils/settingsValidation/splitFilters';
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
23
|
* Decorator that validates the input before actually executing the client methods.
|
|
@@ -25,14 +25,14 @@ import { flagSetsAreValid } from '../utils/settingsValidation/splitFilters';
|
|
|
25
25
|
*/
|
|
26
26
|
export function clientInputValidationDecorator<TClient extends SplitIO.IClient | SplitIO.IAsyncClient>(settings: ISettings, client: TClient, readinessManager: IReadinessManager): TClient {
|
|
27
27
|
|
|
28
|
-
const log = settings
|
|
29
|
-
const
|
|
28
|
+
const { log, mode } = settings;
|
|
29
|
+
const isAsync = isConsumerMode(mode);
|
|
30
30
|
|
|
31
31
|
/**
|
|
32
32
|
* Avoid repeating this validations code
|
|
33
33
|
*/
|
|
34
34
|
function validateEvaluationParams(maybeKey: SplitIO.SplitKey, maybeFeatureFlagNameOrNames: string | string[] | undefined, maybeAttributes: SplitIO.Attributes | undefined, methodName: string, maybeFlagSetNameOrNames?: string[]) {
|
|
35
|
-
const multi = startsWith(methodName,
|
|
35
|
+
const multi = startsWith(methodName, GET_TREATMENTS);
|
|
36
36
|
const key = validateKey(log, maybeKey, methodName);
|
|
37
37
|
let splitOrSplits: string | string[] | false = false;
|
|
38
38
|
let flagSetOrFlagSets: string[] = [];
|
|
@@ -42,7 +42,7 @@ export function clientInputValidationDecorator<TClient extends SplitIO.IClient |
|
|
|
42
42
|
const attributes = validateAttributes(log, maybeAttributes, methodName);
|
|
43
43
|
const isNotDestroyed = validateIfNotDestroyed(log, readinessManager, methodName);
|
|
44
44
|
if (maybeFlagSetNameOrNames) {
|
|
45
|
-
flagSetOrFlagSets =
|
|
45
|
+
flagSetOrFlagSets = validateFlagSets(log, methodName, maybeFlagSetNameOrNames, settings.sync.__splitFiltersValidation.groupedFilters.bySet);
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
validateIfOperational(log, readinessManager, methodName, splitOrSplits);
|
|
@@ -59,11 +59,11 @@ export function clientInputValidationDecorator<TClient extends SplitIO.IClient |
|
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
function wrapResult<T>(value: T): MaybeThenable<T> {
|
|
62
|
-
return
|
|
62
|
+
return isAsync ? Promise.resolve(value) : value;
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
function getTreatment(maybeKey: SplitIO.SplitKey, maybeFeatureFlagName: string, maybeAttributes?: SplitIO.Attributes) {
|
|
66
|
-
const params = validateEvaluationParams(maybeKey, maybeFeatureFlagName, maybeAttributes,
|
|
66
|
+
const params = validateEvaluationParams(maybeKey, maybeFeatureFlagName, maybeAttributes, GET_TREATMENT);
|
|
67
67
|
|
|
68
68
|
if (params.valid) {
|
|
69
69
|
return client.getTreatment(params.key as SplitIO.SplitKey, params.splitOrSplits as string, params.attributes as SplitIO.Attributes | undefined);
|
|
@@ -73,7 +73,7 @@ export function clientInputValidationDecorator<TClient extends SplitIO.IClient |
|
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
function getTreatmentWithConfig(maybeKey: SplitIO.SplitKey, maybeFeatureFlagName: string, maybeAttributes?: SplitIO.Attributes) {
|
|
76
|
-
const params = validateEvaluationParams(maybeKey, maybeFeatureFlagName, maybeAttributes,
|
|
76
|
+
const params = validateEvaluationParams(maybeKey, maybeFeatureFlagName, maybeAttributes, GET_TREATMENT_WITH_CONFIG);
|
|
77
77
|
|
|
78
78
|
if (params.valid) {
|
|
79
79
|
return client.getTreatmentWithConfig(params.key as SplitIO.SplitKey, params.splitOrSplits as string, params.attributes as SplitIO.Attributes | undefined);
|
|
@@ -83,7 +83,7 @@ export function clientInputValidationDecorator<TClient extends SplitIO.IClient |
|
|
|
83
83
|
}
|
|
84
84
|
|
|
85
85
|
function getTreatments(maybeKey: SplitIO.SplitKey, maybeFeatureFlagNames: string[], maybeAttributes?: SplitIO.Attributes) {
|
|
86
|
-
const params = validateEvaluationParams(maybeKey, maybeFeatureFlagNames, maybeAttributes,
|
|
86
|
+
const params = validateEvaluationParams(maybeKey, maybeFeatureFlagNames, maybeAttributes, GET_TREATMENTS);
|
|
87
87
|
|
|
88
88
|
if (params.valid) {
|
|
89
89
|
return client.getTreatments(params.key as SplitIO.SplitKey, params.splitOrSplits as string[], params.attributes as SplitIO.Attributes | undefined);
|
|
@@ -96,7 +96,7 @@ export function clientInputValidationDecorator<TClient extends SplitIO.IClient |
|
|
|
96
96
|
}
|
|
97
97
|
|
|
98
98
|
function getTreatmentsWithConfig(maybeKey: SplitIO.SplitKey, maybeFeatureFlagNames: string[], maybeAttributes?: SplitIO.Attributes) {
|
|
99
|
-
const params = validateEvaluationParams(maybeKey, maybeFeatureFlagNames, maybeAttributes,
|
|
99
|
+
const params = validateEvaluationParams(maybeKey, maybeFeatureFlagNames, maybeAttributes, GET_TREATMENTS_WITH_CONFIG);
|
|
100
100
|
|
|
101
101
|
if (params.valid) {
|
|
102
102
|
return client.getTreatmentsWithConfig(params.key as SplitIO.SplitKey, params.splitOrSplits as string[], params.attributes as SplitIO.Attributes | undefined);
|
|
@@ -109,7 +109,7 @@ export function clientInputValidationDecorator<TClient extends SplitIO.IClient |
|
|
|
109
109
|
}
|
|
110
110
|
|
|
111
111
|
function getTreatmentsByFlagSets(maybeKey: SplitIO.SplitKey, maybeFlagSets: string[], maybeAttributes?: SplitIO.Attributes) {
|
|
112
|
-
const params = validateEvaluationParams(maybeKey, undefined, maybeAttributes,
|
|
112
|
+
const params = validateEvaluationParams(maybeKey, undefined, maybeAttributes, GET_TREATMENTS_BY_FLAG_SETS, maybeFlagSets);
|
|
113
113
|
|
|
114
114
|
if (params.valid) {
|
|
115
115
|
return client.getTreatmentsByFlagSets(params.key as SplitIO.SplitKey, params.flagSetOrFlagSets as string[], params.attributes as SplitIO.Attributes | undefined);
|
|
@@ -119,7 +119,7 @@ export function clientInputValidationDecorator<TClient extends SplitIO.IClient |
|
|
|
119
119
|
}
|
|
120
120
|
|
|
121
121
|
function getTreatmentsWithConfigByFlagSets(maybeKey: SplitIO.SplitKey, maybeFlagSets: string[], maybeAttributes?: SplitIO.Attributes) {
|
|
122
|
-
const params = validateEvaluationParams(maybeKey, undefined, maybeAttributes,
|
|
122
|
+
const params = validateEvaluationParams(maybeKey, undefined, maybeAttributes, GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SETS, maybeFlagSets);
|
|
123
123
|
|
|
124
124
|
if (params.valid) {
|
|
125
125
|
return client.getTreatmentsWithConfigByFlagSets(params.key as SplitIO.SplitKey, params.flagSetOrFlagSets as string[], params.attributes as SplitIO.Attributes | undefined);
|
|
@@ -129,7 +129,7 @@ export function clientInputValidationDecorator<TClient extends SplitIO.IClient |
|
|
|
129
129
|
}
|
|
130
130
|
|
|
131
131
|
function getTreatmentsByFlagSet(maybeKey: SplitIO.SplitKey, maybeFlagSet: string, maybeAttributes?: SplitIO.Attributes) {
|
|
132
|
-
const params = validateEvaluationParams(maybeKey, undefined, maybeAttributes,
|
|
132
|
+
const params = validateEvaluationParams(maybeKey, undefined, maybeAttributes, GET_TREATMENTS_BY_FLAG_SET, [maybeFlagSet]);
|
|
133
133
|
|
|
134
134
|
if (params.valid) {
|
|
135
135
|
return client.getTreatmentsByFlagSet(params.key as SplitIO.SplitKey, params.flagSetOrFlagSets[0] as string, params.attributes as SplitIO.Attributes | undefined);
|
|
@@ -139,7 +139,7 @@ export function clientInputValidationDecorator<TClient extends SplitIO.IClient |
|
|
|
139
139
|
}
|
|
140
140
|
|
|
141
141
|
function getTreatmentsWithConfigByFlagSet(maybeKey: SplitIO.SplitKey, maybeFlagSet: string, maybeAttributes?: SplitIO.Attributes) {
|
|
142
|
-
const params = validateEvaluationParams(maybeKey, undefined, maybeAttributes,
|
|
142
|
+
const params = validateEvaluationParams(maybeKey, undefined, maybeAttributes, GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SET, [maybeFlagSet]);
|
|
143
143
|
|
|
144
144
|
if (params.valid) {
|
|
145
145
|
return client.getTreatmentsWithConfigByFlagSet(params.key as SplitIO.SplitKey, params.flagSetOrFlagSets[0] as string, params.attributes as SplitIO.Attributes | undefined);
|
|
@@ -149,17 +149,17 @@ export function clientInputValidationDecorator<TClient extends SplitIO.IClient |
|
|
|
149
149
|
}
|
|
150
150
|
|
|
151
151
|
function track(maybeKey: SplitIO.SplitKey, maybeTT: string, maybeEvent: string, maybeEventValue?: number, maybeProperties?: SplitIO.Properties) {
|
|
152
|
-
const key = validateKey(log, maybeKey,
|
|
153
|
-
const tt = validateTrafficType(log, maybeTT,
|
|
154
|
-
const event = validateEvent(log, maybeEvent,
|
|
155
|
-
const eventValue = validateEventValue(log, maybeEventValue,
|
|
156
|
-
const { properties, size } = validateEventProperties(log, maybeProperties,
|
|
157
|
-
const isNotDestroyed = validateIfNotDestroyed(log, readinessManager,
|
|
152
|
+
const key = validateKey(log, maybeKey, TRACK_FN_LABEL);
|
|
153
|
+
const tt = validateTrafficType(log, maybeTT, TRACK_FN_LABEL);
|
|
154
|
+
const event = validateEvent(log, maybeEvent, TRACK_FN_LABEL);
|
|
155
|
+
const eventValue = validateEventValue(log, maybeEventValue, TRACK_FN_LABEL);
|
|
156
|
+
const { properties, size } = validateEventProperties(log, maybeProperties, TRACK_FN_LABEL);
|
|
157
|
+
const isNotDestroyed = validateIfNotDestroyed(log, readinessManager, TRACK_FN_LABEL);
|
|
158
158
|
|
|
159
159
|
if (isNotDestroyed && key && tt && event && eventValue !== false && properties !== false) { // @ts-expect-error
|
|
160
160
|
return client.track(key, tt, event, eventValue, properties, size);
|
|
161
161
|
} else {
|
|
162
|
-
return
|
|
162
|
+
return isAsync ? Promise.resolve(false) : false;
|
|
163
163
|
}
|
|
164
164
|
}
|
|
165
165
|
|
package/src/sdkManager/index.ts
CHANGED
|
@@ -6,11 +6,8 @@ import { ISplitsCacheAsync, ISplitsCacheSync } from '../storages/types';
|
|
|
6
6
|
import { ISdkReadinessManager } from '../readiness/types';
|
|
7
7
|
import { ISplit } from '../dtos/types';
|
|
8
8
|
import { ISettings, SplitIO } from '../types';
|
|
9
|
-
import {
|
|
10
|
-
|
|
11
|
-
const SPLIT_FN_LABEL = 'split';
|
|
12
|
-
const SPLITS_FN_LABEL = 'splits';
|
|
13
|
-
const NAMES_FN_LABEL = 'names';
|
|
9
|
+
import { isConsumerMode } from '../utils/settingsValidation/mode';
|
|
10
|
+
import { SPLIT_FN_LABEL, SPLITS_FN_LABEL, NAMES_FN_LABEL } from '../utils/constants';
|
|
14
11
|
|
|
15
12
|
function collectTreatments(splitObject: ISplit) {
|
|
16
13
|
const conditions = splitObject.conditions;
|
|
@@ -54,8 +51,8 @@ export function sdkManagerFactory<TSplitCache extends ISplitsCacheSync | ISplits
|
|
|
54
51
|
{ readinessManager, sdkStatus }: ISdkReadinessManager,
|
|
55
52
|
): TSplitCache extends ISplitsCacheAsync ? SplitIO.IAsyncManager : SplitIO.IManager {
|
|
56
53
|
|
|
57
|
-
const log = settings
|
|
58
|
-
const
|
|
54
|
+
const { log, mode } = settings;
|
|
55
|
+
const isAsync = isConsumerMode(mode);
|
|
59
56
|
|
|
60
57
|
return objectAssign(
|
|
61
58
|
// Proto-linkage of the readiness Event Emitter
|
|
@@ -67,7 +64,7 @@ export function sdkManagerFactory<TSplitCache extends ISplitsCacheSync | ISplits
|
|
|
67
64
|
split(featureFlagName: string) {
|
|
68
65
|
const splitName = validateSplit(log, featureFlagName, SPLIT_FN_LABEL);
|
|
69
66
|
if (!validateIfNotDestroyed(log, readinessManager, SPLIT_FN_LABEL) || !validateIfOperational(log, readinessManager, SPLIT_FN_LABEL) || !splitName) {
|
|
70
|
-
return
|
|
67
|
+
return isAsync ? Promise.resolve(null) : null;
|
|
71
68
|
}
|
|
72
69
|
|
|
73
70
|
const split = splits.getSplit(splitName);
|
|
@@ -88,7 +85,7 @@ export function sdkManagerFactory<TSplitCache extends ISplitsCacheSync | ISplits
|
|
|
88
85
|
*/
|
|
89
86
|
splits() {
|
|
90
87
|
if (!validateIfNotDestroyed(log, readinessManager, SPLITS_FN_LABEL) || !validateIfOperational(log, readinessManager, SPLITS_FN_LABEL)) {
|
|
91
|
-
return
|
|
88
|
+
return isAsync ? Promise.resolve([]) : [];
|
|
92
89
|
}
|
|
93
90
|
const currentSplits = splits.getAll();
|
|
94
91
|
|
|
@@ -101,7 +98,7 @@ export function sdkManagerFactory<TSplitCache extends ISplitsCacheSync | ISplits
|
|
|
101
98
|
*/
|
|
102
99
|
names() {
|
|
103
100
|
if (!validateIfNotDestroyed(log, readinessManager, NAMES_FN_LABEL) || !validateIfOperational(log, readinessManager, NAMES_FN_LABEL)) {
|
|
104
|
-
return
|
|
101
|
+
return isAsync ? Promise.resolve([]) : [];
|
|
105
102
|
}
|
|
106
103
|
const splitNames = splits.getSplitNames();
|
|
107
104
|
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { ISettings } from '../types';
|
|
1
2
|
import { startsWith } from '../utils/lang';
|
|
3
|
+
import { hash } from '../utils/murmur3/murmur3';
|
|
2
4
|
|
|
3
5
|
const everythingAtTheEnd = /[^.]+$/;
|
|
4
6
|
|
|
@@ -10,7 +12,7 @@ export function validatePrefix(prefix: unknown) {
|
|
|
10
12
|
|
|
11
13
|
export class KeyBuilder {
|
|
12
14
|
|
|
13
|
-
|
|
15
|
+
readonly prefix: string;
|
|
14
16
|
|
|
15
17
|
constructor(prefix: string = DEFAULT_PREFIX) {
|
|
16
18
|
this.prefix = prefix;
|
|
@@ -73,4 +75,15 @@ export class KeyBuilder {
|
|
|
73
75
|
}
|
|
74
76
|
}
|
|
75
77
|
|
|
78
|
+
buildHashKey() {
|
|
79
|
+
return `${this.prefix}.hash`;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Generates a murmur32 hash based on the authorization key and the feature flags filter query.
|
|
85
|
+
* The hash is in hexadecimal format (8 characters max, 32 bits).
|
|
86
|
+
*/
|
|
87
|
+
export function getStorageHash(settings: ISettings) {
|
|
88
|
+
return hash(`${settings.core.authorizationKey}::${settings.sync.__splitFiltersValidation.queryString}`).toString(16);
|
|
76
89
|
}
|
|
@@ -9,7 +9,7 @@ export class KeyBuilderCS extends KeyBuilder {
|
|
|
9
9
|
constructor(prefix: string, matchingKey: string) {
|
|
10
10
|
super(prefix);
|
|
11
11
|
this.matchingKey = matchingKey;
|
|
12
|
-
this.regexSplitsCacheKey = new RegExp(`^${prefix}
|
|
12
|
+
this.regexSplitsCacheKey = new RegExp(`^${prefix}\\.`);
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
/**
|
|
@@ -45,8 +45,4 @@ export class KeyBuilderCS extends KeyBuilder {
|
|
|
45
45
|
isSplitsCacheKey(key: string) {
|
|
46
46
|
return this.regexSplitsCacheKey.test(key);
|
|
47
47
|
}
|
|
48
|
-
|
|
49
|
-
buildSplitsFilterQueryKey() {
|
|
50
|
-
return `${this.prefix}.splits.filterQuery`;
|
|
51
|
-
}
|
|
52
48
|
}
|
|
@@ -16,10 +16,10 @@ export const METHOD_NAMES: Record<Method, string> = {
|
|
|
16
16
|
|
|
17
17
|
export class KeyBuilderSS extends KeyBuilder {
|
|
18
18
|
|
|
19
|
-
latencyPrefix: string;
|
|
20
|
-
exceptionPrefix: string;
|
|
21
|
-
initPrefix: string;
|
|
22
|
-
private versionablePrefix: string;
|
|
19
|
+
readonly latencyPrefix: string;
|
|
20
|
+
readonly exceptionPrefix: string;
|
|
21
|
+
readonly initPrefix: string;
|
|
22
|
+
private readonly versionablePrefix: string;
|
|
23
23
|
|
|
24
24
|
constructor(prefix: string, metadata: IMetadata) {
|
|
25
25
|
super(prefix);
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import { ISplit
|
|
1
|
+
import { ISplit } from '../../dtos/types';
|
|
2
2
|
import { AbstractSplitsCacheSync, usesSegments } from '../AbstractSplitsCacheSync';
|
|
3
3
|
import { isFiniteNumber, toNumber, isNaNNumber } from '../../utils/lang';
|
|
4
4
|
import { KeyBuilderCS } from '../KeyBuilderCS';
|
|
5
5
|
import { ILogger } from '../../logger/types';
|
|
6
6
|
import { LOG_PREFIX } from './constants';
|
|
7
7
|
import { ISet, _Set, setToArray } from '../../utils/lang/sets';
|
|
8
|
+
import { ISettings } from '../../types';
|
|
9
|
+
import { getStorageHash } from '../KeyBuilder';
|
|
8
10
|
|
|
9
11
|
/**
|
|
10
12
|
* ISplitsCacheSync implementation that stores split definitions in browser LocalStorage.
|
|
@@ -12,7 +14,8 @@ import { ISet, _Set, setToArray } from '../../utils/lang/sets';
|
|
|
12
14
|
export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
|
|
13
15
|
|
|
14
16
|
private readonly keys: KeyBuilderCS;
|
|
15
|
-
private readonly
|
|
17
|
+
private readonly log: ILogger;
|
|
18
|
+
private readonly storageHash: string;
|
|
16
19
|
private readonly flagSetsFilter: string[];
|
|
17
20
|
private hasSync?: boolean;
|
|
18
21
|
private updateNewFilter?: boolean;
|
|
@@ -22,11 +25,12 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
|
|
|
22
25
|
* @param {number | undefined} expirationTimestamp
|
|
23
26
|
* @param {ISplitFiltersValidation} splitFiltersValidation
|
|
24
27
|
*/
|
|
25
|
-
constructor(
|
|
28
|
+
constructor(settings: ISettings, keys: KeyBuilderCS, expirationTimestamp?: number) {
|
|
26
29
|
super();
|
|
27
30
|
this.keys = keys;
|
|
28
|
-
this.
|
|
29
|
-
this.
|
|
31
|
+
this.log = settings.log;
|
|
32
|
+
this.storageHash = getStorageHash(settings);
|
|
33
|
+
this.flagSetsFilter = settings.sync.__splitFiltersValidation.groupedFilters.bySet;
|
|
30
34
|
|
|
31
35
|
this._checkExpiration(expirationTimestamp);
|
|
32
36
|
|
|
@@ -142,12 +146,10 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
|
|
|
142
146
|
|
|
143
147
|
// when using a new split query, we must update it at the store
|
|
144
148
|
if (this.updateNewFilter) {
|
|
145
|
-
this.log.info(LOG_PREFIX + '
|
|
146
|
-
const
|
|
147
|
-
const queryString = this.splitFiltersValidation.queryString;
|
|
149
|
+
this.log.info(LOG_PREFIX + 'SDK key or feature flag filter criteria was modified. Updating cache.');
|
|
150
|
+
const storageHashKey = this.keys.buildHashKey();
|
|
148
151
|
try {
|
|
149
|
-
|
|
150
|
-
else localStorage.removeItem(queryKey);
|
|
152
|
+
localStorage.setItem(storageHashKey, this.storageHash);
|
|
151
153
|
} catch (e) {
|
|
152
154
|
this.log.error(LOG_PREFIX + e);
|
|
153
155
|
}
|
|
@@ -237,12 +239,12 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
|
|
|
237
239
|
}
|
|
238
240
|
}
|
|
239
241
|
|
|
242
|
+
// @TODO eventually remove `_checkFilterQuery`. Cache should be cleared at the storage level, reusing same logic than PluggableStorage
|
|
240
243
|
private _checkFilterQuery() {
|
|
241
|
-
const
|
|
242
|
-
const
|
|
243
|
-
const currentQueryString = localStorage.getItem(queryKey);
|
|
244
|
+
const storageHashKey = this.keys.buildHashKey();
|
|
245
|
+
const storageHash = localStorage.getItem(storageHashKey);
|
|
244
246
|
|
|
245
|
-
if (
|
|
247
|
+
if (storageHash !== this.storageHash) {
|
|
246
248
|
try {
|
|
247
249
|
// mark cache to update the new query filter on first successful splits fetch
|
|
248
250
|
this.updateNewFilter = true;
|
|
@@ -41,7 +41,7 @@ export function InLocalStorage(options: InLocalStorageOptions = {}): IStorageSyn
|
|
|
41
41
|
const keys = new KeyBuilderCS(prefix, matchingKey as string);
|
|
42
42
|
const expirationTimestamp = Date.now() - DEFAULT_CACHE_EXPIRATION_IN_MILLIS;
|
|
43
43
|
|
|
44
|
-
const splits = new SplitsCacheInLocal(
|
|
44
|
+
const splits = new SplitsCacheInLocal(settings, keys, expirationTimestamp);
|
|
45
45
|
const segments = new MySegmentsCacheInLocal(log, keys);
|
|
46
46
|
|
|
47
47
|
return {
|
|
@@ -4,7 +4,7 @@ import { ILogger } from '../../logger/types';
|
|
|
4
4
|
import { LOG_PREFIX } from './constants';
|
|
5
5
|
import { ISplit, ISplitFiltersValidation } from '../../dtos/types';
|
|
6
6
|
import { AbstractSplitsCacheAsync } from '../AbstractSplitsCacheAsync';
|
|
7
|
-
import { ISet, _Set,
|
|
7
|
+
import { ISet, _Set, returnDifference } from '../../utils/lang/sets';
|
|
8
8
|
import type { RedisAdapter } from './RedisAdapter';
|
|
9
9
|
|
|
10
10
|
/**
|
|
@@ -60,9 +60,9 @@ export class SplitsCacheInRedis extends AbstractSplitsCacheAsync {
|
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
private _updateFlagSets(featureFlagName: string, flagSetsOfRemovedFlag?: string[], flagSetsOfAddedFlag?: string[]) {
|
|
63
|
-
const removeFromFlagSets =
|
|
63
|
+
const removeFromFlagSets = returnDifference(flagSetsOfRemovedFlag, flagSetsOfAddedFlag);
|
|
64
64
|
|
|
65
|
-
let addToFlagSets =
|
|
65
|
+
let addToFlagSets = returnDifference(flagSetsOfAddedFlag, flagSetsOfRemovedFlag);
|
|
66
66
|
if (this.flagSetsFilter.length > 0) {
|
|
67
67
|
addToFlagSets = addToFlagSets.filter(flagSet => {
|
|
68
68
|
return this.flagSetsFilter.some(filterFlagSet => filterFlagSet === flagSet);
|
|
@@ -45,7 +45,7 @@ export function InRedisStorage(options: InRedisStorageOptions = {}): IStorageAsy
|
|
|
45
45
|
});
|
|
46
46
|
|
|
47
47
|
return {
|
|
48
|
-
splits: new SplitsCacheInRedis(log, keys, redisClient),
|
|
48
|
+
splits: new SplitsCacheInRedis(log, keys, redisClient, settings.sync.__splitFiltersValidation),
|
|
49
49
|
segments: new SegmentsCacheInRedis(log, keys, redisClient),
|
|
50
50
|
impressions: new ImpressionsCacheInRedis(log, keys.buildImpressionsKey(), redisClient, metadata),
|
|
51
51
|
impressionCounts: impressionCountsCache,
|
|
@@ -5,7 +5,7 @@ import { ILogger } from '../../logger/types';
|
|
|
5
5
|
import { ISplit, ISplitFiltersValidation } from '../../dtos/types';
|
|
6
6
|
import { LOG_PREFIX } from './constants';
|
|
7
7
|
import { AbstractSplitsCacheAsync } from '../AbstractSplitsCacheAsync';
|
|
8
|
-
import { ISet, _Set,
|
|
8
|
+
import { ISet, _Set, returnDifference } from '../../utils/lang/sets';
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* ISplitsCacheAsync implementation for pluggable storages.
|
|
@@ -44,9 +44,9 @@ export class SplitsCachePluggable extends AbstractSplitsCacheAsync {
|
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
private _updateFlagSets(featureFlagName: string, flagSetsOfRemovedFlag?: string[], flagSetsOfAddedFlag?: string[]) {
|
|
47
|
-
const removeFromFlagSets =
|
|
47
|
+
const removeFromFlagSets = returnDifference(flagSetsOfRemovedFlag, flagSetsOfAddedFlag);
|
|
48
48
|
|
|
49
|
-
let addToFlagSets =
|
|
49
|
+
let addToFlagSets = returnDifference(flagSetsOfAddedFlag, flagSetsOfRemovedFlag);
|
|
50
50
|
if (this.flagSetsFilter.length > 0) {
|
|
51
51
|
addToFlagSets = addToFlagSets.filter(flagSet => {
|
|
52
52
|
return this.flagSetsFilter.some(filterFlagSet => filterFlagSet === flagSet);
|
|
@@ -7,7 +7,7 @@ import { ImpressionsCachePluggable } from './ImpressionsCachePluggable';
|
|
|
7
7
|
import { EventsCachePluggable } from './EventsCachePluggable';
|
|
8
8
|
import { wrapperAdapter, METHODS_TO_PROMISE_WRAP } from './wrapperAdapter';
|
|
9
9
|
import { isObject } from '../../utils/lang';
|
|
10
|
-
import { validatePrefix } from '../KeyBuilder';
|
|
10
|
+
import { getStorageHash, validatePrefix } from '../KeyBuilder';
|
|
11
11
|
import { CONSUMER_PARTIAL_MODE, DEBUG, NONE, STORAGE_PLUGGABLE } from '../../utils/constants';
|
|
12
12
|
import { ImpressionsCacheInMemory } from '../inMemory/ImpressionsCacheInMemory';
|
|
13
13
|
import { EventsCacheInMemory } from '../inMemory/EventsCacheInMemory';
|
|
@@ -90,13 +90,23 @@ export function PluggableStorage(options: PluggableStorageOptions): IStorageAsyn
|
|
|
90
90
|
|
|
91
91
|
// Connects to wrapper and emits SDK_READY event on main client
|
|
92
92
|
const connectPromise = wrapper.connect().then(() => {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
93
|
+
if (isSyncronizer) {
|
|
94
|
+
// In standalone or producer mode, clear storage if SDK key or feature flag filter has changed
|
|
95
|
+
return wrapper.get(keys.buildHashKey()).then((hash) => {
|
|
96
|
+
const currentHash = getStorageHash(settings);
|
|
97
|
+
if (hash !== currentHash) {
|
|
98
|
+
return wrapper.getKeysByPrefix(`${keys.prefix}.`).then(storageKeys => {
|
|
99
|
+
return Promise.all(storageKeys.map(storageKey => wrapper.del(storageKey)));
|
|
100
|
+
}).then(() => wrapper.set(keys.buildHashKey(), currentHash));
|
|
101
|
+
}
|
|
102
|
+
}).then(onReadyCb);
|
|
103
|
+
} else {
|
|
104
|
+
// Start periodic flush of async storages if not running synchronizer (producer mode)
|
|
97
105
|
if (impressionCountsCache && (impressionCountsCache as ImpressionCountsCachePluggable).start) (impressionCountsCache as ImpressionCountsCachePluggable).start();
|
|
98
106
|
if (uniqueKeysCache && (uniqueKeysCache as UniqueKeysCachePluggable).start) (uniqueKeysCache as UniqueKeysCachePluggable).start();
|
|
99
107
|
if (telemetry && (telemetry as ITelemetryCacheAsync).recordConfig) (telemetry as ITelemetryCacheAsync).recordConfig();
|
|
108
|
+
|
|
109
|
+
onReadyCb();
|
|
100
110
|
}
|
|
101
111
|
}).catch((e) => {
|
|
102
112
|
e = e || new Error('Error connecting wrapper');
|
package/src/storages/types.ts
CHANGED
|
@@ -457,7 +457,7 @@ export interface IStorageBase<
|
|
|
457
457
|
TEventsCache extends IEventsCacheBase,
|
|
458
458
|
TTelemetryCache extends ITelemetryCacheSync | ITelemetryCacheAsync,
|
|
459
459
|
TUniqueKeysCache extends IUniqueKeysCacheBase
|
|
460
|
-
|
|
460
|
+
> {
|
|
461
461
|
splits: TSplitsCache,
|
|
462
462
|
segments: TSegmentsCache,
|
|
463
463
|
impressions: TImpressionsCache,
|
|
@@ -477,7 +477,7 @@ export interface IStorageSync extends IStorageBase<
|
|
|
477
477
|
IEventsCacheSync,
|
|
478
478
|
ITelemetryCacheSync,
|
|
479
479
|
IUniqueKeysCacheSync
|
|
480
|
-
|
|
480
|
+
> { }
|
|
481
481
|
|
|
482
482
|
export interface IStorageAsync extends IStorageBase<
|
|
483
483
|
ISplitsCacheAsync,
|
|
@@ -487,7 +487,7 @@ export interface IStorageAsync extends IStorageBase<
|
|
|
487
487
|
IEventsCacheAsync | IEventsCacheSync,
|
|
488
488
|
ITelemetryCacheAsync | ITelemetryCacheSync,
|
|
489
489
|
IUniqueKeysCacheBase
|
|
490
|
-
|
|
490
|
+
> { }
|
|
491
491
|
|
|
492
492
|
/** StorageFactory */
|
|
493
493
|
|
|
@@ -5,7 +5,7 @@ import { IEventsHandler, IEventTracker } from './types';
|
|
|
5
5
|
import { ISettings, SplitIO } from '../types';
|
|
6
6
|
import { EVENTS_TRACKER_SUCCESS, ERROR_EVENTS_TRACKER } from '../logger/constants';
|
|
7
7
|
import { CONSENT_DECLINED, DROPPED, QUEUED } from '../utils/constants';
|
|
8
|
-
import {
|
|
8
|
+
import { isConsumerMode } from '../utils/settingsValidation/mode';
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* Event tracker stores events in cache and pass them to the integrations manager if provided.
|
|
@@ -20,8 +20,8 @@ export function eventTrackerFactory(
|
|
|
20
20
|
telemetryCache?: ITelemetryCacheSync | ITelemetryCacheAsync
|
|
21
21
|
): IEventTracker {
|
|
22
22
|
|
|
23
|
-
const log = settings
|
|
24
|
-
const
|
|
23
|
+
const { log, mode } = settings;
|
|
24
|
+
const isAsync = isConsumerMode(mode);
|
|
25
25
|
|
|
26
26
|
function queueEventsCallback(eventData: SplitIO.EventData, tracked: boolean) {
|
|
27
27
|
const { eventTypeId, trafficTypeName, key, value, timestamp, properties } = eventData;
|
|
@@ -50,7 +50,7 @@ export function eventTrackerFactory(
|
|
|
50
50
|
return {
|
|
51
51
|
track(eventData: SplitIO.EventData, size?: number) {
|
|
52
52
|
if (settings.userConsent === CONSENT_DECLINED) {
|
|
53
|
-
return
|
|
53
|
+
return isAsync ? Promise.resolve(false) : false;
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
const tracked = eventsCache.track(eventData, size);
|
|
@@ -39,6 +39,22 @@ export const CONSENT_GRANTED = 'GRANTED'; // The user has granted consent for tr
|
|
|
39
39
|
export const CONSENT_DECLINED = 'DECLINED'; // The user has declined consent for tracking events and impressions
|
|
40
40
|
export const CONSENT_UNKNOWN = 'UNKNOWN'; // The user has neither granted nor declined consent for tracking events and impressions
|
|
41
41
|
|
|
42
|
+
// Client method names
|
|
43
|
+
export const GET_TREATMENT = 'getTreatment';
|
|
44
|
+
export const GET_TREATMENTS = 'getTreatments';
|
|
45
|
+
export const GET_TREATMENT_WITH_CONFIG = 'getTreatmentWithConfig';
|
|
46
|
+
export const GET_TREATMENTS_WITH_CONFIG = 'getTreatmentsWithConfig';
|
|
47
|
+
export const GET_TREATMENTS_BY_FLAG_SET = 'getTreatmentsByFlagSet';
|
|
48
|
+
export const GET_TREATMENTS_BY_FLAG_SETS = 'getTreatmentsByFlagSets';
|
|
49
|
+
export const GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SET = 'getTreatmentsWithConfigByFlagSet';
|
|
50
|
+
export const GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SETS = 'getTreatmentsWithConfigByFlagSets';
|
|
51
|
+
export const TRACK_FN_LABEL = 'track';
|
|
52
|
+
|
|
53
|
+
// Manager method names
|
|
54
|
+
export const SPLIT_FN_LABEL = 'split';
|
|
55
|
+
export const SPLITS_FN_LABEL = 'splits';
|
|
56
|
+
export const NAMES_FN_LABEL = 'names';
|
|
57
|
+
|
|
42
58
|
// Telemetry
|
|
43
59
|
export const QUEUED = 0;
|
|
44
60
|
export const DROPPED = 1;
|
package/src/utils/lang/sets.ts
CHANGED
|
@@ -120,7 +120,7 @@ export function returnSetsUnion<T>(set: ISet<T>, set2: ISet<T>): ISet<T> {
|
|
|
120
120
|
return result;
|
|
121
121
|
}
|
|
122
122
|
|
|
123
|
-
export function
|
|
123
|
+
export function returnDifference<T>(list: T[] = [], list2: T[] = []): T[] {
|
|
124
124
|
const result = new _Set(list);
|
|
125
125
|
list2.forEach(item => {
|
|
126
126
|
result.delete(item);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { merge, get } from '../lang';
|
|
2
|
-
import {
|
|
2
|
+
import { validateMode } from './mode';
|
|
3
3
|
import { validateSplitFilters } from './splitFilters';
|
|
4
4
|
import { STANDALONE_MODE, OPTIMIZED, LOCALHOST_MODE, DEBUG } from '../constants';
|
|
5
5
|
import { validImpressionsMode } from './impressionsMode';
|
|
@@ -146,7 +146,7 @@ export function settingsValidation(config: unknown, validationParams: ISettingsV
|
|
|
146
146
|
|
|
147
147
|
// ensure a valid SDK mode
|
|
148
148
|
// @ts-ignore, modify readonly prop
|
|
149
|
-
withDefaults.mode =
|
|
149
|
+
withDefaults.mode = validateMode(withDefaults.core.authorizationKey, withDefaults.mode);
|
|
150
150
|
|
|
151
151
|
// ensure a valid Storage based on mode defined.
|
|
152
152
|
// @ts-ignore, modify readonly prop
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { LOCALHOST_MODE, STANDALONE_MODE, PRODUCER_MODE, CONSUMER_MODE, CONSUMER_PARTIAL_MODE } from '../constants';
|
|
2
2
|
|
|
3
|
-
export function
|
|
3
|
+
export function validateMode(key: string, mode: string) {
|
|
4
4
|
// Leaving the comparison as is, in case we change the mode name but not the setting.
|
|
5
5
|
if (key === 'localhost') return LOCALHOST_MODE;
|
|
6
6
|
|
|
@@ -8,3 +8,10 @@ export function mode(key: string, mode: string) {
|
|
|
8
8
|
|
|
9
9
|
return mode;
|
|
10
10
|
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Storage is async if mode is consumer or partial consumer
|
|
14
|
+
*/
|
|
15
|
+
export function isConsumerMode(mode: string) {
|
|
16
|
+
return CONSUMER_MODE === mode || CONSUMER_PARTIAL_MODE === mode;
|
|
17
|
+
}
|