flagsmith-nodejs 1.1.0 → 2.0.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/pull_request.yaml +33 -0
- package/.gitmodules +3 -0
- package/.husky/pre-commit +6 -0
- package/{LICENCE.md → LICENCE} +4 -5
- package/README.md +1 -1
- package/build/flagsmith-engine/environments/integrations/models.d.ts +4 -0
- package/build/flagsmith-engine/environments/integrations/models.js +8 -0
- package/build/flagsmith-engine/environments/models.d.ts +25 -0
- package/build/flagsmith-engine/environments/models.js +40 -0
- package/build/flagsmith-engine/environments/util.d.ts +3 -0
- package/build/flagsmith-engine/environments/util.js +19 -0
- package/build/flagsmith-engine/features/constants.d.ts +4 -0
- package/build/flagsmith-engine/features/constants.js +7 -0
- package/build/flagsmith-engine/features/models.d.ts +32 -0
- package/build/flagsmith-engine/features/models.js +85 -0
- package/build/flagsmith-engine/features/util.d.ts +3 -0
- package/build/flagsmith-engine/features/util.js +20 -0
- package/build/flagsmith-engine/identities/models.d.ts +15 -0
- package/build/flagsmith-engine/identities/models.js +47 -0
- package/build/flagsmith-engine/identities/traits/models.d.ts +5 -0
- package/build/flagsmith-engine/identities/traits/models.js +12 -0
- package/build/flagsmith-engine/identities/util.d.ts +4 -0
- package/build/flagsmith-engine/identities/util.js +22 -0
- package/build/flagsmith-engine/index.d.ts +8 -0
- package/build/flagsmith-engine/index.js +61 -0
- package/build/flagsmith-engine/organisations/models.d.ts +9 -0
- package/build/flagsmith-engine/organisations/models.js +21 -0
- package/build/flagsmith-engine/organisations/util.d.ts +2 -0
- package/build/flagsmith-engine/organisations/util.js +8 -0
- package/build/flagsmith-engine/projects/models.d.ts +10 -0
- package/build/flagsmith-engine/projects/models.js +17 -0
- package/build/flagsmith-engine/projects/util.d.ts +2 -0
- package/build/flagsmith-engine/projects/util.js +15 -0
- package/build/flagsmith-engine/segments/constants.d.ts +26 -0
- package/build/flagsmith-engine/segments/constants.js +31 -0
- package/build/flagsmith-engine/segments/evaluators.d.ts +6 -0
- package/build/flagsmith-engine/segments/evaluators.js +29 -0
- package/build/flagsmith-engine/segments/models.d.ts +31 -0
- package/build/flagsmith-engine/segments/models.js +83 -0
- package/build/flagsmith-engine/segments/util.d.ts +4 -0
- package/build/flagsmith-engine/segments/util.js +23 -0
- package/build/flagsmith-engine/utils/collections.d.ts +4 -0
- package/build/flagsmith-engine/utils/collections.js +16 -0
- package/build/flagsmith-engine/utils/errors.d.ts +2 -0
- package/build/flagsmith-engine/utils/errors.js +6 -0
- package/build/flagsmith-engine/utils/hashing/index.d.ts +9 -0
- package/build/flagsmith-engine/utils/hashing/index.js +54 -0
- package/build/flagsmith-engine/utils/index.d.ts +1 -0
- package/build/flagsmith-engine/utils/index.js +14 -0
- package/build/index.d.ts +1 -0
- package/build/index.js +11 -0
- package/build/sdk/analytics.d.ts +28 -0
- package/build/sdk/analytics.js +60 -0
- package/build/sdk/errors.d.ts +4 -0
- package/build/sdk/errors.js +9 -0
- package/build/sdk/index.d.ts +98 -0
- package/build/sdk/index.js +215 -0
- package/build/sdk/models.d.ts +55 -0
- package/build/sdk/models.js +102 -0
- package/build/sdk/polling_manager.d.ts +9 -0
- package/build/sdk/polling_manager.js +31 -0
- package/build/sdk/utils.d.ts +12 -0
- package/build/sdk/utils.js +45 -0
- package/example/README.md +0 -6
- package/example/package-lock.json +1070 -2
- package/example/package.json +4 -3
- package/example/server/api/index.js +18 -18
- package/example/server/index.js +4 -6
- package/flagsmith-engine/environments/integrations/models.ts +4 -0
- package/flagsmith-engine/environments/models.ts +50 -0
- package/flagsmith-engine/environments/util.ts +29 -0
- package/flagsmith-engine/features/constants.ts +4 -0
- package/flagsmith-engine/features/models.ts +105 -0
- package/flagsmith-engine/features/util.ts +38 -0
- package/flagsmith-engine/identities/models.ts +60 -0
- package/flagsmith-engine/identities/traits/models.ts +9 -0
- package/flagsmith-engine/identities/util.ts +30 -0
- package/flagsmith-engine/index.ts +93 -0
- package/flagsmith-engine/organisations/models.ts +25 -0
- package/flagsmith-engine/organisations/util.ts +11 -0
- package/flagsmith-engine/projects/models.ts +22 -0
- package/flagsmith-engine/projects/util.ts +18 -0
- package/flagsmith-engine/segments/constants.ts +31 -0
- package/flagsmith-engine/segments/evaluators.ts +72 -0
- package/flagsmith-engine/segments/models.ts +103 -0
- package/flagsmith-engine/segments/util.ts +29 -0
- package/flagsmith-engine/utils/collections.ts +14 -0
- package/flagsmith-engine/utils/errors.ts +1 -0
- package/flagsmith-engine/utils/hashing/index.ts +52 -0
- package/flagsmith-engine/utils/index.ts +10 -0
- package/index.ts +6 -0
- package/jest.config.js +5 -0
- package/package.json +28 -12
- package/sdk/analytics.ts +60 -0
- package/sdk/errors.ts +2 -0
- package/sdk/index.ts +270 -0
- package/sdk/models.ts +145 -0
- package/sdk/polling_manager.ts +31 -0
- package/sdk/utils.ts +45 -0
- package/tests/engine/e2e/engine.test.ts +51 -0
- package/tests/engine/engine-tests/engine-test-data/data/environment_n9fbf9h3v4fFgH3U3ngWhb.json +12393 -0
- package/tests/engine/engine-tests/engine-test-data/readme.md +30 -0
- package/tests/engine/unit/egine.test.ts +96 -0
- package/tests/engine/unit/environments/builder.test.ts +148 -0
- package/tests/engine/unit/environments/models.test.ts +49 -0
- package/tests/engine/unit/features/models.test.ts +72 -0
- package/tests/engine/unit/identities/identities_builders.test.ts +85 -0
- package/tests/engine/unit/identities/identities_models.test.ts +105 -0
- package/tests/engine/unit/organization/models.test.ts +12 -0
- package/tests/engine/unit/segments/segments_model.test.ts +101 -0
- package/tests/engine/unit/segments/util.ts +151 -0
- package/tests/engine/unit/utils.ts +114 -0
- package/tests/index.js +0 -0
- package/tests/sdk/analytics.test.ts +43 -0
- package/tests/sdk/data/environment.json +33 -0
- package/tests/sdk/data/flags.json +20 -0
- package/tests/sdk/data/identities.json +29 -0
- package/tests/sdk/flagsmith.test.ts +184 -0
- package/tests/sdk/polling.test.ts +34 -0
- package/tests/sdk/utils.ts +39 -0
- package/tsconfig.json +19 -0
- package/.idea/codeStyles/Project.xml +0 -52
- package/.idea/codeStyles/codeStyleConfig.xml +0 -5
- package/flagsmith-core.js +0 -241
- package/index.d.ts +0 -85
- package/index.js +0 -3
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { EnvironmentModel } from '../environments/models';
|
|
2
|
+
import { IdentityModel } from '../identities/models';
|
|
3
|
+
import { TraitModel } from '../identities/traits/models';
|
|
4
|
+
import { getHashedPercentateForObjIds } from '../utils/hashing';
|
|
5
|
+
import { PERCENTAGE_SPLIT } from './constants';
|
|
6
|
+
import { SegmentConditionModel, SegmentModel, SegmentRuleModel } from './models';
|
|
7
|
+
|
|
8
|
+
export function getIdentitySegments(
|
|
9
|
+
environment: EnvironmentModel,
|
|
10
|
+
identity: IdentityModel,
|
|
11
|
+
overrideTraits?: TraitModel[]
|
|
12
|
+
): SegmentModel[] {
|
|
13
|
+
return environment.project.segments.filter(segment =>
|
|
14
|
+
evaluateIdentityInSegment(identity, segment, overrideTraits)
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function evaluateIdentityInSegment(
|
|
19
|
+
identity: IdentityModel,
|
|
20
|
+
segment: SegmentModel,
|
|
21
|
+
overrideTraits?: TraitModel[]
|
|
22
|
+
): boolean {
|
|
23
|
+
return (
|
|
24
|
+
segment.rules.length > 0 &&
|
|
25
|
+
segment.rules.filter(rule =>
|
|
26
|
+
traitsMatchSegmentRule(
|
|
27
|
+
overrideTraits || identity.identityTraits,
|
|
28
|
+
rule,
|
|
29
|
+
segment.id,
|
|
30
|
+
identity.compositeKey
|
|
31
|
+
)
|
|
32
|
+
).length === segment.rules.length
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function traitsMatchSegmentRule(
|
|
37
|
+
identityTraits: TraitModel[],
|
|
38
|
+
rule: SegmentRuleModel,
|
|
39
|
+
segmentId: number | string,
|
|
40
|
+
identityId: number | string
|
|
41
|
+
): boolean {
|
|
42
|
+
const matchesConditions =
|
|
43
|
+
rule.conditions.length > 0
|
|
44
|
+
? rule.matchingFunction()(
|
|
45
|
+
rule.conditions.map(condition =>
|
|
46
|
+
traitsMatchSegmentCondition(identityTraits, condition, segmentId, identityId)
|
|
47
|
+
)
|
|
48
|
+
)
|
|
49
|
+
: true;
|
|
50
|
+
return (
|
|
51
|
+
matchesConditions &&
|
|
52
|
+
rule.rules.filter(rule =>
|
|
53
|
+
traitsMatchSegmentRule(identityTraits, rule, segmentId, identityId)
|
|
54
|
+
).length === rule.rules.length
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function traitsMatchSegmentCondition(
|
|
59
|
+
identityTraits: TraitModel[],
|
|
60
|
+
condition: SegmentConditionModel,
|
|
61
|
+
segmentId: number | string,
|
|
62
|
+
identityId: number | string
|
|
63
|
+
): boolean {
|
|
64
|
+
if (condition.operator == PERCENTAGE_SPLIT) {
|
|
65
|
+
return getHashedPercentateForObjIds([segmentId, identityId]) <= parseFloat(condition.value);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const traits = identityTraits.filter(t => t.traitKey === condition.property_);
|
|
69
|
+
const trait = traits.length > 0 ? traits[0] : undefined;
|
|
70
|
+
|
|
71
|
+
return trait ? condition.matchesTraitValue(trait.traitValue) : false;
|
|
72
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { FeatureStateModel } from '../features/models';
|
|
2
|
+
import { getCastingFunction as getCastingFunction } from '../utils';
|
|
3
|
+
import {
|
|
4
|
+
ALL_RULE,
|
|
5
|
+
ANY_RULE,
|
|
6
|
+
NONE_RULE,
|
|
7
|
+
NOT_CONTAINS,
|
|
8
|
+
REGEX,
|
|
9
|
+
CONDITION_OPERATORS
|
|
10
|
+
} from './constants';
|
|
11
|
+
|
|
12
|
+
export const all = (iterable: Array<any>) => iterable.filter(e => !!e).length === iterable.length;
|
|
13
|
+
export const any = (iterable: Array<any>) => iterable.filter(e => !!e).length > 0;
|
|
14
|
+
|
|
15
|
+
export const matchingFunctions = {
|
|
16
|
+
[CONDITION_OPERATORS.EQUAL]: (thisValue: any, otherValue: any) => thisValue == otherValue,
|
|
17
|
+
[CONDITION_OPERATORS.GREATER_THAN]: (thisValue: any, otherValue: any) => otherValue > thisValue,
|
|
18
|
+
[CONDITION_OPERATORS.GREATER_THAN_INCLUSIVE]: (thisValue: any, otherValue: any) =>
|
|
19
|
+
otherValue >= thisValue,
|
|
20
|
+
[CONDITION_OPERATORS.LESS_THAN]: (thisValue: any, otherValue: any) => thisValue > otherValue,
|
|
21
|
+
[CONDITION_OPERATORS.LESS_THAN_INCLUSIVE]: (thisValue: any, otherValue: any) =>
|
|
22
|
+
thisValue >= otherValue,
|
|
23
|
+
[CONDITION_OPERATORS.NOT_EQUAL]: (thisValue: any, otherValue: any) => thisValue != otherValue,
|
|
24
|
+
[CONDITION_OPERATORS.CONTAINS]: (thisValue: any, otherValue: any) =>
|
|
25
|
+
otherValue.includes(thisValue),
|
|
26
|
+
[CONDITION_OPERATORS.NOT_CONTAINS]: (thisValue: any, otherValue: any) =>
|
|
27
|
+
!otherValue.includes(thisValue)
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export class SegmentConditionModel {
|
|
31
|
+
EXCEPTION_OPERATOR_METHODS: { [key: string]: string } = {
|
|
32
|
+
[NOT_CONTAINS]: 'evaluateNotContains',
|
|
33
|
+
[REGEX]: 'evaluateRegex'
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
operator: string;
|
|
37
|
+
value: string;
|
|
38
|
+
property_: string | undefined;
|
|
39
|
+
|
|
40
|
+
constructor(operator: string, value: string, property?: string) {
|
|
41
|
+
this.operator = operator;
|
|
42
|
+
this.value = value;
|
|
43
|
+
this.property_ = property;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
matchesTraitValue(traitValue: any) {
|
|
47
|
+
const evaluators: { [key: string]: CallableFunction } = {
|
|
48
|
+
evaluateNotContains: (traitValue: any) => {
|
|
49
|
+
return !traitValue.includes(this.value);
|
|
50
|
+
},
|
|
51
|
+
evaluateRegex: (traitValue: any) => {
|
|
52
|
+
return !!traitValue.match(new RegExp(this.value));
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
// TODO: move this logic to the evaluator module
|
|
57
|
+
if (this.EXCEPTION_OPERATOR_METHODS[this.operator]) {
|
|
58
|
+
const evaluatorFunction = evaluators[this.EXCEPTION_OPERATOR_METHODS[this.operator]];
|
|
59
|
+
return evaluatorFunction(traitValue);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const defaultFunction = (x: any, y: any) => false;
|
|
63
|
+
|
|
64
|
+
const matchingFunction = matchingFunctions[this.operator] || defaultFunction;
|
|
65
|
+
|
|
66
|
+
const castToTypeOfTraitValue = getCastingFunction(traitValue);
|
|
67
|
+
return matchingFunction(castToTypeOfTraitValue(this.value), traitValue);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export class SegmentRuleModel {
|
|
72
|
+
type: string;
|
|
73
|
+
rules: SegmentRuleModel[] = [];
|
|
74
|
+
conditions: SegmentConditionModel[] = [];
|
|
75
|
+
|
|
76
|
+
constructor(type: string) {
|
|
77
|
+
this.type = type;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
static none(iterable: Array<any>) {
|
|
81
|
+
return iterable.filter(e => !!e).length === 0;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
matchingFunction(): CallableFunction {
|
|
85
|
+
return {
|
|
86
|
+
[ANY_RULE]: any,
|
|
87
|
+
[ALL_RULE]: all,
|
|
88
|
+
[NONE_RULE]: SegmentRuleModel.none
|
|
89
|
+
}[this.type] as CallableFunction;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export class SegmentModel {
|
|
94
|
+
id: number;
|
|
95
|
+
name: string;
|
|
96
|
+
rules: SegmentRuleModel[] = [];
|
|
97
|
+
featureStates: FeatureStateModel[] = [];
|
|
98
|
+
|
|
99
|
+
constructor(id: number, name: string) {
|
|
100
|
+
this.id = id;
|
|
101
|
+
this.name = name;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { buildFeatureStateModel } from '../features/util';
|
|
2
|
+
import { SegmentConditionModel, SegmentModel, SegmentRuleModel } from './models';
|
|
3
|
+
|
|
4
|
+
export function buildSegmentConditionModel(segmentConditionJSON: any): SegmentConditionModel {
|
|
5
|
+
return new SegmentConditionModel(
|
|
6
|
+
segmentConditionJSON.operator,
|
|
7
|
+
segmentConditionJSON.value,
|
|
8
|
+
segmentConditionJSON.property_
|
|
9
|
+
);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function buildSegmentRuleModel(ruleModelJSON: any): SegmentRuleModel {
|
|
13
|
+
const ruleModel = new SegmentRuleModel(ruleModelJSON.type);
|
|
14
|
+
|
|
15
|
+
ruleModel.rules = ruleModelJSON.rules.map((r: any) => buildSegmentRuleModel(r));
|
|
16
|
+
ruleModel.conditions = ruleModelJSON.conditions.map((c: any) => buildSegmentConditionModel(c));
|
|
17
|
+
return ruleModel;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function buildSegmentModel(segmentModelJSON: any): SegmentModel {
|
|
21
|
+
const model = new SegmentModel(segmentModelJSON.id, segmentModelJSON.name);
|
|
22
|
+
|
|
23
|
+
model.featureStates = segmentModelJSON['feature_states'].map((fs: any) =>
|
|
24
|
+
buildFeatureStateModel(fs)
|
|
25
|
+
);
|
|
26
|
+
model.rules = segmentModelJSON['rules'].map((r: any) => buildSegmentRuleModel(r));
|
|
27
|
+
|
|
28
|
+
return model;
|
|
29
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { FeatureStateModel } from '../features/models';
|
|
2
|
+
|
|
3
|
+
export class IdentityFeaturesList extends Array<FeatureStateModel> {
|
|
4
|
+
public push(...e: FeatureStateModel[]): number {
|
|
5
|
+
for (const [_, item] of e.entries()) {
|
|
6
|
+
for (const [k, v] of this.entries()) {
|
|
7
|
+
if (v.djangoID === item.djangoID) {
|
|
8
|
+
throw new Error('feature state for this feature already exists');
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
return super.push(...e);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export class FeatureStateNotFound extends Error {}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import md5 from 'md5';
|
|
2
|
+
import bigInt from 'big-integer';
|
|
3
|
+
|
|
4
|
+
const makeRepeated = (arr: Array<any>, repeats: number) =>
|
|
5
|
+
Array.from({ length: repeats }, () => arr).flat();
|
|
6
|
+
|
|
7
|
+
// https://stackoverflow.com/questions/12532871/how-to-convert-a-very-large-hex-number-to-decimal-in-javascript
|
|
8
|
+
function h2d(s: any): string {
|
|
9
|
+
function add(x: any, y: any) {
|
|
10
|
+
var c = 0,
|
|
11
|
+
r = [];
|
|
12
|
+
var x = x.split('').map(Number);
|
|
13
|
+
var y = y.split('').map(Number);
|
|
14
|
+
while (x.length || y.length) {
|
|
15
|
+
var s = (x.pop() || 0) + (y.pop() || 0) + c;
|
|
16
|
+
r.unshift(s < 10 ? s : s - 10);
|
|
17
|
+
c = s < 10 ? 0 : 1;
|
|
18
|
+
}
|
|
19
|
+
if (c) r.unshift(c);
|
|
20
|
+
return r.join('');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
var dec = '0';
|
|
24
|
+
s.split('').forEach(function (chr: any) {
|
|
25
|
+
var n = parseInt(chr, 16);
|
|
26
|
+
for (var t = 8; t; t >>= 1) {
|
|
27
|
+
dec = add(dec, dec);
|
|
28
|
+
if (n & t) dec = add(dec, '1');
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
return dec;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Given a list of object ids, get a floating point number between 0 and 1 based on
|
|
35
|
+
* the hash of those ids. This should give the same value every time for any list of ids.
|
|
36
|
+
*
|
|
37
|
+
* @param {Array<any>} objectIds list of object ids to calculate the has for
|
|
38
|
+
* @param {} iterations=1 num times to include each id in the generated string to hash
|
|
39
|
+
* @returns number number between 0 (inclusive) and 100 (exclusive)
|
|
40
|
+
*/
|
|
41
|
+
export function getHashedPercentateForObjIds(objectIds: Array<any>, iterations = 1): number {
|
|
42
|
+
let to_hash = makeRepeated(objectIds, iterations).join(',');
|
|
43
|
+
const hashedValue = md5(to_hash);
|
|
44
|
+
const hashedInt = bigInt(h2d(hashedValue));
|
|
45
|
+
const value = (hashedInt.mod(9999).toJSNumber() / 9998) * 100;
|
|
46
|
+
|
|
47
|
+
if (value === 100) {
|
|
48
|
+
return getHashedPercentateForObjIds(objectIds, iterations + 1);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return value;
|
|
52
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export function getCastingFunction(input: any): CallableFunction {
|
|
2
|
+
switch (typeof input) {
|
|
3
|
+
case 'boolean':
|
|
4
|
+
return (x: any) => !['False', 'false'].includes(x);
|
|
5
|
+
case 'number':
|
|
6
|
+
return (x: any) => parseFloat(x);
|
|
7
|
+
default:
|
|
8
|
+
return (x: any) => String(x);
|
|
9
|
+
}
|
|
10
|
+
}
|
package/index.ts
ADDED
package/jest.config.js
ADDED
package/package.json
CHANGED
|
@@ -1,24 +1,25 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "flagsmith-nodejs",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0-beta.2",
|
|
4
4
|
"description": "Flagsmith lets you manage features flags and remote config across web, mobile and server side applications. Deliver true Continuous Integration. Get builds out faster. Control who has access to new features.",
|
|
5
|
-
"main": "index.js",
|
|
5
|
+
"main": "build/index.js",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
8
|
-
"url": "https://github.com/
|
|
8
|
+
"url": "https://github.com/Flagsmith/flagsmith-nodejs-client"
|
|
9
9
|
},
|
|
10
10
|
"keywords": [
|
|
11
11
|
"nodejs",
|
|
12
|
-
"bullet train",
|
|
13
12
|
"flagsmith",
|
|
14
|
-
"feature
|
|
13
|
+
"feature flags",
|
|
14
|
+
"feature toggles",
|
|
15
|
+
"remote configuration",
|
|
15
16
|
"continuous deployment"
|
|
16
17
|
],
|
|
17
18
|
"bugs": {
|
|
18
|
-
"url": "https://github.com/
|
|
19
|
+
"url": "https://github.com/Flagsmith/flagsmith-nodejs-client/issues"
|
|
19
20
|
},
|
|
20
|
-
"homepage": "http://
|
|
21
|
-
"author": "
|
|
21
|
+
"homepage": "http://flagsmith.com/",
|
|
22
|
+
"author": "Flagsmith",
|
|
22
23
|
"contributors": [
|
|
23
24
|
{
|
|
24
25
|
"name": "Tom Stuart",
|
|
@@ -26,7 +27,7 @@
|
|
|
26
27
|
},
|
|
27
28
|
{
|
|
28
29
|
"name": "Kyle Johnson",
|
|
29
|
-
"email": "kyle@
|
|
30
|
+
"email": "kyle.johnson@flagsmith.com",
|
|
30
31
|
"url": "https://www.npmjs.com/~kyle-ssg"
|
|
31
32
|
},
|
|
32
33
|
{
|
|
@@ -40,12 +41,27 @@
|
|
|
40
41
|
],
|
|
41
42
|
"license": "MIT",
|
|
42
43
|
"scripts": {
|
|
43
|
-
"lint": "prettier --write ."
|
|
44
|
+
"lint": "prettier --write .",
|
|
45
|
+
"test": "jest --coverage --coverageReporters='text'",
|
|
46
|
+
"build": "tsc",
|
|
47
|
+
"prepare": "husky install"
|
|
44
48
|
},
|
|
45
49
|
"dependencies": {
|
|
46
|
-
"
|
|
50
|
+
"big-integer": "^1.6.51",
|
|
51
|
+
"md5": "^2.3.0",
|
|
52
|
+
"node-fetch": "^2.1.2",
|
|
53
|
+
"uuid": "^8.3.2"
|
|
47
54
|
},
|
|
48
55
|
"devDependencies": {
|
|
49
|
-
"
|
|
56
|
+
"@types/jest": "^27.4.1",
|
|
57
|
+
"@types/md5": "^2.3.2",
|
|
58
|
+
"@types/node-fetch": "^2.6.1",
|
|
59
|
+
"@types/uuid": "^8.3.4",
|
|
60
|
+
"esbuild": "^0.14.25",
|
|
61
|
+
"husky": "^7.0.4",
|
|
62
|
+
"jest": "^27.5.1",
|
|
63
|
+
"prettier": "^2.2.1",
|
|
64
|
+
"ts-jest": "^27.1.3",
|
|
65
|
+
"typescript": "^4.6.2"
|
|
50
66
|
}
|
|
51
67
|
}
|
package/sdk/analytics.ts
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import fetch from 'node-fetch';
|
|
2
|
+
|
|
3
|
+
const ANALYTICS_ENDPOINT = 'analytics/flags/';
|
|
4
|
+
|
|
5
|
+
// Used to control how often we send data(in seconds)
|
|
6
|
+
const ANALYTICS_TIMER = 10;
|
|
7
|
+
|
|
8
|
+
const delay = (ms: number) => new Promise(resolve => setTimeout(() => resolve(undefined), ms));
|
|
9
|
+
|
|
10
|
+
export class AnalyticsProcessor {
|
|
11
|
+
private analyticsEndpoint: string;
|
|
12
|
+
private environmentKey: string;
|
|
13
|
+
private lastFlushed: number;
|
|
14
|
+
analyticsData: { [key: string]: any };
|
|
15
|
+
private timeout: number = 3;
|
|
16
|
+
/**
|
|
17
|
+
* AnalyticsProcessor is used to track how often individual Flags are evaluated within
|
|
18
|
+
* the Flagsmith SDK. Docs: https://docs.flagsmith.com/advanced-use/flag-analytics.
|
|
19
|
+
*
|
|
20
|
+
* @param data.environmentKey environment key obtained from the Flagsmith UI
|
|
21
|
+
* @param data.baseApiUrl base api url to override when using self hosted version
|
|
22
|
+
* @param data.timeout used to tell requests to stop waiting for a response after a
|
|
23
|
+
given number of seconds
|
|
24
|
+
*/
|
|
25
|
+
constructor(data: { environmentKey: string; baseApiUrl: string; timeout?: number }) {
|
|
26
|
+
this.analyticsEndpoint = data.baseApiUrl + ANALYTICS_ENDPOINT;
|
|
27
|
+
this.environmentKey = data.environmentKey;
|
|
28
|
+
this.lastFlushed = Date.now();
|
|
29
|
+
this.analyticsData = {};
|
|
30
|
+
this.timeout = data.timeout || this.timeout;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Sends all the collected data to the api asynchronously and resets the timer
|
|
34
|
+
*/
|
|
35
|
+
async flush() {
|
|
36
|
+
if (!Object.keys(this.analyticsData).length) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
await fetch(this.analyticsEndpoint, {
|
|
41
|
+
method: 'POST',
|
|
42
|
+
body: JSON.stringify(this.analyticsData),
|
|
43
|
+
timeout: this.timeout,
|
|
44
|
+
headers: {
|
|
45
|
+
'Content-Type': 'application/json',
|
|
46
|
+
'X-Environment-Key': this.environmentKey
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
this.analyticsData = {};
|
|
51
|
+
this.lastFlushed = Date.now();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
trackFeature(featureId: number) {
|
|
55
|
+
this.analyticsData[featureId] = (this.analyticsData[featureId] || 0) + 1;
|
|
56
|
+
if (Date.now() - this.lastFlushed > ANALYTICS_TIMER * 1000) {
|
|
57
|
+
this.flush();
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
package/sdk/errors.ts
ADDED