flagsmith-nodejs 5.0.0 → 5.1.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.
@@ -12,6 +12,9 @@ on:
12
12
  - main
13
13
  jobs:
14
14
  build-and-test:
15
+ strategy:
16
+ matrix:
17
+ node-version: [18.x, 20.x, 22.x]
15
18
  runs-on: ubuntu-latest
16
19
  steps:
17
20
  - uses: actions/checkout@v4
@@ -19,14 +22,14 @@ jobs:
19
22
  submodules: true
20
23
  - uses: actions/setup-node@v4
21
24
  with:
22
- node-version: "18.x"
25
+ node-version: "${{ matrix.node-version }}"
23
26
  - name: cache node modules
24
27
  uses: actions/cache@v4
25
28
  with:
26
29
  path: ~/.npm # npm cache files are stored in `~/.npm` on Linux/macOS
27
- key: npm-${{ hashFiles('package-lock.json') }}
30
+ key: npm-${{ matrix.node-version }}-${{ hashFiles('package-lock.json') }}
28
31
  restore-keys: |
29
- npm-${{ hashFiles('package-lock.json') }}
32
+ npm-${{ matrix.node-version }}-${{ hashFiles('package-lock.json') }}
30
33
  npm-
31
34
  - run: npm ci
32
35
  - run: npm test
@@ -1,4 +1,4 @@
1
- export { AnalyticsProcessor, FlagsmithAPIError, FlagsmithClientError, EnvironmentDataPollingManager, FlagsmithCache, DefaultFlag, Flags, Flagsmith, } from './sdk/index.js';
1
+ export { AnalyticsProcessor, AnalyticsProcessorOptions, FlagsmithAPIError, FlagsmithClientError, EnvironmentDataPollingManager, FlagsmithCache, DefaultFlag, Flags, Flagsmith, } from './sdk/index.js';
2
2
  export { BaseOfflineHandler, LocalFileHandler, } from './sdk/offline_handlers.js';
3
3
  export { FlagsmithConfig } from './sdk/types.js';
4
4
  export { EnvironmentModel, FeatureStateModel, IdentityModel, TraitModel, SegmentModel, OrganisationModel } from './flagsmith-engine/index.js';
@@ -1,7 +1,32 @@
1
1
  import { Logger } from 'pino';
2
2
  import { Fetch } from "./types.js";
