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.
Files changed (126) hide show
  1. package/.github/workflows/pull_request.yaml +33 -0
  2. package/.gitmodules +3 -0
  3. package/.husky/pre-commit +6 -0
  4. package/{LICENCE.md → LICENCE} +4 -5
  5. package/README.md +1 -1
  6. package/build/flagsmith-engine/environments/integrations/models.d.ts +4 -0
  7. package/build/flagsmith-engine/environments/integrations/models.js +8 -0
  8. package/build/flagsmith-engine/environments/models.d.ts +25 -0
  9. package/build/flagsmith-engine/environments/models.js +40 -0
  10. package/build/flagsmith-engine/environments/util.d.ts +3 -0
  11. package/build/flagsmith-engine/environments/util.js +19 -0
  12. package/build/flagsmith-engine/features/constants.d.ts +4 -0
  13. package/build/flagsmith-engine/features/constants.js +7 -0
  14. package/build/flagsmith-engine/features/models.d.ts +32 -0
  15. package/build/flagsmith-engine/features/models.js +85 -0
  16. package/build/flagsmith-engine/features/util.d.ts +3 -0
  17. package/build/flagsmith-engine/features/util.js +20 -0
  18. package/build/flagsmith-engine/identities/models.d.ts +15 -0
  19. package/build/flagsmith-engine/identities/models.js +47 -0
  20. package/build/flagsmith-engine/identities/traits/models.d.ts +5 -0
  21. package/build/flagsmith-engine/identities/traits/models.js +12 -0
  22. package/build/flagsmith-engine/identities/util.d.ts +4 -0
  23. package/build/flagsmith-engine/identities/util.js +22 -0
  24. package/build/flagsmith-engine/index.d.ts +8 -0
  25. package/build/flagsmith-engine/index.js +61 -0
  26. package/build/flagsmith-engine/organisations/models.d.ts +9 -0
  27. package/build/flagsmith-engine/organisations/models.js +21 -0
  28. package/build/flagsmith-engine/organisations/util.d.ts +2 -0
  29. package/build/flagsmith-engine/organisations/util.js +8 -0
  30. package/build/flagsmith-engine/projects/models.d.ts +10 -0
  31. package/build/flagsmith-engine/projects/models.js +17 -0
  32. package/build/flagsmith-engine/projects/util.d.ts +2 -0
  33. package/build/flagsmith-engine/projects/util.js +15 -0
  34. package/build/flagsmith-engine/segments/constants.d.ts +26 -0
  35. package/build/flagsmith-engine/segments/constants.js +31 -0
  36. package/build/flagsmith-engine/segments/evaluators.d.ts +6 -0
  37. package/build/flagsmith-engine/segments/evaluators.js +29 -0
  38. package/build/flagsmith-engine/segments/models.d.ts +31 -0
  39. package/build/flagsmith-engine/segments/models.js +83 -0
  40. package/build/flagsmith-engine/segments/util.d.ts +4 -0
  41. package/build/flagsmith-engine/segments/util.js +23 -0
  42. package/build/flagsmith-engine/utils/collections.d.ts +4 -0
  43. package/build/flagsmith-engine/utils/collections.js +16 -0
  44. package/build/flagsmith-engine/utils/errors.d.ts +2 -0
  45. package/build/flagsmith-engine/utils/errors.js +6 -0
  46. package/build/flagsmith-engine/utils/hashing/index.d.ts +9 -0
  47. package/build/flagsmith-engine/utils/hashing/index.js +54 -0
  48. package/build/flagsmith-engine/utils/index.d.ts +1 -0
  49. package/build/flagsmith-engine/utils/index.js +14 -0
  50. package/build/index.d.ts +1 -0
  51. package/build/index.js +11 -0
  52. package/build/sdk/analytics.d.ts +28 -0
  53. package/build/sdk/analytics.js +60 -0
  54. package/build/sdk/errors.d.ts +4 -0
  55. package/build/sdk/errors.js +9 -0
  56. package/build/sdk/index.d.ts +98 -0
  57. package/build/sdk/index.js +215 -0
  58. package/build/sdk/models.d.ts +55 -0
  59. package/build/sdk/models.js +102 -0
  60. package/build/sdk/polling_manager.d.ts +9 -0
  61. package/build/sdk/polling_manager.js +31 -0
  62. package/build/sdk/utils.d.ts +12 -0
  63. package/build/sdk/utils.js +45 -0
  64. package/example/README.md +0 -6
  65. package/example/package-lock.json +1070 -2
  66. package/example/package.json +4 -3
  67. package/example/server/api/index.js +18 -18
  68. package/example/server/index.js +4 -6
  69. package/flagsmith-engine/environments/integrations/models.ts +4 -0
  70. package/flagsmith-engine/environments/models.ts +50 -0
  71. package/flagsmith-engine/environments/util.ts +29 -0
  72. package/flagsmith-engine/features/constants.ts +4 -0
  73. package/flagsmith-engine/features/models.ts +105 -0
  74. package/flagsmith-engine/features/util.ts +38 -0
  75. package/flagsmith-engine/identities/models.ts +60 -0
  76. package/flagsmith-engine/identities/traits/models.ts +9 -0
  77. package/flagsmith-engine/identities/util.ts +30 -0
  78. package/flagsmith-engine/index.ts +93 -0
  79. package/flagsmith-engine/organisations/models.ts +25 -0
  80. package/flagsmith-engine/organisations/util.ts +11 -0
  81. package/flagsmith-engine/projects/models.ts +22 -0
  82. package/flagsmith-engine/projects/util.ts +18 -0
  83. package/flagsmith-engine/segments/constants.ts +31 -0
  84. package/flagsmith-engine/segments/evaluators.ts +72 -0
  85. package/flagsmith-engine/segments/models.ts +103 -0
  86. package/flagsmith-engine/segments/util.ts +29 -0
  87. package/flagsmith-engine/utils/collections.ts +14 -0
  88. package/flagsmith-engine/utils/errors.ts +1 -0
  89. package/flagsmith-engine/utils/hashing/index.ts +52 -0
  90. package/flagsmith-engine/utils/index.ts +10 -0
  91. package/index.ts +6 -0
  92. package/jest.config.js +5 -0
  93. package/package.json +28 -12
  94. package/sdk/analytics.ts +60 -0
  95. package/sdk/errors.ts +2 -0
  96. package/sdk/index.ts +270 -0
  97. package/sdk/models.ts +145 -0
  98. package/sdk/polling_manager.ts +31 -0
  99. package/sdk/utils.ts +45 -0
  100. package/tests/engine/e2e/engine.test.ts +51 -0
  101. package/tests/engine/engine-tests/engine-test-data/data/environment_n9fbf9h3v4fFgH3U3ngWhb.json +12393 -0
  102. package/tests/engine/engine-tests/engine-test-data/readme.md +30 -0
  103. package/tests/engine/unit/egine.test.ts +96 -0
  104. package/tests/engine/unit/environments/builder.test.ts +148 -0
  105. package/tests/engine/unit/environments/models.test.ts +49 -0
  106. package/tests/engine/unit/features/models.test.ts +72 -0
  107. package/tests/engine/unit/identities/identities_builders.test.ts +85 -0
  108. package/tests/engine/unit/identities/identities_models.test.ts +105 -0
  109. package/tests/engine/unit/organization/models.test.ts +12 -0
  110. package/tests/engine/unit/segments/segments_model.test.ts +101 -0
  111. package/tests/engine/unit/segments/util.ts +151 -0
  112. package/tests/engine/unit/utils.ts +114 -0
  113. package/tests/index.js +0 -0
  114. package/tests/sdk/analytics.test.ts +43 -0
  115. package/tests/sdk/data/environment.json +33 -0
  116. package/tests/sdk/data/flags.json +20 -0
  117. package/tests/sdk/data/identities.json +29 -0
  118. package/tests/sdk/flagsmith.test.ts +184 -0
  119. package/tests/sdk/polling.test.ts +34 -0
  120. package/tests/sdk/utils.ts +39 -0
  121. package/tsconfig.json +19 -0
  122. package/.idea/codeStyles/Project.xml +0 -52
  123. package/.idea/codeStyles/codeStyleConfig.xml +0 -5
  124. package/flagsmith-core.js +0 -241
  125. package/index.d.ts +0 -85
  126. 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
