Skip to content

Commit

Permalink
feat: Allow using custom user-agent name.
Browse files Browse the repository at this point in the history
  • Loading branch information
kinyoklion committed Sep 12, 2024
1 parent fe82500 commit 485d3a8
Show file tree
Hide file tree
Showing 14 changed files with 74 additions and 62 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export type LDInternalOptions = {
analyticsEventPath?: string;
diagnosticEventPath?: string;
includeAuthorizationHeader?: boolean;
userAgentHeaderName?: 'user-agent' | 'x-launchdarkly-user-agent';

/**
* In seconds. Log a warning if identifyTimeout is greater than this value.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,14 +101,18 @@ describe('given a stream processor with mock event source', () => {

diagnosticsManager = new DiagnosticsManager(sdkKey, basicPlatform, {});
streamingProcessor = new StreamingProcessor(
sdkKey,
{
basicConfiguration: getBasicConfiguration(logger),
platform: basicPlatform,
},
'/all',
[],
listeners,
{
authorization: 'my-sdk-key',
'user-agent': 'TestUserAgent/2.0.2',
'x-launchdarkly-wrapper': 'Rapper/1.2.3',
},
diagnosticsManager,
mockErrorHandler,
);
Expand Down Expand Up @@ -137,14 +141,18 @@ describe('given a stream processor with mock event source', () => {

it('sets streamInitialReconnectDelay correctly', () => {
streamingProcessor = new StreamingProcessor(
sdkKey,
{
basicConfiguration: getBasicConfiguration(logger),
platform: basicPlatform,
},
'/all',
[],
listeners,
{
authorization: 'my-sdk-key',
'user-agent': 'TestUserAgent/2.0.2',
'x-launchdarkly-wrapper': 'Rapper/1.2.3',
},
diagnosticsManager,
mockErrorHandler,
22,
Expand Down
10 changes: 5 additions & 5 deletions packages/shared/common/src/internal/stream/StreamingProcessor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { LDStreamProcessor } from '../../api/subsystem';
import { LDStreamingError } from '../../errors';
import { ClientContext } from '../../options';
import { getStreamingUri } from '../../options/ServiceEndpoints';
import { defaultHeaders, httpErrorMessage, shouldRetry } from '../../utils';
import { defaultHeaders, httpErrorMessage, LDHeaders, shouldRetry } from '../../utils';

Check failure on line 13 in packages/shared/common/src/internal/stream/StreamingProcessor.ts

View workflow job for this annotation

GitHub Actions / build-test-common

'defaultHeaders' is defined but never used. Allowed unused vars must match /^__/u
import { DiagnosticsManager } from '../diagnostics';
import { StreamingErrorHandler } from './types';

Expand All @@ -35,20 +35,20 @@ class StreamingProcessor implements LDStreamProcessor {
private connectionAttemptStartTime?: number;

constructor(
sdkKey: string,
clientContext: ClientContext,
streamUriPath: string,
parameters: { key: string; value: string }[],
private readonly listeners: Map<EventName, ProcessStreamResponse>,
baseHeaders: LDHeaders,
private readonly diagnosticsManager?: DiagnosticsManager,
private readonly errorHandler?: StreamingErrorHandler,
private readonly streamInitialReconnectDelay = 1,
) {
const { basicConfiguration, platform } = clientContext;
const { logger, tags } = basicConfiguration;
const { info, requests } = platform;
const { logger } = basicConfiguration;
const { requests } = platform;

this.headers = defaultHeaders(sdkKey, info, tags);
this.headers = { ...baseHeaders };
this.logger = logger;
this.requests = requests;
this.streamUri = getStreamingUri(
Expand Down
10 changes: 6 additions & 4 deletions packages/shared/common/src/utils/http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import { ApplicationTags } from '../options';

export type LDHeaders = {
authorization?: string;
'user-agent': string;
'user-agent'?: string;
'x-launchdarkly-user-agent'?: string;
'x-launchdarkly-wrapper'?: string;
'x-launchdarkly-tags'?: string;
};
Expand All @@ -14,12 +15,13 @@ export function defaultHeaders(
info: Info,
tags?: ApplicationTags,
includeAuthorizationHeader: boolean = true,
userAgentHeaderName: 'user-agent' | 'x-launchdarkly-user-agent' = 'user-agent',
): LDHeaders {
const { userAgentBase, version, wrapperName, wrapperVersion } = info.sdkData();

const headers: LDHeaders = {
'user-agent': `${userAgentBase ?? 'NodeJSClient'}/${version}`,
};
const headers: LDHeaders = {};

headers[userAgentHeaderName] = `${userAgentBase ?? 'NodeJSClient'}/${version}`;

// edge sdks sets this to false because they use the clientSideID
// and they don't need the authorization header
Expand Down
3 changes: 2 additions & 1 deletion packages/shared/mocks/src/streamingProcessor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type {
ClientContext,
EventName,
internal,
LDHeaders,
LDStreamingError,
ProcessStreamResponse,
} from '@common';
Expand All @@ -22,11 +23,11 @@ export const setupMockStreamingProcessor = (

MockStreamingProcessor.mockImplementation(
(
sdkKey: string,
clientContext: ClientContext,
streamUriPath: string,
parameters: { key: string; value: string }[],
listeners: Map<EventName, ProcessStreamResponse>,
baseHeaders: LDHeaders,
diagnosticsManager: internal.DiagnosticsManager,
errorHandler: internal.StreamingErrorHandler,
_streamInitialReconnectDelay: number,
Expand Down
4 changes: 2 additions & 2 deletions packages/shared/sdk-client/__tests__/LDClientImpl.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,11 +108,11 @@ describe('sdk-client object', () => {
'dev-test-flag': false,
});
expect(MockStreamingProcessor).toHaveBeenCalledWith(
expect.anything(),
expect.anything(),
'/stream/path',
expect.anything(),
expect.anything(),
expect.anything(),
undefined,
expect.anything(),
);
Expand All @@ -129,11 +129,11 @@ describe('sdk-client object', () => {
await ldc.identify(carContext);

expect(MockStreamingProcessor).toHaveBeenCalledWith(
expect.anything(),
expect.anything(),
'/stream/path',
[{ key: 'withReasons', value: 'true' }],
expect.anything(),
expect.anything(),
undefined,
expect.anything(),
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,12 +90,11 @@ it('makes no requests until it is started', () => {
const requests = makeRequests();
// eslint-disable-next-line no-new
new PollingProcessor(
'the-sdk-key',
requests,
makeInfo(),
'/polling',
[],
makeConfig(),
{},
(_flags) => {},
(_error) => {},
);
Expand All @@ -107,12 +106,11 @@ it('polls immediately when started', () => {
const requests = makeRequests();

const polling = new PollingProcessor(
'the-sdk-key',
requests,
makeInfo(),
'/polling',
[],
makeConfig(),
{},
(_flags) => {},
(_error) => {},
);
Expand All @@ -128,12 +126,11 @@ it('calls callback on success', async () => {
const errorCallback = jest.fn();

const polling = new PollingProcessor(
'the-sdk-key',
requests,
makeInfo(),
'/polling',
[],
makeConfig(),
{},
dataCallback,
errorCallback,
);
Expand All @@ -150,12 +147,11 @@ it('polls repeatedly', async () => {

requests.fetch = mockFetch('{ "flagA": true }', 200);
const polling = new PollingProcessor(
'the-sdk-key',
requests,
makeInfo(),
'/polling',
[],
makeConfig({ pollInterval: 0.1 }),
{},
dataCallback,
errorCallback,
);
Expand Down Expand Up @@ -189,12 +185,11 @@ it('stops polling when stopped', (done) => {
const errorCallback = jest.fn();

const polling = new PollingProcessor(
'the-sdk-key',
requests,
makeInfo(),
'/stops',
[],
makeConfig({ pollInterval: 0.01 }),
{},
dataCallback,
errorCallback,
);
Expand All @@ -212,15 +207,14 @@ it('includes the correct headers on requests', () => {
const requests = makeRequests();

const polling = new PollingProcessor(
'the-sdk-key',
requests,
makeInfo({
userAgentBase: 'AnSDK',
version: '42',
}),
'/polling',
[],
makeConfig(),
{
authorization: 'the-sdk-key',
'user-agent': 'AnSDK/42',
},
(_flags) => {},
(_error) => {},
);
Expand All @@ -242,12 +236,11 @@ it('defaults to using the "GET" verb', () => {
const requests = makeRequests();

const polling = new PollingProcessor(
'the-sdk-key',
requests,
makeInfo(),
'/polling',
[],
makeConfig(),
{},
(_flags) => {},
(_error) => {},
);
Expand All @@ -266,12 +259,11 @@ it('can be configured to use the "REPORT" verb', () => {
const requests = makeRequests();

const polling = new PollingProcessor(
'the-sdk-key',
requests,
makeInfo(),
'/polling',
[],
makeConfig({ useReport: true }),
{},
(_flags) => {},
(_error) => {},
);
Expand All @@ -293,12 +285,11 @@ it('continues polling after receiving bad JSON', async () => {
const config = makeConfig({ pollInterval: 0.1 });

const polling = new PollingProcessor(
'the-sdk-key',
requests,
makeInfo(),
'/polling',
[],
config,
{},
dataCallback,
errorCallback,
);
Expand All @@ -322,12 +313,11 @@ it('continues polling after an exception thrown during a request', async () => {
const config = makeConfig({ pollInterval: 0.1 });

const polling = new PollingProcessor(
'the-sdk-key',
requests,
makeInfo(),
'/polling',
[],
config,
{},
dataCallback,
errorCallback,
);
Expand All @@ -354,12 +344,11 @@ it('can handle recoverable http errors', async () => {
const config = makeConfig({ pollInterval: 0.1 });

const polling = new PollingProcessor(
'the-sdk-key',
requests,
makeInfo(),
'/polling',
[],
config,
{},
dataCallback,
errorCallback,
);
Expand All @@ -384,12 +373,11 @@ it('stops polling on unrecoverable error codes', (done) => {
const config = makeConfig({ pollInterval: 0.01 });

const polling = new PollingProcessor(
'the-sdk-key',
requests,
makeInfo(),
'/polling',
[],
config,
{},
dataCallback,
errorCallback,
);
Expand Down
16 changes: 13 additions & 3 deletions packages/shared/sdk-client/src/LDClientImpl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@ import {
ClientContext,
clone,
Context,
defaultHeaders,
internal,
LDClientError,
LDContext,
LDFlagSet,
LDFlagValue,
LDHeaders,
LDLogger,
Platform,
ProcessStreamResponse,
Expand Down Expand Up @@ -60,6 +62,7 @@ export default class LDClientImpl implements LDClient {
private eventSendingEnabled: boolean = true;
private networkAvailable: boolean = true;
private connectionMode: ConnectionMode;
private baseHeaders: LDHeaders;

/**
* Creates the client object synchronously. No async, no network calls.
Expand Down Expand Up @@ -109,6 +112,14 @@ export default class LDClientImpl implements LDClient {
const ldContext = Context.toLDContext(context);
this.emitter.emit('change', ldContext, flagKeys);
});

this.baseHeaders = defaultHeaders(
this.sdkKey,
this.platform.info,
this.config.tags,
true,
'x-launchdarkly-user-agent',
);
}

/**
Expand Down Expand Up @@ -407,12 +418,11 @@ export default class LDClientImpl implements LDClient {
}

this.updateProcessor = new PollingProcessor(
this.sdkKey,
this.clientContext.platform.requests,
this.clientContext.platform.info,
this.createPollUriPath(context),
parameters,
this.config,
this.baseHeaders,
async (flags) => {
this.logger.debug(`Handling polling result: ${Object.keys(flags)}`);

Expand Down Expand Up @@ -446,11 +456,11 @@ export default class LDClientImpl implements LDClient {
}

this.updateProcessor = new internal.StreamingProcessor(
this.sdkKey,
this.clientContext,
this.createStreamUriPath(context),
parameters,
this.createStreamListeners(checkedContext, identifyResolve),
this.baseHeaders,
this.diagnosticsManager,
(e) => {
identifyReject(e);
Expand Down
Loading

0 comments on commit 485d3a8

Please sign in to comment.