flagsmith-nodejs 5.1.0 → 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
package/build/esm/sdk/index.js
CHANGED
|
@@ -6,7 +6,7 @@ import { ANALYTICS_ENDPOINT, AnalyticsProcessor } from './analytics.js';
|
|
|
6
6
|
import { FlagsmithAPIError } from './errors.js';
|
|
7
7
|
import { Flags } from './models.js';
|
|
8
8
|
import { EnvironmentDataPollingManager } from './polling_manager.js';
|
|
9
|
-
import { generateIdentitiesData, retryFetch } from './utils.js';
|
|
9
|
+
import { Deferred, generateIdentitiesData, retryFetch } from './utils.js';
|
|
10
10
|
import { getIdentitySegments } from '../flagsmith-engine/segments/evaluators.js';
|
|
11
11
|
import { pino } from 'pino';
|
|
12
12
|
export { AnalyticsProcessor } from './analytics.js';
|
|
@@ -15,6 +15,30 @@ export { DefaultFlag, Flags } from './models.js';
|
|
|
15
15
|
export { EnvironmentDataPollingManager } from './polling_manager.js';
|
|
16
16
|
const DEFAULT_API_URL = 'https://edge.api.flagsmith.com/api/v1/';
|
|
17
17
|
const DEFAULT_REQUEST_TIMEOUT_SECONDS = 10;
|
|
18
|
+
/**
|
|
19
|
+
* A client for evaluating Flagsmith feature flags.
|
|
20
|
+
*
|
|
21
|
+
* Flags are evaluated remotely by the Flagsmith API over HTTP by default.
|
|
22
|
+
* To evaluate flags locally, create the client using {@link FlagsmithConfig.enableLocalEvaluation} and a server-side SDK key.
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* import { Flagsmith, Flags, DefaultFlag } from 'flagsmith-nodejs'
|
|
26
|
+
*
|
|
27
|
+
* const flagsmith = new Flagsmith({
|
|
28
|
+
* environmentKey: 'your_sdk_key',
|
|
29
|
+
* defaultFlagHandler: (flagKey: string) => { new DefaultFlag(...) },
|
|
30
|
+
* });
|
|
31
|
+
*
|
|
32
|
+
* // Fetch the current environment flags
|
|
33
|
+
* const environmentFlags: Flags = flagsmith.getEnvironmentFlags()
|
|
34
|
+
* const isFooEnabled: boolean = environmentFlags.isFeatureEnabled('foo')
|
|
35
|
+
*
|
|
36
|
+
* // Evaluate flags for any identity
|
|
37
|
+
* const identityFlags: Flags = flagsmith.getIdentityFlags('my_user_123', {'vip': true})
|
|
38
|
+
* const bannerVariation: string = identityFlags.getFeatureValue('banner_flag')
|
|
39
|
+
*
|
|
40
|
+
* @see FlagsmithConfig
|
|
41
|
+
*/
|
|
18
42
|
export class Flagsmith {
|
|
19
43
|
environmentKey = undefined;
|
|
20
44
|
apiUrl = undefined;
|
|
@@ -40,61 +64,30 @@ export class Flagsmith {
|
|
|
40
64
|
analyticsProcessor;
|
|
41
65
|
logger;
|
|
42
66
|
customFetch;
|
|
67
|
+
requestRetryDelayMilliseconds;
|
|
43
68
|
/**
|
|
44
|
-
*
|
|
69
|
+
* Creates a new {@link Flagsmith} client.
|
|
45
70
|
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
* const environmentFlags = flagsmith.getEnvironmentFlags();
|
|
52
|
-
* const featureEnabled = environmentFlags.isFeatureEnabled('foo');
|
|
53
|
-
* const identityFlags = flagsmith.getIdentityFlags('identifier', {'foo': 'bar'});
|
|
54
|
-
* const featureEnabledForIdentity = identityFlags.isFeatureEnabled("foo")
|
|
55
|
-
*
|
|
56
|
-
* @param {string} data.environmentKey: The environment key obtained from Flagsmith interface
|
|
57
|
-
* Required unless offlineMode is True.
|
|
58
|
-
@param {string} data.apiUrl: Override the URL of the Flagsmith API to communicate with
|
|
59
|
-
@param data.customHeaders: Additional headers to add to requests made to the
|
|
60
|
-
Flagsmith API
|
|
61
|
-
@param {number} data.requestTimeoutSeconds: Number of seconds to wait for a request to
|
|
62
|
-
complete before terminating the request
|
|
63
|
-
@param {boolean} data.enableLocalEvaluation: Enables local evaluation of flags
|
|
64
|
-
@param {number} data.environmentRefreshIntervalSeconds: If using local evaluation,
|
|
65
|
-
specify the interval period between refreshes of local environment data
|
|
66
|
-
@param {number} data.retries: a urllib3.Retry object to use on all http requests to the
|
|
67
|
-
Flagsmith API
|
|
68
|
-
@param {boolean} data.enableAnalytics: if enabled, sends additional requests to the Flagsmith
|
|
69
|
-
API to power flag analytics charts
|
|
70
|
-
@param data.defaultFlagHandler: callable which will be used in the case where
|
|
71
|
-
flags cannot be retrieved from the API or a non-existent feature is
|
|
72
|
-
requested
|
|
73
|
-
@param data.logger: an instance of the pino Logger class to use for logging
|
|
74
|
-
@param {boolean} data.offlineMode: sets the client into offline mode. Relies on offlineHandler for
|
|
75
|
-
evaluating flags.
|
|
76
|
-
@param {BaseOfflineHandler} data.offlineHandler: provide a handler for offline logic. Used to get environment
|
|
77
|
-
document from another source when in offlineMode. Works in place of
|
|
78
|
-
defaultFlagHandler if offlineMode is not set and using remote evaluation.
|
|
79
|
-
*/
|
|
80
|
-
constructor(data = {}) {
|
|
81
|
-
// if (!data.offlineMode && !data.environmentKey) {
|
|
82
|
-
// throw new Error('ValueError: environmentKey is required.');
|
|
83
|
-
// }
|
|
71
|
+
* If using local evaluation, the environment will be fetched lazily when needed by any method. Polling the
|
|
72
|
+
* environment for updates will start after {@link environmentRefreshIntervalSeconds} once the client is created.
|
|
73
|
+
* @param data The {@link FlagsmithConfig} options for this client.
|
|
74
|
+
*/
|
|
75
|
+
constructor(data) {
|
|
84
76
|
this.agent = data.agent;
|
|
85
77
|
this.customFetch = data.fetch ?? fetch;
|
|
86
78
|
this.environmentKey = data.environmentKey;
|
|
87
|
-
this.apiUrl = data.apiUrl ||
|
|
79
|
+
this.apiUrl = data.apiUrl || DEFAULT_API_URL;
|
|
88
80
|
this.customHeaders = data.customHeaders;
|
|
89
81
|
this.requestTimeoutMs =
|
|
90
82
|
1000 * (data.requestTimeoutSeconds ?? DEFAULT_REQUEST_TIMEOUT_SECONDS);
|
|
83
|
+
this.requestRetryDelayMilliseconds = data.requestRetryDelayMilliseconds ?? 1000;
|
|
91
84
|
this.enableLocalEvaluation = data.enableLocalEvaluation;
|
|
92
85
|
this.environmentRefreshIntervalSeconds =
|
|
93
86
|
data.environmentRefreshIntervalSeconds || this.environmentRefreshIntervalSeconds;
|
|
94
87
|
this.retries = data.retries;
|
|
95
88
|
this.enableAnalytics = data.enableAnalytics || false;
|
|
96
89
|
this.defaultFlagHandler = data.defaultFlagHandler;
|
|
97
|
-
this.onEnvironmentChange = data.onEnvironmentChange;
|
|
90
|
+
this.onEnvironmentChange = (error, result) => data.onEnvironmentChange?.(error, result);
|
|
98
91
|
this.logger = data.logger || pino();
|
|
99
92
|
this.offlineMode = data.offlineMode || false;
|
|
100
93
|
this.offlineHandler = data.offlineHandler;
|
|
@@ -105,9 +98,6 @@ export class Flagsmith {
|
|
|
105
98
|
else if (this.defaultFlagHandler && this.offlineHandler) {
|
|
106
99
|
throw new Error('ValueError: Cannot use both defaultFlagHandler and offlineHandler.');
|
|
107
100
|
}
|
|
108
|
-
if (this.offlineHandler) {
|
|
109
|
-
this.environment = this.offlineHandler.getEnvironment();
|
|
110
|
-
}
|
|
111
101
|
if (!!data.cache) {
|
|
112
102
|
this.cache = data.cache;
|
|
113
103
|
}
|
|
@@ -123,11 +113,12 @@ export class Flagsmith {
|
|
|
123
113
|
this.environmentUrl = `${this.apiUrl}environment-document/`;
|
|
124
114
|
if (this.enableLocalEvaluation) {
|
|
125
115
|
if (!this.environmentKey.startsWith('ser.')) {
|
|
126
|
-
|
|
116
|
+
throw new Error('Using local evaluation requires a server-side environment key');
|
|
117
|
+
}
|
|
118
|
+
if (this.environmentRefreshIntervalSeconds > 0) {
|
|
119
|
+
this.environmentDataPollingManager = new EnvironmentDataPollingManager(this, this.environmentRefreshIntervalSeconds, this.logger);
|
|
120
|
+
this.environmentDataPollingManager.start();
|
|
127
121
|
}
|
|
128
|
-
this.environmentDataPollingManager = new EnvironmentDataPollingManager(this, this.environmentRefreshIntervalSeconds);
|
|
129
|
-
this.environmentDataPollingManager.start();
|
|
130
|
-
this.updateEnvironment();
|
|
131
122
|
}
|
|
132
123
|
if (data.enableAnalytics) {
|
|
133
124
|
this.analyticsProcessor = new AnalyticsProcessor({
|
|
@@ -149,15 +140,22 @@ export class Flagsmith {
|
|
|
149
140
|
if (!!cachedItem) {
|
|
150
141
|
return cachedItem;
|
|
151
142
|
}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
}
|
|
143
|
+
try {
|
|
144
|
+
if (this.enableLocalEvaluation || this.offlineMode) {
|
|
145
|
+
return await this.getEnvironmentFlagsFromDocument();
|
|
146
|
+
}
|
|
147
|
+
return await this.getEnvironmentFlagsFromApi();
|
|
156
148
|
}
|
|
157
|
-
|
|
158
|
-
|
|
149
|
+
catch (error) {
|
|
150
|
+
if (!this.defaultFlagHandler) {
|
|
151
|
+
throw new Error('getEnvironmentFlags failed and no default flag handler was provided', { cause: error });
|
|
152
|
+
}
|
|
153
|
+
this.logger.error(error, 'getEnvironmentFlags failed');
|
|
154
|
+
return new Flags({
|
|
155
|
+
flags: {},
|
|
156
|
+
defaultFlagHandler: this.defaultFlagHandler
|
|
157
|
+
});
|
|
159
158
|
}
|
|
160
|
-
return this.getEnvironmentFlagsFromApi();
|
|
161
159
|
}
|
|
162
160
|
/**
|
|
163
161
|
* Get all the flags for the current environment for a given identity. Will also
|
|
@@ -179,15 +177,22 @@ export class Flagsmith {
|
|
|
179
177
|
return cachedItem;
|
|
180
178
|
}
|
|
181
179
|
traits = traits || {};
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
}
|
|
180
|
+
try {
|
|
181
|
+
if (this.enableLocalEvaluation || this.offlineMode) {
|
|
182
|
+
return await this.getIdentityFlagsFromDocument(identifier, traits || {});
|
|
183
|
+
}
|
|
184
|
+
return await this.getIdentityFlagsFromApi(identifier, traits, transient);
|
|
186
185
|
}
|
|
187
|
-
|
|
188
|
-
|
|
186
|
+
catch (error) {
|
|
187
|
+
if (!this.defaultFlagHandler) {
|
|
188
|
+
throw new Error('getIdentityFlags failed and no default flag handler was provided', { cause: error });
|
|
189
|
+
}
|
|
190
|
+
this.logger.error(error, 'getIdentityFlags failed');
|
|
191
|
+
return new Flags({
|
|
192
|
+
flags: {},
|
|
193
|
+
defaultFlagHandler: this.defaultFlagHandler
|
|
194
|
+
});
|
|
189
195
|
}
|
|
190
|
-
return this.getIdentityFlagsFromApi(identifier, traits, transient);
|
|
191
196
|
}
|
|
192
197
|
/**
|
|
193
198
|
* Get the segments for the current environment for a given identity. Will also
|
|
@@ -200,57 +205,59 @@ export class Flagsmith {
|
|
|
200
205
|
Flagsmith, e.g. {"num_orders": 10}
|
|
201
206
|
* @returns Segments that the given identity belongs to.
|
|
202
207
|
*/
|
|
203
|
-
getIdentitySegments(identifier, traits) {
|
|
208
|
+
async getIdentitySegments(identifier, traits) {
|
|
204
209
|
if (!identifier) {
|
|
205
210
|
throw new Error('`identifier` argument is missing or invalid.');
|
|
206
211
|
}
|
|
212
|
+
if (!this.enableLocalEvaluation) {
|
|
213
|
+
this.logger.error('This function is only permitted with local evaluation.');
|
|
214
|
+
return Promise.resolve([]);
|
|
215
|
+
}
|
|
207
216
|
traits = traits || {};
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
217
|
+
const environment = await this.getEnvironment();
|
|
218
|
+
const identityModel = this.getIdentityModel(environment, identifier, Object.keys(traits || {}).map(key => ({
|
|
219
|
+
key,
|
|
220
|
+
value: traits?.[key]
|
|
221
|
+
})));
|
|
222
|
+
return getIdentitySegments(environment, identityModel);
|
|
223
|
+
}
|
|
224
|
+
async fetchEnvironment() {
|
|
225
|
+
const deferred = new Deferred();
|
|
226
|
+
this.environmentPromise = deferred.promise;
|
|
227
|
+
try {
|
|
228
|
+
const environment = await this.getEnvironmentFromApi();
|
|
229
|
+
this.environment = environment;
|
|
230
|
+
if (environment.identityOverrides?.length) {
|
|
231
|
+
this.identitiesWithOverridesByIdentifier = new Map(environment.identityOverrides.map(identity => [identity.identifier, identity]));
|
|
232
|
+
}
|
|
233
|
+
deferred.resolve(environment);
|
|
234
|
+
return deferred.promise;
|
|
235
|
+
}
|
|
236
|
+
catch (error) {
|
|
237
|
+
deferred.reject(error);
|
|
238
|
+
return deferred.promise;
|
|
239
|
+
}
|
|
240
|
+
finally {
|
|
241
|
+
this.environmentPromise = undefined;
|
|
219
242
|
}
|
|
220
|
-
console.error('This function is only permitted with local evaluation.');
|
|
221
|
-
return Promise.resolve([]);
|
|
222
243
|
}
|
|
223
244
|
/**
|
|
224
|
-
*
|
|
225
|
-
*
|
|
226
|
-
*
|
|
245
|
+
* Fetch the latest environment state from the Flagsmith API to use for local flag evaluation.
|
|
246
|
+
*
|
|
247
|
+
* If the environment is currently being fetched, calling this method will not cause additional fetches.
|
|
227
248
|
*/
|
|
228
249
|
async updateEnvironment() {
|
|
229
250
|
try {
|
|
230
|
-
|
|
231
|
-
if (!this.environmentPromise) {
|
|
232
|
-
this.environmentPromise = request.then(res => {
|
|
233
|
-
this.environment = res;
|
|
234
|
-
});
|
|
251
|
+
if (this.environmentPromise) {
|
|
235
252
|
await this.environmentPromise;
|
|
253
|
+
return;
|
|
236
254
|
}
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
}
|
|
240
|
-
if (this.environment.identityOverrides?.length) {
|
|
241
|
-
this.identitiesWithOverridesByIdentifier = new Map(this.environment.identityOverrides.map(identity => [
|
|
242
|
-
identity.identifier,
|
|
243
|
-
identity
|
|
244
|
-
]));
|
|
245
|
-
}
|
|
246
|
-
if (this.onEnvironmentChange) {
|
|
247
|
-
this.onEnvironmentChange(null, this.environment);
|
|
248
|
-
}
|
|
255
|
+
const environment = await this.fetchEnvironment();
|
|
256
|
+
this.onEnvironmentChange(null, environment);
|
|
249
257
|
}
|
|
250
258
|
catch (e) {
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
}
|
|
259
|
+
this.logger.error(e, 'updateEnvironment failed');
|
|
260
|
+
this.onEnvironmentChange(e);
|
|
254
261
|
}
|
|
255
262
|
}
|
|
256
263
|
async close() {
|
|
@@ -271,7 +278,7 @@ export class Flagsmith {
|
|
|
271
278
|
method: method,
|
|
272
279
|
body: JSON.stringify(body),
|
|
273
280
|
headers: headers
|
|
274
|
-
}, this.retries, this.requestTimeoutMs, this.customFetch);
|
|
281
|
+
}, this.retries, this.requestTimeoutMs, this.requestRetryDelayMilliseconds, this.customFetch);
|
|
275
282
|
if (data.status !== 200) {
|
|
276
283
|
throw new FlagsmithAPIError(`Invalid request made to Flagsmith API. Response status code: ${data.status}`);
|
|
277
284
|
}
|
|
@@ -281,6 +288,23 @@ export class Flagsmith {
|
|
|
281
288
|
* This promise ensures that the environment is retrieved before attempting to locally evaluate.
|
|
282
289
|
*/
|
|
283
290
|
environmentPromise;
|
|
291
|
+
/**
|
|
292
|
+
* Returns the current environment, fetching it from the API if needed.
|
|
293
|
+
*
|
|
294
|
+
* Calling this method concurrently while the environment is being fetched will not cause additional requests.
|
|
295
|
+
*/
|
|
296
|
+
async getEnvironment() {
|
|
297
|
+
if (this.offlineHandler) {
|
|
298
|
+
return this.offlineHandler.getEnvironment();
|
|
299
|
+
}
|
|
300
|
+
if (this.environment) {
|
|
301
|
+
return this.environment;
|
|
302
|
+
}
|
|
303
|
+
if (!this.environmentPromise) {
|
|
304
|
+
this.environmentPromise = this.fetchEnvironment();
|
|
305
|
+
}
|
|
306
|
+
return this.environmentPromise;
|
|
307
|
+
}
|
|
284
308
|
async getEnvironmentFromApi() {
|
|
285
309
|
if (!this.environmentUrl) {
|
|
286
310
|
throw new Error('`apiUrl` argument is missing or invalid.');
|
|
@@ -289,8 +313,9 @@ export class Flagsmith {
|
|
|
289
313
|
return buildEnvironmentModel(environment_data);
|
|
290
314
|
}
|
|
291
315
|
async getEnvironmentFlagsFromDocument() {
|
|
316
|
+
const environment = await this.getEnvironment();
|
|
292
317
|
const flags = Flags.fromFeatureStateModels({
|
|
293
|
-
featureStates: getEnvironmentFeatureStates(
|
|
318
|
+
featureStates: getEnvironmentFeatureStates(environment),
|
|
294
319
|
analyticsProcessor: this.analyticsProcessor,
|
|
295
320
|
defaultFlagHandler: this.defaultFlagHandler
|
|
296
321
|
});
|
|
@@ -300,11 +325,12 @@ export class Flagsmith {
|
|
|
300
325
|
return flags;
|
|
301
326
|
}
|
|
302
327
|
async getIdentityFlagsFromDocument(identifier, traits) {
|
|
303
|
-
const
|
|
328
|
+
const environment = await this.getEnvironment();
|
|
329
|
+
const identityModel = this.getIdentityModel(environment, identifier, Object.keys(traits).map(key => ({
|
|
304
330
|
key,
|
|
305
331
|
value: traits[key]
|
|
306
332
|
})));
|
|
307
|
-
const featureStates = getIdentityFeatureStates(
|
|
333
|
+
const featureStates = getIdentityFeatureStates(environment, identityModel);
|
|
308
334
|
const flags = Flags.fromFeatureStateModels({
|
|
309
335
|
featureStates: featureStates,
|
|
310
336
|
analyticsProcessor: this.analyticsProcessor,
|
|
@@ -320,69 +346,41 @@ export class Flagsmith {
|
|
|
320
346
|
if (!this.environmentFlagsUrl) {
|
|
321
347
|
throw new Error('`apiUrl` argument is missing or invalid.');
|
|
322
348
|
}
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
await this.cache.set('flags', flags);
|
|
332
|
-
}
|
|
333
|
-
return flags;
|
|
334
|
-
}
|
|
335
|
-
catch (e) {
|
|
336
|
-
if (this.offlineHandler) {
|
|
337
|
-
return this.getEnvironmentFlagsFromDocument();
|
|
338
|
-
}
|
|
339
|
-
if (this.defaultFlagHandler) {
|
|
340
|
-
return new Flags({
|
|
341
|
-
flags: {},
|
|
342
|
-
defaultFlagHandler: this.defaultFlagHandler
|
|
343
|
-
});
|
|
344
|
-
}
|
|
345
|
-
throw e;
|
|
349
|
+
const apiFlags = await this.getJSONResponse(this.environmentFlagsUrl, 'GET');
|
|
350
|
+
const flags = Flags.fromAPIFlags({
|
|
351
|
+
apiFlags: apiFlags,
|
|
352
|
+
analyticsProcessor: this.analyticsProcessor,
|
|
353
|
+
defaultFlagHandler: this.defaultFlagHandler
|
|
354
|
+
});
|
|
355
|
+
if (!!this.cache) {
|
|
356
|
+
await this.cache.set('flags', flags);
|
|
346
357
|
}
|
|
358
|
+
return flags;
|
|
347
359
|
}
|
|
348
360
|
async getIdentityFlagsFromApi(identifier, traits, transient = false) {
|
|
349
361
|
if (!this.identitiesUrl) {
|
|
350
362
|
throw new Error('`apiUrl` argument is missing or invalid.');
|
|
351
363
|
}
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
await this.cache.set(`flags-${identifier}`, flags);
|
|
362
|
-
}
|
|
363
|
-
return flags;
|
|
364
|
-
}
|
|
365
|
-
catch (e) {
|
|
366
|
-
if (this.offlineHandler) {
|
|
367
|
-
return this.getIdentityFlagsFromDocument(identifier, traits);
|
|
368
|
-
}
|
|
369
|
-
if (this.defaultFlagHandler) {
|
|
370
|
-
return new Flags({
|
|
371
|
-
flags: {},
|
|
372
|
-
defaultFlagHandler: this.defaultFlagHandler
|
|
373
|
-
});
|
|
374
|
-
}
|
|
375
|
-
throw e;
|
|
364
|
+
const data = generateIdentitiesData(identifier, traits, transient);
|
|
365
|
+
const jsonResponse = await this.getJSONResponse(this.identitiesUrl, 'POST', data);
|
|
366
|
+
const flags = Flags.fromAPIFlags({
|
|
367
|
+
apiFlags: jsonResponse['flags'],
|
|
368
|
+
analyticsProcessor: this.analyticsProcessor,
|
|
369
|
+
defaultFlagHandler: this.defaultFlagHandler
|
|
370
|
+
});
|
|
371
|
+
if (!!this.cache) {
|
|
372
|
+
await this.cache.set(`flags-${identifier}`, flags);
|
|
376
373
|
}
|
|
374
|
+
return flags;
|
|
377
375
|
}
|
|
378
|
-
getIdentityModel(identifier, traits) {
|
|
376
|
+
getIdentityModel(environment, identifier, traits) {
|
|
379
377
|
const traitModels = traits.map(trait => new TraitModel(trait.key, trait.value));
|
|
380
378
|
let identityWithOverrides = this.identitiesWithOverridesByIdentifier?.get(identifier);
|
|
381
379
|
if (identityWithOverrides) {
|
|
382
380
|
identityWithOverrides.updateTraits(traitModels);
|
|
383
381
|
return identityWithOverrides;
|
|
384
382
|
}
|
|
385
|
-
return new IdentityModel('0', traitModels, [],
|
|
383
|
+
return new IdentityModel('0', traitModels, [], environment.apiKey, identifier);
|
|
386
384
|
}
|
|
387
385
|
}
|
|
388
386
|
export default 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
|
}
|
|
@@ -2,16 +2,23 @@ export class EnvironmentDataPollingManager {
|
|
|
2
2
|
interval;
|
|
3
3
|
main;
|
|
4
4
|
refreshIntervalSeconds;
|
|
5
|
-
|
|
5
|
+
logger;
|
|
6
|
+
constructor(main, refreshIntervalSeconds, logger) {
|
|
6
7
|
this.main = main;
|
|
7
8
|
this.refreshIntervalSeconds = refreshIntervalSeconds;
|
|
9
|
+
this.logger = logger;
|
|
8
10
|
}
|
|
9
11
|
start() {
|
|
10
12
|
const updateEnvironment = () => {
|
|
11
13
|
if (this.interval)
|
|
12
14
|
clearInterval(this.interval);
|
|
13
15
|
this.interval = setInterval(async () => {
|
|
14
|
-
|
|
16
|
+
try {
|
|
17
|
+
await this.main.updateEnvironment();
|
|
18
|
+
}
|
|
19
|
+
catch (error) {
|
|
20
|
+
this.logger.error(error, 'failed to poll environment');
|
|
21
|
+
}
|
|
15
22
|
}, this.refreshIntervalSeconds * 1000);
|
|
16
23
|
};
|
|
17
24
|
updateEnvironment();
|
package/build/esm/sdk/types.d.ts
CHANGED
|
@@ -21,24 +21,92 @@ export interface FlagsmithCache {
|
|
|
21
21
|
set(key: string, value: Flags): Promise<void>;
|
|
22
22
|
}
|
|
23
23
|
export type Fetch = typeof fetch;
|
|
24
|
+
/**
|
|
25
|
+
* The configuration options for a {@link Flagsmith} client.
|
|
26
|
+
*/
|
|
24
27
|
export interface FlagsmithConfig {
|
|
28
|
+
/**
|
|
29
|
+
* The environment's client-side or server-side key.
|
|
30
|
+
*/
|
|
25
31
|
environmentKey?: string;
|
|
32
|
+
/**
|
|
33
|
+
* The Flagsmith API URL. Set this if you are not using Flagsmith's public service, i.e. https://app.flagsmith.com.
|
|
34
|
+
*
|
|
35
|
+
* @default https://edge.api.flagsmith.com/api/v1/
|
|
36
|
+
*/
|
|
26
37
|
apiUrl?: string;
|
|
38
|
+
/**
|
|
39
|
+
* A custom {@link Dispatcher} to use when making HTTP requests.
|
|
40
|
+
*/
|
|
27
41
|
agent?: Dispatcher;
|
|
42
|
+
/**
|
|
43
|
+
* A custom {@link fetch} implementation to use when making HTTP requests.
|
|
44
|
+
*/
|
|
28
45
|
fetch?: Fetch;
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
46
|
+
/**
|
|
47
|
+
* Custom headers to use in all HTTP requests.
|
|
48
|
+
*/
|
|
49
|
+
customHeaders?: HeadersInit;
|
|
50
|
+
/**
|
|
51
|
+
* The network request timeout duration, in seconds.
|
|
52
|
+
*
|
|
53
|
+
* @default 10
|
|
54
|
+
*/
|
|
32
55
|
requestTimeoutSeconds?: number;
|
|
56
|
+
/**
|
|
57
|
+
* The amount of time, in milliseconds, to wait before retrying failed network requests.
|
|
58
|
+
*/
|
|
59
|
+
requestRetryDelayMilliseconds?: number;
|
|
60
|
+
/**
|
|
61
|
+
* If enabled, flags are evaluated locally using the environment state cached in memory.
|
|
62
|
+
*
|
|
63
|
+
* The client will lazily fetch the environment from the Flagsmith API, and poll it every {@link environmentRefreshIntervalSeconds}.
|
|
64
|
+
*/
|
|
33
65
|
enableLocalEvaluation?: boolean;
|
|
66
|
+
/**
|
|
67
|
+
* The time, in seconds, to wait before refreshing the cached environment state.
|
|
68
|
+
* @default 60
|
|
69
|
+
*/
|
|
34
70
|
environmentRefreshIntervalSeconds?: number;
|
|
71
|
+
/**
|
|
72
|
+
* How many times to retry any failed network request before giving up.
|
|
73
|
+
* @default 3
|
|
74
|
+
*/
|
|
35
75
|
retries?: number;
|
|
76
|
+
/**
|
|
77
|
+
* If enabled, the client will keep track of any flags evaluated using {@link Flags.isFeatureEnabled},
|
|
78
|
+
* {@link Flags.getFeatureValue} or {@link Flags.getFlag}, and periodically flush this data to the Flagsmith API.
|
|
79
|
+
*/
|
|
36
80
|
enableAnalytics?: boolean;
|
|
37
|
-
|
|
81
|
+
/**
|
|
82
|
+
* Used to return fallback values for flags when evaluation fails for any reason. If not provided and flag
|
|
83
|
+
* evaluation fails, an error will be thrown intsead.
|
|
84
|
+
*
|
|
85
|
+
* @param flagKey The key of the flag that failed to evaluate.
|
|
86
|
+
*
|
|
87
|
+
* @example
|
|
88
|
+
* // All flags disabled and with no value by default
|
|
89
|
+
* const defaultHandler = () => new DefaultFlag(undefined, false)
|
|
90
|
+
*
|
|
91
|
+
* // Enable only VIP flags by default
|
|
92
|
+
* const vipDefaultHandler = (key: string) => new Default(undefined, key.startsWith('vip_'))
|
|
93
|
+
*/
|
|
94
|
+
defaultFlagHandler?: (flagKey: string) => DefaultFlag;
|
|
38
95
|
cache?: FlagsmithCache;
|
|
39
|
-
|
|
96
|
+
/**
|
|
97
|
+
* A callback function to invoke whenever the cached environment is updated.
|
|
98
|
+
* @param error The error that occurred when the environment state failed to update, if any.
|
|
99
|
+
* @param result The updated environment state, if no error was thrown.
|
|
100
|
+
*/
|
|
101
|
+
onEnvironmentChange?: (error: Error | null, result?: EnvironmentModel) => void;
|
|
40
102
|
logger?: Logger;
|
|
103
|
+
/**
|
|
104
|
+
* If enabled, the client will work offline and not make any network requests. Requires {@link offlineHandler}.
|
|
105
|
+
*/
|
|
41
106
|
offlineMode?: boolean;
|
|
107
|
+
/**
|
|
108
|
+
* If {@link offlineMode} is enabled, this handler is used to calculate the values of all flags.
|
|
109
|
+
*/
|
|
42
110
|
offlineHandler?: BaseOfflineHandler;
|
|
43
111
|
}
|
|
44
112
|
export interface ITraitConfig {
|
package/build/esm/sdk/utils.d.ts
CHANGED
|
@@ -32,5 +32,28 @@ export declare function generateIdentitiesData(identifier: string, traits: Trait
|
|
|
32
32
|
export declare const delay: (ms: number) => Promise<unknown>;
|
|
33
33
|
export declare const retryFetch: (url: string, fetchOptions: RequestInit & {
|
|
34
34
|
dispatcher?: Dispatcher;
|
|
35
|
-
}, retries: number | undefined, timeoutMs: number | undefined, customFetch: Fetch) => Promise<Response>;
|
|
35
|
+
}, retries: number | undefined, timeoutMs: number | undefined, retryDelayMs: number | undefined, customFetch: Fetch) => Promise<Response>;
|
|
36
|
+
/**
|
|
37
|
+
* A deferred promise can be resolved or rejected outside its creation scope.
|
|
38
|
+
*
|
|
39
|
+
* @template T The type of the value that the deferred promise will resolve to.
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* const deferred = new Deferred<string>()
|
|
43
|
+
*
|
|
44
|
+
* // Pass the promise somewhere
|
|
45
|
+
* performAsyncOperation(deferred.promise)
|
|
46
|
+
*
|
|
47
|
+
* // Resolve it when ready from anywhere
|
|
48
|
+
* deferred.resolve("Operation completed")
|
|
49
|
+
* deferred.failed("Error")
|
|
50
|
+
*/
|
|
51
|
+
export declare class Deferred<T> {
|
|
52
|
+
readonly promise: Promise<T>;
|
|
53
|
+
private resolvePromise;
|
|
54
|
+
private rejectPromise;
|
|
55
|
+
constructor(initial?: T);
|
|
56
|
+
resolve(value: T | PromiseLike<T>): void;
|
|
57
|
+
reject(reason?: unknown): void;
|
|
58
|
+
}
|
|
36
59
|
export {};
|