Skip to content
Merged
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
4 changes: 2 additions & 2 deletions src/integrations/__tests__/browser.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(() => {
Expand Down
8 changes: 4 additions & 4 deletions src/integrations/browser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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 => {
Expand Down
2 changes: 1 addition & 1 deletion src/integrations/ga/GaToSplit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
Original file line number Diff line number Diff line change
@@ -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) => {
Expand Down
2 changes: 1 addition & 1 deletion src/integrations/ga/SplitToGa.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [''];
Expand Down
Original file line number Diff line number Diff line change
@@ -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) => {
Expand Down
2 changes: 1 addition & 1 deletion src/integrations/ga/__tests__/GaToSplit.spec.ts
Original file line number Diff line number Diff line change
@@ -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';

Expand Down
2 changes: 1 addition & 1 deletion src/integrations/ga/__tests__/SplitToGa.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down
4 changes: 2 additions & 2 deletions src/listeners/__tests__/node.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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<void>).then(() => {
await handlerPromise.then(() => {
// Clean up is called.
expect(listener.stop).toBeCalledTimes(1);
// It called for kill again, so the shutdown keeps going.
Expand Down
1 change: 1 addition & 0 deletions src/logger/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
14 changes: 7 additions & 7 deletions src/logger/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export const LogLevels: { [level: string]: LogLevel } = {
NONE: 'NONE'
};

const LogLevelRanks = {
const LogLevelIndexes = {
DEBUG: 1,
INFO: 2,
WARN: 3,
Expand Down Expand Up @@ -47,28 +47,28 @@ export class Logger implements ILogger {
constructor(options?: ILoggerOptions, codes?: IMap<number, string>) {
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[]) {
Expand Down
1 change: 1 addition & 0 deletions src/logger/messages/error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.']
];
2 changes: 1 addition & 1 deletion src/sdkFactory/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,

Expand Down
11 changes: 11 additions & 0 deletions src/sync/offline/LocalhostFromFile.ts
Original file line number Diff line number Diff line change
@@ -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;
}
10 changes: 10 additions & 0 deletions src/sync/offline/LocalhostFromObject.ts
Original file line number Diff line number Diff line change
@@ -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;
}
9 changes: 5 additions & 4 deletions src/sync/offline/splitsParser/splitsParserFromFile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: ';

Expand Down Expand Up @@ -68,7 +69,7 @@ function arrangeConditions(mocksData: Record<string, Required<ISplitPartial> & {
});
}

export function splitsParserFromFileFactory() {
export function splitsParserFromFileFactory(): ISplitsParser {

let previousMock = 'NO_MOCK_LOADED';

Expand Down Expand Up @@ -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<string, ISplitPartial> {
const filePath = configFilesPath(features);
return function splitsParserFromFile({ features, log }: ISettings): false | Record<string, ISplitPartial> {
const filePath = configFilesPath(features as string);
let mockData: false | Record<string, ISplitPartial>;

// If we have a filePath, it means the extension is correct, choose the parser.
Expand Down
Original file line number Diff line number Diff line change
@@ -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();
Expand Down
11 changes: 10 additions & 1 deletion src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -581,6 +583,13 @@ export namespace SplitIO {
* @typedef {Promise<SplitNames>} SplitNamesAsync
*/
export type SplitNamesAsync = Promise<SplitNames>;
/**
* 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.
Expand Down
4 changes: 3 additions & 1 deletion src/utils/settingsValidation/__tests__/settings.mocks.ts
Original file line number Diff line number Diff line change
@@ -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: {
Expand Down Expand Up @@ -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,
Expand Down
28 changes: 16 additions & 12 deletions src/utils/settingsValidation/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,8 @@ const base = {
sync: {
splitFilters: undefined,
// impressions collection mode
impressionsMode: OPTIMIZED
impressionsMode: OPTIMIZED,
localhostMode: undefined
},

runtime: {
Expand All @@ -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;
Expand All @@ -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
Expand All @@ -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
Expand Down
Loading