@splitsoftware/splitio-commons 1.7.3-rc.0 → 1.8.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/CHANGES.txt CHANGED
@@ -1,7 +1,10 @@
1
+ 1.8.0 (February 3, 2023)
2
+ - Added flush data method to client
3
+
1
4
  1.7.3 (December 16, 2022)
2
- - Updated events and impressions cache in localhost mode in order to avoid memory leaks (Related to issue https://github.com/splitio/javascript-commons/issues/181).
3
5
  - Updated unique keys cache for Redis and Pluggable storages to optimize the usage of the underlying storage.
4
6
  - Updated some transitive dependencies for vulnerability fixes.
7
+ - Bugfixing - Updated events and impressions cache in localhost mode in order to avoid memory leaks (Related to issue https://github.com/splitio/javascript-commons/issues/181).
5
8
 
6
9
  1.7.2 (October 14, 2022)
7
10
  - Bugfixing - Handle `Navigator.sendBeacon` API exceptions in the browser, and fallback to regular Fetch/XHR transport in case of error.
package/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright © 2022 Split Software, Inc.
1
+ Copyright © 2023 Split Software, Inc.
2
2
 
3
3
  Licensed under the Apache License, Version 2.0 (the "License");
4
4
  you may not use this file except in compliance with the License.
@@ -5,11 +5,32 @@ var objectAssign_1 = require("../utils/lang/objectAssign");
5
5
  var apiKey_1 = require("../utils/inputValidation/apiKey");
6
6
  var client_1 = require("./client");
7
7
  var clientInputValidation_1 = require("./clientInputValidation");
8
+ var COOLDOWN_TIME_IN_MILLIS = 1000;
8
9
  /**
9
10
  * Creates an Sdk client, i.e., a base client with status and destroy interface
10
11
  */
11
12
  function sdkClientFactory(params, isSharedClient) {
12
13
  var sdkReadinessManager = params.sdkReadinessManager, syncManager = params.syncManager, storage = params.storage, signalListener = params.signalListener, settings = params.settings, telemetryTracker = params.telemetryTracker, uniqueKeysTracker = params.uniqueKeysTracker;
14
+ var lastActionTime = 0;
15
+ function __cooldown(func, time) {
16
+ var now = Date.now();
17
+ //get the actual time elapsed in ms
18
+ var timeElapsed = now - lastActionTime;
19
+ //check if the time elapsed is less than desired cooldown
20
+ if (timeElapsed < time) {
21
+ //if yes, return message with remaining time in seconds
22
+ settings.log.warn("Flush cooldown, remaining time " + (time - timeElapsed) / 1000 + " seconds");
23
+ return Promise.resolve();
24
+ }
25
+ else {
26
+ //Do the requested action and re-assign the lastActionTime
27
+ lastActionTime = now;
28
+ return func();
29
+ }
30
+ }
31
+ function __flush() {
32
+ return syncManager ? syncManager.flush() : Promise.resolve();
33
+ }
13
34
  return (0, objectAssign_1.objectAssign)(
14
35
  // Proto-linkage of the readiness Event Emitter
15
36
  Object.create(sdkReadinessManager.sdkStatus),
@@ -17,14 +38,17 @@ function sdkClientFactory(params, isSharedClient) {
17
38
  (0, clientInputValidation_1.clientInputValidationDecorator)(settings, (0, client_1.clientFactory)(params), sdkReadinessManager.readinessManager),
18
39
  // Sdk destroy
19
40
  {
41
+ flush: function () {
42
+ // @TODO define cooldown time
43
+ return __cooldown(__flush, COOLDOWN_TIME_IN_MILLIS);
44
+ },
20
45
  destroy: function () {
21
46
  // record stat before flushing data
22
47
  if (!isSharedClient)
23
48
  telemetryTracker.sessionLength();
24
49
  // Stop background jobs
25
50
  syncManager && syncManager.stop();
26
- var flush = syncManager ? syncManager.flush() : Promise.resolve();
27
- return flush.then(function () {
51
+ return __flush().then(function () {
28
52
  // Cleanup event listeners
29
53
  sdkReadinessManager.readinessManager.destroy();
30
54
  signalListener && signalListener.stop();
@@ -2,11 +2,32 @@ import { objectAssign } from '../utils/lang/objectAssign';
2
2
  import { releaseApiKey } from '../utils/inputValidation/apiKey';
3
3
  import { clientFactory } from './client';
4
4
  import { clientInputValidationDecorator } from './clientInputValidation';
5
+ var COOLDOWN_TIME_IN_MILLIS = 1000;
5
6
  /**
6
7
  * Creates an Sdk client, i.e., a base client with status and destroy interface
7
8
  */
8
9
  export function sdkClientFactory(params, isSharedClient) {
9
10
  var sdkReadinessManager = params.sdkReadinessManager, syncManager = params.syncManager, storage = params.storage, signalListener = params.signalListener, settings = params.settings, telemetryTracker = params.telemetryTracker, uniqueKeysTracker = params.uniqueKeysTracker;
11
+ var lastActionTime = 0;
12
+ function __cooldown(func, time) {
13
+ var now = Date.now();
14
+ //get the actual time elapsed in ms
15
+ var timeElapsed = now - lastActionTime;
16
+ //check if the time elapsed is less than desired cooldown
17
+ if (timeElapsed < time) {
18
+ //if yes, return message with remaining time in seconds
19
+ settings.log.warn("Flush cooldown, remaining time " + (time - timeElapsed) / 1000 + " seconds");
20
+ return Promise.resolve();
21
+ }
22
+ else {
23
+ //Do the requested action and re-assign the lastActionTime
24
+ lastActionTime = now;
25
+ return func();
26
+ }
27
+ }
28
+ function __flush() {
29
+ return syncManager ? syncManager.flush() : Promise.resolve();
30
+ }
10
31
  return objectAssign(
11
32
  // Proto-linkage of the readiness Event Emitter
12
33
  Object.create(sdkReadinessManager.sdkStatus),
@@ -14,14 +35,17 @@ export function sdkClientFactory(params, isSharedClient) {
14
35
  clientInputValidationDecorator(settings, clientFactory(params), sdkReadinessManager.readinessManager),
15
36
  // Sdk destroy
16
37
  {
38
+ flush: function () {
39
+ // @TODO define cooldown time
40
+ return __cooldown(__flush, COOLDOWN_TIME_IN_MILLIS);
41
+ },
17
42
  destroy: function () {
18
43
  // record stat before flushing data
19
44
  if (!isSharedClient)
20
45
  telemetryTracker.sessionLength();
21
46
  // Stop background jobs
22
47
  syncManager && syncManager.stop();
23
- var flush = syncManager ? syncManager.flush() : Promise.resolve();
24
- return flush.then(function () {
48
+ return __flush().then(function () {
25
49
  // Cleanup event listeners
26
50
  sdkReadinessManager.readinessManager.destroy();
27
51
  signalListener && signalListener.stop();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@splitsoftware/splitio-commons",
3
- "version": "1.7.3-rc.0",
3
+ "version": "1.8.0",
4
4
  "description": "Split Javascript SDK common components",
5
5
  "main": "cjs/index.js",
6
6
  "module": "esm/index.js",
@@ -5,12 +5,36 @@ import { clientFactory } from './client';
5
5
  import { clientInputValidationDecorator } from './clientInputValidation';
6
6
  import { ISdkFactoryContext } from '../sdkFactory/types';
7
7
 
8
+ const COOLDOWN_TIME_IN_MILLIS = 1000;
9
+
8
10
  /**
9
11
  * Creates an Sdk client, i.e., a base client with status and destroy interface
10
12
  */
11
13
  export function sdkClientFactory(params: ISdkFactoryContext, isSharedClient?: boolean): SplitIO.IClient | SplitIO.IAsyncClient {
12
14
  const { sdkReadinessManager, syncManager, storage, signalListener, settings, telemetryTracker, uniqueKeysTracker } = params;
13
15
 
16
+ let lastActionTime = 0;
17
+
18
+ function __cooldown(func: Function, time: number) {
19
+ const now = Date.now();
20
+ //get the actual time elapsed in ms
21
+ const timeElapsed = now - lastActionTime;
22
+ //check if the time elapsed is less than desired cooldown
23
+ if (timeElapsed < time){
24
+ //if yes, return message with remaining time in seconds
25
+ settings.log.warn(`Flush cooldown, remaining time ${(time-timeElapsed)/1000} seconds`);
26
+ return Promise.resolve();
27
+ } else {
28
+ //Do the requested action and re-assign the lastActionTime
29
+ lastActionTime = now;
30
+ return func();
31
+ }
32
+ }
33
+
34
+ function __flush() {
35
+ return syncManager ? syncManager.flush() : Promise.resolve();
36
+ }
37
+
14
38
  return objectAssign(
15
39
  // Proto-linkage of the readiness Event Emitter
16
40
  Object.create(sdkReadinessManager.sdkStatus) as IStatusInterface,
@@ -24,15 +48,18 @@ export function sdkClientFactory(params: ISdkFactoryContext, isSharedClient?: bo
24
48
 
25
49
  // Sdk destroy
26
50
  {
51
+ flush() {
52
+ // @TODO define cooldown time
53
+ return __cooldown(__flush, COOLDOWN_TIME_IN_MILLIS);
54
+ },
27
55
  destroy() {
28
56
  // record stat before flushing data
29
57
  if (!isSharedClient) telemetryTracker.sessionLength();
30
58
 
31
59
  // Stop background jobs
32
60
  syncManager && syncManager.stop();
33
- const flush = syncManager ? syncManager.flush() : Promise.resolve();
34
61
 
35
- return flush.then(() => {
62
+ return __flush().then(() => {
36
63
  // Cleanup event listeners
37
64
  sdkReadinessManager.readinessManager.destroy();
38
65
  signalListener && signalListener.stop();
package/src/types.ts CHANGED
@@ -411,6 +411,12 @@ export interface IStatusInterface extends IEventEmitter {
411
411
  * @extends IStatusInterface
412
412
  */
413
413
  interface IBasicClient extends IStatusInterface {
414
+ /**
415
+ * Flush data
416
+ * @function flush
417
+ * @return {Promise<void>}
418
+ */
419
+ flush(): Promise<void>
414
420
  /**
415
421
  * Destroy the client instance.
416
422
  * @function destroy
@@ -0,0 +1,18 @@
1
+ import { ISignalListener } from '../listeners/types';
2
+ import { ISdkReadinessManager } from '../readiness/types';
3
+ import { IStorageAsync, IStorageSync } from '../storages/types';
4
+ import { ISyncManager } from '../sync/types';
5
+ import { IEventTracker, IImpressionsTracker } from '../trackers/types';
6
+ import { ISettings } from '../types';
7
+ export interface IClientFactoryParams {
8
+ storage: IStorageSync | IStorageAsync;
9
+ sdkReadinessManager: ISdkReadinessManager;
10
+ settings: ISettings;
11
+ impressionsTracker: IImpressionsTracker;
12
+ eventTracker: IEventTracker;
13
+ }
14
+ export interface ISdkClientFactoryParams extends IClientFactoryParams {
15
+ signalListener?: ISignalListener;
16
+ syncManager?: ISyncManager;
17
+ sharedClient?: boolean;
18
+ }
@@ -0,0 +1,20 @@
1
+ import { ICountsCacheSync } from '../types';
2
+ export declare class CountsCacheInMemory implements ICountsCacheSync {
3
+ private counters;
4
+ /**
5
+ * Add counts.
6
+ */
7
+ track(metricName: string): boolean;
8
+ /**
9
+ * Clear the collector
10
+ */
11
+ clear(): void;
12
+ /**
13
+ * Get the collected data, used as payload for posting.
14
+ */
15
+ state(): Record<string, number>;
16
+ /**
17
+ * Check if the cache is empty.
18
+ */
19
+ isEmpty(): boolean;
20
+ }
@@ -0,0 +1,20 @@
1
+ import { ILatenciesCacheSync } from '../types';
2
+ export declare class LatenciesCacheInMemory implements ILatenciesCacheSync {
3
+ private counters;
4
+ /**
5
+ * Add latencies.
6
+ */
7
+ track(metricName: string, latency: number): boolean;
8
+ /**
9
+ * Clear the collector
10
+ */
11
+ clear(): void;
12
+ /**
13
+ * Get the collected data, used as payload for posting.
14
+ */
15
+ state(): Record<string, number[]>;
16
+ /**
17
+ * Check if the cache is empty.
18
+ */
19
+ isEmpty(): boolean;
20
+ }
@@ -0,0 +1,9 @@
1
+ import { ICountsCacheAsync } from '../types';
2
+ import { KeyBuilderSS } from '../KeyBuilderSS';
3
+ import { Redis } from 'ioredis';
4
+ export declare class CountsCacheInRedis implements ICountsCacheAsync {
5
+ private readonly redis;
6
+ private readonly keys;
7
+ constructor(keys: KeyBuilderSS, redis: Redis);
8
+ track(metricName: string): Promise<boolean>;
9
+ }
@@ -0,0 +1,9 @@
1
+ import { ILatenciesCacheAsync } from '../types';
2
+ import { KeyBuilderSS } from '../KeyBuilderSS';
3
+ import { Redis } from 'ioredis';
4
+ export declare class LatenciesCacheInRedis implements ILatenciesCacheAsync {
5
+ private readonly redis;
6
+ private readonly keys;
7
+ constructor(keys: KeyBuilderSS, redis: Redis);
8
+ track(metricName: string, latency: number): Promise<boolean>;
9
+ }
@@ -0,0 +1,2 @@
1
+ import { SplitIO } from '../../types';
2
+ export declare function LocalhostFromFile(): SplitIO.LocalhostFactory;
@@ -0,0 +1,2 @@
1
+ import { ISplitsParser } from './types';
2
+ export declare function splitsParserFromFileFactory(): ISplitsParser;
@@ -0,0 +1,8 @@
1
+ import { IEventsCacheSync } from '../../storages/types';
2
+ import { IPostEventsBulk } from '../../services/types';
3
+ import { ISyncTask, ITimeTracker } from '../types';
4
+ import { ILogger } from '../../logger/types';
5
+ /**
6
+ * Sync task that periodically posts tracked events
7
+ */
8
+ export declare function eventsSyncTaskFactory(log: ILogger, postEventsBulk: IPostEventsBulk, eventsCache: IEventsCacheSync, eventsPushRate: number, eventsFirstPushWindow: number, latencyTracker?: ITimeTracker): ISyncTask;
@@ -0,0 +1,5 @@
1
+ import { ISdkFactoryContextSync } from '../../sdkFactory/types';
2
+ /**
3
+ * Submitter that periodically posts impression counts
4
+ */
5
+ export declare function impressionCountsSubmitterInRedisFactory(params: ISdkFactoryContextSync): import("../types").ISyncTask<[], void>;
@@ -0,0 +1,13 @@
1
+ import { ISyncTask, ITimeTracker } from '../types';
2
+ import { IPostTestImpressionsCount } from '../../services/types';
3
+ import { IImpressionCountsCacheSync } from '../../storages/types';
4
+ import { ImpressionCountsPayload } from './types';
5
+ import { ILogger } from '../../logger/types';
6
+ /**
7
+ * Converts `impressionCounts` data from cache into request payload.
8
+ */
9
+ export declare function fromImpressionCountsCollector(impressionsCount: Record<string, number>): ImpressionCountsPayload;
10
+ /**
11
+ * Sync task that periodically posts impression counts
12
+ */
13
+ export declare function impressionCountsSyncTaskFactory(log: ILogger, postTestImpressionsCount: IPostTestImpressionsCount, impressionCountsCache: IImpressionCountsCacheSync, latencyTracker?: ITimeTracker): ISyncTask;
@@ -0,0 +1,14 @@
1
+ import { ISyncTask, ITimeTracker } from '../types';
2
+ import { IPostTestImpressionsBulk } from '../../services/types';
3
+ import { IImpressionsCacheSync } from '../../storages/types';
4
+ import { ImpressionDTO } from '../../types';
5
+ import { ImpressionsPayload } from './types';
6
+ import { ILogger } from '../../logger/types';
7
+ /**
8
+ * Converts `impressions` data from cache into request payload.
9
+ */
10
+ export declare function fromImpressionsCollector(sendLabels: boolean, data: ImpressionDTO[]): ImpressionsPayload;
11
+ /**
12
+ * Sync task that periodically posts impressions data
13
+ */
14
+ export declare function impressionsSyncTaskFactory(log: ILogger, postTestImpressionsBulk: IPostTestImpressionsBulk, impressionsCache: IImpressionsCacheSync, impressionsRefreshRate: number, sendLabels?: boolean, latencyTracker?: ITimeTracker): ISyncTask;
@@ -0,0 +1,12 @@
1
+ import { ICountsCacheSync, ILatenciesCacheSync } from '../../storages/types';
2
+ import { IPostMetricsCounters, IPostMetricsTimes } from '../../services/types';
3
+ import { ISyncTask, ITimeTracker } from '../types';
4
+ import { ILogger } from '../../logger/types';
5
+ /**
6
+ * Sync task that periodically posts telemetry counts
7
+ */
8
+ export declare function countsSyncTaskFactory(log: ILogger, postMetricsCounters: IPostMetricsCounters, countsCache: ICountsCacheSync, metricsRefreshRate: number, latencyTracker?: ITimeTracker): ISyncTask;
9
+ /**
10
+ * Sync task that periodically posts telemetry latencies
11
+ */
12
+ export declare function latenciesSyncTaskFactory(log: ILogger, postMetricsLatencies: IPostMetricsTimes, latenciesCache: ILatenciesCacheSync, metricsRefreshRate: number, latencyTracker?: ITimeTracker): ISyncTask;
@@ -0,0 +1,10 @@
1
+ import { ISyncTask, ITimeTracker } from '../types';
2
+ import { IRecorderCacheProducerSync } from '../../storages/types';
3
+ import { ILogger } from '../../logger/types';
4
+ import { IResponse } from '../../services/types';
5
+ /**
6
+ * Base function to create submitter sync tasks, such as ImpressionsSyncTask and EventsSyncTask
7
+ */
8
+ export declare function submitterSyncTaskFactory<TState extends {
9
+ length?: number;
10
+ }>(log: ILogger, postClient: (body: string) => Promise<IResponse>, sourceCache: IRecorderCacheProducerSync<TState>, postRate: number, dataName: string, latencyTracker?: ITimeTracker, fromCacheToPayload?: (cacheData: TState) => any, maxRetries?: number, debugLogs?: boolean): ISyncTask<[], void>;
@@ -0,0 +1,5 @@
1
+ import { ISdkFactoryContextSync } from '../../sdkFactory/types';
2
+ /**
3
+ * Submitter that periodically posts impression counts
4
+ */
5
+ export declare function uniqueKeysSubmitterInRedisFactory(params: ISdkFactoryContextSync): import("../types").ISyncTask<[], void>;
@@ -0,0 +1,5 @@
1
+ import { ISyncTask } from './types';
2
+ /**
3
+ * Composite Sync Task: group of sync tasks that are treated as a single one.
4
+ */
5
+ export declare function syncTaskComposite(syncTasks: ISyncTask[]): ISyncTask;
@@ -0,0 +1,10 @@
1
+ import { IFilter } from './types';
2
+ export declare class BloomFilterImp implements IFilter {
3
+ private spectedInsertions;
4
+ private errorRate;
5
+ private filter;
6
+ constructor(spectedInsertions: number, errorRate: number);
7
+ add(data: string): boolean;
8
+ contains(data: string): boolean;
9
+ clear(): void;
10
+ }
@@ -0,0 +1,8 @@
1
+ import { IFilter } from './types';
2
+ export declare class DictionaryFilter implements IFilter {
3
+ private filter;
4
+ constructor();
5
+ add(data: string): boolean;
6
+ contains(data: string): boolean;
7
+ clear(): void;
8
+ }
@@ -0,0 +1,5 @@
1
+ export interface IFilter {
2
+ add(data: string): boolean;
3
+ contains(data: string): boolean;
4
+ clear(): void;
5
+ }
package/types/types.d.ts CHANGED
@@ -405,6 +405,12 @@ export interface IStatusInterface extends IEventEmitter {
405
405
  * @extends IStatusInterface
406
406
  */
407
407
  interface IBasicClient extends IStatusInterface {
408
+ /**
409
+ * Flush data
410
+ * @function flush
411
+ * @return {Promise<void>}
412
+ */
413
+ flush(): Promise<void>;
408
414
  /**
409
415
  * Destroy the client instance.
410
416
  * @function destroy
@@ -0,0 +1,70 @@
1
+ import { ILogger } from '../../logger/types';
2
+ import { IResponse } from '../../services/types';
3
+ interface MetricsCollector {
4
+ countException(): void;
5
+ count(status: number): void;
6
+ latency(ms: number): void;
7
+ ready(ms: number): void;
8
+ getTreatment(ms: number): void;
9
+ getTreatments(ms: number): void;
10
+ getTreatmentWithConfig(ms: number): void;
11
+ getTreatmentsWithConfig(ms: number): void;
12
+ [method: string]: (ms: number) => void;
13
+ }
14
+ export declare const TrackerAPI: {
15
+ /**
16
+ * "Private" method, used to attach count/countException and stop callbacks to a promise.
17
+ *
18
+ * @param {ILogger} log - Logger.
19
+ * @param {Promise} promise - The promise we want to attach the callbacks.
20
+ * @param {string} task - The name of the task.
21
+ * @param {number | string} modifier - (optional) The modifier for the task, if any.
22
+ */
23
+ __attachToPromise(log: ILogger, promise: Promise<IResponse>, task: string, collector: false | MetricsCollector, modifier?: string | number | undefined): Promise<IResponse>;
24
+ /**
25
+ * Starts tracking the time for a given task. All tasks tracked are considered "unique" because
26
+ * there may be multiple SDK instances tracking a "generic" task, making any task non-generic.
27
+ *
28
+ * @param {ILogger} log - Logger.
29
+ * @param {string} task - The task we are starting.
30
+ * @param {Object} collectors - The collectors map.
31
+ * @param {Promise} promise - (optional) The promise we are tracking.
32
+ * @return {Function | Promise} The stop function for this specific task or the promise received with the callbacks registered.
33
+ */
34
+ start(log: ILogger, task: string, collectors?: Record<string, MetricsCollector> | undefined, promise?: Promise<IResponse> | undefined, now?: (() => number) | undefined): Promise<IResponse> | (() => number);
35
+ /**
36
+ * Setup the collector for a task that reports metrics.
37
+ *
38
+ * @param {string} task - The task name
39
+ * @param {number | string} taskUniqueId - The unique identifier for this task
40
+ * @param {Object} collectors - The collectors map.
41
+ */
42
+ setCollectorForTask(task: string, taskUniqueId: number | string, collectors: Record<string, MetricsCollector>): void;
43
+ /**
44
+ * Stops the tracking of a given task.
45
+ *
46
+ * @param {ILogger} log - Logger.
47
+ * @param {string} task - The task we are starting.
48
+ * @param {number | string} modifier - (optional) The modifier for that specific task.
49
+ */
50
+ stop(log: ILogger, task: string, modifier?: string | number | undefined): number | undefined;
51
+ /**
52
+ * The constants shortcut for the task names.
53
+ */
54
+ TaskNames: {
55
+ SDK_READY: string;
56
+ SDK_GET_TREATMENT: string;
57
+ SDK_GET_TREATMENTS: string;
58
+ SDK_GET_TREATMENT_WITH_CONFIG: string;
59
+ SDK_GET_TREATMENTS_WITH_CONFIG: string;
60
+ SPLITS_READY: string;
61
+ SEGMENTS_READY: string;
62
+ METRICS_PUSH: string;
63
+ IMPRESSIONS_PUSH: string;
64
+ EVENTS_PUSH: string;
65
+ MY_SEGMENTS_FETCH: string;
66
+ SEGMENTS_FETCH: string;
67
+ SPLITS_FETCH: string;
68
+ };
69
+ };
70
+ export {};