diff --git a/.eslintrc.js b/.eslintrc.js index af5ecdab2..f6659e123 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -47,6 +47,38 @@ module.exports = { 'jest/no-focused-tests': 'error', 'jest/no-identical-title': 'error', 'jest/valid-expect': 'error', + 'no-underscore-dangle': ['error', { allowAfterThis: true }], + '@typescript-eslint/naming-convention': [ + 'error', + { + selector: ['method'], + format: ['camelCase'], + leadingUnderscore: 'forbid', + }, + { + selector: ['method'], + format: ['camelCase'], + modifiers: ['private'], + leadingUnderscore: 'require', + }, + { + selector: ['classProperty', 'parameterProperty'], + format: ['camelCase'], + leadingUnderscore: 'forbid', + }, + { + selector: ['classProperty', 'parameterProperty'], + modifiers: ['static'], + format: ['PascalCase'], + leadingUnderscore: 'forbid', + }, + { + selector: ['classProperty', 'parameterProperty'], + modifiers: ['private'], + format: ['camelCase'], + leadingUnderscore: 'require', + }, + ], }, globals: { BigInt: 'readonly', diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml index 8c799cef5..57a260d96 100644 --- a/.github/workflows/release-please.yml +++ b/.github/workflows/release-please.yml @@ -191,7 +191,6 @@ jobs: workspace_path: packages/sdk/browser aws_assume_role: ${{ vars.AWS_ROLE_ARN }} - release-server-node: runs-on: ubuntu-latest needs: ['release-please', 'release-sdk-server'] diff --git a/packages/sdk/akamai-edgekv/src/edgekv/edgeKVProvider.ts b/packages/sdk/akamai-edgekv/src/edgekv/edgeKVProvider.ts index 97c8806f0..66a1f17ed 100644 --- a/packages/sdk/akamai-edgekv/src/edgekv/edgeKVProvider.ts +++ b/packages/sdk/akamai-edgekv/src/edgekv/edgeKVProvider.ts @@ -8,15 +8,15 @@ type EdgeKVProviderParams = { }; export default class EdgeKVProvider implements EdgeProvider { - private edgeKv: EdgeKV; + private _edgeKv: EdgeKV; constructor({ namespace, group }: EdgeKVProviderParams) { - this.edgeKv = new EdgeKV({ namespace, group } as any); + this._edgeKv = new EdgeKV({ namespace, group } as any); } async get(rootKey: string): Promise { try { - return await this.edgeKv.getText({ item: rootKey } as any); + return await this._edgeKv.getText({ item: rootKey } as any); } catch (e) { /* empty */ } diff --git a/packages/sdk/browser/README.md b/packages/sdk/browser/README.md index bb6bc6e99..6202f2b1e 100644 --- a/packages/sdk/browser/README.md +++ b/packages/sdk/browser/README.md @@ -9,6 +9,7 @@ --> # ⛔️⛔️⛔️⛔️ + > [!CAUTION] > This library is a alpha version and should not be considered ready for production use while this message is visible. diff --git a/packages/sdk/browser/contract-tests/entity/src/ClientEntity.ts b/packages/sdk/browser/contract-tests/entity/src/ClientEntity.ts index 7e69fb857..6d50584d0 100644 --- a/packages/sdk/browser/contract-tests/entity/src/ClientEntity.ts +++ b/packages/sdk/browser/contract-tests/entity/src/ClientEntity.ts @@ -75,17 +75,17 @@ function makeDefaultInitialContext() { export class ClientEntity { constructor( - private readonly client: LDClient, - private readonly logger: LDLogger, + private readonly _client: LDClient, + private readonly _logger: LDLogger, ) {} close() { - this.client.close(); - this.logger.info('Test ended'); + this._client.close(); + this._logger.info('Test ended'); } async doCommand(params: CommandParams) { - this.logger.info(`Received command: ${params.command}`); + this._logger.info(`Received command: ${params.command}`); switch (params.command) { case CommandType.EvaluateFlag: { const evaluationParams = params.evaluate; @@ -95,23 +95,23 @@ export class ClientEntity { if (evaluationParams.detail) { switch (evaluationParams.valueType) { case ValueType.Bool: - return this.client.boolVariationDetail( + return this._client.boolVariationDetail( evaluationParams.flagKey, evaluationParams.defaultValue as boolean, ); case ValueType.Int: // Intentional fallthrough. case ValueType.Double: - return this.client.numberVariationDetail( + return this._client.numberVariationDetail( evaluationParams.flagKey, evaluationParams.defaultValue as number, ); case ValueType.String: - return this.client.stringVariationDetail( + return this._client.stringVariationDetail( evaluationParams.flagKey, evaluationParams.defaultValue as string, ); default: - return this.client.variationDetail( + return this._client.variationDetail( evaluationParams.flagKey, evaluationParams.defaultValue, ); @@ -120,7 +120,7 @@ export class ClientEntity { switch (evaluationParams.valueType) { case ValueType.Bool: return { - value: this.client.boolVariation( + value: this._client.boolVariation( evaluationParams.flagKey, evaluationParams.defaultValue as boolean, ), @@ -128,34 +128,37 @@ export class ClientEntity { case ValueType.Int: // Intentional fallthrough. case ValueType.Double: return { - value: this.client.numberVariation( + value: this._client.numberVariation( evaluationParams.flagKey, evaluationParams.defaultValue as number, ), }; case ValueType.String: return { - value: this.client.stringVariation( + value: this._client.stringVariation( evaluationParams.flagKey, evaluationParams.defaultValue as string, ), }; default: return { - value: this.client.variation(evaluationParams.flagKey, evaluationParams.defaultValue), + value: this._client.variation( + evaluationParams.flagKey, + evaluationParams.defaultValue, + ), }; } } case CommandType.EvaluateAllFlags: - return { state: this.client.allFlags() }; + return { state: this._client.allFlags() }; case CommandType.IdentifyEvent: { const identifyParams = params.identifyEvent; if (!identifyParams) { throw malformedCommand; } - await this.client.identify(identifyParams.user || identifyParams.context); + await this._client.identify(identifyParams.user || identifyParams.context); return undefined; } @@ -164,7 +167,7 @@ export class ClientEntity { if (!customEventParams) { throw malformedCommand; } - this.client.track( + this._client.track( customEventParams.eventKey, customEventParams.data, customEventParams.metricValue, @@ -173,7 +176,7 @@ export class ClientEntity { } case CommandType.FlushEvents: - this.client.flush(); + this._client.flush(); return undefined; default: diff --git a/packages/sdk/browser/contract-tests/entity/src/TestHarnessWebSocket.ts b/packages/sdk/browser/contract-tests/entity/src/TestHarnessWebSocket.ts index d01236cb2..83707c52e 100644 --- a/packages/sdk/browser/contract-tests/entity/src/TestHarnessWebSocket.ts +++ b/packages/sdk/browser/contract-tests/entity/src/TestHarnessWebSocket.ts @@ -4,31 +4,31 @@ import { ClientEntity, newSdkClientEntity } from './ClientEntity'; import { makeLogger } from './makeLogger'; export default class TestHarnessWebSocket { - private ws?: WebSocket; - private readonly entities: Record = {}; - private clientCounter = 0; - private logger: LDLogger = makeLogger('TestHarnessWebSocket'); + private _ws?: WebSocket; + private readonly _entities: Record = {}; + private _clientCounter = 0; + private _logger: LDLogger = makeLogger('TestHarnessWebSocket'); - constructor(private readonly url: string) {} + constructor(private readonly _url: string) {} connect() { - this.logger.info(`Connecting to web socket.`); - this.ws = new WebSocket(this.url, ['v1']); - this.ws.onopen = () => { - this.logger.info('Connected to websocket.'); + this._logger.info(`Connecting to web socket.`); + this._ws = new WebSocket(this._url, ['v1']); + this._ws.onopen = () => { + this._logger.info('Connected to websocket.'); }; - this.ws.onclose = () => { - this.logger.info('Websocket closed. Attempting to reconnect in 1 second.'); + this._ws.onclose = () => { + this._logger.info('Websocket closed. Attempting to reconnect in 1 second.'); setTimeout(() => { this.connect(); }, 1000); }; - this.ws.onerror = (err) => { - this.logger.info(`error:`, err); + this._ws.onerror = (err) => { + this._logger.info(`error:`, err); }; - this.ws.onmessage = async (msg) => { - this.logger.info('Test harness message', msg); + this._ws.onmessage = async (msg) => { + this._logger.info('Test harness message', msg); const data = JSON.parse(msg.data); const resData: any = { reqId: data.reqId }; switch (data.command) { @@ -46,33 +46,33 @@ export default class TestHarnessWebSocket { break; case 'createClient': { - resData.resourceUrl = `/clients/${this.clientCounter}`; + resData.resourceUrl = `/clients/${this._clientCounter}`; resData.status = 201; const entity = await newSdkClientEntity(data.body); - this.entities[this.clientCounter] = entity; - this.clientCounter += 1; + this._entities[this._clientCounter] = entity; + this._clientCounter += 1; } break; case 'runCommand': - if (Object.prototype.hasOwnProperty.call(this.entities, data.id)) { - const entity = this.entities[data.id]; + if (Object.prototype.hasOwnProperty.call(this._entities, data.id)) { + const entity = this._entities[data.id]; const body = await entity.doCommand(data.body); resData.body = body; resData.status = body ? 200 : 204; } else { resData.status = 404; - this.logger.warn(`Client did not exist: ${data.id}`); + this._logger.warn(`Client did not exist: ${data.id}`); } break; case 'deleteClient': - if (Object.prototype.hasOwnProperty.call(this.entities, data.id)) { - const entity = this.entities[data.id]; + if (Object.prototype.hasOwnProperty.call(this._entities, data.id)) { + const entity = this._entities[data.id]; entity.close(); - delete this.entities[data.id]; + delete this._entities[data.id]; } else { resData.status = 404; - this.logger.warn(`Could not delete client because it did not exist: ${data.id}`); + this._logger.warn(`Could not delete client because it did not exist: ${data.id}`); } break; default: @@ -84,10 +84,10 @@ export default class TestHarnessWebSocket { } disconnect() { - this.ws?.close(); + this._ws?.close(); } send(data: unknown) { - this.ws?.send(JSON.stringify(data)); + this._ws?.send(JSON.stringify(data)); } } diff --git a/packages/sdk/browser/rollup.config.js b/packages/sdk/browser/rollup.config.js index fd2f50cce..4ac931184 100644 --- a/packages/sdk/browser/rollup.config.js +++ b/packages/sdk/browser/rollup.config.js @@ -33,7 +33,13 @@ export default [ esmExternals: true, }), resolve(), - terser(), + terser({ + mangle: { + properties: { + regex: /^_/, + }, + }, + }), json(), // The 'sourcemap' option allows using the minified size, not the size before minification. visualizer({ sourcemap: true }), @@ -41,6 +47,18 @@ export default [ }, { ...getSharedConfig('cjs', 'dist/index.cjs.js'), - plugins: [typescript(), common(), resolve(), terser(), json()], + plugins: [ + typescript(), + common(), + resolve(), + terser({ + mangle: { + properties: { + regex: /^_/, + }, + }, + }), + json(), + ], }, ]; diff --git a/packages/sdk/browser/src/BrowserClient.ts b/packages/sdk/browser/src/BrowserClient.ts index 11b4c886e..454b01f8d 100644 --- a/packages/sdk/browser/src/BrowserClient.ts +++ b/packages/sdk/browser/src/BrowserClient.ts @@ -86,10 +86,10 @@ export type LDClient = Omit< }; export class BrowserClient extends LDClientImpl implements LDClient { - private readonly goalManager?: GoalManager; + private readonly _goalManager?: GoalManager; constructor( - private readonly clientSideId: string, + clientSideId: string, autoEnvAttributes: AutoEnvAttributes, options: BrowserOptions = {}, overridePlatform?: Platform, @@ -174,7 +174,7 @@ export class BrowserClient extends LDClientImpl implements LDClient { this.setEventSendingEnabled(true, false); if (validatedBrowserOptions.fetchGoals) { - this.goalManager = new GoalManager( + this._goalManager = new GoalManager( clientSideId, platform.requests, baseUrl, @@ -215,7 +215,7 @@ export class BrowserClient extends LDClientImpl implements LDClient { // "waitForGoalsReady", then we would make an async immediately invoked function expression // which emits the event, and assign its promise to a member. The "waitForGoalsReady" function // would return that promise. - this.goalManager.initialize(); + this._goalManager.initialize(); if (validatedBrowserOptions.automaticBackgroundHandling) { registerStateDetection(() => this.flush()); @@ -225,7 +225,7 @@ export class BrowserClient extends LDClientImpl implements LDClient { override async identify(context: LDContext, identifyOptions?: LDIdentifyOptions): Promise { await super.identify(context, identifyOptions); - this.goalManager?.startTracking(); + this._goalManager?.startTracking(); } setStreaming(streaming?: boolean): void { @@ -235,7 +235,7 @@ export class BrowserClient extends LDClientImpl implements LDClient { browserDataManager.setForcedStreaming(streaming); } - private updateAutomaticStreamingState() { + private _updateAutomaticStreamingState() { const browserDataManager = this.dataManager as BrowserDataManager; // This will need changed if support for listening to individual flag change // events it added. @@ -244,11 +244,11 @@ export class BrowserClient extends LDClientImpl implements LDClient { override on(eventName: LDEmitterEventName, listener: Function): void { super.on(eventName, listener); - this.updateAutomaticStreamingState(); + this._updateAutomaticStreamingState(); } override off(eventName: LDEmitterEventName, listener: Function): void { super.off(eventName, listener); - this.updateAutomaticStreamingState(); + this._updateAutomaticStreamingState(); } } diff --git a/packages/sdk/browser/src/BrowserDataManager.ts b/packages/sdk/browser/src/BrowserDataManager.ts index a540f3bea..4a00d769d 100644 --- a/packages/sdk/browser/src/BrowserDataManager.ts +++ b/packages/sdk/browser/src/BrowserDataManager.ts @@ -24,9 +24,9 @@ const logTag = '[BrowserDataManager]'; export default class BrowserDataManager extends BaseDataManager { // If streaming is forced on or off, then we follow that setting. // Otherwise we automatically manage streaming state. - private forcedStreaming?: boolean = undefined; - private automaticStreamingState: boolean = false; - private secureModeHash?: string; + private _forcedStreaming?: boolean = undefined; + private _automaticStreamingState: boolean = false; + private _secureModeHash?: string; // +-----------+-----------+---------------+ // | forced | automatic | state | @@ -44,7 +44,7 @@ export default class BrowserDataManager extends BaseDataManager { flagManager: FlagManager, credential: string, config: Configuration, - private readonly browserConfig: ValidatedOptions, + private readonly _browserConfig: ValidatedOptions, getPollingPaths: () => DataSourcePaths, getStreamingPaths: () => DataSourcePaths, baseHeaders: LDHeaders, @@ -62,10 +62,10 @@ export default class BrowserDataManager extends BaseDataManager { emitter, diagnosticsManager, ); - this.forcedStreaming = browserConfig.streaming; + this._forcedStreaming = _browserConfig.streaming; } - private debugLog(message: any, ...args: any[]) { + private _debugLog(message: any, ...args: any[]) { this.logger.debug(`${logTag} ${message}`, ...args); } @@ -84,23 +84,23 @@ export default class BrowserDataManager extends BaseDataManager { } else { this.setConnectionParams(); } - this.secureModeHash = browserIdentifyOptions?.hash; + this._secureModeHash = browserIdentifyOptions?.hash; if (browserIdentifyOptions?.bootstrap) { - this.finishIdentifyFromBootstrap(context, browserIdentifyOptions.bootstrap, identifyResolve); + this._finishIdentifyFromBootstrap(context, browserIdentifyOptions.bootstrap, identifyResolve); } else { if (await this.flagManager.loadCached(context)) { - this.debugLog('Identify - Flags loaded from cache. Continuing to initialize via a poll.'); + this._debugLog('Identify - Flags loaded from cache. Continuing to initialize via a poll.'); } const plainContextString = JSON.stringify(Context.toLDContext(context)); - const requestor = this.getRequestor(plainContextString); - await this.finishIdentifyFromPoll(requestor, context, identifyResolve, identifyReject); + const requestor = this._getRequestor(plainContextString); + await this._finishIdentifyFromPoll(requestor, context, identifyResolve, identifyReject); } - this.updateStreamingState(); + this._updateStreamingState(); } - private async finishIdentifyFromPoll( + private async _finishIdentifyFromPoll( requestor: Requestor, context: Context, identifyResolve: () => void, @@ -129,65 +129,66 @@ export default class BrowserDataManager extends BaseDataManager { } } - private finishIdentifyFromBootstrap( + private _finishIdentifyFromBootstrap( context: Context, bootstrap: unknown, identifyResolve: () => void, ) { this.flagManager.setBootstrap(context, readFlagsFromBootstrap(this.logger, bootstrap)); - this.debugLog('Identify - Initialization completed from bootstrap'); + this._debugLog('Identify - Initialization completed from bootstrap'); identifyResolve(); } setForcedStreaming(streaming?: boolean) { - this.forcedStreaming = streaming; - this.updateStreamingState(); + this._forcedStreaming = streaming; + this._updateStreamingState(); } setAutomaticStreamingState(streaming: boolean) { - this.automaticStreamingState = streaming; - this.updateStreamingState(); + this._automaticStreamingState = streaming; + this._updateStreamingState(); } - private updateStreamingState() { + private _updateStreamingState() { const shouldBeStreaming = - this.forcedStreaming || (this.automaticStreamingState && this.forcedStreaming === undefined); + this._forcedStreaming || + (this._automaticStreamingState && this._forcedStreaming === undefined); - this.debugLog( - `Updating streaming state. forced(${this.forcedStreaming}) automatic(${this.automaticStreamingState})`, + this._debugLog( + `Updating streaming state. forced(${this._forcedStreaming}) automatic(${this._automaticStreamingState})`, ); if (shouldBeStreaming) { - this.startDataSource(); + this._startDataSource(); } else { - this.stopDataSource(); + this._stopDataSource(); } } - private stopDataSource() { + private _stopDataSource() { if (this.updateProcessor) { - this.debugLog('Stopping update processor.'); + this._debugLog('Stopping update processor.'); } this.updateProcessor?.close(); this.updateProcessor = undefined; } - private startDataSource() { + private _startDataSource() { if (this.updateProcessor) { - this.debugLog('Update processor already active. Not changing state.'); + this._debugLog('Update processor already active. Not changing state.'); return; } if (!this.context) { - this.debugLog('Context not set, not starting update processor.'); + this._debugLog('Context not set, not starting update processor.'); return; } - this.debugLog('Starting update processor.'); - this.setupConnection(this.context); + this._debugLog('Starting update processor.'); + this._setupConnection(this.context); } - private setupConnection( + private _setupConnection( context: Context, identifyResolve?: () => void, identifyReject?: (err: Error) => void, @@ -200,7 +201,7 @@ export default class BrowserDataManager extends BaseDataManager { this.updateProcessor!.start(); } - private getRequestor(plainContextString: string): Requestor { + private _getRequestor(plainContextString: string): Requestor { const paths = this.getPollingPaths(); const path = this.config.useReport ? paths.pathReport(this.platform.encoding!, plainContextString) @@ -210,8 +211,8 @@ export default class BrowserDataManager extends BaseDataManager { if (this.config.withReasons) { parameters.push({ key: 'withReasons', value: 'true' }); } - if (this.secureModeHash) { - parameters.push({ key: 'h', value: this.secureModeHash }); + if (this._secureModeHash) { + parameters.push({ key: 'h', value: this._secureModeHash }); } const headers: { [key: string]: string } = { ...this.baseHeaders }; diff --git a/packages/sdk/browser/src/goals/GoalManager.ts b/packages/sdk/browser/src/goals/GoalManager.ts index 1862cfaa8..1c44022b8 100644 --- a/packages/sdk/browser/src/goals/GoalManager.ts +++ b/packages/sdk/browser/src/goals/GoalManager.ts @@ -6,64 +6,64 @@ import GoalTracker from './GoalTracker'; import { DefaultLocationWatcher, LocationWatcher } from './LocationWatcher'; export default class GoalManager { - private goals?: Goal[] = []; - private url: string; - private watcher?: LocationWatcher; - private tracker?: GoalTracker; - private isTracking = false; + private _goals?: Goal[] = []; + private _url: string; + private _watcher?: LocationWatcher; + private _tracker?: GoalTracker; + private _isTracking = false; constructor( credential: string, - private readonly requests: Requests, + private readonly _requests: Requests, baseUrl: string, - private readonly reportError: (err: Error) => void, - private readonly reportGoal: (url: string, goal: Goal) => void, + private readonly _reportError: (err: Error) => void, + private readonly _reportGoal: (url: string, goal: Goal) => void, locationWatcherFactory: (cb: () => void) => LocationWatcher = (cb) => new DefaultLocationWatcher(cb), ) { // TODO: Generate URL in a better way. - this.url = `${baseUrl}/sdk/goals/${credential}`; + this._url = `${baseUrl}/sdk/goals/${credential}`; - this.watcher = locationWatcherFactory(() => { - this.createTracker(); + this._watcher = locationWatcherFactory(() => { + this._createTracker(); }); } public async initialize(): Promise { - await this.fetchGoals(); + await this._fetchGoals(); // If tracking has been started before goal fetching completes, we need to // create the tracker so it can start watching for events. - this.createTracker(); + this._createTracker(); } public startTracking() { - this.isTracking = true; - this.createTracker(); + this._isTracking = true; + this._createTracker(); } - private createTracker() { - if (!this.isTracking) { + private _createTracker() { + if (!this._isTracking) { return; } - this.tracker?.close(); - if (this.goals && this.goals.length) { - this.tracker = new GoalTracker(this.goals, (goal) => { - this.reportGoal(getHref(), goal); + this._tracker?.close(); + if (this._goals && this._goals.length) { + this._tracker = new GoalTracker(this._goals, (goal) => { + this._reportGoal(getHref(), goal); }); } } - private async fetchGoals(): Promise { + private async _fetchGoals(): Promise { try { - const res = await this.requests.fetch(this.url); - this.goals = await res.json(); + const res = await this._requests.fetch(this._url); + this._goals = await res.json(); } catch (err) { - this.reportError(new LDUnexpectedResponseError(`Encountered error fetching goals: ${err}`)); + this._reportError(new LDUnexpectedResponseError(`Encountered error fetching goals: ${err}`)); } } close(): void { - this.watcher?.close(); - this.tracker?.close(); + this._watcher?.close(); + this._tracker?.close(); } } diff --git a/packages/sdk/browser/src/goals/GoalTracker.ts b/packages/sdk/browser/src/goals/GoalTracker.ts index 268d0b68a..f3da9a030 100644 --- a/packages/sdk/browser/src/goals/GoalTracker.ts +++ b/packages/sdk/browser/src/goals/GoalTracker.ts @@ -71,7 +71,7 @@ function findGoalsForClick(event: Event, clickGoals: ClickGoal[]) { * Tracks the goals on an individual "page" (combination of route, query params, and hash). */ export default class GoalTracker { - private cleanup?: () => void; + private _cleanup?: () => void; constructor(goals: Goal[], onEvent: EventHandler) { const goalsMatchingUrl = goals.filter((goal) => goal.urls?.some((matcher) => @@ -92,7 +92,7 @@ export default class GoalTracker { onEvent(clickGoal); }); }; - this.cleanup = addDocumentEventListener('click', clickHandler); + this._cleanup = addDocumentEventListener('click', clickHandler); } } @@ -100,6 +100,6 @@ export default class GoalTracker { * Close the tracker which stops listening to any events. */ close() { - this.cleanup?.(); + this._cleanup?.(); } } diff --git a/packages/sdk/browser/src/goals/LocationWatcher.ts b/packages/sdk/browser/src/goals/LocationWatcher.ts index 75aceb306..729d7af28 100644 --- a/packages/sdk/browser/src/goals/LocationWatcher.ts +++ b/packages/sdk/browser/src/goals/LocationWatcher.ts @@ -18,20 +18,20 @@ export interface LocationWatcher { * @internal */ export class DefaultLocationWatcher { - private previousLocation?: string; - private watcherHandle: IntervalHandle; - private cleanupListeners?: () => void; + private _previousLocation?: string; + private _watcherHandle: IntervalHandle; + private _cleanupListeners?: () => void; /** * @param callback Callback that is executed whenever a URL change is detected. */ constructor(callback: () => void) { - this.previousLocation = getHref(); + this._previousLocation = getHref(); const checkUrl = () => { const currentLocation = getHref(); - if (currentLocation !== this.previousLocation) { - this.previousLocation = currentLocation; + if (currentLocation !== this._previousLocation) { + this._previousLocation = currentLocation; callback(); } }; @@ -41,11 +41,11 @@ export class DefaultLocationWatcher { * Details on when popstate is called: * https://developer.mozilla.org/en-US/docs/Web/API/Window/popstate_event#when_popstate_is_sent */ - this.watcherHandle = setInterval(checkUrl, LOCATION_WATCHER_INTERVAL_MS); + this._watcherHandle = setInterval(checkUrl, LOCATION_WATCHER_INTERVAL_MS); const removeListener = addWindowEventListener('popstate', checkUrl); - this.cleanupListeners = () => { + this._cleanupListeners = () => { removeListener(); }; } @@ -54,9 +54,9 @@ export class DefaultLocationWatcher { * Stop watching for location changes. */ close(): void { - if (this.watcherHandle) { - clearInterval(this.watcherHandle); + if (this._watcherHandle) { + clearInterval(this._watcherHandle); } - this.cleanupListeners?.(); + this._cleanupListeners?.(); } } diff --git a/packages/sdk/browser/src/platform/Backoff.ts b/packages/sdk/browser/src/platform/Backoff.ts index f90bcd7c4..ce0e931ee 100644 --- a/packages/sdk/browser/src/platform/Backoff.ts +++ b/packages/sdk/browser/src/platform/Backoff.ts @@ -12,33 +12,33 @@ const JITTER_RATIO = 0.5; // Delay should be 50%-100% of calculated time. * success, without an intervening faulure, then the backoff is reset to initialRetryDelayMillis. */ export default class Backoff { - private retryCount: number = 0; - private activeSince?: number; - private initialRetryDelayMillis: number; + private _retryCount: number = 0; + private _activeSince?: number; + private _initialRetryDelayMillis: number; /** * The exponent at which the backoff delay will exceed the maximum. * Beyond this limit the backoff can be set to the max. */ - private readonly maxExponent: number; + private readonly _maxExponent: number; constructor( initialRetryDelayMillis: number, - private readonly retryResetIntervalMillis: number, - private readonly random = Math.random, + private readonly _retryResetIntervalMillis: number, + private readonly _random = Math.random, ) { // Initial retry delay cannot be 0. - this.initialRetryDelayMillis = Math.max(1, initialRetryDelayMillis); - this.maxExponent = Math.ceil(Math.log2(MAX_RETRY_DELAY / this.initialRetryDelayMillis)); + this._initialRetryDelayMillis = Math.max(1, initialRetryDelayMillis); + this._maxExponent = Math.ceil(Math.log2(MAX_RETRY_DELAY / this._initialRetryDelayMillis)); } - private backoff(): number { - const exponent = Math.min(this.retryCount, this.maxExponent); - const delay = this.initialRetryDelayMillis * 2 ** exponent; + private _backoff(): number { + const exponent = Math.min(this._retryCount, this._maxExponent); + const delay = this._initialRetryDelayMillis * 2 ** exponent; return Math.min(delay, MAX_RETRY_DELAY); } - private jitter(computedDelayMillis: number): number { - return computedDelayMillis - Math.trunc(this.random() * JITTER_RATIO * computedDelayMillis); + private _jitter(computedDelayMillis: number): number { + return computedDelayMillis - Math.trunc(this._random() * JITTER_RATIO * computedDelayMillis); } /** @@ -48,7 +48,7 @@ export default class Backoff { * the current time is used. */ success(timeStampMs: number = Date.now()): void { - this.activeSince = timeStampMs; + this._activeSince = timeStampMs; } /** @@ -63,14 +63,14 @@ export default class Backoff { // If the last successful connection was active for more than the RESET_INTERVAL, then we // return to the initial retry delay. if ( - this.activeSince !== undefined && - timeStampMs - this.activeSince > this.retryResetIntervalMillis + this._activeSince !== undefined && + timeStampMs - this._activeSince > this._retryResetIntervalMillis ) { - this.retryCount = 0; + this._retryCount = 0; } - this.activeSince = undefined; - const delay = this.jitter(this.backoff()); - this.retryCount += 1; + this._activeSince = undefined; + const delay = this._jitter(this._backoff()); + this._retryCount += 1; return delay; } } diff --git a/packages/sdk/browser/src/platform/BrowserHasher.ts b/packages/sdk/browser/src/platform/BrowserHasher.ts index fc46d8d87..7f1107ec8 100644 --- a/packages/sdk/browser/src/platform/BrowserHasher.ts +++ b/packages/sdk/browser/src/platform/BrowserHasher.ts @@ -1,18 +1,18 @@ import { Hasher } from '@launchdarkly/js-client-sdk-common'; export default class BrowserHasher implements Hasher { - private data: string[] = []; - private algorithm: string; + private _data: string[] = []; + private _algorithm: string; constructor( - private readonly webcrypto: Crypto, + private readonly _webcrypto: Crypto, algorithm: string, ) { switch (algorithm) { case 'sha1': - this.algorithm = 'SHA-1'; + this._algorithm = 'SHA-1'; break; case 'sha256': - this.algorithm = 'SHA-256'; + this._algorithm = 'SHA-256'; break; default: throw new Error(`Algorithm is not supported ${algorithm}`); @@ -20,9 +20,9 @@ export default class BrowserHasher implements Hasher { } async asyncDigest(encoding: string): Promise { - const combinedData = this.data.join(''); + const combinedData = this._data.join(''); const encoded = new TextEncoder().encode(combinedData); - const digestedBuffer = await this.webcrypto.subtle.digest(this.algorithm, encoded); + const digestedBuffer = await this._webcrypto.subtle.digest(this._algorithm, encoded); switch (encoding) { case 'base64': return btoa(String.fromCharCode(...new Uint8Array(digestedBuffer))); @@ -38,7 +38,7 @@ export default class BrowserHasher implements Hasher { } update(data: string): Hasher { - this.data.push(data); + this._data.push(data); return this as Hasher; } } diff --git a/packages/sdk/browser/src/platform/DefaultBrowserEventSource.ts b/packages/sdk/browser/src/platform/DefaultBrowserEventSource.ts index b9084349b..3ecdeb3a1 100644 --- a/packages/sdk/browser/src/platform/DefaultBrowserEventSource.ts +++ b/packages/sdk/browser/src/platform/DefaultBrowserEventSource.ts @@ -21,22 +21,22 @@ import Backoff from './Backoff'; * source with additional reconnection logic. */ export default class DefaultBrowserEventSource implements LDEventSource { - private es?: EventSource; - private backoff: Backoff; - private errorFilter: (err: HttpErrorResponse) => boolean; + private _es?: EventSource; + private _backoff: Backoff; + private _errorFilter: (err: HttpErrorResponse) => boolean; // The type of the handle can be platform specific and we treat is opaquely. - private reconnectTimeoutHandle?: any; + private _reconnectTimeoutHandle?: any; - private listeners: Record = {}; + private _listeners: Record = {}; constructor( - private readonly url: string, + private readonly _url: string, options: EventSourceInitDict, ) { - this.backoff = new Backoff(options.initialRetryDelayMillis, options.retryResetIntervalMillis); - this.errorFilter = options.errorFilter; - this.openConnection(); + this._backoff = new Backoff(options.initialRetryDelayMillis, options.retryResetIntervalMillis); + this._errorFilter = options.errorFilter; + this._openConnection(); } onclose: (() => void) | undefined; @@ -47,60 +47,60 @@ export default class DefaultBrowserEventSource implements LDEventSource { onretrying: ((e: { delayMillis: number }) => void) | undefined; - private openConnection() { - this.es = new EventSource(this.url); - this.es.onopen = () => { - this.backoff.success(); + private _openConnection() { + this._es = new EventSource(this._url); + this._es.onopen = () => { + this._backoff.success(); this.onopen?.(); }; // The error could be from a polyfill, or from the browser event source, so we are loose on the // typing. - this.es.onerror = (err: any) => { - this.handleError(err); + this._es.onerror = (err: any) => { + this._handleError(err); this.onerror?.(err); }; - Object.entries(this.listeners).forEach(([eventName, listeners]) => { + Object.entries(this._listeners).forEach(([eventName, listeners]) => { listeners.forEach((listener) => { - this.es?.addEventListener(eventName, listener); + this._es?.addEventListener(eventName, listener); }); }); } addEventListener(type: EventName, listener: EventListener): void { - this.listeners[type] ??= []; - this.listeners[type].push(listener); - this.es?.addEventListener(type, listener); + this._listeners[type] ??= []; + this._listeners[type].push(listener); + this._es?.addEventListener(type, listener); } close(): void { // Ensure any pending retry attempts are not done. - clearTimeout(this.reconnectTimeoutHandle); - this.reconnectTimeoutHandle = undefined; + clearTimeout(this._reconnectTimeoutHandle); + this._reconnectTimeoutHandle = undefined; // Close the event source and notify any listeners. - this.es?.close(); + this._es?.close(); this.onclose?.(); } - private tryConnect(delayMs: number) { + private _tryConnect(delayMs: number) { this.onretrying?.({ delayMillis: delayMs }); - this.reconnectTimeoutHandle = setTimeout(() => { - this.openConnection(); + this._reconnectTimeoutHandle = setTimeout(() => { + this._openConnection(); }, delayMs); } - private handleError(err: any): void { + private _handleError(err: any): void { this.close(); // The event source may not produce a status. But the LaunchDarkly // polyfill can. If we can get the status, then we should stop retrying // on certain error codes. - if (err.status && typeof err.status === 'number' && !this.errorFilter(err)) { + if (err.status && typeof err.status === 'number' && !this._errorFilter(err)) { // If we encounter an unrecoverable condition, then we do not want to // retry anymore. return; } - this.tryConnect(this.backoff.fail()); + this._tryConnect(this._backoff.fail()); } } diff --git a/packages/sdk/browser/src/platform/LocalStorage.ts b/packages/sdk/browser/src/platform/LocalStorage.ts index 75e8be6de..88748d70c 100644 --- a/packages/sdk/browser/src/platform/LocalStorage.ts +++ b/packages/sdk/browser/src/platform/LocalStorage.ts @@ -13,12 +13,12 @@ export function isLocalStorageSupported() { * and none of the methods need to internally await their operations. */ export default class PlatformStorage implements Storage { - constructor(private readonly logger?: LDLogger) {} + constructor(private readonly _logger?: LDLogger) {} async clear(key: string): Promise { try { localStorage.removeItem(key); } catch (error) { - this.logger?.error(`Error clearing key from localStorage: ${key}, reason: ${error}`); + this._logger?.error(`Error clearing key from localStorage: ${key}, reason: ${error}`); } } @@ -27,7 +27,7 @@ export default class PlatformStorage implements Storage { const value = localStorage.getItem(key); return value ?? null; } catch (error) { - this.logger?.error(`Error getting key from localStorage: ${key}, reason: ${error}`); + this._logger?.error(`Error getting key from localStorage: ${key}, reason: ${error}`); return null; } } @@ -36,7 +36,7 @@ export default class PlatformStorage implements Storage { try { localStorage.setItem(key, value); } catch (error) { - this.logger?.error(`Error setting key in localStorage: ${key}, reason: ${error}`); + this._logger?.error(`Error setting key in localStorage: ${key}, reason: ${error}`); } } } diff --git a/packages/sdk/cloudflare/jsr.json b/packages/sdk/cloudflare/jsr.json index 993f58bfd..2a4fd08c5 100644 --- a/packages/sdk/cloudflare/jsr.json +++ b/packages/sdk/cloudflare/jsr.json @@ -3,15 +3,7 @@ "version": "2.5.15", "exports": "./src/index.ts", "publish": { - "include": [ - "LICENSE", - "README.md", - "package.json", - "jsr.json", - "src/**/*.ts" - ], - "exclude": [ - "src/**/*.test.ts" - ] + "include": ["LICENSE", "README.md", "package.json", "jsr.json", "src/**/*.ts"], + "exclude": ["src/**/*.test.ts"] } } diff --git a/packages/sdk/react-native/__tests__/fromExternal/react-native-sse/EventSource.test.ts b/packages/sdk/react-native/__tests__/fromExternal/react-native-sse/EventSource.test.ts index a7d323fa9..2073d1a0f 100644 --- a/packages/sdk/react-native/__tests__/fromExternal/react-native-sse/EventSource.test.ts +++ b/packages/sdk/react-native/__tests__/fromExternal/react-native-sse/EventSource.test.ts @@ -78,12 +78,12 @@ describe('EventSource', () => { test('getNextRetryDelay', () => { // @ts-ignore - const delay0 = eventSource.getNextRetryDelay(); + const delay0 = eventSource._getNextRetryDelay(); // @ts-ignore - const delay1 = eventSource.getNextRetryDelay(); + const delay1 = eventSource._getNextRetryDelay(); // @ts-ignore - expect(eventSource.retryCount).toEqual(2); + expect(eventSource._retryCount).toEqual(2); expect(delay0).toEqual(556); expect(delay1).toEqual(1001); }); @@ -102,7 +102,7 @@ describe('EventSource', () => { jest.runAllTimers(); // This forces it to reconnect. // @ts-ignore - eventSource.tryConnect(); + eventSource._tryConnect(); jest.runAllTimers(); expect(logger.debug).toHaveBeenNthCalledWith( diff --git a/packages/sdk/react-native/src/MobileDataManager.ts b/packages/sdk/react-native/src/MobileDataManager.ts index 0285af2bd..5eecd2c81 100644 --- a/packages/sdk/react-native/src/MobileDataManager.ts +++ b/packages/sdk/react-native/src/MobileDataManager.ts @@ -26,7 +26,7 @@ export default class MobileDataManager extends BaseDataManager { flagManager: FlagManager, credential: string, config: Configuration, - private readonly rnConfig: ValidatedOptions, + private readonly _rnConfig: ValidatedOptions, getPollingPaths: () => DataSourcePaths, getStreamingPaths: () => DataSourcePaths, baseHeaders: LDHeaders, @@ -44,10 +44,10 @@ export default class MobileDataManager extends BaseDataManager { emitter, diagnosticsManager, ); - this.connectionMode = rnConfig.initialConnectionMode; + this.connectionMode = _rnConfig.initialConnectionMode; } - private debugLog(message: any, ...args: any[]) { + private _debugLog(message: any, ...args: any[]) { this.logger.debug(`${logTag} ${message}`, ...args); } @@ -64,31 +64,31 @@ export default class MobileDataManager extends BaseDataManager { const loadedFromCache = await this.flagManager.loadCached(context); if (loadedFromCache && !waitForNetworkResults) { - this.debugLog('Identify completing with cached flags'); + this._debugLog('Identify completing with cached flags'); identifyResolve(); } if (loadedFromCache && waitForNetworkResults) { - this.debugLog( + this._debugLog( 'Identify - Flags loaded from cache, but identify was requested with "waitForNetworkResults"', ); } if (this.connectionMode === 'offline') { if (loadedFromCache) { - this.debugLog('Offline identify - using cached flags.'); + this._debugLog('Offline identify - using cached flags.'); } else { - this.debugLog( + this._debugLog( 'Offline identify - no cached flags, using defaults or already loaded flags.', ); identifyResolve(); } } else { // Context has been validated in LDClientImpl.identify - this.setupConnection(context, identifyResolve, identifyReject); + this._setupConnection(context, identifyResolve, identifyReject); } } - private setupConnection( + private _setupConnection( context: Context, identifyResolve?: () => void, identifyReject?: (err: Error) => void, @@ -115,12 +115,12 @@ export default class MobileDataManager extends BaseDataManager { async setConnectionMode(mode: ConnectionMode): Promise { if (this.connectionMode === mode) { - this.debugLog(`setConnectionMode ignored. Mode is already '${mode}'.`); + this._debugLog(`setConnectionMode ignored. Mode is already '${mode}'.`); return; } this.connectionMode = mode; - this.debugLog(`setConnectionMode ${mode}.`); + this._debugLog(`setConnectionMode ${mode}.`); switch (mode) { case 'offline': @@ -130,7 +130,7 @@ export default class MobileDataManager extends BaseDataManager { case 'streaming': if (this.context) { // identify will start the update processor - this.setupConnection(this.context); + this._setupConnection(this.context); } break; diff --git a/packages/sdk/react-native/src/RNStateDetector.ts b/packages/sdk/react-native/src/RNStateDetector.ts index 2a9cf3c05..7694c475c 100644 --- a/packages/sdk/react-native/src/RNStateDetector.ts +++ b/packages/sdk/react-native/src/RNStateDetector.ts @@ -21,28 +21,28 @@ function translateAppState(state: AppStateStatus): ApplicationState { * @internal */ export default class RNStateDetector implements StateDetector { - private applicationStateListener?: (state: ApplicationState) => void; - private networkStateListener?: (state: NetworkState) => void; + private _applicationStateListener?: (state: ApplicationState) => void; + private _networkStateListener?: (state: NetworkState) => void; constructor() { AppState.addEventListener('change', (state: AppStateStatus) => { - this.applicationStateListener?.(translateAppState(state)); + this._applicationStateListener?.(translateAppState(state)); }); } setApplicationStateListener(fn: (state: ApplicationState) => void): void { - this.applicationStateListener = fn; + this._applicationStateListener = fn; // When you listen provide the current state immediately. - this.applicationStateListener(translateAppState(AppState.currentState)); + this._applicationStateListener(translateAppState(AppState.currentState)); } setNetworkStateListener(fn: (state: NetworkState) => void): void { - this.networkStateListener = fn; + this._networkStateListener = fn; // Not implemented. } stopListening(): void { - this.applicationStateListener = undefined; - this.networkStateListener = undefined; + this._applicationStateListener = undefined; + this._networkStateListener = undefined; } } diff --git a/packages/sdk/react-native/src/ReactNativeLDClient.ts b/packages/sdk/react-native/src/ReactNativeLDClient.ts index b4cc0db2f..7e46abc2f 100644 --- a/packages/sdk/react-native/src/ReactNativeLDClient.ts +++ b/packages/sdk/react-native/src/ReactNativeLDClient.ts @@ -34,7 +34,7 @@ import RNStateDetector from './RNStateDetector'; * ``` */ export default class ReactNativeLDClient extends LDClientImpl { - private connectionManager: ConnectionManager; + private _connectionManager: ConnectionManager; /** * Creates an instance of the LaunchDarkly client. * @@ -123,7 +123,7 @@ export default class ReactNativeLDClient extends LDClientImpl { }; const initialConnectionMode = options.initialConnectionMode ?? 'streaming'; - this.connectionManager = new ConnectionManager( + this._connectionManager = new ConnectionManager( logger, { initialConnectionMode, @@ -139,9 +139,9 @@ export default class ReactNativeLDClient extends LDClientImpl { async setConnectionMode(mode: ConnectionMode): Promise { // Set the connection mode before setting offline, in case there is any mode transition work // such as flushing on entering the background. - this.connectionManager.setConnectionMode(mode); + this._connectionManager.setConnectionMode(mode); // For now the data source connection and the event processing state are connected. - this.connectionManager.setOffline(mode === 'offline'); + this._connectionManager.setOffline(mode === 'offline'); } /** diff --git a/packages/sdk/react-native/src/fromExternal/react-native-sse/EventSource.ts b/packages/sdk/react-native/src/fromExternal/react-native-sse/EventSource.ts index 710f3257c..9a5d87dc2 100644 --- a/packages/sdk/react-native/src/fromExternal/react-native-sse/EventSource.ts +++ b/packages/sdk/react-native/src/fromExternal/react-native-sse/EventSource.ts @@ -39,29 +39,29 @@ export default class EventSource { OPEN = 1; CLOSED = 2; - private lastEventId: undefined | string; - private lastIndexProcessed = 0; - private eventType: undefined | EventType; - private status = this.CONNECTING; - private eventHandlers: any = { + private _lastEventId: undefined | string; + private _lastIndexProcessed = 0; + private _eventType: undefined | EventType; + private _status = this.CONNECTING; + private _eventHandlers: any = { open: [], message: [], error: [], close: [], }; - private method: string; - private timeout: number; - private withCredentials: boolean; - private headers: Record; - private body: any; - private url: string; - private xhr: XMLHttpRequest = new XMLHttpRequest(); - private connectTimer: any; - private retryAndHandleError?: (err: any) => boolean; - private initialRetryDelayMillis: number = 1000; - private retryCount: number = 0; - private logger?: any; + private _method: string; + private _timeout: number; + private _withCredentials: boolean; + private _headers: Record; + private _body: any; + private _url: string; + private _xhr: XMLHttpRequest = new XMLHttpRequest(); + private _connectTimer: any; + private _retryAndHandleError?: (err: any) => boolean; + private _initialRetryDelayMillis: number = 1000; + private _retryCount: number = 0; + private _logger?: any; constructor(url: string, options?: EventSourceOptions) { const opts = { @@ -69,163 +69,163 @@ export default class EventSource { ...options, }; - this.url = url; - this.method = opts.method!; - this.timeout = opts.timeout!; - this.withCredentials = opts.withCredentials!; - this.headers = opts.headers!; - this.body = opts.body; - this.retryAndHandleError = opts.retryAndHandleError; - this.initialRetryDelayMillis = opts.initialRetryDelayMillis!; - this.logger = opts.logger; - - this.tryConnect(true); + this._url = url; + this._method = opts.method!; + this._timeout = opts.timeout!; + this._withCredentials = opts.withCredentials!; + this._headers = opts.headers!; + this._body = opts.body; + this._retryAndHandleError = opts.retryAndHandleError; + this._initialRetryDelayMillis = opts.initialRetryDelayMillis!; + this._logger = opts.logger; + + this._tryConnect(true); } - private getNextRetryDelay() { - const delay = jitter(backoff(this.initialRetryDelayMillis, this.retryCount)); - this.retryCount += 1; + private _getNextRetryDelay() { + const delay = jitter(backoff(this._initialRetryDelayMillis, this._retryCount)); + this._retryCount += 1; return delay; } - private tryConnect(initialConnection: boolean = false) { - let delay = initialConnection ? 0 : this.getNextRetryDelay(); + private _tryConnect(initialConnection: boolean = false) { + let delay = initialConnection ? 0 : this._getNextRetryDelay(); if (initialConnection) { - this.logger?.debug(`[EventSource] opening new connection.`); + this._logger?.debug(`[EventSource] opening new connection.`); } else { - this.logger?.debug(`[EventSource] Will open new connection in ${delay} ms.`); + this._logger?.debug(`[EventSource] Will open new connection in ${delay} ms.`); this.dispatch('retry', { type: 'retry', delayMillis: delay }); } - this.connectTimer = setTimeout(() => { + this._connectTimer = setTimeout(() => { if (!initialConnection) { this.close(); } - this.open(); + this._open(); }, delay); } - private open() { + private _open() { try { - this.lastIndexProcessed = 0; - this.status = this.CONNECTING; - this.xhr.open(this.method, this.url, true); + this._lastIndexProcessed = 0; + this._status = this.CONNECTING; + this._xhr.open(this._method, this._url, true); - if (this.withCredentials) { - this.xhr.withCredentials = true; + if (this._withCredentials) { + this._xhr.withCredentials = true; } - this.xhr.setRequestHeader('Accept', 'text/event-stream'); - this.xhr.setRequestHeader('Cache-Control', 'no-cache'); - this.xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); + this._xhr.setRequestHeader('Accept', 'text/event-stream'); + this._xhr.setRequestHeader('Cache-Control', 'no-cache'); + this._xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); - if (this.headers) { - Object.entries(this.headers).forEach(([key, value]) => { - this.xhr.setRequestHeader(key, value); + if (this._headers) { + Object.entries(this._headers).forEach(([key, value]) => { + this._xhr.setRequestHeader(key, value); }); } - if (typeof this.lastEventId !== 'undefined') { - this.xhr.setRequestHeader('Last-Event-ID', this.lastEventId); + if (typeof this._lastEventId !== 'undefined') { + this._xhr.setRequestHeader('Last-Event-ID', this._lastEventId); } - this.xhr.timeout = this.timeout; + this._xhr.timeout = this._timeout; - this.xhr.onreadystatechange = () => { - if (this.status === this.CLOSED) { + this._xhr.onreadystatechange = () => { + if (this._status === this.CLOSED) { return; } - this.logger?.debug( + this._logger?.debug( `[EventSource][onreadystatechange] ReadyState: ${ - XMLReadyStateMap[this.xhr.readyState] || 'Unknown' - }(${this.xhr.readyState}), status: ${this.xhr.status}`, + XMLReadyStateMap[this._xhr.readyState] || 'Unknown' + }(${this._xhr.readyState}), status: ${this._xhr.status}`, ); if ( - this.xhr.readyState !== XMLHttpRequest.DONE && - this.xhr.readyState !== XMLHttpRequest.LOADING + this._xhr.readyState !== XMLHttpRequest.DONE && + this._xhr.readyState !== XMLHttpRequest.LOADING ) { return; } - if (this.xhr.status >= 200 && this.xhr.status < 400) { - if (this.status === this.CONNECTING) { - this.retryCount = 0; - this.status = this.OPEN; + if (this._xhr.status >= 200 && this._xhr.status < 400) { + if (this._status === this.CONNECTING) { + this._retryCount = 0; + this._status = this.OPEN; this.dispatch('open', { type: 'open' }); - this.logger?.debug('[EventSource][onreadystatechange][OPEN] Connection opened.'); + this._logger?.debug('[EventSource][onreadystatechange][OPEN] Connection opened.'); } // retry from server gets set here - this.handleEvent(this.xhr.responseText || ''); + this._handleEvent(this._xhr.responseText || ''); - if (this.xhr.readyState === XMLHttpRequest.DONE) { - this.logger?.debug('[EventSource][onreadystatechange][DONE] Operation done.'); - this.tryConnect(); + if (this._xhr.readyState === XMLHttpRequest.DONE) { + this._logger?.debug('[EventSource][onreadystatechange][DONE] Operation done.'); + this._tryConnect(); } } else { - this.status = this.ERROR; + this._status = this.ERROR; this.dispatch('error', { type: 'error', - message: this.xhr.responseText, - xhrStatus: this.xhr.status, - xhrState: this.xhr.readyState, + message: this._xhr.responseText, + xhrStatus: this._xhr.status, + xhrState: this._xhr.readyState, }); - if (this.xhr.readyState === XMLHttpRequest.DONE) { - this.logger?.debug('[EventSource][onreadystatechange][ERROR] Response status error.'); + if (this._xhr.readyState === XMLHttpRequest.DONE) { + this._logger?.debug('[EventSource][onreadystatechange][ERROR] Response status error.'); - if (!this.retryAndHandleError) { + if (!this._retryAndHandleError) { // by default just try and reconnect if there's an error. - this.tryConnect(); + this._tryConnect(); } else { // custom retry logic taking into account status codes. - const shouldRetry = this.retryAndHandleError({ - status: this.xhr.status, - message: this.xhr.responseText, + const shouldRetry = this._retryAndHandleError({ + status: this._xhr.status, + message: this._xhr.responseText, }); if (shouldRetry) { - this.tryConnect(); + this._tryConnect(); } } } } }; - this.xhr.onerror = () => { - if (this.status === this.CLOSED) { + this._xhr.onerror = () => { + if (this._status === this.CLOSED) { return; } - this.status = this.ERROR; + this._status = this.ERROR; this.dispatch('error', { type: 'error', - message: this.xhr.responseText, - xhrStatus: this.xhr.status, - xhrState: this.xhr.readyState, + message: this._xhr.responseText, + xhrStatus: this._xhr.status, + xhrState: this._xhr.readyState, }); }; - if (this.body) { - this.xhr.send(this.body); + if (this._body) { + this._xhr.send(this._body); } else { - this.xhr.send(); + this._xhr.send(); } - if (this.timeout > 0) { + if (this._timeout > 0) { setTimeout(() => { - if (this.xhr.readyState === XMLHttpRequest.LOADING) { + if (this._xhr.readyState === XMLHttpRequest.LOADING) { this.dispatch('error', { type: 'timeout' }); this.close(); } - }, this.timeout); + }, this._timeout); } } catch (e: any) { - this.status = this.ERROR; + this._status = this.ERROR; this.dispatch('error', { type: 'exception', message: e.message, @@ -234,12 +234,12 @@ export default class EventSource { } } - private handleEvent(response: string) { - const parts = response.slice(this.lastIndexProcessed).split('\n'); + private _handleEvent(response: string) { + const parts = response.slice(this._lastIndexProcessed).split('\n'); const indexOfDoubleNewline = response.lastIndexOf('\n\n'); if (indexOfDoubleNewline !== -1) { - this.lastIndexProcessed = indexOfDoubleNewline + 2; + this._lastIndexProcessed = indexOfDoubleNewline + 2; } let data = []; @@ -250,7 +250,7 @@ export default class EventSource { for (let i = 0; i < parts.length; i++) { line = parts[i].replace(/^(\s|\u00A0)+|(\s|\u00A0)+$/g, ''); if (line.indexOf('event') === 0) { - this.eventType = line.replace(/event:?\s*/, '') as EventType; + this._eventType = line.replace(/event:?\s*/, '') as EventType; } else if (line.indexOf('retry') === 0) { retry = parseInt(line.replace(/retry:?\s*/, ''), 10); if (!Number.isNaN(retry)) { @@ -260,62 +260,62 @@ export default class EventSource { } else if (line.indexOf('data') === 0) { data.push(line.replace(/data:?\s*/, '')); } else if (line.indexOf('id:') === 0) { - this.lastEventId = line.replace(/id:?\s*/, ''); + this._lastEventId = line.replace(/id:?\s*/, ''); } else if (line.indexOf('id') === 0) { - this.lastEventId = undefined; + this._lastEventId = undefined; } else if (line === '') { if (data.length > 0) { - const eventType = this.eventType || 'message'; + const eventType = this._eventType || 'message'; const event: any = { type: eventType, data: data.join('\n'), - url: this.url, - lastEventId: this.lastEventId, + url: this._url, + lastEventId: this._lastEventId, }; this.dispatch(eventType, event); data = []; - this.eventType = undefined; + this._eventType = undefined; } } } } addEventListener>(type: T, listener: EventSourceListener): void { - if (this.eventHandlers[type] === undefined) { - this.eventHandlers[type] = []; + if (this._eventHandlers[type] === undefined) { + this._eventHandlers[type] = []; } - this.eventHandlers[type].push(listener); + this._eventHandlers[type].push(listener); } removeEventListener>(type: T, listener: EventSourceListener): void { - if (this.eventHandlers[type] !== undefined) { - this.eventHandlers[type] = this.eventHandlers[type].filter( + if (this._eventHandlers[type] !== undefined) { + this._eventHandlers[type] = this._eventHandlers[type].filter( (handler: EventSourceListener) => handler !== listener, ); } } removeAllEventListeners>(type?: T) { - const availableTypes = Object.keys(this.eventHandlers); + const availableTypes = Object.keys(this._eventHandlers); if (type === undefined) { availableTypes.forEach((eventType) => { - this.eventHandlers[eventType] = []; + this._eventHandlers[eventType] = []; }); } else { if (!availableTypes.includes(type)) { throw Error(`[EventSource] '${type}' type is not supported event type.`); } - this.eventHandlers[type] = []; + this._eventHandlers[type] = []; } } dispatch>(type: T, data: EventSourceEvent) { - this.eventHandlers[type]?.forEach((handler: EventSourceListener) => handler(data)); + this._eventHandlers[type]?.forEach((handler: EventSourceListener) => handler(data)); switch (type) { case 'open': @@ -325,7 +325,7 @@ export default class EventSource { this.onclose(); break; case 'error': - this.logger?.debug(`[EventSource][dispatch][ERROR]: ${JSON.stringify(data)}`); + this._logger?.debug(`[EventSource][dispatch][ERROR]: ${JSON.stringify(data)}`); this.onerror(data); break; case 'retry': @@ -337,17 +337,17 @@ export default class EventSource { } close() { - this.status = this.CLOSED; - clearTimeout(this.connectTimer); - if (this.xhr) { - this.xhr.abort(); + this._status = this.CLOSED; + clearTimeout(this._connectTimer); + if (this._xhr) { + this._xhr.abort(); } this.dispatch('close', { type: 'close' }); } getStatus() { - return this.status; + return this._status; } onopen() {} diff --git a/packages/sdk/react-native/src/platform/ConnectionManager.ts b/packages/sdk/react-native/src/platform/ConnectionManager.ts index 8f39f76f0..4463b824b 100644 --- a/packages/sdk/react-native/src/platform/ConnectionManager.ts +++ b/packages/sdk/react-native/src/platform/ConnectionManager.ts @@ -76,61 +76,61 @@ export interface ConnectionManagerConfig { * @internal */ export class ConnectionManager { - private applicationState: ApplicationState = ApplicationState.Foreground; - private networkState: NetworkState = NetworkState.Available; - private offline: boolean = false; - private currentConnectionMode: ConnectionMode; + private _applicationState: ApplicationState = ApplicationState.Foreground; + private _networkState: NetworkState = NetworkState.Available; + private _offline: boolean = false; + private _currentConnectionMode: ConnectionMode; constructor( - private readonly logger: LDLogger, - private readonly config: ConnectionManagerConfig, - private readonly destination: ConnectionDestination, - private readonly detector: StateDetector, + private readonly _logger: LDLogger, + private readonly _config: ConnectionManagerConfig, + private readonly _destination: ConnectionDestination, + private readonly _detector: StateDetector, ) { - this.currentConnectionMode = config.initialConnectionMode; - if (config.automaticBackgroundHandling) { - detector.setApplicationStateListener((state) => { - this.applicationState = state; - this.handleState(); + this._currentConnectionMode = _config.initialConnectionMode; + if (_config.automaticBackgroundHandling) { + _detector.setApplicationStateListener((state) => { + this._applicationState = state; + this._handleState(); }); } - if (config.automaticNetworkHandling) { - detector.setNetworkStateListener((state) => { - this.networkState = state; - this.handleState(); + if (_config.automaticNetworkHandling) { + _detector.setNetworkStateListener((state) => { + this._networkState = state; + this._handleState(); }); } } public setOffline(offline: boolean): void { - this.offline = offline; - this.handleState(); + this._offline = offline; + this._handleState(); } public setConnectionMode(mode: ConnectionMode) { - this.currentConnectionMode = mode; - this.handleState(); + this._currentConnectionMode = mode; + this._handleState(); } public close() { - this.detector.stopListening(); + this._detector.stopListening(); } - private handleState(): void { - this.logger.debug(`Handling state: ${this.applicationState}:${this.networkState}`); + private _handleState(): void { + this._logger.debug(`Handling state: ${this._applicationState}:${this._networkState}`); - switch (this.networkState) { + switch (this._networkState) { case NetworkState.Unavailable: - this.destination.setNetworkAvailability(false); + this._destination.setNetworkAvailability(false); break; case NetworkState.Available: - this.destination.setNetworkAvailability(true); - switch (this.applicationState) { + this._destination.setNetworkAvailability(true); + switch (this._applicationState) { case ApplicationState.Foreground: - this.setForegroundAvailable(); + this._setForegroundAvailable(); break; case ApplicationState.Background: - this.setBackgroundAvailable(); + this._setBackgroundAvailable(); break; default: break; @@ -141,25 +141,25 @@ export class ConnectionManager { } } - private setForegroundAvailable(): void { - if (this.offline) { - this.destination.setConnectionMode('offline'); + private _setForegroundAvailable(): void { + if (this._offline) { + this._destination.setConnectionMode('offline'); // Don't attempt to flush. If the user wants to flush when entering offline // mode, then they can do that directly. - this.destination.setEventSendingEnabled(false, false); + this._destination.setEventSendingEnabled(false, false); return; } // Currently the foreground mode will always be whatever the last active // connection mode was. - this.destination.setConnectionMode(this.currentConnectionMode); - this.destination.setEventSendingEnabled(true, false); + this._destination.setConnectionMode(this._currentConnectionMode); + this._destination.setEventSendingEnabled(true, false); } - private setBackgroundAvailable(): void { - if (!this.config.runInBackground) { - this.destination.setConnectionMode('offline'); - this.destination.setEventSendingEnabled(false, true); + private _setBackgroundAvailable(): void { + if (!this._config.runInBackground) { + this._destination.setConnectionMode('offline'); + this._destination.setEventSendingEnabled(false, true); return; } @@ -167,6 +167,6 @@ export class ConnectionManager { // If connections in the background are allowed, then use the same mode // as is configured for the foreground. - this.setForegroundAvailable(); + this._setForegroundAvailable(); } } diff --git a/packages/sdk/react-native/src/platform/PlatformInfo.ts b/packages/sdk/react-native/src/platform/PlatformInfo.ts index ef68b4480..ee84d4909 100644 --- a/packages/sdk/react-native/src/platform/PlatformInfo.ts +++ b/packages/sdk/react-native/src/platform/PlatformInfo.ts @@ -4,7 +4,7 @@ import { name, version } from '../../package.json'; import { ldApplication, ldDevice } from './autoEnv'; export default class PlatformInfo implements Info { - constructor(private readonly logger: LDLogger) {} + constructor(private readonly _logger: LDLogger) {} platformData(): PlatformData { const data = { @@ -13,7 +13,7 @@ export default class PlatformInfo implements Info { ld_device: ldDevice, }; - this.logger.debug(`platformData: ${JSON.stringify(data, null, 2)}`); + this._logger.debug(`platformData: ${JSON.stringify(data, null, 2)}`); return data; } @@ -24,7 +24,7 @@ export default class PlatformInfo implements Info { userAgentBase: 'ReactNativeClient', }; - this.logger.debug(`sdkData: ${JSON.stringify(data, null, 2)}`); + this._logger.debug(`sdkData: ${JSON.stringify(data, null, 2)}`); return data; } } diff --git a/packages/sdk/react-native/src/platform/PlatformRequests.ts b/packages/sdk/react-native/src/platform/PlatformRequests.ts index 0fe8698f7..4cbe4f6da 100644 --- a/packages/sdk/react-native/src/platform/PlatformRequests.ts +++ b/packages/sdk/react-native/src/platform/PlatformRequests.ts @@ -12,7 +12,7 @@ import type { import RNEventSource from '../fromExternal/react-native-sse'; export default class PlatformRequests implements Requests { - constructor(private readonly logger: LDLogger) {} + constructor(private readonly _logger: LDLogger) {} createEventSource(url: string, eventSourceInitDict: EventSourceInitDict): EventSource { return new RNEventSource(url, { @@ -20,7 +20,7 @@ export default class PlatformRequests implements Requests { headers: eventSourceInitDict.headers, body: eventSourceInitDict.body, retryAndHandleError: eventSourceInitDict.errorFilter, - logger: this.logger, + logger: this._logger, }); } diff --git a/packages/sdk/react-native/src/platform/PlatformStorage.ts b/packages/sdk/react-native/src/platform/PlatformStorage.ts index 33e003c82..9460bdf37 100644 --- a/packages/sdk/react-native/src/platform/PlatformStorage.ts +++ b/packages/sdk/react-native/src/platform/PlatformStorage.ts @@ -3,7 +3,7 @@ import type { LDLogger, Storage } from '@launchdarkly/js-client-sdk-common'; import AsyncStorage from './ConditionalAsyncStorage'; export default class PlatformStorage implements Storage { - constructor(private readonly logger: LDLogger) {} + constructor(private readonly _logger: LDLogger) {} async clear(key: string): Promise { await AsyncStorage.removeItem(key); } @@ -13,7 +13,7 @@ export default class PlatformStorage implements Storage { const value = await AsyncStorage.getItem(key); return value ?? null; } catch (error) { - this.logger.debug(`Error getting AsyncStorage key: ${key}, error: ${error}`); + this._logger.debug(`Error getting AsyncStorage key: ${key}, error: ${error}`); return null; } } @@ -22,7 +22,7 @@ export default class PlatformStorage implements Storage { try { await AsyncStorage.setItem(key, value); } catch (error) { - this.logger.debug(`Error saving AsyncStorage key: ${key}, value: ${value}, error: ${error}`); + this._logger.debug(`Error saving AsyncStorage key: ${key}, value: ${value}, error: ${error}`); } } } diff --git a/packages/sdk/react-native/src/platform/crypto/PlatformHasher.ts b/packages/sdk/react-native/src/platform/crypto/PlatformHasher.ts index 081af2624..a1bdd6f7d 100644 --- a/packages/sdk/react-native/src/platform/crypto/PlatformHasher.ts +++ b/packages/sdk/react-native/src/platform/crypto/PlatformHasher.ts @@ -5,12 +5,12 @@ import { base64FromByteArray } from '../../polyfills'; import { SupportedHashAlgorithm, SupportedOutputEncoding } from './types'; export default class PlatformHasher implements LDHasher { - private hasher: Hasher; + private _hasher: Hasher; constructor(algorithm: SupportedHashAlgorithm, hmacKey?: string) { switch (algorithm) { case 'sha256': - this.hasher = hmacKey ? sha256.hmac.create(hmacKey) : sha256.create(); + this._hasher = hmacKey ? sha256.hmac.create(hmacKey) : sha256.create(); break; default: throw new Error(`Unsupported hash algorithm: ${algorithm}. Only sha256 is supported.`); @@ -20,9 +20,9 @@ export default class PlatformHasher implements LDHasher { digest(encoding: SupportedOutputEncoding): string { switch (encoding) { case 'base64': - return base64FromByteArray(new Uint8Array(this.hasher.arrayBuffer())); + return base64FromByteArray(new Uint8Array(this._hasher.arrayBuffer())); case 'hex': - return this.hasher.hex(); + return this._hasher.hex(); default: throw new Error( `unsupported output encoding: ${encoding}. Only base64 and hex are supported.`, @@ -31,7 +31,7 @@ export default class PlatformHasher implements LDHasher { } update(data: string): this { - this.hasher.update(data); + this._hasher.update(data); return this; } } diff --git a/packages/sdk/react-universal/src/ldClientRsc.ts b/packages/sdk/react-universal/src/ldClientRsc.ts index 8d74de47e..7fd333839 100644 --- a/packages/sdk/react-universal/src/ldClientRsc.ts +++ b/packages/sdk/react-universal/src/ldClientRsc.ts @@ -17,33 +17,33 @@ type PartialJSSdk = Omit, 'variationDetail'>; */ export class LDClientRsc implements PartialJSSdk { constructor( - private readonly ldContext: LDContext, - private readonly bootstrap: LDFlagSet, + private readonly _ldContext: LDContext, + private readonly _bootstrap: LDFlagSet, ) {} allFlags(): LDFlagSet { - return this.bootstrap; + return this._bootstrap; } getContext(): LDContext { - return this.ldContext; + return this._ldContext; } variation(key: string, defaultValue?: LDFlagValue): LDFlagValue { if (isServer) { // On the server during ssr, call variation for analytics purposes. - global.nodeSdk.variation(key, this.ldContext, defaultValue).then(/* ignore */); + global.nodeSdk.variation(key, this._ldContext, defaultValue).then(/* ignore */); } - return this.bootstrap[key] ?? defaultValue; + return this._bootstrap[key] ?? defaultValue; } variationDetail(key: string, defaultValue?: LDFlagValue): LDEvaluationDetail { if (isServer) { // On the server during ssr, call variation for analytics purposes. - global.nodeSdk.variationDetail(key, this.ldContext, defaultValue).then(/* ignore */); + global.nodeSdk.variationDetail(key, this._ldContext, defaultValue).then(/* ignore */); } - const { reason, variation: variationIndex } = this.bootstrap.$flagsState[key]; - return { value: this.bootstrap[key], reason, variationIndex }; + const { reason, variation: variationIndex } = this._bootstrap.$flagsState[key]; + return { value: this._bootstrap[key], reason, variationIndex }; } } diff --git a/packages/sdk/server-node/src/BigSegmentsStoreStatusProviderNode.ts b/packages/sdk/server-node/src/BigSegmentsStoreStatusProviderNode.ts index 52e1c3acf..c397863db 100644 --- a/packages/sdk/server-node/src/BigSegmentsStoreStatusProviderNode.ts +++ b/packages/sdk/server-node/src/BigSegmentsStoreStatusProviderNode.ts @@ -10,18 +10,18 @@ import { Emits } from './Emits'; class BigSegmentStoreStatusProviderNode implements interfaces.BigSegmentStoreStatusProvider { emitter: EventEmitter = new EventEmitter(); - constructor(private readonly provider: BigSegmentStoreStatusProviderImpl) { - this.provider.setListener((status: interfaces.BigSegmentStoreStatus) => { + constructor(private readonly _provider: BigSegmentStoreStatusProviderImpl) { + this._provider.setListener((status: interfaces.BigSegmentStoreStatus) => { this.dispatch('change', status); }); } getStatus(): interfaces.BigSegmentStoreStatus | undefined { - return this.provider.getStatus(); + return this._provider.getStatus(); } requireStatus(): Promise { - return this.provider.requireStatus(); + return this._provider.requireStatus(); } dispatch(eventType: string, status: interfaces.BigSegmentStoreStatus) { diff --git a/packages/sdk/server-node/src/platform/HeaderWrapper.ts b/packages/sdk/server-node/src/platform/HeaderWrapper.ts index fc84250fc..166d73854 100644 --- a/packages/sdk/server-node/src/platform/HeaderWrapper.ts +++ b/packages/sdk/server-node/src/platform/HeaderWrapper.ts @@ -7,14 +7,14 @@ import { platform } from '@launchdarkly/js-server-sdk-common'; * @internal */ export default class HeaderWrapper implements platform.Headers { - private headers: http.IncomingHttpHeaders; + private _headers: http.IncomingHttpHeaders; constructor(headers: http.IncomingHttpHeaders) { - this.headers = headers; + this._headers = headers; } - private headerVal(name: string) { - const val = this.headers[name]; + private _headerVal(name: string) { + const val = this._headers[name]; if (val === undefined || val === null) { return null; } @@ -25,11 +25,11 @@ export default class HeaderWrapper implements platform.Headers { } get(name: string): string | null { - return this.headerVal(name); + return this._headerVal(name); } keys(): Iterable { - return Object.keys(this.headers); + return Object.keys(this._headers); } // We want to use generators here for the simplicity of maintaining @@ -55,6 +55,6 @@ export default class HeaderWrapper implements platform.Headers { } has(name: string): boolean { - return Object.prototype.hasOwnProperty.call(this.headers, name); + return Object.prototype.hasOwnProperty.call(this._headers, name); } } diff --git a/packages/sdk/server-node/src/platform/NodeInfo.ts b/packages/sdk/server-node/src/platform/NodeInfo.ts index 1494d8362..3f30355b3 100644 --- a/packages/sdk/server-node/src/platform/NodeInfo.ts +++ b/packages/sdk/server-node/src/platform/NodeInfo.ts @@ -19,7 +19,7 @@ function processPlatformName(name: string): string { } export default class NodeInfo implements platform.Info { - constructor(private readonly config: { wrapperName?: string; wrapperVersion?: string }) {} + constructor(private readonly _config: { wrapperName?: string; wrapperVersion?: string }) {} platformData(): platform.PlatformData { return { os: { @@ -39,8 +39,8 @@ export default class NodeInfo implements platform.Info { name: packageJson.name, version: packageJson.version, userAgentBase: 'NodeJSClient', - wrapperName: this.config.wrapperName, - wrapperVersion: this.config.wrapperVersion, + wrapperName: this._config.wrapperName, + wrapperVersion: this._config.wrapperVersion, }; } } diff --git a/packages/sdk/server-node/src/platform/NodeRequests.ts b/packages/sdk/server-node/src/platform/NodeRequests.ts index ede410b63..8c4b48755 100644 --- a/packages/sdk/server-node/src/platform/NodeRequests.ts +++ b/packages/sdk/server-node/src/platform/NodeRequests.ts @@ -93,18 +93,18 @@ function createAgent( } export default class NodeRequests implements platform.Requests { - private agent: https.Agent | http.Agent | undefined; + private _agent: https.Agent | http.Agent | undefined; - private tlsOptions: LDTLSOptions | undefined; + private _tlsOptions: LDTLSOptions | undefined; - private hasProxy: boolean = false; + private _hasProxy: boolean = false; - private hasProxyAuth: boolean = false; + private _hasProxyAuth: boolean = false; constructor(tlsOptions?: LDTLSOptions, proxyOptions?: LDProxyOptions, logger?: LDLogger) { - this.agent = createAgent(tlsOptions, proxyOptions, logger); - this.hasProxy = !!proxyOptions; - this.hasProxyAuth = !!proxyOptions?.auth; + this._agent = createAgent(tlsOptions, proxyOptions, logger); + this._hasProxy = !!proxyOptions; + this._hasProxyAuth = !!proxyOptions?.auth; } fetch(url: string, options: platform.Options = {}): Promise { @@ -128,7 +128,7 @@ export default class NodeRequests implements platform.Requests { timeout: options.timeout, headers, method: options.method, - agent: this.agent, + agent: this._agent, }, (res) => resolve(new NodeResponse(res)), ); @@ -151,8 +151,8 @@ export default class NodeRequests implements platform.Requests { ): platform.EventSource { const expandedOptions = { ...eventSourceInitDict, - agent: this.agent, - tlsParams: this.tlsOptions, + agent: this._agent, + tlsParams: this._tlsOptions, maxBackoffMillis: 30 * 1000, jitterRatio: 0.5, }; @@ -168,10 +168,10 @@ export default class NodeRequests implements platform.Requests { } usingProxy(): boolean { - return this.hasProxy; + return this._hasProxy; } usingProxyAuth(): boolean { - return this.hasProxyAuth; + return this._hasProxyAuth; } } diff --git a/packages/sdk/server-node/src/platform/NodeResponse.ts b/packages/sdk/server-node/src/platform/NodeResponse.ts index 2f765b23f..1309d276f 100644 --- a/packages/sdk/server-node/src/platform/NodeResponse.ts +++ b/packages/sdk/server-node/src/platform/NodeResponse.ts @@ -57,7 +57,7 @@ export default class NodeResponse implements platform.Response { }); } - private async wrappedWait(): Promise { + private async _wrappedWait(): Promise { this.listened = true; if (this.rejection) { throw this.rejection; @@ -66,11 +66,11 @@ export default class NodeResponse implements platform.Response { } text(): Promise { - return this.wrappedWait(); + return this._wrappedWait(); } async json(): Promise { - const stringValue = await this.wrappedWait(); + const stringValue = await this._wrappedWait(); return JSON.parse(stringValue); } } diff --git a/packages/shared/akamai-edgeworker-sdk/src/api/LDClient.ts b/packages/shared/akamai-edgeworker-sdk/src/api/LDClient.ts index 891ae0aaf..a34fc73a3 100644 --- a/packages/shared/akamai-edgeworker-sdk/src/api/LDClient.ts +++ b/packages/shared/akamai-edgeworker-sdk/src/api/LDClient.ts @@ -20,7 +20,7 @@ export interface CustomLDOptions extends LDOptions {} * The LaunchDarkly Akamai SDK edge client object. */ class LDClient extends LDClientImpl { - private cacheableStoreProvider!: CacheableStoreProvider; + private _cacheableStoreProvider!: CacheableStoreProvider; // sdkKey is only used to query featureStore, not to initialize with LD servers constructor( @@ -31,7 +31,7 @@ class LDClient extends LDClientImpl { ) { const finalOptions = createOptions(options); super(sdkKey, platform, finalOptions, createCallbacks(finalOptions.logger)); - this.cacheableStoreProvider = storeProvider; + this._cacheableStoreProvider = storeProvider; } override waitForInitialization(): Promise { @@ -46,7 +46,7 @@ class LDClient extends LDClientImpl { defaultValue: LDFlagValue, callback?: (err: any, res: LDFlagValue) => void, ): Promise { - await this.cacheableStoreProvider.prefetchPayloadFromOriginStore(); + await this._cacheableStoreProvider.prefetchPayloadFromOriginStore(); return super.variation(key, context, defaultValue, callback); } @@ -56,7 +56,7 @@ class LDClient extends LDClientImpl { defaultValue: LDFlagValue, callback?: (err: any, res: LDEvaluationDetail) => void, ): Promise { - await this.cacheableStoreProvider.prefetchPayloadFromOriginStore(); + await this._cacheableStoreProvider.prefetchPayloadFromOriginStore(); return super.variationDetail(key, context, defaultValue, callback); } @@ -65,7 +65,7 @@ class LDClient extends LDClientImpl { options?: LDFlagsStateOptions, callback?: (err: Error | null, res: LDFlagsState) => void, ): Promise { - await this.cacheableStoreProvider.prefetchPayloadFromOriginStore(); + await this._cacheableStoreProvider.prefetchPayloadFromOriginStore(); return super.allFlagsState(context, options, callback); } } diff --git a/packages/shared/akamai-edgeworker-sdk/src/featureStore/cacheableStoreProvider.ts b/packages/shared/akamai-edgeworker-sdk/src/featureStore/cacheableStoreProvider.ts index 7dc345cec..11aecdf65 100644 --- a/packages/shared/akamai-edgeworker-sdk/src/featureStore/cacheableStoreProvider.ts +++ b/packages/shared/akamai-edgeworker-sdk/src/featureStore/cacheableStoreProvider.ts @@ -8,8 +8,8 @@ export default class CacheableStoreProvider implements EdgeProvider { cache: string | null | undefined; constructor( - private readonly edgeProvider: EdgeProvider, - private readonly rootKey: string, + private readonly _edgeProvider: EdgeProvider, + private readonly _rootKey: string, ) {} /** @@ -19,7 +19,7 @@ export default class CacheableStoreProvider implements EdgeProvider { */ async get(rootKey: string): Promise { if (!this.cache) { - this.cache = await this.edgeProvider.get(rootKey); + this.cache = await this._edgeProvider.get(rootKey); } return this.cache; @@ -34,6 +34,6 @@ export default class CacheableStoreProvider implements EdgeProvider { */ async prefetchPayloadFromOriginStore(rootKey?: string): Promise { this.cache = undefined; // clear the cache so that new data can be fetched from the origin - return this.get(rootKey || this.rootKey); + return this.get(rootKey || this._rootKey); } } diff --git a/packages/shared/akamai-edgeworker-sdk/src/featureStore/index.ts b/packages/shared/akamai-edgeworker-sdk/src/featureStore/index.ts index 812ae1dfa..18960e1f7 100644 --- a/packages/shared/akamai-edgeworker-sdk/src/featureStore/index.ts +++ b/packages/shared/akamai-edgeworker-sdk/src/featureStore/index.ts @@ -20,15 +20,15 @@ export interface EdgeProvider { export const buildRootKey = (sdkKey: string) => `LD-Env-${sdkKey}`; export class EdgeFeatureStore implements LDFeatureStore { - private readonly rootKey: string; + private readonly _rootKey: string; constructor( - private readonly edgeProvider: EdgeProvider, - private readonly sdkKey: string, - private readonly description: string, - private logger: LDLogger, + private readonly _edgeProvider: EdgeProvider, + private readonly _sdkKey: string, + private readonly _description: string, + private _logger: LDLogger, ) { - this.rootKey = buildRootKey(this.sdkKey); + this._rootKey = buildRootKey(this._sdkKey); } async get( @@ -38,13 +38,13 @@ export class EdgeFeatureStore implements LDFeatureStore { ): Promise { const { namespace } = kind; const kindKey = namespace === 'features' ? 'flags' : namespace; - this.logger.debug(`Requesting ${dataKey} from ${this.rootKey}.${kindKey}`); + this._logger.debug(`Requesting ${dataKey} from ${this._rootKey}.${kindKey}`); try { - const i = await this.edgeProvider.get(this.rootKey); + const i = await this._edgeProvider.get(this._rootKey); if (!i) { - throw new Error(`${this.rootKey}.${kindKey} is not found in KV.`); + throw new Error(`${this._rootKey}.${kindKey} is not found in KV.`); } const item = deserializePoll(i); @@ -63,7 +63,7 @@ export class EdgeFeatureStore implements LDFeatureStore { callback(null); } } catch (err) { - this.logger.error(err); + this._logger.error(err); callback(null); } } @@ -71,11 +71,11 @@ export class EdgeFeatureStore implements LDFeatureStore { async all(kind: DataKind, callback: (res: LDFeatureStoreKindData) => void = noop): Promise { const { namespace } = kind; const kindKey = namespace === 'features' ? 'flags' : namespace; - this.logger.debug(`Requesting all from ${this.rootKey}.${kindKey}`); + this._logger.debug(`Requesting all from ${this._rootKey}.${kindKey}`); try { - const i = await this.edgeProvider.get(this.rootKey); + const i = await this._edgeProvider.get(this._rootKey); if (!i) { - throw new Error(`${this.rootKey}.${kindKey} is not found in KV.`); + throw new Error(`${this._rootKey}.${kindKey} is not found in KV.`); } const item = deserializePoll(i); @@ -94,15 +94,15 @@ export class EdgeFeatureStore implements LDFeatureStore { throw new Error(`Unsupported DataKind: ${namespace}`); } } catch (err) { - this.logger.error(err); + this._logger.error(err); callback({}); } } async initialized(callback: (isInitialized: boolean) => void = noop): Promise { - const config = await this.edgeProvider.get(this.rootKey); + const config = await this._edgeProvider.get(this._rootKey); const result = config !== null; - this.logger.debug(`Is ${this.rootKey} initialized? ${result}`); + this._logger.debug(`Is ${this._rootKey} initialized? ${result}`); callback(result); } @@ -111,7 +111,7 @@ export class EdgeFeatureStore implements LDFeatureStore { } getDescription(): string { - return this.description; + return this._description; } // unused diff --git a/packages/shared/akamai-edgeworker-sdk/src/platform/crypto/cryptoJSHasher.ts b/packages/shared/akamai-edgeworker-sdk/src/platform/crypto/cryptoJSHasher.ts index 8d5cab22a..a75f13abd 100644 --- a/packages/shared/akamai-edgeworker-sdk/src/platform/crypto/cryptoJSHasher.ts +++ b/packages/shared/akamai-edgeworker-sdk/src/platform/crypto/cryptoJSHasher.ts @@ -7,7 +7,7 @@ import { Hasher as LDHasher } from '@launchdarkly/js-server-sdk-common'; import { SupportedHashAlgorithm, SupportedOutputEncoding } from './types'; export default class CryptoJSHasher implements LDHasher { - private cryptoJSHasher; + private _cryptoJSHasher; constructor(algorithm: SupportedHashAlgorithm) { let algo; @@ -23,11 +23,11 @@ export default class CryptoJSHasher implements LDHasher { throw new Error('unsupported hash algorithm. Only sha1 and sha256 are supported.'); } - this.cryptoJSHasher = algo.create(); + this._cryptoJSHasher = algo.create(); } digest(encoding: SupportedOutputEncoding): string { - const result = this.cryptoJSHasher.finalize(); + const result = this._cryptoJSHasher.finalize(); let enc; switch (encoding) { @@ -45,7 +45,7 @@ export default class CryptoJSHasher implements LDHasher { } update(data: string): this { - this.cryptoJSHasher.update(data); + this._cryptoJSHasher.update(data); return this; } } diff --git a/packages/shared/akamai-edgeworker-sdk/src/platform/crypto/cryptoJSHmac.ts b/packages/shared/akamai-edgeworker-sdk/src/platform/crypto/cryptoJSHmac.ts index 050a84bde..bc23f04d8 100644 --- a/packages/shared/akamai-edgeworker-sdk/src/platform/crypto/cryptoJSHmac.ts +++ b/packages/shared/akamai-edgeworker-sdk/src/platform/crypto/cryptoJSHmac.ts @@ -5,7 +5,7 @@ import { Hmac as LDHmac } from '@launchdarkly/js-server-sdk-common'; import { SupportedHashAlgorithm, SupportedOutputEncoding } from './types'; export default class CryptoJSHmac implements LDHmac { - private CryptoJSHmac; + private _cryptoJSHmac; constructor(algorithm: SupportedHashAlgorithm, key: string) { let algo; @@ -21,11 +21,11 @@ export default class CryptoJSHmac implements LDHmac { throw new Error('unsupported hash algorithm. Only sha1 and sha256 are supported.'); } - this.CryptoJSHmac = CryptoAlgo.HMAC.create(algo, key); + this._cryptoJSHmac = CryptoAlgo.HMAC.create(algo, key); } digest(encoding: SupportedOutputEncoding): string { - const result = this.CryptoJSHmac.finalize(); + const result = this._cryptoJSHmac.finalize(); if (encoding === 'base64') { return result.toString(CryptoJS.enc.Base64); @@ -39,7 +39,7 @@ export default class CryptoJSHmac implements LDHmac { } update(data: string): this { - this.CryptoJSHmac.update(data); + this._cryptoJSHmac.update(data); return this; } } diff --git a/packages/shared/akamai-edgeworker-sdk/src/platform/info/index.ts b/packages/shared/akamai-edgeworker-sdk/src/platform/info/index.ts index 557b312cb..c2b88218e 100644 --- a/packages/shared/akamai-edgeworker-sdk/src/platform/info/index.ts +++ b/packages/shared/akamai-edgeworker-sdk/src/platform/info/index.ts @@ -2,21 +2,21 @@ import type { Info, PlatformData, SdkData } from '@launchdarkly/js-server-sdk-co class AkamaiPlatformInfo implements Info { constructor( - private platformName: string, - private sdkName: string, - private sdkVersion: string, + private _platformName: string, + private _sdkName: string, + private _sdkVersion: string, ) {} platformData(): PlatformData { return { - name: this.platformName, + name: this._platformName, }; } sdkData(): SdkData { return { - name: this.sdkName, - version: this.sdkVersion, + name: this._sdkName, + version: this._sdkVersion, userAgentBase: 'AkamaiEdgeSDK', }; } diff --git a/packages/shared/common/src/AttributeReference.ts b/packages/shared/common/src/AttributeReference.ts index a84c03db9..497381831 100644 --- a/packages/shared/common/src/AttributeReference.ts +++ b/packages/shared/common/src/AttributeReference.ts @@ -43,9 +43,9 @@ export default class AttributeReference { /** * For use as invalid references when deserializing Flag/Segment data. */ - public static readonly invalidReference = new AttributeReference(''); + public static readonly InvalidReference = new AttributeReference(''); - private readonly components: string[]; + private readonly _components: string[]; /** * Take an attribute reference string, or literal string, and produce @@ -66,29 +66,29 @@ export default class AttributeReference { this.redactionName = refOrLiteral; if (refOrLiteral === '' || refOrLiteral === '/' || !validate(refOrLiteral)) { this.isValid = false; - this.components = []; + this._components = []; return; } if (isLiteral(refOrLiteral)) { - this.components = [refOrLiteral]; + this._components = [refOrLiteral]; } else if (refOrLiteral.indexOf('/', 1) < 0) { - this.components = [unescape(refOrLiteral.slice(1))]; + this._components = [unescape(refOrLiteral.slice(1))]; } else { - this.components = getComponents(refOrLiteral); + this._components = getComponents(refOrLiteral); } // The items inside of '_meta' are not intended to be addressable. // Excluding it as a valid reference means that we can make it non-addressable // without having to copy all the attributes out of the context object // provided by the user. - if (this.components[0] === '_meta') { + if (this._components[0] === '_meta') { this.isValid = false; } else { this.isValid = true; } } else { const literalVal = refOrLiteral; - this.components = [literalVal]; + this._components = [literalVal]; this.isValid = literalVal !== ''; // Literals which start with '/' need escaped to prevent ambiguity. this.redactionName = literalVal.startsWith('/') ? toRefString(literalVal) : literalVal; @@ -96,7 +96,7 @@ export default class AttributeReference { } public get(target: LDContextCommon) { - const { components, isValid } = this; + const { _components: components, isValid } = this; if (!isValid) { return undefined; } @@ -127,21 +127,21 @@ export default class AttributeReference { } public getComponent(depth: number) { - return this.components[depth]; + return this._components[depth]; } public get depth() { - return this.components.length; + return this._components.length; } public get isKind(): boolean { - return this.components.length === 1 && this.components[0] === 'kind'; + return this._components.length === 1 && this._components[0] === 'kind'; } public compare(other: AttributeReference) { return ( this.depth === other.depth && - this.components.every((value, index) => value === other.getComponent(index)) + this._components.every((value, index) => value === other.getComponent(index)) ); } } diff --git a/packages/shared/common/src/Context.ts b/packages/shared/common/src/Context.ts index 9220afcdf..6b4a0bca7 100644 --- a/packages/shared/common/src/Context.ts +++ b/packages/shared/common/src/Context.ts @@ -158,17 +158,17 @@ function legacyToSingleKind(user: LDUser): LDSingleKindContext { * the type system. */ export default class Context { - private context?: LDContextCommon; + private _context?: LDContextCommon; - private isMulti: boolean = false; + private _isMulti: boolean = false; - private isUser: boolean = false; + private _isUser: boolean = false; - private wasLegacy: boolean = false; + private _wasLegacy: boolean = false; - private contexts: Record = {}; + private _contexts: Record = {}; - private privateAttributeReferences?: Record; + private _privateAttributeReferences?: Record; public readonly kind: string; @@ -180,7 +180,7 @@ export default class Context { public readonly message?: string; - static readonly userKind: string = DEFAULT_KIND; + static readonly UserKind: string = DEFAULT_KIND; /** * Contexts should be created using the static factory method {@link Context.fromLDContext}. @@ -195,11 +195,11 @@ export default class Context { this.message = message; } - private static contextForError(kind: string, message: string) { + private static _contextForError(kind: string, message: string) { return new Context(false, kind, message); } - private static getValueFromContext( + private static _getValueFromContext( reference: AttributeReference, context?: LDContextCommon, ): any { @@ -213,29 +213,29 @@ export default class Context { return reference.get(context); } - private contextForKind(kind: string): LDContextCommon | undefined { - if (this.isMulti) { - return this.contexts[kind]; + private _contextForKind(kind: string): LDContextCommon | undefined { + if (this._isMulti) { + return this._contexts[kind]; } if (this.kind === kind) { - return this.context; + return this._context; } return undefined; } - private static fromMultiKindContext(context: LDMultiKindContext): Context { + private static _fromMultiKindContext(context: LDMultiKindContext): Context { const kinds = Object.keys(context).filter((key) => key !== 'kind'); const kindsValid = kinds.every(validKind); if (!kinds.length) { - return Context.contextForError( + return Context._contextForError( 'multi', 'A multi-kind context must contain at least one kind', ); } if (!kindsValid) { - return Context.contextForError('multi', 'Context contains invalid kinds'); + return Context._contextForError('multi', 'Context contains invalid kinds'); } const privateAttributes: Record = {}; @@ -253,11 +253,11 @@ export default class Context { }, {}); if (!contextsAreObjects) { - return Context.contextForError('multi', 'Context contained contexts that were not objects'); + return Context._contextForError('multi', 'Context contained contexts that were not objects'); } if (!Object.values(contexts).every((part) => validKey(part.key))) { - return Context.contextForError('multi', 'Context contained invalid keys'); + return Context._contextForError('multi', 'Context contained invalid keys'); } // There was only a single kind in the multi-kind context. @@ -265,56 +265,56 @@ export default class Context { if (kinds.length === 1) { const kind = kinds[0]; const created = new Context(true, kind); - created.context = { ...contexts[kind], kind }; - created.privateAttributeReferences = privateAttributes; - created.isUser = kind === 'user'; + created._context = { ...contexts[kind], kind }; + created._privateAttributeReferences = privateAttributes; + created._isUser = kind === 'user'; return created; } const created = new Context(true, context.kind); - created.contexts = contexts; - created.privateAttributeReferences = privateAttributes; + created._contexts = contexts; + created._privateAttributeReferences = privateAttributes; - created.isMulti = true; + created._isMulti = true; return created; } - private static fromSingleKindContext(context: LDSingleKindContext): Context { + private static _fromSingleKindContext(context: LDSingleKindContext): Context { const { key, kind } = context; const kindValid = validKind(kind); const keyValid = validKey(key); if (!kindValid) { - return Context.contextForError(kind ?? 'unknown', 'The kind was not valid for the context'); + return Context._contextForError(kind ?? 'unknown', 'The kind was not valid for the context'); } if (!keyValid) { - return Context.contextForError(kind, 'The key for the context was not valid'); + return Context._contextForError(kind, 'The key for the context was not valid'); } // The JSON interfaces uses dangling _. // eslint-disable-next-line no-underscore-dangle const privateAttributeReferences = processPrivateAttributes(context._meta?.privateAttributes); const created = new Context(true, kind); - created.isUser = kind === 'user'; - created.context = context; - created.privateAttributeReferences = { + created._isUser = kind === 'user'; + created._context = context; + created._privateAttributeReferences = { [kind]: privateAttributeReferences, }; return created; } - private static fromLegacyUser(context: LDUser): Context { + private static _fromLegacyUser(context: LDUser): Context { const keyValid = context.key !== undefined && context.key !== null; // For legacy users we allow empty keys. if (!keyValid) { - return Context.contextForError('user', 'The key for the context was not valid'); + return Context._contextForError('user', 'The key for the context was not valid'); } const created = new Context(true, 'user'); - created.isUser = true; - created.wasLegacy = true; - created.context = legacyToSingleKind(context); - created.privateAttributeReferences = { + created._isUser = true; + created._wasLegacy = true; + created._context = legacyToSingleKind(context); + created._privateAttributeReferences = { user: processPrivateAttributes(context.privateAttributeNames, true), }; return created; @@ -328,19 +328,19 @@ export default class Context { */ public static fromLDContext(context: LDContext): Context { if (!context) { - return Context.contextForError('unknown', 'No context specified. Returning default value'); + return Context._contextForError('unknown', 'No context specified. Returning default value'); } if (isSingleKind(context)) { - return Context.fromSingleKindContext(context); + return Context._fromSingleKindContext(context); } if (isMultiKind(context)) { - return Context.fromMultiKindContext(context); + return Context._fromMultiKindContext(context); } if (isLegacyUser(context)) { - return Context.fromLegacyUser(context); + return Context._fromLegacyUser(context); } - return Context.contextForError('unknown', 'Context was not of a valid kind'); + return Context._contextForError('unknown', 'Context was not of a valid kind'); } /** @@ -354,7 +354,7 @@ export default class Context { } const contexts = context.getContexts(); - if (!context.isMulti) { + if (!context._isMulti) { return contexts[0][1]; } const result: LDMultiKindContext = { @@ -378,7 +378,7 @@ export default class Context { if (reference.isKind) { return this.kinds; } - return Context.getValueFromContext(reference, this.contextForKind(kind)); + return Context._getValueFromContext(reference, this._contextForKind(kind)); } /** @@ -387,38 +387,38 @@ export default class Context { * @returns The key for the specified kind, or undefined. */ public key(kind: string = DEFAULT_KIND): string | undefined { - return this.contextForKind(kind)?.key; + return this._contextForKind(kind)?.key; } /** * True if this is a multi-kind context. */ public get isMultiKind(): boolean { - return this.isMulti; + return this._isMulti; } /** * Get the canonical key for this context. */ public get canonicalKey(): string { - if (this.isUser) { - return this.context!.key; + if (this._isUser) { + return this._context!.key; } - if (this.isMulti) { - return Object.keys(this.contexts) + if (this._isMulti) { + return Object.keys(this._contexts) .sort() - .map((key) => `${key}:${encodeKey(this.contexts[key].key)}`) + .map((key) => `${key}:${encodeKey(this._contexts[key].key)}`) .join(':'); } - return `${this.kind}:${encodeKey(this.context!.key)}`; + return `${this.kind}:${encodeKey(this._context!.key)}`; } /** * Get the kinds of this context. */ public get kinds(): string[] { - if (this.isMulti) { - return Object.keys(this.contexts); + if (this._isMulti) { + return Object.keys(this._contexts); } return [this.kind]; } @@ -427,8 +427,8 @@ export default class Context { * Get the kinds, and their keys, for this context. */ public get kindsAndKeys(): Record { - if (this.isMulti) { - return Object.entries(this.contexts).reduce( + if (this._isMulti) { + return Object.entries(this._contexts).reduce( (acc: Record, [kind, context]) => { acc[kind] = context.key; return acc; @@ -436,7 +436,7 @@ export default class Context { {}, ); } - return { [this.kind]: this.context!.key }; + return { [this.kind]: this._context!.key }; } /** @@ -445,7 +445,7 @@ export default class Context { * @param kind */ public privateAttributes(kind: string): AttributeReference[] { - return this.privateAttributeReferences?.[kind] || []; + return this._privateAttributeReferences?.[kind] || []; } /** @@ -456,13 +456,13 @@ export default class Context { * The returned objects should not be modified. */ public getContexts(): [string, LDContextCommon][] { - if (this.isMulti) { - return Object.entries(this.contexts); + if (this._isMulti) { + return Object.entries(this._contexts); } - return [[this.kind, this.context!]]; + return [[this.kind, this._context!]]; } public get legacy(): boolean { - return this.wasLegacy; + return this._wasLegacy; } } diff --git a/packages/shared/common/src/ContextFilter.ts b/packages/shared/common/src/ContextFilter.ts index f0373618d..34f78a53e 100644 --- a/packages/shared/common/src/ContextFilter.ts +++ b/packages/shared/common/src/ContextFilter.ts @@ -94,14 +94,14 @@ function cloneWithRedactions(target: LDContextCommon, references: AttributeRefer export default class ContextFilter { constructor( - private readonly allAttributesPrivate: boolean, - private readonly privateAttributes: AttributeReference[], + private readonly _allAttributesPrivate: boolean, + private readonly _privateAttributes: AttributeReference[], ) {} filter(context: Context, redactAnonymousAttributes: boolean = false): any { const contexts = context.getContexts(); if (contexts.length === 1) { - return this.filterSingleKind( + return this._filterSingleKind( context, contexts[0][1], contexts[0][0], @@ -112,12 +112,17 @@ export default class ContextFilter { kind: 'multi', }; contexts.forEach(([kind, single]) => { - filteredMulti[kind] = this.filterSingleKind(context, single, kind, redactAnonymousAttributes); + filteredMulti[kind] = this._filterSingleKind( + context, + single, + kind, + redactAnonymousAttributes, + ); }); return filteredMulti; } - private getAttributesToFilter( + private _getAttributesToFilter( context: Context, single: LDContextCommon, kind: string, @@ -126,21 +131,21 @@ export default class ContextFilter { return ( redactAllAttributes ? Object.keys(single).map((k) => new AttributeReference(k, true)) - : [...this.privateAttributes, ...context.privateAttributes(kind)] + : [...this._privateAttributes, ...context.privateAttributes(kind)] ).filter((attr) => !protectedAttributes.some((protectedAttr) => protectedAttr.compare(attr))); } - private filterSingleKind( + private _filterSingleKind( context: Context, single: LDContextCommon, kind: string, redactAnonymousAttributes: boolean, ): any { const redactAllAttributes = - this.allAttributesPrivate || (redactAnonymousAttributes && single.anonymous === true); + this._allAttributesPrivate || (redactAnonymousAttributes && single.anonymous === true); const { cloned, excluded } = cloneWithRedactions( single, - this.getAttributesToFilter(context, single, kind, redactAllAttributes), + this._getAttributesToFilter(context, single, kind, redactAllAttributes), ); if (context.legacy) { diff --git a/packages/shared/common/src/internal/diagnostics/DiagnosticsManager.ts b/packages/shared/common/src/internal/diagnostics/DiagnosticsManager.ts index 4516b9f17..438adb39f 100644 --- a/packages/shared/common/src/internal/diagnostics/DiagnosticsManager.ts +++ b/packages/shared/common/src/internal/diagnostics/DiagnosticsManager.ts @@ -2,20 +2,20 @@ import { Platform } from '../../api'; import { DiagnosticId, DiagnosticInitEvent, DiagnosticStatsEvent, StreamInitData } from './types'; export default class DiagnosticsManager { - private readonly startTime: number; - private streamInits: StreamInitData[] = []; - private readonly id: DiagnosticId; - private dataSinceDate: number; + private readonly _startTime: number; + private _streamInits: StreamInitData[] = []; + private readonly _id: DiagnosticId; + private _dataSinceDate: number; constructor( sdkKey: string, - private readonly platform: Platform, - private readonly diagnosticInitConfig: any, + private readonly _platform: Platform, + private readonly _diagnosticInitConfig: any, ) { - this.startTime = Date.now(); - this.dataSinceDate = this.startTime; - this.id = { - diagnosticId: platform.crypto.randomUUID(), + this._startTime = Date.now(); + this._dataSinceDate = this._startTime; + this._id = { + diagnosticId: _platform.crypto.randomUUID(), sdkKeySuffix: sdkKey.length > 6 ? sdkKey.substring(sdkKey.length - 6) : sdkKey, }; } @@ -25,15 +25,15 @@ export default class DiagnosticsManager { * not be repeated during the lifetime of the SDK client. */ createInitEvent(): DiagnosticInitEvent { - const sdkData = this.platform.info.sdkData(); - const platformData = this.platform.info.platformData(); + const sdkData = this._platform.info.sdkData(); + const platformData = this._platform.info.platformData(); return { kind: 'diagnostic-init', - id: this.id, - creationDate: this.startTime, + id: this._id, + creationDate: this._startTime, sdk: sdkData, - configuration: this.diagnosticInitConfig, + configuration: this._diagnosticInitConfig, platform: { name: platformData.name, osArch: platformData.os?.arch, @@ -54,7 +54,7 @@ export default class DiagnosticsManager { */ recordStreamInit(timestamp: number, failed: boolean, durationMillis: number) { const item = { timestamp, failed, durationMillis }; - this.streamInits.push(item); + this._streamInits.push(item); } /** @@ -73,17 +73,17 @@ export default class DiagnosticsManager { const currentTime = Date.now(); const evt: DiagnosticStatsEvent = { kind: 'diagnostic', - id: this.id, + id: this._id, creationDate: currentTime, - dataSinceDate: this.dataSinceDate, + dataSinceDate: this._dataSinceDate, droppedEvents, deduplicatedUsers, eventsInLastBatch, - streamInits: this.streamInits, + streamInits: this._streamInits, }; - this.streamInits = []; - this.dataSinceDate = currentTime; + this._streamInits = []; + this._dataSinceDate = currentTime; return evt; } } diff --git a/packages/shared/common/src/internal/evaluation/EventFactoryBase.ts b/packages/shared/common/src/internal/evaluation/EventFactoryBase.ts index 0b877a400..cba0feff2 100644 --- a/packages/shared/common/src/internal/evaluation/EventFactoryBase.ts +++ b/packages/shared/common/src/internal/evaluation/EventFactoryBase.ts @@ -19,11 +19,11 @@ export type EvalEventArgs = { }; export default class EventFactoryBase { - constructor(private readonly withReasons: boolean) {} + constructor(private readonly _withReasons: boolean) {} evalEvent(e: EvalEventArgs): InputEvalEvent { return new InputEvalEvent( - this.withReasons, + this._withReasons, e.context, e.flagKey, e.value, @@ -33,7 +33,7 @@ export default class EventFactoryBase { e.variation ?? undefined, e.trackEvents || e.addExperimentData, e.prereqOfFlagKey, - this.withReasons || e.addExperimentData ? e.reason : undefined, + this._withReasons || e.addExperimentData ? e.reason : undefined, e.debugEventsUntilDate, e.excludeFromSummaries, e.samplingRatio, @@ -42,7 +42,7 @@ export default class EventFactoryBase { unknownFlagEvent(key: string, defVal: LDFlagValue, context: Context) { return new InputEvalEvent( - this.withReasons, + this._withReasons, context, key, defVal, diff --git a/packages/shared/common/src/internal/events/ClientMessages.ts b/packages/shared/common/src/internal/events/ClientMessages.ts index 99e5df43c..8564952da 100644 --- a/packages/shared/common/src/internal/events/ClientMessages.ts +++ b/packages/shared/common/src/internal/events/ClientMessages.ts @@ -2,7 +2,7 @@ * Messages for issues which can be encountered processing client requests. */ export default class ClientMessages { - static readonly missingContextKeyNoEvent = + static readonly MissingContextKeyNoEvent = 'Context was unspecified or had no key; event will not be sent'; static invalidMetricValue(badType: string) { diff --git a/packages/shared/common/src/internal/events/EventProcessor.ts b/packages/shared/common/src/internal/events/EventProcessor.ts index 4e6acd128..76c7bf1ac 100644 --- a/packages/shared/common/src/internal/events/EventProcessor.ts +++ b/packages/shared/common/src/internal/events/EventProcessor.ts @@ -105,40 +105,40 @@ export interface EventProcessorOptions { } export default class EventProcessor implements LDEventProcessor { - private eventSender: EventSender; - private summarizer = new EventSummarizer(); - private queue: OutputEvent[] = []; - private lastKnownPastTime = 0; - private droppedEvents = 0; - private deduplicatedUsers = 0; - private exceededCapacity = false; - private eventsInLastBatch = 0; - private shutdown = false; - private capacity: number; - private logger?: LDLogger; - private contextFilter: ContextFilter; + private _eventSender: EventSender; + private _summarizer = new EventSummarizer(); + private _queue: OutputEvent[] = []; + private _lastKnownPastTime = 0; + private _droppedEvents = 0; + private _deduplicatedUsers = 0; + private _exceededCapacity = false; + private _eventsInLastBatch = 0; + private _shutdown = false; + private _capacity: number; + private _logger?: LDLogger; + private _contextFilter: ContextFilter; // Using any here, because setInterval handles are not the same // between node and web. - private diagnosticsTimer: any; - private flushTimer: any; - private flushUsersTimer: any = null; + private _diagnosticsTimer: any; + private _flushTimer: any; + private _flushUsersTimer: any = null; constructor( - private readonly config: EventProcessorOptions, + private readonly _config: EventProcessorOptions, clientContext: ClientContext, baseHeaders: LDHeaders, - private readonly contextDeduplicator?: LDContextDeduplicator, - private readonly diagnosticsManager?: DiagnosticsManager, + private readonly _contextDeduplicator?: LDContextDeduplicator, + private readonly _diagnosticsManager?: DiagnosticsManager, start: boolean = true, ) { - this.capacity = config.eventsCapacity; - this.logger = clientContext.basicConfiguration.logger; - this.eventSender = new EventSender(clientContext, baseHeaders); + this._capacity = _config.eventsCapacity; + this._logger = clientContext.basicConfiguration.logger; + this._eventSender = new EventSender(clientContext, baseHeaders); - this.contextFilter = new ContextFilter( - config.allAttributesPrivate, - config.privateAttributes.map((ref) => new AttributeReference(ref)), + this._contextFilter = new ContextFilter( + _config.allAttributesPrivate, + _config.privateAttributes.map((ref) => new AttributeReference(ref)), ); if (start) { @@ -147,58 +147,58 @@ export default class EventProcessor implements LDEventProcessor { } start() { - if (this.contextDeduplicator?.flushInterval !== undefined) { - this.flushUsersTimer = setInterval(() => { - this.contextDeduplicator?.flush(); - }, this.contextDeduplicator.flushInterval * 1000); + if (this._contextDeduplicator?.flushInterval !== undefined) { + this._flushUsersTimer = setInterval(() => { + this._contextDeduplicator?.flush(); + }, this._contextDeduplicator.flushInterval * 1000); } - this.flushTimer = setInterval(async () => { + this._flushTimer = setInterval(async () => { try { await this.flush(); } catch (e) { // Log errors and swallow them - this.logger?.debug(`Flush failed: ${e}`); + this._logger?.debug(`Flush failed: ${e}`); } - }, this.config.flushInterval * 1000); + }, this._config.flushInterval * 1000); - if (this.diagnosticsManager) { - const initEvent = this.diagnosticsManager!.createInitEvent(); - this.postDiagnosticEvent(initEvent); + if (this._diagnosticsManager) { + const initEvent = this._diagnosticsManager!.createInitEvent(); + this._postDiagnosticEvent(initEvent); - this.diagnosticsTimer = setInterval(() => { - const statsEvent = this.diagnosticsManager!.createStatsEventAndReset( - this.droppedEvents, - this.deduplicatedUsers, - this.eventsInLastBatch, + this._diagnosticsTimer = setInterval(() => { + const statsEvent = this._diagnosticsManager!.createStatsEventAndReset( + this._droppedEvents, + this._deduplicatedUsers, + this._eventsInLastBatch, ); - this.droppedEvents = 0; - this.deduplicatedUsers = 0; + this._droppedEvents = 0; + this._deduplicatedUsers = 0; - this.postDiagnosticEvent(statsEvent); - }, this.config.diagnosticRecordingInterval * 1000); + this._postDiagnosticEvent(statsEvent); + }, this._config.diagnosticRecordingInterval * 1000); } - this.logger?.debug('Started EventProcessor.'); + this._logger?.debug('Started EventProcessor.'); } - private postDiagnosticEvent(event: DiagnosticEvent) { - this.eventSender.sendEventData(LDEventType.DiagnosticEvent, event); + private _postDiagnosticEvent(event: DiagnosticEvent) { + this._eventSender.sendEventData(LDEventType.DiagnosticEvent, event); } close() { - clearInterval(this.flushTimer); - if (this.flushUsersTimer) { - clearInterval(this.flushUsersTimer); + clearInterval(this._flushTimer); + if (this._flushUsersTimer) { + clearInterval(this._flushUsersTimer); } - if (this.diagnosticsTimer) { - clearInterval(this.diagnosticsTimer); + if (this._diagnosticsTimer) { + clearInterval(this._diagnosticsTimer); } } async flush(): Promise { - if (this.shutdown) { + if (this._shutdown) { throw new LDInvalidSDKKeyError( 'Events cannot be posted because a permanent error has been encountered. ' + 'This is most likely an invalid SDK key. The specific error information ' + @@ -206,10 +206,10 @@ export default class EventProcessor implements LDEventProcessor { ); } - const eventsToFlush = this.queue; - this.queue = []; - const summary = this.summarizer.getSummary(); - this.summarizer.clearSummary(); + const eventsToFlush = this._queue; + this._queue = []; + const summary = this._summarizer.getSummary(); + this._summarizer.clearSummary(); if (Object.keys(summary.features).length) { eventsToFlush.push(summary); @@ -219,13 +219,13 @@ export default class EventProcessor implements LDEventProcessor { return; } - this.eventsInLastBatch = eventsToFlush.length; - this.logger?.debug('Flushing %d events', eventsToFlush.length); - await this.tryPostingEvents(eventsToFlush); + this._eventsInLastBatch = eventsToFlush.length; + this._logger?.debug('Flushing %d events', eventsToFlush.length); + await this._tryPostingEvents(eventsToFlush); } sendEvent(inputEvent: InputEvent) { - if (this.shutdown) { + if (this._shutdown) { return; } @@ -240,34 +240,34 @@ export default class EventProcessor implements LDEventProcessor { if (migrationEvent.samplingRatio === 1) { delete migrationEvent.samplingRatio; } - this.enqueue(migrationEvent); + this._enqueue(migrationEvent); } return; } - this.summarizer.summarizeEvent(inputEvent); + this._summarizer.summarizeEvent(inputEvent); const isFeatureEvent = isFeature(inputEvent); const addFullEvent = (isFeatureEvent && inputEvent.trackEvents) || !isFeatureEvent; - const addDebugEvent = this.shouldDebugEvent(inputEvent); + const addDebugEvent = this._shouldDebugEvent(inputEvent); const isIdentifyEvent = isIdentify(inputEvent); - const shouldNotDeduplicate = this.contextDeduplicator?.processContext(inputEvent.context); + const shouldNotDeduplicate = this._contextDeduplicator?.processContext(inputEvent.context); // If there is no cache, then it will never be in the cache. if (!shouldNotDeduplicate) { if (!isIdentifyEvent) { - this.deduplicatedUsers += 1; + this._deduplicatedUsers += 1; } } const addIndexEvent = shouldNotDeduplicate && !isIdentifyEvent; if (addIndexEvent) { - this.enqueue( - this.makeOutputEvent( + this._enqueue( + this._makeOutputEvent( { kind: 'index', creationDate: inputEvent.creationDate, @@ -279,20 +279,20 @@ export default class EventProcessor implements LDEventProcessor { ); } if (addFullEvent && shouldSample(inputEvent.samplingRatio)) { - this.enqueue(this.makeOutputEvent(inputEvent, false)); + this._enqueue(this._makeOutputEvent(inputEvent, false)); } if (addDebugEvent && shouldSample(inputEvent.samplingRatio)) { - this.enqueue(this.makeOutputEvent(inputEvent, true)); + this._enqueue(this._makeOutputEvent(inputEvent, true)); } } - private makeOutputEvent(event: InputEvent | IndexInputEvent, debug: boolean): OutputEvent { + private _makeOutputEvent(event: InputEvent | IndexInputEvent, debug: boolean): OutputEvent { switch (event.kind) { case 'feature': { const out: FeatureOutputEvent = { kind: debug ? 'debug' : 'feature', creationDate: event.creationDate, - context: this.contextFilter.filter(event.context, !debug), + context: this._contextFilter.filter(event.context, !debug), key: event.key, value: event.value, default: event.default, @@ -319,7 +319,7 @@ export default class EventProcessor implements LDEventProcessor { const out: IdentifyOutputEvent = { kind: event.kind, creationDate: event.creationDate, - context: this.contextFilter.filter(event.context), + context: this._contextFilter.filter(event.context), }; if (event.samplingRatio !== 1) { out.samplingRatio = event.samplingRatio; @@ -378,38 +378,38 @@ export default class EventProcessor implements LDEventProcessor { } } - private enqueue(event: OutputEvent) { - if (this.queue.length < this.capacity) { - this.queue.push(event); - this.exceededCapacity = false; + private _enqueue(event: OutputEvent) { + if (this._queue.length < this._capacity) { + this._queue.push(event); + this._exceededCapacity = false; } else { - if (!this.exceededCapacity) { - this.exceededCapacity = true; - this.logger?.warn( + if (!this._exceededCapacity) { + this._exceededCapacity = true; + this._logger?.warn( 'Exceeded event queue capacity. Increase capacity to avoid dropping events.', ); } - this.droppedEvents += 1; + this._droppedEvents += 1; } } - private shouldDebugEvent(event: InputEvent) { + private _shouldDebugEvent(event: InputEvent) { return ( isFeature(event) && event.debugEventsUntilDate && - event.debugEventsUntilDate > this.lastKnownPastTime && + event.debugEventsUntilDate > this._lastKnownPastTime && event.debugEventsUntilDate > Date.now() ); } - private async tryPostingEvents(events: OutputEvent[] | OutputEvent): Promise { - const res = await this.eventSender.sendEventData(LDEventType.AnalyticsEvents, events); + private async _tryPostingEvents(events: OutputEvent[] | OutputEvent): Promise { + const res = await this._eventSender.sendEventData(LDEventType.AnalyticsEvents, events); if (res.status === LDDeliveryStatus.FailedAndMustShutDown) { - this.shutdown = true; + this._shutdown = true; } if (res.serverTime) { - this.lastKnownPastTime = res.serverTime; + this._lastKnownPastTime = res.serverTime; } if (res.error) { diff --git a/packages/shared/common/src/internal/events/EventSender.ts b/packages/shared/common/src/internal/events/EventSender.ts index 3bb585024..dbb46a430 100644 --- a/packages/shared/common/src/internal/events/EventSender.ts +++ b/packages/shared/common/src/internal/events/EventSender.ts @@ -14,13 +14,13 @@ import { ClientContext, getEventsUri } from '../../options'; import { httpErrorMessage, LDHeaders, sleep } from '../../utils'; export default class EventSender implements LDEventSender { - private crypto: Crypto; - private defaultHeaders: { + private _crypto: Crypto; + private _defaultHeaders: { [key: string]: string; }; - private diagnosticEventsUri: string; - private eventsUri: string; - private requests: Requests; + private _diagnosticEventsUri: string; + private _eventsUri: string; + private _requests: Requests; constructor(clientContext: ClientContext, baseHeaders: LDHeaders) { const { basicConfiguration, platform } = clientContext; @@ -29,18 +29,18 @@ export default class EventSender implements LDEventSender { } = basicConfiguration; const { crypto, requests } = platform; - this.defaultHeaders = { ...baseHeaders }; - this.eventsUri = getEventsUri(basicConfiguration.serviceEndpoints, analyticsEventPath, []); - this.diagnosticEventsUri = getEventsUri( + this._defaultHeaders = { ...baseHeaders }; + this._eventsUri = getEventsUri(basicConfiguration.serviceEndpoints, analyticsEventPath, []); + this._diagnosticEventsUri = getEventsUri( basicConfiguration.serviceEndpoints, diagnosticEventPath, [], ); - this.requests = requests; - this.crypto = crypto; + this._requests = requests; + this._crypto = crypto; } - private async tryPostingEvents( + private async _tryPostingEvents( events: any, uri: string, payloadId: string | undefined, @@ -51,7 +51,7 @@ export default class EventSender implements LDEventSender { }; const headers: Record = { - ...this.defaultHeaders, + ...this._defaultHeaders, 'content-type': 'application/json', }; @@ -61,7 +61,7 @@ export default class EventSender implements LDEventSender { } let error; try { - const { status, headers: resHeaders } = await this.requests.fetch(uri, { + const { status, headers: resHeaders } = await this._requests.fetch(uri, { headers, body: JSON.stringify(events), method: 'POST', @@ -110,13 +110,13 @@ export default class EventSender implements LDEventSender { // wait 1 second before retrying await sleep(); - return this.tryPostingEvents(events, this.eventsUri, payloadId, false); + return this._tryPostingEvents(events, this._eventsUri, payloadId, false); } async sendEventData(type: LDEventType, data: any): Promise { - const payloadId = type === LDEventType.AnalyticsEvents ? this.crypto.randomUUID() : undefined; - const uri = type === LDEventType.AnalyticsEvents ? this.eventsUri : this.diagnosticEventsUri; + const payloadId = type === LDEventType.AnalyticsEvents ? this._crypto.randomUUID() : undefined; + const uri = type === LDEventType.AnalyticsEvents ? this._eventsUri : this._diagnosticEventsUri; - return this.tryPostingEvents(data, uri, payloadId, true); + return this._tryPostingEvents(data, uri, payloadId, true); } } diff --git a/packages/shared/common/src/internal/events/EventSummarizer.ts b/packages/shared/common/src/internal/events/EventSummarizer.ts index 932a09f5f..2e34f2e67 100644 --- a/packages/shared/common/src/internal/events/EventSummarizer.ts +++ b/packages/shared/common/src/internal/events/EventSummarizer.ts @@ -43,29 +43,29 @@ export interface SummarizedFlagsEvent { * @internal */ export default class EventSummarizer { - private startDate = 0; + private _startDate = 0; - private endDate = 0; + private _endDate = 0; - private counters: Record = {}; + private _counters: Record = {}; - private contextKinds: Record> = {}; + private _contextKinds: Record> = {}; summarizeEvent(event: InputEvent) { if (isFeature(event) && !event.excludeFromSummaries) { const countKey = counterKey(event); - const counter = this.counters[countKey]; - let kinds = this.contextKinds[event.key]; + const counter = this._counters[countKey]; + let kinds = this._contextKinds[event.key]; if (!kinds) { kinds = new Set(); - this.contextKinds[event.key] = kinds; + this._contextKinds[event.key] = kinds; } event.context.kinds.forEach((kind) => kinds.add(kind)); if (counter) { counter.increment(); } else { - this.counters[countKey] = new SummaryCounter( + this._counters[countKey] = new SummaryCounter( 1, event.key, event.value, @@ -75,24 +75,24 @@ export default class EventSummarizer { ); } - if (this.startDate === 0 || event.creationDate < this.startDate) { - this.startDate = event.creationDate; + if (this._startDate === 0 || event.creationDate < this._startDate) { + this._startDate = event.creationDate; } - if (event.creationDate > this.endDate) { - this.endDate = event.creationDate; + if (event.creationDate > this._endDate) { + this._endDate = event.creationDate; } } } getSummary(): SummarizedFlagsEvent { - const features = Object.values(this.counters).reduce( + const features = Object.values(this._counters).reduce( (acc: Record, counter) => { let flagSummary = acc[counter.key]; if (!flagSummary) { flagSummary = { default: counter.default, counters: [], - contextKinds: [...this.contextKinds[counter.key]], + contextKinds: [...this._contextKinds[counter.key]], }; acc[counter.key] = flagSummary; } @@ -117,17 +117,17 @@ export default class EventSummarizer { ); return { - startDate: this.startDate, - endDate: this.endDate, + startDate: this._startDate, + endDate: this._endDate, features, kind: 'summary', }; } clearSummary() { - this.startDate = 0; - this.endDate = 0; - this.counters = {}; - this.contextKinds = {}; + this._startDate = 0; + this._endDate = 0; + this._counters = {}; + this._contextKinds = {}; } } diff --git a/packages/shared/common/src/internal/stream/StreamingProcessor.ts b/packages/shared/common/src/internal/stream/StreamingProcessor.ts index aad8b5269..5ffcc90cf 100644 --- a/packages/shared/common/src/internal/stream/StreamingProcessor.ts +++ b/packages/shared/common/src/internal/stream/StreamingProcessor.ts @@ -30,52 +30,52 @@ const reportJsonError = ( // TODO: SDK-156 - Move to Server SDK specific location class StreamingProcessor implements LDStreamProcessor { - private readonly headers: { [key: string]: string | string[] }; - private readonly streamUri: string; - private readonly logger?: LDLogger; + private readonly _headers: { [key: string]: string | string[] }; + private readonly _streamUri: string; + private readonly _logger?: LDLogger; - private eventSource?: EventSource; - private requests: Requests; - private connectionAttemptStartTime?: number; + private _eventSource?: EventSource; + private _requests: Requests; + private _connectionAttemptStartTime?: number; constructor( clientContext: ClientContext, streamUriPath: string, parameters: { key: string; value: string }[], - private readonly listeners: Map, + private readonly _listeners: Map, baseHeaders: LDHeaders, - private readonly diagnosticsManager?: DiagnosticsManager, - private readonly errorHandler?: StreamingErrorHandler, - private readonly streamInitialReconnectDelay = 1, + private readonly _diagnosticsManager?: DiagnosticsManager, + private readonly _errorHandler?: StreamingErrorHandler, + private readonly _streamInitialReconnectDelay = 1, ) { const { basicConfiguration, platform } = clientContext; const { logger } = basicConfiguration; const { requests } = platform; - this.headers = { ...baseHeaders }; - this.logger = logger; - this.requests = requests; - this.streamUri = getStreamingUri( + this._headers = { ...baseHeaders }; + this._logger = logger; + this._requests = requests; + this._streamUri = getStreamingUri( basicConfiguration.serviceEndpoints, streamUriPath, parameters, ); } - private logConnectionStarted() { - this.connectionAttemptStartTime = Date.now(); + private _logConnectionStarted() { + this._connectionAttemptStartTime = Date.now(); } - private logConnectionResult(success: boolean) { - if (this.connectionAttemptStartTime && this.diagnosticsManager) { - this.diagnosticsManager.recordStreamInit( - this.connectionAttemptStartTime, + private _logConnectionResult(success: boolean) { + if (this._connectionAttemptStartTime && this._diagnosticsManager) { + this._diagnosticsManager.recordStreamInit( + this._connectionAttemptStartTime, !success, - Date.now() - this.connectionAttemptStartTime, + Date.now() - this._connectionAttemptStartTime, ); } - this.connectionAttemptStartTime = undefined; + this._connectionAttemptStartTime = undefined; } /** @@ -87,37 +87,37 @@ class StreamingProcessor implements LDStreamProcessor { * * @private */ - private retryAndHandleError(err: HttpErrorResponse) { + private _retryAndHandleError(err: HttpErrorResponse) { if (!shouldRetry(err)) { - this.logConnectionResult(false); - this.errorHandler?.( + this._logConnectionResult(false); + this._errorHandler?.( new LDStreamingError(DataSourceErrorKind.ErrorResponse, err.message, err.status), ); - this.logger?.error(httpErrorMessage(err, 'streaming request')); + this._logger?.error(httpErrorMessage(err, 'streaming request')); return false; } - this.logger?.warn(httpErrorMessage(err, 'streaming request', 'will retry')); - this.logConnectionResult(false); - this.logConnectionStarted(); + this._logger?.warn(httpErrorMessage(err, 'streaming request', 'will retry')); + this._logConnectionResult(false); + this._logConnectionStarted(); return true; } start() { - this.logConnectionStarted(); + this._logConnectionStarted(); // TLS is handled by the platform implementation. - const eventSource = this.requests.createEventSource(this.streamUri, { - headers: this.headers, - errorFilter: (error: HttpErrorResponse) => this.retryAndHandleError(error), - initialRetryDelayMillis: 1000 * this.streamInitialReconnectDelay, + const eventSource = this._requests.createEventSource(this._streamUri, { + headers: this._headers, + errorFilter: (error: HttpErrorResponse) => this._retryAndHandleError(error), + initialRetryDelayMillis: 1000 * this._streamInitialReconnectDelay, readTimeoutMillis: 5 * 60 * 1000, retryResetIntervalMillis: 60 * 1000, }); - this.eventSource = eventSource; + this._eventSource = eventSource; eventSource.onclose = () => { - this.logger?.info('Closed LaunchDarkly stream connection'); + this._logger?.info('Closed LaunchDarkly stream connection'); }; eventSource.onerror = () => { @@ -125,29 +125,29 @@ class StreamingProcessor implements LDStreamProcessor { }; eventSource.onopen = () => { - this.logger?.info('Opened LaunchDarkly stream connection'); + this._logger?.info('Opened LaunchDarkly stream connection'); }; eventSource.onretrying = (e) => { - this.logger?.info(`Will retry stream connection in ${e.delayMillis} milliseconds`); + this._logger?.info(`Will retry stream connection in ${e.delayMillis} milliseconds`); }; - this.listeners.forEach(({ deserializeData, processJson }, eventName) => { + this._listeners.forEach(({ deserializeData, processJson }, eventName) => { eventSource.addEventListener(eventName, (event) => { - this.logger?.debug(`Received ${eventName} event`); + this._logger?.debug(`Received ${eventName} event`); if (event?.data) { - this.logConnectionResult(true); + this._logConnectionResult(true); const { data } = event; const dataJson = deserializeData(data); if (!dataJson) { - reportJsonError(eventName, data, this.logger, this.errorHandler); + reportJsonError(eventName, data, this._logger, this._errorHandler); return; } processJson(dataJson); } else { - this.errorHandler?.( + this._errorHandler?.( new LDStreamingError( DataSourceErrorKind.Unknown, 'Unexpected payload from event stream', @@ -159,8 +159,8 @@ class StreamingProcessor implements LDStreamProcessor { } stop() { - this.eventSource?.close(); - this.eventSource = undefined; + this._eventSource?.close(); + this._eventSource = undefined; } close() { diff --git a/packages/shared/common/src/logging/BasicLogger.ts b/packages/shared/common/src/logging/BasicLogger.ts index f46f667e3..149aafbdf 100644 --- a/packages/shared/common/src/logging/BasicLogger.ts +++ b/packages/shared/common/src/logging/BasicLogger.ts @@ -23,13 +23,13 @@ const LevelNames = ['debug', 'info', 'warn', 'error', 'none']; * as well for performance. */ export default class BasicLogger implements LDLogger { - private logLevel: number; + private _logLevel: number; - private name: string; + private _name: string; - private destination?: (line: string) => void; + private _destination?: (line: string) => void; - private formatter?: (...args: any[]) => string; + private _formatter?: (...args: any[]) => string; /** * This should only be used as a default fallback and not as a convenient @@ -41,18 +41,18 @@ export default class BasicLogger implements LDLogger { } constructor(options: BasicLoggerOptions) { - this.logLevel = LogPriority[options.level ?? 'info'] ?? LogPriority.info; - this.name = options.name ?? 'LaunchDarkly'; + this._logLevel = LogPriority[options.level ?? 'info'] ?? LogPriority.info; + this._name = options.name ?? 'LaunchDarkly'; // eslint-disable-next-line no-console - this.destination = options.destination; - this.formatter = options.formatter; + this._destination = options.destination; + this._formatter = options.formatter; } - private tryFormat(...args: any[]): string { + private _tryFormat(...args: any[]): string { try { - if (this.formatter) { + if (this._formatter) { // In case the provided formatter fails. - return this.formatter?.(...args); + return this._formatter?.(...args); } return format(...args); } catch { @@ -60,21 +60,21 @@ export default class BasicLogger implements LDLogger { } } - private tryWrite(msg: string) { + private _tryWrite(msg: string) { try { - this.destination!(msg); + this._destination!(msg); } catch { // eslint-disable-next-line no-console console.error(msg); } } - private log(level: number, args: any[]) { - if (level >= this.logLevel) { - const prefix = `${LevelNames[level]}: [${this.name}]`; + private _log(level: number, args: any[]) { + if (level >= this._logLevel) { + const prefix = `${LevelNames[level]}: [${this._name}]`; try { - if (this.destination) { - this.tryWrite(`${prefix} ${this.tryFormat(...args)}`); + if (this._destination) { + this._tryWrite(`${prefix} ${this._tryFormat(...args)}`); } else { // `console.error` has its own formatter. // So we don't need to do anything. @@ -90,18 +90,18 @@ export default class BasicLogger implements LDLogger { } error(...args: any[]): void { - this.log(LogPriority.error, args); + this._log(LogPriority.error, args); } warn(...args: any[]): void { - this.log(LogPriority.warn, args); + this._log(LogPriority.warn, args); } info(...args: any[]): void { - this.log(LogPriority.info, args); + this._log(LogPriority.info, args); } debug(...args: any[]): void { - this.log(LogPriority.debug, args); + this._log(LogPriority.debug, args); } } diff --git a/packages/shared/common/src/logging/SafeLogger.ts b/packages/shared/common/src/logging/SafeLogger.ts index 8b7b84289..d51d09ca2 100644 --- a/packages/shared/common/src/logging/SafeLogger.ts +++ b/packages/shared/common/src/logging/SafeLogger.ts @@ -19,9 +19,9 @@ const loggerRequirements = { * checking for the presence of required methods at configuration time. */ export default class SafeLogger implements LDLogger { - private logger: LDLogger; + private _logger: LDLogger; - private fallback: LDLogger; + private _fallback: LDLogger; /** * Construct a safe logger with the specified logger. @@ -39,32 +39,32 @@ export default class SafeLogger implements LDLogger { // criteria since the SDK calls the logger during nearly all of its operations. } }); - this.logger = logger; - this.fallback = fallback; + this._logger = logger; + this._fallback = fallback; } - private log(level: 'error' | 'warn' | 'info' | 'debug', args: any[]) { + private _log(level: 'error' | 'warn' | 'info' | 'debug', args: any[]) { try { - this.logger[level](...args); + this._logger[level](...args); } catch { // If all else fails do not break. - this.fallback[level](...args); + this._fallback[level](...args); } } error(...args: any[]): void { - this.log('error', args); + this._log('error', args); } warn(...args: any[]): void { - this.log('warn', args); + this._log('warn', args); } info(...args: any[]): void { - this.log('info', args); + this._log('info', args); } debug(...args: any[]): void { - this.log('debug', args); + this._log('debug', args); } } diff --git a/packages/shared/common/src/logging/format.ts b/packages/shared/common/src/logging/format.ts index 84c60862c..d9440920a 100644 --- a/packages/shared/common/src/logging/format.ts +++ b/packages/shared/common/src/logging/format.ts @@ -97,6 +97,7 @@ const escapes: Record string> = { f: (val: any) => toFloat(val), j: (val: any) => tryStringify(val), o: (val: any) => tryStringify(val), + // eslint-disable-next-line @typescript-eslint/naming-convention O: (val: any) => tryStringify(val), c: () => '', }; diff --git a/packages/shared/common/src/options/ServiceEndpoints.ts b/packages/shared/common/src/options/ServiceEndpoints.ts index d0781b0a9..e0577b79b 100644 --- a/packages/shared/common/src/options/ServiceEndpoints.ts +++ b/packages/shared/common/src/options/ServiceEndpoints.ts @@ -10,6 +10,7 @@ function canonicalizePath(path: string): string { * Specifies the base service URIs used by SDK components. */ export default class ServiceEndpoints { + // eslint-disable-next-line @typescript-eslint/naming-convention public static DEFAULT_EVENTS = 'https://events.launchdarkly.com'; public readonly streaming: string; diff --git a/packages/shared/common/src/validators.ts b/packages/shared/common/src/validators.ts index d294643bd..a070043a9 100644 --- a/packages/shared/common/src/validators.ts +++ b/packages/shared/common/src/validators.ts @@ -38,12 +38,12 @@ export class FactoryOrInstance implements TypeValidator { * Validate a basic type. */ export class Type implements TypeValidator { - private typeName: string; + private _typeName: string; protected typeOf: string; constructor(typeName: string, example: T) { - this.typeName = typeName; + this._typeName = typeName; this.typeOf = typeof example; } @@ -55,7 +55,7 @@ export class Type implements TypeValidator { } getType(): string { - return this.typeName; + return this._typeName; } } @@ -66,12 +66,12 @@ export class Type implements TypeValidator { * of classes will simply objects. */ export class TypeArray implements TypeValidator { - private typeName: string; + private _typeName: string; protected typeOf: string; constructor(typeName: string, example: T) { - this.typeName = typeName; + this._typeName = typeName; this.typeOf = typeof example; } @@ -86,7 +86,7 @@ export class TypeArray implements TypeValidator { } getType(): string { - return this.typeName; + return this._typeName; } } diff --git a/packages/shared/sdk-client/__tests__/LDClientImpl.events.test.ts b/packages/shared/sdk-client/__tests__/LDClientImpl.events.test.ts index ca7b006e0..946a76d9b 100644 --- a/packages/shared/sdk-client/__tests__/LDClientImpl.events.test.ts +++ b/packages/shared/sdk-client/__tests__/LDClientImpl.events.test.ts @@ -109,7 +109,7 @@ describe('sdk-client object', () => { expect.objectContaining({ kind: 'identify', context: expect.objectContaining({ - contexts: expect.objectContaining({ + _contexts: expect.objectContaining({ car: { key: 'test-car' }, }), }), @@ -130,7 +130,7 @@ describe('sdk-client object', () => { kind: 'custom', key: 'the-event', context: expect.objectContaining({ - contexts: expect.objectContaining({ + _contexts: expect.objectContaining({ car: { key: 'test-car' }, }), }), @@ -153,7 +153,7 @@ describe('sdk-client object', () => { kind: 'custom', key: 'the-event', context: expect.objectContaining({ - contexts: expect.objectContaining({ + _contexts: expect.objectContaining({ car: { key: 'test-car' }, }), }), @@ -176,7 +176,7 @@ describe('sdk-client object', () => { kind: 'custom', key: 'the-event', context: expect.objectContaining({ - contexts: expect.objectContaining({ + _contexts: expect.objectContaining({ car: { key: 'test-car' }, }), }), diff --git a/packages/shared/sdk-client/__tests__/LDClientImpl.variation.test.ts b/packages/shared/sdk-client/__tests__/LDClientImpl.variation.test.ts index 257d1a10a..d6fbd74b9 100644 --- a/packages/shared/sdk-client/__tests__/LDClientImpl.variation.test.ts +++ b/packages/shared/sdk-client/__tests__/LDClientImpl.variation.test.ts @@ -100,7 +100,8 @@ describe('sdk-client object', () => { const checkedContext = Context.fromLDContext(context); // @ts-ignore - await ldc.flagManager.upsert(checkedContext, 'dev-test-flag', { + // eslint-disable-next-line no-underscore-dangle + await ldc._flagManager.upsert(checkedContext, 'dev-test-flag', { version: 999, flag: { deleted: true, diff --git a/packages/shared/sdk-client/__tests__/TestDataManager.ts b/packages/shared/sdk-client/__tests__/TestDataManager.ts index 45b526a17..b7d09e410 100644 --- a/packages/shared/sdk-client/__tests__/TestDataManager.ts +++ b/packages/shared/sdk-client/__tests__/TestDataManager.ts @@ -24,7 +24,7 @@ export default class TestDataManager extends BaseDataManager { getStreamingPaths: () => DataSourcePaths, baseHeaders: LDHeaders, emitter: LDEmitter, - private readonly disableNetwork: boolean, + private readonly _disableNetwork: boolean, diagnosticsManager?: internal.DiagnosticsManager, ) { super( @@ -58,15 +58,15 @@ export default class TestDataManager extends BaseDataManager { 'Identify - Flags loaded from cache, but identify was requested with "waitForNetworkResults"', ); } - if (this.disableNetwork) { + if (this._disableNetwork) { identifyResolve(); return; } - this.setupConnection(context, identifyResolve, identifyReject); + this._setupConnection(context, identifyResolve, identifyReject); } - private setupConnection( + private _setupConnection( context: Context, identifyResolve?: () => void, identifyReject?: (err: Error) => void, diff --git a/packages/shared/sdk-client/__tests__/configuration/Configuration.test.ts b/packages/shared/sdk-client/__tests__/configuration/Configuration.test.ts index 81a797a7d..7c2c880d7 100644 --- a/packages/shared/sdk-client/__tests__/configuration/Configuration.test.ts +++ b/packages/shared/sdk-client/__tests__/configuration/Configuration.test.ts @@ -23,9 +23,9 @@ describe('Configuration', () => { eventsUri: 'https://events.launchdarkly.com', flushInterval: 30, logger: { - destination: console.error, - logLevel: 1, - name: 'LaunchDarkly', + _destination: console.error, + _logLevel: 1, + _name: 'LaunchDarkly', }, maxCachedContexts: 5, privateAttributes: [], diff --git a/packages/shared/sdk-client/src/DataManager.ts b/packages/shared/sdk-client/src/DataManager.ts index 444b88055..bf5524280 100644 --- a/packages/shared/sdk-client/src/DataManager.ts +++ b/packages/shared/sdk-client/src/DataManager.ts @@ -66,9 +66,9 @@ export abstract class BaseDataManager implements DataManager { protected updateProcessor?: subsystem.LDStreamProcessor; protected readonly logger: LDLogger; protected context?: Context; - private connectionParams?: ConnectionParams; + private _connectionParams?: ConnectionParams; protected readonly dataSourceStatusManager: DataSourceStatusManager; - private readonly dataSourceEventHandler: DataSourceEventHandler; + private readonly _dataSourceEventHandler: DataSourceEventHandler; constructor( protected readonly platform: Platform, @@ -83,7 +83,7 @@ export abstract class BaseDataManager implements DataManager { ) { this.logger = config.logger; this.dataSourceStatusManager = new DataSourceStatusManager(emitter); - this.dataSourceEventHandler = new DataSourceEventHandler( + this._dataSourceEventHandler = new DataSourceEventHandler( flagManager, this.dataSourceStatusManager, this.config.logger, @@ -94,7 +94,7 @@ export abstract class BaseDataManager implements DataManager { * Set additional connection parameters for requests polling/streaming. */ protected setConnectionParams(connectionParams?: ConnectionParams) { - this.connectionParams = connectionParams; + this._connectionParams = connectionParams; } abstract identify( @@ -120,22 +120,22 @@ export abstract class BaseDataManager implements DataManager { pollInterval: this.config.pollInterval, withReasons: this.config.withReasons, useReport: this.config.useReport, - queryParameters: this.connectionParams?.queryParameters, + queryParameters: this._connectionParams?.queryParameters, }, this.platform.requests, this.platform.encoding!, async (flags) => { - await this.dataSourceEventHandler.handlePut(checkedContext, flags); + await this._dataSourceEventHandler.handlePut(checkedContext, flags); identifyResolve?.(); }, (err) => { this.emitter.emit('error', context, err); - this.dataSourceEventHandler.handlePollingError(err); + this._dataSourceEventHandler.handlePollingError(err); identifyReject?.(err); }, ); - this.updateProcessor = this.decorateProcessorWithStatusReporting( + this.updateProcessor = this._decorateProcessorWithStatusReporting( processor, this.dataSourceStatusManager, ); @@ -157,7 +157,7 @@ export abstract class BaseDataManager implements DataManager { initialRetryDelayMillis: this.config.streamInitialReconnectDelay * 1000, withReasons: this.config.withReasons, useReport: this.config.useReport, - queryParameters: this.connectionParams?.queryParameters, + queryParameters: this._connectionParams?.queryParameters, }, this.createStreamListeners(checkedContext, identifyResolve), this.platform.requests, @@ -165,12 +165,12 @@ export abstract class BaseDataManager implements DataManager { this.diagnosticsManager, (e) => { this.emitter.emit('error', context, e); - this.dataSourceEventHandler.handleStreamingError(e); + this._dataSourceEventHandler.handleStreamingError(e); identifyReject?.(e); }, ); - this.updateProcessor = this.decorateProcessorWithStatusReporting( + this.updateProcessor = this._decorateProcessorWithStatusReporting( processor, this.dataSourceStatusManager, ); @@ -185,7 +185,7 @@ export abstract class BaseDataManager implements DataManager { listeners.set('put', { deserializeData: JSON.parse, processJson: async (flags: Flags) => { - await this.dataSourceEventHandler.handlePut(context, flags); + await this._dataSourceEventHandler.handlePut(context, flags); identifyResolve?.(); }, }); @@ -193,21 +193,21 @@ export abstract class BaseDataManager implements DataManager { listeners.set('patch', { deserializeData: JSON.parse, processJson: async (patchFlag: PatchFlag) => { - this.dataSourceEventHandler.handlePatch(context, patchFlag); + this._dataSourceEventHandler.handlePatch(context, patchFlag); }, }); listeners.set('delete', { deserializeData: JSON.parse, processJson: async (deleteFlag: DeleteFlag) => { - this.dataSourceEventHandler.handleDelete(context, deleteFlag); + this._dataSourceEventHandler.handleDelete(context, deleteFlag); }, }); return listeners; } - private decorateProcessorWithStatusReporting( + private _decorateProcessorWithStatusReporting( processor: LDStreamProcessor, statusManager: DataSourceStatusManager, ): LDStreamProcessor { diff --git a/packages/shared/sdk-client/src/HookRunner.ts b/packages/shared/sdk-client/src/HookRunner.ts index f2f670342..8380bfba1 100644 --- a/packages/shared/sdk-client/src/HookRunner.ts +++ b/packages/shared/sdk-client/src/HookRunner.ts @@ -115,13 +115,13 @@ function executeAfterIdentify( } export default class HookRunner { - private readonly hooks: Hook[] = []; + private readonly _hooks: Hook[] = []; constructor( - private readonly logger: LDLogger, + private readonly _logger: LDLogger, initialHooks: Hook[], ) { - this.hooks.push(...initialHooks); + this._hooks.push(...initialHooks); } withEvaluation( @@ -130,19 +130,19 @@ export default class HookRunner { defaultValue: unknown, method: () => LDEvaluationDetail, ): LDEvaluationDetail { - if (this.hooks.length === 0) { + if (this._hooks.length === 0) { return method(); } - const hooks: Hook[] = [...this.hooks]; + const hooks: Hook[] = [...this._hooks]; const hookContext: EvaluationSeriesContext = { flagKey: key, context, defaultValue, }; - const hookData = executeBeforeEvaluation(this.logger, hooks, hookContext); + const hookData = executeBeforeEvaluation(this._logger, hooks, hookContext); const result = method(); - executeAfterEvaluation(this.logger, hooks, hookContext, hookData, result); + executeAfterEvaluation(this._logger, hooks, hookContext, hookData, result); return result; } @@ -150,18 +150,18 @@ export default class HookRunner { context: LDContext, timeout: number | undefined, ): (result: IdentifySeriesResult) => void { - const hooks: Hook[] = [...this.hooks]; + const hooks: Hook[] = [...this._hooks]; const hookContext: IdentifySeriesContext = { context, timeout, }; - const hookData = executeBeforeIdentify(this.logger, hooks, hookContext); + const hookData = executeBeforeIdentify(this._logger, hooks, hookContext); return (result) => { - executeAfterIdentify(this.logger, hooks, hookContext, hookData, result); + executeAfterIdentify(this._logger, hooks, hookContext, hookData, result); }; } addHook(hook: Hook): void { - this.hooks.push(hook); + this._hooks.push(hook); } } diff --git a/packages/shared/sdk-client/src/LDClientImpl.ts b/packages/shared/sdk-client/src/LDClientImpl.ts index d1576aa22..106a5ed6d 100644 --- a/packages/shared/sdk-client/src/LDClientImpl.ts +++ b/packages/shared/sdk-client/src/LDClientImpl.ts @@ -37,26 +37,26 @@ import LDEmitter, { EventName } from './LDEmitter'; const { ClientMessages, ErrorKinds } = internal; export default class LDClientImpl implements LDClient { - private readonly config: Configuration; - private uncheckedContext?: LDContext; - private checkedContext?: Context; - private readonly diagnosticsManager?: internal.DiagnosticsManager; - private eventProcessor?: internal.EventProcessor; - private identifyTimeout: number = 5; + private readonly _config: Configuration; + private _uncheckedContext?: LDContext; + private _checkedContext?: Context; + private readonly _diagnosticsManager?: internal.DiagnosticsManager; + private _eventProcessor?: internal.EventProcessor; + private _identifyTimeout: number = 5; readonly logger: LDLogger; - private updateProcessor?: subsystem.LDStreamProcessor; + private _updateProcessor?: subsystem.LDStreamProcessor; - private readonly highTimeoutThreshold: number = 15; + private readonly _highTimeoutThreshold: number = 15; - private eventFactoryDefault = new EventFactory(false); - private eventFactoryWithReasons = new EventFactory(true); + private _eventFactoryDefault = new EventFactory(false); + private _eventFactoryWithReasons = new EventFactory(true); protected emitter: LDEmitter; - private flagManager: FlagManager; + private _flagManager: FlagManager; - private eventSendingEnabled: boolean = false; - private baseHeaders: LDHeaders; + private _eventSendingEnabled: boolean = false; + private _baseHeaders: LDHeaders; protected dataManager: DataManager; - private hookRunner: HookRunner; + private _hookRunner: HookRunner; /** * Creates the client object synchronously. No async, no network calls. @@ -77,37 +77,37 @@ export default class LDClientImpl implements LDClient { throw new Error('Platform must implement Encoding because btoa is required.'); } - this.config = new ConfigurationImpl(options, internalOptions); - this.logger = this.config.logger; + this._config = new ConfigurationImpl(options, internalOptions); + this.logger = this._config.logger; - this.baseHeaders = defaultHeaders( + this._baseHeaders = defaultHeaders( this.sdkKey, this.platform.info, - this.config.tags, - this.config.serviceEndpoints.includeAuthorizationHeader, - this.config.userAgentHeaderName, + this._config.tags, + this._config.serviceEndpoints.includeAuthorizationHeader, + this._config.userAgentHeaderName, ); - this.flagManager = new DefaultFlagManager( + this._flagManager = new DefaultFlagManager( this.platform, sdkKey, - this.config.maxCachedContexts, - this.config.logger, + this._config.maxCachedContexts, + this._config.logger, ); - this.diagnosticsManager = createDiagnosticsManager(sdkKey, this.config, platform); - this.eventProcessor = createEventProcessor( + this._diagnosticsManager = createDiagnosticsManager(sdkKey, this._config, platform); + this._eventProcessor = createEventProcessor( sdkKey, - this.config, + this._config, platform, - this.baseHeaders, - this.diagnosticsManager, + this._baseHeaders, + this._diagnosticsManager, ); this.emitter = new LDEmitter(); this.emitter.on('error', (c: LDContext, err: any) => { this.logger.error(`error: ${err}, context: ${JSON.stringify(c)}`); }); - this.flagManager.on((context, flagKeys) => { + this._flagManager.on((context, flagKeys) => { const ldContext = Context.toLDContext(context); this.emitter.emit('change', ldContext, flagKeys); flagKeys.forEach((it) => { @@ -116,19 +116,19 @@ export default class LDClientImpl implements LDClient { }); this.dataManager = dataManagerFactory( - this.flagManager, - this.config, - this.baseHeaders, + this._flagManager, + this._config, + this._baseHeaders, this.emitter, - this.diagnosticsManager, + this._diagnosticsManager, ); - this.hookRunner = new HookRunner(this.logger, this.config.hooks); + this._hookRunner = new HookRunner(this.logger, this._config.hooks); } allFlags(): LDFlagSet { // extracting all flag values - const result = Object.entries(this.flagManager.getAll()).reduce( + const result = Object.entries(this._flagManager.getAll()).reduce( (acc: LDFlagSet, [key, descriptor]) => { if (descriptor.flag !== null && descriptor.flag !== undefined && !descriptor.flag.deleted) { acc[key] = descriptor.flag.value; @@ -142,14 +142,14 @@ export default class LDClientImpl implements LDClient { async close(): Promise { await this.flush(); - this.eventProcessor?.close(); - this.updateProcessor?.close(); + this._eventProcessor?.close(); + this._updateProcessor?.close(); this.logger.debug('Closed event processor and data source.'); } async flush(): Promise<{ error?: Error; result: boolean }> { try { - await this.eventProcessor?.flush(); + await this._eventProcessor?.flush(); this.logger.debug('Successfully flushed event processor.'); } catch (e) { this.logger.error(`Error flushing event processor: ${e}.`); @@ -165,14 +165,14 @@ export default class LDClientImpl implements LDClient { // code. We are returned the unchecked context so that if a consumer identifies with an invalid context // and then calls getContext, they get back the same context they provided, without any assertion about // validity. - return this.uncheckedContext ? clone(this.uncheckedContext) : undefined; + return this._uncheckedContext ? clone(this._uncheckedContext) : undefined; } protected getInternalContext(): Context | undefined { - return this.checkedContext; + return this._checkedContext; } - private createIdentifyPromise(timeout: number): { + private _createIdentifyPromise(timeout: number): { identifyPromise: Promise; identifyResolve: () => void; identifyReject: (err: Error) => void; @@ -213,21 +213,21 @@ export default class LDClientImpl implements LDClient { */ async identify(pristineContext: LDContext, identifyOptions?: LDIdentifyOptions): Promise { if (identifyOptions?.timeout) { - this.identifyTimeout = identifyOptions.timeout; + this._identifyTimeout = identifyOptions.timeout; } - if (this.identifyTimeout > this.highTimeoutThreshold) { + if (this._identifyTimeout > this._highTimeoutThreshold) { this.logger.warn( 'The identify function was called with a timeout greater than ' + - `${this.highTimeoutThreshold} seconds. We recommend a timeout of less than ` + - `${this.highTimeoutThreshold} seconds.`, + `${this._highTimeoutThreshold} seconds. We recommend a timeout of less than ` + + `${this._highTimeoutThreshold} seconds.`, ); } let context = await ensureKey(pristineContext, this.platform); if (this.autoEnvAttributes === AutoEnvAttributes.Enabled) { - context = await addAutoEnv(context, this.platform, this.config); + context = await addAutoEnv(context, this.platform, this._config); } const checkedContext = Context.fromLDContext(context); @@ -236,16 +236,16 @@ export default class LDClientImpl implements LDClient { this.emitter.emit('error', context, error); return Promise.reject(error); } - this.uncheckedContext = context; - this.checkedContext = checkedContext; + this._uncheckedContext = context; + this._checkedContext = checkedContext; - this.eventProcessor?.sendEvent(this.eventFactoryDefault.identifyEvent(this.checkedContext)); - const { identifyPromise, identifyResolve, identifyReject } = this.createIdentifyPromise( - this.identifyTimeout, + this._eventProcessor?.sendEvent(this._eventFactoryDefault.identifyEvent(this._checkedContext)); + const { identifyPromise, identifyResolve, identifyReject } = this._createIdentifyPromise( + this._identifyTimeout, ); - this.logger.debug(`Identifying ${JSON.stringify(this.checkedContext)}`); + this.logger.debug(`Identifying ${JSON.stringify(this._checkedContext)}`); - const afterIdentify = this.hookRunner.identify(context, identifyOptions?.timeout); + const afterIdentify = this._hookRunner.identify(context, identifyOptions?.timeout); await this.dataManager.identify( identifyResolve, @@ -275,8 +275,8 @@ export default class LDClientImpl implements LDClient { } track(key: string, data?: any, metricValue?: number): void { - if (!this.checkedContext || !this.checkedContext.valid) { - this.logger.warn(ClientMessages.missingContextKeyNoEvent); + if (!this._checkedContext || !this._checkedContext.valid) { + this.logger.warn(ClientMessages.MissingContextKeyNoEvent); return; } @@ -285,35 +285,35 @@ export default class LDClientImpl implements LDClient { this.logger?.warn(ClientMessages.invalidMetricValue(typeof metricValue)); } - this.eventProcessor?.sendEvent( - this.config.trackEventModifier( - this.eventFactoryDefault.customEvent(key, this.checkedContext!, data, metricValue), + this._eventProcessor?.sendEvent( + this._config.trackEventModifier( + this._eventFactoryDefault.customEvent(key, this._checkedContext!, data, metricValue), ), ); } - private variationInternal( + private _variationInternal( flagKey: string, defaultValue: any, eventFactory: EventFactory, typeChecker?: (value: any) => [boolean, string], ): LDEvaluationDetail { - if (!this.uncheckedContext) { - this.logger.debug(ClientMessages.missingContextKeyNoEvent); + if (!this._uncheckedContext) { + this.logger.debug(ClientMessages.MissingContextKeyNoEvent); return createErrorEvaluationDetail(ErrorKinds.UserNotSpecified, defaultValue); } - const evalContext = Context.fromLDContext(this.uncheckedContext); - const foundItem = this.flagManager.get(flagKey); + const evalContext = Context.fromLDContext(this._uncheckedContext); + const foundItem = this._flagManager.get(flagKey); if (foundItem === undefined || foundItem.flag.deleted) { const defVal = defaultValue ?? null; const error = new LDClientError( `Unknown feature flag "${flagKey}"; returning default value ${defVal}.`, ); - this.emitter.emit('error', this.uncheckedContext, error); - this.eventProcessor?.sendEvent( - this.eventFactoryDefault.unknownFlagEvent(flagKey, defVal, evalContext), + this.emitter.emit('error', this._uncheckedContext, error); + this._eventProcessor?.sendEvent( + this._eventFactoryDefault.unknownFlagEvent(flagKey, defVal, evalContext), ); return createErrorEvaluationDetail(ErrorKinds.FlagNotFound, defaultValue); } @@ -323,7 +323,7 @@ export default class LDClientImpl implements LDClient { if (typeChecker) { const [matched, type] = typeChecker(value); if (!matched) { - this.eventProcessor?.sendEvent( + this._eventProcessor?.sendEvent( eventFactory.evalEventClient( flagKey, defaultValue, // track default value on type errors @@ -336,7 +336,7 @@ export default class LDClientImpl implements LDClient { const error = new LDClientError( `Wrong type "${type}" for feature flag "${flagKey}"; returning default value`, ); - this.emitter.emit('error', this.uncheckedContext, error); + this.emitter.emit('error', this._uncheckedContext, error); return createErrorEvaluationDetail(ErrorKinds.WrongType, defaultValue); } } @@ -346,7 +346,7 @@ export default class LDClientImpl implements LDClient { this.logger.debug('Result value is null. Providing default value.'); successDetail.value = defaultValue; } - this.eventProcessor?.sendEvent( + this._eventProcessor?.sendEvent( eventFactory.evalEventClient( flagKey, value, @@ -360,33 +360,33 @@ export default class LDClientImpl implements LDClient { } variation(flagKey: string, defaultValue?: LDFlagValue): LDFlagValue { - const { value } = this.hookRunner.withEvaluation( + const { value } = this._hookRunner.withEvaluation( flagKey, - this.uncheckedContext, + this._uncheckedContext, defaultValue, - () => this.variationInternal(flagKey, defaultValue, this.eventFactoryDefault), + () => this._variationInternal(flagKey, defaultValue, this._eventFactoryDefault), ); return value; } variationDetail(flagKey: string, defaultValue?: LDFlagValue): LDEvaluationDetail { - return this.hookRunner.withEvaluation(flagKey, this.uncheckedContext, defaultValue, () => - this.variationInternal(flagKey, defaultValue, this.eventFactoryWithReasons), + return this._hookRunner.withEvaluation(flagKey, this._uncheckedContext, defaultValue, () => + this._variationInternal(flagKey, defaultValue, this._eventFactoryWithReasons), ); } - private typedEval( + private _typedEval( key: string, defaultValue: T, eventFactory: EventFactory, typeChecker: (value: unknown) => [boolean, string], ): LDEvaluationDetailTyped { - return this.hookRunner.withEvaluation(key, this.uncheckedContext, defaultValue, () => - this.variationInternal(key, defaultValue, eventFactory, typeChecker), + return this._hookRunner.withEvaluation(key, this._uncheckedContext, defaultValue, () => + this._variationInternal(key, defaultValue, eventFactory, typeChecker), ); } boolVariation(key: string, defaultValue: boolean): boolean { - return this.typedEval(key, defaultValue, this.eventFactoryDefault, (value) => [ + return this._typedEval(key, defaultValue, this._eventFactoryDefault, (value) => [ TypeValidators.Boolean.is(value), TypeValidators.Boolean.getType(), ]).value; @@ -397,35 +397,35 @@ export default class LDClientImpl implements LDClient { } numberVariation(key: string, defaultValue: number): number { - return this.typedEval(key, defaultValue, this.eventFactoryDefault, (value) => [ + return this._typedEval(key, defaultValue, this._eventFactoryDefault, (value) => [ TypeValidators.Number.is(value), TypeValidators.Number.getType(), ]).value; } stringVariation(key: string, defaultValue: string): string { - return this.typedEval(key, defaultValue, this.eventFactoryDefault, (value) => [ + return this._typedEval(key, defaultValue, this._eventFactoryDefault, (value) => [ TypeValidators.String.is(value), TypeValidators.String.getType(), ]).value; } boolVariationDetail(key: string, defaultValue: boolean): LDEvaluationDetailTyped { - return this.typedEval(key, defaultValue, this.eventFactoryWithReasons, (value) => [ + return this._typedEval(key, defaultValue, this._eventFactoryWithReasons, (value) => [ TypeValidators.Boolean.is(value), TypeValidators.Boolean.getType(), ]); } numberVariationDetail(key: string, defaultValue: number): LDEvaluationDetailTyped { - return this.typedEval(key, defaultValue, this.eventFactoryWithReasons, (value) => [ + return this._typedEval(key, defaultValue, this._eventFactoryWithReasons, (value) => [ TypeValidators.Number.is(value), TypeValidators.Number.getType(), ]); } stringVariationDetail(key: string, defaultValue: string): LDEvaluationDetailTyped { - return this.typedEval(key, defaultValue, this.eventFactoryWithReasons, (value) => [ + return this._typedEval(key, defaultValue, this._eventFactoryWithReasons, (value) => [ TypeValidators.String.is(value), TypeValidators.String.getType(), ]); @@ -436,7 +436,7 @@ export default class LDClientImpl implements LDClient { } addHook(hook: Hook): void { - this.hookRunner.addHook(hook); + this._hookRunner.addHook(hook); } /** @@ -445,33 +445,33 @@ export default class LDClientImpl implements LDClient { * @param flush True to flush while disabling. Useful to flush on certain state transitions. */ protected setEventSendingEnabled(enabled: boolean, flush: boolean): void { - if (this.eventSendingEnabled === enabled) { + if (this._eventSendingEnabled === enabled) { return; } - this.eventSendingEnabled = enabled; + this._eventSendingEnabled = enabled; if (enabled) { this.logger.debug('Starting event processor'); - this.eventProcessor?.start(); + this._eventProcessor?.start(); } else if (flush) { this.logger?.debug('Flushing event processor before disabling.'); // Disable and flush. this.flush().then(() => { // While waiting for the flush event sending could be re-enabled, in which case // we do not want to close the event processor. - if (!this.eventSendingEnabled) { + if (!this._eventSendingEnabled) { this.logger?.debug('Stopping event processor.'); - this.eventProcessor?.close(); + this._eventProcessor?.close(); } }); } else { // Just disabled. this.logger?.debug('Stopping event processor.'); - this.eventProcessor?.close(); + this._eventProcessor?.close(); } } protected sendEvent(event: internal.InputEvent): void { - this.eventProcessor?.sendEvent(event); + this._eventProcessor?.sendEvent(event); } } diff --git a/packages/shared/sdk-client/src/LDEmitter.ts b/packages/shared/sdk-client/src/LDEmitter.ts index 9c56f3b1b..76705f90c 100644 --- a/packages/shared/sdk-client/src/LDEmitter.ts +++ b/packages/shared/sdk-client/src/LDEmitter.ts @@ -15,15 +15,15 @@ export type EventName = 'change' | FlagChangeKey | 'dataSourceStatus' | 'error'; * a system to allow listeners which have counts independent of the primary listener counts. */ export default class LDEmitter { - private listeners: Map = new Map(); + private _listeners: Map = new Map(); - constructor(private logger?: LDLogger) {} + constructor(private _logger?: LDLogger) {} on(name: EventName, listener: Function) { - if (!this.listeners.has(name)) { - this.listeners.set(name, [listener]); + if (!this._listeners.has(name)) { + this._listeners.set(name, [listener]); } else { - this.listeners.get(name)?.push(listener); + this._listeners.get(name)?.push(listener); } } @@ -34,7 +34,7 @@ export default class LDEmitter { * @param listener Optional. If unspecified, all listeners for the event will be removed. */ off(name: EventName, listener?: Function) { - const existingListeners = this.listeners.get(name); + const existingListeners = this._listeners.get(name); if (!existingListeners) { return; } @@ -43,35 +43,35 @@ export default class LDEmitter { // remove from internal cache const updated = existingListeners.filter((fn) => fn !== listener); if (updated.length === 0) { - this.listeners.delete(name); + this._listeners.delete(name); } else { - this.listeners.set(name, updated); + this._listeners.set(name, updated); } return; } // listener was not specified, so remove them all for that event - this.listeners.delete(name); + this._listeners.delete(name); } - private invokeListener(listener: Function, name: EventName, ...detail: any[]) { + private _invokeListener(listener: Function, name: EventName, ...detail: any[]) { try { listener(...detail); } catch (err) { - this.logger?.error(`Encountered error invoking handler for "${name}", detail: "${err}"`); + this._logger?.error(`Encountered error invoking handler for "${name}", detail: "${err}"`); } } emit(name: EventName, ...detail: any[]) { - const listeners = this.listeners.get(name); - listeners?.forEach((listener) => this.invokeListener(listener, name, ...detail)); + const listeners = this._listeners.get(name); + listeners?.forEach((listener) => this._invokeListener(listener, name, ...detail)); } eventNames(): string[] { - return [...this.listeners.keys()]; + return [...this._listeners.keys()]; } listenerCount(name: EventName): number { - return this.listeners.get(name)?.length ?? 0; + return this._listeners.get(name)?.length ?? 0; } } diff --git a/packages/shared/sdk-client/src/configuration/Configuration.ts b/packages/shared/sdk-client/src/configuration/Configuration.ts index 47f6e1539..4a7cf34d5 100644 --- a/packages/shared/sdk-client/src/configuration/Configuration.ts +++ b/packages/shared/sdk-client/src/configuration/Configuration.ts @@ -71,8 +71,13 @@ function ensureSafeLogger(logger?: LDLogger): LDLogger { export default class ConfigurationImpl implements Configuration { public readonly logger: LDLogger = createSafeLogger(); + // Naming conventions is not followed for these lines because the config validation + // accesses members based on the keys of the options. (sdk-763) + // eslint-disable-next-line @typescript-eslint/naming-convention private readonly baseUri = DEFAULT_POLLING; + // eslint-disable-next-line @typescript-eslint/naming-convention private readonly eventsUri = ServiceEndpoints.DEFAULT_EVENTS; + // eslint-disable-next-line @typescript-eslint/naming-convention private readonly streamUri = DEFAULT_STREAM; public readonly maxCachedContexts = 5; @@ -126,7 +131,7 @@ export default class ConfigurationImpl implements Configuration { constructor(pristineOptions: LDOptions = {}, internalOptions: LDClientInternalOptions = {}) { this.logger = ensureSafeLogger(pristineOptions.logger); - const errors = this.validateTypesAndNames(pristineOptions); + const errors = this._validateTypesAndNames(pristineOptions); errors.forEach((e: string) => this.logger.warn(e)); this.serviceEndpoints = new ServiceEndpoints( @@ -145,7 +150,7 @@ export default class ConfigurationImpl implements Configuration { this.trackEventModifier = internalOptions.trackEventModifier ?? ((event) => event); } - private validateTypesAndNames(pristineOptions: LDOptions): string[] { + private _validateTypesAndNames(pristineOptions: LDOptions): string[] { const errors: string[] = []; Object.entries(pristineOptions).forEach(([k, v]) => { diff --git a/packages/shared/sdk-client/src/datasource/DataSourceEventHandler.ts b/packages/shared/sdk-client/src/datasource/DataSourceEventHandler.ts index d590248b5..2e8791939 100644 --- a/packages/shared/sdk-client/src/datasource/DataSourceEventHandler.ts +++ b/packages/shared/sdk-client/src/datasource/DataSourceEventHandler.ts @@ -8,13 +8,13 @@ import DataSourceStatusManager from './DataSourceStatusManager'; export default class DataSourceEventHandler { constructor( - private readonly flagManager: FlagManager, - private readonly statusManager: DataSourceStatusManager, - private readonly logger: LDLogger, + private readonly _flagManager: FlagManager, + private readonly _statusManager: DataSourceStatusManager, + private readonly _logger: LDLogger, ) {} async handlePut(context: Context, flags: Flags) { - this.logger.debug(`Got PUT: ${Object.keys(flags)}`); + this._logger.debug(`Got PUT: ${Object.keys(flags)}`); // mapping flags to item descriptors const descriptors = Object.entries(flags).reduce( @@ -24,22 +24,22 @@ export default class DataSourceEventHandler { }, {}, ); - await this.flagManager.init(context, descriptors); - this.statusManager.requestStateUpdate(DataSourceState.Valid); + await this._flagManager.init(context, descriptors); + this._statusManager.requestStateUpdate(DataSourceState.Valid); } async handlePatch(context: Context, patchFlag: PatchFlag) { - this.logger.debug(`Got PATCH ${JSON.stringify(patchFlag, null, 2)}`); - this.flagManager.upsert(context, patchFlag.key, { + this._logger.debug(`Got PATCH ${JSON.stringify(patchFlag, null, 2)}`); + this._flagManager.upsert(context, patchFlag.key, { version: patchFlag.version, flag: patchFlag, }); } async handleDelete(context: Context, deleteFlag: DeleteFlag) { - this.logger.debug(`Got DELETE ${JSON.stringify(deleteFlag, null, 2)}`); + this._logger.debug(`Got DELETE ${JSON.stringify(deleteFlag, null, 2)}`); - this.flagManager.upsert(context, deleteFlag.key, { + this._flagManager.upsert(context, deleteFlag.key, { version: deleteFlag.version, flag: { ...deleteFlag, @@ -55,10 +55,10 @@ export default class DataSourceEventHandler { } handleStreamingError(error: LDStreamingError) { - this.statusManager.reportError(error.kind, error.message, error.code, error.recoverable); + this._statusManager.reportError(error.kind, error.message, error.code, error.recoverable); } handlePollingError(error: LDPollingError) { - this.statusManager.reportError(error.kind, error.message, error.status, error.recoverable); + this._statusManager.reportError(error.kind, error.message, error.status, error.recoverable); } } diff --git a/packages/shared/sdk-client/src/datasource/DataSourceStatusManager.ts b/packages/shared/sdk-client/src/datasource/DataSourceStatusManager.ts index c5335c5e6..dd739625c 100644 --- a/packages/shared/sdk-client/src/datasource/DataSourceStatusManager.ts +++ b/packages/shared/sdk-client/src/datasource/DataSourceStatusManager.ts @@ -8,25 +8,25 @@ import DataSourceStatusErrorInfo from './DataSourceStatusErrorInfo'; * Tracks the current data source status and emits updates when the status changes. */ export default class DataSourceStatusManager { - private state: DataSourceState; - private stateSinceMillis: number; // UNIX epoch timestamp in milliseconds - private errorInfo?: DataSourceStatusErrorInfo; - private timeStamper: () => number; + private _state: DataSourceState; + private _stateSinceMillis: number; // UNIX epoch timestamp in milliseconds + private _errorInfo?: DataSourceStatusErrorInfo; + private _timeStamper: () => number; constructor( - private readonly emitter: LDEmitter, + private readonly _emitter: LDEmitter, timeStamper: () => number = () => Date.now(), ) { - this.state = DataSourceState.Closed; - this.stateSinceMillis = timeStamper(); - this.timeStamper = timeStamper; + this._state = DataSourceState.Closed; + this._stateSinceMillis = timeStamper(); + this._timeStamper = timeStamper; } get status(): DataSourceStatus { return { - state: this.state, - stateSince: this.stateSinceMillis, - lastError: this.errorInfo, + state: this._state, + stateSince: this._stateSinceMillis, + lastError: this._errorInfo, }; } @@ -36,20 +36,20 @@ export default class DataSourceStatusManager { * @param requestedState to track * @param isError to indicate that the state update is a result of an error occurring. */ - private updateState(requestedState: DataSourceState, isError = false) { + private _updateState(requestedState: DataSourceState, isError = false) { const newState = - requestedState === DataSourceState.Interrupted && this.state === DataSourceState.Initializing // don't go to interrupted from initializing (recoverable errors when initializing are not noteworthy) + requestedState === DataSourceState.Interrupted && this._state === DataSourceState.Initializing // don't go to interrupted from initializing (recoverable errors when initializing are not noteworthy) ? DataSourceState.Initializing : requestedState; - const changedState = this.state !== newState; + const changedState = this._state !== newState; if (changedState) { - this.state = newState; - this.stateSinceMillis = this.timeStamper(); + this._state = newState; + this._stateSinceMillis = this._timeStamper(); } if (changedState || isError) { - this.emitter.emit('dataSourceStatus', this.status); + this._emitter.emit('dataSourceStatus', this.status); } } @@ -59,7 +59,7 @@ export default class DataSourceStatusManager { * @param state that is requested */ requestStateUpdate(state: DataSourceState) { - this.updateState(state); + this._updateState(state); } /** @@ -82,10 +82,10 @@ export default class DataSourceStatusManager { kind, message, statusCode, - time: this.timeStamper(), + time: this._timeStamper(), }; - this.errorInfo = errorInfo; - this.updateState(recoverable ? DataSourceState.Interrupted : DataSourceState.Closed, true); + this._errorInfo = errorInfo; + this._updateState(recoverable ? DataSourceState.Interrupted : DataSourceState.Closed, true); } // TODO: SDK-702 - Implement network availability behaviors diff --git a/packages/shared/sdk-client/src/flag-manager/FlagManager.ts b/packages/shared/sdk-client/src/flag-manager/FlagManager.ts index 4dcc35a3e..61338c4f7 100644 --- a/packages/shared/sdk-client/src/flag-manager/FlagManager.ts +++ b/packages/shared/sdk-client/src/flag-manager/FlagManager.ts @@ -58,9 +58,9 @@ export interface FlagManager { } export default class DefaultFlagManager implements FlagManager { - private flagStore = new DefaultFlagStore(); - private flagUpdater: FlagUpdater; - private flagPersistencePromise: Promise; + private _flagStore = new DefaultFlagStore(); + private _flagUpdater: FlagUpdater; + private _flagPersistencePromise: Promise; /** * @param platform implementation of various platform provided functionality @@ -74,10 +74,10 @@ export default class DefaultFlagManager implements FlagManager { sdkKey: string, maxCachedContexts: number, logger: LDLogger, - private readonly timeStamper: () => number = () => Date.now(), + timeStamper: () => number = () => Date.now(), ) { - this.flagUpdater = new FlagUpdater(this.flagStore, logger); - this.flagPersistencePromise = this.initPersistence( + this._flagUpdater = new FlagUpdater(this._flagStore, logger); + this._flagPersistencePromise = this._initPersistence( platform, sdkKey, maxCachedContexts, @@ -86,7 +86,7 @@ export default class DefaultFlagManager implements FlagManager { ); } - private async initPersistence( + private async _initPersistence( platform: Platform, sdkKey: string, maxCachedContexts: number, @@ -99,44 +99,44 @@ export default class DefaultFlagManager implements FlagManager { platform, environmentNamespace, maxCachedContexts, - this.flagStore, - this.flagUpdater, + this._flagStore, + this._flagUpdater, logger, timeStamper, ); } get(key: string): ItemDescriptor | undefined { - return this.flagStore.get(key); + return this._flagStore.get(key); } getAll(): { [key: string]: ItemDescriptor } { - return this.flagStore.getAll(); + return this._flagStore.getAll(); } setBootstrap(context: Context, newFlags: { [key: string]: ItemDescriptor }): void { // Bypasses the persistence as we do not want to put these flags into any cache. // Generally speaking persistence likely *SHOULD* be disabled when using bootstrap. - this.flagUpdater.init(context, newFlags); + this._flagUpdater.init(context, newFlags); } async init(context: Context, newFlags: { [key: string]: ItemDescriptor }): Promise { - return (await this.flagPersistencePromise).init(context, newFlags); + return (await this._flagPersistencePromise).init(context, newFlags); } async upsert(context: Context, key: string, item: ItemDescriptor): Promise { - return (await this.flagPersistencePromise).upsert(context, key, item); + return (await this._flagPersistencePromise).upsert(context, key, item); } async loadCached(context: Context): Promise { - return (await this.flagPersistencePromise).loadCached(context); + return (await this._flagPersistencePromise).loadCached(context); } on(callback: FlagsChangeCallback): void { - this.flagUpdater.on(callback); + this._flagUpdater.on(callback); } off(callback: FlagsChangeCallback): void { - this.flagUpdater.off(callback); + this._flagUpdater.off(callback); } } diff --git a/packages/shared/sdk-client/src/flag-manager/FlagPersistence.ts b/packages/shared/sdk-client/src/flag-manager/FlagPersistence.ts index c761847a4..3977412d2 100644 --- a/packages/shared/sdk-client/src/flag-manager/FlagPersistence.ts +++ b/packages/shared/sdk-client/src/flag-manager/FlagPersistence.ts @@ -13,20 +13,20 @@ import { ItemDescriptor } from './ItemDescriptor'; * then persists changes after the updater has completed. */ export default class FlagPersistence { - private contextIndex: ContextIndex | undefined; - private indexKey?: string; - private indexKeyPromise: Promise; + private _contextIndex: ContextIndex | undefined; + private _indexKey?: string; + private _indexKeyPromise: Promise; constructor( - private readonly platform: Platform, - private readonly environmentNamespace: string, - private readonly maxCachedContexts: number, - private readonly flagStore: FlagStore, - private readonly flagUpdater: FlagUpdater, - private readonly logger: LDLogger, - private readonly timeStamper: () => number = () => Date.now(), + private readonly _platform: Platform, + private readonly _environmentNamespace: string, + private readonly _maxCachedContexts: number, + private readonly _flagStore: FlagStore, + private readonly _flagUpdater: FlagUpdater, + private readonly _logger: LDLogger, + private readonly _timeStamper: () => number = () => Date.now(), ) { - this.indexKeyPromise = namespaceForContextIndex(this.environmentNamespace); + this._indexKeyPromise = namespaceForContextIndex(this._environmentNamespace); } /** @@ -34,8 +34,8 @@ export default class FlagPersistence { * in the underlying {@link FlagUpdater} switching its active context. */ async init(context: Context, newFlags: { [key: string]: ItemDescriptor }): Promise { - this.flagUpdater.init(context, newFlags); - await this.storeCache(context); + this._flagUpdater.init(context, newFlags); + await this._storeCache(context); } /** @@ -44,8 +44,8 @@ export default class FlagPersistence { * the active context. */ async upsert(context: Context, key: string, item: ItemDescriptor): Promise { - if (this.flagUpdater.upsert(context, key, item)) { - await this.storeCache(context); + if (this._flagUpdater.upsert(context, key, item)) { + await this._storeCache(context); return true; } return false; @@ -57,23 +57,23 @@ export default class FlagPersistence { */ async loadCached(context: Context): Promise { const storageKey = await namespaceForContextData( - this.platform.crypto, - this.environmentNamespace, + this._platform.crypto, + this._environmentNamespace, context, ); - let flagsJson = await this.platform.storage?.get(storageKey); + let flagsJson = await this._platform.storage?.get(storageKey); if (flagsJson === null || flagsJson === undefined) { // Fallback: in version <10.3.1 flag data was stored under the canonical key, check // to see if data is present and migrate the data if present. - flagsJson = await this.platform.storage?.get(context.canonicalKey); + flagsJson = await this._platform.storage?.get(context.canonicalKey); if (flagsJson === null || flagsJson === undefined) { // return false indicating cache did not load if flag json is still absent return false; } // migrate data from version <10.3.1 and cleanup data that was under canonical key - await this.platform.storage?.set(storageKey, flagsJson); - await this.platform.storage?.clear(context.canonicalKey); + await this._platform.storage?.set(storageKey, flagsJson); + await this._platform.storage?.clear(context.canonicalKey); } try { @@ -88,53 +88,53 @@ export default class FlagPersistence { {}, ); - this.flagUpdater.initCached(context, descriptors); - this.logger.debug('Loaded cached flag evaluations from persistent storage'); + this._flagUpdater.initCached(context, descriptors); + this._logger.debug('Loaded cached flag evaluations from persistent storage'); return true; } catch (e: any) { - this.logger.warn( + this._logger.warn( `Could not load cached flag evaluations from persistent storage: ${e.message}`, ); return false; } } - private async loadIndex(): Promise { - if (this.contextIndex !== undefined) { - return this.contextIndex; + private async _loadIndex(): Promise { + if (this._contextIndex !== undefined) { + return this._contextIndex; } - const json = await this.platform.storage?.get(await this.indexKeyPromise); + const json = await this._platform.storage?.get(await this._indexKeyPromise); if (!json) { - this.contextIndex = new ContextIndex(); - return this.contextIndex; + this._contextIndex = new ContextIndex(); + return this._contextIndex; } try { - this.contextIndex = ContextIndex.fromJson(json); - this.logger.debug('Loaded context index from persistent storage'); + this._contextIndex = ContextIndex.fromJson(json); + this._logger.debug('Loaded context index from persistent storage'); } catch (e: any) { - this.logger.warn(`Could not load index from persistent storage: ${e.message}`); - this.contextIndex = new ContextIndex(); + this._logger.warn(`Could not load index from persistent storage: ${e.message}`); + this._contextIndex = new ContextIndex(); } - return this.contextIndex; + return this._contextIndex; } - private async storeCache(context: Context): Promise { - const index = await this.loadIndex(); + private async _storeCache(context: Context): Promise { + const index = await this._loadIndex(); const storageKey = await namespaceForContextData( - this.platform.crypto, - this.environmentNamespace, + this._platform.crypto, + this._environmentNamespace, context, ); - index.notice(storageKey, this.timeStamper()); + index.notice(storageKey, this._timeStamper()); - const pruned = index.prune(this.maxCachedContexts); - await Promise.all(pruned.map(async (it) => this.platform.storage?.clear(it.id))); + const pruned = index.prune(this._maxCachedContexts); + await Promise.all(pruned.map(async (it) => this._platform.storage?.clear(it.id))); // store index - await this.platform.storage?.set(await this.indexKeyPromise, index.toJson()); - const allFlags = this.flagStore.getAll(); + await this._platform.storage?.set(await this._indexKeyPromise, index.toJson()); + const allFlags = this._flagStore.getAll(); // mapping item descriptors to flags const flags = Object.entries(allFlags).reduce((acc: Flags, [key, descriptor]) => { @@ -146,6 +146,6 @@ export default class FlagPersistence { const jsonAll = JSON.stringify(flags); // store flag data - await this.platform.storage?.set(storageKey, jsonAll); + await this._platform.storage?.set(storageKey, jsonAll); } } diff --git a/packages/shared/sdk-client/src/flag-manager/FlagStore.ts b/packages/shared/sdk-client/src/flag-manager/FlagStore.ts index f58959721..2108aa646 100644 --- a/packages/shared/sdk-client/src/flag-manager/FlagStore.ts +++ b/packages/shared/sdk-client/src/flag-manager/FlagStore.ts @@ -14,10 +14,10 @@ export default interface FlagStore { * In memory flag store. */ export class DefaultFlagStore implements FlagStore { - private flags: { [key: string]: ItemDescriptor } = {}; + private _flags: { [key: string]: ItemDescriptor } = {}; init(newFlags: { [key: string]: ItemDescriptor }) { - this.flags = Object.entries(newFlags).reduce( + this._flags = Object.entries(newFlags).reduce( (acc: { [k: string]: ItemDescriptor }, [key, flag]) => { acc[key] = flag; return acc; @@ -27,17 +27,17 @@ export class DefaultFlagStore implements FlagStore { } insertOrUpdate(key: string, update: ItemDescriptor) { - this.flags[key] = update; + this._flags[key] = update; } get(key: string): ItemDescriptor | undefined { - if (Object.prototype.hasOwnProperty.call(this.flags, key)) { - return this.flags[key]; + if (Object.prototype.hasOwnProperty.call(this._flags, key)) { + return this._flags[key]; } return undefined; } getAll(): { [key: string]: ItemDescriptor } { - return this.flags; + return this._flags; } } diff --git a/packages/shared/sdk-client/src/flag-manager/FlagUpdater.ts b/packages/shared/sdk-client/src/flag-manager/FlagUpdater.ts index d96f9b393..f556094ca 100644 --- a/packages/shared/sdk-client/src/flag-manager/FlagUpdater.ts +++ b/packages/shared/sdk-client/src/flag-manager/FlagUpdater.ts @@ -25,23 +25,23 @@ export type FlagsChangeCallback = (context: Context, flagKeys: Array) => * also handles flag comparisons for change notification. */ export default class FlagUpdater { - private flagStore: FlagStore; - private logger: LDLogger; - private activeContextKey: string | undefined; - private changeCallbacks = new Array(); + private _flagStore: FlagStore; + private _logger: LDLogger; + private _activeContextKey: string | undefined; + private _changeCallbacks = new Array(); constructor(flagStore: FlagStore, logger: LDLogger) { - this.flagStore = flagStore; - this.logger = logger; + this._flagStore = flagStore; + this._logger = logger; } init(context: Context, newFlags: { [key: string]: ItemDescriptor }) { - this.activeContextKey = context.canonicalKey; - const oldFlags = this.flagStore.getAll(); - this.flagStore.init(newFlags); + this._activeContextKey = context.canonicalKey; + const oldFlags = this._flagStore.getAll(); + this._flagStore.init(newFlags); const changed = calculateChangedKeys(oldFlags, newFlags); if (changed.length > 0) { - this.changeCallbacks.forEach((callback) => { + this._changeCallbacks.forEach((callback) => { try { callback(context, changed); } catch (err) { @@ -52,7 +52,7 @@ export default class FlagUpdater { } initCached(context: Context, newFlags: { [key: string]: ItemDescriptor }) { - if (this.activeContextKey === context.canonicalKey) { + if (this._activeContextKey === context.canonicalKey) { return; } @@ -60,19 +60,19 @@ export default class FlagUpdater { } upsert(context: Context, key: string, item: ItemDescriptor): boolean { - if (this.activeContextKey !== context.canonicalKey) { - this.logger.warn('Received an update for an inactive context.'); + if (this._activeContextKey !== context.canonicalKey) { + this._logger.warn('Received an update for an inactive context.'); return false; } - const currentValue = this.flagStore.get(key); + const currentValue = this._flagStore.get(key); if (currentValue !== undefined && currentValue.version >= item.version) { // this is an out of order update that can be ignored return false; } - this.flagStore.insertOrUpdate(key, item); - this.changeCallbacks.forEach((callback) => { + this._flagStore.insertOrUpdate(key, item); + this._changeCallbacks.forEach((callback) => { try { callback(context, [key]); } catch (err) { @@ -83,13 +83,13 @@ export default class FlagUpdater { } on(callback: FlagsChangeCallback): void { - this.changeCallbacks.push(callback); + this._changeCallbacks.push(callback); } off(callback: FlagsChangeCallback): void { - const index = this.changeCallbacks.indexOf(callback); + const index = this._changeCallbacks.indexOf(callback); if (index > -1) { - this.changeCallbacks.splice(index, 1); + this._changeCallbacks.splice(index, 1); } } } diff --git a/packages/shared/sdk-client/src/polling/PollingProcessor.ts b/packages/shared/sdk-client/src/polling/PollingProcessor.ts index b421607df..a37a81121 100644 --- a/packages/shared/sdk-client/src/polling/PollingProcessor.ts +++ b/packages/shared/sdk-client/src/polling/PollingProcessor.ts @@ -21,58 +21,58 @@ export type PollingErrorHandler = (err: LDPollingError) => void; * @internal */ export default class PollingProcessor implements subsystem.LDStreamProcessor { - private stopped = false; + private _stopped = false; - private pollInterval: number; + private _pollInterval: number; - private timeoutHandle: any; + private _timeoutHandle: any; - private requestor: Requestor; + private _requestor: Requestor; constructor( - private readonly plainContextString: string, - private readonly dataSourceConfig: PollingDataSourceConfig, + private readonly _plainContextString: string, + private readonly _dataSourceConfig: PollingDataSourceConfig, requests: Requests, encoding: Encoding, - private readonly dataHandler: (flags: Flags) => void, - private readonly errorHandler?: PollingErrorHandler, - private readonly logger?: LDLogger, + private readonly _dataHandler: (flags: Flags) => void, + private readonly _errorHandler?: PollingErrorHandler, + private readonly _logger?: LDLogger, ) { - const path = dataSourceConfig.useReport - ? dataSourceConfig.paths.pathReport(encoding, plainContextString) - : dataSourceConfig.paths.pathGet(encoding, plainContextString); + const path = _dataSourceConfig.useReport + ? _dataSourceConfig.paths.pathReport(encoding, _plainContextString) + : _dataSourceConfig.paths.pathGet(encoding, _plainContextString); const parameters: { key: string; value: string }[] = [ - ...(dataSourceConfig.queryParameters ?? []), + ...(_dataSourceConfig.queryParameters ?? []), ]; - if (this.dataSourceConfig.withReasons) { + if (this._dataSourceConfig.withReasons) { parameters.push({ key: 'withReasons', value: 'true' }); } - const uri = getPollingUri(dataSourceConfig.serviceEndpoints, path, parameters); - this.pollInterval = dataSourceConfig.pollInterval; + const uri = getPollingUri(_dataSourceConfig.serviceEndpoints, path, parameters); + this._pollInterval = _dataSourceConfig.pollInterval; let method = 'GET'; - const headers: { [key: string]: string } = { ...dataSourceConfig.baseHeaders }; + const headers: { [key: string]: string } = { ..._dataSourceConfig.baseHeaders }; let body; - if (dataSourceConfig.useReport) { + if (_dataSourceConfig.useReport) { method = 'REPORT'; headers['content-type'] = 'application/json'; - body = plainContextString; // context is in body for REPORT + body = _plainContextString; // context is in body for REPORT } - this.requestor = new Requestor(requests, uri, headers, method, body); + this._requestor = new Requestor(requests, uri, headers, method, body); } - private async poll() { - if (this.stopped) { + private async _poll() { + if (this._stopped) { return; } const reportJsonError = (data: string) => { - this.logger?.error('Polling received invalid data'); - this.logger?.debug(`Invalid JSON follows: ${data}`); - this.errorHandler?.( + this._logger?.error('Polling received invalid data'); + this._logger?.debug(`Invalid JSON follows: ${data}`); + this._errorHandler?.( new LDPollingError( DataSourceErrorKind.InvalidData, 'Malformed JSON data in polling response', @@ -80,16 +80,16 @@ export default class PollingProcessor implements subsystem.LDStreamProcessor { ); }; - this.logger?.debug('Polling LaunchDarkly for feature flag updates'); + this._logger?.debug('Polling LaunchDarkly for feature flag updates'); const startTime = Date.now(); try { - const res = await this.requestor.requestPayload(); + const res = await this._requestor.requestPayload(); try { const flags = JSON.parse(res); try { - this.dataHandler?.(flags); + this._dataHandler?.(flags); } catch (err) { - this.logger?.error(`Exception from data handler: ${err}`); + this._logger?.error(`Exception from data handler: ${err}`); } } catch { reportJsonError(res); @@ -98,8 +98,8 @@ export default class PollingProcessor implements subsystem.LDStreamProcessor { const requestError = err as LDRequestError; if (requestError.status !== undefined) { if (!isHttpRecoverable(requestError.status)) { - this.logger?.error(httpErrorMessage(err as HttpErrorResponse, 'polling request')); - this.errorHandler?.( + this._logger?.error(httpErrorMessage(err as HttpErrorResponse, 'polling request')); + this._errorHandler?.( new LDPollingError( DataSourceErrorKind.ErrorResponse, requestError.message, @@ -109,31 +109,31 @@ export default class PollingProcessor implements subsystem.LDStreamProcessor { return; } } - this.logger?.error( + this._logger?.error( httpErrorMessage(err as HttpErrorResponse, 'polling request', 'will retry'), ); } const elapsed = Date.now() - startTime; - const sleepFor = Math.max(this.pollInterval * 1000 - elapsed, 0); + const sleepFor = Math.max(this._pollInterval * 1000 - elapsed, 0); - this.logger?.debug('Elapsed: %d ms, sleeping for %d ms', elapsed, sleepFor); + this._logger?.debug('Elapsed: %d ms, sleeping for %d ms', elapsed, sleepFor); - this.timeoutHandle = setTimeout(() => { - this.poll(); + this._timeoutHandle = setTimeout(() => { + this._poll(); }, sleepFor); } start() { - this.poll(); + this._poll(); } stop() { - if (this.timeoutHandle) { - clearTimeout(this.timeoutHandle); - this.timeoutHandle = undefined; + if (this._timeoutHandle) { + clearTimeout(this._timeoutHandle); + this._timeoutHandle = undefined; } - this.stopped = true; + this._stopped = true; } close() { diff --git a/packages/shared/sdk-client/src/polling/Requestor.ts b/packages/shared/sdk-client/src/polling/Requestor.ts index 2798f7474..449e202cf 100644 --- a/packages/shared/sdk-client/src/polling/Requestor.ts +++ b/packages/shared/sdk-client/src/polling/Requestor.ts @@ -21,20 +21,20 @@ export class LDRequestError extends Error implements HttpErrorResponse { */ export default class Requestor { constructor( - private requests: Requests, - private readonly uri: string, - private readonly headers: { [key: string]: string }, - private readonly method: string, - private readonly body?: string, + private _requests: Requests, + private readonly _uri: string, + private readonly _headers: { [key: string]: string }, + private readonly _method: string, + private readonly _body?: string, ) {} async requestPayload(): Promise { let status: number | undefined; try { - const res = await this.requests.fetch(this.uri, { - method: this.method, - headers: this.headers, - body: this.body, + const res = await this._requests.fetch(this._uri, { + method: this._method, + headers: this._headers, + body: this._body, }); if (isOk(res.status)) { return await res.text(); diff --git a/packages/shared/sdk-client/src/streaming/StreamingProcessor.ts b/packages/shared/sdk-client/src/streaming/StreamingProcessor.ts index cddb67839..f12c375a3 100644 --- a/packages/shared/sdk-client/src/streaming/StreamingProcessor.ts +++ b/packages/shared/sdk-client/src/streaming/StreamingProcessor.ts @@ -31,60 +31,60 @@ const reportJsonError = ( }; class StreamingProcessor implements subsystem.LDStreamProcessor { - private readonly headers: { [key: string]: string | string[] }; - private readonly streamUri: string; + private readonly _headers: { [key: string]: string | string[] }; + private readonly _streamUri: string; - private eventSource?: EventSource; - private connectionAttemptStartTime?: number; + private _eventSource?: EventSource; + private _connectionAttemptStartTime?: number; constructor( - private readonly plainContextString: string, - private readonly dataSourceConfig: StreamingDataSourceConfig, - private readonly listeners: Map, - private readonly requests: Requests, + private readonly _plainContextString: string, + private readonly _dataSourceConfig: StreamingDataSourceConfig, + private readonly _listeners: Map, + private readonly _requests: Requests, encoding: Encoding, - private readonly diagnosticsManager?: internal.DiagnosticsManager, - private readonly errorHandler?: internal.StreamingErrorHandler, - private readonly logger?: LDLogger, + private readonly _diagnosticsManager?: internal.DiagnosticsManager, + private readonly _errorHandler?: internal.StreamingErrorHandler, + private readonly _logger?: LDLogger, ) { // TODO: SC-255969 Implement better REPORT fallback logic - if (dataSourceConfig.useReport && !requests.getEventSourceCapabilities().customMethod) { - logger?.error( + if (_dataSourceConfig.useReport && !_requests.getEventSourceCapabilities().customMethod) { + _logger?.error( "Configuration option useReport is true, but platform's EventSource does not support custom HTTP methods. Streaming may not work.", ); } - const path = dataSourceConfig.useReport - ? dataSourceConfig.paths.pathReport(encoding, plainContextString) - : dataSourceConfig.paths.pathGet(encoding, plainContextString); + const path = _dataSourceConfig.useReport + ? _dataSourceConfig.paths.pathReport(encoding, _plainContextString) + : _dataSourceConfig.paths.pathGet(encoding, _plainContextString); const parameters: { key: string; value: string }[] = [ - ...(dataSourceConfig.queryParameters ?? []), + ...(_dataSourceConfig.queryParameters ?? []), ]; - if (this.dataSourceConfig.withReasons) { + if (this._dataSourceConfig.withReasons) { parameters.push({ key: 'withReasons', value: 'true' }); } - this.requests = requests; - this.headers = { ...dataSourceConfig.baseHeaders }; - this.logger = logger; - this.streamUri = getStreamingUri(dataSourceConfig.serviceEndpoints, path, parameters); + this._requests = _requests; + this._headers = { ..._dataSourceConfig.baseHeaders }; + this._logger = _logger; + this._streamUri = getStreamingUri(_dataSourceConfig.serviceEndpoints, path, parameters); } - private logConnectionStarted() { - this.connectionAttemptStartTime = Date.now(); + private _logConnectionStarted() { + this._connectionAttemptStartTime = Date.now(); } - private logConnectionResult(success: boolean) { - if (this.connectionAttemptStartTime && this.diagnosticsManager) { - this.diagnosticsManager.recordStreamInit( - this.connectionAttemptStartTime, + private _logConnectionResult(success: boolean) { + if (this._connectionAttemptStartTime && this._diagnosticsManager) { + this._diagnosticsManager.recordStreamInit( + this._connectionAttemptStartTime, !success, - Date.now() - this.connectionAttemptStartTime, + Date.now() - this._connectionAttemptStartTime, ); } - this.connectionAttemptStartTime = undefined; + this._connectionAttemptStartTime = undefined; } /** @@ -96,50 +96,50 @@ class StreamingProcessor implements subsystem.LDStreamProcessor { * * @private */ - private retryAndHandleError(err: HttpErrorResponse) { + private _retryAndHandleError(err: HttpErrorResponse) { if (!shouldRetry(err)) { - this.logConnectionResult(false); - this.errorHandler?.( + this._logConnectionResult(false); + this._errorHandler?.( new LDStreamingError(DataSourceErrorKind.ErrorResponse, err.message, err.status, false), ); - this.logger?.error(httpErrorMessage(err, 'streaming request')); + this._logger?.error(httpErrorMessage(err, 'streaming request')); return false; } - this.logger?.warn(httpErrorMessage(err, 'streaming request', 'will retry')); - this.logConnectionResult(false); - this.logConnectionStarted(); + this._logger?.warn(httpErrorMessage(err, 'streaming request', 'will retry')); + this._logConnectionResult(false); + this._logConnectionStarted(); return true; } start() { - this.logConnectionStarted(); + this._logConnectionStarted(); let methodAndBodyOverrides; - if (this.dataSourceConfig.useReport) { + if (this._dataSourceConfig.useReport) { // REPORT will include a body, so content type is required. - this.headers['content-type'] = 'application/json'; + this._headers['content-type'] = 'application/json'; // orverrides default method with REPORT and adds body. - methodAndBodyOverrides = { method: 'REPORT', body: this.plainContextString }; + methodAndBodyOverrides = { method: 'REPORT', body: this._plainContextString }; } else { // no method or body override methodAndBodyOverrides = {}; } // TLS is handled by the platform implementation. - const eventSource = this.requests.createEventSource(this.streamUri, { - headers: this.headers, // adds content-type header required when body will be present + const eventSource = this._requests.createEventSource(this._streamUri, { + headers: this._headers, // adds content-type header required when body will be present ...methodAndBodyOverrides, - errorFilter: (error: HttpErrorResponse) => this.retryAndHandleError(error), - initialRetryDelayMillis: this.dataSourceConfig.initialRetryDelayMillis, + errorFilter: (error: HttpErrorResponse) => this._retryAndHandleError(error), + initialRetryDelayMillis: this._dataSourceConfig.initialRetryDelayMillis, readTimeoutMillis: 5 * 60 * 1000, retryResetIntervalMillis: 60 * 1000, }); - this.eventSource = eventSource; + this._eventSource = eventSource; eventSource.onclose = () => { - this.logger?.info('Closed LaunchDarkly stream connection'); + this._logger?.info('Closed LaunchDarkly stream connection'); }; eventSource.onerror = () => { @@ -147,29 +147,29 @@ class StreamingProcessor implements subsystem.LDStreamProcessor { }; eventSource.onopen = () => { - this.logger?.info('Opened LaunchDarkly stream connection'); + this._logger?.info('Opened LaunchDarkly stream connection'); }; eventSource.onretrying = (e) => { - this.logger?.info(`Will retry stream connection in ${e.delayMillis} milliseconds`); + this._logger?.info(`Will retry stream connection in ${e.delayMillis} milliseconds`); }; - this.listeners.forEach(({ deserializeData, processJson }, eventName) => { + this._listeners.forEach(({ deserializeData, processJson }, eventName) => { eventSource.addEventListener(eventName, (event) => { - this.logger?.debug(`Received ${eventName} event`); + this._logger?.debug(`Received ${eventName} event`); if (event?.data) { - this.logConnectionResult(true); + this._logConnectionResult(true); const { data } = event; const dataJson = deserializeData(data); if (!dataJson) { - reportJsonError(eventName, data, this.logger, this.errorHandler); + reportJsonError(eventName, data, this._logger, this._errorHandler); return; } processJson(dataJson); } else { - this.errorHandler?.( + this._errorHandler?.( new LDStreamingError( DataSourceErrorKind.InvalidData, 'Unexpected payload from event stream', @@ -181,8 +181,8 @@ class StreamingProcessor implements subsystem.LDStreamProcessor { } stop() { - this.eventSource?.close(); - this.eventSource = undefined; + this._eventSource?.close(); + this._eventSource = undefined; } close() { diff --git a/packages/shared/sdk-server-edge/src/api/EdgeFeatureStore.ts b/packages/shared/sdk-server-edge/src/api/EdgeFeatureStore.ts index 933943d20..039ee4a30 100644 --- a/packages/shared/sdk-server-edge/src/api/EdgeFeatureStore.ts +++ b/packages/shared/sdk-server-edge/src/api/EdgeFeatureStore.ts @@ -13,15 +13,15 @@ export interface EdgeProvider { } export class EdgeFeatureStore implements LDFeatureStore { - private readonly rootKey: string; + private readonly _rootKey: string; constructor( - private readonly edgeProvider: EdgeProvider, - private readonly sdkKey: string, - private readonly description: string, - private logger: LDLogger, + private readonly _edgeProvider: EdgeProvider, + sdkKey: string, + private readonly _description: string, + private _logger: LDLogger, ) { - this.rootKey = `LD-Env-${sdkKey}`; + this._rootKey = `LD-Env-${sdkKey}`; } async get( @@ -31,13 +31,13 @@ export class EdgeFeatureStore implements LDFeatureStore { ): Promise { const { namespace } = kind; const kindKey = namespace === 'features' ? 'flags' : namespace; - this.logger.debug(`Requesting ${dataKey} from ${this.rootKey}.${kindKey}`); + this._logger.debug(`Requesting ${dataKey} from ${this._rootKey}.${kindKey}`); try { - const i = await this.edgeProvider.get(this.rootKey); + const i = await this._edgeProvider.get(this._rootKey); if (!i) { - throw new Error(`${this.rootKey}.${kindKey} is not found in KV.`); + throw new Error(`${this._rootKey}.${kindKey} is not found in KV.`); } const item = deserializePoll(i); @@ -56,7 +56,7 @@ export class EdgeFeatureStore implements LDFeatureStore { callback(null); } } catch (err) { - this.logger.error(err); + this._logger.error(err); callback(null); } } @@ -64,11 +64,11 @@ export class EdgeFeatureStore implements LDFeatureStore { async all(kind: DataKind, callback: (res: LDFeatureStoreKindData) => void = noop): Promise { const { namespace } = kind; const kindKey = namespace === 'features' ? 'flags' : namespace; - this.logger.debug(`Requesting all from ${this.rootKey}.${kindKey}`); + this._logger.debug(`Requesting all from ${this._rootKey}.${kindKey}`); try { - const i = await this.edgeProvider.get(this.rootKey); + const i = await this._edgeProvider.get(this._rootKey); if (!i) { - throw new Error(`${this.rootKey}.${kindKey} is not found in KV.`); + throw new Error(`${this._rootKey}.${kindKey} is not found in KV.`); } const item = deserializePoll(i); @@ -87,15 +87,15 @@ export class EdgeFeatureStore implements LDFeatureStore { callback({}); } } catch (err) { - this.logger.error(err); + this._logger.error(err); callback({}); } } async initialized(callback: (isInitialized: boolean) => void = noop): Promise { - const config = await this.edgeProvider.get(this.rootKey); + const config = await this._edgeProvider.get(this._rootKey); const result = config !== null; - this.logger.debug(`Is ${this.rootKey} initialized? ${result}`); + this._logger.debug(`Is ${this._rootKey} initialized? ${result}`); callback(result); } @@ -104,7 +104,7 @@ export class EdgeFeatureStore implements LDFeatureStore { } getDescription(): string { - return this.description; + return this._description; } // unused diff --git a/packages/shared/sdk-server-edge/src/platform/crypto/cryptoJSHasher.ts b/packages/shared/sdk-server-edge/src/platform/crypto/cryptoJSHasher.ts index b8596649e..4aec22105 100644 --- a/packages/shared/sdk-server-edge/src/platform/crypto/cryptoJSHasher.ts +++ b/packages/shared/sdk-server-edge/src/platform/crypto/cryptoJSHasher.ts @@ -5,7 +5,7 @@ import { Hasher as LDHasher } from '@launchdarkly/js-server-sdk-common'; import { SupportedHashAlgorithm, SupportedOutputEncoding } from './types'; export default class CryptoJSHasher implements LDHasher { - private cryptoJSHasher; + private _cryptoJSHasher; constructor(algorithm: SupportedHashAlgorithm) { let algo; @@ -21,11 +21,11 @@ export default class CryptoJSHasher implements LDHasher { throw new Error('unsupported hash algorithm. Only sha1 and sha256 are supported.'); } - this.cryptoJSHasher = algo.create(); + this._cryptoJSHasher = algo.create(); } digest(encoding: SupportedOutputEncoding): string { - const result = this.cryptoJSHasher.finalize(); + const result = this._cryptoJSHasher.finalize(); let enc; switch (encoding) { @@ -43,7 +43,7 @@ export default class CryptoJSHasher implements LDHasher { } update(data: string): this { - this.cryptoJSHasher.update(data); + this._cryptoJSHasher.update(data); return this; } } diff --git a/packages/shared/sdk-server-edge/src/platform/crypto/cryptoJSHmac.ts b/packages/shared/sdk-server-edge/src/platform/crypto/cryptoJSHmac.ts index 38f7ea259..98e8976bb 100644 --- a/packages/shared/sdk-server-edge/src/platform/crypto/cryptoJSHmac.ts +++ b/packages/shared/sdk-server-edge/src/platform/crypto/cryptoJSHmac.ts @@ -5,7 +5,7 @@ import { Hmac as LDHmac } from '@launchdarkly/js-server-sdk-common'; import { SupportedHashAlgorithm, SupportedOutputEncoding } from './types'; export default class CryptoJSHmac implements LDHmac { - private CryptoJSHmac; + private _cryptoJSHmac; constructor(algorithm: SupportedHashAlgorithm, key: string) { let algo; @@ -21,11 +21,11 @@ export default class CryptoJSHmac implements LDHmac { throw new Error('unsupported hash algorithm. Only sha1 and sha256 are supported.'); } - this.CryptoJSHmac = CryptoJS.algo.HMAC.create(algo, key); + this._cryptoJSHmac = CryptoJS.algo.HMAC.create(algo, key); } digest(encoding: SupportedOutputEncoding): string { - const result = this.CryptoJSHmac.finalize(); + const result = this._cryptoJSHmac.finalize(); if (encoding === 'base64') { return result.toString(CryptoJS.enc.Base64); @@ -39,7 +39,7 @@ export default class CryptoJSHmac implements LDHmac { } update(data: string): this { - this.CryptoJSHmac.update(data); + this._cryptoJSHmac.update(data); return this; } } diff --git a/packages/shared/sdk-server/__tests__/BigSegmentsManager.test.ts b/packages/shared/sdk-server/__tests__/BigSegmentsManager.test.ts index 1bda8c9dd..ab4a157cd 100644 --- a/packages/shared/sdk-server/__tests__/BigSegmentsManager.test.ts +++ b/packages/shared/sdk-server/__tests__/BigSegmentsManager.test.ts @@ -22,15 +22,15 @@ const userKey = 'userkey'; const userHash = 'is_hashed:userkey'; class TestHasher implements Hasher { - private value: string = 'is_hashed:'; + private _value: string = 'is_hashed:'; update(toAdd: string): Hasher { - this.value += toAdd; + this._value += toAdd; return this; } digest() { - return this.value; + return this._value; } } diff --git a/packages/shared/sdk-server/__tests__/LDClientImpl.bigSegments.test.ts b/packages/shared/sdk-server/__tests__/LDClientImpl.bigSegments.test.ts index 9015cc338..e5ef9bc52 100644 --- a/packages/shared/sdk-server/__tests__/LDClientImpl.bigSegments.test.ts +++ b/packages/shared/sdk-server/__tests__/LDClientImpl.bigSegments.test.ts @@ -25,15 +25,15 @@ const flag = { }; class TestHasher implements Hasher { - private value: string = 'is_hashed:'; + private _value: string = 'is_hashed:'; update(toAdd: string): Hasher { - this.value += toAdd; + this._value += toAdd; return this; } digest() { - return this.value; + return this._value; } } diff --git a/packages/shared/sdk-server/__tests__/Logger.ts b/packages/shared/sdk-server/__tests__/Logger.ts index 6f7e2ff5f..7340cf6d5 100644 --- a/packages/shared/sdk-server/__tests__/Logger.ts +++ b/packages/shared/sdk-server/__tests__/Logger.ts @@ -18,7 +18,7 @@ function replacer(key: string, value: any) { } export default class TestLogger implements LDLogger { - private readonly messages: Record = { + private readonly _messages: Record = { debug: [], info: [], warn: [], @@ -27,13 +27,13 @@ export default class TestLogger implements LDLogger { none: [], }; - private callCount = 0; + private _callCount = 0; - private waiters: Array<() => void> = []; + private _waiters: Array<() => void> = []; timeout(timeoutMs: number): Promise { return new Promise((resolve) => { - setTimeout(() => resolve(this.callCount), timeoutMs); + setTimeout(() => resolve(this._callCount), timeoutMs); }); } @@ -41,12 +41,12 @@ export default class TestLogger implements LDLogger { return Promise.race([ new Promise((resolve) => { const waiter = () => { - if (this.callCount >= count) { - resolve(this.callCount); + if (this._callCount >= count) { + resolve(this._callCount); } }; waiter(); - this.waiters.push(waiter); + this._waiters.push(waiter); }), this.timeout(timeoutMs), ]); @@ -69,7 +69,7 @@ export default class TestLogger implements LDLogger { }; expectedMessages.forEach((expectedMessage) => { - const received = this.messages[expectedMessage.level]; + const received = this._messages[expectedMessage.level]; const index = received.findIndex((receivedMessage) => receivedMessage.match(expectedMessage.matches), ); @@ -78,14 +78,14 @@ export default class TestLogger implements LDLogger { `Did not find expected message: ${JSON.stringify( expectedMessage, replacer, - )} received: ${JSON.stringify(this.messages)}`, + )} received: ${JSON.stringify(this._messages)}`, ); } else if (matched[expectedMessage.level].indexOf(index) >= 0) { throw new Error( `Did not find expected message: ${JSON.stringify( expectedMessage, replacer, - )} received: ${JSON.stringify(this.messages)}`, + )} received: ${JSON.stringify(this._messages)}`, ); } else { matched[expectedMessage.level].push(index); @@ -95,34 +95,34 @@ export default class TestLogger implements LDLogger { getCount(level?: LogLevel) { if (level === undefined) { - return this.callCount; + return this._callCount; } - return this.messages[level].length; + return this._messages[level].length; } - private checkResolves() { - this.waiters.forEach((waiter) => waiter()); + private _checkResolves() { + this._waiters.forEach((waiter) => waiter()); } - private log(level: LDLogLevel, ...args: any[]) { - this.messages[level].push(args.join(' ')); - this.callCount += 1; - this.checkResolves(); + private _log(level: LDLogLevel, ...args: any[]) { + this._messages[level].push(args.join(' ')); + this._callCount += 1; + this._checkResolves(); } error(...args: any[]): void { - this.log('error', args); + this._log('error', args); } warn(...args: any[]): void { - this.log('warn', args); + this._log('warn', args); } info(...args: any[]): void { - this.log('info', args); + this._log('info', args); } debug(...args: any[]): void { - this.log('debug', args); + this._log('debug', args); } } diff --git a/packages/shared/sdk-server/__tests__/evaluation/Evaluator.segments.test.ts b/packages/shared/sdk-server/__tests__/evaluation/Evaluator.segments.test.ts index d27ac07dd..f4cf80ced 100644 --- a/packages/shared/sdk-server/__tests__/evaluation/Evaluator.segments.test.ts +++ b/packages/shared/sdk-server/__tests__/evaluation/Evaluator.segments.test.ts @@ -32,19 +32,19 @@ const basicMultiKindUser: LDContext = { kind: 'multi', user: { key: 'userkey' } class TestQueries implements Queries { constructor( - private readonly data: { + private readonly _data: { flags?: Flag[]; segments?: Segment[]; }, ) {} getFlag(key: string, cb: (flag: Flag | undefined) => void): void { - const res = this.data.flags?.find((flag) => flag.key === key); + const res = this._data.flags?.find((flag) => flag.key === key); cb(res); } getSegment(key: string, cb: (segment: Segment | undefined) => void): void { - const res = this.data.segments?.find((segment) => segment.key === key); + const res = this._data.segments?.find((segment) => segment.key === key); cb(res); } diff --git a/packages/shared/sdk-server/__tests__/integrations/test_data/TestData.test.ts b/packages/shared/sdk-server/__tests__/integrations/test_data/TestData.test.ts index facb21258..aee9a6b3b 100644 --- a/packages/shared/sdk-server/__tests__/integrations/test_data/TestData.test.ts +++ b/packages/shared/sdk-server/__tests__/integrations/test_data/TestData.test.ts @@ -318,7 +318,7 @@ describe('given a TestData instance', () => { { attribute: 'name', attributeReference: { - components: ['name'], + _components: ['name'], isValid: true, redactionName: 'name', }, diff --git a/packages/shared/sdk-server/src/BigSegmentStatusProviderImpl.ts b/packages/shared/sdk-server/src/BigSegmentStatusProviderImpl.ts index 07ae740d6..0b5a9dfc4 100644 --- a/packages/shared/sdk-server/src/BigSegmentStatusProviderImpl.ts +++ b/packages/shared/sdk-server/src/BigSegmentStatusProviderImpl.ts @@ -5,11 +5,11 @@ import { BigSegmentStoreStatus, BigSegmentStoreStatusProvider } from './api/inte * @ignore */ export default class BigSegmentStoreStatusProviderImpl implements BigSegmentStoreStatusProvider { - private lastStatus: BigSegmentStoreStatus | undefined; + private _lastStatus: BigSegmentStoreStatus | undefined; - private listener?: (status: BigSegmentStoreStatus) => void; + private _listener?: (status: BigSegmentStoreStatus) => void; - constructor(private readonly onRequestStatus: () => Promise) {} + constructor(private readonly _onRequestStatus: () => Promise) {} /** * Gets the current status of the store, if known. @@ -18,7 +18,7 @@ export default class BigSegmentStoreStatusProviderImpl implements BigSegmentStor * Big Segment store status */ getStatus(): BigSegmentStoreStatus | undefined { - return this.lastStatus; + return this._lastStatus; } /** @@ -27,25 +27,25 @@ export default class BigSegmentStoreStatusProviderImpl implements BigSegmentStor * @returns a Promise for the status of the store */ async requireStatus(): Promise { - if (!this.lastStatus) { - await this.onRequestStatus(); + if (!this._lastStatus) { + await this._onRequestStatus(); } // Status will be defined at this point. - return this.lastStatus!; + return this._lastStatus!; } notify() { - if (this.lastStatus) { - this.listener?.(this.lastStatus); + if (this._lastStatus) { + this._listener?.(this._lastStatus); } } setListener(listener: (status: BigSegmentStoreStatus) => void) { - this.listener = listener; + this._listener = listener; } setStatus(status: BigSegmentStoreStatus) { - this.lastStatus = status; + this._lastStatus = status; } } diff --git a/packages/shared/sdk-server/src/BigSegmentsManager.ts b/packages/shared/sdk-server/src/BigSegmentsManager.ts index d3311cc8e..e6c933bf3 100644 --- a/packages/shared/sdk-server/src/BigSegmentsManager.ts +++ b/packages/shared/sdk-server/src/BigSegmentsManager.ts @@ -15,27 +15,27 @@ interface MembershipCacheItem { } export default class BigSegmentsManager { - private cache: LruCache | undefined; + private _cache: LruCache | undefined; - private pollHandle: any; + private _pollHandle: any; - private staleTimeMs: number; + private _staleTimeMs: number; public readonly statusProvider: BigSegmentStoreStatusProviderImpl; constructor( - private store: BigSegmentStore | undefined, + private _store: BigSegmentStore | undefined, // The store will have been created before the manager is instantiated, so we do not need // it in the options at this stage. config: Omit, - private readonly logger: LDLogger | undefined, - private readonly crypto: Crypto, + private readonly _logger: LDLogger | undefined, + private readonly _crypto: Crypto, ) { this.statusProvider = new BigSegmentStoreStatusProviderImpl(async () => - this.pollStoreAndUpdateStatus(), + this._pollStoreAndUpdateStatus(), ); - this.staleTimeMs = + this._staleTimeMs = (TypeValidators.Number.is(config.staleAfter) && config.staleAfter > 0 ? config.staleAfter : DEFAULT_STALE_AFTER_SECONDS) * 1000; @@ -45,12 +45,12 @@ export default class BigSegmentsManager { ? config.statusPollInterval : DEFAULT_STATUS_POLL_INTERVAL_SECONDS) * 1000; - this.pollHandle = store - ? setInterval(() => this.pollStoreAndUpdateStatus(), pollIntervalMs) + this._pollHandle = _store + ? setInterval(() => this._pollStoreAndUpdateStatus(), pollIntervalMs) : null; - if (store) { - this.cache = new LruCache({ + if (_store) { + this._cache = new LruCache({ max: config.userCacheSize || DEFAULT_USER_CACHE_SIZE, maxAge: (config.userCacheTime || DEFAULT_USER_CACHE_TIME_SECONDS) * 1000, }); @@ -58,31 +58,31 @@ export default class BigSegmentsManager { } public close() { - if (this.pollHandle) { - clearInterval(this.pollHandle); - this.pollHandle = undefined; + if (this._pollHandle) { + clearInterval(this._pollHandle); + this._pollHandle = undefined; } - if (this.store) { - this.store.close(); + if (this._store) { + this._store.close(); } } public async getUserMembership( userKey: string, ): Promise<[BigSegmentStoreMembership | null, string] | undefined> { - if (!this.store) { + if (!this._store) { return undefined; } - const memberCache: MembershipCacheItem | undefined = this.cache?.get(userKey); + const memberCache: MembershipCacheItem | undefined = this._cache?.get(userKey); let membership: BigSegmentStoreMembership | undefined; if (!memberCache) { try { - membership = await this.store.getUserMembership(this.hashForUserKey(userKey)); + membership = await this._store.getUserMembership(this._hashForUserKey(userKey)); const cacheItem: MembershipCacheItem = { membership }; - this.cache?.set(userKey, cacheItem); + this._cache?.set(userKey, cacheItem); } catch (err) { - this.logger?.error(`Big Segment store membership query returned error: ${err}`); + this._logger?.error(`Big Segment store membership query returned error: ${err}`); return [null, 'STORE_ERROR']; } } else { @@ -90,7 +90,7 @@ export default class BigSegmentsManager { } if (!this.statusProvider.getStatus()) { - await this.pollStoreAndUpdateStatus(); + await this._pollStoreAndUpdateStatus(); } // Status will be present, because polling is done earlier in this method if it is not. @@ -103,24 +103,24 @@ export default class BigSegmentsManager { return [membership || null, lastStatus.stale ? 'STALE' : 'HEALTHY']; } - private async pollStoreAndUpdateStatus() { - if (!this.store) { + private async _pollStoreAndUpdateStatus() { + if (!this._store) { this.statusProvider.setStatus({ available: false, stale: false }); return; } - this.logger?.debug('Querying Big Segment store status'); + this._logger?.debug('Querying Big Segment store status'); let newStatus; try { - const metadata = await this.store.getMetadata(); + const metadata = await this._store.getMetadata(); newStatus = { available: true, - stale: !metadata || !metadata.lastUpToDate || this.isStale(metadata.lastUpToDate), + stale: !metadata || !metadata.lastUpToDate || this._isStale(metadata.lastUpToDate), }; } catch (err) { - this.logger?.error(`Big Segment store status query returned error: ${err}`); + this._logger?.error(`Big Segment store status query returned error: ${err}`); newStatus = { available: false, stale: false }; } @@ -131,7 +131,7 @@ export default class BigSegmentsManager { lastStatus.available !== newStatus.available || lastStatus.stale !== newStatus.stale ) { - this.logger?.debug( + this._logger?.debug( 'Big Segment store status changed from %s to %s', JSON.stringify(lastStatus), JSON.stringify(newStatus), @@ -141,8 +141,8 @@ export default class BigSegmentsManager { } } - private hashForUserKey(userKey: string): string { - const hasher = this.crypto.createHash('sha256'); + private _hashForUserKey(userKey: string): string { + const hasher = this._crypto.createHash('sha256'); hasher.update(userKey); if (!hasher.digest) { // This represents an error in platform implementation. @@ -151,7 +151,7 @@ export default class BigSegmentsManager { return hasher.digest('base64'); } - private isStale(timestamp: number) { - return Date.now() - timestamp >= this.staleTimeMs; + private _isStale(timestamp: number) { + return Date.now() - timestamp >= this._staleTimeMs; } } diff --git a/packages/shared/sdk-server/src/FlagsStateBuilder.ts b/packages/shared/sdk-server/src/FlagsStateBuilder.ts index 86bbc23bb..ed78f3305 100644 --- a/packages/shared/sdk-server/src/FlagsStateBuilder.ts +++ b/packages/shared/sdk-server/src/FlagsStateBuilder.ts @@ -13,13 +13,13 @@ interface FlagMeta { } export default class FlagsStateBuilder { - private flagValues: LDFlagSet = {}; + private _flagValues: LDFlagSet = {}; - private flagMetadata: Record = {}; + private _flagMetadata: Record = {}; constructor( - private valid: boolean, - private withReasons: boolean, + private _valid: boolean, + private _withReasons: boolean, ) {} addFlag( @@ -31,7 +31,7 @@ export default class FlagsStateBuilder { trackReason: boolean, detailsOnlyIfTracked: boolean, ) { - this.flagValues[flag.key] = value; + this._flagValues[flag.key] = value; const meta: FlagMeta = {}; if (variation !== undefined) { meta.variation = variation; @@ -44,7 +44,7 @@ export default class FlagsStateBuilder { if (!omitDetails) { meta.version = flag.version; } - if (reason && (trackReason || (this.withReasons && !omitDetails))) { + if (reason && (trackReason || (this._withReasons && !omitDetails))) { meta.reason = reason; } if (trackEvents) { @@ -56,21 +56,20 @@ export default class FlagsStateBuilder { if (flag.debugEventsUntilDate !== undefined) { meta.debugEventsUntilDate = flag.debugEventsUntilDate; } - this.flagMetadata[flag.key] = meta; + this._flagMetadata[flag.key] = meta; } build(): LDFlagsState { - const state = this; return { - valid: state.valid, - allValues: () => state.flagValues, - getFlagValue: (key) => state.flagValues[key], + valid: this._valid, + allValues: () => this._flagValues, + getFlagValue: (key) => this._flagValues[key], getFlagReason: (key) => - (state.flagMetadata[key] ? state.flagMetadata[key].reason : null) ?? null, + (this._flagMetadata[key] ? this._flagMetadata[key].reason : null) ?? null, toJSON: () => ({ - ...state.flagValues, - $flagsState: state.flagMetadata, - $valid: state.valid, + ...this._flagValues, + $flagsState: this._flagMetadata, + $valid: this._valid, }), }; } diff --git a/packages/shared/sdk-server/src/LDClientImpl.ts b/packages/shared/sdk-server/src/LDClientImpl.ts index 34d6f02a5..baddcb883 100644 --- a/packages/shared/sdk-server/src/LDClientImpl.ts +++ b/packages/shared/sdk-server/src/LDClientImpl.ts @@ -90,43 +90,43 @@ const VARIATION_METHOD_DETAIL_NAME = 'LDClient.variationDetail'; * @ignore */ export default class LDClientImpl implements LDClient { - private initState: InitState = InitState.Initializing; + private _initState: InitState = InitState.Initializing; - private featureStore: LDFeatureStore; + private _featureStore: LDFeatureStore; - private updateProcessor?: subsystem.LDStreamProcessor; + private _updateProcessor?: subsystem.LDStreamProcessor; - private eventFactoryDefault = new EventFactory(false); + private _eventFactoryDefault = new EventFactory(false); - private eventFactoryWithReasons = new EventFactory(true); + private _eventFactoryWithReasons = new EventFactory(true); - private eventProcessor: subsystem.LDEventProcessor; + private _eventProcessor: subsystem.LDEventProcessor; - private evaluator: Evaluator; + private _evaluator: Evaluator; - private initResolve?: (value: LDClient | PromiseLike) => void; + private _initResolve?: (value: LDClient | PromiseLike) => void; - private initReject?: (err: Error) => void; + private _initReject?: (err: Error) => void; - private rejectionReason: Error | undefined; + private _rejectionReason: Error | undefined; - private initializedPromise?: Promise; + private _initializedPromise?: Promise; - private logger?: LDLogger; + private _logger?: LDLogger; - private config: Configuration; + private _config: Configuration; - private bigSegmentsManager: BigSegmentsManager; + private _bigSegmentsManager: BigSegmentsManager; - private onError: (err: Error) => void; + private _onError: (err: Error) => void; - private onFailed: (err: Error) => void; + private _onFailed: (err: Error) => void; - private onReady: () => void; + private _onReady: () => void; - private diagnosticsManager?: internal.DiagnosticsManager; + private _diagnosticsManager?: internal.DiagnosticsManager; - private hookRunner: HookRunner; + private _hookRunner: HookRunner; /** * Intended for use by platform specific client implementations. @@ -138,62 +138,62 @@ export default class LDClientImpl implements LDClient { protected bigSegmentStatusProviderInternal: BigSegmentStoreStatusProvider; constructor( - private sdkKey: string, - private platform: Platform, + private _sdkKey: string, + private _platform: Platform, options: LDOptions, callbacks: LDClientCallbacks, internalOptions?: internal.LDInternalOptions, ) { - this.onError = callbacks.onError; - this.onFailed = callbacks.onFailed; - this.onReady = callbacks.onReady; + this._onError = callbacks.onError; + this._onFailed = callbacks.onFailed; + this._onReady = callbacks.onReady; const { onUpdate, hasEventListeners } = callbacks; const config = new Configuration(options, internalOptions); - this.hookRunner = new HookRunner(config.logger, config.hooks || []); + this._hookRunner = new HookRunner(config.logger, config.hooks || []); - if (!sdkKey && !config.offline) { + if (!_sdkKey && !config.offline) { throw new Error('You must configure the client with an SDK key'); } - this.config = config; - this.logger = config.logger; - const baseHeaders = defaultHeaders(sdkKey, platform.info, config.tags); + this._config = config; + this._logger = config.logger; + const baseHeaders = defaultHeaders(_sdkKey, _platform.info, config.tags); - const clientContext = new ClientContext(sdkKey, config, platform); + const clientContext = new ClientContext(_sdkKey, config, _platform); const featureStore = config.featureStoreFactory(clientContext); const dataSourceUpdates = new DataSourceUpdates(featureStore, hasEventListeners, onUpdate); if (config.sendEvents && !config.offline && !config.diagnosticOptOut) { - this.diagnosticsManager = new internal.DiagnosticsManager( - sdkKey, - platform, - createDiagnosticsInitConfig(config, platform, featureStore), + this._diagnosticsManager = new internal.DiagnosticsManager( + _sdkKey, + _platform, + createDiagnosticsInitConfig(config, _platform, featureStore), ); } if (!config.sendEvents || config.offline) { - this.eventProcessor = new NullEventProcessor(); + this._eventProcessor = new NullEventProcessor(); } else { - this.eventProcessor = new internal.EventProcessor( + this._eventProcessor = new internal.EventProcessor( config, clientContext, baseHeaders, new ContextDeduplicator(config), - this.diagnosticsManager, + this._diagnosticsManager, ); } - this.featureStore = featureStore; + this._featureStore = featureStore; const manager = new BigSegmentsManager( config.bigSegments?.store?.(clientContext), config.bigSegments ?? {}, config.logger, - this.platform.crypto, + this._platform.crypto, ); - this.bigSegmentsManager = manager; + this._bigSegmentsManager = manager; this.bigSegmentStatusProviderInternal = manager.statusProvider as BigSegmentStoreStatusProvider; const queries: Queries = { @@ -209,10 +209,10 @@ export default class LDClientImpl implements LDClient { return manager.getUserMembership(userKey); }, }; - this.evaluator = new Evaluator(this.platform, queries); + this._evaluator = new Evaluator(this._platform, queries); - const listeners = createStreamListeners(dataSourceUpdates, this.logger, { - put: () => this.initSuccess(), + const listeners = createStreamListeners(dataSourceUpdates, this._logger, { + put: () => this._initSuccess(), }); const makeDefaultProcessor = () => config.stream @@ -222,39 +222,39 @@ export default class LDClientImpl implements LDClient { [], listeners, baseHeaders, - this.diagnosticsManager, - (e) => this.dataSourceErrorHandler(e), - this.config.streamInitialReconnectDelay, + this._diagnosticsManager, + (e) => this._dataSourceErrorHandler(e), + this._config.streamInitialReconnectDelay, ) : new PollingProcessor( config, - new Requestor(config, this.platform.requests, baseHeaders), + new Requestor(config, this._platform.requests, baseHeaders), dataSourceUpdates, - () => this.initSuccess(), - (e) => this.dataSourceErrorHandler(e), + () => this._initSuccess(), + (e) => this._dataSourceErrorHandler(e), ); if (!(config.offline || config.useLdd)) { - this.updateProcessor = + this._updateProcessor = config.updateProcessorFactory?.( clientContext, dataSourceUpdates, - () => this.initSuccess(), - (e) => this.dataSourceErrorHandler(e), + () => this._initSuccess(), + (e) => this._dataSourceErrorHandler(e), ) ?? makeDefaultProcessor(); } - if (this.updateProcessor) { - this.updateProcessor.start(); + if (this._updateProcessor) { + this._updateProcessor.start(); } else { // Deferring the start callback should allow client construction to complete before we start // emitting events. Allowing the client an opportunity to register events. - setTimeout(() => this.initSuccess(), 0); + setTimeout(() => this._initSuccess(), 0); } } initialized(): boolean { - return this.initState === InitState.Initialized; + return this._initState === InitState.Initialized; } waitForInitialization(options?: LDWaitForInitializationOptions): Promise { @@ -266,8 +266,8 @@ export default class LDClientImpl implements LDClient { // If there is no update processor, then there is functionally no initialization // so it is fine not to wait. - if (options?.timeout === undefined && this.updateProcessor !== undefined) { - this.logger?.warn( + if (options?.timeout === undefined && this._updateProcessor !== undefined) { + this._logger?.warn( 'The waitForInitialization function was called without a timeout specified.' + ' In a future version a default timeout will be applied.', ); @@ -275,9 +275,9 @@ export default class LDClientImpl implements LDClient { if ( options?.timeout !== undefined && options?.timeout > HIGH_TIMEOUT_THRESHOLD && - this.updateProcessor !== undefined + this._updateProcessor !== undefined ) { - this.logger?.warn( + this._logger?.warn( 'The waitForInitialization function was called with a timeout greater than ' + `${HIGH_TIMEOUT_THRESHOLD} seconds. We recommend a timeout of less than ` + `${HIGH_TIMEOUT_THRESHOLD} seconds.`, @@ -285,34 +285,34 @@ export default class LDClientImpl implements LDClient { } // Initialization promise was created by a previous call to waitForInitialization. - if (this.initializedPromise) { + if (this._initializedPromise) { // This promise may already be resolved/rejected, but it doesn't hurt to wrap it in a timeout. - return this.clientWithTimeout(this.initializedPromise, options?.timeout, this.logger); + return this._clientWithTimeout(this._initializedPromise, options?.timeout, this._logger); } // Initialization completed before waitForInitialization was called, so we have completed // and there was no promise. So we make a resolved promise and return it. - if (this.initState === InitState.Initialized) { - this.initializedPromise = Promise.resolve(this); + if (this._initState === InitState.Initialized) { + this._initializedPromise = Promise.resolve(this); // Already initialized, no need to timeout. - return this.initializedPromise; + return this._initializedPromise; } // Initialization failed before waitForInitialization was called, so we have completed // and there was no promise. So we make a rejected promise and return it. - if (this.initState === InitState.Failed) { + if (this._initState === InitState.Failed) { // Already failed, no need to timeout. - this.initializedPromise = Promise.reject(this.rejectionReason); - return this.initializedPromise; + this._initializedPromise = Promise.reject(this._rejectionReason); + return this._initializedPromise; } - if (!this.initializedPromise) { - this.initializedPromise = new Promise((resolve, reject) => { - this.initResolve = resolve; - this.initReject = reject; + if (!this._initializedPromise) { + this._initializedPromise = new Promise((resolve, reject) => { + this._initResolve = resolve; + this._initReject = reject; }); } - return this.clientWithTimeout(this.initializedPromise, options?.timeout, this.logger); + return this._clientWithTimeout(this._initializedPromise, options?.timeout, this._logger); } variation( @@ -321,7 +321,7 @@ export default class LDClientImpl implements LDClient { defaultValue: any, callback?: (err: any, res: any) => void, ): Promise { - return this.hookRunner + return this._hookRunner .withEvaluationSeries( key, context, @@ -329,9 +329,15 @@ export default class LDClientImpl implements LDClient { VARIATION_METHOD_NAME, () => new Promise((resolve) => { - this.evaluateIfPossible(key, context, defaultValue, this.eventFactoryDefault, (res) => { - resolve(res.detail); - }); + this._evaluateIfPossible( + key, + context, + defaultValue, + this._eventFactoryDefault, + (res) => { + resolve(res.detail); + }, + ); }), ) .then((detail) => { @@ -346,18 +352,18 @@ export default class LDClientImpl implements LDClient { defaultValue: any, callback?: (err: any, res: LDEvaluationDetail) => void, ): Promise { - return this.hookRunner.withEvaluationSeries( + return this._hookRunner.withEvaluationSeries( key, context, defaultValue, VARIATION_METHOD_DETAIL_NAME, () => new Promise((resolve) => { - this.evaluateIfPossible( + this._evaluateIfPossible( key, context, defaultValue, - this.eventFactoryWithReasons, + this._eventFactoryWithReasons, (res) => { resolve(res.detail); callback?.(null, res.detail); @@ -367,7 +373,7 @@ export default class LDClientImpl implements LDClient { ); } - private typedEval( + private _typedEval( key: string, context: LDContext, defaultValue: TResult, @@ -375,14 +381,14 @@ export default class LDClientImpl implements LDClient { methodName: string, typeChecker: (value: unknown) => [boolean, string], ): Promise { - return this.hookRunner.withEvaluationSeries( + return this._hookRunner.withEvaluationSeries( key, context, defaultValue, methodName, () => new Promise>((resolve) => { - this.evaluateIfPossible( + this._evaluateIfPossible( key, context, defaultValue, @@ -403,11 +409,11 @@ export default class LDClientImpl implements LDClient { async boolVariation(key: string, context: LDContext, defaultValue: boolean): Promise { return ( - await this.typedEval( + await this._typedEval( key, context, defaultValue, - this.eventFactoryDefault, + this._eventFactoryDefault, BOOL_VARIATION_METHOD_NAME, (value) => [TypeValidators.Boolean.is(value), TypeValidators.Boolean.getType()], ) @@ -416,11 +422,11 @@ export default class LDClientImpl implements LDClient { async numberVariation(key: string, context: LDContext, defaultValue: number): Promise { return ( - await this.typedEval( + await this._typedEval( key, context, defaultValue, - this.eventFactoryDefault, + this._eventFactoryDefault, NUMBER_VARIATION_METHOD_NAME, (value) => [TypeValidators.Number.is(value), TypeValidators.Number.getType()], ) @@ -429,11 +435,11 @@ export default class LDClientImpl implements LDClient { async stringVariation(key: string, context: LDContext, defaultValue: string): Promise { return ( - await this.typedEval( + await this._typedEval( key, context, defaultValue, - this.eventFactoryDefault, + this._eventFactoryDefault, STRING_VARIATION_METHOD_NAME, (value) => [TypeValidators.String.is(value), TypeValidators.String.getType()], ) @@ -441,7 +447,7 @@ export default class LDClientImpl implements LDClient { } jsonVariation(key: string, context: LDContext, defaultValue: unknown): Promise { - return this.hookRunner + return this._hookRunner .withEvaluationSeries( key, context, @@ -449,9 +455,15 @@ export default class LDClientImpl implements LDClient { JSON_VARIATION_METHOD_NAME, () => new Promise((resolve) => { - this.evaluateIfPossible(key, context, defaultValue, this.eventFactoryDefault, (res) => { - resolve(res.detail); - }); + this._evaluateIfPossible( + key, + context, + defaultValue, + this._eventFactoryDefault, + (res) => { + resolve(res.detail); + }, + ); }), ) .then((detail) => detail.value); @@ -462,11 +474,11 @@ export default class LDClientImpl implements LDClient { context: LDContext, defaultValue: boolean, ): Promise> { - return this.typedEval( + return this._typedEval( key, context, defaultValue, - this.eventFactoryWithReasons, + this._eventFactoryWithReasons, BOOL_VARIATION_DETAIL_METHOD_NAME, (value) => [TypeValidators.Boolean.is(value), TypeValidators.Boolean.getType()], ); @@ -477,11 +489,11 @@ export default class LDClientImpl implements LDClient { context: LDContext, defaultValue: number, ): Promise> { - return this.typedEval( + return this._typedEval( key, context, defaultValue, - this.eventFactoryWithReasons, + this._eventFactoryWithReasons, NUMBER_VARIATION_DETAIL_METHOD_NAME, (value) => [TypeValidators.Number.is(value), TypeValidators.Number.getType()], ); @@ -492,11 +504,11 @@ export default class LDClientImpl implements LDClient { context: LDContext, defaultValue: string, ): Promise> { - return this.typedEval( + return this._typedEval( key, context, defaultValue, - this.eventFactoryWithReasons, + this._eventFactoryWithReasons, STRING_VARIATION_DETAIL_METHOD_NAME, (value) => [TypeValidators.String.is(value), TypeValidators.String.getType()], ); @@ -507,18 +519,18 @@ export default class LDClientImpl implements LDClient { context: LDContext, defaultValue: unknown, ): Promise> { - return this.hookRunner.withEvaluationSeries( + return this._hookRunner.withEvaluationSeries( key, context, defaultValue, JSON_VARIATION_DETAIL_METHOD_NAME, () => new Promise((resolve) => { - this.evaluateIfPossible( + this._evaluateIfPossible( key, context, defaultValue, - this.eventFactoryWithReasons, + this._eventFactoryWithReasons, (res) => { resolve(res.detail); }, @@ -527,24 +539,24 @@ export default class LDClientImpl implements LDClient { ); } - private async migrationVariationInternal( + private async _migrationVariationInternal( key: string, context: LDContext, defaultValue: LDMigrationStage, ): Promise<{ detail: LDEvaluationDetail; migration: LDMigrationVariation }> { const convertedContext = Context.fromLDContext(context); const res = await new Promise<{ detail: LDEvaluationDetail; flag?: Flag }>((resolve) => { - this.evaluateIfPossible( + this._evaluateIfPossible( key, context, defaultValue, - this.eventFactoryWithReasons, + this._eventFactoryWithReasons, ({ detail }, flag) => { if (!IsMigrationStage(detail.value)) { const error = new Error( `Unrecognized MigrationState for "${key}"; returning default value.`, ); - this.onError(error); + this._onError(error); const reason = { kind: 'ERROR', errorKind: ErrorKinds.WrongType, @@ -583,7 +595,7 @@ export default class LDClientImpl implements LDClient { detail.variationIndex === null ? undefined : detail.variationIndex, flag?.version, samplingRatio, - this.logger, + this._logger, ), }, }; @@ -594,12 +606,12 @@ export default class LDClientImpl implements LDClient { context: LDContext, defaultValue: LDMigrationStage, ): Promise { - const res = await this.hookRunner.withEvaluationSeriesExtraDetail( + const res = await this._hookRunner.withEvaluationSeriesExtraDetail( key, context, defaultValue, MIGRATION_VARIATION_METHOD_NAME, - () => this.migrationVariationInternal(key, context, defaultValue), + () => this._migrationVariationInternal(key, context, defaultValue), ); return res.migration; @@ -610,8 +622,8 @@ export default class LDClientImpl implements LDClient { options?: LDFlagsStateOptions, callback?: (err: Error | null, res: LDFlagsState) => void, ): Promise { - if (this.config.offline) { - this.logger?.info('allFlagsState() called in offline mode. Returning empty state.'); + if (this._config.offline) { + this._logger?.info('allFlagsState() called in offline mode. Returning empty state.'); const allFlagState = new FlagsStateBuilder(false, false).build(); callback?.(null, allFlagState); return Promise.resolve(allFlagState); @@ -619,13 +631,13 @@ export default class LDClientImpl implements LDClient { const evalContext = Context.fromLDContext(context); if (!evalContext.valid) { - this.logger?.info(`${evalContext.message ?? 'Invalid context.'}. Returning empty state.`); + this._logger?.info(`${evalContext.message ?? 'Invalid context.'}. Returning empty state.`); return Promise.resolve(new FlagsStateBuilder(false, false).build()); } return new Promise((resolve) => { const doEval = (valid: boolean) => - this.featureStore.all(VersionedDataKinds.Features, (allFlags) => { + this._featureStore.all(VersionedDataKinds.Features, (allFlags) => { const builder = new FlagsStateBuilder(valid, !!options?.withReasons); const clientOnly = !!options?.clientSideOnly; const detailsOnlyIfTracked = !!options?.detailsOnlyForTrackedFlags; @@ -638,9 +650,9 @@ export default class LDClientImpl implements LDClient { iterCb(true); return; } - this.evaluator.evaluateCb(flag, evalContext, (res) => { + this._evaluator.evaluateCb(flag, evalContext, (res) => { if (res.isError) { - this.onError( + this._onError( new Error( `Error for feature flag "${flag.key}" while evaluating all flags: ${res.message}`, ), @@ -667,15 +679,15 @@ export default class LDClientImpl implements LDClient { ); }); if (!this.initialized()) { - this.featureStore.initialized((storeInitialized) => { + this._featureStore.initialized((storeInitialized) => { let valid = true; if (storeInitialized) { - this.logger?.warn( + this._logger?.warn( 'Called allFlagsState before client initialization; using last known' + ' values from data store', ); } else { - this.logger?.warn( + this._logger?.warn( 'Called allFlagsState before client initialization. Data store not available; ' + 'returning empty state', ); @@ -692,11 +704,11 @@ export default class LDClientImpl implements LDClient { secureModeHash(context: LDContext): string { const checkedContext = Context.fromLDContext(context); const key = checkedContext.valid ? checkedContext.canonicalKey : undefined; - if (!this.platform.crypto.createHmac) { + if (!this._platform.crypto.createHmac) { // This represents an error in platform implementation. throw new Error('Platform must implement createHmac'); } - const hmac = this.platform.crypto.createHmac('sha256', this.sdkKey); + const hmac = this._platform.crypto.createHmac('sha256', this._sdkKey); if (key === undefined) { throw new LDClientError('Could not generate secure mode hash for invalid context'); @@ -706,30 +718,30 @@ export default class LDClientImpl implements LDClient { } close(): void { - this.eventProcessor.close(); - this.updateProcessor?.close(); - this.featureStore.close(); - this.bigSegmentsManager.close(); + this._eventProcessor.close(); + this._updateProcessor?.close(); + this._featureStore.close(); + this._bigSegmentsManager.close(); } isOffline(): boolean { - return this.config.offline; + return this._config.offline; } track(key: string, context: LDContext, data?: any, metricValue?: number): void { const checkedContext = Context.fromLDContext(context); if (!checkedContext.valid) { - this.logger?.warn(ClientMessages.missingContextKeyNoEvent); + this._logger?.warn(ClientMessages.MissingContextKeyNoEvent); return; } // 0 is valid, so do not truthy check the metric value if (metricValue !== undefined && !TypeValidators.Number.is(metricValue)) { - this.logger?.warn(ClientMessages.invalidMetricValue(typeof metricValue)); + this._logger?.warn(ClientMessages.invalidMetricValue(typeof metricValue)); } - this.eventProcessor.sendEvent( - this.eventFactoryDefault.customEvent(key, checkedContext!, data, metricValue), + this._eventProcessor.sendEvent( + this._eventFactoryDefault.customEvent(key, checkedContext!, data, metricValue), ); } @@ -739,21 +751,21 @@ export default class LDClientImpl implements LDClient { return; } - this.eventProcessor.sendEvent(converted); + this._eventProcessor.sendEvent(converted); } identify(context: LDContext): void { const checkedContext = Context.fromLDContext(context); if (!checkedContext.valid) { - this.logger?.warn(ClientMessages.missingContextKeyNoEvent); + this._logger?.warn(ClientMessages.MissingContextKeyNoEvent); return; } - this.eventProcessor.sendEvent(this.eventFactoryDefault.identifyEvent(checkedContext!)); + this._eventProcessor.sendEvent(this._eventFactoryDefault.identifyEvent(checkedContext!)); } async flush(callback?: (err: Error | null, res: boolean) => void): Promise { try { - await this.eventProcessor.flush(); + await this._eventProcessor.flush(); } catch (err) { callback?.(err as Error, false); } @@ -761,10 +773,10 @@ export default class LDClientImpl implements LDClient { } addHook(hook: Hook): void { - this.hookRunner.addHook(hook); + this._hookRunner.addHook(hook); } - private variationInternal( + private _variationInternal( flagKey: string, context: LDContext, defaultValue: any, @@ -772,14 +784,14 @@ export default class LDClientImpl implements LDClient { cb: (res: EvalResult, flag?: Flag) => void, typeChecker?: (value: any) => [boolean, string], ): void { - if (this.config.offline) { - this.logger?.info('Variation called in offline mode. Returning default value.'); + if (this._config.offline) { + this._logger?.info('Variation called in offline mode. Returning default value.'); cb(EvalResult.forError(ErrorKinds.ClientNotReady, undefined, defaultValue)); return; } const evalContext = Context.fromLDContext(context); if (!evalContext.valid) { - this.onError( + this._onError( new LDClientError( `${evalContext.message ?? 'Context not valid;'} returning default value.`, ), @@ -788,21 +800,21 @@ export default class LDClientImpl implements LDClient { return; } - this.featureStore.get(VersionedDataKinds.Features, flagKey, (item) => { + this._featureStore.get(VersionedDataKinds.Features, flagKey, (item) => { const flag = item as Flag; if (!flag) { const error = new LDClientError( `Unknown feature flag "${flagKey}"; returning default value`, ); - this.onError(error); + this._onError(error); const result = EvalResult.forError(ErrorKinds.FlagNotFound, undefined, defaultValue); - this.eventProcessor.sendEvent( - this.eventFactoryDefault.unknownFlagEvent(flagKey, defaultValue, evalContext), + this._eventProcessor.sendEvent( + this._eventFactoryDefault.unknownFlagEvent(flagKey, defaultValue, evalContext), ); cb(result); return; } - this.evaluator.evaluateCb( + this._evaluator.evaluateCb( flag, evalContext, (evalRes) => { @@ -810,7 +822,7 @@ export default class LDClientImpl implements LDClient { evalRes.detail.variationIndex === undefined || evalRes.detail.variationIndex === null ) { - this.logger?.debug('Result value is null in variation'); + this._logger?.debug('Result value is null in variation'); evalRes.setDefault(defaultValue); } @@ -822,13 +834,13 @@ export default class LDClientImpl implements LDClient { `Did not receive expected type (${type}) evaluating feature flag "${flagKey}"`, defaultValue, ); - this.sendEvalEvent(errorRes, eventFactory, flag, evalContext, defaultValue); + this._sendEvalEvent(errorRes, eventFactory, flag, evalContext, defaultValue); cb(errorRes, flag); return; } } - this.sendEvalEvent(evalRes, eventFactory, flag, evalContext, defaultValue); + this._sendEvalEvent(evalRes, eventFactory, flag, evalContext, defaultValue); cb(evalRes, flag); }, eventFactory, @@ -836,7 +848,7 @@ export default class LDClientImpl implements LDClient { }); } - private sendEvalEvent( + private _sendEvalEvent( evalRes: EvalResult, eventFactory: EventFactory, flag: Flag, @@ -844,14 +856,14 @@ export default class LDClientImpl implements LDClient { defaultValue: any, ) { evalRes.events?.forEach((event) => { - this.eventProcessor.sendEvent({ ...event }); + this._eventProcessor.sendEvent({ ...event }); }); - this.eventProcessor.sendEvent( + this._eventProcessor.sendEvent( eventFactory.evalEventServer(flag, evalContext, evalRes.detail, defaultValue, undefined), ); } - private evaluateIfPossible( + private _evaluateIfPossible( flagKey: string, context: LDContext, defaultValue: any, @@ -860,16 +872,16 @@ export default class LDClientImpl implements LDClient { typeChecker?: (value: any) => [boolean, string], ): void { if (!this.initialized()) { - this.featureStore.initialized((storeInitialized) => { + this._featureStore.initialized((storeInitialized) => { if (storeInitialized) { - this.logger?.warn( + this._logger?.warn( 'Variation called before LaunchDarkly client initialization completed' + " (did you wait for the 'ready' event?) - using last known values from feature store", ); - this.variationInternal(flagKey, context, defaultValue, eventFactory, cb, typeChecker); + this._variationInternal(flagKey, context, defaultValue, eventFactory, cb, typeChecker); return; } - this.logger?.warn( + this._logger?.warn( 'Variation called before LaunchDarkly client initialization completed (did you wait for the' + "'ready' event?) - using default value", ); @@ -877,28 +889,28 @@ export default class LDClientImpl implements LDClient { }); return; } - this.variationInternal(flagKey, context, defaultValue, eventFactory, cb, typeChecker); + this._variationInternal(flagKey, context, defaultValue, eventFactory, cb, typeChecker); } - private dataSourceErrorHandler(e: any) { + private _dataSourceErrorHandler(e: any) { const error = e.code === 401 ? new Error('Authentication failed. Double check your SDK key.') : e; - this.onError(error); - this.onFailed(error); + this._onError(error); + this._onFailed(error); if (!this.initialized()) { - this.initState = InitState.Failed; - this.rejectionReason = error; - this.initReject?.(error); + this._initState = InitState.Failed; + this._rejectionReason = error; + this._initReject?.(error); } } - private initSuccess() { + private _initSuccess() { if (!this.initialized()) { - this.initState = InitState.Initialized; - this.initResolve?.(this); - this.onReady(); + this._initState = InitState.Initialized; + this._initResolve?.(this); + this._onReady(); } } @@ -914,7 +926,7 @@ export default class LDClientImpl implements LDClient { * @param logger A logger to log when the timeout expires. * @returns */ - private clientWithTimeout( + private _clientWithTimeout( basePromise: Promise, timeout?: number, logger?: LDLogger, diff --git a/packages/shared/sdk-server/src/Migration.ts b/packages/shared/sdk-server/src/Migration.ts index 475eef32e..2a5b2a037 100644 --- a/packages/shared/sdk-server/src/Migration.ts +++ b/packages/shared/sdk-server/src/Migration.ts @@ -99,58 +99,70 @@ class Migration< > implements LDMigration { - private readonly execution: LDSerialExecution | LDConcurrentExecution; + private readonly _execution: LDSerialExecution | LDConcurrentExecution; - private readonly errorTracking: boolean; + private readonly _errorTracking: boolean; - private readonly latencyTracking: boolean; + private readonly _latencyTracking: boolean; - private readonly readTable: { + private readonly _readTable: { [index: string]: ( context: MigrationContext, ) => Promise>; } = { [LDMigrationStage.Off]: async (context) => - this.doSingleOp(context, 'old', this.config.readOld.bind(this.config)), + this._doSingleOp(context, 'old', this._config.readOld.bind(this._config)), [LDMigrationStage.DualWrite]: async (context) => - this.doSingleOp(context, 'old', this.config.readOld.bind(this.config)), + this._doSingleOp(context, 'old', this._config.readOld.bind(this._config)), [LDMigrationStage.Shadow]: async (context) => { - const { fromOld, fromNew } = await this.doRead(context); + const { fromOld, fromNew } = await this._doRead(context); - this.trackConsistency(context, fromOld, fromNew); + this._trackConsistency(context, fromOld, fromNew); return fromOld; }, [LDMigrationStage.Live]: async (context) => { - const { fromNew, fromOld } = await this.doRead(context); + const { fromNew, fromOld } = await this._doRead(context); - this.trackConsistency(context, fromOld, fromNew); + this._trackConsistency(context, fromOld, fromNew); return fromNew; }, [LDMigrationStage.RampDown]: async (context) => - this.doSingleOp(context, 'new', this.config.readNew.bind(this.config)), + this._doSingleOp(context, 'new', this._config.readNew.bind(this._config)), [LDMigrationStage.Complete]: async (context) => - this.doSingleOp(context, 'new', this.config.readNew.bind(this.config)), + this._doSingleOp(context, 'new', this._config.readNew.bind(this._config)), }; - private readonly writeTable: { + private readonly _writeTable: { [index: string]: ( context: MigrationContext, ) => Promise>; } = { [LDMigrationStage.Off]: async (context) => ({ - authoritative: await this.doSingleOp(context, 'old', this.config.writeOld.bind(this.config)), + authoritative: await this._doSingleOp( + context, + 'old', + this._config.writeOld.bind(this._config), + ), }), [LDMigrationStage.DualWrite]: async (context) => { - const fromOld = await this.doSingleOp(context, 'old', this.config.writeOld.bind(this.config)); + const fromOld = await this._doSingleOp( + context, + 'old', + this._config.writeOld.bind(this._config), + ); if (!fromOld.success) { return { authoritative: fromOld, }; } - const fromNew = await this.doSingleOp(context, 'new', this.config.writeNew.bind(this.config)); + const fromNew = await this._doSingleOp( + context, + 'new', + this._config.writeNew.bind(this._config), + ); return { authoritative: fromOld, @@ -158,14 +170,22 @@ class Migration< }; }, [LDMigrationStage.Shadow]: async (context) => { - const fromOld = await this.doSingleOp(context, 'old', this.config.writeOld.bind(this.config)); + const fromOld = await this._doSingleOp( + context, + 'old', + this._config.writeOld.bind(this._config), + ); if (!fromOld.success) { return { authoritative: fromOld, }; } - const fromNew = await this.doSingleOp(context, 'new', this.config.writeNew.bind(this.config)); + const fromNew = await this._doSingleOp( + context, + 'new', + this._config.writeNew.bind(this._config), + ); return { authoritative: fromOld, @@ -173,14 +193,22 @@ class Migration< }; }, [LDMigrationStage.Live]: async (context) => { - const fromNew = await this.doSingleOp(context, 'new', this.config.writeNew.bind(this.config)); + const fromNew = await this._doSingleOp( + context, + 'new', + this._config.writeNew.bind(this._config), + ); if (!fromNew.success) { return { authoritative: fromNew, }; } - const fromOld = await this.doSingleOp(context, 'old', this.config.writeOld.bind(this.config)); + const fromOld = await this._doSingleOp( + context, + 'old', + this._config.writeOld.bind(this._config), + ); return { authoritative: fromNew, @@ -188,14 +216,22 @@ class Migration< }; }, [LDMigrationStage.RampDown]: async (context) => { - const fromNew = await this.doSingleOp(context, 'new', this.config.writeNew.bind(this.config)); + const fromNew = await this._doSingleOp( + context, + 'new', + this._config.writeNew.bind(this._config), + ); if (!fromNew.success) { return { authoritative: fromNew, }; } - const fromOld = await this.doSingleOp(context, 'old', this.config.writeOld.bind(this.config)); + const fromOld = await this._doSingleOp( + context, + 'old', + this._config.writeOld.bind(this._config), + ); return { authoritative: fromNew, @@ -203,27 +239,31 @@ class Migration< }; }, [LDMigrationStage.Complete]: async (context) => ({ - authoritative: await this.doSingleOp(context, 'new', this.config.writeNew.bind(this.config)), + authoritative: await this._doSingleOp( + context, + 'new', + this._config.writeNew.bind(this._config), + ), }), }; constructor( - private readonly client: LDClient, - private readonly config: LDMigrationOptions< + private readonly _client: LDClient, + private readonly _config: LDMigrationOptions< TMigrationRead, TMigrationWrite, TMigrationReadInput, TMigrationWriteInput >, ) { - if (this.config.execution) { - this.execution = this.config.execution; + if (this._config.execution) { + this._execution = this._config.execution; } else { - this.execution = new LDConcurrentExecution(); + this._execution = new LDConcurrentExecution(); } - this.latencyTracking = this.config.latencyTracking ?? true; - this.errorTracking = this.config.errorTracking ?? true; + this._latencyTracking = this._config.latencyTracking ?? true; + this._errorTracking = this._config.errorTracking ?? true; } async read( @@ -232,13 +272,13 @@ class Migration< defaultStage: LDMigrationStage, payload?: TMigrationReadInput, ): Promise> { - const stage = await this.client.migrationVariation(key, context, defaultStage); - const res = await this.readTable[stage.value]({ + const stage = await this._client.migrationVariation(key, context, defaultStage); + const res = await this._readTable[stage.value]({ payload, tracker: stage.tracker, }); stage.tracker.op('read'); - this.sendEvent(stage.tracker); + this._sendEvent(stage.tracker); return res; } @@ -248,57 +288,65 @@ class Migration< defaultStage: LDMigrationStage, payload?: TMigrationWriteInput, ): Promise> { - const stage = await this.client.migrationVariation(key, context, defaultStage); - const res = await this.writeTable[stage.value]({ + const stage = await this._client.migrationVariation(key, context, defaultStage); + const res = await this._writeTable[stage.value]({ payload, tracker: stage.tracker, }); stage.tracker.op('write'); - this.sendEvent(stage.tracker); + this._sendEvent(stage.tracker); return res; } - private sendEvent(tracker: LDMigrationTracker) { + private _sendEvent(tracker: LDMigrationTracker) { const event = tracker.createEvent(); if (event) { - this.client.trackMigration(event); + this._client.trackMigration(event); } } - private trackConsistency( + private _trackConsistency( context: MigrationContext, oldValue: LDMethodResult, newValue: LDMethodResult, ) { - if (!this.config.check) { + if (!this._config.check) { return; } if (oldValue.success && newValue.success) { // Check is validated before this point, so it is force unwrapped. - context.tracker.consistency(() => this.config.check!(oldValue.result, newValue.result)); + context.tracker.consistency(() => this._config.check!(oldValue.result, newValue.result)); } } - private async readSequentialFixed( + private async _readSequentialFixed( context: MigrationContext, ): Promise> { - const fromOld = await this.doSingleOp(context, 'old', this.config.readOld.bind(this.config)); - const fromNew = await this.doSingleOp(context, 'new', this.config.readNew.bind(this.config)); + const fromOld = await this._doSingleOp(context, 'old', this._config.readOld.bind(this._config)); + const fromNew = await this._doSingleOp(context, 'new', this._config.readNew.bind(this._config)); return { fromOld, fromNew }; } - private async readConcurrent( + private async _readConcurrent( context: MigrationContext, ): Promise> { - const fromOldPromise = this.doSingleOp(context, 'old', this.config.readOld.bind(this.config)); - const fromNewPromise = this.doSingleOp(context, 'new', this.config.readNew.bind(this.config)); + const fromOldPromise = this._doSingleOp( + context, + 'old', + this._config.readOld.bind(this._config), + ); + const fromNewPromise = this._doSingleOp( + context, + 'new', + this._config.readNew.bind(this._config), + ); const [fromOld, fromNew] = await Promise.all([fromOldPromise, fromNewPromise]); return { fromOld, fromNew }; } - private async readSequentialRandom( + private async _readSequentialRandom( context: MigrationContext, ): Promise> { // This number is not used for a purpose requiring cryptographic security. @@ -306,49 +354,57 @@ class Migration< // Effectively flip a coin and do it on one order or the other. if (randomIndex === 0) { - const fromOld = await this.doSingleOp(context, 'old', this.config.readOld.bind(this.config)); - const fromNew = await this.doSingleOp(context, 'new', this.config.readNew.bind(this.config)); + const fromOld = await this._doSingleOp( + context, + 'old', + this._config.readOld.bind(this._config), + ); + const fromNew = await this._doSingleOp( + context, + 'new', + this._config.readNew.bind(this._config), + ); return { fromOld, fromNew }; } - const fromNew = await this.doSingleOp(context, 'new', this.config.readNew.bind(this.config)); - const fromOld = await this.doSingleOp(context, 'old', this.config.readOld.bind(this.config)); + const fromNew = await this._doSingleOp(context, 'new', this._config.readNew.bind(this._config)); + const fromOld = await this._doSingleOp(context, 'old', this._config.readOld.bind(this._config)); return { fromOld, fromNew }; } - private async doRead( + private async _doRead( context: MigrationContext, ): Promise> { - if (this.execution?.type === LDExecution.Serial) { - const serial = this.execution as LDSerialExecution; + if (this._execution?.type === LDExecution.Serial) { + const serial = this._execution as LDSerialExecution; if (serial.ordering === LDExecutionOrdering.Fixed) { - return this.readSequentialFixed(context); + return this._readSequentialFixed(context); } - return this.readSequentialRandom(context); + return this._readSequentialRandom(context); } - return this.readConcurrent(context); + return this._readConcurrent(context); } - private async doSingleOp( + private async _doSingleOp( context: MigrationContext, origin: LDMigrationOrigin, method: (payload?: TInput) => Promise>, ): Promise> { context.tracker.invoked(origin); - const res = await this.trackLatency(context.tracker, origin, () => + const res = await this._trackLatency(context.tracker, origin, () => safeCall(() => method(context.payload)), ); - if (!res.success && this.errorTracking) { + if (!res.success && this._errorTracking) { context.tracker.error(origin); } return { origin, ...res }; } - private async trackLatency( + private async _trackLatency( tracker: LDMigrationTracker, origin: LDMigrationOrigin, method: () => Promise, ): Promise { - if (!this.latencyTracking) { + if (!this._latencyTracking) { return method(); } let start; diff --git a/packages/shared/sdk-server/src/MigrationOpTracker.ts b/packages/shared/sdk-server/src/MigrationOpTracker.ts index fae4884bf..a1fa23c84 100644 --- a/packages/shared/sdk-server/src/MigrationOpTracker.ts +++ b/packages/shared/sdk-server/src/MigrationOpTracker.ts @@ -21,57 +21,57 @@ function isPopulated(data: number): boolean { } export default class MigrationOpTracker implements LDMigrationTracker { - private errors = { + private _errors = { old: false, new: false, }; - private wasInvoked = { + private _wasInvoked = { old: false, new: false, }; - private consistencyCheck: LDConsistencyCheck = LDConsistencyCheck.NotChecked; + private _consistencyCheck: LDConsistencyCheck = LDConsistencyCheck.NotChecked; - private latencyMeasurement = { + private _latencyMeasurement = { old: NaN, new: NaN, }; - private operation?: LDMigrationOp; + private _operation?: LDMigrationOp; constructor( - private readonly flagKey: string, - private readonly contextKeys: Record, - private readonly defaultStage: LDMigrationStage, - private readonly stage: LDMigrationStage, - private readonly reason: LDEvaluationReason, - private readonly checkRatio?: number, - private readonly variation?: number, - private readonly version?: number, - private readonly samplingRatio?: number, - private readonly logger?: LDLogger, + private readonly _flagKey: string, + private readonly _contextKeys: Record, + private readonly _defaultStage: LDMigrationStage, + private readonly _stage: LDMigrationStage, + private readonly _reason: LDEvaluationReason, + private readonly _checkRatio?: number, + private readonly _variation?: number, + private readonly _version?: number, + private readonly _samplingRatio?: number, + private readonly _logger?: LDLogger, ) {} op(op: LDMigrationOp) { - this.operation = op; + this._operation = op; } error(origin: LDMigrationOrigin) { - this.errors[origin] = true; + this._errors[origin] = true; } consistency(check: () => boolean) { - if (internal.shouldSample(this.checkRatio ?? 1)) { + if (internal.shouldSample(this._checkRatio ?? 1)) { try { const res = check(); - this.consistencyCheck = res + this._consistencyCheck = res ? LDConsistencyCheck.Consistent : LDConsistencyCheck.Inconsistent; } catch (exception) { - this.logger?.error( + this._logger?.error( 'Exception when executing consistency check function for migration' + - ` '${this.flagKey}' the consistency check will not be included in the generated migration` + + ` '${this._flagKey}' the consistency check will not be included in the generated migration` + ` op event. Exception: ${exception}`, ); } @@ -79,106 +79,106 @@ export default class MigrationOpTracker implements LDMigrationTracker { } latency(origin: LDMigrationOrigin, value: number) { - this.latencyMeasurement[origin] = value; + this._latencyMeasurement[origin] = value; } invoked(origin: LDMigrationOrigin) { - this.wasInvoked[origin] = true; + this._wasInvoked[origin] = true; } createEvent(): LDMigrationOpEvent | undefined { - if (!TypeValidators.String.is(this.flagKey) || this.flagKey === '') { - this.logger?.error('The flag key for a migration operation must be a non-empty string.'); + if (!TypeValidators.String.is(this._flagKey) || this._flagKey === '') { + this._logger?.error('The flag key for a migration operation must be a non-empty string.'); return undefined; } - if (!this.operation) { - this.logger?.error('The operation must be set using "op" before an event can be created.'); + if (!this._operation) { + this._logger?.error('The operation must be set using "op" before an event can be created.'); return undefined; } - if (Object.keys(this.contextKeys).length === 0) { - this.logger?.error( + if (Object.keys(this._contextKeys).length === 0) { + this._logger?.error( 'The migration was not done against a valid context and cannot generate an event.', ); return undefined; } - if (!this.wasInvoked.old && !this.wasInvoked.new) { - this.logger?.error( + if (!this._wasInvoked.old && !this._wasInvoked.new) { + this._logger?.error( 'The migration invoked neither the "old" or "new" implementation and' + 'an event cannot be generated', ); return undefined; } - if (!this.measurementConsistencyCheck()) { + if (!this._measurementConsistencyCheck()) { return undefined; } const measurements: LDMigrationMeasurement[] = []; - this.populateInvoked(measurements); - this.populateConsistency(measurements); - this.populateLatency(measurements); - this.populateErrors(measurements); + this._populateInvoked(measurements); + this._populateConsistency(measurements); + this._populateLatency(measurements); + this._populateErrors(measurements); return { kind: 'migration_op', - operation: this.operation, + operation: this._operation, creationDate: Date.now(), - contextKeys: this.contextKeys, + contextKeys: this._contextKeys, evaluation: { - key: this.flagKey, - value: this.stage, - default: this.defaultStage, - reason: this.reason, - variation: this.variation, - version: this.version, + key: this._flagKey, + value: this._stage, + default: this._defaultStage, + reason: this._reason, + variation: this._variation, + version: this._version, }, measurements, - samplingRatio: this.samplingRatio ?? 1, + samplingRatio: this._samplingRatio ?? 1, }; } - private logTag() { - return `For migration ${this.operation}-${this.flagKey}:`; + private _logTag() { + return `For migration ${this._operation}-${this._flagKey}:`; } - private latencyConsistencyMessage(origin: LDMigrationOrigin) { + private _latencyConsistencyMessage(origin: LDMigrationOrigin) { return `Latency measurement for "${origin}", but "${origin}" was not invoked.`; } - private errorConsistencyMessage(origin: LDMigrationOrigin) { + private _errorConsistencyMessage(origin: LDMigrationOrigin) { return `Error occurred for "${origin}", but "${origin}" was not invoked.`; } - private consistencyCheckConsistencyMessage(origin: LDMigrationOrigin) { + private _consistencyCheckConsistencyMessage(origin: LDMigrationOrigin) { return ( `Consistency check was done, but "${origin}" was not invoked.` + 'Both "old" and "new" must be invoked to do a consistency check.' ); } - private checkOriginEventConsistency(origin: LDMigrationOrigin): boolean { - if (this.wasInvoked[origin]) { + private _checkOriginEventConsistency(origin: LDMigrationOrigin): boolean { + if (this._wasInvoked[origin]) { return true; } // If the specific origin was not invoked, but it contains measurements, then // that is a problem. Check each measurement and log a message if it is present. - if (!Number.isNaN(this.latencyMeasurement[origin])) { - this.logger?.error(`${this.logTag()} ${this.latencyConsistencyMessage(origin)}`); + if (!Number.isNaN(this._latencyMeasurement[origin])) { + this._logger?.error(`${this._logTag()} ${this._latencyConsistencyMessage(origin)}`); return false; } - if (this.errors[origin]) { - this.logger?.error(`${this.logTag()} ${this.errorConsistencyMessage(origin)}`); + if (this._errors[origin]) { + this._logger?.error(`${this._logTag()} ${this._errorConsistencyMessage(origin)}`); return false; } - if (this.consistencyCheck !== LDConsistencyCheck.NotChecked) { - this.logger?.error(`${this.logTag()} ${this.consistencyCheckConsistencyMessage(origin)}`); + if (this._consistencyCheck !== LDConsistencyCheck.NotChecked) { + this._logger?.error(`${this._logTag()} ${this._consistencyCheckConsistencyMessage(origin)}`); return false; } return true; @@ -187,66 +187,66 @@ export default class MigrationOpTracker implements LDMigrationTracker { /** * Check that the latency, error, consistency and invoked measurements are self-consistent. */ - private measurementConsistencyCheck(): boolean { - return this.checkOriginEventConsistency('old') && this.checkOriginEventConsistency('new'); + private _measurementConsistencyCheck(): boolean { + return this._checkOriginEventConsistency('old') && this._checkOriginEventConsistency('new'); } - private populateInvoked(measurements: LDMigrationMeasurement[]) { + private _populateInvoked(measurements: LDMigrationMeasurement[]) { const measurement: LDMigrationInvokedMeasurement = { key: 'invoked', values: {}, }; - if (!this.wasInvoked.old && !this.wasInvoked.new) { - this.logger?.error('Migration op completed without executing any origins (old/new).'); + if (!this._wasInvoked.old && !this._wasInvoked.new) { + this._logger?.error('Migration op completed without executing any origins (old/new).'); } - if (this.wasInvoked.old) { + if (this._wasInvoked.old) { measurement.values.old = true; } - if (this.wasInvoked.new) { + if (this._wasInvoked.new) { measurement.values.new = true; } measurements.push(measurement); } - private populateConsistency(measurements: LDMigrationMeasurement[]) { + private _populateConsistency(measurements: LDMigrationMeasurement[]) { if ( - this.consistencyCheck !== undefined && - this.consistencyCheck !== LDConsistencyCheck.NotChecked + this._consistencyCheck !== undefined && + this._consistencyCheck !== LDConsistencyCheck.NotChecked ) { measurements.push({ key: 'consistent', - value: this.consistencyCheck === LDConsistencyCheck.Consistent, - samplingRatio: this.checkRatio ?? 1, + value: this._consistencyCheck === LDConsistencyCheck.Consistent, + samplingRatio: this._checkRatio ?? 1, }); } } - private populateErrors(measurements: LDMigrationMeasurement[]) { - if (this.errors.new || this.errors.old) { + private _populateErrors(measurements: LDMigrationMeasurement[]) { + if (this._errors.new || this._errors.old) { const measurement: LDMigrationErrorMeasurement = { key: 'error', values: {}, }; - if (this.errors.new) { + if (this._errors.new) { measurement.values.new = true; } - if (this.errors.old) { + if (this._errors.old) { measurement.values.old = true; } measurements.push(measurement); } } - private populateLatency(measurements: LDMigrationMeasurement[]) { - const newIsPopulated = isPopulated(this.latencyMeasurement.new); - const oldIsPopulated = isPopulated(this.latencyMeasurement.old); + private _populateLatency(measurements: LDMigrationMeasurement[]) { + const newIsPopulated = isPopulated(this._latencyMeasurement.new); + const oldIsPopulated = isPopulated(this._latencyMeasurement.old); if (newIsPopulated || oldIsPopulated) { const values: { old?: number; new?: number } = {}; if (newIsPopulated) { - values.new = this.latencyMeasurement.new; + values.new = this._latencyMeasurement.new; } if (oldIsPopulated) { - values.old = this.latencyMeasurement.old; + values.old = this._latencyMeasurement.old; } measurements.push({ key: 'latency_ms', diff --git a/packages/shared/sdk-server/src/cache/LruCache.ts b/packages/shared/sdk-server/src/cache/LruCache.ts index 7180efccc..c904b4bf8 100644 --- a/packages/shared/sdk-server/src/cache/LruCache.ts +++ b/packages/shared/sdk-server/src/cache/LruCache.ts @@ -16,143 +16,143 @@ export interface LruCacheOptions { * @internal */ export default class LruCache { - private values: any[]; + private _values: any[]; - private keys: Array; + private _keys: Array; - private lastUpdated: number[]; + private _lastUpdated: number[]; - private next: Uint32Array; + private _next: Uint32Array; - private prev: Uint32Array; + private _prev: Uint32Array; - private keyMap: Map = new Map(); + private _keyMap: Map = new Map(); - private head: number = 0; + private _head: number = 0; - private tail: number = 0; + private _tail: number = 0; - private max: number; + private _max: number; - private size: number = 0; + private _size: number = 0; - private maxAge: number; + private _maxAge: number; constructor(options: LruCacheOptions) { const { max } = options; - this.max = max; + this._max = max; // This is effectively a struct-of-arrays implementation // of a linked list. All the nodes exist statically and then // the links between them are changed by updating the previous/next // arrays. - this.values = new Array(max); - this.keys = new Array(max); - this.next = new Uint32Array(max); - this.prev = new Uint32Array(max); + this._values = new Array(max); + this._keys = new Array(max); + this._next = new Uint32Array(max); + this._prev = new Uint32Array(max); if (options.maxAge) { - this.lastUpdated = new Array(max).fill(0); - this.maxAge = options.maxAge; + this._lastUpdated = new Array(max).fill(0); + this._maxAge = options.maxAge; } else { // To please linting. - this.lastUpdated = []; - this.maxAge = 0; + this._lastUpdated = []; + this._maxAge = 0; } } set(key: string, val: any) { - let index = this.keyMap.get(key); + let index = this._keyMap.get(key); if (index === undefined) { - index = this.index(); - this.keys[index] = key; - this.keyMap.set(key, index); - this.next[this.tail] = index; - this.prev[index] = this.tail; - this.tail = index; - this.size += 1; + index = this._index(); + this._keys[index] = key; + this._keyMap.set(key, index); + this._next[this._tail] = index; + this._prev[index] = this._tail; + this._tail = index; + this._size += 1; } else { - this.setTail(index); + this._setTail(index); } - this.values[index] = val; - if (this.maxAge) { - this.lastUpdated[index] = Date.now(); + this._values[index] = val; + if (this._maxAge) { + this._lastUpdated[index] = Date.now(); } } get(key: string): any { - const index = this.keyMap.get(key); + const index = this._keyMap.get(key); if (index !== undefined) { - if (this.maxAge) { - const lastUpdated = this.lastUpdated[index]; - if (Date.now() - lastUpdated > this.maxAge) { + if (this._maxAge) { + const lastUpdated = this._lastUpdated[index]; + if (Date.now() - lastUpdated > this._maxAge) { // The oldest items are always the head, so they get incrementally // replaced. This would not be the case if we supported per item TTL. return undefined; } } - this.setTail(index); - if (this.maxAge) { - this.lastUpdated[index] = Date.now(); + this._setTail(index); + if (this._maxAge) { + this._lastUpdated[index] = Date.now(); } - return this.values[index]; + return this._values[index]; } return undefined; } clear() { - this.head = 0; - this.tail = 0; - this.size = 0; - this.values.fill(undefined); - this.keys.fill(undefined); - this.next.fill(0); - this.prev.fill(0); - this.keyMap.clear(); + this._head = 0; + this._tail = 0; + this._size = 0; + this._values.fill(undefined); + this._keys.fill(undefined); + this._next.fill(0); + this._prev.fill(0); + this._keyMap.clear(); } - private index() { - if (this.size === 0) { - return this.tail; + private _index() { + if (this._size === 0) { + return this._tail; } - if (this.size === this.max) { - return this.evict(); + if (this._size === this._max) { + return this._evict(); } // The initial list is being populated, so we can just continue increasing size. - return this.size; + return this._size; } - private evict(): number { - const { head } = this; - const k = this.keys[head]; - this.head = this.next[head]; - this.keyMap.delete(k!); - this.size -= 1; + private _evict(): number { + const { _head: head } = this; + const k = this._keys[head]; + this._head = this._next[head]; + this._keyMap.delete(k!); + this._size -= 1; return head; } - private link(p: number, n: number) { - this.prev[n] = p; - this.next[p] = n; + private _link(p: number, n: number) { + this._prev[n] = p; + this._next[p] = n; } - private setTail(index: number) { + private _setTail(index: number) { // If it is already the tail, then there is nothing to do. - if (index !== this.tail) { + if (index !== this._tail) { // If this is the head, then we change the next item // to the head. - if (index === this.head) { - this.head = this.next[index]; + if (index === this._head) { + this._head = this._next[index]; } else { // Link the previous item to the next item, effectively removing // the current node. - this.link(this.prev[index], this.next[index]); + this._link(this._prev[index], this._next[index]); } // Connect the current tail to this node. - this.link(this.tail, index); - this.tail = index; + this._link(this._tail, index); + this._tail = index; } } } diff --git a/packages/shared/sdk-server/src/cache/TtlCache.ts b/packages/shared/sdk-server/src/cache/TtlCache.ts index 11e5fcb9e..8ec208efa 100644 --- a/packages/shared/sdk-server/src/cache/TtlCache.ts +++ b/packages/shared/sdk-server/src/cache/TtlCache.ts @@ -30,14 +30,14 @@ interface CacheRecord { * @internal */ export default class TtlCache { - private storage: Map = new Map(); + private _storage: Map = new Map(); - private checkIntervalHandle: any; + private _checkIntervalHandle: any; - constructor(private readonly options: TtlCacheOptions) { - this.checkIntervalHandle = setInterval(() => { - this.purgeStale(); - }, options.checkInterval * 1000); + constructor(private readonly _options: TtlCacheOptions) { + this._checkIntervalHandle = setInterval(() => { + this._purgeStale(); + }, _options.checkInterval * 1000); } /** @@ -47,9 +47,9 @@ export default class TtlCache { * if the value has expired. */ public get(key: string): any { - const record = this.storage.get(key); + const record = this._storage.get(key); if (record && isStale(record)) { - this.storage.delete(key); + this._storage.delete(key); return undefined; } return record?.value; @@ -62,9 +62,9 @@ export default class TtlCache { * @param value The value to set. */ public set(key: string, value: any) { - this.storage.set(key, { + this._storage.set(key, { value, - expiration: Date.now() + this.options.ttl * 1000, + expiration: Date.now() + this._options.ttl * 1000, }); } @@ -74,14 +74,14 @@ export default class TtlCache { * @param key The key of the value to delete. */ public delete(key: string) { - this.storage.delete(key); + this._storage.delete(key); } /** * Clear the items that are in the cache. */ public clear() { - this.storage.clear(); + this._storage.clear(); } /** @@ -90,16 +90,16 @@ export default class TtlCache { */ public close() { this.clear(); - if (this.checkIntervalHandle) { - clearInterval(this.checkIntervalHandle); - this.checkIntervalHandle = null; + if (this._checkIntervalHandle) { + clearInterval(this._checkIntervalHandle); + this._checkIntervalHandle = null; } } - private purgeStale() { - this.storage.forEach((record, key) => { + private _purgeStale() { + this._storage.forEach((record, key) => { if (isStale(record)) { - this.storage.delete(key); + this._storage.delete(key); } }); } @@ -109,6 +109,6 @@ export default class TtlCache { * @internal */ public get size() { - return this.storage.size; + return this._storage.size; } } diff --git a/packages/shared/sdk-server/src/data_sources/DataSourceUpdates.ts b/packages/shared/sdk-server/src/data_sources/DataSourceUpdates.ts index 1187ff268..ac6e3820d 100644 --- a/packages/shared/sdk-server/src/data_sources/DataSourceUpdates.ts +++ b/packages/shared/sdk-server/src/data_sources/DataSourceUpdates.ts @@ -58,26 +58,26 @@ function computeDependencies(namespace: string, item: LDFeatureStoreItem) { * @internal */ export default class DataSourceUpdates implements LDDataSourceUpdates { - private readonly dependencyTracker = new DependencyTracker(); + private readonly _dependencyTracker = new DependencyTracker(); constructor( - private readonly featureStore: LDFeatureStore, - private readonly hasEventListeners: () => boolean, - private readonly onChange: (key: string) => void, + private readonly _featureStore: LDFeatureStore, + private readonly _hasEventListeners: () => boolean, + private readonly _onChange: (key: string) => void, ) {} init(allData: LDFeatureStoreDataStorage, callback: () => void): void { - const checkForChanges = this.hasEventListeners(); + const checkForChanges = this._hasEventListeners(); const doInit = (oldData?: LDFeatureStoreDataStorage) => { - this.featureStore.init(allData, () => { + this._featureStore.init(allData, () => { // Defer change events so they execute after the callback. Promise.resolve().then(() => { - this.dependencyTracker.reset(); + this._dependencyTracker.reset(); Object.entries(allData).forEach(([namespace, items]) => { Object.keys(items || {}).forEach((key) => { const item = items[key]; - this.dependencyTracker.updateDependenciesFrom( + this._dependencyTracker.updateDependenciesFrom( namespace, key, computeDependencies(namespace, item), @@ -109,8 +109,8 @@ export default class DataSourceUpdates implements LDDataSourceUpdates { }; if (checkForChanges) { - this.featureStore.all(VersionedDataKinds.Features, (oldFlags) => { - this.featureStore.all(VersionedDataKinds.Segments, (oldSegments) => { + this._featureStore.all(VersionedDataKinds.Features, (oldFlags) => { + this._featureStore.all(VersionedDataKinds.Segments, (oldSegments) => { const oldData = { [VersionedDataKinds.Features.namespace]: oldFlags, [VersionedDataKinds.Segments.namespace]: oldSegments, @@ -125,12 +125,12 @@ export default class DataSourceUpdates implements LDDataSourceUpdates { upsert(kind: DataKind, data: LDKeyedFeatureStoreItem, callback: () => void): void { const { key } = data; - const checkForChanges = this.hasEventListeners(); + const checkForChanges = this._hasEventListeners(); const doUpsert = (oldItem?: LDFeatureStoreItem | null) => { - this.featureStore.upsert(kind, data, () => { + this._featureStore.upsert(kind, data, () => { // Defer change events so they execute after the callback. Promise.resolve().then(() => { - this.dependencyTracker.updateDependenciesFrom( + this._dependencyTracker.updateDependenciesFrom( kind.namespace, key, computeDependencies(kind.namespace, data), @@ -146,7 +146,7 @@ export default class DataSourceUpdates implements LDDataSourceUpdates { }); }; if (checkForChanges) { - this.featureStore.get(kind, key, doUpsert); + this._featureStore.get(kind, key, doUpsert); } else { doUpsert(); } @@ -162,13 +162,13 @@ export default class DataSourceUpdates implements LDDataSourceUpdates { if (newValue && oldValue && newValue.version <= oldValue.version) { return; } - this.dependencyTracker.updateModifiedItems(toDataSet, namespace, key); + this._dependencyTracker.updateModifiedItems(toDataSet, namespace, key); } sendChangeEvents(dataSet: NamespacedDataSet) { dataSet.enumerate((namespace, key) => { if (namespace === VersionedDataKinds.Features.namespace) { - this.onChange(key); + this._onChange(key); } }); } diff --git a/packages/shared/sdk-server/src/data_sources/DependencyTracker.ts b/packages/shared/sdk-server/src/data_sources/DependencyTracker.ts index 0904150dc..050b4ef2a 100644 --- a/packages/shared/sdk-server/src/data_sources/DependencyTracker.ts +++ b/packages/shared/sdk-server/src/data_sources/DependencyTracker.ts @@ -4,27 +4,27 @@ import NamespacedDataSet from './NamespacedDataSet'; * @internal */ export default class DependencyTracker { - private readonly dependenciesFrom = new NamespacedDataSet>(); + private readonly _dependenciesFrom = new NamespacedDataSet>(); - private readonly dependenciesTo = new NamespacedDataSet>(); + private readonly _dependenciesTo = new NamespacedDataSet>(); updateDependenciesFrom( namespace: string, key: string, newDependencySet: NamespacedDataSet, ) { - const oldDependencySet = this.dependenciesFrom.get(namespace, key); + const oldDependencySet = this._dependenciesFrom.get(namespace, key); oldDependencySet?.enumerate((depNs, depKey) => { - const depsToThisDep = this.dependenciesTo.get(depNs, depKey); + const depsToThisDep = this._dependenciesTo.get(depNs, depKey); depsToThisDep?.remove(namespace, key); }); - this.dependenciesFrom.set(namespace, key, newDependencySet); + this._dependenciesFrom.set(namespace, key, newDependencySet); newDependencySet?.enumerate((depNs, depKey) => { - let depsToThisDep = this.dependenciesTo.get(depNs, depKey); + let depsToThisDep = this._dependenciesTo.get(depNs, depKey); if (!depsToThisDep) { depsToThisDep = new NamespacedDataSet(); - this.dependenciesTo.set(depNs, depKey, depsToThisDep); + this._dependenciesTo.set(depNs, depKey, depsToThisDep); } depsToThisDep.set(namespace, key, true); }); @@ -37,7 +37,7 @@ export default class DependencyTracker { ) { if (!inDependencySet.get(modifiedNamespace, modifiedKey)) { inDependencySet.set(modifiedNamespace, modifiedKey, true); - const affectedItems = this.dependenciesTo.get(modifiedNamespace, modifiedKey); + const affectedItems = this._dependenciesTo.get(modifiedNamespace, modifiedKey); affectedItems?.enumerate((namespace, key) => { this.updateModifiedItems(inDependencySet, namespace, key); }); @@ -45,7 +45,7 @@ export default class DependencyTracker { } reset() { - this.dependenciesFrom.removeAll(); - this.dependenciesTo.removeAll(); + this._dependenciesFrom.removeAll(); + this._dependenciesTo.removeAll(); } } diff --git a/packages/shared/sdk-server/src/data_sources/FileDataSource.ts b/packages/shared/sdk-server/src/data_sources/FileDataSource.ts index 87d896e71..0422a4d34 100644 --- a/packages/shared/sdk-server/src/data_sources/FileDataSource.ts +++ b/packages/shared/sdk-server/src/data_sources/FileDataSource.ts @@ -27,13 +27,13 @@ function makeFlagWithValue(key: string, value: any, version: number): Flag { } export default class FileDataSource implements subsystem.LDStreamProcessor { - private logger?: LDLogger; + private _logger?: LDLogger; - private yamlParser?: (data: string) => any; + private _yamlParser?: (data: string) => any; - private fileLoader: FileLoader; + private _fileLoader: FileLoader; - private allData: LDFeatureStoreDataStorage = {}; + private _allData: LDFeatureStoreDataStorage = {}; /** * This is internal because we want instances to only be created with the @@ -43,11 +43,11 @@ export default class FileDataSource implements subsystem.LDStreamProcessor { constructor( options: FileDataSourceOptions, filesystem: Filesystem, - private readonly featureStore: LDDataSourceUpdates, - private initSuccessHandler: VoidFunction = () => {}, - private readonly errorHandler?: FileDataSourceErrorHandler, + private readonly _featureStore: LDDataSourceUpdates, + private _initSuccessHandler: VoidFunction = () => {}, + private readonly _errorHandler?: FileDataSourceErrorHandler, ) { - this.fileLoader = new FileLoader( + this._fileLoader = new FileLoader( filesystem, options.paths, options.autoUpdate ?? false, @@ -55,17 +55,17 @@ export default class FileDataSource implements subsystem.LDStreamProcessor { // Whenever changes are detected we re-process all of the data. // The FileLoader will have handled debouncing for us. try { - this.processFileData(results); + this._processFileData(results); } catch (err) { // If this was during start, then the initCallback will be present. - this.errorHandler?.(err as LDFileDataSourceError); - this.logger?.error(`Error processing files: ${err}`); + this._errorHandler?.(err as LDFileDataSourceError); + this._logger?.error(`Error processing files: ${err}`); } }, ); - this.logger = options.logger; - this.yamlParser = options.yamlParser; + this._logger = options.logger; + this._yamlParser = options.yamlParser; } start(): void { @@ -73,45 +73,45 @@ export default class FileDataSource implements subsystem.LDStreamProcessor { // async loading without making start async itself. (async () => { try { - await this.fileLoader.loadAndWatch(); + await this._fileLoader.loadAndWatch(); } catch (err) { // There was an issue loading/watching the files. // Report back to the caller. - this.errorHandler?.(err as LDFileDataSourceError); + this._errorHandler?.(err as LDFileDataSourceError); } })(); } stop(): void { - this.fileLoader.close(); + this._fileLoader.close(); } close(): void { this.stop(); } - private addItem(kind: DataKind, item: any) { - if (!this.allData[kind.namespace]) { - this.allData[kind.namespace] = {}; + private _addItem(kind: DataKind, item: any) { + if (!this._allData[kind.namespace]) { + this._allData[kind.namespace] = {}; } - if (this.allData[kind.namespace][item.key]) { + if (this._allData[kind.namespace][item.key]) { throw new Error(`found duplicate key: "${item.key}"`); } else { - this.allData[kind.namespace][item.key] = item; + this._allData[kind.namespace][item.key] = item; } } - private processFileData(fileData: { path: string; data: string }[]) { + private _processFileData(fileData: { path: string; data: string }[]) { // Clear any existing data before re-populating it. - const oldData = this.allData; - this.allData = {}; + const oldData = this._allData; + this._allData = {}; // We let the parsers throw, and the caller can handle the rejection. fileData.forEach((fd) => { let parsed: any; if (fd.path.endsWith('.yml') || fd.path.endsWith('.yaml')) { - if (this.yamlParser) { - parsed = this.yamlParser(fd.data); + if (this._yamlParser) { + parsed = this._yamlParser(fd.data); } else { throw new Error(`Attempted to parse yaml file (${fd.path}) without parser.`); } @@ -119,21 +119,21 @@ export default class FileDataSource implements subsystem.LDStreamProcessor { parsed = JSON.parse(fd.data); } - this.processParsedData(parsed, oldData); + this._processParsedData(parsed, oldData); }); - this.featureStore.init(this.allData, () => { + this._featureStore.init(this._allData, () => { // Call the init callback if present. // Then clear the callback so we cannot call it again. - this.initSuccessHandler(); - this.initSuccessHandler = () => {}; + this._initSuccessHandler(); + this._initSuccessHandler = () => {}; }); } - private processParsedData(parsed: any, oldData: LDFeatureStoreDataStorage) { + private _processParsedData(parsed: any, oldData: LDFeatureStoreDataStorage) { Object.keys(parsed.flags || {}).forEach((key) => { processFlag(parsed.flags[key]); - this.addItem(VersionedDataKinds.Features, parsed.flags[key]); + this._addItem(VersionedDataKinds.Features, parsed.flags[key]); }); Object.keys(parsed.flagValues || {}).forEach((key) => { const previousInstance = oldData[VersionedDataKinds.Features.namespace]?.[key]; @@ -147,11 +147,11 @@ export default class FileDataSource implements subsystem.LDStreamProcessor { } const flag = makeFlagWithValue(key, parsed.flagValues[key], version); processFlag(flag); - this.addItem(VersionedDataKinds.Features, flag); + this._addItem(VersionedDataKinds.Features, flag); }); Object.keys(parsed.segments || {}).forEach((key) => { processSegment(parsed.segments[key]); - this.addItem(VersionedDataKinds.Segments, parsed.segments[key]); + this._addItem(VersionedDataKinds.Segments, parsed.segments[key]); }); } } diff --git a/packages/shared/sdk-server/src/data_sources/FileLoader.ts b/packages/shared/sdk-server/src/data_sources/FileLoader.ts index d7762b146..fe2168c43 100644 --- a/packages/shared/sdk-server/src/data_sources/FileLoader.ts +++ b/packages/shared/sdk-server/src/data_sources/FileLoader.ts @@ -13,69 +13,69 @@ import { Filesystem, WatchHandle } from '@launchdarkly/js-sdk-common'; * @internal */ export default class FileLoader { - private watchers: WatchHandle[] = []; + private _watchers: WatchHandle[] = []; - private fileData: Record = {}; + private _fileData: Record = {}; - private fileTimestamps: Record = {}; + private _fileTimestamps: Record = {}; - private debounceHandle: any; + private _debounceHandle: any; constructor( - private readonly filesystem: Filesystem, - private readonly paths: string[], - private readonly watch: boolean, - private readonly callback: (results: { path: string; data: string }[]) => void, + private readonly _filesystem: Filesystem, + private readonly _paths: string[], + private readonly _watch: boolean, + private readonly _callback: (results: { path: string; data: string }[]) => void, ) {} /** * Load all the files and start watching them if watching is enabled. */ async loadAndWatch() { - const promises = this.paths.map(async (path) => { - const data = await this.filesystem.readFile(path); - const timeStamp = await this.filesystem.getFileTimestamp(path); + const promises = this._paths.map(async (path) => { + const data = await this._filesystem.readFile(path); + const timeStamp = await this._filesystem.getFileTimestamp(path); return { data, path, timeStamp }; }); // This promise could be rejected, let the caller handle it. const results = await Promise.all(promises); results.forEach((res) => { - this.fileData[res.path] = res.data; - this.fileTimestamps[res.path] = res.timeStamp; + this._fileData[res.path] = res.data; + this._fileTimestamps[res.path] = res.timeStamp; }); - this.callback(results); + this._callback(results); // If we are watching, then setup watchers and notify of any changes. - if (this.watch) { - this.paths.forEach((path) => { - const watcher = this.filesystem.watch(path, async (_: string, updatePath: string) => { - const timeStamp = await this.filesystem.getFileTimestamp(updatePath); + if (this._watch) { + this._paths.forEach((path) => { + const watcher = this._filesystem.watch(path, async (_: string, updatePath: string) => { + const timeStamp = await this._filesystem.getFileTimestamp(updatePath); // The modification time is the same, so we are going to ignore this update. // In some implementations watch might be triggered multiple times for a single update. - if (timeStamp === this.fileTimestamps[updatePath]) { + if (timeStamp === this._fileTimestamps[updatePath]) { return; } - this.fileTimestamps[updatePath] = timeStamp; - const data = await this.filesystem.readFile(updatePath); - this.fileData[updatePath] = data; - this.debounceCallback(); + this._fileTimestamps[updatePath] = timeStamp; + const data = await this._filesystem.readFile(updatePath); + this._fileData[updatePath] = data; + this._debounceCallback(); }); - this.watchers.push(watcher); + this._watchers.push(watcher); }); } } close() { - this.watchers.forEach((watcher) => watcher.close()); + this._watchers.forEach((watcher) => watcher.close()); } - private debounceCallback() { + private _debounceCallback() { // If there is a handle, then we have already started the debounce process. - if (!this.debounceHandle) { - this.debounceHandle = setTimeout(() => { - this.debounceHandle = undefined; - this.callback( - Object.entries(this.fileData).reduce( + if (!this._debounceHandle) { + this._debounceHandle = setTimeout(() => { + this._debounceHandle = undefined; + this._callback( + Object.entries(this._fileData).reduce( (acc: { path: string; data: string }[], [path, data]) => { acc.push({ path, data }); return acc; diff --git a/packages/shared/sdk-server/src/data_sources/NamespacedDataSet.ts b/packages/shared/sdk-server/src/data_sources/NamespacedDataSet.ts index 1b6ae6fee..1510579b4 100644 --- a/packages/shared/sdk-server/src/data_sources/NamespacedDataSet.ts +++ b/packages/shared/sdk-server/src/data_sources/NamespacedDataSet.ts @@ -2,32 +2,32 @@ * @internal */ export default class NamespacedDataSet { - private itemsByNamespace: Record> = {}; + private _itemsByNamespace: Record> = {}; get(namespace: string, key: string): T | undefined { - return this.itemsByNamespace[namespace]?.[key]; + return this._itemsByNamespace[namespace]?.[key]; } set(namespace: string, key: string, value: T) { - if (!(namespace in this.itemsByNamespace)) { - this.itemsByNamespace[namespace] = {}; + if (!(namespace in this._itemsByNamespace)) { + this._itemsByNamespace[namespace] = {}; } - this.itemsByNamespace[namespace][key] = value; + this._itemsByNamespace[namespace][key] = value; } remove(namespace: string, key: string) { - const items = this.itemsByNamespace[namespace]; + const items = this._itemsByNamespace[namespace]; if (items) { delete items[key]; } } removeAll() { - this.itemsByNamespace = {}; + this._itemsByNamespace = {}; } enumerate(callback: (namespace: string, key: string, value: T) => void) { - Object.entries(this.itemsByNamespace).forEach(([namespace, values]) => { + Object.entries(this._itemsByNamespace).forEach(([namespace, values]) => { Object.entries(values).forEach(([key, value]) => { callback(namespace, key, value); }); diff --git a/packages/shared/sdk-server/src/data_sources/PollingProcessor.ts b/packages/shared/sdk-server/src/data_sources/PollingProcessor.ts index 5014b8eed..d376b4535 100644 --- a/packages/shared/sdk-server/src/data_sources/PollingProcessor.ts +++ b/packages/shared/sdk-server/src/data_sources/PollingProcessor.ts @@ -20,34 +20,34 @@ export type PollingErrorHandler = (err: LDPollingError) => void; * @internal */ export default class PollingProcessor implements subsystem.LDStreamProcessor { - private stopped = false; + private _stopped = false; - private logger?: LDLogger; + private _logger?: LDLogger; - private pollInterval: number; + private _pollInterval: number; - private timeoutHandle: any; + private _timeoutHandle: any; constructor( config: Configuration, - private readonly requestor: Requestor, - private readonly featureStore: LDDataSourceUpdates, - private readonly initSuccessHandler: VoidFunction = () => {}, - private readonly errorHandler?: PollingErrorHandler, + private readonly _requestor: Requestor, + private readonly _featureStore: LDDataSourceUpdates, + private readonly _initSuccessHandler: VoidFunction = () => {}, + private readonly _errorHandler?: PollingErrorHandler, ) { - this.logger = config.logger; - this.pollInterval = config.pollInterval; + this._logger = config.logger; + this._pollInterval = config.pollInterval; } - private poll() { - if (this.stopped) { + private _poll() { + if (this._stopped) { return; } const reportJsonError = (data: string) => { - this.logger?.error('Polling received invalid data'); - this.logger?.debug(`Invalid JSON follows: ${data}`); - this.errorHandler?.( + this._logger?.error('Polling received invalid data'); + this._logger?.debug(`Invalid JSON follows: ${data}`); + this._errorHandler?.( new LDPollingError( DataSourceErrorKind.InvalidData, 'Malformed JSON data in polling response', @@ -56,25 +56,25 @@ export default class PollingProcessor implements subsystem.LDStreamProcessor { }; const startTime = Date.now(); - this.logger?.debug('Polling LaunchDarkly for feature flag updates'); - this.requestor.requestAllData((err, body) => { + this._logger?.debug('Polling LaunchDarkly for feature flag updates'); + this._requestor.requestAllData((err, body) => { const elapsed = Date.now() - startTime; - const sleepFor = Math.max(this.pollInterval * 1000 - elapsed, 0); + const sleepFor = Math.max(this._pollInterval * 1000 - elapsed, 0); - this.logger?.debug('Elapsed: %d ms, sleeping for %d ms', elapsed, sleepFor); + this._logger?.debug('Elapsed: %d ms, sleeping for %d ms', elapsed, sleepFor); if (err) { const { status } = err; if (status && !isHttpRecoverable(status)) { const message = httpErrorMessage(err, 'polling request'); - this.logger?.error(message); - this.errorHandler?.( + this._logger?.error(message); + this._errorHandler?.( new LDPollingError(DataSourceErrorKind.ErrorResponse, message, status), ); // It is not recoverable, return and do not trigger another // poll. return; } - this.logger?.warn(httpErrorMessage(err, 'polling request', 'will retry')); + this._logger?.warn(httpErrorMessage(err, 'polling request', 'will retry')); } else if (body) { const parsed = deserializePoll(body); if (!parsed) { @@ -86,11 +86,11 @@ export default class PollingProcessor implements subsystem.LDStreamProcessor { [VersionedDataKinds.Features.namespace]: parsed.flags, [VersionedDataKinds.Segments.namespace]: parsed.segments, }; - this.featureStore.init(initData, () => { - this.initSuccessHandler(); + this._featureStore.init(initData, () => { + this._initSuccessHandler(); // Triggering the next poll after the init has completed. - this.timeoutHandle = setTimeout(() => { - this.poll(); + this._timeoutHandle = setTimeout(() => { + this._poll(); }, sleepFor); }); // The poll will be triggered by the feature store initialization @@ -101,22 +101,22 @@ export default class PollingProcessor implements subsystem.LDStreamProcessor { // Falling through, there was some type of error and we need to trigger // a new poll. - this.timeoutHandle = setTimeout(() => { - this.poll(); + this._timeoutHandle = setTimeout(() => { + this._poll(); }, sleepFor); }); } start() { - this.poll(); + this._poll(); } stop() { - if (this.timeoutHandle) { - clearTimeout(this.timeoutHandle); - this.timeoutHandle = undefined; + if (this._timeoutHandle) { + clearTimeout(this._timeoutHandle); + this._timeoutHandle = undefined; } - this.stopped = true; + this._stopped = true; } close() { diff --git a/packages/shared/sdk-server/src/data_sources/Requestor.ts b/packages/shared/sdk-server/src/data_sources/Requestor.ts index 9c8aa5c10..0d3567eae 100644 --- a/packages/shared/sdk-server/src/data_sources/Requestor.ts +++ b/packages/shared/sdk-server/src/data_sources/Requestor.ts @@ -15,11 +15,11 @@ import Configuration from '../options/Configuration'; * @internal */ export default class Requestor implements LDFeatureRequestor { - private readonly headers: Record; + private readonly _headers: Record; - private readonly uri: string; + private readonly _uri: string; - private readonly eTagCache: Record< + private readonly _eTagCache: Record< string, { etag: string; @@ -29,25 +29,25 @@ export default class Requestor implements LDFeatureRequestor { constructor( config: Configuration, - private readonly requests: Requests, + private readonly _requests: Requests, baseHeaders: LDHeaders, ) { - this.headers = { ...baseHeaders }; - this.uri = getPollingUri(config.serviceEndpoints, '/sdk/latest-all', []); + this._headers = { ...baseHeaders }; + this._uri = getPollingUri(config.serviceEndpoints, '/sdk/latest-all', []); } /** * Perform a request and utilize the ETag cache. The ETags are cached in the * requestor instance. */ - private async requestWithETagCache( + private async _requestWithETagCache( requestUrl: string, options: Options, ): Promise<{ res: Response; body: string; }> { - const cacheEntry = this.eTagCache[requestUrl]; + const cacheEntry = this._eTagCache[requestUrl]; const cachedETag = cacheEntry?.etag; const updatedOptions = cachedETag @@ -57,7 +57,7 @@ export default class Requestor implements LDFeatureRequestor { } : options; - const res = await this.requests.fetch(requestUrl, updatedOptions); + const res = await this._requests.fetch(requestUrl, updatedOptions); if (res.status === 304 && cacheEntry) { return { res, body: cacheEntry.body }; @@ -65,7 +65,7 @@ export default class Requestor implements LDFeatureRequestor { const etag = res.headers.get('etag'); const body = await res.text(); if (etag) { - this.eTagCache[requestUrl] = { etag, body }; + this._eTagCache[requestUrl] = { etag, body }; } return { res, body }; } @@ -73,10 +73,10 @@ export default class Requestor implements LDFeatureRequestor { async requestAllData(cb: (err: any, body: any) => void) { const options: Options = { method: 'GET', - headers: this.headers, + headers: this._headers, }; try { - const { res, body } = await this.requestWithETagCache(this.uri, options); + const { res, body } = await this._requestWithETagCache(this._uri, options); if (res.status !== 200 && res.status !== 304) { const err = new LDPollingError( DataSourceErrorKind.ErrorResponse, diff --git a/packages/shared/sdk-server/src/evaluation/Bucketer.ts b/packages/shared/sdk-server/src/evaluation/Bucketer.ts index c9febf4f0..34367ce6f 100644 --- a/packages/shared/sdk-server/src/evaluation/Bucketer.ts +++ b/packages/shared/sdk-server/src/evaluation/Bucketer.ts @@ -17,14 +17,14 @@ function valueForBucketing(value: any): string | null { } export default class Bucketer { - private crypto: Crypto; + private _crypto: Crypto; constructor(crypto: Crypto) { - this.crypto = crypto; + this._crypto = crypto; } - private sha1Hex(value: string) { - const hash = this.crypto.createHash('sha1'); + private _sha1Hex(value: string) { + const hash = this._crypto.createHash('sha1'); hash.update(value); if (!hash.digest) { // This represents an error in platform implementation. @@ -69,7 +69,7 @@ export default class Bucketer { const prefix = seed ? Number(seed) : `${key}.${salt}`; const hashKey = `${prefix}.${bucketableValue}`; - const hashVal = parseInt(this.sha1Hex(hashKey).substring(0, 15), 16); + const hashVal = parseInt(this._sha1Hex(hashKey).substring(0, 15), 16); // This is how this has worked in previous implementations, but it is not // ideal. diff --git a/packages/shared/sdk-server/src/evaluation/Evaluator.ts b/packages/shared/sdk-server/src/evaluation/Evaluator.ts index ef95b9eee..2215762ae 100644 --- a/packages/shared/sdk-server/src/evaluation/Evaluator.ts +++ b/packages/shared/sdk-server/src/evaluation/Evaluator.ts @@ -105,19 +105,19 @@ type MatchOrError = Match | MatchError; * @internal */ export default class Evaluator { - private queries: Queries; + private _queries: Queries; - private bucketer: Bucketer; + private _bucketer: Bucketer; constructor(platform: Platform, queries: Queries) { - this.queries = queries; - this.bucketer = new Bucketer(platform.crypto); + this._queries = queries; + this._bucketer = new Bucketer(platform.crypto); } async evaluate(flag: Flag, context: Context, eventFactory?: EventFactory): Promise { return new Promise((resolve) => { const state: EvalState = {}; - this.evaluateInternal( + this._evaluateInternal( flag, context, state, @@ -144,7 +144,7 @@ export default class Evaluator { eventFactory?: EventFactory, ) { const state: EvalState = {}; - this.evaluateInternal( + this._evaluateInternal( flag, context, state, @@ -173,7 +173,7 @@ export default class Evaluator { * @param visitedFlags The flags that have been visited during this evaluation. * This is not part of the state, because it needs to be forked during prerequisite evaluations. */ - private evaluateInternal( + private _evaluateInternal( flag: Flag, context: Context, state: EvalState, @@ -186,7 +186,7 @@ export default class Evaluator { return; } - this.checkPrerequisites( + this._checkPrerequisites( flag, context, state, @@ -205,13 +205,13 @@ export default class Evaluator { return; } - this.evaluateRules(flag, context, state, (evalRes) => { + this._evaluateRules(flag, context, state, (evalRes) => { if (evalRes) { cb(evalRes); return; } - cb(this.variationForContext(flag.fallthrough, context, flag, Reasons.Fallthrough)); + cb(this._variationForContext(flag.fallthrough, context, flag, Reasons.Fallthrough)); }); }, eventFactory, @@ -228,7 +228,7 @@ export default class Evaluator { * an {@link EvalResult} containing an error result or `undefined` if the prerequisites * are met. */ - private checkPrerequisites( + private _checkPrerequisites( flag: Flag, context: Context, state: EvalState, @@ -258,14 +258,14 @@ export default class Evaluator { return; } const updatedVisitedFlags = [...visitedFlags, prereq.key]; - this.queries.getFlag(prereq.key, (prereqFlag) => { + this._queries.getFlag(prereq.key, (prereqFlag) => { if (!prereqFlag) { prereqResult = getOffVariation(flag, Reasons.prerequisiteFailed(prereq.key)); iterCb(false); return; } - this.evaluateInternal( + this._evaluateInternal( prereqFlag, context, state, @@ -310,7 +310,7 @@ export default class Evaluator { * @param cb Callback called when rule evaluation is complete, it will be called with either * an {@link EvalResult} or 'undefined'. */ - private evaluateRules( + private _evaluateRules( flag: Flag, context: Context, state: EvalState, @@ -321,7 +321,7 @@ export default class Evaluator { firstSeriesAsync( flag.rules, (rule, ruleIndex, iterCb: (res: boolean) => void) => { - this.ruleMatchContext(flag, rule, ruleIndex, context, state, [], (res) => { + this._ruleMatchContext(flag, rule, ruleIndex, context, state, [], (res) => { ruleResult = res; iterCb(!!res); }); @@ -330,7 +330,7 @@ export default class Evaluator { ); } - private clauseMatchContext( + private _clauseMatchContext( clause: Clause, context: Context, segmentsVisited: string[], @@ -342,7 +342,7 @@ export default class Evaluator { firstSeriesAsync( clause.values, (value, _index, iterCb) => { - this.queries.getSegment(value, (segment) => { + this._queries.getSegment(value, (segment) => { if (segment) { if (segmentsVisited.includes(segment.key)) { errorResult = EvalResult.forError( @@ -399,7 +399,7 @@ export default class Evaluator { * @param cb Called when matching is complete with an {@link EvalResult} or `undefined` if there * are no matches or errors. */ - private ruleMatchContext( + private _ruleMatchContext( flag: Flag, rule: FlagRule, ruleIndex: number, @@ -416,7 +416,7 @@ export default class Evaluator { allSeriesAsync( rule.clauses, (clause, _index, iterCb) => { - this.clauseMatchContext(clause, context, segmentsVisited, state, (res) => { + this._clauseMatchContext(clause, context, segmentsVisited, state, (res) => { errorResult = res.result; return iterCb(res.error || res.isMatch); }); @@ -428,7 +428,7 @@ export default class Evaluator { if (match) { return cb( - this.variationForContext(rule, context, flag, Reasons.ruleMatch(rule.id, ruleIndex)), + this._variationForContext(rule, context, flag, Reasons.ruleMatch(rule.id, ruleIndex)), ); } return cb(undefined); @@ -436,7 +436,7 @@ export default class Evaluator { ); } - private variationForContext( + private _variationForContext( varOrRollout: VariationOrRollout, context: Context, flag: Flag, @@ -467,7 +467,7 @@ export default class Evaluator { ); } - const [bucket, hadContext] = this.bucketer.bucket( + const [bucket, hadContext] = this._bucketer.bucket( context, flag.key, bucketBy, @@ -522,7 +522,7 @@ export default class Evaluator { allSeriesAsync( rule.clauses, (clause, _index, iterCb) => { - this.clauseMatchContext(clause, context, segmentsVisited, state, (res) => { + this._clauseMatchContext(clause, context, segmentsVisited, state, (res) => { errorResult = res.result; iterCb(res.error || res.isMatch); }); @@ -548,7 +548,7 @@ export default class Evaluator { ); } - const [bucket] = this.bucketer.bucket( + const [bucket] = this._bucketer.bucket( context, segment.key, bucketBy, @@ -647,7 +647,7 @@ export default class Evaluator { return; } - this.queries.getBigSegmentsMembership(keyForBigSegment).then((result) => { + this._queries.getBigSegmentsMembership(keyForBigSegment).then((result) => { // eslint-disable-next-line no-param-reassign state.bigSegmentsMembership = state.bigSegmentsMembership || {}; if (result) { diff --git a/packages/shared/sdk-server/src/evaluation/evalTargets.ts b/packages/shared/sdk-server/src/evaluation/evalTargets.ts index 9f489593d..4440a0763 100644 --- a/packages/shared/sdk-server/src/evaluation/evalTargets.ts +++ b/packages/shared/sdk-server/src/evaluation/evalTargets.ts @@ -34,7 +34,7 @@ export default function evalTargets(flag: Flag, context: Context): EvalResult | } return firstResult(flag.contextTargets, (target) => { - if (!target.contextKind || target.contextKind === Context.userKind) { + if (!target.contextKind || target.contextKind === Context.UserKind) { // When a context target is for a user, then use a user target with the same variation. const userTarget = (flag.targets || []).find((ut) => ut.variation === target.variation); if (userTarget) { diff --git a/packages/shared/sdk-server/src/events/ContextDeduplicator.ts b/packages/shared/sdk-server/src/events/ContextDeduplicator.ts index 4792d7f14..bfedb5825 100644 --- a/packages/shared/sdk-server/src/events/ContextDeduplicator.ts +++ b/packages/shared/sdk-server/src/events/ContextDeduplicator.ts @@ -10,23 +10,23 @@ export interface ContextDeduplicatorOptions { export default class ContextDeduplicator implements subsystem.LDContextDeduplicator { public readonly flushInterval: number; - private contextKeysCache: LruCache; + private _contextKeysCache: LruCache; constructor(options: ContextDeduplicatorOptions) { - this.contextKeysCache = new LruCache({ max: options.contextKeysCapacity }); + this._contextKeysCache = new LruCache({ max: options.contextKeysCapacity }); this.flushInterval = options.contextKeysFlushInterval; } public processContext(context: Context): boolean { const { canonicalKey } = context; - const inCache = this.contextKeysCache.get(canonicalKey); - this.contextKeysCache.set(canonicalKey, true); + const inCache = this._contextKeysCache.get(canonicalKey); + this._contextKeysCache.set(canonicalKey, true); // If it is in the cache, then we do not want to add an event. return !inCache; } public flush(): void { - this.contextKeysCache.clear(); + this._contextKeysCache.clear(); } } diff --git a/packages/shared/sdk-server/src/hooks/HookRunner.ts b/packages/shared/sdk-server/src/hooks/HookRunner.ts index 5e9e531ed..c7c1e61f4 100644 --- a/packages/shared/sdk-server/src/hooks/HookRunner.ts +++ b/packages/shared/sdk-server/src/hooks/HookRunner.ts @@ -7,13 +7,13 @@ const AFTER_EVALUATION_STAGE_NAME = 'afterEvaluation'; const UNKNOWN_HOOK_NAME = 'unknown hook'; export default class HookRunner { - private readonly hooks: Hook[] = []; + private readonly _hooks: Hook[] = []; constructor( - private readonly logger: LDLogger | undefined, + private readonly _logger: LDLogger | undefined, hooks: Hook[], ) { - this.hooks.push(...hooks); + this._hooks.push(...hooks); } public async withEvaluationSeries( @@ -25,7 +25,7 @@ export default class HookRunner { ): Promise { // This early return is here to avoid the extra async/await associated with // using withHooksDataWithDetail. - if (this.hooks.length === 0) { + if (this._hooks.length === 0) { return method(); } @@ -52,18 +52,18 @@ export default class HookRunner { methodName: string, method: () => Promise<{ detail: LDEvaluationDetail; [index: string]: any }>, ): Promise<{ detail: LDEvaluationDetail; [index: string]: any }> { - if (this.hooks.length === 0) { + if (this._hooks.length === 0) { return method(); } const { hooks, hookContext }: { hooks: Hook[]; hookContext: EvaluationSeriesContext } = - this.prepareHooks(key, context, defaultValue, methodName); - const hookData = this.executeBeforeEvaluation(hooks, hookContext); + this._prepareHooks(key, context, defaultValue, methodName); + const hookData = this._executeBeforeEvaluation(hooks, hookContext); const result = await method(); - this.executeAfterEvaluation(hooks, hookContext, hookData, result.detail); + this._executeAfterEvaluation(hooks, hookContext, hookData, result.detail); return result; } - private tryExecuteStage( + private _tryExecuteStage( method: string, hookName: string, stage: () => EvaluationSeriesData, @@ -71,23 +71,23 @@ export default class HookRunner { try { return stage(); } catch (err) { - this.logger?.error( + this._logger?.error( `An error was encountered in "${method}" of the "${hookName}" hook: ${err}`, ); return {}; } } - private hookName(hook?: Hook): string { + private _hookName(hook?: Hook): string { try { return hook?.getMetadata().name ?? UNKNOWN_HOOK_NAME; } catch { - this.logger?.error(`Exception thrown getting metadata for hook. Unable to get hook name.`); + this._logger?.error(`Exception thrown getting metadata for hook. Unable to get hook name.`); return UNKNOWN_HOOK_NAME; } } - private executeAfterEvaluation( + private _executeAfterEvaluation( hooks: Hook[], hookContext: EvaluationSeriesContext, updatedData: (EvaluationSeriesData | undefined)[], @@ -98,28 +98,28 @@ export default class HookRunner { for (let hookIndex = hooks.length - 1; hookIndex >= 0; hookIndex -= 1) { const hook = hooks[hookIndex]; const data = updatedData[hookIndex] ?? {}; - this.tryExecuteStage( + this._tryExecuteStage( AFTER_EVALUATION_STAGE_NAME, - this.hookName(hook), + this._hookName(hook), () => hook?.afterEvaluation?.(hookContext, data, result) ?? {}, ); } } - private executeBeforeEvaluation( + private _executeBeforeEvaluation( hooks: Hook[], hookContext: EvaluationSeriesContext, ): EvaluationSeriesData[] { return hooks.map((hook) => - this.tryExecuteStage( + this._tryExecuteStage( BEFORE_EVALUATION_STAGE_NAME, - this.hookName(hook), + this._hookName(hook), () => hook?.beforeEvaluation?.(hookContext, {}) ?? {}, ), ); } - private prepareHooks( + private _prepareHooks( key: string, context: LDContext, defaultValue: unknown, @@ -131,7 +131,7 @@ export default class HookRunner { // Copy the hooks to use a consistent set during evaluation. Hooks could be added and we want // to ensure all correct stages for any give hook execute. Not for instance the afterEvaluation // stage without beforeEvaluation having been called on that hook. - const hooks: Hook[] = [...this.hooks]; + const hooks: Hook[] = [...this._hooks]; const hookContext: EvaluationSeriesContext = { flagKey: key, context, @@ -142,6 +142,6 @@ export default class HookRunner { } addHook(hook: Hook): void { - this.hooks.push(hook); + this._hooks.push(hook); } } diff --git a/packages/shared/sdk-server/src/integrations/FileDataSourceFactory.ts b/packages/shared/sdk-server/src/integrations/FileDataSourceFactory.ts index 885776ee2..60f49d4ef 100644 --- a/packages/shared/sdk-server/src/integrations/FileDataSourceFactory.ts +++ b/packages/shared/sdk-server/src/integrations/FileDataSourceFactory.ts @@ -18,7 +18,7 @@ export interface FileDataSourceFactoryConfig { */ export default class FileDataSourceFactory { - constructor(private readonly options: FileDataSourceOptions) {} + constructor(private readonly _options: FileDataSourceOptions) {} /** * Method for creating instances of the file data source. This method is intended to be used @@ -37,10 +37,10 @@ export default class FileDataSourceFactory { errorHandler?: FileDataSourceErrorHandler, ) { const updatedOptions: FileDataSourceOptions = { - paths: this.options.paths, - autoUpdate: this.options.autoUpdate, - logger: this.options.logger || ldClientContext.basicConfiguration.logger, - yamlParser: this.options.yamlParser, + paths: this._options.paths, + autoUpdate: this._options.autoUpdate, + logger: this._options.logger || ldClientContext.basicConfiguration.logger, + yamlParser: this._options.yamlParser, }; return new FileDataSource( updatedOptions, diff --git a/packages/shared/sdk-server/src/integrations/test_data/TestData.ts b/packages/shared/sdk-server/src/integrations/test_data/TestData.ts index cd4b41417..dc17aa1a2 100644 --- a/packages/shared/sdk-server/src/integrations/test_data/TestData.ts +++ b/packages/shared/sdk-server/src/integrations/test_data/TestData.ts @@ -43,13 +43,13 @@ import TestDataSource from './TestDataSource'; * any changes made to the data will propagate to all of the `LDClient`s. */ export default class TestData { - private currentFlags: Record = {}; + private _currentFlags: Record = {}; - private currentSegments: Record = {}; + private _currentSegments: Record = {}; - private dataSources: TestDataSource[] = []; + private _dataSources: TestDataSource[] = []; - private flagBuilders: Record = {}; + private _flagBuilders: Record = {}; /** * Get a factory for update processors that will be attached to this TestData instance. @@ -78,15 +78,15 @@ export default class TestData { ); const newSource = new TestDataSource( new AsyncStoreFacade(featureStore), - this.currentFlags, - this.currentSegments, + this._currentFlags, + this._currentSegments, (tds) => { - this.dataSources.splice(this.dataSources.indexOf(tds)); + this._dataSources.splice(this._dataSources.indexOf(tds)); }, listeners, ); - this.dataSources.push(newSource); + this._dataSources.push(newSource); return newSource; }; } @@ -112,8 +112,8 @@ export default class TestData { * */ flag(key: string): TestDataFlagBuilder { - if (this.flagBuilders[key]) { - return this.flagBuilders[key].clone(); + if (this._flagBuilders[key]) { + return this._flagBuilders[key].clone(); } return new TestDataFlagBuilder(key).booleanFlag(); } @@ -136,14 +136,14 @@ export default class TestData { */ update(flagBuilder: TestDataFlagBuilder): Promise { const flagKey = flagBuilder.getKey(); - const oldItem = this.currentFlags[flagKey]; + const oldItem = this._currentFlags[flagKey]; const oldVersion = oldItem ? oldItem.version : 0; const newFlag = flagBuilder.build(oldVersion + 1); - this.currentFlags[flagKey] = newFlag; - this.flagBuilders[flagKey] = flagBuilder.clone(); + this._currentFlags[flagKey] = newFlag; + this._flagBuilders[flagKey] = flagBuilder.clone(); return Promise.all( - this.dataSources.map((impl) => impl.upsert(VersionedDataKinds.Features, newFlag)), + this._dataSources.map((impl) => impl.upsert(VersionedDataKinds.Features, newFlag)), ); } @@ -169,13 +169,13 @@ export default class TestData { // We need to do things like process attribute reference, and // we do not want to modify the passed in value. const flagConfig = JSON.parse(JSON.stringify(inConfig)); - const oldItem = this.currentFlags[flagConfig.key]; + const oldItem = this._currentFlags[flagConfig.key]; const newItem = { ...flagConfig, version: oldItem ? oldItem.version + 1 : flagConfig.version }; processFlag(newItem); - this.currentFlags[flagConfig.key] = newItem; + this._currentFlags[flagConfig.key] = newItem; return Promise.all( - this.dataSources.map((impl) => impl.upsert(VersionedDataKinds.Features, newItem)), + this._dataSources.map((impl) => impl.upsert(VersionedDataKinds.Features, newItem)), ); } @@ -198,16 +198,16 @@ export default class TestData { usePreconfiguredSegment(inConfig: any): Promise { const segmentConfig = JSON.parse(JSON.stringify(inConfig)); - const oldItem = this.currentSegments[segmentConfig.key]; + const oldItem = this._currentSegments[segmentConfig.key]; const newItem = { ...segmentConfig, version: oldItem ? oldItem.version + 1 : segmentConfig.version, }; processSegment(newItem); - this.currentSegments[segmentConfig.key] = newItem; + this._currentSegments[segmentConfig.key] = newItem; return Promise.all( - this.dataSources.map((impl) => impl.upsert(VersionedDataKinds.Segments, newItem)), + this._dataSources.map((impl) => impl.upsert(VersionedDataKinds.Segments, newItem)), ); } } diff --git a/packages/shared/sdk-server/src/integrations/test_data/TestDataFlagBuilder.ts b/packages/shared/sdk-server/src/integrations/test_data/TestDataFlagBuilder.ts index 88dade591..a3cf80a41 100644 --- a/packages/shared/sdk-server/src/integrations/test_data/TestDataFlagBuilder.ts +++ b/packages/shared/sdk-server/src/integrations/test_data/TestDataFlagBuilder.ts @@ -28,7 +28,7 @@ interface BuilderData { * A builder for feature flag configurations to be used with {@link TestData}. */ export default class TestDataFlagBuilder { - private data: BuilderData = { + private _data: BuilderData = { on: true, variations: [], }; @@ -37,38 +37,38 @@ export default class TestDataFlagBuilder { * @internal */ constructor( - private readonly key: string, + private readonly _key: string, data?: BuilderData, ) { if (data) { // Not the fastest way to deep copy, but this is a testing mechanism. - this.data = { + this._data = { on: data.on, variations: [...data.variations], }; if (data.offVariation !== undefined) { - this.data.offVariation = data.offVariation; + this._data.offVariation = data.offVariation; } if (data.fallthroughVariation !== undefined) { - this.data.fallthroughVariation = data.fallthroughVariation; + this._data.fallthroughVariation = data.fallthroughVariation; } if (data.targetsByVariation) { - this.data.targetsByVariation = JSON.parse(JSON.stringify(data.targetsByVariation)); + this._data.targetsByVariation = JSON.parse(JSON.stringify(data.targetsByVariation)); } if (data.rules) { - this.data.rules = []; + this._data.rules = []; data.rules.forEach((rule) => { - this.data.rules?.push(rule.clone()); + this._data.rules?.push(rule.clone()); }); } } } - private get isBooleanFlag(): boolean { + private get _isBooleanFlag(): boolean { return ( - this.data.variations.length === 2 && - this.data.variations[TRUE_VARIATION_INDEX] === true && - this.data.variations[FALSE_VARIATION_INDEX] === false + this._data.variations.length === 2 && + this._data.variations[TRUE_VARIATION_INDEX] === true && + this._data.variations[FALSE_VARIATION_INDEX] === false ); } @@ -83,7 +83,7 @@ export default class TestDataFlagBuilder { * @return the flag builder */ booleanFlag(): TestDataFlagBuilder { - if (this.isBooleanFlag) { + if (this._isBooleanFlag) { return this; } // Change this flag into a boolean flag. @@ -103,7 +103,7 @@ export default class TestDataFlagBuilder { * @return the flag builder */ variations(...values: any[]): TestDataFlagBuilder { - this.data.variations = [...values]; + this._data.variations = [...values]; return this; } @@ -120,7 +120,7 @@ export default class TestDataFlagBuilder { * @return the flag builder */ on(targetingOn: boolean): TestDataFlagBuilder { - this.data.on = targetingOn; + this._data.on = targetingOn; return this; } @@ -141,7 +141,7 @@ export default class TestDataFlagBuilder { if (TypeValidators.Boolean.is(variation)) { return this.booleanFlag().fallthroughVariation(variationForBoolean(variation)); } - this.data.fallthroughVariation = variation; + this._data.fallthroughVariation = variation; return this; } @@ -161,7 +161,7 @@ export default class TestDataFlagBuilder { if (TypeValidators.Boolean.is(variation)) { return this.booleanFlag().offVariation(variationForBoolean(variation)); } - this.data.offVariation = variation; + this._data.offVariation = variation; return this; } @@ -254,14 +254,14 @@ export default class TestDataFlagBuilder { ); } - if (!this.data.targetsByVariation) { - this.data.targetsByVariation = {}; + if (!this._data.targetsByVariation) { + this._data.targetsByVariation = {}; } - this.data.variations.forEach((_, i) => { + this._data.variations.forEach((_, i) => { if (i === variation) { // If there is nothing set at the current variation then set it to the empty array - const targetsForVariation = this.data.targetsByVariation![i] || {}; + const targetsForVariation = this._data.targetsByVariation![i] || {}; if (!(contextKind in targetsForVariation)) { targetsForVariation[contextKind] = []; @@ -272,10 +272,10 @@ export default class TestDataFlagBuilder { targetsForVariation[contextKind].push(contextKey); } - this.data.targetsByVariation![i] = targetsForVariation; + this._data.targetsByVariation![i] = targetsForVariation; } else { // remove user from other variation set if necessary - const targetsForVariation = this.data.targetsByVariation![i]; + const targetsForVariation = this._data.targetsByVariation![i]; if (targetsForVariation) { const targetsForContextKind = targetsForVariation[contextKind]; if (targetsForContextKind) { @@ -288,7 +288,7 @@ export default class TestDataFlagBuilder { } } if (!Object.keys(targetsForVariation).length) { - delete this.data.targetsByVariation![i]; + delete this._data.targetsByVariation![i]; } } } @@ -304,7 +304,7 @@ export default class TestDataFlagBuilder { * @return the same flag builder */ clearRules(): TestDataFlagBuilder { - delete this.data.rules; + delete this._data.rules; return this; } @@ -315,7 +315,7 @@ export default class TestDataFlagBuilder { * @return the same flag builder */ clearAllTargets(): TestDataFlagBuilder { - delete this.data.targetsByVariation; + delete this._data.targetsByVariation; return this; } @@ -372,13 +372,13 @@ export default class TestDataFlagBuilder { } checkRatio(ratio: number): TestDataFlagBuilder { - this.data.migration = this.data.migration ?? {}; - this.data.migration.checkRatio = ratio; + this._data.migration = this._data.migration ?? {}; + this._data.migration.checkRatio = ratio; return this; } samplingRatio(ratio: number): TestDataFlagBuilder { - this.data.samplingRatio = ratio; + this._data.samplingRatio = ratio; return this; } @@ -386,10 +386,10 @@ export default class TestDataFlagBuilder { * @internal */ addRule(flagRuleBuilder: TestDataRuleBuilder) { - if (!this.data.rules) { - this.data.rules = []; + if (!this._data.rules) { + this._data.rules = []; } - this.data.rules.push(flagRuleBuilder as TestDataRuleBuilder); + this._data.rules.push(flagRuleBuilder as TestDataRuleBuilder); } /** @@ -397,22 +397,22 @@ export default class TestDataFlagBuilder { */ build(version: number) { const baseFlagObject: Flag = { - key: this.key, + key: this._key, version, - on: this.data.on, - offVariation: this.data.offVariation, + on: this._data.on, + offVariation: this._data.offVariation, fallthrough: { - variation: this.data.fallthroughVariation, + variation: this._data.fallthroughVariation, }, - variations: [...this.data.variations], - migration: this.data.migration, - samplingRatio: this.data.samplingRatio, + variations: [...this._data.variations], + migration: this._data.migration, + samplingRatio: this._data.samplingRatio, }; - if (this.data.targetsByVariation) { + if (this._data.targetsByVariation) { const contextTargets: Target[] = []; const userTargets: Omit[] = []; - Object.entries(this.data.targetsByVariation).forEach( + Object.entries(this._data.targetsByVariation).forEach( ([variation, contextTargetsForVariation]) => { Object.entries(contextTargetsForVariation).forEach(([contextKind, values]) => { const numberVariation = parseInt(variation, 10); @@ -432,8 +432,8 @@ export default class TestDataFlagBuilder { baseFlagObject.contextTargets = contextTargets; } - if (this.data.rules) { - baseFlagObject.rules = this.data.rules.map((rule, i) => + if (this._data.rules) { + baseFlagObject.rules = this._data.rules.map((rule, i) => (rule as TestDataRuleBuilder).build(String(i)), ); } @@ -445,13 +445,13 @@ export default class TestDataFlagBuilder { * @internal */ clone(): TestDataFlagBuilder { - return new TestDataFlagBuilder(this.key, this.data); + return new TestDataFlagBuilder(this._key, this._data); } /** * @internal */ getKey(): string { - return this.key; + return this._key; } } diff --git a/packages/shared/sdk-server/src/integrations/test_data/TestDataRuleBuilder.ts b/packages/shared/sdk-server/src/integrations/test_data/TestDataRuleBuilder.ts index a134231de..000fb2fd0 100644 --- a/packages/shared/sdk-server/src/integrations/test_data/TestDataRuleBuilder.ts +++ b/packages/shared/sdk-server/src/integrations/test_data/TestDataRuleBuilder.ts @@ -18,15 +18,15 @@ import { variationForBoolean } from './booleanVariation'; */ export default class TestDataRuleBuilder { - private clauses: Clause[] = []; + private _clauses: Clause[] = []; - private variation?: number; + private _variation?: number; /** * @internal */ constructor( - private readonly flagBuilder: BuilderType & { + private readonly _flagBuilder: BuilderType & { addRule: (rule: TestDataRuleBuilder) => void; booleanFlag: () => BuilderType; }, @@ -34,10 +34,10 @@ export default class TestDataRuleBuilder { variation?: number, ) { if (clauses) { - this.clauses = [...clauses]; + this._clauses = [...clauses]; } if (variation !== undefined) { - this.variation = variation; + this._variation = variation; } } @@ -62,7 +62,7 @@ export default class TestDataRuleBuilder { attribute: string, ...values: any ): TestDataRuleBuilder { - this.clauses.push({ + this._clauses.push({ contextKind, attribute, attributeReference: new AttributeReference(attribute), @@ -94,7 +94,7 @@ export default class TestDataRuleBuilder { attribute: string, ...values: any ): TestDataRuleBuilder { - this.clauses.push({ + this._clauses.push({ contextKind, attribute, attributeReference: new AttributeReference(attribute), @@ -121,13 +121,13 @@ export default class TestDataRuleBuilder { */ thenReturn(variation: number | boolean): BuilderType { if (TypeValidators.Boolean.is(variation)) { - this.flagBuilder.booleanFlag(); + this._flagBuilder.booleanFlag(); return this.thenReturn(variationForBoolean(variation)); } - this.variation = variation; - this.flagBuilder.addRule(this); - return this.flagBuilder; + this._variation = variation; + this._flagBuilder.addRule(this); + return this._flagBuilder; } /** @@ -136,8 +136,8 @@ export default class TestDataRuleBuilder { build(id: string) { return { id: `rule${id}`, - variation: this.variation, - clauses: this.clauses, + variation: this._variation, + clauses: this._clauses, }; } @@ -145,6 +145,6 @@ export default class TestDataRuleBuilder { * @internal */ clone(): TestDataRuleBuilder { - return new TestDataRuleBuilder(this.flagBuilder, this.clauses, this.variation); + return new TestDataRuleBuilder(this._flagBuilder, this._clauses, this._variation); } } diff --git a/packages/shared/sdk-server/src/integrations/test_data/TestDataSource.ts b/packages/shared/sdk-server/src/integrations/test_data/TestDataSource.ts index a4984ef07..152d595e7 100644 --- a/packages/shared/sdk-server/src/integrations/test_data/TestDataSource.ts +++ b/packages/shared/sdk-server/src/integrations/test_data/TestDataSource.ts @@ -9,30 +9,30 @@ import AsyncStoreFacade from '../../store/AsyncStoreFacade'; * @internal */ export default class TestDataSource implements subsystem.LDStreamProcessor { - private readonly flags: Record; - private readonly segments: Record; + private readonly _flags: Record; + private readonly _segments: Record; constructor( - private readonly featureStore: AsyncStoreFacade, + private readonly _featureStore: AsyncStoreFacade, initialFlags: Record, initialSegments: Record, - private readonly onStop: (tfs: TestDataSource) => void, - private readonly listeners: Map, + private readonly _onStop: (tfs: TestDataSource) => void, + private readonly _listeners: Map, ) { // make copies of these objects to decouple them from the originals // so updates made to the originals don't affect these internal data. - this.flags = { ...initialFlags }; - this.segments = { ...initialSegments }; + this._flags = { ...initialFlags }; + this._segments = { ...initialSegments }; } async start() { - this.listeners.forEach(({ processJson }) => { - const dataJson = { data: { flags: this.flags, segments: this.segments } }; + this._listeners.forEach(({ processJson }) => { + const dataJson = { data: { flags: this._flags, segments: this._segments } }; processJson(dataJson); }); } stop() { - this.onStop(this); + this._onStop(this); } close() { @@ -40,6 +40,6 @@ export default class TestDataSource implements subsystem.LDStreamProcessor { } async upsert(kind: DataKind, value: LDKeyedFeatureStoreItem) { - return this.featureStore.upsert(kind, value); + return this._featureStore.upsert(kind, value); } } diff --git a/packages/shared/sdk-server/src/store/AsyncStoreFacade.ts b/packages/shared/sdk-server/src/store/AsyncStoreFacade.ts index 358a402b7..5d24d8199 100644 --- a/packages/shared/sdk-server/src/store/AsyncStoreFacade.ts +++ b/packages/shared/sdk-server/src/store/AsyncStoreFacade.ts @@ -15,49 +15,49 @@ import promisify from '../async/promisify'; * */ export default class AsyncStoreFacade { - private store: LDFeatureStore; + private _store: LDFeatureStore; constructor(store: LDFeatureStore) { - this.store = store; + this._store = store; } async get(kind: DataKind, key: string): Promise { return promisify((cb) => { - this.store.get(kind, key, cb); + this._store.get(kind, key, cb); }); } async all(kind: DataKind): Promise { return promisify((cb) => { - this.store.all(kind, cb); + this._store.all(kind, cb); }); } async init(allData: LDFeatureStoreDataStorage): Promise { return promisify((cb) => { - this.store.init(allData, cb); + this._store.init(allData, cb); }); } async delete(kind: DataKind, key: string, version: number): Promise { return promisify((cb) => { - this.store.delete(kind, key, version, cb); + this._store.delete(kind, key, version, cb); }); } async upsert(kind: DataKind, data: LDKeyedFeatureStoreItem): Promise { return promisify((cb) => { - this.store.upsert(kind, data, cb); + this._store.upsert(kind, data, cb); }); } async initialized(): Promise { return promisify((cb) => { - this.store.initialized(cb); + this._store.initialized(cb); }); } close(): void { - this.store.close(); + this._store.close(); } } diff --git a/packages/shared/sdk-server/src/store/InMemoryFeatureStore.ts b/packages/shared/sdk-server/src/store/InMemoryFeatureStore.ts index 6464a87cb..61814f2aa 100644 --- a/packages/shared/sdk-server/src/store/InMemoryFeatureStore.ts +++ b/packages/shared/sdk-server/src/store/InMemoryFeatureStore.ts @@ -8,15 +8,15 @@ import { } from '../api/subsystems'; export default class InMemoryFeatureStore implements LDFeatureStore { - private allData: LDFeatureStoreDataStorage = {}; + private _allData: LDFeatureStoreDataStorage = {}; - private initCalled = false; + private _initCalled = false; - private addItem(kind: DataKind, key: string, item: LDFeatureStoreItem) { - let items = this.allData[kind.namespace]; + private _addItem(kind: DataKind, key: string, item: LDFeatureStoreItem) { + let items = this._allData[kind.namespace]; if (!items) { items = {}; - this.allData[kind.namespace] = items; + this._allData[kind.namespace] = items; } if (Object.hasOwnProperty.call(items, key)) { const old = items[key]; @@ -29,7 +29,7 @@ export default class InMemoryFeatureStore implements LDFeatureStore { } get(kind: DataKind, key: string, callback: (res: LDFeatureStoreItem | null) => void): void { - const items = this.allData[kind.namespace]; + const items = this._allData[kind.namespace]; if (items) { if (Object.prototype.hasOwnProperty.call(items, key)) { const item = items[key]; @@ -43,7 +43,7 @@ export default class InMemoryFeatureStore implements LDFeatureStore { all(kind: DataKind, callback: (res: LDFeatureStoreKindData) => void): void { const result: LDFeatureStoreKindData = {}; - const items = this.allData[kind.namespace] ?? {}; + const items = this._allData[kind.namespace] ?? {}; Object.entries(items).forEach(([key, item]) => { if (item && !item.deleted) { result[key] = item; @@ -53,24 +53,24 @@ export default class InMemoryFeatureStore implements LDFeatureStore { } init(allData: LDFeatureStoreDataStorage, callback: () => void): void { - this.initCalled = true; - this.allData = allData as LDFeatureStoreDataStorage; + this._initCalled = true; + this._allData = allData as LDFeatureStoreDataStorage; callback?.(); } delete(kind: DataKind, key: string, version: number, callback: () => void): void { const deletedItem = { version, deleted: true }; - this.addItem(kind, key, deletedItem); + this._addItem(kind, key, deletedItem); callback?.(); } upsert(kind: DataKind, data: LDKeyedFeatureStoreItem, callback: () => void): void { - this.addItem(kind, data.key, data); + this._addItem(kind, data.key, data); callback?.(); } initialized(callback: (isInitialized: boolean) => void): void { - return callback?.(this.initCalled); + return callback?.(this._initCalled); } /* eslint-disable class-methods-use-this */ diff --git a/packages/shared/sdk-server/src/store/PersistentDataStoreWrapper.ts b/packages/shared/sdk-server/src/store/PersistentDataStoreWrapper.ts index 63b21c80f..c682e4234 100644 --- a/packages/shared/sdk-server/src/store/PersistentDataStoreWrapper.ts +++ b/packages/shared/sdk-server/src/store/PersistentDataStoreWrapper.ts @@ -88,33 +88,33 @@ function deserialize( * create new database integrations by implementing only the database-specific logic. */ export default class PersistentDataStoreWrapper implements LDFeatureStore { - private isInitialized = false; + private _isInitialized = false; /** * Cache for storing individual items. */ - private itemCache: TtlCache | undefined; + private _itemCache: TtlCache | undefined; /** * Cache for storing all items of a type. */ - private allItemsCache: TtlCache | undefined; + private _allItemsCache: TtlCache | undefined; /** * Used to preserve order of operations of async requests. */ - private queue: UpdateQueue = new UpdateQueue(); + private _queue: UpdateQueue = new UpdateQueue(); constructor( - private readonly core: PersistentDataStore, + private readonly _core: PersistentDataStore, ttl: number, ) { if (ttl) { - this.itemCache = new TtlCache({ + this._itemCache = new TtlCache({ ttl, checkInterval: defaultCheckInterval, }); - this.allItemsCache = new TtlCache({ + this._allItemsCache = new TtlCache({ ttl, checkInterval: defaultCheckInterval, }); @@ -122,17 +122,17 @@ export default class PersistentDataStoreWrapper implements LDFeatureStore { } init(allData: LDFeatureStoreDataStorage, callback: () => void): void { - this.queue.enqueue((cb) => { + this._queue.enqueue((cb) => { const afterStoreInit = () => { - this.isInitialized = true; - if (this.itemCache) { - this.itemCache.clear(); - this.allItemsCache!.clear(); + this._isInitialized = true; + if (this._itemCache) { + this._itemCache.clear(); + this._allItemsCache!.clear(); Object.keys(allData).forEach((kindNamespace) => { const kind = persistentStoreKinds[kindNamespace]; const items = allData[kindNamespace]; - this.allItemsCache!.set(allForKindCacheKey(kind), items); + this._allItemsCache!.set(allForKindCacheKey(kind), items); Object.keys(items).forEach((key) => { const itemForKey = items[key]; @@ -140,20 +140,20 @@ export default class PersistentDataStoreWrapper implements LDFeatureStore { version: itemForKey.version, item: itemForKey, }; - this.itemCache!.set(cacheKey(kind, key), itemDescriptor); + this._itemCache!.set(cacheKey(kind, key), itemDescriptor); }); }); } cb(); }; - this.core.init(sortDataSet(allData), afterStoreInit); + this._core.init(sortDataSet(allData), afterStoreInit); }, callback); } get(kind: DataKind, key: string, callback: (res: LDFeatureStoreItem | null) => void): void { - if (this.itemCache) { - const item = this.itemCache.get(cacheKey(kind, key)); + if (this._itemCache) { + const item = this._itemCache.get(cacheKey(kind, key)); if (item) { callback(itemIfNotDeleted(item)); return; @@ -161,10 +161,10 @@ export default class PersistentDataStoreWrapper implements LDFeatureStore { } const persistKind = persistentStoreKinds[kind.namespace]; - this.core.get(persistKind, key, (descriptor) => { + this._core.get(persistKind, key, (descriptor) => { if (descriptor && descriptor.serializedItem) { const value = deserialize(persistKind, descriptor); - this.itemCache?.set(cacheKey(kind, key), value); + this._itemCache?.set(cacheKey(kind, key), value); callback(itemIfNotDeleted(value)); return; } @@ -173,30 +173,30 @@ export default class PersistentDataStoreWrapper implements LDFeatureStore { } initialized(callback: (isInitialized: boolean) => void): void { - if (this.isInitialized) { + if (this._isInitialized) { callback(true); - } else if (this.itemCache?.get(initializationCheckedKey)) { + } else if (this._itemCache?.get(initializationCheckedKey)) { callback(false); } else { - this.core.initialized((storeInitialized) => { - this.isInitialized = storeInitialized; - if (!this.isInitialized) { - this.itemCache?.set(initializationCheckedKey, true); + this._core.initialized((storeInitialized) => { + this._isInitialized = storeInitialized; + if (!this._isInitialized) { + this._itemCache?.set(initializationCheckedKey, true); } - callback(this.isInitialized); + callback(this._isInitialized); }); } } all(kind: DataKind, callback: (res: LDFeatureStoreKindData) => void): void { - const items = this.allItemsCache?.get(allForKindCacheKey(kind)); + const items = this._allItemsCache?.get(allForKindCacheKey(kind)); if (items) { callback(items); return; } const persistKind = persistentStoreKinds[kind.namespace]; - this.core.getAll(persistKind, (storeItems) => { + this._core.getAll(persistKind, (storeItems) => { if (!storeItems) { callback({}); return; @@ -211,20 +211,20 @@ export default class PersistentDataStoreWrapper implements LDFeatureStore { } }); - this.allItemsCache?.set(allForKindCacheKey(kind), filteredItems); + this._allItemsCache?.set(allForKindCacheKey(kind), filteredItems); callback(filteredItems); }); } upsert(kind: DataKind, data: LDKeyedFeatureStoreItem, callback: () => void): void { - this.queue.enqueue((cb) => { + this._queue.enqueue((cb) => { // Clear the caches which contain all the values of a specific kind. - if (this.allItemsCache) { - this.allItemsCache.clear(); + if (this._allItemsCache) { + this._allItemsCache.clear(); } const persistKind = persistentStoreKinds[kind.namespace]; - this.core.upsert( + this._core.upsert( persistKind, data.key, persistKind.serialize(data), @@ -232,10 +232,10 @@ export default class PersistentDataStoreWrapper implements LDFeatureStore { if (!err && updatedDescriptor) { if (updatedDescriptor.serializedItem) { const value = deserialize(persistKind, updatedDescriptor); - this.itemCache?.set(cacheKey(kind, data.key), value); + this._itemCache?.set(cacheKey(kind, data.key), value); } else if (updatedDescriptor.deleted) { // Deleted and there was not a serialized representation. - this.itemCache?.set(data.key, { + this._itemCache?.set(data.key, { key: data.key, version: updatedDescriptor.version, deleted: true, @@ -253,12 +253,12 @@ export default class PersistentDataStoreWrapper implements LDFeatureStore { } close(): void { - this.itemCache?.close(); - this.allItemsCache?.close(); - this.core.close(); + this._itemCache?.close(); + this._allItemsCache?.close(); + this._core.close(); } getDescription(): string { - return this.core.getDescription(); + return this._core.getDescription(); } } diff --git a/packages/shared/sdk-server/src/store/UpdateQueue.ts b/packages/shared/sdk-server/src/store/UpdateQueue.ts index baeda25e4..02d69a888 100644 --- a/packages/shared/sdk-server/src/store/UpdateQueue.ts +++ b/packages/shared/sdk-server/src/store/UpdateQueue.ts @@ -2,11 +2,11 @@ type CallbackFunction = () => void; type UpdateFunction = (cb: CallbackFunction) => void; export default class UpdateQueue { - private queue: [UpdateFunction, CallbackFunction][] = []; + private _queue: [UpdateFunction, CallbackFunction][] = []; enqueue(updateFn: UpdateFunction, cb: CallbackFunction) { - this.queue.push([updateFn, cb]); - if (this.queue.length === 1) { + this._queue.push([updateFn, cb]); + if (this._queue.length === 1) { // If this is the only item in the queue, then there is not a series // of updates already in progress. So we can start executing those updates. this.executePendingUpdates(); @@ -14,15 +14,15 @@ export default class UpdateQueue { } executePendingUpdates() { - if (this.queue.length > 0) { - const [fn, cb] = this.queue[0]; + if (this._queue.length > 0) { + const [fn, cb] = this._queue[0]; const newCb = () => { // We just completed work, so remove it from the queue. // Don't remove it before the work is done, because then the // count could hit 0, and overlapping execution chains could be started. - this.queue.shift(); + this._queue.shift(); // There is more work to do, so schedule an update. - if (this.queue.length > 0) { + if (this._queue.length > 0) { setTimeout(() => this.executePendingUpdates(), 0); } // Call the original callback. diff --git a/packages/shared/sdk-server/src/store/serialization.ts b/packages/shared/sdk-server/src/store/serialization.ts index c6f34d3f2..44bdf7edd 100644 --- a/packages/shared/sdk-server/src/store/serialization.ts +++ b/packages/shared/sdk-server/src/store/serialization.ts @@ -168,7 +168,7 @@ export function processFlag(flag: Flag) { // So use the contextKind to indicate if this is new or old data. clause.attributeReference = new AttributeReference(clause.attribute, !clause.contextKind); } else if (clause) { - clause.attributeReference = AttributeReference.invalidReference; + clause.attributeReference = AttributeReference.InvalidReference; } }); }); @@ -223,7 +223,7 @@ export function processSegment(segment: Segment) { // So use the contextKind to indicate if this is new or old data. clause.attributeReference = new AttributeReference(clause.attribute, !clause.contextKind); } else if (clause) { - clause.attributeReference = AttributeReference.invalidReference; + clause.attributeReference = AttributeReference.InvalidReference; } }); }); diff --git a/packages/store/node-server-sdk-dynamodb/__tests__/DynamoDBCore.test.ts b/packages/store/node-server-sdk-dynamodb/__tests__/DynamoDBCore.test.ts index e6a7f8c01..faf0b47a4 100644 --- a/packages/store/node-server-sdk-dynamodb/__tests__/DynamoDBCore.test.ts +++ b/packages/store/node-server-sdk-dynamodb/__tests__/DynamoDBCore.test.ts @@ -38,23 +38,23 @@ type UpsertResult = { }; class AsyncCoreFacade { - constructor(private readonly core: DynamoDBCore) {} + constructor(private readonly _core: DynamoDBCore) {} init(allData: interfaces.KindKeyedStore): Promise { - return promisify((cb) => this.core.init(allData, cb)); + return promisify((cb) => this._core.init(allData, cb)); } get( kind: interfaces.PersistentStoreDataKind, key: string, ): Promise { - return promisify((cb) => this.core.get(kind, key, cb)); + return promisify((cb) => this._core.get(kind, key, cb)); } getAll( kind: interfaces.PersistentStoreDataKind, ): Promise[] | undefined> { - return promisify((cb) => this.core.getAll(kind, cb)); + return promisify((cb) => this._core.getAll(kind, cb)); } upsert( @@ -63,22 +63,22 @@ class AsyncCoreFacade { descriptor: interfaces.SerializedItemDescriptor, ): Promise { return new Promise((resolve) => { - this.core.upsert(kind, key, descriptor, (err, updatedDescriptor) => { + this._core.upsert(kind, key, descriptor, (err, updatedDescriptor) => { resolve({ err, updatedDescriptor }); }); }); } initialized(): Promise { - return promisify((cb) => this.core.initialized(cb)); + return promisify((cb) => this._core.initialized(cb)); } close(): void { - this.core.close(); + this._core.close(); } getDescription(): string { - return this.core.getDescription(); + return this._core.getDescription(); } } diff --git a/packages/store/node-server-sdk-dynamodb/src/DynamoDBBigSegmentStore.ts b/packages/store/node-server-sdk-dynamodb/src/DynamoDBBigSegmentStore.ts index e7ed6614b..a984562e1 100644 --- a/packages/store/node-server-sdk-dynamodb/src/DynamoDBBigSegmentStore.ts +++ b/packages/store/node-server-sdk-dynamodb/src/DynamoDBBigSegmentStore.ts @@ -35,21 +35,21 @@ export const ATTR_INCLUDED = 'included'; export const ATTR_EXCLUDED = 'excluded'; export default class DynamoDBBigSegmentStore implements interfaces.BigSegmentStore { - private state: DynamoDBClientState; + private _state: DynamoDBClientState; // Logger is not currently used, but is included to reduce the chance of a // compatibility break to add a log. constructor( - private readonly tableName: string, + private readonly _tableName: string, options?: LDDynamoDBOptions, - private readonly logger?: LDLogger, + _logger?: LDLogger, ) { - this.state = new DynamoDBClientState(options); + this._state = new DynamoDBClientState(options); } async getMetadata(): Promise { - const key = this.state.prefixedKey(KEY_METADATA); - const data = await this.state.get(this.tableName, { + const key = this._state.prefixedKey(KEY_METADATA); + const data = await this._state.get(this._tableName, { namespace: stringValue(key), key: stringValue(key), }); @@ -65,8 +65,8 @@ export default class DynamoDBBigSegmentStore implements interfaces.BigSegmentSto async getUserMembership( userHash: string, ): Promise { - const data = await this.state.get(this.tableName, { - namespace: stringValue(this.state.prefixedKey(KEY_USER_DATA)), + const data = await this._state.get(this._tableName, { + namespace: stringValue(this._state.prefixedKey(KEY_USER_DATA)), key: stringValue(userHash), }); if (data) { @@ -87,6 +87,6 @@ export default class DynamoDBBigSegmentStore implements interfaces.BigSegmentSto } close(): void { - this.state.close(); + this._state.close(); } } diff --git a/packages/store/node-server-sdk-dynamodb/src/DynamoDBClientState.ts b/packages/store/node-server-sdk-dynamodb/src/DynamoDBClientState.ts index 47d0f5a3c..ed96866b9 100644 --- a/packages/store/node-server-sdk-dynamodb/src/DynamoDBClientState.ts +++ b/packages/store/node-server-sdk-dynamodb/src/DynamoDBClientState.ts @@ -30,25 +30,25 @@ const WRITE_BATCH_SIZE = 25; */ export default class DynamoDBClientState { // This will include the ':' if a prefix is set. - private prefix: string; + private _prefix: string; - private client: DynamoDBClient; + private _client: DynamoDBClient; - private owned: boolean; + private _owned: boolean; constructor(options?: LDDynamoDBOptions) { - this.prefix = options?.prefix ? `${options!.prefix}:` : DEFAULT_PREFIX; + this._prefix = options?.prefix ? `${options!.prefix}:` : DEFAULT_PREFIX; // We track if we own the client so that we can destroy clients that we own. if (options?.dynamoDBClient) { - this.client = options.dynamoDBClient; - this.owned = false; + this._client = options.dynamoDBClient; + this._owned = false; } else if (options?.clientOptions) { - this.client = new DynamoDBClient(options.clientOptions); - this.owned = true; + this._client = new DynamoDBClient(options.clientOptions); + this._owned = true; } else { - this.client = new DynamoDBClient({}); - this.owned = true; + this._client = new DynamoDBClient({}); + this._owned = true; } } @@ -58,14 +58,14 @@ export default class DynamoDBClientState { * @returns The prefixed key. */ prefixedKey(key: string): string { - return `${this.prefix}${key}`; + return `${this._prefix}${key}`; } async query(params: QueryCommandInput): Promise[]> { const records: Record[] = []; // Using a generator here is a substantial ergonomic improvement. // eslint-disable-next-line no-restricted-syntax - for await (const page of paginateQuery({ client: this.client }, params)) { + for await (const page of paginateQuery({ client: this._client }, params)) { if (page.Items) { records.push(...page.Items); } @@ -83,7 +83,7 @@ export default class DynamoDBClientState { // Execute all the batches and wait for them to complete. await Promise.all( batches.map((batch) => - this.client.send( + this._client.send( new BatchWriteItemCommand({ RequestItems: { [table]: batch }, }), @@ -96,7 +96,7 @@ export default class DynamoDBClientState { table: string, key: Record, ): Promise | undefined> { - const res = await this.client.send( + const res = await this._client.send( new GetItemCommand({ TableName: table, Key: key, @@ -108,7 +108,7 @@ export default class DynamoDBClientState { async put(params: PutItemCommandInput): Promise { try { - await this.client.send(new PutItemCommand(params)); + await this._client.send(new PutItemCommand(params)); } catch (err) { // If we couldn't upsert because of the version, then that is fine. // Otherwise we return failure. @@ -119,8 +119,8 @@ export default class DynamoDBClientState { } close() { - if (this.owned) { - this.client.destroy(); + if (this._owned) { + this._client.destroy(); } } } diff --git a/packages/store/node-server-sdk-dynamodb/src/DynamoDBCore.ts b/packages/store/node-server-sdk-dynamodb/src/DynamoDBCore.ts index c4983e94c..e2fbf89e7 100644 --- a/packages/store/node-server-sdk-dynamodb/src/DynamoDBCore.ts +++ b/packages/store/node-server-sdk-dynamodb/src/DynamoDBCore.ts @@ -62,13 +62,13 @@ export function calculateSize(item: Record, logger?: LDL */ export default class DynamoDBCore implements interfaces.PersistentDataStore { constructor( - private readonly tableName: string, - private readonly state: DynamoDBClientState, - private readonly logger?: LDLogger, + private readonly _tableName: string, + private readonly _state: DynamoDBClientState, + private readonly _logger?: LDLogger, ) {} - private initializedToken() { - const prefixed = stringValue(this.state.prefixedKey('$inited')); + private _initializedToken() { + const prefixed = stringValue(this._state.prefixedKey('$inited')); return { namespace: prefixed, key: prefixed }; } @@ -77,12 +77,12 @@ export default class DynamoDBCore implements interfaces.PersistentDataStore { * @param allData A set of init data. * @returns A list of all data with matching namespaces. */ - private async readExistingItems( + private async _readExistingItems( allData: interfaces.KindKeyedStore, ) { const promises = allData.map((kind) => { const { namespace } = kind.key; - return this.state.query(this.queryParamsForNamespace(namespace)); + return this._state.query(this._queryParamsForNamespace(namespace)); }); const records = (await Promise.all(promises)).flat(); @@ -95,12 +95,12 @@ export default class DynamoDBCore implements interfaces.PersistentDataStore { * @param item The item to marshal. * @returns The marshalled data. */ - private marshalItem( + private _marshalItem( kind: interfaces.PersistentStoreDataKind, item: interfaces.KeyedItem, ): Record { const dbItem: Record = { - namespace: stringValue(this.state.prefixedKey(kind.namespace)), + namespace: stringValue(this._state.prefixedKey(kind.namespace)), key: stringValue(item.key), version: numberValue(item.item.version), }; @@ -110,7 +110,7 @@ export default class DynamoDBCore implements interfaces.PersistentDataStore { return dbItem; } - private unmarshalItem( + private _unmarshalItem( dbItem: Record, ): interfaces.SerializedItemDescriptor { return { @@ -126,7 +126,7 @@ export default class DynamoDBCore implements interfaces.PersistentDataStore { allData: interfaces.KindKeyedStore, callback: () => void, ) { - const items = await this.readExistingItems(allData); + const items = await this._readExistingItems(allData); // Make a key from an existing DB item. function makeNamespaceKey(item: Record) { @@ -137,17 +137,17 @@ export default class DynamoDBCore implements interfaces.PersistentDataStore { items.forEach((item) => { existingNamespaceKeys[makeNamespaceKey(item)] = true; }); - delete existingNamespaceKeys[makeNamespaceKey(this.initializedToken())]; + delete existingNamespaceKeys[makeNamespaceKey(this._initializedToken())]; // Generate a list of write operations, and then execute them in a batch. const ops: WriteRequest[] = []; allData.forEach((collection) => { collection.item.forEach((item) => { - const dbItem = this.marshalItem(collection.key, item); - if (this.checkSizeLimit(dbItem)) { + const dbItem = this._marshalItem(collection.key, item); + if (this._checkSizeLimit(dbItem)) { delete existingNamespaceKeys[ - `${this.state.prefixedKey(collection.key.namespace)}$${item.key}` + `${this._state.prefixedKey(collection.key.namespace)}$${item.key}` ]; ops.push({ PutRequest: { Item: dbItem } }); } @@ -165,9 +165,9 @@ export default class DynamoDBCore implements interfaces.PersistentDataStore { }); // Always write the initialized token when we initialize. - ops.push({ PutRequest: { Item: this.initializedToken() } }); + ops.push({ PutRequest: { Item: this._initializedToken() } }); - await this.state.batchWrite(this.tableName, ops); + await this._state.batchWrite(this._tableName, ops); callback(); } @@ -176,12 +176,12 @@ export default class DynamoDBCore implements interfaces.PersistentDataStore { key: string, callback: (descriptor: interfaces.SerializedItemDescriptor | undefined) => void, ) { - const read = await this.state.get(this.tableName, { - namespace: stringValue(this.state.prefixedKey(kind.namespace)), + const read = await this._state.get(this._tableName, { + namespace: stringValue(this._state.prefixedKey(kind.namespace)), key: stringValue(key), }); if (read) { - callback(this.unmarshalItem(read)); + callback(this._unmarshalItem(read)); } else { callback(undefined); } @@ -193,9 +193,11 @@ export default class DynamoDBCore implements interfaces.PersistentDataStore { descriptors: interfaces.KeyedItem[] | undefined, ) => void, ) { - const params = this.queryParamsForNamespace(kind.namespace); - const results = await this.state.query(params); - callback(results.map((record) => ({ key: record!.key!.S!, item: this.unmarshalItem(record) }))); + const params = this._queryParamsForNamespace(kind.namespace); + const results = await this._state.query(params); + callback( + results.map((record) => ({ key: record!.key!.S!, item: this._unmarshalItem(record) })), + ); } async upsert( @@ -207,8 +209,8 @@ export default class DynamoDBCore implements interfaces.PersistentDataStore { updatedDescriptor?: interfaces.SerializedItemDescriptor | undefined, ) => void, ) { - const params = this.makeVersionedPutRequest(kind, { key, item: descriptor }); - if (!this.checkSizeLimit(params.Item)) { + const params = this._makeVersionedPutRequest(kind, { key, item: descriptor }); + if (!this._checkSizeLimit(params.Item)) { // We deliberately don't report this back to the SDK as an error, because we don't want to trigger any // useless retry behavior. We just won't do the update. callback(); @@ -216,7 +218,7 @@ export default class DynamoDBCore implements interfaces.PersistentDataStore { } try { - await this.state.put(params); + await this._state.put(params); this.get(kind, key, (readDescriptor) => { callback(undefined, readDescriptor); }); @@ -228,11 +230,11 @@ export default class DynamoDBCore implements interfaces.PersistentDataStore { async initialized(callback: (isInitialized: boolean) => void) { let initialized = false; try { - const token = this.initializedToken(); - const data = await this.state.get(this.tableName, token); + const token = this._initializedToken(); + const data = await this._state.get(this._tableName, token); initialized = !!(data?.key?.S === token.key.S); } catch (err) { - this.logger?.error(`Error reading inited: ${err}`); + this._logger?.error(`Error reading inited: ${err}`); initialized = false; } // Callback outside the try. In case it raised an exception. @@ -240,44 +242,44 @@ export default class DynamoDBCore implements interfaces.PersistentDataStore { } close(): void { - this.state.close(); + this._state.close(); } getDescription(): string { return 'DynamoDB'; } - private queryParamsForNamespace(namespace: string): QueryCommandInput { + private _queryParamsForNamespace(namespace: string): QueryCommandInput { return { - TableName: this.tableName, + TableName: this._tableName, KeyConditionExpression: 'namespace = :namespace', FilterExpression: 'attribute_not_exists(deleted) OR deleted = :deleted', ExpressionAttributeValues: { - ':namespace': stringValue(this.state.prefixedKey(namespace)), + ':namespace': stringValue(this._state.prefixedKey(namespace)), ':deleted': boolValue(false), }, }; } - private makeVersionedPutRequest( + private _makeVersionedPutRequest( kind: interfaces.PersistentStoreDataKind, item: interfaces.KeyedItem, ) { return { - TableName: this.tableName, - Item: this.marshalItem(kind, item), + TableName: this._tableName, + Item: this._marshalItem(kind, item), ConditionExpression: 'attribute_not_exists(version) OR version < :new_version', ExpressionAttributeValues: { ':new_version': numberValue(item.item.version) }, }; } - private checkSizeLimit(item: Record) { + private _checkSizeLimit(item: Record) { const size = calculateSize(item); if (size <= DYNAMODB_MAX_SIZE) { return true; } - this.logger?.error( + this._logger?.error( `The item "${item.key.S}" in "${item.namespace.S}" was too large to store in DynamoDB and was dropped`, ); return false; diff --git a/packages/store/node-server-sdk-dynamodb/src/DynamoDBFeatureStore.ts b/packages/store/node-server-sdk-dynamodb/src/DynamoDBFeatureStore.ts index f41ea37c9..100d7b92d 100644 --- a/packages/store/node-server-sdk-dynamodb/src/DynamoDBFeatureStore.ts +++ b/packages/store/node-server-sdk-dynamodb/src/DynamoDBFeatureStore.ts @@ -18,10 +18,10 @@ import TtlFromOptions from './TtlFromOptions'; * Integration between the LaunchDarkly SDK and DynamoDB. */ export default class DynamoDBFeatureStore implements LDFeatureStore { - private wrapper: PersistentDataStoreWrapper; + private _wrapper: PersistentDataStoreWrapper; constructor(tableName: string, options?: LDDynamoDBOptions, logger?: LDLogger) { - this.wrapper = new PersistentDataStoreWrapper( + this._wrapper = new PersistentDataStoreWrapper( new DynamoDBCore(tableName, new DynamoDBClientState(options), logger), TtlFromOptions(options), ); @@ -32,34 +32,34 @@ export default class DynamoDBFeatureStore implements LDFeatureStore { key: string, callback: (res: LDFeatureStoreItem | null) => void, ): void { - this.wrapper.get(kind, key, callback); + this._wrapper.get(kind, key, callback); } all(kind: interfaces.DataKind, callback: (res: LDFeatureStoreKindData) => void): void { - this.wrapper.all(kind, callback); + this._wrapper.all(kind, callback); } init(allData: LDFeatureStoreDataStorage, callback: () => void): void { - this.wrapper.init(allData, callback); + this._wrapper.init(allData, callback); } delete(kind: interfaces.DataKind, key: string, version: number, callback: () => void): void { - this.wrapper.delete(kind, key, version, callback); + this._wrapper.delete(kind, key, version, callback); } upsert(kind: interfaces.DataKind, data: LDKeyedFeatureStoreItem, callback: () => void): void { - this.wrapper.upsert(kind, data, callback); + this._wrapper.upsert(kind, data, callback); } initialized(callback: (isInitialized: boolean) => void): void { - this.wrapper.initialized(callback); + this._wrapper.initialized(callback); } close(): void { - this.wrapper.close(); + this._wrapper.close(); } getDescription?(): string { - return this.wrapper.getDescription(); + return this._wrapper.getDescription(); } } diff --git a/packages/store/node-server-sdk-redis/__tests__/RedisCore.test.ts b/packages/store/node-server-sdk-redis/__tests__/RedisCore.test.ts index fffb1151d..4a8a940ba 100644 --- a/packages/store/node-server-sdk-redis/__tests__/RedisCore.test.ts +++ b/packages/store/node-server-sdk-redis/__tests__/RedisCore.test.ts @@ -26,23 +26,23 @@ type UpsertResult = { }; class AsyncCoreFacade { - constructor(private readonly core: RedisCore) {} + constructor(private readonly _core: RedisCore) {} init(allData: interfaces.KindKeyedStore): Promise { - return promisify((cb) => this.core.init(allData, cb)); + return promisify((cb) => this._core.init(allData, cb)); } get( kind: interfaces.PersistentStoreDataKind, key: string, ): Promise { - return promisify((cb) => this.core.get(kind, key, cb)); + return promisify((cb) => this._core.get(kind, key, cb)); } getAll( kind: interfaces.PersistentStoreDataKind, ): Promise[] | undefined> { - return promisify((cb) => this.core.getAll(kind, cb)); + return promisify((cb) => this._core.getAll(kind, cb)); } upsert( @@ -51,22 +51,22 @@ class AsyncCoreFacade { descriptor: interfaces.SerializedItemDescriptor, ): Promise { return new Promise((resolve) => { - this.core.upsert(kind, key, descriptor, (err, updatedDescriptor) => { + this._core.upsert(kind, key, descriptor, (err, updatedDescriptor) => { resolve({ err, updatedDescriptor }); }); }); } initialized(): Promise { - return promisify((cb) => this.core.initialized(cb)); + return promisify((cb) => this._core.initialized(cb)); } close(): void { - this.core.close(); + this._core.close(); } getDescription(): string { - return this.core.getDescription(); + return this._core.getDescription(); } } diff --git a/packages/store/node-server-sdk-redis/src/RedisBigSegmentStore.ts b/packages/store/node-server-sdk-redis/src/RedisBigSegmentStore.ts index 8934bd2ba..f6d3bf75e 100644 --- a/packages/store/node-server-sdk-redis/src/RedisBigSegmentStore.ts +++ b/packages/store/node-server-sdk-redis/src/RedisBigSegmentStore.ts @@ -19,19 +19,16 @@ export const KEY_USER_INCLUDE = 'big_segment_include'; export const KEY_USER_EXCLUDE = 'big_segment_exclude'; export default class RedisBigSegmentStore implements interfaces.BigSegmentStore { - private state: RedisClientState; + private _state: RedisClientState; // Logger is not currently used, but is included to reduce the chance of a // compatibility break to add a log. - constructor( - options?: LDRedisOptions, - private readonly logger?: LDLogger, - ) { - this.state = new RedisClientState(options); + constructor(options?: LDRedisOptions, _logger?: LDLogger) { + this._state = new RedisClientState(options); } async getMetadata(): Promise { - const value = await this.state.getClient().get(this.state.prefixedKey(KEY_LAST_SYNCHRONIZED)); + const value = await this._state.getClient().get(this._state.prefixedKey(KEY_LAST_SYNCHRONIZED)); // Value will be true if it is a string containing any characters, which is fine // for this check. if (value) { @@ -43,12 +40,12 @@ export default class RedisBigSegmentStore implements interfaces.BigSegmentStore async getUserMembership( userHash: string, ): Promise { - const includedRefs = await this.state + const includedRefs = await this._state .getClient() - .smembers(this.state.prefixedKey(`${KEY_USER_INCLUDE}:${userHash}`)); - const excludedRefs = await this.state + .smembers(this._state.prefixedKey(`${KEY_USER_INCLUDE}:${userHash}`)); + const excludedRefs = await this._state .getClient() - .smembers(this.state.prefixedKey(`${KEY_USER_EXCLUDE}:${userHash}`)); + .smembers(this._state.prefixedKey(`${KEY_USER_EXCLUDE}:${userHash}`)); // If there are no included/excluded refs, the don't return any membership. if ((!includedRefs || !includedRefs.length) && (!excludedRefs || !excludedRefs.length)) { @@ -68,6 +65,6 @@ export default class RedisBigSegmentStore implements interfaces.BigSegmentStore } close(): void { - this.state.close(); + this._state.close(); } } diff --git a/packages/store/node-server-sdk-redis/src/RedisClientState.ts b/packages/store/node-server-sdk-redis/src/RedisClientState.ts index 34332f203..9701fd3a6 100644 --- a/packages/store/node-server-sdk-redis/src/RedisClientState.ts +++ b/packages/store/node-server-sdk-redis/src/RedisClientState.ts @@ -14,17 +14,17 @@ const DEFAULT_PREFIX = 'launchdarkly'; * @internal */ export default class RedisClientState { - private connected: boolean = false; + private _connected: boolean = false; - private attempt: number = 0; + private _attempt: number = 0; - private initialConnection: boolean = true; + private _initialConnection: boolean = true; - private readonly client: Redis; + private readonly _client: Redis; - private readonly owned: boolean; + private readonly _owned: boolean; - private readonly base_prefix: string; + private readonly _basePrefix: string; /** * Construct a state with the given client. @@ -35,52 +35,52 @@ export default class RedisClientState { */ constructor( options?: LDRedisOptions, - private readonly logger?: LDLogger, + private readonly _logger?: LDLogger, ) { if (options?.client) { - this.client = options.client; - this.owned = false; + this._client = options.client; + this._owned = false; } else if (options?.redisOpts) { - this.client = new Redis(options.redisOpts); - this.owned = true; + this._client = new Redis(options.redisOpts); + this._owned = true; } else { - this.client = new Redis(); - this.owned = true; + this._client = new Redis(); + this._owned = true; } - this.base_prefix = options?.prefix || DEFAULT_PREFIX; + this._basePrefix = options?.prefix || DEFAULT_PREFIX; // If the client is not owned, then it should already be connected. - this.connected = !this.owned; + this._connected = !this._owned; // We don't want to log a message on the first connection, only when reconnecting. - this.initialConnection = !this.connected; + this._initialConnection = !this._connected; - const { client } = this; + const { _client: client } = this; client.on('error', (err) => { - logger?.error(`Redis error - ${err}`); + _logger?.error(`Redis error - ${err}`); }); client.on('reconnecting', (delay: number) => { - this.attempt += 1; - logger?.info( - `Attempting to reconnect to redis (attempt # ${this.attempt}, delay: ${delay}ms)`, + this._attempt += 1; + _logger?.info( + `Attempting to reconnect to redis (attempt # ${this._attempt}, delay: ${delay}ms)`, ); }); client.on('connect', () => { - this.attempt = 0; + this._attempt = 0; - if (!this.initialConnection) { - this?.logger?.warn('Reconnecting to Redis'); + if (!this._initialConnection) { + this?._logger?.warn('Reconnecting to Redis'); } - this.initialConnection = false; - this.connected = true; + this._initialConnection = false; + this._connected = true; }); client.on('end', () => { - this.connected = false; + this._connected = false; }); } @@ -90,7 +90,7 @@ export default class RedisClientState { * @returns True if currently connected. */ isConnected(): boolean { - return this.connected; + return this._connected; } /** @@ -99,7 +99,7 @@ export default class RedisClientState { * @returns True if using the initial connection. */ isInitialConnection(): boolean { - return this.initialConnection; + return this._initialConnection; } /** @@ -108,17 +108,17 @@ export default class RedisClientState { * @returns The redis client. */ getClient(): Redis { - return this.client; + return this._client; } /** * If the client is owned, then this will 'quit' the client. */ close() { - if (this.owned) { - this.client.quit().catch((err) => { + if (this._owned) { + this._client.quit().catch((err) => { // Not any action that can be taken for an error on quit. - this.logger?.debug('Error closing ioredis client:', err); + this._logger?.debug('Error closing ioredis client:', err); }); } } @@ -129,6 +129,6 @@ export default class RedisClientState { * @returns The prefixed key. */ prefixedKey(key: string): string { - return `${this.base_prefix}:${key}`; + return `${this._basePrefix}:${key}`; } } diff --git a/packages/store/node-server-sdk-redis/src/RedisCore.ts b/packages/store/node-server-sdk-redis/src/RedisCore.ts index 1c3c30944..853540a18 100644 --- a/packages/store/node-server-sdk-redis/src/RedisCore.ts +++ b/packages/store/node-server-sdk-redis/src/RedisCore.ts @@ -24,25 +24,25 @@ import RedisClientState from './RedisClientState'; * @internal */ export default class RedisCore implements interfaces.PersistentDataStore { - private initedKey: string; + private _initedKey: string; constructor( - private readonly state: RedisClientState, - private readonly logger?: LDLogger, + private readonly _state: RedisClientState, + private readonly _logger?: LDLogger, ) { - this.initedKey = this.state.prefixedKey('$inited'); + this._initedKey = this._state.prefixedKey('$inited'); } init( allData: interfaces.KindKeyedStore, callback: () => void, ): void { - const multi = this.state.getClient().multi(); + const multi = this._state.getClient().multi(); allData.forEach((keyedItems) => { const kind = keyedItems.key; const items = keyedItems.item; - const namespaceKey = this.state.prefixedKey(kind.namespace); + const namespaceKey = this._state.prefixedKey(kind.namespace); // Delete the namespace for the kind. multi.del(namespaceKey); @@ -60,11 +60,11 @@ export default class RedisCore implements interfaces.PersistentDataStore { } }); - multi.set(this.initedKey, ''); + multi.set(this._initedKey, ''); multi.exec((err) => { if (err) { - this.logger?.error(`Error initializing Redis store ${err}`); + this._logger?.error(`Error initializing Redis store ${err}`); } callback(); }); @@ -75,15 +75,15 @@ export default class RedisCore implements interfaces.PersistentDataStore { key: string, callback: (descriptor: interfaces.SerializedItemDescriptor | undefined) => void, ): void { - if (!this.state.isConnected() && !this.state.isInitialConnection()) { - this.logger?.warn(`Attempted to fetch key '${key}' while Redis connection is down`); + if (!this._state.isConnected() && !this._state.isInitialConnection()) { + this._logger?.warn(`Attempted to fetch key '${key}' while Redis connection is down`); callback(undefined); return; } - this.state.getClient().hget(this.state.prefixedKey(kind.namespace), key, (err, val) => { + this._state.getClient().hget(this._state.prefixedKey(kind.namespace), key, (err, val) => { if (err) { - this.logger?.error(`Error fetching key '${key}' from Redis in '${kind.namespace}' ${err}`); + this._logger?.error(`Error fetching key '${key}' from Redis in '${kind.namespace}' ${err}`); callback(undefined); } else if (val) { // When getting we do not populate version and deleted. @@ -105,15 +105,15 @@ export default class RedisCore implements interfaces.PersistentDataStore { descriptors: interfaces.KeyedItem[] | undefined, ) => void, ): void { - if (!this.state.isConnected() && !this.state.isInitialConnection()) { - this.logger?.warn('Attempted to fetch all keys while Redis connection is down'); + if (!this._state.isConnected() && !this._state.isInitialConnection()) { + this._logger?.warn('Attempted to fetch all keys while Redis connection is down'); callback(undefined); return; } - this.state.getClient().hgetall(this.state.prefixedKey(kind.namespace), (err, values) => { + this._state.getClient().hgetall(this._state.prefixedKey(kind.namespace), (err, values) => { if (err) { - this.logger?.error(`Error fetching '${kind.namespace}' from Redis ${err}`); + this._logger?.error(`Error fetching '${kind.namespace}' from Redis ${err}`); } else if (values) { const results: interfaces.KeyedItem[] = []; Object.keys(values).forEach((key) => { @@ -140,8 +140,8 @@ export default class RedisCore implements interfaces.PersistentDataStore { ): void { // The persistent store wrapper manages interactions with a queue, so we can use watch like // this without concerns for overlapping transactions. - this.state.getClient().watch(this.state.prefixedKey(kind.namespace)); - const multi = this.state.getClient().multi(); + this._state.getClient().watch(this._state.prefixedKey(kind.namespace)); + const multi = this._state.getClient().multi(); this.get(kind, key, (old) => { if (old?.serializedItem) { @@ -163,23 +163,23 @@ export default class RedisCore implements interfaces.PersistentDataStore { } if (descriptor.deleted) { multi.hset( - this.state.prefixedKey(kind.namespace), + this._state.prefixedKey(kind.namespace), key, JSON.stringify({ version: descriptor.version, deleted: true }), ); } else if (descriptor.serializedItem) { - multi.hset(this.state.prefixedKey(kind.namespace), key, descriptor.serializedItem); + multi.hset(this._state.prefixedKey(kind.namespace), key, descriptor.serializedItem); } else { // This call violates the contract. multi.discard(); - this.logger?.error('Attempt to write a non-deleted item without data to Redis.'); + this._logger?.error('Attempt to write a non-deleted item without data to Redis.'); callback(undefined, undefined); return; } multi.exec((err, replies) => { if (!err && (replies === null || replies === undefined)) { // This means the EXEC failed because someone modified the watched key - this.logger?.debug('Concurrent modification detected, retrying'); + this._logger?.debug('Concurrent modification detected, retrying'); this.upsert(kind, key, descriptor, callback); } else { callback(err || undefined, descriptor); @@ -189,7 +189,7 @@ export default class RedisCore implements interfaces.PersistentDataStore { } initialized(callback: (isInitialized: boolean) => void): void { - this.state.getClient().exists(this.initedKey, (err, count) => { + this._state.getClient().exists(this._initedKey, (err, count) => { // Initialized if there is not an error and the key does exists. // (A count >= 1) callback(!!(!err && count)); @@ -197,7 +197,7 @@ export default class RedisCore implements interfaces.PersistentDataStore { } close(): void { - this.state.close(); + this._state.close(); } getDescription(): string { diff --git a/packages/store/node-server-sdk-redis/src/RedisFeatureStore.ts b/packages/store/node-server-sdk-redis/src/RedisFeatureStore.ts index 450328e61..c01b5d752 100644 --- a/packages/store/node-server-sdk-redis/src/RedisFeatureStore.ts +++ b/packages/store/node-server-sdk-redis/src/RedisFeatureStore.ts @@ -18,13 +18,10 @@ import TtlFromOptions from './TtlFromOptions'; * Integration between the LaunchDarkly SDK and Redis. */ export default class RedisFeatureStore implements LDFeatureStore { - private wrapper: PersistentDataStoreWrapper; + private _wrapper: PersistentDataStoreWrapper; - constructor( - options?: LDRedisOptions, - private readonly logger?: LDLogger, - ) { - this.wrapper = new PersistentDataStoreWrapper( + constructor(options?: LDRedisOptions, logger?: LDLogger) { + this._wrapper = new PersistentDataStoreWrapper( new RedisCore(new RedisClientState(options, logger), logger), TtlFromOptions(options), ); @@ -35,34 +32,34 @@ export default class RedisFeatureStore implements LDFeatureStore { key: string, callback: (res: LDFeatureStoreItem | null) => void, ): void { - this.wrapper.get(kind, key, callback); + this._wrapper.get(kind, key, callback); } all(kind: interfaces.DataKind, callback: (res: LDFeatureStoreKindData) => void): void { - this.wrapper.all(kind, callback); + this._wrapper.all(kind, callback); } init(allData: LDFeatureStoreDataStorage, callback: () => void): void { - this.wrapper.init(allData, callback); + this._wrapper.init(allData, callback); } delete(kind: interfaces.DataKind, key: string, version: number, callback: () => void): void { - this.wrapper.delete(kind, key, version, callback); + this._wrapper.delete(kind, key, version, callback); } upsert(kind: interfaces.DataKind, data: LDKeyedFeatureStoreItem, callback: () => void): void { - this.wrapper.upsert(kind, data, callback); + this._wrapper.upsert(kind, data, callback); } initialized(callback: (isInitialized: boolean) => void): void { - this.wrapper.initialized(callback); + this._wrapper.initialized(callback); } close(): void { - this.wrapper.close(); + this._wrapper.close(); } getDescription?(): string { - return this.wrapper.getDescription(); + return this._wrapper.getDescription(); } } diff --git a/packages/telemetry/node-server-sdk-otel/src/TracingHook.ts b/packages/telemetry/node-server-sdk-otel/src/TracingHook.ts index 82aade9ff..087464c5c 100644 --- a/packages/telemetry/node-server-sdk-otel/src/TracingHook.ts +++ b/packages/telemetry/node-server-sdk-otel/src/TracingHook.ts @@ -106,8 +106,8 @@ function validateOptions(options?: TracingHookOptions): ValidatedHookOptions { * (LaunchDarkly), and the key of the flag being evaluated. */ export default class TracingHook implements integrations.Hook { - private readonly options: ValidatedHookOptions; - private readonly tracer = trace.getTracer('launchdarkly-client'); + private readonly _options: ValidatedHookOptions; + private readonly _tracer = trace.getTracer('launchdarkly-client'); /** * Construct a TracingHook with the given options. @@ -115,7 +115,7 @@ export default class TracingHook implements integrations.Hook { * @param options Options to customize tracing behavior. */ constructor(options?: TracingHookOptions) { - this.options = validateOptions(options); + this._options = validateOptions(options); } /** @@ -134,10 +134,10 @@ export default class TracingHook implements integrations.Hook { hookContext: integrations.EvaluationSeriesContext, data: integrations.EvaluationSeriesData, ): integrations.EvaluationSeriesData { - if (this.options.spans) { + if (this._options.spans) { const { canonicalKey } = Context.fromLDContext(hookContext.context); - const span = this.tracer.startSpan(hookContext.method, undefined, context.active()); + const span = this._tracer.startSpan(hookContext.method, undefined, context.active()); span.setAttribute('feature_flag.context.key', canonicalKey); span.setAttribute('feature_flag.key', hookContext.flagKey); @@ -163,7 +163,7 @@ export default class TracingHook implements integrations.Hook { [FEATURE_FLAG_PROVIDER_ATTR]: 'LaunchDarkly', [FEATURE_FLAG_CONTEXT_KEY_ATTR]: Context.fromLDContext(hookContext.context).canonicalKey, }; - if (this.options.includeVariant) { + if (this._options.includeVariant) { eventAttributes[FEATURE_FLAG_VARIANT_ATTR] = JSON.stringify(detail.value); } currentTrace.addEvent(FEATURE_FLAG_SCOPE, eventAttributes);