From 3e6d404ae665c5cc7e5a1394a59c8f2c9d5d682a Mon Sep 17 00:00:00 2001 From: Ryan Lamb <4955475+kinyoklion@users.noreply.github.com> Date: Thu, 10 Oct 2024 15:37:19 -0700 Subject: [PATCH] feat: Apply private property naming standard. Mangle browser private properties. (#620) 1. Update private properties and methods to start with a leading underscore. 2. Update browser terser config to mangle properties that start with an underscore that are not `_meta`. (It would be nice to have a more elegant way to do this, but this is what we have.) 3. Add linting rules for typescript naming conventions. 5. Fix lint issues from rules. Conventions Private properties and methods start with _ and CamelCase case names. Public methods and properties use CamelCase and do not start with an underscore. Public static properties use PascalCase and do not start with an underscore. (This is negotiable, but required fewer changes.) sdk-764 --- .eslintrc.js | 32 ++ .github/workflows/release-please.yml | 1 - .../src/edgekv/edgeKVProvider.ts | 6 +- packages/sdk/browser/README.md | 1 + .../contract-tests/entity/src/ClientEntity.ts | 37 +- .../entity/src/TestHarnessWebSocket.ts | 54 +-- packages/sdk/browser/rollup.config.js | 22 +- packages/sdk/browser/src/BrowserClient.ts | 16 +- .../sdk/browser/src/BrowserDataManager.ts | 73 ++-- packages/sdk/browser/src/goals/GoalManager.ts | 54 +-- packages/sdk/browser/src/goals/GoalTracker.ts | 6 +- .../sdk/browser/src/goals/LocationWatcher.ts | 22 +- packages/sdk/browser/src/platform/Backoff.ts | 40 +- .../sdk/browser/src/platform/BrowserHasher.ts | 16 +- .../src/platform/DefaultBrowserEventSource.ts | 58 +-- .../sdk/browser/src/platform/LocalStorage.ts | 8 +- packages/sdk/cloudflare/jsr.json | 12 +- .../react-native-sse/EventSource.test.ts | 8 +- .../sdk/react-native/src/MobileDataManager.ts | 24 +- .../sdk/react-native/src/RNStateDetector.ts | 16 +- .../react-native/src/ReactNativeLDClient.ts | 8 +- .../react-native-sse/EventSource.ts | 236 ++++++------ .../src/platform/ConnectionManager.ts | 82 ++--- .../react-native/src/platform/PlatformInfo.ts | 6 +- .../src/platform/PlatformRequests.ts | 4 +- .../src/platform/PlatformStorage.ts | 6 +- .../src/platform/crypto/PlatformHasher.ts | 10 +- .../sdk/react-universal/src/ldClientRsc.ts | 18 +- .../src/BigSegmentsStoreStatusProviderNode.ts | 8 +- .../server-node/src/platform/HeaderWrapper.ts | 14 +- .../sdk/server-node/src/platform/NodeInfo.ts | 6 +- .../server-node/src/platform/NodeRequests.ts | 24 +- .../server-node/src/platform/NodeResponse.ts | 6 +- .../akamai-edgeworker-sdk/src/api/LDClient.ts | 10 +- .../featureStore/cacheableStoreProvider.ts | 8 +- .../src/featureStore/index.ts | 34 +- .../src/platform/crypto/cryptoJSHasher.ts | 8 +- .../src/platform/crypto/cryptoJSHmac.ts | 8 +- .../src/platform/info/index.ts | 12 +- .../shared/common/src/AttributeReference.ts | 26 +- packages/shared/common/src/Context.ts | 122 +++---- packages/shared/common/src/ContextFilter.ts | 23 +- .../diagnostics/DiagnosticsManager.ts | 42 +-- .../internal/evaluation/EventFactoryBase.ts | 8 +- .../src/internal/events/ClientMessages.ts | 2 +- .../src/internal/events/EventProcessor.ts | 172 ++++----- .../common/src/internal/events/EventSender.ts | 34 +- .../src/internal/events/EventSummarizer.ts | 40 +- .../src/internal/stream/StreamingProcessor.ts | 90 ++--- .../shared/common/src/logging/BasicLogger.ts | 44 +-- .../shared/common/src/logging/SafeLogger.ts | 22 +- packages/shared/common/src/logging/format.ts | 1 + .../common/src/options/ServiceEndpoints.ts | 1 + packages/shared/common/src/validators.ts | 12 +- .../__tests__/LDClientImpl.events.test.ts | 8 +- .../__tests__/LDClientImpl.variation.test.ts | 3 +- .../sdk-client/__tests__/TestDataManager.ts | 8 +- .../configuration/Configuration.test.ts | 6 +- packages/shared/sdk-client/src/DataManager.ts | 30 +- packages/shared/sdk-client/src/HookRunner.ts | 22 +- .../shared/sdk-client/src/LDClientImpl.ts | 182 ++++----- packages/shared/sdk-client/src/LDEmitter.ts | 30 +- .../src/configuration/Configuration.ts | 9 +- .../src/datasource/DataSourceEventHandler.ts | 24 +- .../src/datasource/DataSourceStatusManager.ts | 42 +-- .../src/flag-manager/FlagManager.ts | 34 +- .../src/flag-manager/FlagPersistence.ts | 90 ++--- .../sdk-client/src/flag-manager/FlagStore.ts | 12 +- .../src/flag-manager/FlagUpdater.ts | 38 +- .../src/polling/PollingProcessor.ts | 82 ++--- .../sdk-client/src/polling/Requestor.ts | 18 +- .../src/streaming/StreamingProcessor.ts | 112 +++--- .../src/api/EdgeFeatureStore.ts | 34 +- .../src/platform/crypto/cryptoJSHasher.ts | 8 +- .../src/platform/crypto/cryptoJSHmac.ts | 8 +- .../__tests__/BigSegmentsManager.test.ts | 6 +- .../LDClientImpl.bigSegments.test.ts | 6 +- .../shared/sdk-server/__tests__/Logger.ts | 44 +-- .../evaluation/Evaluator.segments.test.ts | 6 +- .../integrations/test_data/TestData.test.ts | 2 +- .../src/BigSegmentStatusProviderImpl.ts | 22 +- .../sdk-server/src/BigSegmentsManager.ts | 68 ++-- .../sdk-server/src/FlagsStateBuilder.ts | 29 +- .../shared/sdk-server/src/LDClientImpl.ts | 344 +++++++++--------- packages/shared/sdk-server/src/Migration.ts | 182 +++++---- .../sdk-server/src/MigrationOpTracker.ts | 158 ++++---- .../shared/sdk-server/src/cache/LruCache.ts | 140 +++---- .../shared/sdk-server/src/cache/TtlCache.ts | 38 +- .../src/data_sources/DataSourceUpdates.ts | 32 +- .../src/data_sources/DependencyTracker.ts | 20 +- .../src/data_sources/FileDataSource.ts | 68 ++-- .../sdk-server/src/data_sources/FileLoader.ts | 62 ++-- .../src/data_sources/NamespacedDataSet.ts | 16 +- .../src/data_sources/PollingProcessor.ts | 66 ++-- .../sdk-server/src/data_sources/Requestor.ts | 24 +- .../sdk-server/src/evaluation/Bucketer.ts | 10 +- .../sdk-server/src/evaluation/Evaluator.ts | 50 +-- .../sdk-server/src/evaluation/evalTargets.ts | 2 +- .../src/events/ContextDeduplicator.ts | 10 +- .../shared/sdk-server/src/hooks/HookRunner.ts | 42 +-- .../src/integrations/FileDataSourceFactory.ts | 10 +- .../src/integrations/test_data/TestData.ts | 40 +- .../test_data/TestDataFlagBuilder.ts | 90 ++--- .../test_data/TestDataRuleBuilder.ts | 28 +- .../integrations/test_data/TestDataSource.ts | 22 +- .../sdk-server/src/store/AsyncStoreFacade.ts | 18 +- .../src/store/InMemoryFeatureStore.ts | 24 +- .../src/store/PersistentDataStoreWrapper.ts | 78 ++-- .../sdk-server/src/store/UpdateQueue.ts | 14 +- .../sdk-server/src/store/serialization.ts | 4 +- .../__tests__/DynamoDBCore.test.ts | 16 +- .../src/DynamoDBBigSegmentStore.ts | 18 +- .../src/DynamoDBClientState.ts | 34 +- .../src/DynamoDBCore.ts | 78 ++-- .../src/DynamoDBFeatureStore.ts | 20 +- .../__tests__/RedisCore.test.ts | 16 +- .../src/RedisBigSegmentStore.ts | 21 +- .../src/RedisClientState.ts | 68 ++-- .../node-server-sdk-redis/src/RedisCore.ts | 48 +-- .../src/RedisFeatureStore.ts | 25 +- .../node-server-sdk-otel/src/TracingHook.ts | 12 +- 121 files changed, 2353 insertions(+), 2231 deletions(-) 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);