diff --git a/package-lock.json b/package-lock.json index 9456d98a..4dc54c8b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@splitsoftware/splitio-commons", - "version": "1.2.1-rc.0", + "version": "1.2.1-rc.3", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -373,6 +373,15 @@ "@babel/helper-plugin-utils": "^7.14.5" } }, + "@babel/runtime": { + "version": "7.17.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.17.2.tgz", + "integrity": "sha512-hzeyJyMA1YGdJTuWU0e/j4wKXrU4OMFvY2MSlaI9B7VQb0r5cxTE3EAIS2Q7Tn2RIcDkRvTA/v2JsAEhxe99uw==", + "dev": true, + "requires": { + "regenerator-runtime": "^0.13.4" + } + }, "@babel/template": { "version": "7.15.4", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.15.4.tgz", @@ -1061,6 +1070,15 @@ "@types/node": "*" } }, + "@types/ip": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@types/ip/-/ip-1.1.0.tgz", + "integrity": "sha512-dwNe8gOoF70VdL6WJBwVHtQmAX4RMd62M+mAB9HQFjG1/qiCLM/meRy95Pd14FYBbEDwCq7jgJs89cHpLBu4HQ==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/istanbul-lib-coverage": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz", @@ -1657,30 +1675,6 @@ "babel-preset-current-node-syntax": "^1.0.0" } }, - "babel-runtime": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", - "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", - "dev": true, - "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" - }, - "dependencies": { - "core-js": { - "version": "2.6.11", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.11.tgz", - "integrity": "sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg==", - "dev": true - }, - "regenerator-runtime": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", - "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", - "dev": true - } - } - }, "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", @@ -2793,12 +2787,13 @@ } }, "fetch-mock": { - "version": "9.10.7", - "resolved": "https://registry.npmjs.org/fetch-mock/-/fetch-mock-9.10.7.tgz", - "integrity": "sha512-YkiMHSL8CQ0vlWYpqGvlaZjViFk0Kar9jonPjSvaWoztkeHH6DENqUzBIsffzjVKhwchPI74SZRLRpIsEyNcZQ==", + "version": "9.11.0", + "resolved": "https://registry.npmjs.org/fetch-mock/-/fetch-mock-9.11.0.tgz", + "integrity": "sha512-PG1XUv+x7iag5p/iNHD4/jdpxL9FtVSqRMUQhPab4hVDt80T1MH5ehzVrL2IdXO9Q2iBggArFvPqjUbHFuI58Q==", "dev": true, "requires": { - "babel-runtime": "^6.26.0", + "@babel/core": "^7.0.0", + "@babel/runtime": "^7.0.0", "core-js": "^3.0.0", "debug": "^4.1.1", "glob-to-regexp": "^0.4.0", @@ -2809,21 +2804,6 @@ "whatwg-url": "^6.5.0" }, "dependencies": { - "debug": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", - "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, "tr46": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", @@ -5341,9 +5321,9 @@ "dev": true }, "querystring": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", - "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.1.tgz", + "integrity": "sha512-wkvS7mL/JMugcup3/rMitHmd9ecIGd2lhFhK9N3UUQ450h66d1r3Y9nvXzQAW1Lq+wyx61k/1pfKS5KuKiyEbg==", "dev": true }, "react-is": { @@ -5397,6 +5377,12 @@ "promise-queue": "^2.2.5" } }, + "regenerator-runtime": { + "version": "0.13.9", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", + "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==", + "dev": true + }, "regexpp": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz", @@ -5939,9 +5925,9 @@ } }, "tslib": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.1.0.tgz", - "integrity": "sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==" + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" }, "tsutils": { "version": "3.17.1", @@ -5991,9 +5977,9 @@ } }, "typescript": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.0.2.tgz", - "integrity": "sha512-e4ERvRV2wb+rRZ/IQeb3jm2VxBsirQLpQhdxplZ2MEzGvDkkMmPglecnNDfSUBivMjP93vRbngYYDQqQ/78bcQ==", + "version": "4.4.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.4.4.tgz", + "integrity": "sha512-DqGhF5IKoBl8WNf8C1gu8q0xZSInh9j1kJJMqT3a94w1JzVaBU4EXOSMrz9yDqMT0xt3selp83fuFMQ0uzv6qA==", "dev": true }, "unbox-primitive": { diff --git a/package.json b/package.json index 012f2ef0..6b962dff 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@splitsoftware/splitio-commons", - "version": "1.2.1-rc.0", + "version": "1.2.1-rc.3", "description": "Split Javascript SDK common components", "main": "cjs/index.js", "module": "esm/index.js", @@ -20,8 +20,8 @@ "check:lint": "eslint src --ext .js,.ts", "check:types": "tsc --noEmit", "build": "npm run build:cjs && npm run build:esm", - "build:esm": "rimraf esm && tsc -m es2015 --outDir esm -d true --declarationDir types --importHelpers", - "build:cjs": "rimraf cjs && tsc -m CommonJS --outDir cjs --importHelpers", + "build:esm": "rimraf esm && tsc -m es2015 --outDir esm -d true --declarationDir types", + "build:cjs": "rimraf cjs && tsc -m CommonJS --outDir cjs", "test": "jest", "test:coverage": "jest --coverage", "publish:rc": "npm run check && npm run test && npm run build && npm publish --tag rc", @@ -44,11 +44,12 @@ "bugs": "https://github.com/splitio/javascript-commons/issues", "homepage": "https://github.com/splitio/javascript-commons#readme", "dependencies": { - "tslib": "^2.1.0" + "tslib": "^2.3.1" }, "devDependencies": { "@types/google.analytics": "0.0.40", "@types/ioredis": "^4.28.0", + "@types/ip": "^1.1.0", "@types/jest": "^27.0.0", "@types/lodash": "^4.14.162", "@typescript-eslint/eslint-plugin": "^4.2.0", @@ -58,7 +59,7 @@ "eslint": "^7.32.0", "eslint-plugin-compat": "3.7.0", "eslint-plugin-import": "^2.25.3", - "fetch-mock": "^9.10.7", + "fetch-mock": "^9.11.0", "ioredis": "^4.28.0", "jest": "^27.2.3", "jest-localstorage-mock": "^2.4.3", @@ -68,7 +69,7 @@ "redis-server": "1.2.2", "rimraf": "^3.0.2", "ts-jest": "^27.0.5", - "typescript": "^4.0.2" + "typescript": "4.4.4" }, "sideEffects": false } diff --git a/src/__tests__/testUtils/fetchMock.ts b/src/__tests__/testUtils/fetchMock.ts index 42a0292e..94a614f7 100644 --- a/src/__tests__/testUtils/fetchMock.ts +++ b/src/__tests__/testUtils/fetchMock.ts @@ -1,6 +1,7 @@ -import { sandbox } from 'fetch-mock'; +// http://www.wheresrhys.co.uk/fetch-mock/#usageinstallation +import fetchMockLib from 'fetch-mock'; -const fetchMock = sandbox(); +const fetchMock = fetchMockLib.sandbox(); // config the fetch mock to chain routes (appends the new route to the list of routes) fetchMock.config.overwriteRoutes = false; diff --git a/src/integrations/ga/GoogleAnalyticsToSplit.ts b/src/integrations/ga/GoogleAnalyticsToSplit.ts index 940447aa..95923713 100644 --- a/src/integrations/ga/GoogleAnalyticsToSplit.ts +++ b/src/integrations/ga/GoogleAnalyticsToSplit.ts @@ -1,11 +1,14 @@ -import { IIntegrationFactoryParams } from '../types'; +import { IIntegrationFactoryParams, IntegrationFactory } from '../types'; import { GaToSplit } from './GaToSplit'; import { GoogleAnalyticsToSplitOptions } from './types'; -export function GoogleAnalyticsToSplit(options: GoogleAnalyticsToSplitOptions) { +export function GoogleAnalyticsToSplit(options: GoogleAnalyticsToSplitOptions): IntegrationFactory { // GaToSplit integration factory - return (params: IIntegrationFactoryParams) => { + function GoogleAnalyticsToSplitFactory(params: IIntegrationFactoryParams) { return GaToSplit(options, params); - }; + } + + GoogleAnalyticsToSplitFactory.type = 'GOOGLE_ANALYTICS_TO_SPLIT'; + return GoogleAnalyticsToSplitFactory; } diff --git a/src/integrations/ga/SplitToGoogleAnalytics.ts b/src/integrations/ga/SplitToGoogleAnalytics.ts index 4ec71177..101df26f 100644 --- a/src/integrations/ga/SplitToGoogleAnalytics.ts +++ b/src/integrations/ga/SplitToGoogleAnalytics.ts @@ -1,11 +1,14 @@ -import { IIntegrationFactoryParams } from '../types'; +import { IIntegrationFactoryParams, IntegrationFactory } from '../types'; import { SplitToGa } from './SplitToGa'; import { SplitToGoogleAnalyticsOptions } from './types'; -export function SplitToGoogleAnalytics(options: SplitToGoogleAnalyticsOptions = {}) { +export function SplitToGoogleAnalytics(options: SplitToGoogleAnalyticsOptions = {}): IntegrationFactory { // SplitToGa integration factory - return (params: IIntegrationFactoryParams) => { + function SplitToGoogleAnalyticsFactory(params: IIntegrationFactoryParams) { return new SplitToGa(params.settings.log, options); - }; + } + + SplitToGoogleAnalyticsFactory.type = 'SPLIT_TO_GOOGLE_ANALYTICS'; + return SplitToGoogleAnalyticsFactory; } diff --git a/src/integrations/types.ts b/src/integrations/types.ts index 02e868f1..0c050879 100644 --- a/src/integrations/types.ts +++ b/src/integrations/types.ts @@ -12,3 +12,8 @@ export interface IIntegrationFactoryParams { storage: { events: IEventsCacheBase } settings: ISettings } + +export type IntegrationFactory = { + readonly type: string + (params: IIntegrationFactoryParams): IIntegration | void +} diff --git a/src/logger/messages/error.ts b/src/logger/messages/error.ts index fee6c423..1d0fe00e 100644 --- a/src/logger/messages/error.ts +++ b/src/logger/messages/error.ts @@ -13,7 +13,7 @@ export const codesError: [number, string][] = [ [c.ERROR_SYNC_OFFLINE_LOADING, c.LOG_PREFIX_SYNC_OFFLINE + 'There was an issue loading the mock Splits data, no changes will be applied to the current cache. %s'], [c.ERROR_STREAMING_SSE, c.LOG_PREFIX_SYNC_STREAMING + 'Failed to connect or error on streaming connection, with error message: %s'], [c.ERROR_STREAMING_AUTH, c.LOG_PREFIX_SYNC_STREAMING + 'Failed to authenticate for streaming. Error: %s.'], - [c.ERROR_HTTP, ' Response status is not OK. Status: %s. URL: %s. Message: %s'], + [c.ERROR_HTTP, 'Response status is not OK. Status: %s. URL: %s. Message: %s'], // client status [c.ERROR_CLIENT_LISTENER, 'A listener was added for %s on the SDK, which has already fired and won\'t be emitted again. The callback won\'t be executed.'], [c.ERROR_CLIENT_DESTROYED, '%s: Client has already been destroyed - no calls possible.'], diff --git a/src/logger/messages/info.ts b/src/logger/messages/info.ts index 298711bd..bf30a97c 100644 --- a/src/logger/messages/info.ts +++ b/src/logger/messages/info.ts @@ -29,5 +29,5 @@ export const codesInfo: [number, string][] = codesWarn.concat([ [c.STREAMING_DISCONNECTING, c.LOG_PREFIX_SYNC_STREAMING + 'Disconnecting streaming.'], [c.SYNC_START_POLLING, c.LOG_PREFIX_SYNC_MANAGER + 'Streaming not available. Starting polling.'], [c.SYNC_CONTINUE_POLLING, c.LOG_PREFIX_SYNC_MANAGER + 'Streaming couldn\'t connect. Continue polling.'], - [c.SYNC_STOP_POLLING, c.LOG_PREFIX_SYNC_MANAGER + 'Streaming (re)connected. Syncing and stopping polling.'], + [c.SYNC_STOP_POLLING, c.LOG_PREFIX_SYNC_MANAGER + 'Streaming connected. Syncing and stopping polling.'], ]); diff --git a/src/logger/types.ts b/src/logger/types.ts index 05043be4..79ec1b07 100644 --- a/src/logger/types.ts +++ b/src/logger/types.ts @@ -9,11 +9,15 @@ export interface ILoggerOptions { export interface ILogger { setLogLevel(logLevel: LogLevel): void + debug(msg: any): void debug(msg: string | number, args?: any[]): void + info(msg: any): void info(msg: string | number, args?: any[]): void + warn(msg: any): void warn(msg: string | number, args?: any[]): void + error(msg: any): void error(msg: string | number, args?: any[]): void } diff --git a/src/sdkClient/__tests__/clientAttributesDecoration.spec.ts b/src/sdkClient/__tests__/clientAttributesDecoration.spec.ts index bb6c0df7..f5c49f96 100644 --- a/src/sdkClient/__tests__/clientAttributesDecoration.spec.ts +++ b/src/sdkClient/__tests__/clientAttributesDecoration.spec.ts @@ -32,10 +32,12 @@ test('ATTRIBUTES DECORATION / storage', () => { expect(client.getAttribute('attributeName1')).toEqual(undefined); // It should throw undefined expect(client.getAttribute('attributeName2')).toEqual('newAttributeValue2'); // It should be equal - client.setAttributes({ + expect(client.setAttributes({ 'attributeName3': 'attributeValue3', 'attributeName4': 'attributeValue4' - }); + })).toEqual(true); // @ts-ignore + expect(client.setAttributes(undefined)).toEqual(false); // @ts-ignore + expect(client.setAttributes(null)).toEqual(false); expect(client.getAttributes()).toEqual({ attributeName2: 'newAttributeValue2', attributeName3: 'attributeValue3', attributeName4: 'attributeValue4' }); // It should be equal @@ -48,7 +50,11 @@ test('ATTRIBUTES DECORATION / storage', () => { describe('ATTRIBUTES DECORATION / validation', () => { - test('Should return true if it is a valid attributes map without logging any errors', () => { + beforeEach(() => { + loggerMock.mockClear(); + }); + + test('Should return true if it is a valid attributes map without logging any errors and warnings', () => { const validAttributes = { amIvalid: 'yes', 'are_you_sure': true, howMuch: 10, 'spell': ['1', '0'] }; expect(client.setAttributes(validAttributes)).toEqual(true); // It should return true if it is valid. @@ -63,6 +69,8 @@ describe('ATTRIBUTES DECORATION / validation', () => { expect(Object.keys(client.getAttributes()).length).toEqual(0); // It should be zero after clearing attributes + expect(loggerMock.error).not.toBeCalled(); // no error logs + expect(loggerMock.warn).not.toBeCalled(); // no warning logs }); test('Should return false if it is an invalid attributes map', () => { @@ -82,12 +90,17 @@ describe('ATTRIBUTES DECORATION / validation', () => { '': 'attributeValue' }; - expect(client.setAttributes(attributes)).toEqual(false); // It should be invalid if the attribute key is not a string + expect(client.setAttributes(attributes)).toEqual(false); // @ts-ignore // It should be invalid if the attribute key is not a string + expect(client.setAttributes(undefined)).toEqual(false); // @ts-ignore // It should be invalid if the attributes param is nullish. Doesn't log an error + expect(client.setAttributes(null)).toEqual(false); // @ts-ignore // It should be invalid if the attributes param is nullish. Doesn't log an error + expect(client.setAttributes('invalid')).toEqual(false); // It should be invalid if the attributes param is not an object expect(Object.keys(client.getAttributes()).length).toEqual(0); // It should be zero after trying to add an invalid attribute expect(client.clearAttributes()).toEqual(true); + expect(loggerMock.error).toBeCalledTimes(1); // error logs + expect(loggerMock.warn).toBeCalledTimes(5); // warning logs }); test('Should return true if attributes map is valid', () => { @@ -114,6 +127,8 @@ describe('ATTRIBUTES DECORATION / validation', () => { expect(client.clearAttributes()).toEqual(true); + expect(loggerMock.error).toBeCalledTimes(0); // no error logs + expect(loggerMock.warn).toBeCalledTimes(0); // no warning logs }); }); diff --git a/src/storages/types.ts b/src/storages/types.ts index 0e3a6001..42e80a06 100644 --- a/src/storages/types.ts +++ b/src/storages/types.ts @@ -443,7 +443,7 @@ export interface IStorageFactoryParams { export type StorageType = 'MEMORY' | 'LOCALSTORAGE' | 'REDIS' | 'PLUGGABLE'; export type IStorageSyncFactory = { - type: StorageType, + readonly type: StorageType, (params: IStorageFactoryParams): IStorageSync } diff --git a/src/sync/offline/splitsParser/splitsParserFromFile.ts b/src/sync/offline/splitsParser/splitsParserFromFile.ts index d65d6a55..0c376918 100644 --- a/src/sync/offline/splitsParser/splitsParserFromFile.ts +++ b/src/sync/offline/splitsParser/splitsParserFromFile.ts @@ -82,7 +82,7 @@ export function splitsParserFromFileFactory(): ISplitsParser { try { data = fs.readFileSync(filePath, 'utf-8'); } catch (e) { - log.error(e.message); + log.error(e && (e as Error).message); return {}; } diff --git a/src/sync/streaming/__tests__/pushManager.spec.ts b/src/sync/streaming/__tests__/pushManager.spec.ts index 72d8c225..fb5fc39d 100644 --- a/src/sync/streaming/__tests__/pushManager.spec.ts +++ b/src/sync/streaming/__tests__/pushManager.spec.ts @@ -65,10 +65,12 @@ describe('pushManager in client-side', () => { }, {}) as IPushManager; // calling start again has no effect (authenticates asynchronously only once) + expect(pushManager.isRunning()).toBe(false); pushManager.start(); pushManager.stop(); pushManager.start(); pushManager.start(); + expect(pushManager.isRunning()).toBe(true); // authenticates asynchronously, only once for both users const mySegmentsSyncTask = syncTaskFactory(); @@ -85,7 +87,9 @@ describe('pushManager in client-side', () => { expect(fetchAuthMock).toHaveBeenLastCalledWith([fullSettings.core.key, 'user2', 'user3', 'user4']); // pausing + expect(pushManager.isRunning()).toBe(true); pushManager.stop(); + expect(pushManager.isRunning()).toBe(false); pushManager.stop(); await new Promise(res => setTimeout(res)); expect(fetchAuthMock).toHaveBeenCalledTimes(2); @@ -150,8 +154,10 @@ describe('pushManager in server-side', () => { }, {}) as IPushManager; // calling start again has no effect (authenticates asynchronously only once) + expect(pushManager.isRunning()).toBe(false); pushManager.start(); pushManager.start(); + expect(pushManager.isRunning()).toBe(true); // @TODO pausing & resuming synchronously is not working as expected in server-side // pushManager.stop(); // pushManager.start(); @@ -160,8 +166,10 @@ describe('pushManager in server-side', () => { expect(fetchAuthMock).toHaveBeenLastCalledWith(undefined); // pausing + expect(pushManager.isRunning()).toBe(true); pushManager.stop(); pushManager.stop(); + expect(pushManager.isRunning()).toBe(false); await new Promise(res => setTimeout(res)); expect(fetchAuthMock).toHaveBeenCalledTimes(1); diff --git a/src/sync/streaming/pushManager.ts b/src/sync/streaming/pushManager.ts index fc3a6ad2..ec5ff916 100644 --- a/src/sync/streaming/pushManager.ts +++ b/src/sync/streaming/pushManager.ts @@ -71,6 +71,7 @@ export function pushManagerFactory( // It is used to halt the `connectPush` process if it was in progress. let disconnected: boolean | undefined; // flag that indicates a PUSH_NONRETRYABLE_ERROR, condition with which starting pushManager again is ignored. + // true if STREAMING_DISABLED control event, or 'pushEnabled: false', or non-recoverable SSE or Auth errors. let disabled: boolean | undefined; // `disabled` implies `disconnected === true` /** PushManager functions related to initialization */ @@ -296,12 +297,15 @@ export function pushManagerFactory( // Expose Event Emitter functionality and Event constants Object.create(pushEmitter), { - // Stop/pause push mode + // Stop/pause push mode. + // It doesn't emit events. Neither PUSH_SUBSYSTEM_DOWN to start polling. stop() { disconnectPush(); // `handleNonRetryableError` cannot be used as `stop`, because it emits PUSH_SUBSYSTEM_DOWN event, which starts polling. if (userKey) this.remove(userKey); // Necessary to properly resume streaming in client-side (e.g., RN SDK transition to foreground). }, - // Start/resume push mode + + // Start/resume push mode. + // It eventually emits PUSH_SUBSYSTEM_DOWN, that starts polling, or PUSH_SUBSYSTEM_UP, that executes a syncAll start() { // Guard condition to avoid calling `connectPush` again if the `start` method is called multiple times or if push has been disabled. if (disabled || disconnected === false) return; @@ -311,6 +315,11 @@ export function pushManagerFactory( else setTimeout(connectPush); // server-side runs in next cycle as in client-side, for consistency with client-side }, + // true/false if start or stop was called last respectively + isRunning(){ + return disconnected === false; + }, + // [Only for client-side] add(userKey: string, mySegmentsSyncTask: ISegmentsSyncTask) { const hash = hashUserKey(userKey); diff --git a/src/utils/inputValidation/__tests__/apiKey.spec.ts b/src/utils/inputValidation/__tests__/apiKey.spec.ts index 76d18f1f..e9cd1c18 100644 --- a/src/utils/inputValidation/__tests__/apiKey.spec.ts +++ b/src/utils/inputValidation/__tests__/apiKey.spec.ts @@ -8,7 +8,7 @@ const invalidKeys = [ { key: null, msg: ERROR_NULL }, { key: undefined, msg: ERROR_NULL }, { key: () => { }, msg: ERROR_INVALID }, - { key: new Promise(r => r()), msg: ERROR_INVALID }, + { key: new Promise(r => r()), msg: ERROR_INVALID }, { key: Symbol('asd'), msg: ERROR_INVALID }, { key: [], msg: ERROR_INVALID }, { key: true, msg: ERROR_INVALID }, diff --git a/src/utils/inputValidation/__tests__/key.spec.ts b/src/utils/inputValidation/__tests__/key.spec.ts index 74bdb62e..5535d6f8 100644 --- a/src/utils/inputValidation/__tests__/key.spec.ts +++ b/src/utils/inputValidation/__tests__/key.spec.ts @@ -9,7 +9,7 @@ const invalidKeys = [ { key: null, msg: ERROR_NULL }, { key: undefined, msg: ERROR_NULL }, { key: () => { }, msg: ERROR_INVALID }, - { key: new Promise(r => r()), msg: ERROR_INVALID }, + { key: new Promise(r => r()), msg: ERROR_INVALID }, { key: Symbol('asd'), msg: ERROR_INVALID }, { key: [], msg: ERROR_INVALID }, { key: true, msg: ERROR_INVALID }, diff --git a/src/utils/promise/__tests__/timeout.spec.ts b/src/utils/promise/__tests__/timeout.spec.ts index da6e4446..5ad9a548 100644 --- a/src/utils/promise/__tests__/timeout.spec.ts +++ b/src/utils/promise/__tests__/timeout.spec.ts @@ -23,7 +23,7 @@ test('Promise utils / timeout - What happens in the event of a timeout or no tim // This should be rejected after 10ms await wrapperProm; expect('Should not execute').toBeFalsy(); - } catch (error) { + } catch (error: any) { // The promise was rejected not resolved. Give it an error margin of 10ms since it's not predictable expect((Date.now() - ts) < baseTimeoutInMs + 20).toBe(true); // The timeout should have rejected the promise. expect(error.message).toMatch(/^Operation timed out because it exceeded the configured time limit of/); // The timeout should have rejected the promise with a Split Timeout Error. diff --git a/src/utils/settingsValidation/index.ts b/src/utils/settingsValidation/index.ts index e10965a0..3d535d57 100644 --- a/src/utils/settingsValidation/index.ts +++ b/src/utils/settingsValidation/index.ts @@ -78,11 +78,6 @@ const base = { localhostMode: undefined }, - runtime: { - ip: false, - hostname: false - }, - // Logger log: undefined }; @@ -139,7 +134,7 @@ export function settingsValidation(config: unknown, validationParams: ISettingsV // Current ip/hostname information // @ts-ignore, modify readonly prop - if (runtime) withDefaults.runtime = runtime(withDefaults); + withDefaults.runtime = runtime(withDefaults); // ensure a valid list of integrations. // `integrations` returns an array of valid integration items. diff --git a/src/utils/settingsValidation/runtime/browser.ts b/src/utils/settingsValidation/runtime/browser.ts new file mode 100644 index 00000000..8da6ac6a --- /dev/null +++ b/src/utils/settingsValidation/runtime/browser.ts @@ -0,0 +1,8 @@ +import { ISettings } from '../../../types'; + +export function validateRuntime(): ISettings['runtime'] { + return { + ip: false, + hostname: false + }; +} diff --git a/src/utils/settingsValidation/runtime/node.ts b/src/utils/settingsValidation/runtime/node.ts new file mode 100644 index 00000000..fa97772d --- /dev/null +++ b/src/utils/settingsValidation/runtime/node.ts @@ -0,0 +1,22 @@ +import osFunction from 'os'; +import ipFunction from 'ip'; + +import { UNKNOWN, NA, CONSUMER_MODE } from '../../constants'; +import { ISettings } from '../../../types'; + +export function validateRuntime(settings: ISettings): ISettings['runtime'] { + const isIPAddressesEnabled = settings.core.IPAddressesEnabled === true; + const isConsumerMode = settings.mode === CONSUMER_MODE; + + // If the values are not available, default to false (for standalone) or "unknown" (for consumer mode, to be used on Redis keys) + let ip = ipFunction.address() || (isConsumerMode ? UNKNOWN : false); + let hostname = osFunction.hostname() || (isConsumerMode ? UNKNOWN : false); + + if (!isIPAddressesEnabled) { // If IPAddresses setting is not enabled, set as false (for standalone) or "NA" (for consumer mode, to be used on Redis keys) + ip = hostname = isConsumerMode ? NA : false; + } + + return { + ip, hostname + }; +} diff --git a/src/utils/settingsValidation/types.ts b/src/utils/settingsValidation/types.ts index db801b12..b305d440 100644 --- a/src/utils/settingsValidation/types.ts +++ b/src/utils/settingsValidation/types.ts @@ -6,15 +6,15 @@ import { ISettings } from '../../types'; */ export interface ISettingsValidationParams { /** - * Object of values to overwrite default settings. - * Version and startup properties are mandatory, because these values are not part of the base setting. + * Object of values to overwrite base settings. + * Version and startup properties are required, because they are not defined in the base settings. */ defaults: Partial & { version: string } & { startup: ISettings['startup'] }, - /** Function to overwrite runtime values (ip and hostname) which are false by default */ - runtime?: (settings: ISettings) => ISettings['runtime'], - /** Storage validator */ + /** Function to define runtime values (`settings.runtime`) */ + runtime: (settings: ISettings) => ISettings['runtime'], + /** Storage validator (`settings.storage`) */ storage?: (settings: ISettings) => ISettings['storage'], - /** Integrations validator */ + /** Integrations validator (`settings.integrations`) */ integrations?: (settings: ISettings) => ISettings['integrations'], /** Logger validator (`settings.debug`) */ logger: (settings: ISettings) => ISettings['log'], diff --git a/tsconfig.json b/tsconfig.json index 1d719785..20433c1a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -18,7 +18,7 @@ // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ // "removeComments": true, /* Do not emit comments to output. */ // "noEmit": true, /* Do not emit outputs. */ - // "importHelpers": true, /* Import emit helpers from 'tslib'. */ + "importHelpers": true, /* Import emit helpers from 'tslib', to avoid duplicated helpers in builds. */ // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */