From 27f5d9190e297df35780059edd59766e39d53332 Mon Sep 17 00:00:00 2001 From: Ryan Lamb <4955475+kinyoklion@users.noreply.github.com> Date: Fri, 27 Sep 2024 09:49:16 -0700 Subject: [PATCH 1/8] feat: Add basic secure mode support for browser SDK. --- .../sdk/browser/BrowserIdentifyOptions.ts | 9 ++ .../__tests__/BrowserDataManager.test.ts | 100 ++++++++++++++++-- packages/sdk/browser/src/BrowserClient.ts | 46 +++++++- .../sdk/browser/src/BrowserDataManager.ts | 16 ++- .../polling/PollingProcessor.test.ts | 45 ++++++++ .../streaming/StreamingProcessor.test.ts | 50 +++++++++ packages/shared/sdk-client/src/DataManager.ts | 14 +++ .../src/polling/PollingProcessor.ts | 4 +- .../src/streaming/DataSourceConfig.ts | 1 + .../src/streaming/StreamingProcessor.ts | 4 +- 10 files changed, 272 insertions(+), 17 deletions(-) create mode 100644 packages/sdk/browser/BrowserIdentifyOptions.ts diff --git a/packages/sdk/browser/BrowserIdentifyOptions.ts b/packages/sdk/browser/BrowserIdentifyOptions.ts new file mode 100644 index 0000000000..9600719ef6 --- /dev/null +++ b/packages/sdk/browser/BrowserIdentifyOptions.ts @@ -0,0 +1,9 @@ +import { LDIdentifyOptions } from '@launchdarkly/js-client-sdk-common'; + +export interface BrowserIdentifyOptions extends Omit { + /** + * The signed context key if you are using [Secure Mode] + * (https://docs.launchdarkly.com/sdk/features/secure-mode#configuring-secure-mode-in-the-javascript-client-side-sdk). + */ + hash?: string; +} diff --git a/packages/sdk/browser/__tests__/BrowserDataManager.test.ts b/packages/sdk/browser/__tests__/BrowserDataManager.test.ts index fee37b29b5..5518801971 100644 --- a/packages/sdk/browser/__tests__/BrowserDataManager.test.ts +++ b/packages/sdk/browser/__tests__/BrowserDataManager.test.ts @@ -11,13 +11,13 @@ import { internal, LDEmitter, LDHeaders, - LDIdentifyOptions, LDLogger, Platform, Response, ServiceEndpoints, } from '@launchdarkly/js-client-sdk-common'; +import { BrowserIdentifyOptions } from '../BrowserIdentifyOptions'; import BrowserDataManager from '../src/BrowserDataManager'; import validateOptions, { ValidatedOptions } from '../src/options'; import BrowserEncoding from '../src/platform/BrowserEncoding'; @@ -196,7 +196,7 @@ describe('given a BrowserDataManager with mocked dependencies', () => { ); const context = Context.fromLDContext({ kind: 'user', key: 'test-user' }); - const identifyOptions: LDIdentifyOptions = { waitForNetworkResults: false }; + const identifyOptions: BrowserIdentifyOptions = {}; const identifyResolve = jest.fn(); const identifyReject = jest.fn(); @@ -205,9 +205,91 @@ describe('given a BrowserDataManager with mocked dependencies', () => { expect(platform.requests.createEventSource).toHaveBeenCalled(); }); + it('includes the secure mode hash for streaming requests', async () => { + dataManager = new BrowserDataManager( + platform, + flagManager, + 'test-credential', + config, + validateOptions({ streaming: true }, logger), + () => ({ + pathGet(encoding: Encoding, _plainContextString: string): string { + return `/msdk/evalx/contexts/${base64UrlEncode(_plainContextString, encoding)}`; + }, + pathReport(_encoding: Encoding, _plainContextString: string): string { + return `/msdk/evalx/context`; + }, + }), + () => ({ + pathGet(encoding: Encoding, _plainContextString: string): string { + return `/meval/${base64UrlEncode(_plainContextString, encoding)}`; + }, + pathReport(_encoding: Encoding, _plainContextString: string): string { + return `/meval`; + }, + }), + baseHeaders, + emitter, + diagnosticsManager, + ); + + const context = Context.fromLDContext({ kind: 'user', key: 'test-user' }); + const identifyOptions: BrowserIdentifyOptions = { hash: 'potato' }; + const identifyResolve = jest.fn(); + const identifyReject = jest.fn(); + + await dataManager.identify(identifyResolve, identifyReject, context, identifyOptions); + + expect(platform.requests.createEventSource).toHaveBeenCalledWith( + '/meval/eyJraW5kIjoidXNlciIsImtleSI6InRlc3QtdXNlciJ9?h=potato&withReasons=true', + expect.anything(), + ); + }); + + it('includes secure mode hash for initial poll request', async () => { + dataManager = new BrowserDataManager( + platform, + flagManager, + 'test-credential', + config, + validateOptions({ streaming: false }, logger), + () => ({ + pathGet(encoding: Encoding, _plainContextString: string): string { + return `/msdk/evalx/contexts/${base64UrlEncode(_plainContextString, encoding)}`; + }, + pathReport(_encoding: Encoding, _plainContextString: string): string { + return `/msdk/evalx/context`; + }, + }), + () => ({ + pathGet(encoding: Encoding, _plainContextString: string): string { + return `/meval/${base64UrlEncode(_plainContextString, encoding)}`; + }, + pathReport(_encoding: Encoding, _plainContextString: string): string { + return `/meval`; + }, + }), + baseHeaders, + emitter, + diagnosticsManager, + ); + + const context = Context.fromLDContext({ kind: 'user', key: 'test-user' }); + const identifyOptions: BrowserIdentifyOptions = { hash: 'potato' }; + const identifyResolve = jest.fn(); + const identifyReject = jest.fn(); + + await dataManager.identify(identifyResolve, identifyReject, context, identifyOptions); + + expect(platform.requests.fetch).toHaveBeenCalledWith( + '/msdk/evalx/contexts/eyJraW5kIjoidXNlciIsImtleSI6InRlc3QtdXNlciJ9?withReasons=true&h=potato', + expect.anything(), + ); + }); + it('should load cached flags and continue to poll to complete identify', async () => { const context = Context.fromLDContext({ kind: 'user', key: 'test-user' }); - const identifyOptions: LDIdentifyOptions = { waitForNetworkResults: false }; + const identifyOptions: BrowserIdentifyOptions = {}; const identifyResolve = jest.fn(); const identifyReject = jest.fn(); @@ -230,7 +312,7 @@ describe('given a BrowserDataManager with mocked dependencies', () => { it('should identify from polling when there are no cached flags', async () => { const context = Context.fromLDContext({ kind: 'user', key: 'test-user' }); - const identifyOptions: LDIdentifyOptions = { waitForNetworkResults: false }; + const identifyOptions: BrowserIdentifyOptions = {}; const identifyResolve = jest.fn(); const identifyReject = jest.fn(); @@ -253,7 +335,7 @@ describe('given a BrowserDataManager with mocked dependencies', () => { it('creates a stream when streaming is enabled after construction', async () => { const context = Context.fromLDContext({ kind: 'user', key: 'test-user' }); - const identifyOptions: LDIdentifyOptions = { waitForNetworkResults: false }; + const identifyOptions: BrowserIdentifyOptions = {}; const identifyResolve = jest.fn(); const identifyReject = jest.fn(); @@ -268,7 +350,7 @@ describe('given a BrowserDataManager with mocked dependencies', () => { it('does not re-create the stream if it already running', async () => { const context = Context.fromLDContext({ kind: 'user', key: 'test-user' }); - const identifyOptions: LDIdentifyOptions = { waitForNetworkResults: false }; + const identifyOptions: BrowserIdentifyOptions = {}; const identifyResolve = jest.fn(); const identifyReject = jest.fn(); @@ -296,7 +378,7 @@ describe('given a BrowserDataManager with mocked dependencies', () => { it('starts a stream on demand when not forced on/off', async () => { const context = Context.fromLDContext({ kind: 'user', key: 'test-user' }); - const identifyOptions: LDIdentifyOptions = { waitForNetworkResults: false }; + const identifyOptions: BrowserIdentifyOptions = {}; const identifyResolve = jest.fn(); const identifyReject = jest.fn(); @@ -315,7 +397,7 @@ describe('given a BrowserDataManager with mocked dependencies', () => { it('does not start a stream when forced off', async () => { const context = Context.fromLDContext({ kind: 'user', key: 'test-user' }); - const identifyOptions: LDIdentifyOptions = { waitForNetworkResults: false }; + const identifyOptions: BrowserIdentifyOptions = {}; const identifyResolve = jest.fn(); const identifyReject = jest.fn(); @@ -335,7 +417,7 @@ describe('given a BrowserDataManager with mocked dependencies', () => { it('starts streaming on identify if the automatic state is true', async () => { const context = Context.fromLDContext({ kind: 'user', key: 'test-user' }); - const identifyOptions: LDIdentifyOptions = { waitForNetworkResults: false }; + const identifyOptions: BrowserIdentifyOptions = {}; const identifyResolve = jest.fn(); const identifyReject = jest.fn(); diff --git a/packages/sdk/browser/src/BrowserClient.ts b/packages/sdk/browser/src/BrowserClient.ts index 7e76bd2f7f..9e7410c575 100644 --- a/packages/sdk/browser/src/BrowserClient.ts +++ b/packages/sdk/browser/src/BrowserClient.ts @@ -13,9 +13,9 @@ import { LDHeaders, Platform, } from '@launchdarkly/js-client-sdk-common'; -import { LDIdentifyOptions } from '@launchdarkly/js-client-sdk-common/dist/api/LDIdentifyOptions'; import { EventName } from '@launchdarkly/js-client-sdk-common/dist/LDEmitter'; +import { BrowserIdentifyOptions as LDIdentifyOptions } from '../BrowserIdentifyOptions'; import BrowserDataManager from './BrowserDataManager'; import GoalManager from './goals/GoalManager'; import { Goal, isClick } from './goals/Goals'; @@ -23,12 +23,19 @@ import validateOptions, { BrowserOptions, filterToBaseOptions } from './options' import BrowserPlatform from './platform/BrowserPlatform'; /** - * We are not supporting dynamically setting the connection mode on the LDClient. - * The SDK does not support offline mode. Instead bootstrap data can be used. + * + * The LaunchDarkly SDK client object. + * + * Applications should configure the client at page load time and reuse the same instance. + * + * For more information, see the [SDK Reference Guide](https://docs.launchdarkly.com/sdk/client-side/javascript). + * + * @ignore Implementation Note: We are not supporting dynamically setting the connection mode on the LDClient. + * @ignore Implementation Note: The SDK does not support offline mode. Instead bootstrap data can be used. */ export type LDClient = Omit< CommonClient, - 'setConnectionMode' | 'getConnectionMode' | 'getOffline' + 'setConnectionMode' | 'getConnectionMode' | 'getOffline' | 'identify' > & { /** * Specifies whether or not to open a streaming connection to LaunchDarkly for live flag updates. @@ -40,9 +47,38 @@ export type LDClient = Omit< * This can also be set as the `streaming` property of {@link LDOptions}. */ setStreaming(streaming?: boolean): void; + + /** + * Identifies a context to LaunchDarkly. + * + * Unlike the server-side SDKs, the client-side JavaScript SDKs maintain a current context state, + * which is set when you call `identify()`. + * + * Changing the current context also causes all feature flag values to be reloaded. Until that has + * finished, calls to {@link variation} will still return flag values for the previous context. You can + * await the Promise to determine when the new flag values are available. + * + * @param context + * The LDContext object. + * @param identifyOptions + * Optional configuration. Please see {@link LDIdentifyOptions}. + * @returns + * A Promise which resolves when the flag values for the specified + * context are available. It rejects when: + * + * 1. The context is unspecified or has no key. + * + * 2. The identify timeout is exceeded. In client SDKs this defaults to 5s. + * You can customize this timeout with {@link LDIdentifyOptions | identifyOptions}. + * + * 3. A network error is encountered during initialization. + * + * @ignore Implementation Note: Browser implementation has different options. + */ + identify(context: LDContext, identifyOptions?: LDIdentifyOptions): Promise; }; -export class BrowserClient extends LDClientImpl { +export class BrowserClient extends LDClientImpl implements LDClient { private readonly goalManager?: GoalManager; constructor( diff --git a/packages/sdk/browser/src/BrowserDataManager.ts b/packages/sdk/browser/src/BrowserDataManager.ts index f1d121baf6..b6ba7ff7a6 100644 --- a/packages/sdk/browser/src/BrowserDataManager.ts +++ b/packages/sdk/browser/src/BrowserDataManager.ts @@ -13,6 +13,7 @@ import { Requestor, } from '@launchdarkly/js-client-sdk-common'; +import { BrowserIdentifyOptions } from '../BrowserIdentifyOptions'; import { ValidatedOptions } from './options'; const logTag = '[BrowserDataManager]'; @@ -22,6 +23,7 @@ export default class BrowserDataManager extends BaseDataManager { // Otherwise we automatically manage streaming state. private forcedStreaming?: boolean = undefined; private automaticStreamingState: boolean = false; + private secureModeHash?: string; // +-----------+-----------+---------------+ // | forced | automatic | state | @@ -68,9 +70,18 @@ export default class BrowserDataManager extends BaseDataManager { identifyResolve: () => void, identifyReject: (err: Error) => void, context: Context, - _identifyOptions?: LDIdentifyOptions, + identifyOptions?: LDIdentifyOptions, ): Promise { this.context = context; + const browserIdentifyOptions = identifyOptions as BrowserIdentifyOptions; + if (browserIdentifyOptions.hash) { + this.setConnectionParams({ + queryParameters: [{ key: 'h', value: browserIdentifyOptions.hash }], + }); + } else { + this.setConnectionParams(); + } + this.secureModeHash = browserIdentifyOptions.hash; if (await this.flagManager.loadCached(context)) { this.debugLog('Identify - Flags loaded from cache. Continuing to initialize via a poll.'); } @@ -162,6 +173,9 @@ 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 }); + } const headers: { [key: string]: string } = { ...this.baseHeaders }; let body; diff --git a/packages/shared/sdk-client/__tests__/polling/PollingProcessor.test.ts b/packages/shared/sdk-client/__tests__/polling/PollingProcessor.test.ts index 593372d837..52ea9f188c 100644 --- a/packages/shared/sdk-client/__tests__/polling/PollingProcessor.test.ts +++ b/packages/shared/sdk-client/__tests__/polling/PollingProcessor.test.ts @@ -75,6 +75,7 @@ function makeConfig( pollInterval: number, withReasons: boolean, useReport: boolean, + queryParameters?: { key: string; value: string }[], ): PollingDataSourceConfig { return { credential: 'the-sdk-key', @@ -91,6 +92,7 @@ function makeConfig( withReasons, useReport, pollInterval, + queryParameters, }; } @@ -109,6 +111,49 @@ it('makes no requests until it is started', () => { expect(requests.fetch).toHaveBeenCalledTimes(0); }); +it('includes custom query parameters when specified', () => { + const requests = makeRequests(); + + const polling = new PollingProcessor( + 'mockContextString', + makeConfig(1, true, false, [ + { key: 'custom', value: 'value' }, + { key: 'custom2', value: 'value2' }, + ]), + requests, + makeEncoding(), + (_flags) => {}, + (_error) => {}, + ); + polling.start(); + + expect(requests.fetch).toHaveBeenCalledWith( + 'mockPollingEndpoint/poll/path/get?custom=value&custom2=value2&withReasons=true&filter=testPayloadFilterKey', + expect.anything(), + ); + polling.stop(); +}); + +it('works without any custom query parameters', () => { + const requests = makeRequests(); + + const polling = new PollingProcessor( + 'mockContextString', + makeConfig(1, true, false), + requests, + makeEncoding(), + (_flags) => {}, + (_error) => {}, + ); + polling.start(); + + expect(requests.fetch).toHaveBeenCalledWith( + 'mockPollingEndpoint/poll/path/get?withReasons=true&filter=testPayloadFilterKey', + expect.anything(), + ); + polling.stop(); +}); + it('polls immediately when started', () => { const requests = makeRequests(); diff --git a/packages/shared/sdk-client/__tests__/streaming/StreamingProcessor.test.ts b/packages/shared/sdk-client/__tests__/streaming/StreamingProcessor.test.ts index fcd4a96d6c..3b52daf679 100644 --- a/packages/shared/sdk-client/__tests__/streaming/StreamingProcessor.test.ts +++ b/packages/shared/sdk-client/__tests__/streaming/StreamingProcessor.test.ts @@ -42,6 +42,7 @@ let basicPlatform: Platform; function getStreamingDataSourceConfig( withReasons: boolean = false, useReport: boolean = false, + queryParameters?: [{ key: string; value: string }], ): StreamingDataSourceConfig { return { credential: sdkKey, @@ -63,6 +64,7 @@ function getStreamingDataSourceConfig( initialRetryDelayMillis: 1000, withReasons, useReport, + queryParameters, }; } @@ -342,3 +344,51 @@ describe('given a stream processor', () => { }); }); }); + +it('includes custom query parameters', () => { + const { info } = basicPlatform; + const listeners = new Map(); + const mockListener = { + deserializeData: jest.fn((data) => data), + processJson: jest.fn(), + }; + listeners.set('put', mockListener); + listeners.set('patch', mockListener); + const diagnosticsManager = new internal.DiagnosticsManager(sdkKey, basicPlatform, {}); + + basicPlatform.requests = { + createEventSource: jest.fn((streamUri: string, options: any) => { + const mockEventSource = createMockEventSource(streamUri, options); + return mockEventSource; + }), + getEventSourceCapabilities: jest.fn(() => ({ + readTimeout: true, + headers: true, + customMethod: true, + })), + } as any; + + const streamingProcessor = new StreamingProcessor( + 'mockContextString', + getStreamingDataSourceConfig(undefined, undefined, [{ key: 'custom', value: 'value' }]), + listeners, + basicPlatform.requests, + basicPlatform.encoding!, + diagnosticsManager, + () => {}, + logger, + ); + + streamingProcessor.start(); + + expect(basicPlatform.requests.createEventSource).toHaveBeenCalledWith( + `${serviceEndpoints.streaming}/stream/path/get?custom=value&filter=testPayloadFilterKey`, + { + errorFilter: expect.any(Function), + headers: defaultHeaders(sdkKey, info, undefined), + initialRetryDelayMillis: 1000, + readTimeoutMillis: 300000, + retryResetIntervalMillis: 60000, + }, + ); +}); diff --git a/packages/shared/sdk-client/src/DataManager.ts b/packages/shared/sdk-client/src/DataManager.ts index 1fb0e9c1c2..252ec44014 100644 --- a/packages/shared/sdk-client/src/DataManager.ts +++ b/packages/shared/sdk-client/src/DataManager.ts @@ -55,10 +55,15 @@ export interface DataManagerFactory { ): DataManager; } +export interface ConnectionParams { + queryParameters?: { key: string; value: string }[]; +} + export abstract class BaseDataManager implements DataManager { protected updateProcessor?: subsystem.LDStreamProcessor; protected readonly logger: LDLogger; protected context?: Context; + private connectionParams?: ConnectionParams; constructor( protected readonly platform: Platform, @@ -74,6 +79,13 @@ export abstract class BaseDataManager implements DataManager { this.logger = config.logger; } + /** + * Set additional connection parameters for requests polling/streaming. + */ + protected setConnectionParams(connectionParams?: ConnectionParams) { + this.connectionParams = connectionParams; + } + abstract identify( identifyResolve: () => void, identifyReject: (err: Error) => void, @@ -97,6 +109,7 @@ export abstract class BaseDataManager implements DataManager { pollInterval: this.config.pollInterval, withReasons: this.config.withReasons, useReport: this.config.useReport, + queryParameters: this.connectionParams?.queryParameters, }, this.platform.requests, this.platform.encoding!, @@ -138,6 +151,7 @@ export abstract class BaseDataManager implements DataManager { initialRetryDelayMillis: this.config.streamInitialReconnectDelay * 1000, withReasons: this.config.withReasons, useReport: this.config.useReport, + queryParameters: this.connectionParams?.queryParameters, }, this.createStreamListeners(checkedContext, identifyResolve), this.platform.requests, diff --git a/packages/shared/sdk-client/src/polling/PollingProcessor.ts b/packages/shared/sdk-client/src/polling/PollingProcessor.ts index 1e4f229dd3..2b7fb524cc 100644 --- a/packages/shared/sdk-client/src/polling/PollingProcessor.ts +++ b/packages/shared/sdk-client/src/polling/PollingProcessor.ts @@ -41,7 +41,9 @@ export default class PollingProcessor implements subsystem.LDStreamProcessor { ? dataSourceConfig.paths.pathReport(encoding, plainContextString) : dataSourceConfig.paths.pathGet(encoding, plainContextString); - const parameters: { key: string; value: string }[] = []; + const parameters: { key: string; value: string }[] = [ + ...(dataSourceConfig.queryParameters ?? []), + ]; if (this.dataSourceConfig.withReasons) { parameters.push({ key: 'withReasons', value: 'true' }); } diff --git a/packages/shared/sdk-client/src/streaming/DataSourceConfig.ts b/packages/shared/sdk-client/src/streaming/DataSourceConfig.ts index 41ce87b402..01fc6f9038 100644 --- a/packages/shared/sdk-client/src/streaming/DataSourceConfig.ts +++ b/packages/shared/sdk-client/src/streaming/DataSourceConfig.ts @@ -7,6 +7,7 @@ export interface DataSourceConfig { withReasons: boolean; useReport: boolean; paths: DataSourcePaths; + queryParameters?: { key: string; value: string }[]; } export interface PollingDataSourceConfig extends DataSourceConfig { diff --git a/packages/shared/sdk-client/src/streaming/StreamingProcessor.ts b/packages/shared/sdk-client/src/streaming/StreamingProcessor.ts index 7c7a083a10..ae963f9bd1 100644 --- a/packages/shared/sdk-client/src/streaming/StreamingProcessor.ts +++ b/packages/shared/sdk-client/src/streaming/StreamingProcessor.ts @@ -55,7 +55,9 @@ class StreamingProcessor implements subsystem.LDStreamProcessor { ? dataSourceConfig.paths.pathReport(encoding, plainContextString) : dataSourceConfig.paths.pathGet(encoding, plainContextString); - const parameters: { key: string; value: string }[] = []; + const parameters: { key: string; value: string }[] = [ + ...(dataSourceConfig.queryParameters ?? []), + ]; if (this.dataSourceConfig.withReasons) { parameters.push({ key: 'withReasons', value: 'true' }); } From 292109612efce1d39fa66e37803848312214ebac Mon Sep 17 00:00:00 2001 From: Ryan Lamb <4955475+kinyoklion@users.noreply.github.com> Date: Fri, 27 Sep 2024 10:21:21 -0700 Subject: [PATCH 2/8] Additional comment. --- packages/sdk/browser/src/BrowserClient.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/sdk/browser/src/BrowserClient.ts b/packages/sdk/browser/src/BrowserClient.ts index 9e7410c575..932cde0c5a 100644 --- a/packages/sdk/browser/src/BrowserClient.ts +++ b/packages/sdk/browser/src/BrowserClient.ts @@ -32,6 +32,8 @@ import BrowserPlatform from './platform/BrowserPlatform'; * * @ignore Implementation Note: We are not supporting dynamically setting the connection mode on the LDClient. * @ignore Implementation Note: The SDK does not support offline mode. Instead bootstrap data can be used. + * @ignore Implementation Note: The browser SDK has different identify options, so omits the base implementation + * @ignore from the interface. */ export type LDClient = Omit< CommonClient, From 156045a17b7a6a49f8d59401eb610218840b0ec1 Mon Sep 17 00:00:00 2001 From: Ryan Lamb <4955475+kinyoklion@users.noreply.github.com> Date: Fri, 27 Sep 2024 10:24:44 -0700 Subject: [PATCH 3/8] Fix optionality. --- packages/sdk/browser/src/BrowserDataManager.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/sdk/browser/src/BrowserDataManager.ts b/packages/sdk/browser/src/BrowserDataManager.ts index b6ba7ff7a6..115981e9d6 100644 --- a/packages/sdk/browser/src/BrowserDataManager.ts +++ b/packages/sdk/browser/src/BrowserDataManager.ts @@ -73,15 +73,15 @@ export default class BrowserDataManager extends BaseDataManager { identifyOptions?: LDIdentifyOptions, ): Promise { this.context = context; - const browserIdentifyOptions = identifyOptions as BrowserIdentifyOptions; - if (browserIdentifyOptions.hash) { + const browserIdentifyOptions = identifyOptions as BrowserIdentifyOptions | undefined; + if (browserIdentifyOptions?.hash) { this.setConnectionParams({ queryParameters: [{ key: 'h', value: browserIdentifyOptions.hash }], }); } else { this.setConnectionParams(); } - this.secureModeHash = browserIdentifyOptions.hash; + this.secureModeHash = browserIdentifyOptions?.hash; if (await this.flagManager.loadCached(context)) { this.debugLog('Identify - Flags loaded from cache. Continuing to initialize via a poll.'); } From ed4a62f0b0097bd855c58d34acb839864c689b97 Mon Sep 17 00:00:00 2001 From: Ryan Lamb <4955475+kinyoklion@users.noreply.github.com> Date: Fri, 27 Sep 2024 10:26:00 -0700 Subject: [PATCH 4/8] Export connection params. --- packages/shared/sdk-client/src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/shared/sdk-client/src/index.ts b/packages/shared/sdk-client/src/index.ts index 038221ba5d..6904475acf 100644 --- a/packages/shared/sdk-client/src/index.ts +++ b/packages/shared/sdk-client/src/index.ts @@ -20,7 +20,7 @@ export type { LDIdentifyOptions, } from './api'; -export type { DataManager, DataManagerFactory } from './DataManager'; +export type { DataManager, DataManagerFactory, ConnectionParams } from './DataManager'; export type { FlagManager } from './flag-manager/FlagManager'; export type { Configuration } from './configuration/Configuration'; From 1fea68fc11ea22dc917d410b7b52f76036fda289 Mon Sep 17 00:00:00 2001 From: Ryan Lamb <4955475+kinyoklion@users.noreply.github.com> Date: Fri, 27 Sep 2024 10:30:54 -0700 Subject: [PATCH 5/8] Move code and fix exports. --- packages/sdk/browser/__tests__/BrowserDataManager.test.ts | 2 +- packages/sdk/browser/src/BrowserClient.ts | 2 +- packages/sdk/browser/src/BrowserDataManager.ts | 2 +- packages/sdk/browser/{ => src}/BrowserIdentifyOptions.ts | 0 packages/sdk/browser/src/index.ts | 2 ++ 5 files changed, 5 insertions(+), 3 deletions(-) rename packages/sdk/browser/{ => src}/BrowserIdentifyOptions.ts (100%) diff --git a/packages/sdk/browser/__tests__/BrowserDataManager.test.ts b/packages/sdk/browser/__tests__/BrowserDataManager.test.ts index 5518801971..bcbd95e2f1 100644 --- a/packages/sdk/browser/__tests__/BrowserDataManager.test.ts +++ b/packages/sdk/browser/__tests__/BrowserDataManager.test.ts @@ -17,7 +17,7 @@ import { ServiceEndpoints, } from '@launchdarkly/js-client-sdk-common'; -import { BrowserIdentifyOptions } from '../BrowserIdentifyOptions'; +import { BrowserIdentifyOptions } from '../src/BrowserIdentifyOptions'; import BrowserDataManager from '../src/BrowserDataManager'; import validateOptions, { ValidatedOptions } from '../src/options'; import BrowserEncoding from '../src/platform/BrowserEncoding'; diff --git a/packages/sdk/browser/src/BrowserClient.ts b/packages/sdk/browser/src/BrowserClient.ts index 932cde0c5a..abf47e0b5d 100644 --- a/packages/sdk/browser/src/BrowserClient.ts +++ b/packages/sdk/browser/src/BrowserClient.ts @@ -15,7 +15,7 @@ import { } from '@launchdarkly/js-client-sdk-common'; import { EventName } from '@launchdarkly/js-client-sdk-common/dist/LDEmitter'; -import { BrowserIdentifyOptions as LDIdentifyOptions } from '../BrowserIdentifyOptions'; +import { BrowserIdentifyOptions as LDIdentifyOptions } from './BrowserIdentifyOptions'; import BrowserDataManager from './BrowserDataManager'; import GoalManager from './goals/GoalManager'; import { Goal, isClick } from './goals/Goals'; diff --git a/packages/sdk/browser/src/BrowserDataManager.ts b/packages/sdk/browser/src/BrowserDataManager.ts index 115981e9d6..b61ff9800c 100644 --- a/packages/sdk/browser/src/BrowserDataManager.ts +++ b/packages/sdk/browser/src/BrowserDataManager.ts @@ -13,7 +13,7 @@ import { Requestor, } from '@launchdarkly/js-client-sdk-common'; -import { BrowserIdentifyOptions } from '../BrowserIdentifyOptions'; +import { BrowserIdentifyOptions } from './BrowserIdentifyOptions'; import { ValidatedOptions } from './options'; const logTag = '[BrowserDataManager]'; diff --git a/packages/sdk/browser/BrowserIdentifyOptions.ts b/packages/sdk/browser/src/BrowserIdentifyOptions.ts similarity index 100% rename from packages/sdk/browser/BrowserIdentifyOptions.ts rename to packages/sdk/browser/src/BrowserIdentifyOptions.ts diff --git a/packages/sdk/browser/src/index.ts b/packages/sdk/browser/src/index.ts index 26f5e703b9..d171664d68 100644 --- a/packages/sdk/browser/src/index.ts +++ b/packages/sdk/browser/src/index.ts @@ -16,6 +16,7 @@ import { // The exported LDClient and LDOptions are the browser specific implementations. // These shadow the common implementations. import { BrowserClient, LDClient } from './BrowserClient'; +import { BrowserIdentifyOptions as LDIdentifyOptions } from './BrowserIdentifyOptions'; import { BrowserOptions as LDOptions } from './options'; export { @@ -32,6 +33,7 @@ export { LDEvaluationDetail, LDEvaluationDetailTyped, LDEvaluationReason, + LDIdentifyOptions, }; export function init(clientSideId: string, options?: LDOptions): LDClient { From 1d01a435cc41929a607bc4073a08692b59759bb8 Mon Sep 17 00:00:00 2001 From: Ryan Lamb <4955475+kinyoklion@users.noreply.github.com> Date: Fri, 27 Sep 2024 10:37:19 -0700 Subject: [PATCH 6/8] Lint --- packages/sdk/browser/__tests__/BrowserDataManager.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/sdk/browser/__tests__/BrowserDataManager.test.ts b/packages/sdk/browser/__tests__/BrowserDataManager.test.ts index bcbd95e2f1..74ca999979 100644 --- a/packages/sdk/browser/__tests__/BrowserDataManager.test.ts +++ b/packages/sdk/browser/__tests__/BrowserDataManager.test.ts @@ -17,8 +17,8 @@ import { ServiceEndpoints, } from '@launchdarkly/js-client-sdk-common'; -import { BrowserIdentifyOptions } from '../src/BrowserIdentifyOptions'; import BrowserDataManager from '../src/BrowserDataManager'; +import { BrowserIdentifyOptions } from '../src/BrowserIdentifyOptions'; import validateOptions, { ValidatedOptions } from '../src/options'; import BrowserEncoding from '../src/platform/BrowserEncoding'; import BrowserInfo from '../src/platform/BrowserInfo'; From bf64ea7727e80aea36be8e618c643634900bea2d Mon Sep 17 00:00:00 2001 From: Ryan Lamb <4955475+kinyoklion@users.noreply.github.com> Date: Fri, 27 Sep 2024 10:41:00 -0700 Subject: [PATCH 7/8] Lint. --- packages/sdk/browser/src/BrowserClient.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/sdk/browser/src/BrowserClient.ts b/packages/sdk/browser/src/BrowserClient.ts index abf47e0b5d..47a1c4a28f 100644 --- a/packages/sdk/browser/src/BrowserClient.ts +++ b/packages/sdk/browser/src/BrowserClient.ts @@ -15,8 +15,8 @@ import { } from '@launchdarkly/js-client-sdk-common'; import { EventName } from '@launchdarkly/js-client-sdk-common/dist/LDEmitter'; -import { BrowserIdentifyOptions as LDIdentifyOptions } from './BrowserIdentifyOptions'; import BrowserDataManager from './BrowserDataManager'; +import { BrowserIdentifyOptions as LDIdentifyOptions } from './BrowserIdentifyOptions'; import GoalManager from './goals/GoalManager'; import { Goal, isClick } from './goals/Goals'; import validateOptions, { BrowserOptions, filterToBaseOptions } from './options'; From 4e54b60118435fc23872ad6343139be9f294617d Mon Sep 17 00:00:00 2001 From: Ryan Lamb <4955475+kinyoklion@users.noreply.github.com> Date: Tue, 1 Oct 2024 14:16:27 -0700 Subject: [PATCH 8/8] Fix typo --- packages/sdk/browser/src/BrowserIdentifyOptions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/sdk/browser/src/BrowserIdentifyOptions.ts b/packages/sdk/browser/src/BrowserIdentifyOptions.ts index 9600719ef6..458178fe6c 100644 --- a/packages/sdk/browser/src/BrowserIdentifyOptions.ts +++ b/packages/sdk/browser/src/BrowserIdentifyOptions.ts @@ -1,6 +1,6 @@ import { LDIdentifyOptions } from '@launchdarkly/js-client-sdk-common'; -export interface BrowserIdentifyOptions extends Omit { +export interface BrowserIdentifyOptions extends Omit { /** * The signed context key if you are using [Secure Mode] * (https://docs.launchdarkly.com/sdk/features/secure-mode#configuring-secure-mode-in-the-javascript-client-side-sdk).