3
+ export declare const ANALYTICS_ENDPOINT = "./analytics/flags/";
4
+ export interface AnalyticsProcessorOptions {
5
+ /** URL of the Flagsmith analytics events API endpoint
6
+ * @example https://flagsmith.example.com/api/v1/analytics
7
+ */
8
+ analyticsUrl?: string;
9
+ /** Client-side key of the environment that analytics will be recorded for. **/
10
+ environmentKey: string;
11
+ /** Duration in milliseconds to wait for API requests to complete before timing out. Defaults to {@link DEFAULT_REQUEST_TIMEOUT_MS}. **/
12
+ requestTimeoutMs?: number;
13
+ logger?: Logger;
14
+ /** Custom {@link fetch} implementation to use for API requests. **/
15
+ fetch?: Fetch;
16
+ /** @deprecated Use {@link analyticsUrl} instead. **/
17
+ baseApiUrl?: string;
18
+ }
19
+ /**
20
+ * Tracks how often individual features are evaluated whenever {@link trackFeature} is called.
21
+ *
22
+ * Analytics data is posted after {@link trackFeature} is called and at least {@link ANALYTICS_TIMER} seconds have
23
+ * passed since the previous analytics API request was made (if any), or by calling {@link flush}.
24
+ *
25
+ * Data will stay in memory indefinitely until it can be successfully posted to the API.
26
+ * @see https://docs.flagsmith.com/advanced-use/flag-analytics.
27
+ */
3
28
  export declare class AnalyticsProcessor {
4
- private analyticsEndpoint;
29
+ private analyticsUrl;
5
30
  private environmentKey;
6
31
  private lastFlushed;
7
32
  analyticsData: {
@@ -11,25 +36,15 @@ export declare class AnalyticsProcessor {
11
36
  private logger;
12
37
  private currentFlush;
13
38
  private customFetch;
39
+ constructor(data: AnalyticsProcessorOptions);
14
40
  /**
15
- * AnalyticsProcessor is used to track how often individual Flags are evaluated within
16
- * the Flagsmith SDK. Docs: https://docs.flagsmith.com/advanced-use/flag-analytics.
17
- *
18
- * @param data.environmentKey environment key obtained from the Flagsmith UI
19
- * @param data.baseApiUrl base api url to override when using self hosted version
20
- * @param data.requestTimeoutMs used to tell requests to stop waiting for a response after a
21
- given number of milliseconds
41
+ * Try to flush pending collected data to the Flagsmith analytics API.
22
42
  */
23
- constructor(data: {
24
- environmentKey: string;
25
- baseApiUrl: string;
26
- requestTimeoutMs?: number;
27
- logger?: Logger;
28
- fetch?: Fetch;
29
- });
43
+ flush(): Promise<void>;
30
44
  /**
31
- * Sends all the collected data to the api asynchronously and resets the timer
45
+ * Track a single evaluation event for a feature.
46
+ *
47
+ * This method is called whenever {@link Flags.isFeatureEnabled}, {@link Flags.getFeatureValue} or {@link Flags.getFlag} are called.
32
48
  */
33
- flush(): Promise<void>;
34
49
  trackFeature(featureName: string): void;
35
50
  }
@@ -1,30 +1,31 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.AnalyticsProcessor = void 0;
3
+ exports.AnalyticsProcessor = exports.ANALYTICS_ENDPOINT = void 0;
4
4
  const pino_1 = require("pino");
5
- const ANALYTICS_ENDPOINT = 'analytics/flags/';
6
- // Used to control how often we send data(in seconds)
5
+ exports.ANALYTICS_ENDPOINT = './analytics/flags/';
6
+ /** Duration in seconds to wait before trying to flush collected data after {@link trackFeature} is called. **/
7
7
  const ANALYTICS_TIMER = 10;
8
+ const DEFAULT_REQUEST_TIMEOUT_MS = 3000;
9
+ /**
10
+ * Tracks how often individual features are evaluated whenever {@link trackFeature} is called.
11
+ *
12
+ * Analytics data is posted after {@link trackFeature} is called and at least {@link ANALYTICS_TIMER} seconds have
13
+ * passed since the previous analytics API request was made (if any), or by calling {@link flush}.
14
+ *
15
+ * Data will stay in memory indefinitely until it can be successfully posted to the API.
16
+ * @see https://docs.flagsmith.com/advanced-use/flag-analytics.
17
+ */
8
18
  class AnalyticsProcessor {
9
- analyticsEndpoint;
19
+ analyticsUrl;
10
20
  environmentKey;
11
21
  lastFlushed;
12
22
  analyticsData;
13
- requestTimeoutMs = 3000;
23
+ requestTimeoutMs = DEFAULT_REQUEST_TIMEOUT_MS;
14
24
  logger;
15
25
  currentFlush;
16
26
  customFetch;
17
- /**
18
- * AnalyticsProcessor is used to track how often individual Flags are evaluated within
19
- * the Flagsmith SDK. Docs: https://docs.flagsmith.com/advanced-use/flag-analytics.
20
- *
21
- * @param data.environmentKey environment key obtained from the Flagsmith UI
22
- * @param data.baseApiUrl base api url to override when using self hosted version
23
- * @param data.requestTimeoutMs used to tell requests to stop waiting for a response after a
24
- given number of milliseconds
25
- */
26
27
  constructor(data) {
27
- this.analyticsEndpoint = data.baseApiUrl + ANALYTICS_ENDPOINT;
28
+ this.analyticsUrl = data.analyticsUrl || data.baseApiUrl + exports.ANALYTICS_ENDPOINT;
28
29
  this.environmentKey = data.environmentKey;
29
30
  this.lastFlushed = Date.now();
30
31
  this.analyticsData = {};
@@ -33,14 +34,14 @@ class AnalyticsProcessor {
33
34
  this.customFetch = data.fetch ?? fetch;
34
35
  }
35
36
  /**
36
- * Sends all the collected data to the api asynchronously and resets the timer
37
+ * Try to flush pending collected data to the Flagsmith analytics API.
37
38
  */
38
39
  async flush() {
39
40
  if (this.currentFlush || !Object.keys(this.analyticsData).length) {
40
41
  return;
41
42
  }
42
43
  try {
43
- this.currentFlush = this.customFetch(this.analyticsEndpoint, {
44
+ this.currentFlush = this.customFetch(this.analyticsUrl, {
44
45
  method: 'POST',
45
46
  body: JSON.stringify(this.analyticsData),
46
47
  signal: AbortSignal.timeout(this.requestTimeoutMs),
@@ -63,6 +64,11 @@ class AnalyticsProcessor {
63
64
  this.analyticsData = {};
64
65
  this.lastFlushed = Date.now();
65
66
  }
67
+ /**
68
+ * Track a single evaluation event for a feature.
69
+ *
70
+ * This method is called whenever {@link Flags.isFeatureEnabled}, {@link Flags.getFeatureValue} or {@link Flags.getFlag} are called.
71
+ */
66
72
  trackFeature(featureName) {
67
73
  this.analyticsData[featureName] = (this.analyticsData[featureName] || 0) + 1;
68
74
  if (Date.now() - this.lastFlushed > ANALYTICS_TIMER * 1000) {
@@ -6,7 +6,7 @@ import { DefaultFlag, Flags } from './models.js';
6
6
  import { EnvironmentDataPollingManager } from './polling_manager.js';
7
7
  import { SegmentModel } from '../flagsmith-engine/index.js';
8
8
  import { FlagsmithConfig, FlagsmithTraitValue, ITraitConfig } from './types.js';
9
- export { AnalyticsProcessor } from './analytics.js';
9
+ export { AnalyticsProcessor, AnalyticsProcessorOptions } from './analytics.js';
10
10
  export { FlagsmithAPIError, FlagsmithClientError } from './errors.js';
11
11
  export { DefaultFlag, Flags } from './models.js';
12
12
  export { EnvironmentDataPollingManager } from './polling_manager.js';
@@ -14,6 +14,7 @@ export { FlagsmithCache, FlagsmithConfig } from './types.js';
14
14
  export declare class Flagsmith {
15
15
  environmentKey?: string;
16
16
  apiUrl?: string;
17
+ analyticsUrl?: string;
17
18
  customHeaders?: {
18
19
  [key: string]: any;
19
20
  };
@@ -27,6 +27,7 @@ const DEFAULT_REQUEST_TIMEOUT_SECONDS = 10;
27
27
  class Flagsmith {
28
28
  environmentKey = undefined;
29
29
  apiUrl = undefined;
30
+ analyticsUrl = undefined;
30
31
  customHeaders;
31
32
  agent;
32
33
  requestTimeoutMs;
@@ -125,6 +126,7 @@ class Flagsmith {
125
126
  }
126
127
  const apiUrl = data.apiUrl || DEFAULT_API_URL;
127
128
  this.apiUrl = apiUrl.endsWith('/') ? apiUrl : `${apiUrl}/`;
129
+ this.analyticsUrl = this.analyticsUrl || new URL(analytics_js_1.ANALYTICS_ENDPOINT, new Request(this.apiUrl).url).href;
128
130
  this.environmentFlagsUrl = `${this.apiUrl}flags/`;
129
131
  this.identitiesUrl = `${this.apiUrl}identities/`;
130
132
  this.environmentUrl = `${this.apiUrl}environment-document/`;
@@ -136,14 +138,14 @@ class Flagsmith {
136
138
  this.environmentDataPollingManager.start();
137
139
  this.updateEnvironment();
138
140
  }
139
- this.analyticsProcessor = data.enableAnalytics
140
- ? new analytics_js_1.AnalyticsProcessor({
141
+ if (data.enableAnalytics) {
142
+ this.analyticsProcessor = new analytics_js_1.AnalyticsProcessor({
141
143
  environmentKey: this.environmentKey,
142
- baseApiUrl: this.apiUrl,
144
+ analyticsUrl: this.analyticsUrl,
143
145
  requestTimeoutMs: this.requestTimeoutMs,
144
- logger: this.logger
145
- })
146
- : undefined;
146
+ logger: this.logger,
147
+ });
148
+ }
147
149
  }
148
150
  }
149
151
  /**
@@ -37,7 +37,7 @@ class Flag extends BaseFlag {
37
37
  static fromAPIFlag(flagData) {
38
38
  return new Flag({
39
39
  enabled: flagData['enabled'],
40
- value: flagData['feature_state_value'] || flagData['value'],
40
+ value: flagData['feature_state_value'] ?? flagData['value'],
41
41
  featureId: flagData['feature']['id'],
42
42
  featureName: flagData['feature']['name']
43
43
  });
@@ -1,4 +1,4 @@
1
- export { AnalyticsProcessor, FlagsmithAPIError, FlagsmithClientError, EnvironmentDataPollingManager, FlagsmithCache, DefaultFlag, Flags, Flagsmith, } from './sdk/index.js';
1
+ export { AnalyticsProcessor, AnalyticsProcessorOptions, FlagsmithAPIError, FlagsmithClientError, EnvironmentDataPollingManager, FlagsmithCache, DefaultFlag, Flags, Flagsmith, } from './sdk/index.js';
2
2
  export { BaseOfflineHandler, LocalFileHandler, } from './sdk/offline_handlers.js';
3
3
  export { FlagsmithConfig } from './sdk/types.js';
4
4
  export { EnvironmentModel, FeatureStateModel, IdentityModel, TraitModel, SegmentModel, OrganisationModel } from './flagsmith-engine/index.js';
@@ -1,7 +1,32 @@
1
1
  import { Logger } from 'pino';
2
2
  import { Fetch } from "./types.js";
3
+ export declare const ANALYTICS_ENDPOINT = "./analytics/flags/";
4
+ export interface AnalyticsProcessorOptions {
5
+ /** URL of the Flagsmith analytics events API endpoint
6
+ * @example https://flagsmith.example.com/api/v1/analytics
7
+ */
8
+ analyticsUrl?: string;
9
+ /** Client-side key of the environment that analytics will be recorded for. **/
10
+ environmentKey: string;
11
+ /** Duration in milliseconds to wait for API requests to complete before timing out. Defaults to {@link DEFAULT_REQUEST_TIMEOUT_MS}. **/
12
+ requestTimeoutMs?: number;
13
+ logger?: Logger;
14
+ /** Custom {@link fetch} implementation to use for API requests. **/
15
+ fetch?: Fetch;
16
+ /** @deprecated Use {@link analyticsUrl} instead. **/
17
+ baseApiUrl?: string;
18
+ }
19
+ /**
20
+ * Tracks how often individual features are evaluated whenever {@link trackFeature} is called.
21
+ *
22
+ * Analytics data is posted after {@link trackFeature} is called and at least {@link ANALYTICS_TIMER} seconds have
23
+ * passed since the previous analytics API request was made (if any), or by calling {@link flush}.
24
+ *
25
+ * Data will stay in memory indefinitely until it can be successfully posted to the API.
26
+ * @see https://docs.flagsmith.com/advanced-use/flag-analytics.
27
+ */
3
28
  export declare class AnalyticsProcessor {
4
- private analyticsEndpoint;
29
+ private analyticsUrl;
5
30
  private environmentKey;
6
31
  private lastFlushed;
7
32
  analyticsData: {
@@ -11,25 +36,15 @@ export declare class AnalyticsProcessor {
11
36
  private logger;
12
37
  private currentFlush;
13
38
  private customFetch;
39
+ constructor(data: AnalyticsProcessorOptions);
14
40
  /**
15
- * AnalyticsProcessor is used to track how often individual Flags are evaluated within
16
- * the Flagsmith SDK. Docs: https://docs.flagsmith.com/advanced-use/flag-analytics.
17
- *
18
- * @param data.environmentKey environment key obtained from the Flagsmith UI
19
- * @param data.baseApiUrl base api url to override when using self hosted version
20
- * @param data.requestTimeoutMs used to tell requests to stop waiting for a response after a
21
- given number of milliseconds
41
+ * Try to flush pending collected data to the Flagsmith analytics API.
22
42
  */
23
- constructor(data: {
24
- environmentKey: string;
25
- baseApiUrl: string;
26
- requestTimeoutMs?: number;
27
- logger?: Logger;
28
- fetch?: Fetch;
29
- });
43
+ flush(): Promise<void>;
30
44
  /**
31
- * Sends all the collected data to the api asynchronously and resets the timer
45
+ * Track a single evaluation event for a feature.
46
+ *
47
+ * This method is called whenever {@link Flags.isFeatureEnabled}, {@link Flags.getFeatureValue} or {@link Flags.getFlag} are called.
32
48
  */
33
- flush(): Promise<void>;
34
49
  trackFeature(featureName: string): void;
35
50
  }
@@ -1,27 +1,28 @@
1
1
  import { pino } from 'pino';
2
- const ANALYTICS_ENDPOINT = 'analytics/flags/';
3
- // Used to control how often we send data(in seconds)
2
+ export const ANALYTICS_ENDPOINT = './analytics/flags/';
3
+ /** Duration in seconds to wait before trying to flush collected data after {@link trackFeature} is called. **/
4
4
  const ANALYTICS_TIMER = 10;
5
+ const DEFAULT_REQUEST_TIMEOUT_MS = 3000;
6
+ /**
7
+ * Tracks how often individual features are evaluated whenever {@link trackFeature} is called.
8
+ *
9
+ * Analytics data is posted after {@link trackFeature} is called and at least {@link ANALYTICS_TIMER} seconds have
10
+ * passed since the previous analytics API request was made (if any), or by calling {@link flush}.
11
+ *
12
+ * Data will stay in memory indefinitely until it can be successfully posted to the API.
13
+ * @see https://docs.flagsmith.com/advanced-use/flag-analytics.
14
+ */
5
15
  export class AnalyticsProcessor {
6
- analyticsEndpoint;
16
+ analyticsUrl;
7
17
  environmentKey;
8
18
  lastFlushed;
9
19
  analyticsData;
10
- requestTimeoutMs = 3000;
20
+ requestTimeoutMs = DEFAULT_REQUEST_TIMEOUT_MS;
11
21
  logger;
12
22
  currentFlush;
13
23
  customFetch;
14
- /**
15
- * AnalyticsProcessor is used to track how often individual Flags are evaluated within
16
- * the Flagsmith SDK. Docs: https://docs.flagsmith.com/advanced-use/flag-analytics.
17
- *
18
- * @param data.environmentKey environment key obtained from the Flagsmith UI
19
- * @param data.baseApiUrl base api url to override when using self hosted version
20
- * @param data.requestTimeoutMs used to tell requests to stop waiting for a response after a
21
- given number of milliseconds
22
- */
23
24
  constructor(data) {
24
- this.analyticsEndpoint = data.baseApiUrl + ANALYTICS_ENDPOINT;
25
+ this.analyticsUrl = data.analyticsUrl || data.baseApiUrl + ANALYTICS_ENDPOINT;
25
26
  this.environmentKey = data.environmentKey;
26
27
  this.lastFlushed = Date.now();
27
28
  this.analyticsData = {};
@@ -30,14 +31,14 @@ export class AnalyticsProcessor {
30
31
  this.customFetch = data.fetch ?? fetch;
31
32
  }
32
33
  /**
33
- * Sends all the collected data to the api asynchronously and resets the timer
34
+ * Try to flush pending collected data to the Flagsmith analytics API.
34
35
  */
35
36
  async flush() {
36
37
  if (this.currentFlush || !Object.keys(this.analyticsData).length) {
37
38
  return;
38
39
  }
39
40
  try {
40
- this.currentFlush = this.customFetch(this.analyticsEndpoint, {
41
+ this.currentFlush = this.customFetch(this.analyticsUrl, {
41
42
  method: 'POST',
42
43
  body: JSON.stringify(this.analyticsData),
43
44
  signal: AbortSignal.timeout(this.requestTimeoutMs),
@@ -60,6 +61,11 @@ export class AnalyticsProcessor {
60
61
  this.analyticsData = {};
61
62
  this.lastFlushed = Date.now();
62
63
  }
64
+ /**
65
+ * Track a single evaluation event for a feature.
66
+ *
67
+ * This method is called whenever {@link Flags.isFeatureEnabled}, {@link Flags.getFeatureValue} or {@link Flags.getFlag} are called.
68
+ */
63
69
  trackFeature(featureName) {
64
70
  this.analyticsData[featureName] = (this.analyticsData[featureName] || 0) + 1;
65
71
  if (Date.now() - this.lastFlushed > ANALYTICS_TIMER * 1000) {
@@ -6,7 +6,7 @@ import { DefaultFlag, Flags } from './models.js';
6
6
  import { EnvironmentDataPollingManager } from './polling_manager.js';
7
7
  import { SegmentModel } from '../flagsmith-engine/index.js';
8
8
  import { FlagsmithConfig, FlagsmithTraitValue, ITraitConfig } from './types.js';
9
- export { AnalyticsProcessor } from './analytics.js';
9
+ export { AnalyticsProcessor, AnalyticsProcessorOptions } from './analytics.js';
10
10
  export { FlagsmithAPIError, FlagsmithClientError } from './errors.js';
11
11
  export { DefaultFlag, Flags } from './models.js';
12
12
  export { EnvironmentDataPollingManager } from './polling_manager.js';
@@ -14,6 +14,7 @@ export { FlagsmithCache, FlagsmithConfig } from './types.js';
14
14
  export declare class Flagsmith {
15
15
  environmentKey?: string;
16
16
  apiUrl?: string;
17
+ analyticsUrl?: string;
17
18
  customHeaders?: {
18
19
  [key: string]: any;
19
20
  };
@@ -2,7 +2,7 @@ import { getEnvironmentFeatureStates, getIdentityFeatureStates } from '../flagsm
2
2
  import { buildEnvironmentModel } from '../flagsmith-engine/environments/util.js';
3
3
  import { IdentityModel } from '../flagsmith-engine/index.js';
4
4
  import { TraitModel } from '../flagsmith-engine/index.js';
5
- import { AnalyticsProcessor } from './analytics.js';
5
+ 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';
@@ -18,6 +18,7 @@ const DEFAULT_REQUEST_TIMEOUT_SECONDS = 10;
18
18
  export class Flagsmith {
19
19
  environmentKey = undefined;
20
20
  apiUrl = undefined;
21
+ analyticsUrl = undefined;
21
22
  customHeaders;
22
23
  agent;
23
24
  requestTimeoutMs;
@@ -116,6 +117,7 @@ export class Flagsmith {
116
117
  }
117
118
  const apiUrl = data.apiUrl || DEFAULT_API_URL;
118
119
  this.apiUrl = apiUrl.endsWith('/') ? apiUrl : `${apiUrl}/`;
120
+ this.analyticsUrl = this.analyticsUrl || new URL(ANALYTICS_ENDPOINT, new Request(this.apiUrl).url).href;
119
121
  this.environmentFlagsUrl = `${this.apiUrl}flags/`;
120
122
  this.identitiesUrl = `${this.apiUrl}identities/`;
121
123
  this.environmentUrl = `${this.apiUrl}environment-document/`;
@@ -127,14 +129,14 @@ export class Flagsmith {
127
129
  this.environmentDataPollingManager.start();
128
130
  this.updateEnvironment();
129
131
  }
130
- this.analyticsProcessor = data.enableAnalytics
131
- ? new AnalyticsProcessor({
132
+ if (data.enableAnalytics) {
133
+ this.analyticsProcessor = new AnalyticsProcessor({
132
134
  environmentKey: this.environmentKey,
133
- baseApiUrl: this.apiUrl,
135
+ analyticsUrl: this.analyticsUrl,
134
136
  requestTimeoutMs: this.requestTimeoutMs,
135
- logger: this.logger
136
- })
137
- : undefined;
137
+ logger: this.logger,
138
+ });
139
+ }
138
140
  }
139
141
  }
140
142
  /**
@@ -32,7 +32,7 @@ export class Flag extends BaseFlag {
32
32
  static fromAPIFlag(flagData) {
33
33
  return new Flag({
34
34
  enabled: flagData['enabled'],
35
- value: flagData['feature_state_value'] || flagData['value'],
35
+ value: flagData['feature_state_value'] ?? flagData['value'],
36
36
  featureId: flagData['feature']['id'],
37
37
  featureName: flagData['feature']['name']
38
38
  });
package/index.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  export {
2
2
  AnalyticsProcessor,
3
+ AnalyticsProcessorOptions,
3
4
  FlagsmithAPIError,
4
5
  FlagsmithClientError,
5
6
  EnvironmentDataPollingManager,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "flagsmith-nodejs",
3
- "version": "5.0.0",
3
+ "version": "5.1.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,32 +1,52 @@
1
1
  import { pino, Logger } from 'pino';
2
2
  import { Fetch } from "./types.js";
3
+ import { Flags } from "./models.js";
3
4
 
4
- const ANALYTICS_ENDPOINT = 'analytics/flags/';
5
+ export const ANALYTICS_ENDPOINT = './analytics/flags/';
5
6
 
6
- // Used to control how often we send data(in seconds)
7
+ /** Duration in seconds to wait before trying to flush collected data after {@link trackFeature} is called. **/
7
8
  const ANALYTICS_TIMER = 10;
8
9
 
10
+ const DEFAULT_REQUEST_TIMEOUT_MS = 3000
11
+
12
+ export interface AnalyticsProcessorOptions {
13
+ /** URL of the Flagsmith analytics events API endpoint
14
+ * @example https://flagsmith.example.com/api/v1/analytics
15
+ */
16
+ analyticsUrl?: string;
17
+ /** Client-side key of the environment that analytics will be recorded for. **/
18
+ environmentKey: string;
19
+ /** Duration in milliseconds to wait for API requests to complete before timing out. Defaults to {@link DEFAULT_REQUEST_TIMEOUT_MS}. **/
20
+ requestTimeoutMs?: number;
21
+ logger?: Logger;
22
+ /** Custom {@link fetch} implementation to use for API requests. **/
23
+ fetch?: Fetch
24
+
25
+ /** @deprecated Use {@link analyticsUrl} instead. **/
26
+ baseApiUrl?: string;
27
+ }
28
+
29
+ /**
30
+ * Tracks how often individual features are evaluated whenever {@link trackFeature} is called.
31
+ *
32
+ * Analytics data is posted after {@link trackFeature} is called and at least {@link ANALYTICS_TIMER} seconds have
33
+ * passed since the previous analytics API request was made (if any), or by calling {@link flush}.
34
+ *
35
+ * Data will stay in memory indefinitely until it can be successfully posted to the API.
36
+ * @see https://docs.flagsmith.com/advanced-use/flag-analytics.
37
+ */
9
38
  export class AnalyticsProcessor {
10
- private analyticsEndpoint: string;
39
+ private analyticsUrl: string;
11
40
  private environmentKey: string;
12
41
  private lastFlushed: number;
13
42
  analyticsData: { [key: string]: any };
14
- private requestTimeoutMs: number = 3000;
43
+ private requestTimeoutMs: number = DEFAULT_REQUEST_TIMEOUT_MS;
15
44
  private logger: Logger;
16
45
  private currentFlush: ReturnType<typeof fetch> | undefined;
17
46
  private customFetch: Fetch;
18
47
 
19
- /**
20
- * AnalyticsProcessor is used to track how often individual Flags are evaluated within
21
- * the Flagsmith SDK. Docs: https://docs.flagsmith.com/advanced-use/flag-analytics.
22
- *
23
- * @param data.environmentKey environment key obtained from the Flagsmith UI
24
- * @param data.baseApiUrl base api url to override when using self hosted version
25
- * @param data.requestTimeoutMs used to tell requests to stop waiting for a response after a
26
- given number of milliseconds
27
- */
28
- constructor(data: { environmentKey: string; baseApiUrl: string; requestTimeoutMs?: number, logger?: Logger, fetch?: Fetch }) {
29
- this.analyticsEndpoint = data.baseApiUrl + ANALYTICS_ENDPOINT;
48
+ constructor(data: AnalyticsProcessorOptions) {
49
+ this.analyticsUrl = data.analyticsUrl || data.baseApiUrl + ANALYTICS_ENDPOINT;
30
50
  this.environmentKey = data.environmentKey;
31
51
  this.lastFlushed = Date.now();
32
52
  this.analyticsData = {};
@@ -35,7 +55,7 @@ export class AnalyticsProcessor {
35
55
  this.customFetch = data.fetch ?? fetch;
36
56
  }
37
57
  /**
38
- * Sends all the collected data to the api asynchronously and resets the timer
58
+ * Try to flush pending collected data to the Flagsmith analytics API.
39
59
  */
40
60
  async flush() {
41
61
  if (this.currentFlush || !Object.keys(this.analyticsData).length) {
@@ -43,7 +63,7 @@ export class AnalyticsProcessor {
43
63
  }
44
64
 
45
65
  try {
46
- this.currentFlush = this.customFetch(this.analyticsEndpoint, {
66
+ this.currentFlush = this.customFetch(this.analyticsUrl, {
47
67
  method: 'POST',
48
68
  body: JSON.stringify(this.analyticsData),
49
69
  signal: AbortSignal.timeout(this.requestTimeoutMs),
@@ -66,6 +86,11 @@ export class AnalyticsProcessor {
66
86
  this.lastFlushed = Date.now();
67
87
  }
68
88
 
89
+ /**
90
+ * Track a single evaluation event for a feature.
91
+ *
92
+ * This method is called whenever {@link Flags.isFeatureEnabled}, {@link Flags.getFeatureValue} or {@link Flags.getFlag} are called.
93
+ */
69
94
  trackFeature(featureName: string) {
70
95
  this.analyticsData[featureName] = (this.analyticsData[featureName] || 0) + 1;
71
96
  if (Date.now() - this.lastFlushed > ANALYTICS_TIMER * 1000) {
package/sdk/index.ts CHANGED
@@ -5,7 +5,7 @@ import { buildEnvironmentModel } from '../flagsmith-engine/environments/util.js'
5
5
  import { IdentityModel } from '../flagsmith-engine/index.js';
6
6
  import { TraitModel } from '../flagsmith-engine/index.js';
7
7
 
8
- import { AnalyticsProcessor } from './analytics.js';
8
+ import {ANALYTICS_ENDPOINT, AnalyticsProcessor} from './analytics.js';
9
9
  import { BaseOfflineHandler } from './offline_handlers.js';
10
10
  import { FlagsmithAPIError, FlagsmithClientError } from './errors.js';
11
11
 
@@ -17,7 +17,7 @@ import { getIdentitySegments } from '../flagsmith-engine/segments/evaluators.js'
17
17
  import { Fetch, FlagsmithCache, FlagsmithConfig, FlagsmithTraitValue, ITraitConfig } from './types.js';
18
18
  import { pino, Logger } from 'pino';
19
19
 
20
- export { AnalyticsProcessor } from './analytics.js';
20
+ export { AnalyticsProcessor, AnalyticsProcessorOptions } from './analytics.js';
21
21
  export { FlagsmithAPIError, FlagsmithClientError } from './errors.js';
22
22
 
23
23
  export { DefaultFlag, Flags } from './models.js';
@@ -30,6 +30,7 @@ const DEFAULT_REQUEST_TIMEOUT_SECONDS = 10;
30
30
  export class Flagsmith {
31
31
  environmentKey?: string = undefined;
32
32
  apiUrl?: string = undefined;
33
+ analyticsUrl?: string = undefined;
33
34
  customHeaders?: { [key: string]: any };
34
35
  agent?: Dispatcher;
35
36
  requestTimeoutMs?: number;
@@ -138,6 +139,7 @@ export class Flagsmith {
138
139
 
139
140
  const apiUrl = data.apiUrl || DEFAULT_API_URL;
140
141
  this.apiUrl = apiUrl.endsWith('/') ? apiUrl : `${apiUrl}/`;
142
+ this.analyticsUrl = this.analyticsUrl || new URL(ANALYTICS_ENDPOINT, new Request(this.apiUrl).url).href
141
143
  this.environmentFlagsUrl = `${this.apiUrl}flags/`;
142
144
  this.identitiesUrl = `${this.apiUrl}identities/`;
143
145
  this.environmentUrl = `${this.apiUrl}environment-document/`;
@@ -156,14 +158,14 @@ export class Flagsmith {
156
158
  this.updateEnvironment();
157
159
  }
158
160
 
159
- this.analyticsProcessor = data.enableAnalytics
160
- ? new AnalyticsProcessor({
161
- environmentKey: this.environmentKey,
162
- baseApiUrl: this.apiUrl,
163
- requestTimeoutMs: this.requestTimeoutMs,
164
- logger: this.logger
165
- })
166
- : undefined;
161
+ if (data.enableAnalytics) {
162
+ this.analyticsProcessor = new AnalyticsProcessor({
163
+ environmentKey: this.environmentKey,
164
+ analyticsUrl: this.analyticsUrl,
165
+ requestTimeoutMs: this.requestTimeoutMs,
166
+ logger: this.logger,
167
+ })
168
+ }
167
169
  }
168
170
  }
169
171
  /**
package/sdk/models.ts CHANGED
@@ -54,7 +54,7 @@ export class Flag extends BaseFlag {
54
54
  static fromAPIFlag(flagData: any): Flag {
55
55
  return new Flag({
56
56
  enabled: flagData['enabled'],
57
- value: flagData['feature_state_value'] || flagData['value'],
57
+ value: flagData['feature_state_value'] ?? flagData['value'],
58
58
  featureId: flagData['feature']['id'],
59
59
  featureName: flagData['feature']['name']
60
60
  });
@@ -26,7 +26,7 @@ test('test_analytics_processor_flush_post_request_data_match_ananlytics_data', a
26
26
  aP.trackFeature("myFeature2");
27
27
  await aP.flush();
28
28
  expect(fetch).toHaveBeenCalledTimes(1);
29
- expect(fetch).toHaveBeenCalledWith('http://testUrlanalytics/flags/', expect.objectContaining({
29
+ expect(fetch).toHaveBeenCalledWith('http://testUrl/analytics/flags/', expect.objectContaining({
30
30
  body: '{"myFeature1":1,"myFeature2":1}',
31
31
  headers: { 'Content-Type': 'application/json', 'X-Environment-Key': 'test-key' },
32
32
  method: 'POST',
@@ -24,7 +24,7 @@ export const fetch = vi.fn(global.fetch)
24
24
  export function analyticsProcessor() {
25
25
  return new AnalyticsProcessor({
26
26
  environmentKey: 'test-key',
27
- baseApiUrl: 'http://testUrl',
27
+ analyticsUrl: 'http://testUrl/analytics/flags/',
28
28
  fetch,
29
29
  });
30
30
  }