flagsmith-nodejs 6.1.0 → 6.2.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/.github/workflows/conventional-commit.yml +29 -0
- package/.github/workflows/publish.yml +17 -17
- package/.github/workflows/pull_request.yaml +33 -33
- package/.github/workflows/release-please.yml +18 -0
- package/.gitmodules +1 -0
- package/.prettierrc.cjs +9 -1
- package/.release-please-manifest.json +1 -0
- package/CHANGELOG.md +552 -0
- package/CODEOWNERS +1 -0
- package/README.md +0 -2
- package/build/cjs/sdk/analytics.js +3 -1
- package/build/cjs/sdk/index.js +41 -5
- package/build/cjs/sdk/utils.d.ts +1 -0
- package/build/cjs/sdk/utils.js +14 -1
- package/build/esm/sdk/analytics.js +3 -1
- package/build/esm/sdk/index.js +42 -6
- package/build/esm/sdk/utils.d.ts +1 -0
- package/build/esm/sdk/utils.js +12 -0
- package/package.json +1 -1
- package/release-please-config.json +62 -0
- package/sdk/analytics.ts +3 -1
- package/sdk/index.ts +53 -7
- package/sdk/utils.ts +13 -0
- package/tests/engine/unit/engine.test.ts +0 -1
- package/tests/sdk/analytics.test.ts +6 -1
- package/tests/sdk/flagsmith-environment-flags.test.ts +28 -0
- package/tests/sdk/flagsmith-identity-flags.test.ts +11 -2
- package/tests/sdk/flagsmith.test.ts +162 -1
package/build/cjs/sdk/index.js
CHANGED
|
@@ -279,6 +279,7 @@ class Flagsmith {
|
|
|
279
279
|
if (this.environmentKey) {
|
|
280
280
|
headers['X-Environment-Key'] = this.environmentKey;
|
|
281
281
|
}
|
|
282
|
+
headers['User-Agent'] = (0, utils_js_1.getUserAgent)();
|
|
282
283
|
if (this.customHeaders) {
|
|
283
284
|
for (const [k, v] of Object.entries(this.customHeaders)) {
|
|
284
285
|
headers[k] = v;
|
|
@@ -293,7 +294,7 @@ class Flagsmith {
|
|
|
293
294
|
if (data.status !== 200) {
|
|
294
295
|
throw new errors_js_1.FlagsmithAPIError(`Invalid request made to Flagsmith API. Response status code: ${data.status}`);
|
|
295
296
|
}
|
|
296
|
-
return data.json();
|
|
297
|
+
return { response: data, data: await data.json() };
|
|
297
298
|
}
|
|
298
299
|
/**
|
|
299
300
|
* This promise ensures that the environment is retrieved before attempting to locally evaluate.
|
|
@@ -320,8 +321,43 @@ class Flagsmith {
|
|
|
320
321
|
if (!this.environmentUrl) {
|
|
321
322
|
throw new Error('`apiUrl` argument is missing or invalid.');
|
|
322
323
|
}
|
|
323
|
-
const
|
|
324
|
-
|
|
324
|
+
const startTime = Date.now();
|
|
325
|
+
const documents = [];
|
|
326
|
+
let url = this.environmentUrl;
|
|
327
|
+
let loggedWarning = false;
|
|
328
|
+
while (true) {
|
|
329
|
+
try {
|
|
330
|
+
if (!loggedWarning) {
|
|
331
|
+
const elapsedMs = Date.now() - startTime;
|
|
332
|
+
if (elapsedMs > this.environmentRefreshIntervalSeconds * 1000) {
|
|
333
|
+
this.logger.warn(`Environment document retrieval exceeded the polling interval of ${this.environmentRefreshIntervalSeconds} seconds.`);
|
|
334
|
+
loggedWarning = true;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
const { response, data } = await this.getJSONResponse(url, 'GET');
|
|
338
|
+
documents.push(data);
|
|
339
|
+
const linkHeader = response.headers.get('link');
|
|
340
|
+
if (linkHeader) {
|
|
341
|
+
const nextMatch = linkHeader.match(/<([^>]+)>;\s*rel="next"/);
|
|
342
|
+
if (nextMatch) {
|
|
343
|
+
const relativeUrl = decodeURIComponent(nextMatch[1]);
|
|
344
|
+
url = new URL(relativeUrl, this.apiUrl).href;
|
|
345
|
+
continue;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
break;
|
|
349
|
+
}
|
|
350
|
+
catch (error) {
|
|
351
|
+
throw error;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
// Compile the document
|
|
355
|
+
const compiledDocument = documents[0];
|
|
356
|
+
for (let i = 1; i < documents.length; i++) {
|
|
357
|
+
compiledDocument.identity_overrides = compiledDocument.identity_overrides || [];
|
|
358
|
+
compiledDocument.identity_overrides.push(...(documents[i].identity_overrides || []));
|
|
359
|
+
}
|
|
360
|
+
return (0, util_js_1.buildEnvironmentModel)(compiledDocument);
|
|
325
361
|
}
|
|
326
362
|
async getEnvironmentFlagsFromDocument() {
|
|
327
363
|
const environment = await this.getEnvironment();
|
|
@@ -357,7 +393,7 @@ class Flagsmith {
|
|
|
357
393
|
if (!this.environmentFlagsUrl) {
|
|
358
394
|
throw new Error('`apiUrl` argument is missing or invalid.');
|
|
359
395
|
}
|
|
360
|
-
const apiFlags = await this.getJSONResponse(this.environmentFlagsUrl, 'GET');
|
|
396
|
+
const { data: apiFlags } = await this.getJSONResponse(this.environmentFlagsUrl, 'GET');
|
|
361
397
|
const flags = models_js_1.Flags.fromAPIFlags({
|
|
362
398
|
apiFlags: apiFlags,
|
|
363
399
|
analyticsProcessor: this.analyticsProcessor,
|
|
@@ -373,7 +409,7 @@ class Flagsmith {
|
|
|
373
409
|
throw new Error('`apiUrl` argument is missing or invalid.');
|
|
374
410
|
}
|
|
375
411
|
const data = (0, utils_js_1.generateIdentitiesData)(identifier, traits, transient);
|
|
376
|
-
const jsonResponse = await this.getJSONResponse(this.identitiesUrl, 'POST', data);
|
|
412
|
+
const { data: jsonResponse } = await this.getJSONResponse(this.identitiesUrl, 'POST', data);
|
|
377
413
|
const flags = models_js_1.Flags.fromAPIFlags({
|
|
378
414
|
apiFlags: jsonResponse['flags'],
|
|
379
415
|
analyticsProcessor: this.analyticsProcessor,
|
package/build/cjs/sdk/utils.d.ts
CHANGED
package/build/cjs/sdk/utils.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.Deferred = exports.retryFetch = exports.delay = exports.generateIdentitiesData = exports.isTraitConfig = void 0;
|
|
3
|
+
exports.getUserAgent = exports.Deferred = exports.retryFetch = exports.delay = exports.generateIdentitiesData = exports.isTraitConfig = void 0;
|
|
4
|
+
const FLAGSMITH_USER_AGENT = 'flagsmith-nodejs-sdk';
|
|
5
|
+
const FLAGSMITH_UNKNOWN_VERSION = 'unknown';
|
|
4
6
|
function isTraitConfig(traitValue) {
|
|
5
7
|
return !!traitValue && typeof traitValue == 'object' && traitValue.value !== undefined;
|
|
6
8
|
}
|
|
@@ -93,3 +95,14 @@ class Deferred {
|
|
|
93
95
|
}
|
|
94
96
|
}
|
|
95
97
|
exports.Deferred = Deferred;
|
|
98
|
+
function getUserAgent() {
|
|
99
|
+
try {
|
|
100
|
+
const packageJson = require('../package.json');
|
|
101
|
+
const version = packageJson?.version;
|
|
102
|
+
return version ? `${FLAGSMITH_USER_AGENT}/${version}` : FLAGSMITH_UNKNOWN_VERSION;
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
return FLAGSMITH_UNKNOWN_VERSION;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
exports.getUserAgent = getUserAgent;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { pino } from 'pino';
|
|
2
|
+
import { getUserAgent } from './utils.js';
|
|
2
3
|
export const ANALYTICS_ENDPOINT = './analytics/flags/';
|
|
3
4
|
/** Duration in seconds to wait before trying to flush collected data after {@link trackFeature} is called. **/
|
|
4
5
|
const ANALYTICS_TIMER = 10;
|
|
@@ -44,7 +45,8 @@ export class AnalyticsProcessor {
|
|
|
44
45
|
signal: AbortSignal.timeout(this.requestTimeoutMs),
|
|
45
46
|
headers: {
|
|
46
47
|
'Content-Type': 'application/json',
|
|
47
|
-
'X-Environment-Key': this.environmentKey
|
|
48
|
+
'X-Environment-Key': this.environmentKey,
|
|
49
|
+
'User-Agent': getUserAgent()
|
|
48
50
|
}
|
|
49
51
|
});
|
|
50
52
|
await this.currentFlush;
|
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 { Deferred, generateIdentitiesData, retryFetch } from './utils.js';
|
|
9
|
+
import { Deferred, generateIdentitiesData, getUserAgent, 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';
|
|
@@ -269,6 +269,7 @@ export class Flagsmith {
|
|
|
269
269
|
if (this.environmentKey) {
|
|
270
270
|
headers['X-Environment-Key'] = this.environmentKey;
|
|
271
271
|
}
|
|
272
|
+
headers['User-Agent'] = getUserAgent();
|
|
272
273
|
if (this.customHeaders) {
|
|
273
274
|
for (const [k, v] of Object.entries(this.customHeaders)) {
|
|
274
275
|
headers[k] = v;
|
|
@@ -283,7 +284,7 @@ export class Flagsmith {
|
|
|
283
284
|
if (data.status !== 200) {
|
|
284
285
|
throw new FlagsmithAPIError(`Invalid request made to Flagsmith API. Response status code: ${data.status}`);
|
|
285
286
|
}
|
|
286
|
-
return data.json();
|
|
287
|
+
return { response: data, data: await data.json() };
|
|
287
288
|
}
|
|
288
289
|
/**
|
|
289
290
|
* This promise ensures that the environment is retrieved before attempting to locally evaluate.
|
|
@@ -310,8 +311,43 @@ export class Flagsmith {
|
|
|
310
311
|
if (!this.environmentUrl) {
|
|
311
312
|
throw new Error('`apiUrl` argument is missing or invalid.');
|
|
312
313
|
}
|
|
313
|
-
const
|
|
314
|
-
|
|
314
|
+
const startTime = Date.now();
|
|
315
|
+
const documents = [];
|
|
316
|
+
let url = this.environmentUrl;
|
|
317
|
+
let loggedWarning = false;
|
|
318
|
+
while (true) {
|
|
319
|
+
try {
|
|
320
|
+
if (!loggedWarning) {
|
|
321
|
+
const elapsedMs = Date.now() - startTime;
|
|
322
|
+
if (elapsedMs > this.environmentRefreshIntervalSeconds * 1000) {
|
|
323
|
+
this.logger.warn(`Environment document retrieval exceeded the polling interval of ${this.environmentRefreshIntervalSeconds} seconds.`);
|
|
324
|
+
loggedWarning = true;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
const { response, data } = await this.getJSONResponse(url, 'GET');
|
|
328
|
+
documents.push(data);
|
|
329
|
+
const linkHeader = response.headers.get('link');
|
|
330
|
+
if (linkHeader) {
|
|
331
|
+
const nextMatch = linkHeader.match(/<([^>]+)>;\s*rel="next"/);
|
|
332
|
+
if (nextMatch) {
|
|
333
|
+
const relativeUrl = decodeURIComponent(nextMatch[1]);
|
|
334
|
+
url = new URL(relativeUrl, this.apiUrl).href;
|
|
335
|
+
continue;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
break;
|
|
339
|
+
}
|
|
340
|
+
catch (error) {
|
|
341
|
+
throw error;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
// Compile the document
|
|
345
|
+
const compiledDocument = documents[0];
|
|
346
|
+
for (let i = 1; i < documents.length; i++) {
|
|
347
|
+
compiledDocument.identity_overrides = compiledDocument.identity_overrides || [];
|
|
348
|
+
compiledDocument.identity_overrides.push(...(documents[i].identity_overrides || []));
|
|
349
|
+
}
|
|
350
|
+
return buildEnvironmentModel(compiledDocument);
|
|
315
351
|
}
|
|
316
352
|
async getEnvironmentFlagsFromDocument() {
|
|
317
353
|
const environment = await this.getEnvironment();
|
|
@@ -347,7 +383,7 @@ export class Flagsmith {
|
|
|
347
383
|
if (!this.environmentFlagsUrl) {
|
|
348
384
|
throw new Error('`apiUrl` argument is missing or invalid.');
|
|
349
385
|
}
|
|
350
|
-
const apiFlags = await this.getJSONResponse(this.environmentFlagsUrl, 'GET');
|
|
386
|
+
const { data: apiFlags } = await this.getJSONResponse(this.environmentFlagsUrl, 'GET');
|
|
351
387
|
const flags = Flags.fromAPIFlags({
|
|
352
388
|
apiFlags: apiFlags,
|
|
353
389
|
analyticsProcessor: this.analyticsProcessor,
|
|
@@ -363,7 +399,7 @@ export class Flagsmith {
|
|
|
363
399
|
throw new Error('`apiUrl` argument is missing or invalid.');
|
|
364
400
|
}
|
|
365
401
|
const data = generateIdentitiesData(identifier, traits, transient);
|
|
366
|
-
const jsonResponse = await this.getJSONResponse(this.identitiesUrl, 'POST', data);
|
|
402
|
+
const { data: jsonResponse } = await this.getJSONResponse(this.identitiesUrl, 'POST', data);
|
|
367
403
|
const flags = Flags.fromAPIFlags({
|
|
368
404
|
apiFlags: jsonResponse['flags'],
|
|
369
405
|
analyticsProcessor: this.analyticsProcessor,
|
package/build/esm/sdk/utils.d.ts
CHANGED
package/build/esm/sdk/utils.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
const FLAGSMITH_USER_AGENT = 'flagsmith-nodejs-sdk';
|
|
2
|
+
const FLAGSMITH_UNKNOWN_VERSION = 'unknown';
|
|
1
3
|
export function isTraitConfig(traitValue) {
|
|
2
4
|
return !!traitValue && typeof traitValue == 'object' && traitValue.value !== undefined;
|
|
3
5
|
}
|
|
@@ -85,3 +87,13 @@ export class Deferred {
|
|
|
85
87
|
this.rejectPromise(reason);
|
|
86
88
|
}
|
|
87
89
|
}
|
|
90
|
+
export function getUserAgent() {
|
|
91
|
+
try {
|
|
92
|
+
const packageJson = require('../package.json');
|
|
93
|
+
const version = packageJson?.version;
|
|
94
|
+
return version ? `${FLAGSMITH_USER_AGENT}/${version}` : FLAGSMITH_UNKNOWN_VERSION;
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
return FLAGSMITH_UNKNOWN_VERSION;
|
|
98
|
+
}
|
|
99
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "flagsmith-nodejs",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.2.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",
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"bootstrap-sha": "644c5c883ecbb3786507b50cea01903dc2e533bf",
|
|
3
|
+
"packages": {
|
|
4
|
+
".": {
|
|
5
|
+
"release-type": "node",
|
|
6
|
+
"changelog-path": "CHANGELOG.md",
|
|
7
|
+
"bump-minor-pre-major": false,
|
|
8
|
+
"bump-patch-for-minor-pre-major": false,
|
|
9
|
+
"draft": false,
|
|
10
|
+
"prerelease": false,
|
|
11
|
+
"include-component-in-tag": false
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json",
|
|
15
|
+
"changelog-sections": [
|
|
16
|
+
{
|
|
17
|
+
"type": "feat",
|
|
18
|
+
"hidden": false,
|
|
19
|
+
"section": "Features"
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"type": "fix",
|
|
23
|
+
"hidden": false,
|
|
24
|
+
"section": "Bug Fixes"
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"type": "ci",
|
|
28
|
+
"hidden": false,
|
|
29
|
+
"section": "CI"
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"type": "docs",
|
|
33
|
+
"hidden": false,
|
|
34
|
+
"section": "Docs"
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"type": "deps",
|
|
38
|
+
"hidden": false,
|
|
39
|
+
"section": "Dependency Updates"
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"type": "perf",
|
|
43
|
+
"hidden": false,
|
|
44
|
+
"section": "Performance Improvements"
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"type": "refactor",
|
|
48
|
+
"hidden": false,
|
|
49
|
+
"section": "Refactoring"
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"type": "test",
|
|
53
|
+
"hidden": false,
|
|
54
|
+
"section": "Tests"
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
"type": "chore",
|
|
58
|
+
"hidden": false,
|
|
59
|
+
"section": "Other"
|
|
60
|
+
}
|
|
61
|
+
]
|
|
62
|
+
}
|
package/sdk/analytics.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { pino, Logger } from 'pino';
|
|
2
2
|
import { Fetch } from './types.js';
|
|
3
3
|
import { FlagsmithConfig } from './types.js';
|
|
4
|
+
import { getUserAgent } from './utils.js';
|
|
4
5
|
|
|
5
6
|
export const ANALYTICS_ENDPOINT = './analytics/flags/';
|
|
6
7
|
|
|
@@ -69,7 +70,8 @@ export class AnalyticsProcessor {
|
|
|
69
70
|
signal: AbortSignal.timeout(this.requestTimeoutMs),
|
|
70
71
|
headers: {
|
|
71
72
|
'Content-Type': 'application/json',
|
|
72
|
-
'X-Environment-Key': this.environmentKey
|
|
73
|
+
'X-Environment-Key': this.environmentKey,
|
|
74
|
+
'User-Agent': getUserAgent()
|
|
73
75
|
}
|
|
74
76
|
});
|
|
75
77
|
await this.currentFlush;
|
package/sdk/index.ts
CHANGED
|
@@ -14,7 +14,7 @@ import { FlagsmithAPIError } from './errors.js';
|
|
|
14
14
|
|
|
15
15
|
import { DefaultFlag, Flags } from './models.js';
|
|
16
16
|
import { EnvironmentDataPollingManager } from './polling_manager.js';
|
|
17
|
-
import { Deferred, generateIdentitiesData, retryFetch } from './utils.js';
|
|
17
|
+
import { Deferred, generateIdentitiesData, getUserAgent, retryFetch } from './utils.js';
|
|
18
18
|
import { SegmentModel } from '../flagsmith-engine/index.js';
|
|
19
19
|
import { getIdentitySegments } from '../flagsmith-engine/segments/evaluators.js';
|
|
20
20
|
import {
|
|
@@ -329,12 +329,14 @@ export class Flagsmith {
|
|
|
329
329
|
url: string,
|
|
330
330
|
method: string,
|
|
331
331
|
body?: { [key: string]: any }
|
|
332
|
-
): Promise<any> {
|
|
332
|
+
): Promise<{ response: Response; data: any }> {
|
|
333
333
|
const headers: { [key: string]: any } = { 'Content-Type': 'application/json' };
|
|
334
334
|
if (this.environmentKey) {
|
|
335
335
|
headers['X-Environment-Key'] = this.environmentKey as string;
|
|
336
336
|
}
|
|
337
337
|
|
|
338
|
+
headers['User-Agent'] = getUserAgent();
|
|
339
|
+
|
|
338
340
|
if (this.customHeaders) {
|
|
339
341
|
for (const [k, v] of Object.entries(this.customHeaders)) {
|
|
340
342
|
headers[k] = v;
|
|
@@ -361,7 +363,7 @@ export class Flagsmith {
|
|
|
361
363
|
);
|
|
362
364
|
}
|
|
363
365
|
|
|
364
|
-
return data.json();
|
|
366
|
+
return { response: data, data: await data.json() };
|
|
365
367
|
}
|
|
366
368
|
|
|
367
369
|
/**
|
|
@@ -391,8 +393,52 @@ export class Flagsmith {
|
|
|
391
393
|
if (!this.environmentUrl) {
|
|
392
394
|
throw new Error('`apiUrl` argument is missing or invalid.');
|
|
393
395
|
}
|
|
394
|
-
const
|
|
395
|
-
|
|
396
|
+
const startTime = Date.now();
|
|
397
|
+
const documents: any[] = [];
|
|
398
|
+
let url = this.environmentUrl;
|
|
399
|
+
let loggedWarning = false;
|
|
400
|
+
|
|
401
|
+
while (true) {
|
|
402
|
+
try {
|
|
403
|
+
if (!loggedWarning) {
|
|
404
|
+
const elapsedMs = Date.now() - startTime;
|
|
405
|
+
if (elapsedMs > this.environmentRefreshIntervalSeconds * 1000) {
|
|
406
|
+
this.logger.warn(
|
|
407
|
+
`Environment document retrieval exceeded the polling interval of ${this.environmentRefreshIntervalSeconds} seconds.`
|
|
408
|
+
);
|
|
409
|
+
loggedWarning = true;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
const { response, data } = await this.getJSONResponse(url, 'GET');
|
|
414
|
+
|
|
415
|
+
documents.push(data);
|
|
416
|
+
|
|
417
|
+
const linkHeader = response.headers.get('link');
|
|
418
|
+
if (linkHeader) {
|
|
419
|
+
const nextMatch = linkHeader.match(/<([^>]+)>;\s*rel="next"/);
|
|
420
|
+
|
|
421
|
+
if (nextMatch) {
|
|
422
|
+
const relativeUrl = decodeURIComponent(nextMatch[1]);
|
|
423
|
+
url = new URL(relativeUrl, this.apiUrl).href;
|
|
424
|
+
|
|
425
|
+
continue;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
break;
|
|
429
|
+
} catch (error) {
|
|
430
|
+
throw error;
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// Compile the document
|
|
435
|
+
const compiledDocument = documents[0];
|
|
436
|
+
for (let i = 1; i < documents.length; i++) {
|
|
437
|
+
compiledDocument.identity_overrides = compiledDocument.identity_overrides || [];
|
|
438
|
+
compiledDocument.identity_overrides.push(...(documents[i].identity_overrides || []));
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
return buildEnvironmentModel(compiledDocument);
|
|
396
442
|
}
|
|
397
443
|
|
|
398
444
|
private async getEnvironmentFlagsFromDocument(): Promise<Flags> {
|
|
@@ -442,7 +488,7 @@ export class Flagsmith {
|
|
|
442
488
|
if (!this.environmentFlagsUrl) {
|
|
443
489
|
throw new Error('`apiUrl` argument is missing or invalid.');
|
|
444
490
|
}
|
|
445
|
-
const apiFlags = await this.getJSONResponse(this.environmentFlagsUrl, 'GET');
|
|
491
|
+
const { data: apiFlags } = await this.getJSONResponse(this.environmentFlagsUrl, 'GET');
|
|
446
492
|
const flags = Flags.fromAPIFlags({
|
|
447
493
|
apiFlags: apiFlags,
|
|
448
494
|
analyticsProcessor: this.analyticsProcessor,
|
|
@@ -463,7 +509,7 @@ export class Flagsmith {
|
|
|
463
509
|
throw new Error('`apiUrl` argument is missing or invalid.');
|
|
464
510
|
}
|
|
465
511
|
const data = generateIdentitiesData(identifier, traits, transient);
|
|
466
|
-
const jsonResponse = await this.getJSONResponse(this.identitiesUrl, 'POST', data);
|
|
512
|
+
const { data: jsonResponse } = await this.getJSONResponse(this.identitiesUrl, 'POST', data);
|
|
467
513
|
const flags = Flags.fromAPIFlags({
|
|
468
514
|
apiFlags: jsonResponse['flags'],
|
|
469
515
|
analyticsProcessor: this.analyticsProcessor,
|
package/sdk/utils.ts
CHANGED
|
@@ -3,6 +3,9 @@ import { Dispatcher } from 'undici-types';
|
|
|
3
3
|
|
|
4
4
|
type Traits = { [key: string]: TraitConfig | FlagsmithTraitValue };
|
|
5
5
|
|
|
6
|
+
const FLAGSMITH_USER_AGENT = 'flagsmith-nodejs-sdk';
|
|
7
|
+
const FLAGSMITH_UNKNOWN_VERSION = 'unknown';
|
|
8
|
+
|
|
6
9
|
export function isTraitConfig(
|
|
7
10
|
traitValue: TraitConfig | FlagsmithTraitValue
|
|
8
11
|
): traitValue is TraitConfig {
|
|
@@ -102,3 +105,13 @@ export class Deferred<T> {
|
|
|
102
105
|
this.rejectPromise(reason);
|
|
103
106
|
}
|
|
104
107
|
}
|
|
108
|
+
|
|
109
|
+
export function getUserAgent(): string {
|
|
110
|
+
try {
|
|
111
|
+
const packageJson = require('../package.json');
|
|
112
|
+
const version = packageJson?.version;
|
|
113
|
+
return version ? `${FLAGSMITH_USER_AGENT}/${version}` : FLAGSMITH_UNKNOWN_VERSION;
|
|
114
|
+
} catch {
|
|
115
|
+
return FLAGSMITH_UNKNOWN_VERSION;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { getUserAgent } from '../../sdk/utils.js';
|
|
1
2
|
import { analyticsProcessor, fetch } from './utils.js';
|
|
2
3
|
|
|
3
4
|
test('test_analytics_processor_track_feature_updates_analytics_data', () => {
|
|
@@ -26,7 +27,11 @@ test('test_analytics_processor_flush_post_request_data_match_ananlytics_data', a
|
|
|
26
27
|
'http://testUrl/analytics/flags/',
|
|
27
28
|
expect.objectContaining({
|
|
28
29
|
body: '{"myFeature1":1,"myFeature2":1}',
|
|
29
|
-
headers: {
|
|
30
|
+
headers: {
|
|
31
|
+
'Content-Type': 'application/json',
|
|
32
|
+
'X-Environment-Key': 'test-key',
|
|
33
|
+
'User-Agent': getUserAgent()
|
|
34
|
+
},
|
|
30
35
|
method: 'POST'
|
|
31
36
|
})
|
|
32
37
|
);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import Flagsmith from '../../sdk/index.js';
|
|
2
2
|
import { environmentJSON, environmentModel, flagsJSON, flagsmith, fetch } from './utils.js';
|
|
3
3
|
import { DefaultFlag } from '../../sdk/models.js';
|
|
4
|
+
import { getUserAgent } from '../../sdk/utils.js';
|
|
4
5
|
|
|
5
6
|
vi.mock('../../sdk/polling_manager');
|
|
6
7
|
|
|
@@ -72,6 +73,33 @@ test('test_getFeatureValue', async () => {
|
|
|
72
73
|
expect(featureValue).toBe('some-value');
|
|
73
74
|
});
|
|
74
75
|
|
|
76
|
+
test('test_user_agent_is_set_when_fetching_environment_flags', async () => {
|
|
77
|
+
const defaultFlag = new DefaultFlag('some-default-value', true);
|
|
78
|
+
|
|
79
|
+
const defaultFlagHandler = (featureName: string) => defaultFlag;
|
|
80
|
+
|
|
81
|
+
const flg = flagsmith({
|
|
82
|
+
environmentKey: 'key',
|
|
83
|
+
defaultFlagHandler: defaultFlagHandler,
|
|
84
|
+
enableAnalytics: true
|
|
85
|
+
});
|
|
86
|
+
const flags = await flg.getEnvironmentFlags();
|
|
87
|
+
const featureValue = flags.getFeatureValue('some_feature');
|
|
88
|
+
|
|
89
|
+
expect(featureValue).toBe('some-value');
|
|
90
|
+
expect(fetch).toHaveBeenCalledWith(
|
|
91
|
+
`https://edge.api.flagsmith.com/api/v1/flags/`,
|
|
92
|
+
expect.objectContaining({
|
|
93
|
+
method: 'GET',
|
|
94
|
+
headers: {
|
|
95
|
+
'Content-Type': 'application/json',
|
|
96
|
+
'X-Environment-Key': 'key',
|
|
97
|
+
'User-Agent': getUserAgent()
|
|
98
|
+
}
|
|
99
|
+
})
|
|
100
|
+
);
|
|
101
|
+
});
|
|
102
|
+
|
|
75
103
|
test('test_throws_when_no_default_flag_handler_after_multiple_API_errors', async () => {
|
|
76
104
|
fetch.mockRejectedValue('Error during fetching the API response');
|
|
77
105
|
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
badFetch
|
|
10
10
|
} from './utils.js';
|
|
11
11
|
import { DefaultFlag } from '../../sdk/models.js';
|
|
12
|
+
import { getUserAgent } from '../../sdk/utils.js';
|
|
12
13
|
|
|
13
14
|
vi.mock('../../sdk/polling_manager');
|
|
14
15
|
|
|
@@ -150,7 +151,11 @@ test('test_transient_identity', async () => {
|
|
|
150
151
|
`https://edge.api.flagsmith.com/api/v1/identities/`,
|
|
151
152
|
expect.objectContaining({
|
|
152
153
|
method: 'POST',
|
|
153
|
-
headers: {
|
|
154
|
+
headers: {
|
|
155
|
+
'Content-Type': 'application/json',
|
|
156
|
+
'X-Environment-Key': 'sometestfakekey',
|
|
157
|
+
'User-Agent': getUserAgent()
|
|
158
|
+
},
|
|
154
159
|
body: JSON.stringify({ identifier, traits: traitsInRequest, transient })
|
|
155
160
|
})
|
|
156
161
|
);
|
|
@@ -191,7 +196,11 @@ test('test_identity_with_transient_traits', async () => {
|
|
|
191
196
|
`https://edge.api.flagsmith.com/api/v1/identities/`,
|
|
192
197
|
expect.objectContaining({
|
|
193
198
|
method: 'POST',
|
|
194
|
-
headers: {
|
|
199
|
+
headers: {
|
|
200
|
+
'Content-Type': 'application/json',
|
|
201
|
+
'X-Environment-Key': 'sometestfakekey',
|
|
202
|
+
'User-Agent': getUserAgent()
|
|
203
|
+
},
|
|
195
204
|
body: JSON.stringify({ identifier, traits: traitsInRequest })
|
|
196
205
|
})
|
|
197
206
|
);
|