flagsmith-nodejs 2.5.0 → 2.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -223,6 +223,9 @@ var Flagsmith = /** @class */ (function () {
223
223
  return __generator(this, function (_b) {
224
224
  switch (_b.label) {
225
225
  case 0:
226
+ if (!identifier) {
227
+ throw new Error("`identifier` argument is missing or invalid.");
228
+ }
226
229
  _a = !!this.cache;
227
230
  if (!_a) return [3 /*break*/, 2];
228
231
  return [4 /*yield*/, this.cache.get("flags-".concat(identifier))];
@@ -260,6 +263,9 @@ var Flagsmith = /** @class */ (function () {
260
263
  */
261
264
  Flagsmith.prototype.getIdentitySegments = function (identifier, traits) {
262
265
  var _this = this;
266
+ if (!identifier) {
267
+ throw new Error("`identifier` argument is missing or invalid.");
268
+ }
263
269
  traits = traits || {};
264
270
  if (this.enableLocalEvaluation) {
265
271
  return new Promise(function (resolve, reject) {
@@ -358,10 +364,9 @@ var Flagsmith = /** @class */ (function () {
358
364
  return [4 /*yield*/, (0, utils_1.retryFetch)(url, {
359
365
  agent: this.agent,
360
366
  method: method,
361
- timeout: this.requestTimeoutMs || undefined,
362
367
  body: JSON.stringify(body),
363
368
  headers: headers
364
- }, this.retries)];
369
+ }, this.retries, this.requestTimeoutMs || undefined)];
365
370
  case 1:
366
371
  data = _e.sent();
367
372
  if (data.status !== 200) {
@@ -423,7 +428,7 @@ var Flagsmith = /** @class */ (function () {
423
428
  featureStates: featureStates,
424
429
  analyticsProcessor: this.analyticsProcessor,
425
430
  defaultFlagHandler: this.defaultFlagHandler,
426
- identityID: identityModel.djangoID || identityModel.identityUuid
431
+ identityID: identityModel.djangoID || identityModel.compositeKey
427
432
  });
428
433
  if (!!!this.cache) return [3 /*break*/, 2];
429
434
  // @ts-ignore node-cache types are incorrect, ttl should be optional
@@ -63,11 +63,8 @@ var retryFetch = function (url, fetchOptions, retries, timeout // set an overall
63
63
  ) {
64
64
  if (retries === void 0) { retries = 3; }
65
65
  return new Promise(function (resolve, reject) {
66
- // check for timeout
67
- if (timeout)
68
- setTimeout(function () { return reject('error: timeout'); }, timeout);
69
- var wrapper = function (n) {
70
- (0, node_fetch_1.default)(url, fetchOptions)
66
+ var retryWrapper = function (n) {
67
+ requestWrapper()
71
68
  .then(function (res) { return resolve(res); })
72
69
  .catch(function (err) { return __awaiter(void 0, void 0, void 0, function () {
73
70
  return __generator(this, function (_a) {
@@ -77,7 +74,7 @@ var retryFetch = function (url, fetchOptions, retries, timeout // set an overall
77
74
  return [4 /*yield*/, (0, exports.delay)(1000)];
78
75
  case 1:
79
76
  _a.sent();
80
- wrapper(--n);
77
+ retryWrapper(--n);
81
78
  return [3 /*break*/, 3];
82
79
  case 2:
83
80
  reject(err);
@@ -87,7 +84,16 @@ var retryFetch = function (url, fetchOptions, retries, timeout // set an overall
87
84
  });
88
85
  }); });
89
86
  };
90
- wrapper(retries);
87
+ var requestWrapper = function () {
88
+ return new Promise(function (resolve, reject) {
89
+ if (timeout)
90
+ setTimeout(function () { return reject('error: timeout'); }, timeout);
91
+ return (0, node_fetch_1.default)(url, fetchOptions)
92
+ .then(function (res) { return resolve(res); })
93
+ .catch(function (err) { return reject(err); });
94
+ });
95
+ };
96
+ retryWrapper(retries);
91
97
  });
92
98
  };
93
99
  exports.retryFetch = retryFetch;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "flagsmith-nodejs",
3
- "version": "2.5.0",
3
+ "version": "2.5.2",
4
4
  "description": "Flagsmith lets you manage features flags and remote config across web, mobile and server side applications. Deliver true Continuous Integration. Get builds out faster. Control who has access to new features.",
5
5
  "main": "build/index.js",
6
6
  "repository": {
package/sdk/index.ts CHANGED
@@ -173,6 +173,10 @@ export class Flagsmith {
173
173
  * @returns Flags object holding all the flags for the given identity.
174
174
  */
175
175
  async getIdentityFlags(identifier: string, traits?: { [key: string]: any }): Promise<Flags> {
176
+ if (!identifier) {
177
+ throw new Error("`identifier` argument is missing or invalid.")
178
+ }
179
+
176
180
  const cachedItem = !!this.cache && await this.cache.get(`flags-${identifier}`);
177
181
  if (!!cachedItem) {
178
182
  return cachedItem;
@@ -203,6 +207,10 @@ export class Flagsmith {
203
207
  identifier: string,
204
208
  traits?: { [key: string]: any }
205
209
  ): Promise<SegmentModel[]> {
210
+ if (!identifier) {
211
+ throw new Error("`identifier` argument is missing or invalid.")
212
+ }
213
+
206
214
  traits = traits || {};
207
215
  if (this.enableLocalEvaluation) {
208
216
  return new Promise((resolve, reject) => {
@@ -275,11 +283,11 @@ export class Flagsmith {
275
283
  {
276
284
  agent: this.agent,
277
285
  method: method,
278
- timeout: this.requestTimeoutMs || undefined,
279
286
  body: JSON.stringify(body),
280
287
  headers: headers
281
288
  },
282
- this.retries
289
+ this.retries,
290
+ this.requestTimeoutMs || undefined,
283
291
  );
284
292
 
285
293
  if (data.status !== 200) {
@@ -329,7 +337,7 @@ export class Flagsmith {
329
337
  featureStates: featureStates,
330
338
  analyticsProcessor: this.analyticsProcessor,
331
339
  defaultFlagHandler: this.defaultFlagHandler,
332
- identityID: identityModel.djangoID || identityModel.identityUuid
340
+ identityID: identityModel.djangoID || identityModel.compositeKey
333
341
  });
334
342
 
335
343
  if (!!this.cache) {
package/sdk/utils.ts CHANGED
@@ -23,22 +23,28 @@ export const retryFetch = (
23
23
  timeout?: number // set an overall timeout for this function
24
24
  ): Promise<Response> => {
25
25
  return new Promise((resolve, reject) => {
26
- // check for timeout
27
- if (timeout) setTimeout(() => reject('error: timeout'), timeout);
28
-
29
- const wrapper = (n: number) => {
30
- fetch(url, fetchOptions)
26
+ const retryWrapper = (n: number) => {
27
+ requestWrapper()
31
28
  .then(res => resolve(res))
32
29
  .catch(async err => {
33
30
  if (n > 0) {
34
31
  await delay(1000);
35
- wrapper(--n);
32
+ retryWrapper(--n);
36
33
  } else {
37
34
  reject(err);
38
35
  }
39
36
  });
40
37
  };
41
38
 
42
- wrapper(retries);
39
+ const requestWrapper = (): Promise<Response> => {
40
+ return new Promise((resolve, reject) => {
41
+ if (timeout) setTimeout(() => reject('error: timeout'), timeout);
42
+ return fetch(url, fetchOptions)
43
+ .then(res => resolve(res))
44
+ .catch(err => reject(err))
45
+ })
46
+ }
47
+
48
+ retryWrapper(retries);
43
49
  });
44
50
  };
@@ -0,0 +1,61 @@
1
+ import { v4 as uuidv4 } from 'uuid';
2
+ import { getHashedPercentateForObjIds } from '../../../../flagsmith-engine/utils/hashing';
3
+
4
+ describe('getHashedPercentageForObjIds', () => {
5
+ it.each([
6
+ [[12, 93]],
7
+ [[uuidv4(), 99]],
8
+ [[99, uuidv4()]],
9
+ [[uuidv4(), uuidv4()]]
10
+ ])('returns x where 0 <= x < 100', (objIds: (string|number)[]) => {
11
+ let result = getHashedPercentateForObjIds(objIds);
12
+ expect(result).toBeLessThan(100);
13
+ expect(result).toBeGreaterThanOrEqual(0);
14
+ });
15
+
16
+ it.each([
17
+ [[12, 93]],
18
+ [[uuidv4(), 99]],
19
+ [[99, uuidv4()]],
20
+ [[uuidv4(), uuidv4()]]
21
+ ])('returns the same value each time', (objIds: (string|number)[]) => {
22
+ let resultOne = getHashedPercentateForObjIds(objIds);
23
+ let resultTwo = getHashedPercentateForObjIds(objIds);
24
+ expect(resultOne).toEqual(resultTwo);
25
+ })
26
+
27
+ it('is unique for different object ids', () => {
28
+ let resultOne = getHashedPercentateForObjIds([14, 106]);
29
+ let resultTwo = getHashedPercentateForObjIds([53, 200]);
30
+ expect(resultOne).not.toEqual(resultTwo);
31
+ })
32
+
33
+ it('is evenly distributed', () => {
34
+ // copied from python test here:
35
+ // https://github.com/Flagsmith/flagsmith-engine/blob/main/tests/unit/utils/test_utils_hashing.py#L56
36
+ const testSample = 500;
37
+ const numTestBuckets = 50;
38
+ const testBucketSize = Math.floor(testSample / numTestBuckets)
39
+ const errorFactor = 0.1
40
+
41
+ // Given
42
+ let objectIdPairs = Array.from(Array(testSample).keys()).flatMap(d => Array.from(Array(testSample).keys()).map(e => [d, e].flat()))
43
+
44
+ // When
45
+ let values = objectIdPairs.map((objIds) => getHashedPercentateForObjIds(objIds));
46
+
47
+ // Then
48
+ for (let i = 0; i++; i < numTestBuckets) {
49
+ let bucketStart = i * testBucketSize;
50
+ let bucketEnd = (i + 1) * testBucketSize;
51
+ let bucketValueLimit = Math.min(
52
+ (i + 1) / numTestBuckets + errorFactor + ((i + 1) / numTestBuckets),
53
+ 1
54
+ )
55
+
56
+ for (let i = bucketStart; i++; i < bucketEnd) {
57
+ expect(values[i]).toBeLessThanOrEqual(bucketValueLimit);
58
+ }
59
+ }
60
+ })
61
+ })
@@ -172,6 +172,27 @@ test('test_default_flag_used_after_multiple_API_errors', async () => {
172
172
  expect(flag.value).toBe(defaultFlag.value);
173
173
  });
174
174
 
175
+ test('default flag handler used when timeout occurs', async () => {
176
+ // @ts-ignore
177
+ fetch.mockReturnValue(Promise.resolve(sleep(10000)));
178
+
179
+ const defaultFlag = new DefaultFlag('some-default-value', true);
180
+
181
+ const defaultFlagHandler = (featureName: string) => defaultFlag;
182
+
183
+ const flg = new Flagsmith({
184
+ environmentKey: 'key',
185
+ defaultFlagHandler: defaultFlagHandler,
186
+ requestTimeoutSeconds: 0.1,
187
+ });
188
+
189
+ const flags = await flg.getEnvironmentFlags();
190
+ const flag = flags.getFlag('some_feature');
191
+ expect(flag.isDefault).toBe(true);
192
+ expect(flag.enabled).toBe(defaultFlag.enabled);
193
+ expect(flag.value).toBe(defaultFlag.value);
194
+ })
195
+
175
196
  test('test_throws_when_no_identity_flags_returned_due_to_error', async () => {
176
197
  // @ts-ignore
177
198
  fetch.mockReturnValue(Promise.resolve(new Response('bad data')));
@@ -227,6 +248,22 @@ test('test onEnvironmentChange is called after error', async () => {
227
248
  expect(callbackSpy).toBeCalled();
228
249
  });
229
250
 
251
+ test('getIdentityFlags throws error if identifier is empty string', async () => {
252
+ const flagsmith = new Flagsmith({
253
+ environmentKey: 'key',
254
+ });
255
+
256
+ await expect(flagsmith.getIdentityFlags('')).rejects.toThrow('`identifier` argument is missing or invalid.');
257
+ })
258
+
259
+
260
+ test('getIdentitySegments throws error if identifier is empty string', () => {
261
+ const flagsmith = new Flagsmith({
262
+ environmentKey: 'key',
263
+ });
264
+
265
+ expect(() => { flagsmith.getIdentitySegments(''); }).toThrow('`identifier` argument is missing or invalid.');
266
+ })
230
267
 
231
268
 
232
269
  async function wipeFeatureStateUUIDs (enviromentModel: EnvironmentModel) {
@@ -247,3 +284,9 @@ async function wipeFeatureStateUUIDs (enviromentModel: EnvironmentModel) {
247
284
  })
248
285
  })
249
286
  }
287
+
288
+ function sleep(ms: number) {
289
+ return new Promise((resolve) => {
290
+ setTimeout(resolve, ms);
291
+ });
292
+ }