flagsmith-nodejs 3.1.1 → 3.3.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/.husky/pre-commit +1 -1
- package/README.md +2 -1
- package/build/flagsmith-engine/environments/models.d.ts +2 -5
- package/build/flagsmith-engine/environments/models.js +1 -0
- package/build/flagsmith-engine/environments/util.js +8 -2
- package/build/flagsmith-engine/features/util.js +2 -2
- package/build/flagsmith-engine/index.d.ts +0 -1
- package/build/flagsmith-engine/index.js +11 -13
- package/build/index.d.ts +2 -1
- package/build/index.js +1 -2
- package/build/sdk/index.d.ts +20 -9
- package/build/sdk/index.js +119 -53
- package/build/sdk/offline_handlers.d.ts +9 -0
- package/build/sdk/offline_handlers.js +66 -0
- package/build/sdk/types.d.ts +4 -1
- package/flagsmith-engine/environments/models.ts +2 -5
- package/flagsmith-engine/environments/util.ts +6 -0
- package/flagsmith-engine/features/util.ts +14 -13
- package/flagsmith-engine/index.ts +0 -1
- package/index.ts +4 -1
- package/package.json +1 -1
- package/sdk/index.ts +119 -48
- package/sdk/offline_handlers.ts +22 -0
- package/sdk/types.ts +4 -1
- package/tests/sdk/data/environment.json +27 -1
- package/tests/sdk/data/offline-environment.json +93 -0
- package/tests/sdk/flagsmith.test.ts +115 -27
- package/tests/sdk/offline-handlers.test.ts +33 -0
- package/tests/sdk/utils.ts +2 -2
- package/build/flagsmith-engine/environments/integrations/models.d.ts +0 -4
- package/build/flagsmith-engine/environments/integrations/models.js +0 -11
- package/examples/README.md +0 -3
- package/flagsmith-engine/environments/integrations/models.ts +0 -4
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __extends = (this && this.__extends) || (function () {
|
|
3
|
+
var extendStatics = function (d, b) {
|
|
4
|
+
extendStatics = Object.setPrototypeOf ||
|
|
5
|
+
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
|
|
6
|
+
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
|
|
7
|
+
return extendStatics(d, b);
|
|
8
|
+
};
|
|
9
|
+
return function (d, b) {
|
|
10
|
+
if (typeof b !== "function" && b !== null)
|
|
11
|
+
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
|
|
12
|
+
extendStatics(d, b);
|
|
13
|
+
function __() { this.constructor = d; }
|
|
14
|
+
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
|
|
15
|
+
};
|
|
16
|
+
})();
|
|
17
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
18
|
+
if (k2 === undefined) k2 = k;
|
|
19
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
20
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
21
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
22
|
+
}
|
|
23
|
+
Object.defineProperty(o, k2, desc);
|
|
24
|
+
}) : (function(o, m, k, k2) {
|
|
25
|
+
if (k2 === undefined) k2 = k;
|
|
26
|
+
o[k2] = m[k];
|
|
27
|
+
}));
|
|
28
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
29
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
30
|
+
}) : function(o, v) {
|
|
31
|
+
o["default"] = v;
|
|
32
|
+
});
|
|
33
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
34
|
+
if (mod && mod.__esModule) return mod;
|
|
35
|
+
var result = {};
|
|
36
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
37
|
+
__setModuleDefault(result, mod);
|
|
38
|
+
return result;
|
|
39
|
+
};
|
|
40
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
41
|
+
exports.LocalFileHandler = exports.BaseOfflineHandler = void 0;
|
|
42
|
+
var fs = __importStar(require("fs"));
|
|
43
|
+
var util_1 = require("../flagsmith-engine/environments/util");
|
|
44
|
+
var BaseOfflineHandler = /** @class */ (function () {
|
|
45
|
+
function BaseOfflineHandler() {
|
|
46
|
+
}
|
|
47
|
+
BaseOfflineHandler.prototype.getEnvironment = function () {
|
|
48
|
+
throw new Error('Not implemented');
|
|
49
|
+
};
|
|
50
|
+
return BaseOfflineHandler;
|
|
51
|
+
}());
|
|
52
|
+
exports.BaseOfflineHandler = BaseOfflineHandler;
|
|
53
|
+
var LocalFileHandler = /** @class */ (function (_super) {
|
|
54
|
+
__extends(LocalFileHandler, _super);
|
|
55
|
+
function LocalFileHandler(environment_document_path) {
|
|
56
|
+
var _this = _super.call(this) || this;
|
|
57
|
+
var environment_document = fs.readFileSync(environment_document_path, 'utf8');
|
|
58
|
+
_this.environment = (0, util_1.buildEnvironmentModel)(JSON.parse(environment_document));
|
|
59
|
+
return _this;
|
|
60
|
+
}
|
|
61
|
+
LocalFileHandler.prototype.getEnvironment = function () {
|
|
62
|
+
return this.environment;
|
|
63
|
+
};
|
|
64
|
+
return LocalFileHandler;
|
|
65
|
+
}(BaseOfflineHandler));
|
|
66
|
+
exports.LocalFileHandler = LocalFileHandler;
|
package/build/sdk/types.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { DefaultFlag, Flags } from "./models";
|
|
|
2
2
|
import { EnvironmentModel } from "../flagsmith-engine";
|
|
3
3
|
import { RequestInit } from "node-fetch";
|
|
4
4
|
import { Logger } from "pino";
|
|
5
|
+
import { BaseOfflineHandler } from "./offline_handlers";
|
|
5
6
|
export interface FlagsmithCache {
|
|
6
7
|
get(key: string): Promise<Flags | undefined> | undefined;
|
|
7
8
|
set(key: string, value: Flags, ttl: string | number): boolean | Promise<boolean>;
|
|
@@ -9,7 +10,7 @@ export interface FlagsmithCache {
|
|
|
9
10
|
[key: string]: any;
|
|
10
11
|
}
|
|
11
12
|
export interface FlagsmithConfig {
|
|
12
|
-
environmentKey
|
|
13
|
+
environmentKey?: string;
|
|
13
14
|
apiUrl?: string;
|
|
14
15
|
agent?: RequestInit['agent'];
|
|
15
16
|
customHeaders?: {
|
|
@@ -24,4 +25,6 @@ export interface FlagsmithConfig {
|
|
|
24
25
|
cache?: FlagsmithCache;
|
|
25
26
|
onEnvironmentChange?: (error: Error | null, result: EnvironmentModel) => void;
|
|
26
27
|
logger?: Logger;
|
|
28
|
+
offlineMode?: boolean;
|
|
29
|
+
offlineHandler?: BaseOfflineHandler;
|
|
27
30
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { FeatureStateModel } from '../features/models';
|
|
2
|
+
import { IdentityModel } from '../identities/models';
|
|
2
3
|
import { ProjectModel } from '../projects/models';
|
|
3
|
-
import { IntegrationModel } from './integrations/models';
|
|
4
4
|
|
|
5
5
|
export class EnvironmentAPIKeyModel {
|
|
6
6
|
id: number;
|
|
@@ -37,10 +37,7 @@ export class EnvironmentModel {
|
|
|
37
37
|
apiKey: string;
|
|
38
38
|
project: ProjectModel;
|
|
39
39
|
featureStates: FeatureStateModel[] = [];
|
|
40
|
-
|
|
41
|
-
segment_config?: IntegrationModel;
|
|
42
|
-
mixpanel_config?: IntegrationModel;
|
|
43
|
-
heap_config?: IntegrationModel;
|
|
40
|
+
identityOverrides: IdentityModel[] = [];
|
|
44
41
|
|
|
45
42
|
constructor(id: number, apiKey: string, project: ProjectModel) {
|
|
46
43
|
this.id = id;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { buildFeatureStateModel } from '../features/util';
|
|
2
|
+
import { buildIdentityModel } from '../identities/util';
|
|
2
3
|
import { buildProjectModel } from '../projects/util';
|
|
3
4
|
import { EnvironmentAPIKeyModel, EnvironmentModel } from './models';
|
|
4
5
|
|
|
@@ -13,6 +14,11 @@ export function buildEnvironmentModel(environmentJSON: any) {
|
|
|
13
14
|
project
|
|
14
15
|
);
|
|
15
16
|
environmentModel.featureStates = featureStates;
|
|
17
|
+
if (!!environmentJSON.identity_overrides) {
|
|
18
|
+
environmentModel.identityOverrides = environmentJSON.identity_overrides.map((identityData: any) =>
|
|
19
|
+
buildIdentityModel(identityData)
|
|
20
|
+
);
|
|
21
|
+
}
|
|
16
22
|
return environmentModel;
|
|
17
23
|
}
|
|
18
24
|
|
|
@@ -16,25 +16,26 @@ export function buildFeatureStateModel(featuresStateModelJSON: any): FeatureStat
|
|
|
16
16
|
featuresStateModelJSON.enabled,
|
|
17
17
|
featuresStateModelJSON.django_id,
|
|
18
18
|
featuresStateModelJSON.feature_state_value,
|
|
19
|
-
featuresStateModelJSON.
|
|
19
|
+
featuresStateModelJSON.featurestate_uuid
|
|
20
20
|
);
|
|
21
21
|
|
|
22
|
-
featureStateModel.featureSegment = featuresStateModelJSON.feature_segment ?
|
|
23
|
-
buildFeatureSegment(featuresStateModelJSON.feature_segment) :
|
|
22
|
+
featureStateModel.featureSegment = featuresStateModelJSON.feature_segment ?
|
|
23
|
+
buildFeatureSegment(featuresStateModelJSON.feature_segment) :
|
|
24
24
|
undefined;
|
|
25
25
|
|
|
26
26
|
const multivariateFeatureStateValues = featuresStateModelJSON.multivariate_feature_state_values
|
|
27
27
|
? featuresStateModelJSON.multivariate_feature_state_values.map((fsv: any) => {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
28
|
+
const featureOption = new MultivariateFeatureOptionModel(
|
|
29
|
+
fsv.multivariate_feature_option.value,
|
|
30
|
+
fsv.multivariate_feature_option.id
|
|
31
|
+
);
|
|
32
|
+
return new MultivariateFeatureStateValueModel(
|
|
33
|
+
featureOption,
|
|
34
|
+
fsv.percentage_allocation,
|
|
35
|
+
fsv.id,
|
|
36
|
+
fsv.mv_fs_value_uuid
|
|
37
|
+
);
|
|
38
|
+
})
|
|
38
39
|
: [];
|
|
39
40
|
|
|
40
41
|
featureStateModel.multivariateFeatureStateValues = multivariateFeatureStateValues;
|
|
@@ -7,7 +7,6 @@ import { SegmentModel } from './segments/models';
|
|
|
7
7
|
import { FeatureStateNotFound } from './utils/errors';
|
|
8
8
|
|
|
9
9
|
export { EnvironmentModel } from './environments/models';
|
|
10
|
-
export { IntegrationModel } from './environments/integrations/models';
|
|
11
10
|
export { FeatureStateModel } from './features/models';
|
|
12
11
|
export { IdentityModel } from './identities/models';
|
|
13
12
|
export { TraitModel } from './identities/traits/models';
|
package/index.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "flagsmith-nodejs",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.3.0",
|
|
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
5
|
"main": "build/index.js",
|
|
6
6
|
"repository": {
|
package/sdk/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { RequestInit } from
|
|
1
|
+
import { RequestInit } from 'node-fetch';
|
|
2
2
|
import { getEnvironmentFeatureStates, getIdentityFeatureStates } from '../flagsmith-engine';
|
|
3
3
|
import { EnvironmentModel } from '../flagsmith-engine/environments/models';
|
|
4
4
|
import { buildEnvironmentModel } from '../flagsmith-engine/environments/util';
|
|
@@ -6,6 +6,7 @@ import { IdentityModel } from '../flagsmith-engine/identities/models';
|
|
|
6
6
|
import { TraitModel } from '../flagsmith-engine/identities/traits/models';
|
|
7
7
|
|
|
8
8
|
import { AnalyticsProcessor } from './analytics';
|
|
9
|
+
import { BaseOfflineHandler } from './offline_handlers';
|
|
9
10
|
import { FlagsmithAPIError, FlagsmithClientError } from './errors';
|
|
10
11
|
|
|
11
12
|
import { DefaultFlag, Flags } from './models';
|
|
@@ -14,7 +15,7 @@ import { generateIdentitiesData, retryFetch } from './utils';
|
|
|
14
15
|
import { SegmentModel } from '../flagsmith-engine/segments/models';
|
|
15
16
|
import { getIdentitySegments } from '../flagsmith-engine/segments/evaluators';
|
|
16
17
|
import { FlagsmithCache, FlagsmithConfig } from './types';
|
|
17
|
-
import pino, { Logger } from
|
|
18
|
+
import pino, { Logger } from 'pino';
|
|
18
19
|
|
|
19
20
|
export { AnalyticsProcessor } from './analytics';
|
|
20
21
|
export { FlagsmithAPIError, FlagsmithClientError } from './errors';
|
|
@@ -26,10 +27,9 @@ export { FlagsmithCache, FlagsmithConfig } from './types';
|
|
|
26
27
|
const DEFAULT_API_URL = 'https://edge.api.flagsmith.com/api/v1/';
|
|
27
28
|
const DEFAULT_REQUEST_TIMEOUT_SECONDS = 10;
|
|
28
29
|
|
|
29
|
-
|
|
30
30
|
export class Flagsmith {
|
|
31
|
-
environmentKey?: string;
|
|
32
|
-
apiUrl
|
|
31
|
+
environmentKey?: string = undefined;
|
|
32
|
+
apiUrl?: string = undefined;
|
|
33
33
|
customHeaders?: { [key: string]: any };
|
|
34
34
|
agent: RequestInit['agent'];
|
|
35
35
|
requestTimeoutMs?: number;
|
|
@@ -40,12 +40,16 @@ export class Flagsmith {
|
|
|
40
40
|
defaultFlagHandler?: (featureName: string) => DefaultFlag;
|
|
41
41
|
|
|
42
42
|
|
|
43
|
-
environmentFlagsUrl
|
|
44
|
-
identitiesUrl
|
|
45
|
-
environmentUrl
|
|
43
|
+
environmentFlagsUrl?: string;
|
|
44
|
+
identitiesUrl?: string;
|
|
45
|
+
environmentUrl?: string;
|
|
46
46
|
|
|
47
47
|
environmentDataPollingManager?: EnvironmentDataPollingManager;
|
|
48
48
|
environment!: EnvironmentModel;
|
|
49
|
+
offlineMode: boolean = false;
|
|
50
|
+
offlineHandler?: BaseOfflineHandler = undefined;
|
|
51
|
+
|
|
52
|
+
identitiesWithOverridesByIdentifier?: Map<string, IdentityModel>;
|
|
49
53
|
|
|
50
54
|
private cache?: FlagsmithCache;
|
|
51
55
|
private onEnvironmentChange?: (error: Error | null, result: EnvironmentModel) => void;
|
|
@@ -65,6 +69,7 @@ export class Flagsmith {
|
|
|
65
69
|
* const featureEnabledForIdentity = identityFlags.isFeatureEnabled("foo")
|
|
66
70
|
*
|
|
67
71
|
* @param {string} data.environmentKey: The environment key obtained from Flagsmith interface
|
|
72
|
+
* Required unless offlineMode is True.
|
|
68
73
|
@param {string} data.apiUrl: Override the URL of the Flagsmith API to communicate with
|
|
69
74
|
@param data.customHeaders: Additional headers to add to requests made to the
|
|
70
75
|
Flagsmith API
|
|
@@ -78,16 +83,26 @@ export class Flagsmith {
|
|
|
78
83
|
@param {boolean} data.enableAnalytics: if enabled, sends additional requests to the Flagsmith
|
|
79
84
|
API to power flag analytics charts
|
|
80
85
|
@param data.defaultFlagHandler: callable which will be used in the case where
|
|
81
|
-
flags cannot be retrieved from the API or a non
|
|
86
|
+
flags cannot be retrieved from the API or a non-existent feature is
|
|
82
87
|
requested
|
|
83
88
|
@param data.logger: an instance of the pino Logger class to use for logging
|
|
84
|
-
|
|
85
|
-
|
|
89
|
+
@param {boolean} data.offlineMode: sets the client into offline mode. Relies on offlineHandler for
|
|
90
|
+
evaluating flags.
|
|
91
|
+
@param {BaseOfflineHandler} data.offlineHandler: provide a handler for offline logic. Used to get environment
|
|
92
|
+
document from another source when in offlineMode. Works in place of
|
|
93
|
+
defaultFlagHandler if offlineMode is not set and using remote evaluation.
|
|
94
|
+
*/
|
|
95
|
+
constructor(data: FlagsmithConfig = {}) {
|
|
96
|
+
// if (!data.offlineMode && !data.environmentKey) {
|
|
97
|
+
// throw new Error('ValueError: environmentKey is required.');
|
|
98
|
+
// }
|
|
99
|
+
|
|
86
100
|
this.agent = data.agent;
|
|
87
101
|
this.environmentKey = data.environmentKey;
|
|
88
102
|
this.apiUrl = data.apiUrl || this.apiUrl;
|
|
89
103
|
this.customHeaders = data.customHeaders;
|
|
90
|
-
this.requestTimeoutMs =
|
|
104
|
+
this.requestTimeoutMs =
|
|
105
|
+
1000 * (data.requestTimeoutSeconds ?? DEFAULT_REQUEST_TIMEOUT_SECONDS);
|
|
91
106
|
this.enableLocalEvaluation = data.enableLocalEvaluation;
|
|
92
107
|
this.environmentRefreshIntervalSeconds =
|
|
93
108
|
data.environmentRefreshIntervalSeconds || this.environmentRefreshIntervalSeconds;
|
|
@@ -95,14 +110,26 @@ export class Flagsmith {
|
|
|
95
110
|
this.enableAnalytics = data.enableAnalytics || false;
|
|
96
111
|
this.defaultFlagHandler = data.defaultFlagHandler;
|
|
97
112
|
|
|
98
|
-
this.environmentFlagsUrl = `${this.apiUrl}flags/`;
|
|
99
|
-
this.identitiesUrl = `${this.apiUrl}identities/`;
|
|
100
|
-
this.environmentUrl = `${this.apiUrl}environment-document/`;
|
|
101
113
|
this.onEnvironmentChange = data.onEnvironmentChange;
|
|
102
114
|
this.logger = data.logger || pino();
|
|
115
|
+
this.offlineMode = data.offlineMode || false;
|
|
116
|
+
this.offlineHandler = data.offlineHandler;
|
|
117
|
+
|
|
118
|
+
// argument validation
|
|
119
|
+
if (this.offlineMode && !this.offlineHandler) {
|
|
120
|
+
throw new Error('ValueError: offlineHandler must be provided to use offline mode.');
|
|
121
|
+
} else if (this.defaultFlagHandler && this.offlineHandler) {
|
|
122
|
+
throw new Error('ValueError: Cannot use both defaultFlagHandler and offlineHandler.');
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (this.offlineHandler) {
|
|
126
|
+
this.environment = this.offlineHandler.getEnvironment();
|
|
127
|
+
}
|
|
103
128
|
|
|
104
129
|
if (!!data.cache) {
|
|
105
|
-
const missingMethods: string[] = ['has', 'get', 'set'].filter(
|
|
130
|
+
const missingMethods: string[] = ['has', 'get', 'set'].filter(
|
|
131
|
+
method => data.cache && !data.cache[method]
|
|
132
|
+
);
|
|
106
133
|
|
|
107
134
|
if (missingMethods.length > 0) {
|
|
108
135
|
throw new Error(
|
|
@@ -114,28 +141,40 @@ export class Flagsmith {
|
|
|
114
141
|
this.cache = data.cache;
|
|
115
142
|
}
|
|
116
143
|
|
|
117
|
-
if (this.
|
|
118
|
-
if (!this.environmentKey
|
|
119
|
-
|
|
120
|
-
|
|
144
|
+
if (!this.offlineMode) {
|
|
145
|
+
if (!this.environmentKey) {
|
|
146
|
+
throw new Error('ValueError: environmentKey is required.');
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const apiUrl = data.apiUrl || DEFAULT_API_URL;
|
|
150
|
+
this.apiUrl = apiUrl.endsWith('/') ? apiUrl : `${apiUrl}/`;
|
|
151
|
+
this.environmentFlagsUrl = `${this.apiUrl}flags/`;
|
|
152
|
+
this.identitiesUrl = `${this.apiUrl}identities/`;
|
|
153
|
+
this.environmentUrl = `${this.apiUrl}environment-document/`;
|
|
154
|
+
|
|
155
|
+
if (this.enableLocalEvaluation) {
|
|
156
|
+
if (!this.environmentKey.startsWith('ser.')) {
|
|
157
|
+
console.error(
|
|
158
|
+
'In order to use local evaluation, please generate a server key in the environment settings page.'
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
this.environmentDataPollingManager = new EnvironmentDataPollingManager(
|
|
162
|
+
this,
|
|
163
|
+
this.environmentRefreshIntervalSeconds
|
|
121
164
|
);
|
|
165
|
+
this.environmentDataPollingManager.start();
|
|
166
|
+
this.updateEnvironment();
|
|
122
167
|
}
|
|
123
|
-
this.environmentDataPollingManager = new EnvironmentDataPollingManager(
|
|
124
|
-
this,
|
|
125
|
-
this.environmentRefreshIntervalSeconds
|
|
126
|
-
);
|
|
127
|
-
this.environmentDataPollingManager.start();
|
|
128
|
-
this.updateEnvironment();
|
|
129
|
-
}
|
|
130
168
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
169
|
+
this.analyticsProcessor = data.enableAnalytics
|
|
170
|
+
? new AnalyticsProcessor({
|
|
171
|
+
environmentKey: this.environmentKey,
|
|
172
|
+
baseApiUrl: this.apiUrl,
|
|
173
|
+
requestTimeoutMs: this.requestTimeoutMs,
|
|
174
|
+
logger: this.logger
|
|
175
|
+
})
|
|
176
|
+
: undefined;
|
|
177
|
+
}
|
|
139
178
|
}
|
|
140
179
|
/**
|
|
141
180
|
* Get all the default for flags for the current environment.
|
|
@@ -143,15 +182,15 @@ export class Flagsmith {
|
|
|
143
182
|
* @returns Flags object holding all the flags for the current environment.
|
|
144
183
|
*/
|
|
145
184
|
async getEnvironmentFlags(): Promise<Flags> {
|
|
146
|
-
const cachedItem = !!this.cache && await this.cache.get(`flags`);
|
|
185
|
+
const cachedItem = !!this.cache && (await this.cache.get(`flags`));
|
|
147
186
|
if (!!cachedItem) {
|
|
148
187
|
return cachedItem;
|
|
149
188
|
}
|
|
150
|
-
if (this.enableLocalEvaluation) {
|
|
189
|
+
if (this.enableLocalEvaluation && !this.offlineMode) {
|
|
151
190
|
return new Promise((resolve, reject) =>
|
|
152
191
|
this.environmentPromise!.then(() => {
|
|
153
192
|
resolve(this.getEnvironmentFlagsFromDocument());
|
|
154
|
-
}).catch(
|
|
193
|
+
}).catch(e => reject(e))
|
|
155
194
|
);
|
|
156
195
|
}
|
|
157
196
|
if (this.environment) {
|
|
@@ -160,6 +199,7 @@ export class Flagsmith {
|
|
|
160
199
|
|
|
161
200
|
return this.getEnvironmentFlagsFromApi();
|
|
162
201
|
}
|
|
202
|
+
|
|
163
203
|
/**
|
|
164
204
|
* Get all the flags for the current environment for a given identity. Will also
|
|
165
205
|
upsert all traits to the Flagsmith API for future evaluations. Providing a
|
|
@@ -173,10 +213,10 @@ export class Flagsmith {
|
|
|
173
213
|
*/
|
|
174
214
|
async getIdentityFlags(identifier: string, traits?: { [key: string]: any }): Promise<Flags> {
|
|
175
215
|
if (!identifier) {
|
|
176
|
-
throw new Error(
|
|
216
|
+
throw new Error('`identifier` argument is missing or invalid.');
|
|
177
217
|
}
|
|
178
218
|
|
|
179
|
-
const cachedItem = !!this.cache && await this.cache.get(`flags-${identifier}`);
|
|
219
|
+
const cachedItem = !!this.cache && (await this.cache.get(`flags-${identifier}`));
|
|
180
220
|
if (!!cachedItem) {
|
|
181
221
|
return cachedItem;
|
|
182
222
|
}
|
|
@@ -188,6 +228,10 @@ export class Flagsmith {
|
|
|
188
228
|
}).catch(e => reject(e))
|
|
189
229
|
);
|
|
190
230
|
}
|
|
231
|
+
if (this.offlineMode) {
|
|
232
|
+
return this.getIdentityFlagsFromDocument(identifier, traits || {});
|
|
233
|
+
}
|
|
234
|
+
|
|
191
235
|
return this.getIdentityFlagsFromApi(identifier, traits);
|
|
192
236
|
}
|
|
193
237
|
|
|
@@ -207,14 +251,14 @@ export class Flagsmith {
|
|
|
207
251
|
traits?: { [key: string]: any }
|
|
208
252
|
): Promise<SegmentModel[]> {
|
|
209
253
|
if (!identifier) {
|
|
210
|
-
throw new Error(
|
|
254
|
+
throw new Error('`identifier` argument is missing or invalid.');
|
|
211
255
|
}
|
|
212
256
|
|
|
213
257
|
traits = traits || {};
|
|
214
258
|
if (this.enableLocalEvaluation) {
|
|
215
259
|
return new Promise((resolve, reject) => {
|
|
216
260
|
return this.environmentPromise!.then(() => {
|
|
217
|
-
const identityModel = this.
|
|
261
|
+
const identityModel = this.getIdentityModel(
|
|
218
262
|
identifier,
|
|
219
263
|
Object.keys(traits || {}).map(key => ({
|
|
220
264
|
key,
|
|
@@ -224,7 +268,7 @@ export class Flagsmith {
|
|
|
224
268
|
|
|
225
269
|
const segments = getIdentitySegments(this.environment, identityModel);
|
|
226
270
|
return resolve(segments);
|
|
227
|
-
}).catch(
|
|
271
|
+
}).catch(e => reject(e));
|
|
228
272
|
});
|
|
229
273
|
}
|
|
230
274
|
console.error('This function is only permitted with local evaluation.');
|
|
@@ -247,6 +291,11 @@ export class Flagsmith {
|
|
|
247
291
|
} else {
|
|
248
292
|
this.environment = await request;
|
|
249
293
|
}
|
|
294
|
+
if (this.environment.identityOverrides?.length) {
|
|
295
|
+
this.identitiesWithOverridesByIdentifier = new Map<string, IdentityModel>(
|
|
296
|
+
this.environment.identityOverrides.map(identity => [identity.identifier, identity]
|
|
297
|
+
));
|
|
298
|
+
}
|
|
250
299
|
if (this.onEnvironmentChange) {
|
|
251
300
|
this.onEnvironmentChange(null, this.environment);
|
|
252
301
|
}
|
|
@@ -286,7 +335,7 @@ export class Flagsmith {
|
|
|
286
335
|
headers: headers
|
|
287
336
|
},
|
|
288
337
|
this.retries,
|
|
289
|
-
this.requestTimeoutMs || undefined
|
|
338
|
+
this.requestTimeoutMs || undefined
|
|
290
339
|
);
|
|
291
340
|
|
|
292
341
|
if (data.status !== 200) {
|
|
@@ -304,6 +353,9 @@ export class Flagsmith {
|
|
|
304
353
|
private environmentPromise: Promise<any> | undefined;
|
|
305
354
|
|
|
306
355
|
private async getEnvironmentFromApi() {
|
|
356
|
+
if (!this.environmentUrl) {
|
|
357
|
+
throw new Error('`apiUrl` argument is missing or invalid.');
|
|
358
|
+
}
|
|
307
359
|
const environment_data = await this.getJSONResponse(this.environmentUrl, 'GET');
|
|
308
360
|
return buildEnvironmentModel(environment_data);
|
|
309
361
|
}
|
|
@@ -321,8 +373,11 @@ export class Flagsmith {
|
|
|
321
373
|
return flags;
|
|
322
374
|
}
|
|
323
375
|
|
|
324
|
-
private async getIdentityFlagsFromDocument(
|
|
325
|
-
|
|
376
|
+
private async getIdentityFlagsFromDocument(
|
|
377
|
+
identifier: string,
|
|
378
|
+
traits: { [key: string]: any }
|
|
379
|
+
): Promise<Flags> {
|
|
380
|
+
const identityModel = this.getIdentityModel(
|
|
326
381
|
identifier,
|
|
327
382
|
Object.keys(traits).map(key => ({
|
|
328
383
|
key,
|
|
@@ -348,6 +403,9 @@ export class Flagsmith {
|
|
|
348
403
|
}
|
|
349
404
|
|
|
350
405
|
private async getEnvironmentFlagsFromApi() {
|
|
406
|
+
if (!this.environmentFlagsUrl) {
|
|
407
|
+
throw new Error('`apiUrl` argument is missing or invalid.');
|
|
408
|
+
}
|
|
351
409
|
try {
|
|
352
410
|
const apiFlags = await this.getJSONResponse(this.environmentFlagsUrl, 'GET');
|
|
353
411
|
const flags = Flags.fromAPIFlags({
|
|
@@ -361,6 +419,9 @@ export class Flagsmith {
|
|
|
361
419
|
}
|
|
362
420
|
return flags;
|
|
363
421
|
} catch (e) {
|
|
422
|
+
if (this.offlineHandler) {
|
|
423
|
+
return this.getEnvironmentFlagsFromDocument();
|
|
424
|
+
}
|
|
364
425
|
if (this.defaultFlagHandler) {
|
|
365
426
|
return new Flags({
|
|
366
427
|
flags: {},
|
|
@@ -373,6 +434,9 @@ export class Flagsmith {
|
|
|
373
434
|
}
|
|
374
435
|
|
|
375
436
|
private async getIdentityFlagsFromApi(identifier: string, traits: { [key: string]: any }) {
|
|
437
|
+
if (!this.identitiesUrl) {
|
|
438
|
+
throw new Error('`apiUrl` argument is missing or invalid.');
|
|
439
|
+
}
|
|
376
440
|
try {
|
|
377
441
|
const data = generateIdentitiesData(identifier, traits);
|
|
378
442
|
const jsonResponse = await this.getJSONResponse(this.identitiesUrl, 'POST', data);
|
|
@@ -387,6 +451,9 @@ export class Flagsmith {
|
|
|
387
451
|
}
|
|
388
452
|
return flags;
|
|
389
453
|
} catch (e) {
|
|
454
|
+
if (this.offlineHandler) {
|
|
455
|
+
return this.getIdentityFlagsFromDocument(identifier, traits);
|
|
456
|
+
}
|
|
390
457
|
if (this.defaultFlagHandler) {
|
|
391
458
|
return new Flags({
|
|
392
459
|
flags: {},
|
|
@@ -398,11 +465,15 @@ export class Flagsmith {
|
|
|
398
465
|
}
|
|
399
466
|
}
|
|
400
467
|
|
|
401
|
-
private
|
|
468
|
+
private getIdentityModel(identifier: string, traits: { key: string; value: any }[]) {
|
|
402
469
|
const traitModels = traits.map(trait => new TraitModel(trait.key, trait.value));
|
|
470
|
+
let identityWithOverrides = this.identitiesWithOverridesByIdentifier?.get(identifier);
|
|
471
|
+
if (identityWithOverrides) {
|
|
472
|
+
identityWithOverrides.updateTraits(traitModels);
|
|
473
|
+
return identityWithOverrides;
|
|
474
|
+
}
|
|
403
475
|
return new IdentityModel('0', traitModels, [], this.environment.apiKey, identifier);
|
|
404
476
|
}
|
|
405
477
|
}
|
|
406
478
|
|
|
407
479
|
export default Flagsmith;
|
|
408
|
-
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import { buildEnvironmentModel } from '../flagsmith-engine/environments/util';
|
|
3
|
+
import { EnvironmentModel } from '../flagsmith-engine/environments/models';
|
|
4
|
+
|
|
5
|
+
export class BaseOfflineHandler {
|
|
6
|
+
getEnvironment() : EnvironmentModel {
|
|
7
|
+
throw new Error('Not implemented');
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export class LocalFileHandler extends BaseOfflineHandler {
|
|
12
|
+
environment: EnvironmentModel;
|
|
13
|
+
constructor(environment_document_path: string) {
|
|
14
|
+
super();
|
|
15
|
+
const environment_document = fs.readFileSync(environment_document_path, 'utf8');
|
|
16
|
+
this.environment = buildEnvironmentModel(JSON.parse(environment_document));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
getEnvironment(): EnvironmentModel {
|
|
20
|
+
return this.environment;
|
|
21
|
+
}
|
|
22
|
+
}
|
package/sdk/types.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { DefaultFlag, Flags } from "./models";
|
|
|
2
2
|
import { EnvironmentModel } from "../flagsmith-engine";
|
|
3
3
|
import { RequestInit } from "node-fetch";
|
|
4
4
|
import { Logger } from "pino";
|
|
5
|
+
import { BaseOfflineHandler } from "./offline_handlers";
|
|
5
6
|
|
|
6
7
|
export interface FlagsmithCache {
|
|
7
8
|
get(key: string): Promise<Flags|undefined> | undefined;
|
|
@@ -11,7 +12,7 @@ export interface FlagsmithCache {
|
|
|
11
12
|
}
|
|
12
13
|
|
|
13
14
|
export interface FlagsmithConfig {
|
|
14
|
-
environmentKey
|
|
15
|
+
environmentKey?: string;
|
|
15
16
|
apiUrl?: string;
|
|
16
17
|
agent?:RequestInit['agent'];
|
|
17
18
|
customHeaders?: { [key: string]: any };
|
|
@@ -24,4 +25,6 @@ export interface FlagsmithConfig {
|
|
|
24
25
|
cache?: FlagsmithCache,
|
|
25
26
|
onEnvironmentChange?: (error: Error | null, result: EnvironmentModel) => void,
|
|
26
27
|
logger?: Logger
|
|
28
|
+
offlineMode?: boolean;
|
|
29
|
+
offlineHandler?: BaseOfflineHandler;
|
|
27
30
|
}
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
"feature_states": [
|
|
18
18
|
{
|
|
19
19
|
"feature_state_value": "segment_override",
|
|
20
|
+
"featurestate_uuid": "dd77a1ab-08cf-4743-8a3b-19e730444a14",
|
|
20
21
|
"multivariate_feature_state_values": [],
|
|
21
22
|
"django_id": 81027,
|
|
22
23
|
"feature": {
|
|
@@ -88,5 +89,30 @@
|
|
|
88
89
|
"featurestate_uuid": "96fc3503-09d7-48f1-a83b-2dc903d5c08a",
|
|
89
90
|
"enabled": false
|
|
90
91
|
}
|
|
92
|
+
],
|
|
93
|
+
"identity_overrides": [
|
|
94
|
+
{
|
|
95
|
+
"identifier": "overridden-id",
|
|
96
|
+
"identity_uuid": "0f21cde8-63c5-4e50-baca-87897fa6cd01",
|
|
97
|
+
"created_date": "2019-08-27T14:53:45.698555Z",
|
|
98
|
+
"updated_at": "2023-07-14 16:12:00.000000",
|
|
99
|
+
"environment_api_key": "B62qaMZNwfiqT76p38ggrQ",
|
|
100
|
+
"identity_features": [
|
|
101
|
+
{
|
|
102
|
+
"id": 1,
|
|
103
|
+
"feature": {
|
|
104
|
+
"id": 1,
|
|
105
|
+
"name": "some_feature",
|
|
106
|
+
"type": "STANDARD"
|
|
107
|
+
},
|
|
108
|
+
"featurestate_uuid": "1bddb9a5-7e59-42c6-9be9-625fa369749f",
|
|
109
|
+
"feature_state_value": "some-overridden-value",
|
|
110
|
+
"enabled": false,
|
|
111
|
+
"environment": 1,
|
|
112
|
+
"identity": null,
|
|
113
|
+
"feature_segment": null
|
|
114
|
+
}
|
|
115
|
+
]
|
|
116
|
+
}
|
|
91
117
|
]
|
|
92
|
-
}
|
|
118
|
+
}
|