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.
@@ -33,24 +33,55 @@ export const delay = (ms) => new Promise(resolve => setTimeout(() => resolve(und
33
33
  export const retryFetch = (url,
34
34
  // built-in RequestInit type doesn't have dispatcher/agent
35
35
  fetchOptions, retries = 3, timeoutMs = 10, // set an overall timeout for this function
36
- customFetch) => {
37
- return new Promise((resolve, reject) => {
38
- const retryWrapper = (n) => {
39
- customFetch(url, {
36
+ retryDelayMs = 1000, customFetch) => {
37
+ const retryWrapper = async (n) => {
38
+ try {
39
+ return await customFetch(url, {
40
40
  ...fetchOptions,
41
41
  signal: AbortSignal.timeout(timeoutMs)
42
- })
43
- .then(res => resolve(res))
44
- .catch(async (err) => {
45
- if (n > 0) {
46
- await delay(1000);
47
- retryWrapper(--n);
48
- }
49
- else {
50
- reject(err);
51
- }
52
42
  });
53
- };
54
- retryWrapper(retries);
55
- });
43
+ }
44
+ catch (e) {
45
+ if (n > 0) {
46
+ await delay(retryDelayMs);
47
+ return await retryWrapper(n - 1);
48
+ }
49
+ else {
50
+ throw e;
51
+ }
52
+ }
53
+ };
54
+ return retryWrapper(retries);
56
55
  };
56
+ /**
57
+ * A deferred promise can be resolved or rejected outside its creation scope.
58
+ *
59
+ * @template T The type of the value that the deferred promise will resolve to.
60
+ *
61
+ * @example
62
+ * const deferred = new Deferred<string>()
63
+ *
64
+ * // Pass the promise somewhere
65
+ * performAsyncOperation(deferred.promise)
66
+ *
67
+ * // Resolve it when ready from anywhere
68
+ * deferred.resolve("Operation completed")
69
+ * deferred.failed("Error")
70
+ */
71
+ export class Deferred {
72
+ promise;
73
+ resolvePromise;
74
+ rejectPromise;
75
+ constructor(initial) {
76
+ this.promise = new Promise((resolve, reject) => {
77
+ this.resolvePromise = resolve;
78
+ this.rejectPromise = reject;
79
+ });
80
+ }
81
+ resolve(value) {
82
+ this.resolvePromise(value);
83
+ }
84
+ reject(reason) {
85
+ this.rejectPromise(reason);
86
+ }
87
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "flagsmith-nodejs",
3
- "version": "5.1.1",
3
+ "version": "6.0.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/cjs/index.js",
6
6
  "type": "module",
package/sdk/analytics.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { pino, Logger } from 'pino';
2
2
  import { Fetch } from "./types.js";
3
- import { Flags } from "./models.js";
3
+ import { FlagsmithConfig } from "./types.js";
4
4
 
5
5
  export const ANALYTICS_ENDPOINT = './analytics/flags/';
6
6
 
@@ -21,17 +21,17 @@ export interface AnalyticsProcessorOptions {
21
21
  logger?: Logger;
22
22
  /** Custom {@link fetch} implementation to use for API requests. **/
23
23
  fetch?: Fetch
24
-
24
+
25
25
  /** @deprecated Use {@link analyticsUrl} instead. **/
26
26
  baseApiUrl?: string;
27
27
  }
28
28
 
29
29
  /**
30
30
  * Tracks how often individual features are evaluated whenever {@link trackFeature} is called.
31
- *
31
+ *
32
32
  * Analytics data is posted after {@link trackFeature} is called and at least {@link ANALYTICS_TIMER} seconds have
33
33
  * passed since the previous analytics API request was made (if any), or by calling {@link flush}.
34
- *
34
+ *
35
35
  * Data will stay in memory indefinitely until it can be successfully posted to the API.
36
36
  * @see https://docs.flagsmith.com/advanced-use/flag-analytics.
37
37
  */
@@ -89,7 +89,7 @@ export class AnalyticsProcessor {
89
89
  /**
90
90
  * Track a single evaluation event for a feature.
91
91
  *
92
- * This method is called whenever {@link Flags.isFeatureEnabled}, {@link Flags.getFeatureValue} or {@link Flags.getFlag} are called.
92
+ * @see FlagsmithConfig.enableAnalytics
93
93
  */
94
94
  trackFeature(featureName: string) {
95
95
  this.analyticsData[featureName] = (this.analyticsData[featureName] || 0) + 1;
package/sdk/index.ts CHANGED
@@ -7,11 +7,11 @@ import { TraitModel } from '../flagsmith-engine/index.js';
7
7
 
8
8
  import {ANALYTICS_ENDPOINT, AnalyticsProcessor} from './analytics.js';
9
9
  import { BaseOfflineHandler } from './offline_handlers.js';
10
- import { FlagsmithAPIError, FlagsmithClientError } from './errors.js';
10
+ import { FlagsmithAPIError } from './errors.js';
11
11
 
12
12
  import { DefaultFlag, Flags } from './models.js';
13
13
  import { EnvironmentDataPollingManager } from './polling_manager.js';
14
- import { generateIdentitiesData, retryFetch } from './utils.js';
14
+ import { Deferred, generateIdentitiesData, retryFetch } from './utils.js';
15
15
  import { SegmentModel } from '../flagsmith-engine/index.js';
16
16
  import { getIdentitySegments } from '../flagsmith-engine/segments/evaluators.js';
17
17
  import { Fetch, FlagsmithCache, FlagsmithConfig, FlagsmithTraitValue, ITraitConfig } from './types.js';
@@ -27,6 +27,30 @@ export { FlagsmithCache, FlagsmithConfig } from './types.js';
27
27
  const DEFAULT_API_URL = 'https://edge.api.flagsmith.com/api/v1/';
28
28
  const DEFAULT_REQUEST_TIMEOUT_SECONDS = 10;
29
29
 
30
+ /**
31
+ * A client for evaluating Flagsmith feature flags.
32
+ *
33
+ * Flags are evaluated remotely by the Flagsmith API over HTTP by default.
34
+ * To evaluate flags locally, create the client using {@link FlagsmithConfig.enableLocalEvaluation} and a server-side SDK key.
35
+ *
36
+ * @example
37
+ * import { Flagsmith, Flags, DefaultFlag } from 'flagsmith-nodejs'
38
+ *
39
+ * const flagsmith = new Flagsmith({
40
+ * environmentKey: 'your_sdk_key',
41
+ * defaultFlagHandler: (flagKey: string) => { new DefaultFlag(...) },
42
+ * });
43
+ *
44
+ * // Fetch the current environment flags
45
+ * const environmentFlags: Flags = flagsmith.getEnvironmentFlags()
46
+ * const isFooEnabled: boolean = environmentFlags.isFeatureEnabled('foo')
47
+ *
48
+ * // Evaluate flags for any identity
49
+ * const identityFlags: Flags = flagsmith.getIdentityFlags('my_user_123', {'vip': true})
50
+ * const bannerVariation: string = identityFlags.getFeatureValue('banner_flag')
51
+ *
52
+ * @see FlagsmithConfig
53
+ */
30
54
  export class Flagsmith {
31
55
  environmentKey?: string = undefined;
32
56
  apiUrl?: string = undefined;
@@ -45,66 +69,35 @@ export class Flagsmith {
45
69
  environmentUrl?: string;
46
70
 
47
71
  environmentDataPollingManager?: EnvironmentDataPollingManager;
48
- environment!: EnvironmentModel;
72
+ private environment?: EnvironmentModel;
49
73
  offlineMode: boolean = false;
50
74
  offlineHandler?: BaseOfflineHandler = undefined;
51
75
 
52
76
  identitiesWithOverridesByIdentifier?: Map<string, IdentityModel>;
53
77
 
54
78
  private cache?: FlagsmithCache;
55
- private onEnvironmentChange?: (error: Error | null, result: EnvironmentModel) => void;
79
+ private onEnvironmentChange: (error: Error | null, result?: EnvironmentModel) => void;
56
80
  private analyticsProcessor?: AnalyticsProcessor;
57
81
  private logger: Logger;
58
82
  private customFetch: Fetch;
83
+ private readonly requestRetryDelayMilliseconds: number;
84
+
59
85
  /**
60
- * A Flagsmith client.
61
- *
62
- * Provides an interface for interacting with the Flagsmith http API.
63
- * Basic Usage::
64
- *
65
- * import flagsmith from Flagsmith
66
- * const flagsmith = new Flagsmith({environmentKey: '<your API key>'});
67
- * const environmentFlags = flagsmith.getEnvironmentFlags();
68
- * const featureEnabled = environmentFlags.isFeatureEnabled('foo');
69
- * const identityFlags = flagsmith.getIdentityFlags('identifier', {'foo': 'bar'});
70
- * const featureEnabledForIdentity = identityFlags.isFeatureEnabled("foo")
86
+ * Creates a new {@link Flagsmith} client.
71
87
  *
72
- * @param {string} data.environmentKey: The environment key obtained from Flagsmith interface
73
- * Required unless offlineMode is True.
74
- @param {string} data.apiUrl: Override the URL of the Flagsmith API to communicate with
75
- @param data.customHeaders: Additional headers to add to requests made to the
76
- Flagsmith API
77
- @param {number} data.requestTimeoutSeconds: Number of seconds to wait for a request to
78
- complete before terminating the request
79
- @param {boolean} data.enableLocalEvaluation: Enables local evaluation of flags
80
- @param {number} data.environmentRefreshIntervalSeconds: If using local evaluation,
81
- specify the interval period between refreshes of local environment data
82
- @param {number} data.retries: a urllib3.Retry object to use on all http requests to the
83
- Flagsmith API
84
- @param {boolean} data.enableAnalytics: if enabled, sends additional requests to the Flagsmith
85
- API to power flag analytics charts
86
- @param data.defaultFlagHandler: callable which will be used in the case where
87
- flags cannot be retrieved from the API or a non-existent feature is
88
- requested
89
- @param data.logger: an instance of the pino Logger class to use for logging
90
- @param {boolean} data.offlineMode: sets the client into offline mode. Relies on offlineHandler for
91
- evaluating flags.
92
- @param {BaseOfflineHandler} data.offlineHandler: provide a handler for offline logic. Used to get environment
93
- document from another source when in offlineMode. Works in place of
94
- defaultFlagHandler if offlineMode is not set and using remote evaluation.
95
- */
96
- constructor(data: FlagsmithConfig = {}) {
97
- // if (!data.offlineMode && !data.environmentKey) {
98
- // throw new Error('ValueError: environmentKey is required.');
99
- // }
100
-
88
+ * If using local evaluation, the environment will be fetched lazily when needed by any method. Polling the
89
+ * environment for updates will start after {@link environmentRefreshIntervalSeconds} once the client is created.
90
+ * @param data The {@link FlagsmithConfig} options for this client.
91
+ */
92
+ constructor(data: FlagsmithConfig) {
101
93
  this.agent = data.agent;
102
94
  this.customFetch = data.fetch ?? fetch;
103
95
  this.environmentKey = data.environmentKey;
104
- this.apiUrl = data.apiUrl || this.apiUrl;
96
+ this.apiUrl = data.apiUrl || DEFAULT_API_URL;
105
97
  this.customHeaders = data.customHeaders;
106
98
  this.requestTimeoutMs =
107
99
  1000 * (data.requestTimeoutSeconds ?? DEFAULT_REQUEST_TIMEOUT_SECONDS);
100
+ this.requestRetryDelayMilliseconds = data.requestRetryDelayMilliseconds ?? 1000;
108
101
  this.enableLocalEvaluation = data.enableLocalEvaluation;
109
102
  this.environmentRefreshIntervalSeconds =
110
103
  data.environmentRefreshIntervalSeconds || this.environmentRefreshIntervalSeconds;
@@ -112,7 +105,7 @@ export class Flagsmith {
112
105
  this.enableAnalytics = data.enableAnalytics || false;
113
106
  this.defaultFlagHandler = data.defaultFlagHandler;
114
107
 
115
- this.onEnvironmentChange = data.onEnvironmentChange;
108
+ this.onEnvironmentChange = (error, result) => data.onEnvironmentChange?.(error, result);
116
109
  this.logger = data.logger || pino();
117
110
  this.offlineMode = data.offlineMode || false;
118
111
  this.offlineHandler = data.offlineHandler;
@@ -124,10 +117,6 @@ export class Flagsmith {
124
117
  throw new Error('ValueError: Cannot use both defaultFlagHandler and offlineHandler.');
125
118
  }
126
119
 
127
- if (this.offlineHandler) {
128
- this.environment = this.offlineHandler.getEnvironment();
129
- }
130
-
131
120
  if (!!data.cache) {
132
121
  this.cache = data.cache;
133
122
  }
@@ -146,16 +135,16 @@ export class Flagsmith {
146
135
 
147
136
  if (this.enableLocalEvaluation) {
148
137
  if (!this.environmentKey.startsWith('ser.')) {
149
- console.error(
150
- 'In order to use local evaluation, please generate a server key in the environment settings page.'
138
+ throw new Error('Using local evaluation requires a server-side environment key');
139
+ }
140
+ if (this.environmentRefreshIntervalSeconds > 0){
141
+ this.environmentDataPollingManager = new EnvironmentDataPollingManager(
142
+ this,
143
+ this.environmentRefreshIntervalSeconds,
144
+ this.logger,
151
145
  );
146
+ this.environmentDataPollingManager.start();
152
147
  }
153
- this.environmentDataPollingManager = new EnvironmentDataPollingManager(
154
- this,
155
- this.environmentRefreshIntervalSeconds
156
- );
157
- this.environmentDataPollingManager.start();
158
- this.updateEnvironment();
159
148
  }
160
149
 
161
150
  if (data.enableAnalytics) {
@@ -178,18 +167,21 @@ export class Flagsmith {
178
167
  if (!!cachedItem) {
179
168
  return cachedItem;
180
169
  }
181
- if (this.enableLocalEvaluation && !this.offlineMode) {
182
- return new Promise((resolve, reject) =>
183
- this.environmentPromise!.then(() => {
184
- resolve(this.getEnvironmentFlagsFromDocument());
185
- }).catch(e => reject(e))
186
- );
187
- }
188
- if (this.environment) {
189
- return this.getEnvironmentFlagsFromDocument();
170
+ try {
171
+ if (this.enableLocalEvaluation || this.offlineMode) {
172
+ return await this.getEnvironmentFlagsFromDocument();
173
+ }
174
+ return await this.getEnvironmentFlagsFromApi();
175
+ } catch (error) {
176
+ if (!this.defaultFlagHandler) {
177
+ throw new Error('getEnvironmentFlags failed and no default flag handler was provided', { cause: error });
178
+ }
179
+ this.logger.error(error, 'getEnvironmentFlags failed');
180
+ return new Flags({
181
+ flags: {},
182
+ defaultFlagHandler: this.defaultFlagHandler
183
+ });
190
184
  }
191
-
192
- return this.getEnvironmentFlagsFromApi();
193
185
  }
194
186
 
195
187
  /**
@@ -217,18 +209,21 @@ export class Flagsmith {
217
209
  return cachedItem;
218
210
  }
219
211
  traits = traits || {};
220
- if (this.enableLocalEvaluation) {
221
- return new Promise((resolve, reject) =>
222
- this.environmentPromise!.then(() => {
223
- resolve(this.getIdentityFlagsFromDocument(identifier, traits || {}));
224
- }).catch(e => reject(e))
225
- );
226
- }
227
- if (this.offlineMode) {
228
- return this.getIdentityFlagsFromDocument(identifier, traits || {});
212
+ try {
213
+ if (this.enableLocalEvaluation || this.offlineMode) {
214
+ return await this.getIdentityFlagsFromDocument(identifier, traits || {});
215
+ }
216
+ return await this.getIdentityFlagsFromApi(identifier, traits, transient);
217
+ } catch (error) {
218
+ if (!this.defaultFlagHandler) {
219
+ throw new Error('getIdentityFlags failed and no default flag handler was provided', { cause: error })
220
+ }
221
+ this.logger.error(error, 'getIdentityFlags failed');
222
+ return new Flags({
223
+ flags: {},
224
+ defaultFlagHandler: this.defaultFlagHandler
225
+ });
229
226
  }
230
-
231
- return this.getIdentityFlagsFromApi(identifier, traits, transient);
232
227
  }
233
228
 
234
229
  /**
@@ -242,66 +237,69 @@ export class Flagsmith {
242
237
  Flagsmith, e.g. {"num_orders": 10}
243
238
  * @returns Segments that the given identity belongs to.
244
239
  */
245
- getIdentitySegments(
240
+ async getIdentitySegments(
246
241
  identifier: string,
247
242
  traits?: { [key: string]: any }
248
243
  ): Promise<SegmentModel[]> {
249
244
  if (!identifier) {
250
245
  throw new Error('`identifier` argument is missing or invalid.');
251
246
  }
247
+ if (!this.enableLocalEvaluation) {
248
+ this.logger.error('This function is only permitted with local evaluation.');
249
+ return Promise.resolve([]);
250
+ }
252
251
 
253
252
  traits = traits || {};
254
- if (this.enableLocalEvaluation) {
255
- return new Promise((resolve, reject) => {
256
- return this.environmentPromise!.then(() => {
257
- const identityModel = this.getIdentityModel(
258
- identifier,
259
- Object.keys(traits || {}).map(key => ({
260
- key,
261
- value: traits?.[key]
262
- }))
263
- );
253
+ const environment = await this.getEnvironment();
254
+ const identityModel = this.getIdentityModel(
255
+ environment,
256
+ identifier,
257
+ Object.keys(traits || {}).map(key => ({
258
+ key,
259
+ value: traits?.[key]
260
+ }))
261
+ );
264
262
 
265
- const segments = getIdentitySegments(this.environment, identityModel);
266
- return resolve(segments);
267
- }).catch(e => reject(e));
268
- });
269
- }
270
- console.error('This function is only permitted with local evaluation.');
271
- return Promise.resolve([]);
263
+ return getIdentitySegments(environment, identityModel);
272
264
  }
273
265
 
274
- /**
275
- * Updates the environment state for local flag evaluation.
276
- * Sets a local promise to prevent race conditions in getIdentityFlags / getIdentitySegments.
277
- * You only need to call this if you wish to bypass environmentRefreshIntervalSeconds.
278
- */
279
- async updateEnvironment() {
266
+ private async fetchEnvironment(): Promise<EnvironmentModel> {
267
+ const deferred = new Deferred<EnvironmentModel>();
268
+ this.environmentPromise = deferred.promise;
280
269
  try {
281
- const request = this.getEnvironmentFromApi();
282
- if (!this.environmentPromise) {
283
- this.environmentPromise = request.then(res => {
284
- this.environment = res;
285
- });
286
- await this.environmentPromise;
287
- } else {
288
- this.environment = await request;
289
- }
290
- if (this.environment.identityOverrides?.length) {
270
+ const environment = await this.getEnvironmentFromApi();
271
+ this.environment = environment;
272
+ if (environment.identityOverrides?.length) {
291
273
  this.identitiesWithOverridesByIdentifier = new Map<string, IdentityModel>(
292
- this.environment.identityOverrides.map(identity => [
293
- identity.identifier,
294
- identity
295
- ])
274
+ environment.identityOverrides.map(identity => [identity.identifier, identity])
296
275
  );
297
276
  }
298
- if (this.onEnvironmentChange) {
299
- this.onEnvironmentChange(null, this.environment);
277
+ deferred.resolve(environment);
278
+ return deferred.promise;
279
+ } catch (error) {
280
+ deferred.reject(error);
281
+ return deferred.promise;
282
+ } finally {
283
+ this.environmentPromise = undefined;
284
+ }
285
+ }
286
+
287
+ /**
288
+ * Fetch the latest environment state from the Flagsmith API to use for local flag evaluation.
289
+ *
290
+ * If the environment is currently being fetched, calling this method will not cause additional fetches.
291
+ */
292
+ async updateEnvironment(): Promise<void> {
293
+ try {
294
+ if (this.environmentPromise) {
295
+ await this.environmentPromise
296
+ return
300
297
  }
298
+ const environment = await this.fetchEnvironment();
299
+ this.onEnvironmentChange(null, environment);
301
300
  } catch (e) {
302
- if (this.onEnvironmentChange) {
303
- this.onEnvironmentChange(e as Error, this.environment);
304
- }
301
+ this.logger.error(e, 'updateEnvironment failed');
302
+ this.onEnvironmentChange(e as Error);
305
303
  }
306
304
  }
307
305
 
@@ -335,6 +333,7 @@ export class Flagsmith {
335
333
  },
336
334
  this.retries,
337
335
  this.requestTimeoutMs,
336
+ this.requestRetryDelayMilliseconds,
338
337
  this.customFetch,
339
338
  );
340
339
 
@@ -350,7 +349,25 @@ export class Flagsmith {
350
349
  /**
351
350
  * This promise ensures that the environment is retrieved before attempting to locally evaluate.
352
351
  */
353
- private environmentPromise: Promise<any> | undefined;
352
+ private environmentPromise?: Promise<EnvironmentModel>;
353
+
354
+ /**
355
+ * Returns the current environment, fetching it from the API if needed.
356
+ *
357
+ * Calling this method concurrently while the environment is being fetched will not cause additional requests.
358
+ */
359
+ async getEnvironment(): Promise<EnvironmentModel> {
360
+ if (this.offlineHandler) {
361
+ return this.offlineHandler.getEnvironment();
362
+ }
363
+ if (this.environment) {
364
+ return this.environment;
365
+ }
366
+ if (!this.environmentPromise) {
367
+ this.environmentPromise = this.fetchEnvironment();
368
+ }
369
+ return this.environmentPromise;
370
+ }
354
371
 
355
372
  private async getEnvironmentFromApi() {
356
373
  if (!this.environmentUrl) {
@@ -361,8 +378,9 @@ export class Flagsmith {
361
378
  }
362
379
 
363
380
  private async getEnvironmentFlagsFromDocument(): Promise<Flags> {
381
+ const environment = await this.getEnvironment();
364
382
  const flags = Flags.fromFeatureStateModels({
365
- featureStates: getEnvironmentFeatureStates(this.environment),
383
+ featureStates: getEnvironmentFeatureStates(environment),
366
384
  analyticsProcessor: this.analyticsProcessor,
367
385
  defaultFlagHandler: this.defaultFlagHandler
368
386
  });
@@ -376,7 +394,9 @@ export class Flagsmith {
376
394
  identifier: string,
377
395
  traits: { [key: string]: any }
378
396
  ): Promise<Flags> {
397
+ const environment = await this.getEnvironment();
379
398
  const identityModel = this.getIdentityModel(
399
+ environment,
380
400
  identifier,
381
401
  Object.keys(traits).map(key => ({
382
402
  key,
@@ -384,7 +404,7 @@ export class Flagsmith {
384
404
  }))
385
405
  );
386
406
 
387
- const featureStates = getIdentityFeatureStates(this.environment, identityModel);
407
+ const featureStates = getIdentityFeatureStates(environment, identityModel);
388
408
 
389
409
  const flags = Flags.fromFeatureStateModels({
390
410
  featureStates: featureStates,
@@ -404,30 +424,16 @@ export class Flagsmith {
404
424
  if (!this.environmentFlagsUrl) {
405
425
  throw new Error('`apiUrl` argument is missing or invalid.');
406
426
  }
407
- try {
408
- const apiFlags = await this.getJSONResponse(this.environmentFlagsUrl, 'GET');
409
- const flags = Flags.fromAPIFlags({
410
- apiFlags: apiFlags,
411
- analyticsProcessor: this.analyticsProcessor,
412
- defaultFlagHandler: this.defaultFlagHandler
413
- });
414
- if (!!this.cache) {
415
- await this.cache.set('flags', flags);
416
- }
417
- return flags;
418
- } catch (e) {
419
- if (this.offlineHandler) {
420
- return this.getEnvironmentFlagsFromDocument();
421
- }
422
- if (this.defaultFlagHandler) {
423
- return new Flags({
424
- flags: {},
425
- defaultFlagHandler: this.defaultFlagHandler
426
- });
427
- }
428
-
429
- throw e;
427
+ const apiFlags = await this.getJSONResponse(this.environmentFlagsUrl, 'GET');
428
+ const flags = Flags.fromAPIFlags({
429
+ apiFlags: apiFlags,
430
+ analyticsProcessor: this.analyticsProcessor,
431
+ defaultFlagHandler: this.defaultFlagHandler
432
+ });
433
+ if (!!this.cache) {
434
+ await this.cache.set('flags', flags);
430
435
  }
436
+ return flags;
431
437
  }
432
438
 
433
439
  private async getIdentityFlagsFromApi(
@@ -438,41 +444,32 @@ export class Flagsmith {
438
444
  if (!this.identitiesUrl) {
439
445
  throw new Error('`apiUrl` argument is missing or invalid.');
440
446
  }
441
- try {
442
- const data = generateIdentitiesData(identifier, traits, transient);
443
- const jsonResponse = await this.getJSONResponse(this.identitiesUrl, 'POST', data);
444
- const flags = Flags.fromAPIFlags({
445
- apiFlags: jsonResponse['flags'],
446
- analyticsProcessor: this.analyticsProcessor,
447
- defaultFlagHandler: this.defaultFlagHandler
448
- });
449
- if (!!this.cache) {
450
- await this.cache.set(`flags-${identifier}`, flags);
451
- }
452
- return flags;
453
- } catch (e) {
454
- if (this.offlineHandler) {
455
- return this.getIdentityFlagsFromDocument(identifier, traits);
456
- }
457
- if (this.defaultFlagHandler) {
458
- return new Flags({
459
- flags: {},
460
- defaultFlagHandler: this.defaultFlagHandler
461
- });
462
- }
463
-
464
- throw e;
447
+ const data = generateIdentitiesData(identifier, traits, transient);
448
+ const jsonResponse = await this.getJSONResponse(this.identitiesUrl, 'POST', data);
449
+ const flags = Flags.fromAPIFlags({
450
+ apiFlags: jsonResponse['flags'],
451
+ analyticsProcessor: this.analyticsProcessor,
452
+ defaultFlagHandler: this.defaultFlagHandler
453
+ });
454
+ if (!!this.cache) {
455
+ await this.cache.set(`flags-${identifier}`, flags);
465
456
  }
457
+ return flags;
466
458
  }
467
459
 
468
- private getIdentityModel(identifier: string, traits: { key: string; value: any }[]) {
460
+ private getIdentityModel(
461
+ environment: EnvironmentModel,
462
+ identifier: string,
463
+ traits: { key: string; value: any }[]
464
+ ) {
469
465
  const traitModels = traits.map(trait => new TraitModel(trait.key, trait.value));
470
- let identityWithOverrides = this.identitiesWithOverridesByIdentifier?.get(identifier);
466
+ let identityWithOverrides =
467
+ this.identitiesWithOverridesByIdentifier?.get(identifier);
471
468
  if (identityWithOverrides) {
472
469
  identityWithOverrides.updateTraits(traitModels);
473
470
  return identityWithOverrides;
474
471
  }
475
- return new IdentityModel('0', traitModels, [], this.environment.apiKey, identifier);
472
+ return new IdentityModel('0', traitModels, [], environment.apiKey, identifier);
476
473
  }
477
474
  }
478
475
 
@@ -1,20 +1,27 @@
1
1
  import Flagsmith from './index.js';
2
+ import { Logger } from 'pino';
2
3
 
3
4
  export class EnvironmentDataPollingManager {
4
5
  private interval?: NodeJS.Timeout;
5
6
  private main: Flagsmith;
6
7
  private refreshIntervalSeconds: number;
8
+ private logger: Logger;
7
9
 
8
- constructor(main: Flagsmith, refreshIntervalSeconds: number) {
10
+ constructor(main: Flagsmith, refreshIntervalSeconds: number, logger: Logger) {
9
11
  this.main = main;
10
12
  this.refreshIntervalSeconds = refreshIntervalSeconds;
13
+ this.logger = logger;
11
14
  }
12
15
 
13
16
  start() {
14
17
  const updateEnvironment = () => {
15
18
  if (this.interval) clearInterval(this.interval);
16
19
  this.interval = setInterval(async () => {
17
- await this.main.updateEnvironment();
20
+ try {
21
+ await this.main.updateEnvironment();
22
+ } catch (error) {
23
+ this.logger.error(error, 'failed to poll environment');
24
+ }
18
25
  }, this.refreshIntervalSeconds * 1000);
19
26
  };
20
27
  updateEnvironment();