@@ -0,0 +1,6 @@
1
+ import Flagsmith from './sdk';
2
+
3
+ export { Flagsmith } from './sdk';
4
+ // export default Flagsmith;
5
+
6
+ module.exports = Flagsmith;
package/jest.config.js ADDED
@@ -0,0 +1,5 @@
1
+ /** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */
2
+ module.exports = {
3
+ preset: 'ts-jest',
4
+ testEnvironment: 'node'
5
+ };
package/package.json CHANGED
@@ -1,24 +1,25 @@
1
1
  {
2
2
  "name": "flagsmith-nodejs",
3
- "version": "1.1.0",
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/SolidStateGroup/bullet-train-nodejs-client"
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 flagger",
13
+ "feature flags",
14
+ "feature toggles",
15
+ "remote configuration",
15
16
  "continuous deployment"
16
17
  ],
17
18
  "bugs": {
18
- "url": "https://github.com/SolidStateGroup/bullet-train-nodejs-client/issues"
19
+ "url": "https://github.com/Flagsmith/flagsmith-nodejs-client/issues"
19
20
  },
20
- "homepage": "http://bullet-train.io",
21
- "author": "SSG",
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@solidstategroup.com",
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
- "node-fetch": "^2.1.2"
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
- "prettier": "^2.2.1"
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
  }
@@ -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
@@ -0,0 +1,2 @@
1
+ export class FlagsmithClientError extends Error {}
2
+ export class FlagsmithAPIError extends Error {}