flagsmith-nodejs 5.1.1 → 6.0.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/build/cjs/sdk/analytics.d.ts +1 -1
- package/build/cjs/sdk/analytics.js +1 -1
- package/build/cjs/sdk/index.d.ts +44 -42
- package/build/cjs/sdk/index.js +150 -152
- package/build/cjs/sdk/polling_manager.d.ts +3 -1
- package/build/cjs/sdk/polling_manager.js +9 -2
- package/build/cjs/sdk/types.d.ts +73 -5
- package/build/cjs/sdk/utils.d.ts +24 -1
- package/build/cjs/sdk/utils.js +50 -18
- package/build/esm/sdk/analytics.d.ts +1 -1
- package/build/esm/sdk/analytics.js +1 -1
- package/build/esm/sdk/index.d.ts +44 -42
- package/build/esm/sdk/index.js +151 -153
- package/build/esm/sdk/polling_manager.d.ts +3 -1
- package/build/esm/sdk/polling_manager.js +9 -2
- package/build/esm/sdk/types.d.ts +73 -5
- package/build/esm/sdk/utils.d.ts +24 -1
- package/build/esm/sdk/utils.js +48 -17
- package/package.json +1 -1
- package/sdk/analytics.ts +5 -5
- package/sdk/index.ts +173 -176
- package/sdk/polling_manager.ts +9 -2
- package/sdk/types.ts +74 -3
- package/sdk/utils.ts +50 -16
- package/tests/sdk/analytics.test.ts +0 -4
- package/tests/sdk/flagsmith-cache.test.ts +5 -17
- package/tests/sdk/flagsmith-environment-flags.test.ts +1 -29
- package/tests/sdk/flagsmith-identity-flags.test.ts +20 -15
- package/tests/sdk/flagsmith.test.ts +88 -83
- package/tests/sdk/polling.test.ts +0 -4
- package/tests/sdk/utils.ts +28 -6
- package/vitest.config.ts +1 -0
- /package/{.prettierrc.js → .prettierrc.cjs} +0 -0
|
@@ -44,7 +44,7 @@ export declare class AnalyticsProcessor {
|
|
|
44
44
|
/**
|
|
45
45
|
* Track a single evaluation event for a feature.
|
|
46
46
|
*
|
|
47
|
-
*
|
|
47
|
+
* @see FlagsmithConfig.enableAnalytics
|
|
48
48
|
*/
|
|
49
49
|
trackFeature(featureName: string): void;
|
|
50
50
|
}
|
|
@@ -67,7 +67,7 @@ class AnalyticsProcessor {
|
|
|
67
67
|
/**
|
|
68
68
|
* Track a single evaluation event for a feature.
|
|
69
69
|
*
|
|
70
|
-
*
|
|
70
|
+
* @see FlagsmithConfig.enableAnalytics
|
|
71
71
|
*/
|
|
72
72
|
trackFeature(featureName) {
|
|
73
73
|
this.analyticsData[featureName] = (this.analyticsData[featureName] || 0) + 1;
|
package/build/cjs/sdk/index.d.ts
CHANGED
|
@@ -11,6 +11,30 @@ export { FlagsmithAPIError, FlagsmithClientError } from './errors.js';
|
|
|
11
11
|
export { DefaultFlag, Flags } from './models.js';
|
|
12
12
|
export { EnvironmentDataPollingManager } from './polling_manager.js';
|
|
13
13
|
export { FlagsmithCache, FlagsmithConfig } from './types.js';
|
|
14
|
+
/**
|
|
15
|
+
* A client for evaluating Flagsmith feature flags.
|
|
16
|
+
*
|
|
17
|
+
* Flags are evaluated remotely by the Flagsmith API over HTTP by default.
|
|
18
|
+
* To evaluate flags locally, create the client using {@link FlagsmithConfig.enableLocalEvaluation} and a server-side SDK key.
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* import { Flagsmith, Flags, DefaultFlag } from 'flagsmith-nodejs'
|
|
22
|
+
*
|
|
23
|
+
* const flagsmith = new Flagsmith({
|
|
24
|
+
* environmentKey: 'your_sdk_key',
|
|
25
|
+
* defaultFlagHandler: (flagKey: string) => { new DefaultFlag(...) },
|
|
26
|
+
* });
|
|
27
|
+
*
|
|
28
|
+
* // Fetch the current environment flags
|
|
29
|
+
* const environmentFlags: Flags = flagsmith.getEnvironmentFlags()
|
|
30
|
+
* const isFooEnabled: boolean = environmentFlags.isFeatureEnabled('foo')
|
|
31
|
+
*
|
|
32
|
+
* // Evaluate flags for any identity
|
|
33
|
+
* const identityFlags: Flags = flagsmith.getIdentityFlags('my_user_123', {'vip': true})
|
|
34
|
+
* const bannerVariation: string = identityFlags.getFeatureValue('banner_flag')
|
|
35
|
+
*
|
|
36
|
+
* @see FlagsmithConfig
|
|
37
|
+
*/
|
|
14
38
|
export declare class Flagsmith {
|
|
15
39
|
environmentKey?: string;
|
|
16
40
|
apiUrl?: string;
|
|
@@ -29,53 +53,24 @@ export declare class Flagsmith {
|
|
|
29
53
|
identitiesUrl?: string;
|
|
30
54
|
environmentUrl?: string;
|
|
31
55
|
environmentDataPollingManager?: EnvironmentDataPollingManager;
|
|
32
|
-
environment
|
|
56
|
+
private environment?;
|
|
33
57
|
offlineMode: boolean;
|
|
34
58
|
offlineHandler?: BaseOfflineHandler;
|
|
35
59
|
identitiesWithOverridesByIdentifier?: Map<string, IdentityModel>;
|
|
36
60
|
private cache?;
|
|
37
|
-
private onEnvironmentChange
|
|
61
|
+
private onEnvironmentChange;
|
|
38
62
|
private analyticsProcessor?;
|
|
39
63
|
private logger;
|
|
40
64
|
private customFetch;
|
|
65
|
+
private readonly requestRetryDelayMilliseconds;
|
|
41
66
|
/**
|
|
42
|
-
*
|
|
67
|
+
* Creates a new {@link Flagsmith} client.
|
|
43
68
|
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
* const environmentFlags = flagsmith.getEnvironmentFlags();
|
|
50
|
-
* const featureEnabled = environmentFlags.isFeatureEnabled('foo');
|
|
51
|
-
* const identityFlags = flagsmith.getIdentityFlags('identifier', {'foo': 'bar'});
|
|
52
|
-
* const featureEnabledForIdentity = identityFlags.isFeatureEnabled("foo")
|
|
53
|
-
*
|
|
54
|
-
* @param {string} data.environmentKey: The environment key obtained from Flagsmith interface
|
|
55
|
-
* Required unless offlineMode is True.
|
|
56
|
-
@param {string} data.apiUrl: Override the URL of the Flagsmith API to communicate with
|
|
57
|
-
@param data.customHeaders: Additional headers to add to requests made to the
|
|
58
|
-
Flagsmith API
|
|
59
|
-
@param {number} data.requestTimeoutSeconds: Number of seconds to wait for a request to
|
|
60
|
-
complete before terminating the request
|
|
61
|
-
@param {boolean} data.enableLocalEvaluation: Enables local evaluation of flags
|
|
62
|
-
@param {number} data.environmentRefreshIntervalSeconds: If using local evaluation,
|
|
63
|
-
specify the interval period between refreshes of local environment data
|
|
64
|
-
@param {number} data.retries: a urllib3.Retry object to use on all http requests to the
|
|
65
|
-
Flagsmith API
|
|
66
|
-
@param {boolean} data.enableAnalytics: if enabled, sends additional requests to the Flagsmith
|
|
67
|
-
API to power flag analytics charts
|
|
68
|
-
@param data.defaultFlagHandler: callable which will be used in the case where
|
|
69
|
-
flags cannot be retrieved from the API or a non-existent feature is
|
|
70
|
-
requested
|
|
71
|
-
@param data.logger: an instance of the pino Logger class to use for logging
|
|
72
|
-
@param {boolean} data.offlineMode: sets the client into offline mode. Relies on offlineHandler for
|
|
73
|
-
evaluating flags.
|
|
74
|
-
@param {BaseOfflineHandler} data.offlineHandler: provide a handler for offline logic. Used to get environment
|
|
75
|
-
document from another source when in offlineMode. Works in place of
|
|
76
|
-
defaultFlagHandler if offlineMode is not set and using remote evaluation.
|
|
77
|
-
*/
|
|
78
|
-
constructor(data?: FlagsmithConfig);
|
|
69
|
+
* If using local evaluation, the environment will be fetched lazily when needed by any method. Polling the
|
|
70
|
+
* environment for updates will start after {@link environmentRefreshIntervalSeconds} once the client is created.
|
|
71
|
+
* @param data The {@link FlagsmithConfig} options for this client.
|
|
72
|
+
*/
|
|
73
|
+
constructor(data: FlagsmithConfig);
|
|
79
74
|
/**
|
|
80
75
|
* Get all the default for flags for the current environment.
|
|
81
76
|
*
|
|
@@ -110,10 +105,11 @@ export declare class Flagsmith {
|
|
|
110
105
|
getIdentitySegments(identifier: string, traits?: {
|
|
111
106
|
[key: string]: any;
|
|
112
107
|
}): Promise<SegmentModel[]>;
|
|
108
|
+
private fetchEnvironment;
|
|
113
109
|
/**
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
*
|
|
110
|
+
* Fetch the latest environment state from the Flagsmith API to use for local flag evaluation.
|
|
111
|
+
*
|
|
112
|
+
* If the environment is currently being fetched, calling this method will not cause additional fetches.
|
|
117
113
|
*/
|
|
118
114
|
updateEnvironment(): Promise<void>;
|
|
119
115
|
close(): Promise<void>;
|
|
@@ -121,7 +117,13 @@ export declare class Flagsmith {
|
|
|
121
117
|
/**
|
|
122
118
|
* This promise ensures that the environment is retrieved before attempting to locally evaluate.
|
|
123
119
|
*/
|
|
124
|
-
private environmentPromise
|
|
120
|
+
private environmentPromise?;
|
|
121
|
+
/**
|
|
122
|
+
* Returns the current environment, fetching it from the API if needed.
|
|
123
|
+
*
|
|
124
|
+
* Calling this method concurrently while the environment is being fetched will not cause additional requests.
|
|
125
|
+
*/
|
|
126
|
+
getEnvironment(): Promise<EnvironmentModel>;
|
|
125
127
|
private getEnvironmentFromApi;
|
|
126
128
|
private getEnvironmentFlagsFromDocument;
|
|
127
129
|
private getIdentityFlagsFromDocument;
|
package/build/cjs/sdk/index.js
CHANGED
|
@@ -24,6 +24,30 @@ var polling_manager_js_2 = require("./polling_manager.js");
|
|
|
24
24
|
Object.defineProperty(exports, "EnvironmentDataPollingManager", { enumerable: true, get: function () { return polling_manager_js_2.EnvironmentDataPollingManager; } });
|
|
25
25
|
const DEFAULT_API_URL = 'https://edge.api.flagsmith.com/api/v1/';
|
|
26
26
|
const DEFAULT_REQUEST_TIMEOUT_SECONDS = 10;
|
|
27
|
+
/**
|
|
28
|
+
* A client for evaluating Flagsmith feature flags.
|
|
29
|
+
*
|
|
30
|
+
* Flags are evaluated remotely by the Flagsmith API over HTTP by default.
|
|
31
|
+
* To evaluate flags locally, create the client using {@link FlagsmithConfig.enableLocalEvaluation} and a server-side SDK key.
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* import { Flagsmith, Flags, DefaultFlag } from 'flagsmith-nodejs'
|
|
35
|
+
*
|
|
36
|
+
* const flagsmith = new Flagsmith({
|
|
37
|
+
* environmentKey: 'your_sdk_key',
|
|
38
|
+
* defaultFlagHandler: (flagKey: string) => { new DefaultFlag(...) },
|
|
39
|
+
* });
|
|
40
|
+
*
|
|
41
|
+
* // Fetch the current environment flags
|
|
42
|
+
* const environmentFlags: Flags = flagsmith.getEnvironmentFlags()
|
|
43
|
+
* const isFooEnabled: boolean = environmentFlags.isFeatureEnabled('foo')
|
|
44
|
+
*
|
|
45
|
+
* // Evaluate flags for any identity
|
|
46
|
+
* const identityFlags: Flags = flagsmith.getIdentityFlags('my_user_123', {'vip': true})
|
|
47
|
+
* const bannerVariation: string = identityFlags.getFeatureValue('banner_flag')
|
|
48
|
+
*
|
|
49
|
+
* @see FlagsmithConfig
|
|
50
|
+
*/
|
|
27
51
|
class Flagsmith {
|
|
28
52
|
environmentKey = undefined;
|
|
29
53
|
apiUrl = undefined;
|
|
@@ -49,61 +73,30 @@ class Flagsmith {
|
|
|
49
73
|
analyticsProcessor;
|
|
50
74
|
logger;
|
|
51
75
|
customFetch;
|
|
76
|
+
requestRetryDelayMilliseconds;
|
|
52
77
|
/**
|
|
53
|
-
*
|
|
78
|
+
* Creates a new {@link Flagsmith} client.
|
|
54
79
|
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
* const environmentFlags = flagsmith.getEnvironmentFlags();
|
|
61
|
-
* const featureEnabled = environmentFlags.isFeatureEnabled('foo');
|
|
62
|
-
* const identityFlags = flagsmith.getIdentityFlags('identifier', {'foo': 'bar'});
|
|
63
|
-
* const featureEnabledForIdentity = identityFlags.isFeatureEnabled("foo")
|
|
64
|
-
*
|
|
65
|
-
* @param {string} data.environmentKey: The environment key obtained from Flagsmith interface
|
|
66
|
-
* Required unless offlineMode is True.
|
|
67
|
-
@param {string} data.apiUrl: Override the URL of the Flagsmith API to communicate with
|
|
68
|
-
@param data.customHeaders: Additional headers to add to requests made to the
|
|
69
|
-
Flagsmith API
|
|
70
|
-
@param {number} data.requestTimeoutSeconds: Number of seconds to wait for a request to
|
|
71
|
-
complete before terminating the request
|
|
72
|
-
@param {boolean} data.enableLocalEvaluation: Enables local evaluation of flags
|
|
73
|
-
@param {number} data.environmentRefreshIntervalSeconds: If using local evaluation,
|
|
74
|
-
specify the interval period between refreshes of local environment data
|
|
75
|
-
@param {number} data.retries: a urllib3.Retry object to use on all http requests to the
|
|
76
|
-
Flagsmith API
|
|
77
|
-
@param {boolean} data.enableAnalytics: if enabled, sends additional requests to the Flagsmith
|
|
78
|
-
API to power flag analytics charts
|
|
79
|
-
@param data.defaultFlagHandler: callable which will be used in the case where
|
|
80
|
-
flags cannot be retrieved from the API or a non-existent feature is
|
|
81
|
-
requested
|
|
82
|
-
@param data.logger: an instance of the pino Logger class to use for logging
|
|
83
|
-
@param {boolean} data.offlineMode: sets the client into offline mode. Relies on offlineHandler for
|
|
84
|
-
evaluating flags.
|
|
85
|
-
@param {BaseOfflineHandler} data.offlineHandler: provide a handler for offline logic. Used to get environment
|
|
86
|
-
document from another source when in offlineMode. Works in place of
|
|
87
|
-
defaultFlagHandler if offlineMode is not set and using remote evaluation.
|
|
88
|
-
*/
|
|
89
|
-
constructor(data = {}) {
|
|
90
|
-
// if (!data.offlineMode && !data.environmentKey) {
|
|
91
|
-
// throw new Error('ValueError: environmentKey is required.');
|
|
92
|
-
// }
|
|
80
|
+
* If using local evaluation, the environment will be fetched lazily when needed by any method. Polling the
|
|
81
|
+
* environment for updates will start after {@link environmentRefreshIntervalSeconds} once the client is created.
|
|
82
|
+
* @param data The {@link FlagsmithConfig} options for this client.
|
|
83
|
+
*/
|
|
84
|
+
constructor(data) {
|
|
93
85
|
this.agent = data.agent;
|
|
94
86
|
this.customFetch = data.fetch ?? fetch;
|
|
95
87
|
this.environmentKey = data.environmentKey;
|
|
96
|
-
this.apiUrl = data.apiUrl ||
|
|
88
|
+
this.apiUrl = data.apiUrl || DEFAULT_API_URL;
|
|
97
89
|
this.customHeaders = data.customHeaders;
|
|
98
90
|
this.requestTimeoutMs =
|
|
99
91
|
1000 * (data.requestTimeoutSeconds ?? DEFAULT_REQUEST_TIMEOUT_SECONDS);
|
|
92
|
+
this.requestRetryDelayMilliseconds = data.requestRetryDelayMilliseconds ?? 1000;
|
|
100
93
|
this.enableLocalEvaluation = data.enableLocalEvaluation;
|
|
101
94
|
this.environmentRefreshIntervalSeconds =
|
|
102
95
|
data.environmentRefreshIntervalSeconds || this.environmentRefreshIntervalSeconds;
|
|
103
96
|
this.retries = data.retries;
|
|
104
97
|
this.enableAnalytics = data.enableAnalytics || false;
|
|
105
98
|
this.defaultFlagHandler = data.defaultFlagHandler;
|
|
106
|
-
this.onEnvironmentChange = data.onEnvironmentChange;
|
|
99
|
+
this.onEnvironmentChange = (error, result) => data.onEnvironmentChange?.(error, result);
|
|
107
100
|
this.logger = data.logger || (0, pino_1.pino)();
|
|
108
101
|
this.offlineMode = data.offlineMode || false;
|
|
109
102
|
this.offlineHandler = data.offlineHandler;
|
|
@@ -114,9 +107,6 @@ class Flagsmith {
|
|
|
114
107
|
else if (this.defaultFlagHandler && this.offlineHandler) {
|
|
115
108
|
throw new Error('ValueError: Cannot use both defaultFlagHandler and offlineHandler.');
|
|
116
109
|
}
|
|
117
|
-
if (this.offlineHandler) {
|
|
118
|
-
this.environment = this.offlineHandler.getEnvironment();
|
|
119
|
-
}
|
|
120
110
|
if (!!data.cache) {
|
|
121
111
|
this.cache = data.cache;
|
|
122
112
|
}
|
|
@@ -132,11 +122,12 @@ class Flagsmith {
|
|
|
132
122
|
this.environmentUrl = `${this.apiUrl}environment-document/`;
|
|
133
123
|
if (this.enableLocalEvaluation) {
|
|
134
124
|
if (!this.environmentKey.startsWith('ser.')) {
|
|
135
|
-
|
|
125
|
+
throw new Error('Using local evaluation requires a server-side environment key');
|
|
126
|
+
}
|
|
127
|
+
if (this.environmentRefreshIntervalSeconds > 0) {
|
|
128
|
+
this.environmentDataPollingManager = new polling_manager_js_1.EnvironmentDataPollingManager(this, this.environmentRefreshIntervalSeconds, this.logger);
|
|
129
|
+
this.environmentDataPollingManager.start();
|
|
136
130
|
}
|
|
137
|
-
this.environmentDataPollingManager = new polling_manager_js_1.EnvironmentDataPollingManager(this, this.environmentRefreshIntervalSeconds);
|
|
138
|
-
this.environmentDataPollingManager.start();
|
|
139
|
-
this.updateEnvironment();
|
|
140
131
|
}
|
|
141
132
|
if (data.enableAnalytics) {
|
|
142
133
|
this.analyticsProcessor = new analytics_js_1.AnalyticsProcessor({
|
|
@@ -158,15 +149,22 @@ class Flagsmith {
|
|
|
158
149
|
if (!!cachedItem) {
|
|
159
150
|
return cachedItem;
|
|
160
151
|
}
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
}
|
|
152
|
+
try {
|
|
153
|
+
if (this.enableLocalEvaluation || this.offlineMode) {
|
|
154
|
+
return await this.getEnvironmentFlagsFromDocument();
|
|
155
|
+
}
|
|
156
|
+
return await this.getEnvironmentFlagsFromApi();
|
|
165
157
|
}
|
|
166
|
-
|
|
167
|
-
|
|
158
|
+
catch (error) {
|
|
159
|
+
if (!this.defaultFlagHandler) {
|
|
160
|
+
throw new Error('getEnvironmentFlags failed and no default flag handler was provided', { cause: error });
|
|
161
|
+
}
|
|
162
|
+
this.logger.error(error, 'getEnvironmentFlags failed');
|
|
163
|
+
return new models_js_1.Flags({
|
|
164
|
+
flags: {},
|
|
165
|
+
defaultFlagHandler: this.defaultFlagHandler
|
|
166
|
+
});
|
|
168
167
|
}
|
|
169
|
-
return this.getEnvironmentFlagsFromApi();
|
|
170
168
|
}
|
|
171
169
|
/**
|
|
172
170
|
* Get all the flags for the current environment for a given identity. Will also
|
|
@@ -188,15 +186,22 @@ class Flagsmith {
|
|
|
188
186
|
return cachedItem;
|
|
189
187
|
}
|
|
190
188
|
traits = traits || {};
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
}
|
|
189
|
+
try {
|
|
190
|
+
if (this.enableLocalEvaluation || this.offlineMode) {
|
|
191
|
+
return await this.getIdentityFlagsFromDocument(identifier, traits || {});
|
|
192
|
+
}
|
|
193
|
+
return await this.getIdentityFlagsFromApi(identifier, traits, transient);
|
|
195
194
|
}
|
|
196
|
-
|
|
197
|
-
|
|
195
|
+
catch (error) {
|
|
196
|
+
if (!this.defaultFlagHandler) {
|
|
197
|
+
throw new Error('getIdentityFlags failed and no default flag handler was provided', { cause: error });
|
|
198
|
+
}
|
|
199
|
+
this.logger.error(error, 'getIdentityFlags failed');
|
|
200
|
+
return new models_js_1.Flags({
|
|
201
|
+
flags: {},
|
|
202
|
+
defaultFlagHandler: this.defaultFlagHandler
|
|
203
|
+
});
|
|
198
204
|
}
|
|
199
|
-
return this.getIdentityFlagsFromApi(identifier, traits, transient);
|
|
200
205
|
}
|
|
201
206
|
/**
|
|
202
207
|
* Get the segments for the current environment for a given identity. Will also
|
|
@@ -209,57 +214,59 @@ class Flagsmith {
|
|
|
209
214
|
Flagsmith, e.g. {"num_orders": 10}
|
|
210
215
|
* @returns Segments that the given identity belongs to.
|
|
211
216
|
*/
|
|
212
|
-
getIdentitySegments(identifier, traits) {
|
|
217
|
+
async getIdentitySegments(identifier, traits) {
|
|
213
218
|
if (!identifier) {
|
|
214
219
|
throw new Error('`identifier` argument is missing or invalid.');
|
|
215
220
|
}
|
|
221
|
+
if (!this.enableLocalEvaluation) {
|
|
222
|
+
this.logger.error('This function is only permitted with local evaluation.');
|
|
223
|
+
return Promise.resolve([]);
|
|
224
|
+
}
|
|
216
225
|
traits = traits || {};
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
226
|
+
const environment = await this.getEnvironment();
|
|
227
|
+
const identityModel = this.getIdentityModel(environment, identifier, Object.keys(traits || {}).map(key => ({
|
|
228
|
+
key,
|
|
229
|
+
value: traits?.[key]
|
|
230
|
+
})));
|
|
231
|
+
return (0, evaluators_js_1.getIdentitySegments)(environment, identityModel);
|
|
232
|
+
}
|
|
233
|
+
async fetchEnvironment() {
|
|
234
|
+
const deferred = new utils_js_1.Deferred();
|
|
235
|
+
this.environmentPromise = deferred.promise;
|
|
236
|
+
try {
|
|
237
|
+
const environment = await this.getEnvironmentFromApi();
|
|
238
|
+
this.environment = environment;
|
|
239
|
+
if (environment.identityOverrides?.length) {
|
|
240
|
+
this.identitiesWithOverridesByIdentifier = new Map(environment.identityOverrides.map(identity => [identity.identifier, identity]));
|
|
241
|
+
}
|
|
242
|
+
deferred.resolve(environment);
|
|
243
|
+
return deferred.promise;
|
|
244
|
+
}
|
|
245
|
+
catch (error) {
|
|
246
|
+
deferred.reject(error);
|
|
247
|
+
return deferred.promise;
|
|
248
|
+
}
|
|
249
|
+
finally {
|
|
250
|
+
this.environmentPromise = undefined;
|
|
228
251
|
}
|
|
229
|
-
console.error('This function is only permitted with local evaluation.');
|
|
230
|
-
return Promise.resolve([]);
|
|
231
252
|
}
|
|
232
253
|
/**
|
|
233
|
-
*
|
|
234
|
-
*
|
|
235
|
-
*
|
|
254
|
+
* Fetch the latest environment state from the Flagsmith API to use for local flag evaluation.
|
|
255
|
+
*
|
|
256
|
+
* If the environment is currently being fetched, calling this method will not cause additional fetches.
|
|
236
257
|
*/
|
|
237
258
|
async updateEnvironment() {
|
|
238
259
|
try {
|
|
239
|
-
|
|
240
|
-
if (!this.environmentPromise) {
|
|
241
|
-
this.environmentPromise = request.then(res => {
|
|
242
|
-
this.environment = res;
|
|
243
|
-
});
|
|
260
|
+
if (this.environmentPromise) {
|
|
244
261
|
await this.environmentPromise;
|
|
262
|
+
return;
|
|
245
263
|
}
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
}
|
|
249
|
-
if (this.environment.identityOverrides?.length) {
|
|
250
|
-
this.identitiesWithOverridesByIdentifier = new Map(this.environment.identityOverrides.map(identity => [
|
|
251
|
-
identity.identifier,
|
|
252
|
-
identity
|
|
253
|
-
]));
|
|
254
|
-
}
|
|
255
|
-
if (this.onEnvironmentChange) {
|
|
256
|
-
this.onEnvironmentChange(null, this.environment);
|
|
257
|
-
}
|
|
264
|
+
const environment = await this.fetchEnvironment();
|
|
265
|
+
this.onEnvironmentChange(null, environment);
|
|
258
266
|
}
|
|
259
267
|
catch (e) {
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
}
|
|
268
|
+
this.logger.error(e, 'updateEnvironment failed');
|
|
269
|
+
this.onEnvironmentChange(e);
|
|
263
270
|
}
|
|
264
271
|
}
|
|
265
272
|
async close() {
|
|
@@ -280,7 +287,7 @@ class Flagsmith {
|
|
|
280
287
|
method: method,
|
|
281
288
|
body: JSON.stringify(body),
|
|
282
289
|
headers: headers
|
|
283
|
-
}, this.retries, this.requestTimeoutMs, this.customFetch);
|
|
290
|
+
}, this.retries, this.requestTimeoutMs, this.requestRetryDelayMilliseconds, this.customFetch);
|
|
284
291
|
if (data.status !== 200) {
|
|
285
292
|
throw new errors_js_1.FlagsmithAPIError(`Invalid request made to Flagsmith API. Response status code: ${data.status}`);
|
|
286
293
|
}
|
|
@@ -290,6 +297,23 @@ class Flagsmith {
|
|
|
290
297
|
* This promise ensures that the environment is retrieved before attempting to locally evaluate.
|
|
291
298
|
*/
|
|
292
299
|
environmentPromise;
|
|
300
|
+
/**
|
|
301
|
+
* Returns the current environment, fetching it from the API if needed.
|
|
302
|
+
*
|
|
303
|
+
* Calling this method concurrently while the environment is being fetched will not cause additional requests.
|
|
304
|
+
*/
|
|
305
|
+
async getEnvironment() {
|
|
306
|
+
if (this.offlineHandler) {
|
|
307
|
+
return this.offlineHandler.getEnvironment();
|
|
308
|
+
}
|
|
309
|
+
if (this.environment) {
|
|
310
|
+
return this.environment;
|
|
311
|
+
}
|
|
312
|
+
if (!this.environmentPromise) {
|
|
313
|
+
this.environmentPromise = this.fetchEnvironment();
|
|
314
|
+
}
|
|
315
|
+
return this.environmentPromise;
|
|
316
|
+
}
|
|
293
317
|
async getEnvironmentFromApi() {
|
|
294
318
|
if (!this.environmentUrl) {
|
|
295
319
|
throw new Error('`apiUrl` argument is missing or invalid.');
|
|
@@ -298,8 +322,9 @@ class Flagsmith {
|
|
|
298
322
|
return (0, util_js_1.buildEnvironmentModel)(environment_data);
|
|
299
323
|
}
|
|
300
324
|
async getEnvironmentFlagsFromDocument() {
|
|
325
|
+
const environment = await this.getEnvironment();
|
|
301
326
|
const flags = models_js_1.Flags.fromFeatureStateModels({
|
|
302
|
-
featureStates: (0, index_js_1.getEnvironmentFeatureStates)(
|
|
327
|
+
featureStates: (0, index_js_1.getEnvironmentFeatureStates)(environment),
|
|
303
328
|
analyticsProcessor: this.analyticsProcessor,
|
|
304
329
|
defaultFlagHandler: this.defaultFlagHandler
|
|
305
330
|
});
|
|
@@ -309,11 +334,12 @@ class Flagsmith {
|
|
|
309
334
|
return flags;
|
|
310
335
|
}
|
|
311
336
|
async getIdentityFlagsFromDocument(identifier, traits) {
|
|
312
|
-
const
|
|
337
|
+
const environment = await this.getEnvironment();
|
|
338
|
+
const identityModel = this.getIdentityModel(environment, identifier, Object.keys(traits).map(key => ({
|
|
313
339
|
key,
|
|
314
340
|
value: traits[key]
|
|
315
341
|
})));
|
|
316
|
-
const featureStates = (0, index_js_1.getIdentityFeatureStates)(
|
|
342
|
+
const featureStates = (0, index_js_1.getIdentityFeatureStates)(environment, identityModel);
|
|
317
343
|
const flags = models_js_1.Flags.fromFeatureStateModels({
|
|
318
344
|
featureStates: featureStates,
|
|
319
345
|
analyticsProcessor: this.analyticsProcessor,
|
|
@@ -329,69 +355,41 @@ class Flagsmith {
|
|
|
329
355
|
if (!this.environmentFlagsUrl) {
|
|
330
356
|
throw new Error('`apiUrl` argument is missing or invalid.');
|
|
331
357
|
}
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
await this.cache.set('flags', flags);
|
|
341
|
-
}
|
|
342
|
-
return flags;
|
|
343
|
-
}
|
|
344
|
-
catch (e) {
|
|
345
|
-
if (this.offlineHandler) {
|
|
346
|
-
return this.getEnvironmentFlagsFromDocument();
|
|
347
|
-
}
|
|
348
|
-
if (this.defaultFlagHandler) {
|
|
349
|
-
return new models_js_1.Flags({
|
|
350
|
-
flags: {},
|
|
351
|
-
defaultFlagHandler: this.defaultFlagHandler
|
|
352
|
-
});
|
|
353
|
-
}
|
|
354
|
-
throw e;
|
|
358
|
+
const apiFlags = await this.getJSONResponse(this.environmentFlagsUrl, 'GET');
|
|
359
|
+
const flags = models_js_1.Flags.fromAPIFlags({
|
|
360
|
+
apiFlags: apiFlags,
|
|
361
|
+
analyticsProcessor: this.analyticsProcessor,
|
|
362
|
+
defaultFlagHandler: this.defaultFlagHandler
|
|
363
|
+
});
|
|
364
|
+
if (!!this.cache) {
|
|
365
|
+
await this.cache.set('flags', flags);
|
|
355
366
|
}
|
|
367
|
+
return flags;
|
|
356
368
|
}
|
|
357
369
|
async getIdentityFlagsFromApi(identifier, traits, transient = false) {
|
|
358
370
|
if (!this.identitiesUrl) {
|
|
359
371
|
throw new Error('`apiUrl` argument is missing or invalid.');
|
|
360
372
|
}
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
await this.cache.set(`flags-${identifier}`, flags);
|
|
371
|
-
}
|
|
372
|
-
return flags;
|
|
373
|
-
}
|
|
374
|
-
catch (e) {
|
|
375
|
-
if (this.offlineHandler) {
|
|
376
|
-
return this.getIdentityFlagsFromDocument(identifier, traits);
|
|
377
|
-
}
|
|
378
|
-
if (this.defaultFlagHandler) {
|
|
379
|
-
return new models_js_1.Flags({
|
|
380
|
-
flags: {},
|
|
381
|
-
defaultFlagHandler: this.defaultFlagHandler
|
|
382
|
-
});
|
|
383
|
-
}
|
|
384
|
-
throw e;
|
|
373
|
+
const data = (0, utils_js_1.generateIdentitiesData)(identifier, traits, transient);
|
|
374
|
+
const jsonResponse = await this.getJSONResponse(this.identitiesUrl, 'POST', data);
|
|
375
|
+
const flags = models_js_1.Flags.fromAPIFlags({
|
|
376
|
+
apiFlags: jsonResponse['flags'],
|
|
377
|
+
analyticsProcessor: this.analyticsProcessor,
|
|
378
|
+
defaultFlagHandler: this.defaultFlagHandler
|
|
379
|
+
});
|
|
380
|
+
if (!!this.cache) {
|
|
381
|
+
await this.cache.set(`flags-${identifier}`, flags);
|
|
385
382
|
}
|
|
383
|
+
return flags;
|
|
386
384
|
}
|
|
387
|
-
getIdentityModel(identifier, traits) {
|
|
385
|
+
getIdentityModel(environment, identifier, traits) {
|
|
388
386
|
const traitModels = traits.map(trait => new index_js_3.TraitModel(trait.key, trait.value));
|
|
389
387
|
let identityWithOverrides = this.identitiesWithOverridesByIdentifier?.get(identifier);
|
|
390
388
|
if (identityWithOverrides) {
|
|
391
389
|
identityWithOverrides.updateTraits(traitModels);
|
|
392
390
|
return identityWithOverrides;
|
|
393
391
|
}
|
|
394
|
-
return new index_js_2.IdentityModel('0', traitModels, [],
|
|
392
|
+
return new index_js_2.IdentityModel('0', traitModels, [], environment.apiKey, identifier);
|
|
395
393
|
}
|
|
396
394
|
}
|
|
397
395
|
exports.Flagsmith = Flagsmith;
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import Flagsmith from './index.js';
|
|
2
|
+
import { Logger } from 'pino';
|
|
2
3
|
export declare class EnvironmentDataPollingManager {
|
|
3
4
|
private interval?;
|
|
4
5
|
private main;
|
|
5
6
|
private refreshIntervalSeconds;
|
|
6
|
-
|
|
7
|
+
private logger;
|
|
8
|
+
constructor(main: Flagsmith, refreshIntervalSeconds: number, logger: Logger);
|
|
7
9
|
start(): void;
|
|
8
10
|
stop(): void;
|
|
9
11
|
}
|
|
@@ -5,16 +5,23 @@ class EnvironmentDataPollingManager {
|
|
|
5
5
|
interval;
|
|
6
6
|
main;
|
|
7
7
|
refreshIntervalSeconds;
|
|
8
|
-
|
|
8
|
+
logger;
|
|
9
|
+
constructor(main, refreshIntervalSeconds, logger) {
|
|
9
10
|
this.main = main;
|
|
10
11
|
this.refreshIntervalSeconds = refreshIntervalSeconds;
|
|
12
|
+
this.logger = logger;
|
|
11
13
|
}
|
|
12
14
|
start() {
|
|
13
15
|
const updateEnvironment = () => {
|
|
14
16
|
if (this.interval)
|
|
15
17
|
clearInterval(this.interval);
|
|
16
18
|
this.interval = setInterval(async () => {
|
|
17
|
-
|
|
19
|
+
try {
|
|
20
|
+
await this.main.updateEnvironment();
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
this.logger.error(error, 'failed to poll environment');
|
|
24
|
+
}
|
|
18
25
|
}, this.refreshIntervalSeconds * 1000);
|
|
19
26
|
};
|
|
20
27
|
updateEnvironment();
|