Skip to content

Commit 485d3a8

Browse files
committed
feat: Allow using custom user-agent name.
1 parent fe82500 commit 485d3a8

File tree

14 files changed

+74
-62
lines changed

14 files changed

+74
-62
lines changed

packages/shared/common/src/internal/events/LDInternalOptions.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export type LDInternalOptions = {
1212
analyticsEventPath?: string;
1313
diagnosticEventPath?: string;
1414
includeAuthorizationHeader?: boolean;
15+
userAgentHeaderName?: 'user-agent' | 'x-launchdarkly-user-agent';
1516

1617
/**
1718
* In seconds. Log a warning if identifyTimeout is greater than this value.

packages/shared/common/src/internal/stream/StreamingProcessor.test.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,14 +101,18 @@ describe('given a stream processor with mock event source', () => {
101101

102102
diagnosticsManager = new DiagnosticsManager(sdkKey, basicPlatform, {});
103103
streamingProcessor = new StreamingProcessor(
104-
sdkKey,
105104
{
106105
basicConfiguration: getBasicConfiguration(logger),
107106
platform: basicPlatform,
108107
},
109108
'/all',
110109
[],
111110
listeners,
111+
{
112+
authorization: 'my-sdk-key',
113+
'user-agent': 'TestUserAgent/2.0.2',
114+
'x-launchdarkly-wrapper': 'Rapper/1.2.3',
115+
},
112116
diagnosticsManager,
113117
mockErrorHandler,
114118
);
@@ -137,14 +141,18 @@ describe('given a stream processor with mock event source', () => {
137141

138142
it('sets streamInitialReconnectDelay correctly', () => {
139143
streamingProcessor = new StreamingProcessor(
140-
sdkKey,
141144
{
142145
basicConfiguration: getBasicConfiguration(logger),
143146
platform: basicPlatform,
144147
},
145148
'/all',
146149
[],
147150
listeners,
151+
{
152+
authorization: 'my-sdk-key',
153+
'user-agent': 'TestUserAgent/2.0.2',
154+
'x-launchdarkly-wrapper': 'Rapper/1.2.3',
155+
},
148156
diagnosticsManager,
149157
mockErrorHandler,
150158
22,

packages/shared/common/src/internal/stream/StreamingProcessor.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { LDStreamProcessor } from '../../api/subsystem';
1010
import { LDStreamingError } from '../../errors';
1111
import { ClientContext } from '../../options';
1212
import { getStreamingUri } from '../../options/ServiceEndpoints';
13-
import { defaultHeaders, httpErrorMessage, shouldRetry } from '../../utils';
13+
import { defaultHeaders, httpErrorMessage, LDHeaders, shouldRetry } from '../../utils';
1414
import { DiagnosticsManager } from '../diagnostics';
1515
import { StreamingErrorHandler } from './types';
1616

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

3737
constructor(
38-
sdkKey: string,
3938
clientContext: ClientContext,
4039
streamUriPath: string,
4140
parameters: { key: string; value: string }[],
4241
private readonly listeners: Map<EventName, ProcessStreamResponse>,
42+
baseHeaders: LDHeaders,
4343
private readonly diagnosticsManager?: DiagnosticsManager,
4444
private readonly errorHandler?: StreamingErrorHandler,
4545
private readonly streamInitialReconnectDelay = 1,
4646
) {
4747
const { basicConfiguration, platform } = clientContext;
48-
const { logger, tags } = basicConfiguration;
49-
const { info, requests } = platform;
48+
const { logger } = basicConfiguration;
49+
const { requests } = platform;
5050

51-
this.headers = defaultHeaders(sdkKey, info, tags);
51+
this.headers = { ...baseHeaders };
5252
this.logger = logger;
5353
this.requests = requests;
5454
this.streamUri = getStreamingUri(

packages/shared/common/src/utils/http.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ import { ApplicationTags } from '../options';
44

55
export type LDHeaders = {
66
authorization?: string;
7-
'user-agent': string;
7+
'user-agent'?: string;
8+
'x-launchdarkly-user-agent'?: string;
89
'x-launchdarkly-wrapper'?: string;
910
'x-launchdarkly-tags'?: string;
1011
};
@@ -14,12 +15,13 @@ export function defaultHeaders(
1415
info: Info,
1516
tags?: ApplicationTags,
1617
includeAuthorizationHeader: boolean = true,
18+
userAgentHeaderName: 'user-agent' | 'x-launchdarkly-user-agent' = 'user-agent',
1719
): LDHeaders {
1820
const { userAgentBase, version, wrapperName, wrapperVersion } = info.sdkData();
1921

20-
const headers: LDHeaders = {
21-
'user-agent': `${userAgentBase ?? 'NodeJSClient'}/${version}`,
22-
};
22+
const headers: LDHeaders = {};
23+
24+
headers[userAgentHeaderName] = `${userAgentBase ?? 'NodeJSClient'}/${version}`;
2325

2426
// edge sdks sets this to false because they use the clientSideID
2527
// and they don't need the authorization header

packages/shared/mocks/src/streamingProcessor.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type {
22
ClientContext,
33
EventName,
44
internal,
5+
LDHeaders,
56
LDStreamingError,
67
ProcessStreamResponse,
78
} from '@common';
@@ -22,11 +23,11 @@ export const setupMockStreamingProcessor = (
2223

2324
MockStreamingProcessor.mockImplementation(
2425
(
25-
sdkKey: string,
2626
clientContext: ClientContext,
2727
streamUriPath: string,
2828
parameters: { key: string; value: string }[],
2929
listeners: Map<EventName, ProcessStreamResponse>,
30+
baseHeaders: LDHeaders,
3031
diagnosticsManager: internal.DiagnosticsManager,
3132
errorHandler: internal.StreamingErrorHandler,
3233
_streamInitialReconnectDelay: number,

packages/shared/sdk-client/__tests__/LDClientImpl.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,11 +108,11 @@ describe('sdk-client object', () => {
108108
'dev-test-flag': false,
109109
});
110110
expect(MockStreamingProcessor).toHaveBeenCalledWith(
111-
expect.anything(),
112111
expect.anything(),
113112
'/stream/path',
114113
expect.anything(),
115114
expect.anything(),
115+
expect.anything(),
116116
undefined,
117117
expect.anything(),
118118
);
@@ -129,11 +129,11 @@ describe('sdk-client object', () => {
129129
await ldc.identify(carContext);
130130

131131
expect(MockStreamingProcessor).toHaveBeenCalledWith(
132-
expect.anything(),
133132
expect.anything(),
134133
'/stream/path',
135134
[{ key: 'withReasons', value: 'true' }],
136135
expect.anything(),
136+
expect.anything(),
137137
undefined,
138138
expect.anything(),
139139
);

packages/shared/sdk-client/__tests__/polling/PollingProcessor.test.ts

Lines changed: 15 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -90,12 +90,11 @@ it('makes no requests until it is started', () => {
9090
const requests = makeRequests();
9191
// eslint-disable-next-line no-new
9292
new PollingProcessor(
93-
'the-sdk-key',
9493
requests,
95-
makeInfo(),
9694
'/polling',
9795
[],
9896
makeConfig(),
97+
{},
9998
(_flags) => {},
10099
(_error) => {},
101100
);
@@ -107,12 +106,11 @@ it('polls immediately when started', () => {
107106
const requests = makeRequests();
108107

109108
const polling = new PollingProcessor(
110-
'the-sdk-key',
111109
requests,
112-
makeInfo(),
113110
'/polling',
114111
[],
115112
makeConfig(),
113+
{},
116114
(_flags) => {},
117115
(_error) => {},
118116
);
@@ -128,12 +126,11 @@ it('calls callback on success', async () => {
128126
const errorCallback = jest.fn();
129127

130128
const polling = new PollingProcessor(
131-
'the-sdk-key',
132129
requests,
133-
makeInfo(),
134130
'/polling',
135131
[],
136132
makeConfig(),
133+
{},
137134
dataCallback,
138135
errorCallback,
139136
);
@@ -150,12 +147,11 @@ it('polls repeatedly', async () => {
150147

151148
requests.fetch = mockFetch('{ "flagA": true }', 200);
152149
const polling = new PollingProcessor(
153-
'the-sdk-key',
154150
requests,
155-
makeInfo(),
156151
'/polling',
157152
[],
158153
makeConfig({ pollInterval: 0.1 }),
154+
{},
159155
dataCallback,
160156
errorCallback,
161157
);
@@ -189,12 +185,11 @@ it('stops polling when stopped', (done) => {
189185
const errorCallback = jest.fn();
190186

191187
const polling = new PollingProcessor(
192-
'the-sdk-key',
193188
requests,
194-
makeInfo(),
195189
'/stops',
196190
[],
197191
makeConfig({ pollInterval: 0.01 }),
192+
{},
198193
dataCallback,
199194
errorCallback,
200195
);
@@ -212,15 +207,14 @@ it('includes the correct headers on requests', () => {
212207
const requests = makeRequests();
213208

214209
const polling = new PollingProcessor(
215-
'the-sdk-key',
216210
requests,
217-
makeInfo({
218-
userAgentBase: 'AnSDK',
219-
version: '42',
220-
}),
221211
'/polling',
222212
[],
223213
makeConfig(),
214+
{
215+
authorization: 'the-sdk-key',
216+
'user-agent': 'AnSDK/42',
217+
},
224218
(_flags) => {},
225219
(_error) => {},
226220
);
@@ -242,12 +236,11 @@ it('defaults to using the "GET" verb', () => {
242236
const requests = makeRequests();
243237

244238
const polling = new PollingProcessor(
245-
'the-sdk-key',
246239
requests,
247-
makeInfo(),
248240
'/polling',
249241
[],
250242
makeConfig(),
243+
{},
251244
(_flags) => {},
252245
(_error) => {},
253246
);
@@ -266,12 +259,11 @@ it('can be configured to use the "REPORT" verb', () => {
266259
const requests = makeRequests();
267260

268261
const polling = new PollingProcessor(
269-
'the-sdk-key',
270262
requests,
271-
makeInfo(),
272263
'/polling',
273264
[],
274265
makeConfig({ useReport: true }),
266+
{},
275267
(_flags) => {},
276268
(_error) => {},
277269
);
@@ -293,12 +285,11 @@ it('continues polling after receiving bad JSON', async () => {
293285
const config = makeConfig({ pollInterval: 0.1 });
294286

295287
const polling = new PollingProcessor(
296-
'the-sdk-key',
297288
requests,
298-
makeInfo(),
299289
'/polling',
300290
[],
301291
config,
292+
{},
302293
dataCallback,
303294
errorCallback,
304295
);
@@ -322,12 +313,11 @@ it('continues polling after an exception thrown during a request', async () => {
322313
const config = makeConfig({ pollInterval: 0.1 });
323314

324315
const polling = new PollingProcessor(
325-
'the-sdk-key',
326316
requests,
327-
makeInfo(),
328317
'/polling',
329318
[],
330319
config,
320+
{},
331321
dataCallback,
332322
errorCallback,
333323
);
@@ -354,12 +344,11 @@ it('can handle recoverable http errors', async () => {
354344
const config = makeConfig({ pollInterval: 0.1 });
355345

356346
const polling = new PollingProcessor(
357-
'the-sdk-key',
358347
requests,
359-
makeInfo(),
360348
'/polling',
361349
[],
362350
config,
351+
{},
363352
dataCallback,
364353
errorCallback,
365354
);
@@ -384,12 +373,11 @@ it('stops polling on unrecoverable error codes', (done) => {
384373
const config = makeConfig({ pollInterval: 0.01 });
385374

386375
const polling = new PollingProcessor(
387-
'the-sdk-key',
388376
requests,
389-
makeInfo(),
390377
'/polling',
391378
[],
392379
config,
380+
{},
393381
dataCallback,
394382
errorCallback,
395383
);

packages/shared/sdk-client/src/LDClientImpl.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@ import {
33
ClientContext,
44
clone,
55
Context,
6+
defaultHeaders,
67
internal,
78
LDClientError,
89
LDContext,
910
LDFlagSet,
1011
LDFlagValue,
12+
LDHeaders,
1113
LDLogger,
1214
Platform,
1315
ProcessStreamResponse,
@@ -60,6 +62,7 @@ export default class LDClientImpl implements LDClient {
6062
private eventSendingEnabled: boolean = true;
6163
private networkAvailable: boolean = true;
6264
private connectionMode: ConnectionMode;
65+
private baseHeaders: LDHeaders;
6366

6467
/**
6568
* Creates the client object synchronously. No async, no network calls.
@@ -109,6 +112,14 @@ export default class LDClientImpl implements LDClient {
109112
const ldContext = Context.toLDContext(context);
110113
this.emitter.emit('change', ldContext, flagKeys);
111114
});
115+
116+
this.baseHeaders = defaultHeaders(
117+
this.sdkKey,
118+
this.platform.info,
119+
this.config.tags,
120+
true,
121+
'x-launchdarkly-user-agent',
122+
);
112123
}
113124

114125
/**
@@ -407,12 +418,11 @@ export default class LDClientImpl implements LDClient {
407418
}
408419

409420
this.updateProcessor = new PollingProcessor(
410-
this.sdkKey,
411421
this.clientContext.platform.requests,
412-
this.clientContext.platform.info,
413422
this.createPollUriPath(context),
414423
parameters,
415424
this.config,
425+
this.baseHeaders,
416426
async (flags) => {
417427
this.logger.debug(`Handling polling result: ${Object.keys(flags)}`);
418428

@@ -446,11 +456,11 @@ export default class LDClientImpl implements LDClient {
446456
}
447457

448458
this.updateProcessor = new internal.StreamingProcessor(
449-
this.sdkKey,
450459
this.clientContext,
451460
this.createStreamUriPath(context),
452461
parameters,
453462
this.createStreamListeners(checkedContext, identifyResolve),
463+
this.baseHeaders,
454464
this.diagnosticsManager,
455465
(e) => {
456466
identifyReject(e);

0 commit comments

Comments
 (0)