diff --git a/package-lock.json b/package-lock.json index 17c71d80..a5324ded 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@splitsoftware/splitio-commons", - "version": "0.1.1-rc.18", + "version": "0.1.1-rc.20", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index c8daf370..d85878ee 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@splitsoftware/splitio-commons", - "version": "0.1.1-rc.18", + "version": "0.1.1-rc.20", "description": "Split Javascript SDK common components", "main": "cjs/index.js", "module": "esm/index.js", diff --git a/src/integrations/__tests__/browser.spec.ts b/src/integrations/__tests__/browser.spec.ts index 9bab3e99..1b79d8bb 100644 --- a/src/integrations/__tests__/browser.spec.ts +++ b/src/integrations/__tests__/browser.spec.ts @@ -6,9 +6,9 @@ import { loggerMock } from '../../logger/__tests__/sdkLogger.mock'; // Mock integration modules (GaToSplit and SplitToGa). jest.mock('../ga/GaToSplit'); -import GaToSplitMock from '../ga/GaToSplit'; +import { GaToSplit as GaToSplitMock } from '../ga/GaToSplit'; jest.mock('../ga/SplitToGa'); -import SplitToGaMock from '../ga/SplitToGa'; +import { SplitToGa as SplitToGaMock } from '../ga/SplitToGa'; const SplitToGaQueueMethod = jest.fn(); (SplitToGaMock as unknown as jest.Mock).mockImplementation(() => { diff --git a/src/integrations/browser.ts b/src/integrations/browser.ts index f9cb8927..f3a17713 100644 --- a/src/integrations/browser.ts +++ b/src/integrations/browser.ts @@ -2,8 +2,8 @@ import { GOOGLE_ANALYTICS_TO_SPLIT, SPLIT_TO_GOOGLE_ANALYTICS } from '../utils/c import { IIntegration, IIntegrationManager, IIntegrationFactoryParams } from './types'; import { BrowserIntegration } from './ga/types'; import pluggableIntegrationsManagerFactory from './pluggable'; -import GaToSplitPlugin from './ga/GaToSplitPlugin'; -import SplitToGaPlugin from './ga/SplitToGaPlugin'; +import { GoogleAnalyticsToSplit } from './ga/GoogleAnalyticsToSplit'; +import { SplitToGoogleAnalytics } from './ga/SplitToGoogleAnalytics'; /** * IntegrationsManager factory for the browser variant of the isomorphic JS SDK. @@ -23,8 +23,8 @@ export default function integrationsManagerFactory( const integrationFactories: Array<(params: IIntegrationFactoryParams) => IIntegration | void> = integrations .map(integrationOptions => { switch (integrationOptions.type) { - case GOOGLE_ANALYTICS_TO_SPLIT: return GaToSplitPlugin(integrationOptions); - case SPLIT_TO_GOOGLE_ANALYTICS: return SplitToGaPlugin(integrationOptions); + case GOOGLE_ANALYTICS_TO_SPLIT: return GoogleAnalyticsToSplit(integrationOptions); + case SPLIT_TO_GOOGLE_ANALYTICS: return SplitToGoogleAnalytics(integrationOptions); } }) .filter(integrationFactory => { diff --git a/src/integrations/ga/GaToSplit.ts b/src/integrations/ga/GaToSplit.ts index befa31f1..360c93a1 100644 --- a/src/integrations/ga/GaToSplit.ts +++ b/src/integrations/ga/GaToSplit.ts @@ -182,7 +182,7 @@ export function fixEventTypeId(log: ILogger, eventTypeId: any) { * @param {object} coreSettings core settings used to define an identity if no one provided as SDK or plugin options * @param {object} log factory logger */ -export default function GaToSplit(sdkOptions: GoogleAnalyticsToSplitOptions, params: IIntegrationFactoryParams) { +export function GaToSplit(sdkOptions: GoogleAnalyticsToSplitOptions, params: IIntegrationFactoryParams) { const { storage, settings: { core: coreSettings, log } } = params; diff --git a/src/integrations/ga/GaToSplitPlugin.ts b/src/integrations/ga/GoogleAnalyticsToSplit.ts similarity index 66% rename from src/integrations/ga/GaToSplitPlugin.ts rename to src/integrations/ga/GoogleAnalyticsToSplit.ts index 04a16905..940447aa 100644 --- a/src/integrations/ga/GaToSplitPlugin.ts +++ b/src/integrations/ga/GoogleAnalyticsToSplit.ts @@ -1,8 +1,8 @@ import { IIntegrationFactoryParams } from '../types'; -import GaToSplit from './GaToSplit'; +import { GaToSplit } from './GaToSplit'; import { GoogleAnalyticsToSplitOptions } from './types'; -export default function GaToSplitPlugin(options: GoogleAnalyticsToSplitOptions) { +export function GoogleAnalyticsToSplit(options: GoogleAnalyticsToSplitOptions) { // GaToSplit integration factory return (params: IIntegrationFactoryParams) => { diff --git a/src/integrations/ga/SplitToGa.ts b/src/integrations/ga/SplitToGa.ts index df933c21..dd469676 100644 --- a/src/integrations/ga/SplitToGa.ts +++ b/src/integrations/ga/SplitToGa.ts @@ -10,7 +10,7 @@ const logPrefix = 'split-to-ga: '; const noGaWarning = '`ga` command queue not found.'; const noHit = 'No hit was sent.'; -export default class SplitToGa implements IIntegration { +export class SplitToGa implements IIntegration { // A falsy object represents the default tracker static defaultTrackerNames = ['']; diff --git a/src/integrations/ga/SplitToGaPlugin.ts b/src/integrations/ga/SplitToGoogleAnalytics.ts similarity index 67% rename from src/integrations/ga/SplitToGaPlugin.ts rename to src/integrations/ga/SplitToGoogleAnalytics.ts index 9001c180..4ec71177 100644 --- a/src/integrations/ga/SplitToGaPlugin.ts +++ b/src/integrations/ga/SplitToGoogleAnalytics.ts @@ -1,8 +1,8 @@ import { IIntegrationFactoryParams } from '../types'; -import SplitToGa from './SplitToGa'; +import { SplitToGa } from './SplitToGa'; import { SplitToGoogleAnalyticsOptions } from './types'; -export default function SplitToGaPlugin(options: SplitToGoogleAnalyticsOptions = {}) { +export function SplitToGoogleAnalytics(options: SplitToGoogleAnalyticsOptions = {}) { // SplitToGa integration factory return (params: IIntegrationFactoryParams) => { diff --git a/src/integrations/ga/__tests__/GaToSplit.spec.ts b/src/integrations/ga/__tests__/GaToSplit.spec.ts index 0a7ef567..3296fe86 100644 --- a/src/integrations/ga/__tests__/GaToSplit.spec.ts +++ b/src/integrations/ga/__tests__/GaToSplit.spec.ts @@ -1,7 +1,7 @@ /* eslint-disable no-undef */ import { IEventsCacheSync } from '../../../storages/types'; import { SplitIO, ISettings } from '../../../types'; -import GaToSplit, { validateIdentities, defaultPrefix, defaultMapper, validateEventData, fixEventTypeId } from '../GaToSplit'; +import { GaToSplit, validateIdentities, defaultPrefix, defaultMapper, validateEventData, fixEventTypeId } from '../GaToSplit'; import { gaMock, gaRemove, modelMock } from './gaMock'; import { loggerMock } from '../../../logger/__tests__/sdkLogger.mock'; diff --git a/src/integrations/ga/__tests__/SplitToGa.spec.ts b/src/integrations/ga/__tests__/SplitToGa.spec.ts index 849350ca..d05e4bab 100644 --- a/src/integrations/ga/__tests__/SplitToGa.spec.ts +++ b/src/integrations/ga/__tests__/SplitToGa.spec.ts @@ -6,7 +6,7 @@ import { loggerMock } from '../../../logger/__tests__/sdkLogger.mock'; import { gaMock, gaRemove } from './gaMock'; // Test target -import SplitToGa from '../SplitToGa'; +import { SplitToGa } from '../SplitToGa'; const fakeImpressionPayload: SplitIO.ImpressionData = { impression: { diff --git a/src/listeners/__tests__/node.spec.ts b/src/listeners/__tests__/node.spec.ts index 4e226a6b..1755fa7d 100644 --- a/src/listeners/__tests__/node.spec.ts +++ b/src/listeners/__tests__/node.spec.ts @@ -144,7 +144,7 @@ test('Node JS listener / Signal Listener SIGTERM callback with async handler tha // Call function // @ts-expect-error - const handlerPromise = listener._sigtermHandler(); + const handlerPromise: Promise = listener._sigtermHandler(); // Handler was properly called. expect(syncManagerMock.flush).toBeCalledTimes(1); @@ -154,7 +154,7 @@ test('Node JS listener / Signal Listener SIGTERM callback with async handler tha expect(processKillSpy).not.toBeCalled(); // Calling .then since the wrapUp handler does not throw. - await (handlerPromise as Promise).then(() => { + await handlerPromise.then(() => { // Clean up is called. expect(listener.stop).toBeCalledTimes(1); // It called for kill again, so the shutdown keeps going. diff --git a/src/logger/constants.ts b/src/logger/constants.ts index 79f629c2..5ca7317c 100644 --- a/src/logger/constants.ts +++ b/src/logger/constants.ts @@ -118,6 +118,7 @@ export const ERROR_EMPTY = 319; export const ERROR_EMPTY_ARRAY = 320; export const ERROR_INVALID_IMPRESSIONS_MODE = 321; export const ERROR_HTTP = 322; +export const ERROR_LOCALHOST_MODULE_REQUIRED = 323; // Log prefixes (a.k.a. tags or categories) export const LOG_PREFIX_SETTINGS = 'settings'; diff --git a/src/logger/index.ts b/src/logger/index.ts index 4738e191..ed2e5032 100644 --- a/src/logger/index.ts +++ b/src/logger/index.ts @@ -12,7 +12,7 @@ export const LogLevels: { [level: string]: LogLevel } = { NONE: 'NONE' }; -const LogLevelRanks = { +const LogLevelIndexes = { DEBUG: 1, INFO: 2, WARN: 3, @@ -47,28 +47,28 @@ export class Logger implements ILogger { constructor(options?: ILoggerOptions, codes?: IMap) { this.options = objectAssign({}, defaultOptions, options); this.codes = codes || new _Map(); - this.logLevel = LogLevelRanks[this.options.logLevel]; + this.logLevel = LogLevelIndexes[this.options.logLevel]; } setLogLevel(logLevel: LogLevel) { this.options.logLevel = logLevel; - this.logLevel = LogLevelRanks[logLevel]; + this.logLevel = LogLevelIndexes[logLevel]; } debug(msg: string | number, args?: any[]) { - if (this._shouldLog(LogLevelRanks.DEBUG)) this._log(LogLevels.DEBUG, msg, args); + if (this._shouldLog(LogLevelIndexes.DEBUG)) this._log(LogLevels.DEBUG, msg, args); } info(msg: string | number, args?: any[]) { - if (this._shouldLog(LogLevelRanks.INFO)) this._log(LogLevels.INFO, msg, args); + if (this._shouldLog(LogLevelIndexes.INFO)) this._log(LogLevels.INFO, msg, args); } warn(msg: string | number, args?: any[]) { - if (this._shouldLog(LogLevelRanks.WARN)) this._log(LogLevels.WARN, msg, args); + if (this._shouldLog(LogLevelIndexes.WARN)) this._log(LogLevels.WARN, msg, args); } error(msg: string | number, args?: any[]) { - if (this._shouldLog(LogLevelRanks.ERROR)) this._log(LogLevels.ERROR, msg, args); + if (this._shouldLog(LogLevelIndexes.ERROR)) this._log(LogLevels.ERROR, msg, args); } private _log(level: LogLevel, msg: string | number, args?: any[]) { diff --git a/src/logger/messages/error.ts b/src/logger/messages/error.ts index 9a6de5c3..75269bc9 100644 --- a/src/logger/messages/error.ts +++ b/src/logger/messages/error.ts @@ -30,4 +30,5 @@ export const codesError: [number, string][] = [ [c.ERROR_EMPTY_ARRAY, '%s: %s must be a non-empty array.'], // initialization / settings validation [c.ERROR_INVALID_IMPRESSIONS_MODE, c.LOG_PREFIX_SETTINGS + ': you passed an invalid "impressionsMode". It should be one of the following values: %s. Defaulting to "%s" mode.'], + [c.ERROR_LOCALHOST_MODULE_REQUIRED, c.LOG_PREFIX_SETTINGS + ': an invalid value was received for "sync.localhostMode" config. A valid entity should be provided for localhost mode.'] ]; diff --git a/src/sdkFactory/types.ts b/src/sdkFactory/types.ts index ce5981ce..b0d87343 100644 --- a/src/sdkFactory/types.ts +++ b/src/sdkFactory/types.ts @@ -40,7 +40,7 @@ export interface ISdkFactoryParams { splitApiFactory?: (settings: ISettings, platform: IPlatform) => ISplitApi, // SyncManager factory. - // It is not required when providing an asynchronous storage. + // Not required when providing an asynchronous storage (consumer mode), but required in standalone mode to avoid SDK timeout. // It can create an offline or online sync manager, with or without streaming support. syncManagerFactory?: (params: ISyncManagerFactoryParams) => ISyncManager, diff --git a/src/sync/offline/LocalhostFromFile.ts b/src/sync/offline/LocalhostFromFile.ts new file mode 100644 index 00000000..2caff329 --- /dev/null +++ b/src/sync/offline/LocalhostFromFile.ts @@ -0,0 +1,11 @@ +import { splitsParserFromFileFactory } from './splitsParser/splitsParserFromFile'; +import { syncManagerOfflineFactory } from './syncManagerOffline'; +import { SplitIO } from '../../types'; + +// Factory of Localhost SyncManager based on yaml file. +// Requires Node 'fs' and 'path' APIs. +export function LocalhostFromFile(): SplitIO.LocalhostFactory { + const localhost = syncManagerOfflineFactory(splitsParserFromFileFactory) as SplitIO.LocalhostFactory; + localhost.type = 'LocalhostFromFile'; + return localhost; +} diff --git a/src/sync/offline/LocalhostFromObject.ts b/src/sync/offline/LocalhostFromObject.ts new file mode 100644 index 00000000..d1510996 --- /dev/null +++ b/src/sync/offline/LocalhostFromObject.ts @@ -0,0 +1,10 @@ +import { splitsParserFromSettingsFactory } from './splitsParser/splitsParserFromSettings'; +import { syncManagerOfflineFactory } from './syncManagerOffline'; +import { SplitIO } from '../../types'; + +// Factory of Localhost SyncManager based on JS object. +export function LocalhostFromObject(): SplitIO.LocalhostFactory { + const localhost = syncManagerOfflineFactory(splitsParserFromSettingsFactory) as SplitIO.LocalhostFactory; + localhost.type = 'LocalhostFromObject'; + return localhost; +} diff --git a/src/sync/offline/splitsParser/splitsParserFromFile.ts b/src/sync/offline/splitsParser/splitsParserFromFile.ts index 62de09f4..95ea8d14 100644 --- a/src/sync/offline/splitsParser/splitsParserFromFile.ts +++ b/src/sync/offline/splitsParser/splitsParserFromFile.ts @@ -7,8 +7,9 @@ import yaml from 'js-yaml'; import { isString, endsWith, find, forOwn, uniq, } from '../../../utils/lang'; import parseCondition, { IMockSplitEntry } from './parseCondition'; import { ISplitPartial } from '../../../dtos/types'; -import { SplitIO } from '../../../types'; +import { ISettings, SplitIO } from '../../../types'; import { ILogger } from '../../../logger/types'; +import { ISplitsParser } from './types'; const logPrefix = 'sync:offline:splits-fetcher: '; @@ -68,7 +69,7 @@ function arrangeConditions(mocksData: Record & { }); } -export function splitsParserFromFileFactory() { +export function splitsParserFromFileFactory(): ISplitsParser { let previousMock = 'NO_MOCK_LOADED'; @@ -163,8 +164,8 @@ export function splitsParserFromFileFactory() { } // Load the content of a configuration file into an Object - return function splitsParserFromFile({ features, log }: { features?: SplitIO.MockedFeaturesFilePath, log: ILogger }): false | Record { - const filePath = configFilesPath(features); + return function splitsParserFromFile({ features, log }: ISettings): false | Record { + const filePath = configFilesPath(features as string); let mockData: false | Record; // If we have a filePath, it means the extension is correct, choose the parser. diff --git a/src/sync/syncManagerOffline.ts b/src/sync/offline/syncManagerOffline.ts similarity index 85% rename from src/sync/syncManagerOffline.ts rename to src/sync/offline/syncManagerOffline.ts index 2ef909ee..58a75b6f 100644 --- a/src/sync/syncManagerOffline.ts +++ b/src/sync/offline/syncManagerOffline.ts @@ -1,9 +1,9 @@ -import { ISyncManager, ISyncManagerCS, ISyncManagerFactoryParams } from './types'; -import fromObjectSyncTaskFactory from './offline/syncTasks/fromObjectSyncTask'; +import { ISyncManager, ISyncManagerCS, ISyncManagerFactoryParams } from '../types'; +import fromObjectSyncTaskFactory from './syncTasks/fromObjectSyncTask'; import objectAssign from 'object-assign'; -import { ISplitsParser } from './offline/splitsParser/types'; -import { IReadinessManager } from '../readiness/types'; -import { SDK_SEGMENTS_ARRIVED } from '../readiness/constants'; +import { ISplitsParser } from './splitsParser/types'; +import { IReadinessManager } from '../../readiness/types'; +import { SDK_SEGMENTS_ARRIVED } from '../../readiness/constants'; function flush() { return Promise.resolve(); diff --git a/src/types.ts b/src/types.ts index 9d20e84f..691c5beb 100644 --- a/src/types.ts +++ b/src/types.ts @@ -4,6 +4,7 @@ import { ILogger } from './logger/types'; /* eslint-disable no-use-before-define */ import { IStorageFactoryParams, IStorageSyncCS, IStorageSync, IStorageAsync, IStorageSyncFactory } from './storages/types'; +import { ISyncManagerFactoryParams, ISyncManagerCS } from './sync/types'; /** * EventEmitter interface with the minimal methods used by the SDK @@ -99,7 +100,8 @@ export interface ISettings { readonly sync: { splitFilters: SplitIO.SplitFilter[], impressionsMode: SplitIO.ImpressionsMode, - __splitFiltersValidation: ISplitFiltersValidation + __splitFiltersValidation: ISplitFiltersValidation, + localhostMode?: SplitIO.LocalhostFactory }, readonly runtime: { ip: string | false @@ -581,6 +583,13 @@ export namespace SplitIO { * @typedef {Promise} SplitNamesAsync */ export type SplitNamesAsync = Promise; + /** + * Localhost mode factory. + */ + export type LocalhostFactory = { + type: 'LocalhostFromObject' | 'LocalhostFromFile' + (params: ISyncManagerFactoryParams): ISyncManagerCS + } /** * Impression listener interface. This is the interface that needs to be implemented * by the element you provide to the SDK as impression listener. diff --git a/src/utils/settingsValidation/__tests__/settings.mocks.ts b/src/utils/settingsValidation/__tests__/settings.mocks.ts index 2bcb0aa2..80aacabf 100644 --- a/src/utils/settingsValidation/__tests__/settings.mocks.ts +++ b/src/utils/settingsValidation/__tests__/settings.mocks.ts @@ -1,6 +1,7 @@ import { InMemoryStorageCSFactory } from '../../../storages/inMemory/InMemoryStorageCS'; import { ISettings } from '../../../types'; import { loggerMock } from '../../../logger/__tests__/sdkLogger.mock'; +import { LocalhostFromObject } from '../../../sync/offline/LocalhostFromObject'; export const settingsWithKey = { core: { @@ -61,13 +62,14 @@ export const fullSettings: ISettings = { }, features: 'path/to/file', storage: InMemoryStorageCSFactory, - integrations: [()=>{}], // A no-op integration + integrations: [() => { }], // A no-op integration mode: 'standalone', debug: false, streamingEnabled: false, sync: { splitFilters: [], impressionsMode: 'OPTIMIZED', + localhostMode: LocalhostFromObject(), __splitFiltersValidation: { validFilters: [], queryString: null, diff --git a/src/utils/settingsValidation/index.ts b/src/utils/settingsValidation/index.ts index 96f22227..1840fdb5 100644 --- a/src/utils/settingsValidation/index.ts +++ b/src/utils/settingsValidation/index.ts @@ -74,7 +74,8 @@ const base = { sync: { splitFilters: undefined, // impressions collection mode - impressionsMode: OPTIMIZED + impressionsMode: OPTIMIZED, + localhostMode: undefined }, runtime: { @@ -99,7 +100,7 @@ function fromSecondsToMillis(n: number) { */ export function settingsValidation(config: unknown, validationParams: ISettingsValidationParams) { - const { defaults, runtime, storage, integrations, logger } = validationParams; + const { defaults, runtime, storage, integrations, logger, localhost } = validationParams; // creates a settings object merging base, defaults and config objects. const withDefaults = merge({}, base, defaults, config) as ISettings; @@ -110,17 +111,18 @@ export function settingsValidation(config: unknown, validationParams: ISettingsV withDefaults.log = log; // Scheduler periods - withDefaults.scheduler.featuresRefreshRate = fromSecondsToMillis(withDefaults.scheduler.featuresRefreshRate); - withDefaults.scheduler.segmentsRefreshRate = fromSecondsToMillis(withDefaults.scheduler.segmentsRefreshRate); - withDefaults.scheduler.metricsRefreshRate = fromSecondsToMillis(withDefaults.scheduler.metricsRefreshRate); - withDefaults.scheduler.impressionsRefreshRate = fromSecondsToMillis(withDefaults.scheduler.impressionsRefreshRate); - withDefaults.scheduler.offlineRefreshRate = fromSecondsToMillis(withDefaults.scheduler.offlineRefreshRate); - withDefaults.scheduler.eventsPushRate = fromSecondsToMillis(withDefaults.scheduler.eventsPushRate); + const { scheduler, startup } = withDefaults; + scheduler.featuresRefreshRate = fromSecondsToMillis(scheduler.featuresRefreshRate); + scheduler.segmentsRefreshRate = fromSecondsToMillis(scheduler.segmentsRefreshRate); + scheduler.metricsRefreshRate = fromSecondsToMillis(scheduler.metricsRefreshRate); + scheduler.impressionsRefreshRate = fromSecondsToMillis(scheduler.impressionsRefreshRate); + scheduler.offlineRefreshRate = fromSecondsToMillis(scheduler.offlineRefreshRate); + scheduler.eventsPushRate = fromSecondsToMillis(scheduler.eventsPushRate); // Startup periods - withDefaults.startup.requestTimeoutBeforeReady = fromSecondsToMillis(withDefaults.startup.requestTimeoutBeforeReady); - withDefaults.startup.readyTimeout = fromSecondsToMillis(withDefaults.startup.readyTimeout); - withDefaults.startup.eventsFirstPushWindow = fromSecondsToMillis(withDefaults.startup.eventsFirstPushWindow); + startup.requestTimeoutBeforeReady = fromSecondsToMillis(startup.requestTimeoutBeforeReady); + startup.readyTimeout = fromSecondsToMillis(startup.readyTimeout); + startup.eventsFirstPushWindow = fromSecondsToMillis(withDefaults.startup.eventsFirstPushWindow); // ensure a valid SDK mode // @ts-ignore, modify readonly prop @@ -144,12 +146,14 @@ export function settingsValidation(config: unknown, validationParams: ISettingsV // @ts-ignore, modify readonly prop if (integrations) withDefaults.integrations = integrations(withDefaults); + if (localhost) withDefaults.sync.localhostMode = localhost(withDefaults); + // validate push options if (withDefaults.streamingEnabled !== false) { // @ts-ignore, modify readonly prop withDefaults.streamingEnabled = true; // Backoff bases. // We are not checking if bases are positive numbers. Thus, we might be reauthenticating immediately (`setTimeout` with NaN or negative number) - withDefaults.scheduler.pushRetryBackoffBase = fromSecondsToMillis(withDefaults.scheduler.pushRetryBackoffBase); + scheduler.pushRetryBackoffBase = fromSecondsToMillis(scheduler.pushRetryBackoffBase); } // validate the `splitFilters` settings and parse splits query diff --git a/src/utils/settingsValidation/localhost/index.ts b/src/utils/settingsValidation/localhost/index.ts new file mode 100644 index 00000000..7d5f9272 --- /dev/null +++ b/src/utils/settingsValidation/localhost/index.ts @@ -0,0 +1,21 @@ +import { ERROR_LOCALHOST_MODULE_REQUIRED } from '../../../logger/constants'; +import { ISettings, } from '../../../types'; +import { LOCALHOST_MODE } from '../../constants'; + +/** + * This function validates `settings.sync.localhostMode` object + * + * @param {any} settings config object provided by the user to initialize the sdk + * + * @returns {Object | undefined} provided localhost mode module at `settings.sync.localhostMode`, or undefined if it is not provided or invalid. + */ +export function validateLocalhost(settings: ISettings) { + const localhostMode = settings.sync.localhostMode; + + // localhostMode.type is used for internal validation. Not considered part of the public API, and might be updated eventually. + if (settings.mode === LOCALHOST_MODE && (typeof localhostMode !== 'function' || localhostMode.type !== 'LocalhostFromObject')) { + settings.log.error(ERROR_LOCALHOST_MODULE_REQUIRED); + return undefined; + } + return localhostMode; +} diff --git a/src/utils/settingsValidation/types.ts b/src/utils/settingsValidation/types.ts index a3ab3761..db801b12 100644 --- a/src/utils/settingsValidation/types.ts +++ b/src/utils/settingsValidation/types.ts @@ -16,6 +16,8 @@ export interface ISettingsValidationParams { storage?: (settings: ISettings) => ISettings['storage'], /** Integrations validator */ integrations?: (settings: ISettings) => ISettings['integrations'], - /** Logger validator */ + /** Logger validator (`settings.debug`) */ logger: (settings: ISettings) => ISettings['log'], + /** Localhost mode validator (`settings.sync.localhostMode`) */ + localhost?: (settings: ISettings) => ISettings['sync']['localhostMode'], }