Skip to content

Commit ed5a206

Browse files
authored
feat: Allow using custom user-agent name. (#580)
Allow for SDK implementations to control the name of the header their SDK name and version are put in. Browser SDKs need to use `x-launchdarkly-user-agent` all other platforms can use `user-agent`. In a browser the `user-agent` is a forbidden header and can only contain the browser's user agent.
1 parent 887548a commit ed5a206

File tree

20 files changed

+99
-91
lines changed

20 files changed

+99
-91
lines changed

packages/shared/common/__tests__/internal/events/EventProcessor.test.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,12 @@ describe('given an event processor', () => {
131131
}),
132132
);
133133
contextDeduplicator = new ContextDeduplicator();
134-
eventProcessor = new EventProcessor(eventProcessorConfig, clientContext, contextDeduplicator);
134+
eventProcessor = new EventProcessor(
135+
eventProcessorConfig,
136+
clientContext,
137+
{},
138+
contextDeduplicator,
139+
);
135140
});
136141

137142
afterEach(() => {
@@ -788,6 +793,7 @@ describe('given an event processor', () => {
788793
eventProcessor = new EventProcessor(
789794
eventProcessorConfig,
790795
clientContextWithDebug,
796+
{},
791797
contextDeduplicator,
792798
);
793799

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import LDEventProcessor from '../../api/subsystem/LDEventProcessor';
55
import AttributeReference from '../../AttributeReference';
66
import ContextFilter from '../../ContextFilter';
77
import { ClientContext } from '../../options';
8+
import { LDHeaders } from '../../utils';
89
import { DiagnosticsManager } from '../diagnostics';
910
import EventSender from './EventSender';
1011
import EventSummarizer, { SummarizedFlagsEvent } from './EventSummarizer';
@@ -106,13 +107,14 @@ export default class EventProcessor implements LDEventProcessor {
106107
constructor(
107108
private readonly config: EventProcessorOptions,
108109
clientContext: ClientContext,
110+
baseHeaders: LDHeaders,
109111
private readonly contextDeduplicator?: LDContextDeduplicator,
110112
private readonly diagnosticsManager?: DiagnosticsManager,
111113
start: boolean = true,
112114
) {
113115
this.capacity = config.eventsCapacity;
114116
this.logger = clientContext.basicConfiguration.logger;
115-
this.eventSender = new EventSender(clientContext);
117+
this.eventSender = new EventSender(clientContext, baseHeaders);
116118

117119
this.contextFilter = new ContextFilter(
118120
config.allAttributesPrivate,

packages/shared/common/src/internal/events/EventSender.test.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,12 @@ describe('given an event sender', () => {
112112

113113
eventSender = new EventSender(
114114
new ClientContext('sdk-key', basicConfig, { ...mockPlatform, info }),
115+
{
116+
authorization: 'sdk-key',
117+
'user-agent': 'TestUserAgent/2.0.2',
118+
'x-launchdarkly-tags': 'application-id/testApplication1 application-version/1.0.0',
119+
'x-launchdarkly-wrapper': 'Rapper/1.2.3',
120+
},
115121
);
116122

117123
eventSenderResult = await eventSender.sendEventData(

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

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {
1111
LDUnexpectedResponseError,
1212
} from '../../errors';
1313
import { ClientContext, getEventsUri } from '../../options';
14-
import { defaultHeaders, httpErrorMessage, sleep } from '../../utils';
14+
import { httpErrorMessage, LDHeaders, sleep } from '../../utils';
1515

1616
export default class EventSender implements LDEventSender {
1717
private crypto: Crypto;
@@ -22,16 +22,14 @@ export default class EventSender implements LDEventSender {
2222
private eventsUri: string;
2323
private requests: Requests;
2424

25-
constructor(clientContext: ClientContext) {
25+
constructor(clientContext: ClientContext, baseHeaders: LDHeaders) {
2626
const { basicConfiguration, platform } = clientContext;
2727
const {
28-
sdkKey,
29-
serviceEndpoints: { analyticsEventPath, diagnosticEventPath, includeAuthorizationHeader },
30-
tags,
28+
serviceEndpoints: { analyticsEventPath, diagnosticEventPath },
3129
} = basicConfiguration;
32-
const { crypto, info, requests } = platform;
30+
const { crypto, requests } = platform;
3331

34-
this.defaultHeaders = defaultHeaders(sdkKey, info, tags, includeAuthorizationHeader);
32+
this.defaultHeaders = { ...baseHeaders };
3533
this.eventsUri = getEventsUri(basicConfiguration.serviceEndpoints, analyticsEventPath, []);
3634
this.diagnosticEventsUri = getEventsUri(
3735
basicConfiguration.serviceEndpoints,

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 { 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: 4 additions & 2 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,11 +15,12 @@ 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

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

2426
// edge sdks sets this to false because they use the clientSideID

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
);

0 commit comments

Comments
 (0)