Skip to content

Commit bbb46cd

Browse files
committed
feat: Add support for Payload Filtering
1 parent e15c7e9 commit bbb46cd

File tree

11 files changed

+82
-2
lines changed

11 files changed

+82
-2
lines changed

contract-tests/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ app.get('/', (req, res) => {
2727
'all-flags-with-reasons',
2828
'tags',
2929
'big-segments',
30+
'filtering',
3031
'user-type',
3132
'migrations',
3233
'event-sampling',

contract-tests/sdkClientEntity.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,13 @@ export function makeSdkConfig(options, tag) {
2626
if (options.streaming) {
2727
cf.streamUri = options.streaming.baseUri;
2828
cf.streamInitialReconnectDelay = maybeTime(options.streaming.initialRetryDelayMs);
29+
cf.payloadFilterKey = options.streaming.filter;
2930
}
3031
if (options.polling) {
3132
cf.stream = false;
3233
cf.baseUri = options.polling.baseUri;
3334
cf.pollInterface = options.polling.pollIntervalMs / 1000;
35+
cf.payloadFilterKey = options.polling.filter;
3436
}
3537
if (options.events) {
3638
cf.allAttributesPrivate = options.events.allAttributesPrivate;

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ beforeEach(() => {
3232
diagnosticEventPath: '/diagnostic',
3333
analyticsEventPath: '/bulk',
3434
includeAuthorizationHeader: true,
35+
getPollingUri: (_: string): string => '/sdk/latest-all',
36+
getStreamingUri: (_: string): string => 'https://mockstream.ld.com/all',
37+
getFilteredUri: (_: string): string => '',
3538
},
3639
},
3740
platform: mockPlatform,

packages/shared/common/__tests__/options/ServiceEndpoints.test.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,11 @@ describe.each([
3333
expect(endpoints.events).toEqual(expected.eventsUri);
3434
});
3535
});
36+
37+
it('applies payload filter to polling and streaming endpoints', () => {
38+
const endpoints = new ServiceEndpoints('https://stream.launchdarkly.com', 'https://sdk.launchdarkly.com', 'https://events.launchdarkly.com', '/bulk', '/diagnostic', true, 'filterKey');
39+
40+
expect(endpoints.getStreamingUri('')).toEqual('https://stream.launchdarkly.com/?filter=filterKey');
41+
expect(endpoints.getPollingUri('')).toEqual('https://sdk.launchdarkly.com/?filter=filterKey');
42+
expect(endpoints.events).toEqual('https://events.launchdarkly.com');
43+
});

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ const basicConfig = {
2525
analyticsEventPath: '/bulk',
2626
diagnosticEventPath: '/diagnostic',
2727
includeAuthorizationHeader: true,
28+
getPollingUri: (_: string): string => '/sdk/latest-all',
29+
getStreamingUri: (_: string): string => '/all',
30+
getFilteredUri: (_: string): string => '',
2831
},
2932
};
3033
const testEventData1 = { eventId: 'test-event-data-1' };

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ const serviceEndpoints = {
1616
diagnosticEventPath: '/diagnostic',
1717
analyticsEventPath: '/bulk',
1818
includeAuthorizationHeader: true,
19+
getPollingUri: (_: string): string => '/sdk/latest-all',
20+
getStreamingUri: (_: string): string => 'https://mockstream.ld.com/all',
21+
getFilteredUri: (_: string): string => '',
1922
};
2023

2124
function getBasicConfiguration(inLogger: LDLogger) {

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,8 @@ class StreamingProcessor implements LDStreamProcessor {
4949
this.headers = defaultHeaders(sdkKey, info, tags);
5050
this.logger = logger;
5151
this.requests = requests;
52-
this.streamUri = `${basicConfiguration.serviceEndpoints.streaming}${streamUriPath}`;
52+
53+
this.streamUri = basicConfiguration.serviceEndpoints.getStreamingUri(streamUriPath);
5354
}
5455

5556
private logConnectionStarted() {

packages/shared/common/src/options/ServiceEndpoints.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export default class ServiceEndpoints {
1111
public readonly streaming: string;
1212
public readonly polling: string;
1313
public readonly events: string;
14+
private readonly payloadFilterKey?: string;
1415

1516
/** Valid paths are:
1617
* /bulk
@@ -36,12 +37,49 @@ export default class ServiceEndpoints {
3637
analyticsEventPath: string = '/bulk',
3738
diagnosticEventPath: string = '/diagnostic',
3839
includeAuthorizationHeader: boolean = true,
40+
payloadFilterKey?: string,
3941
) {
4042
this.streaming = canonicalizeUri(streaming);
4143
this.polling = canonicalizeUri(polling);
4244
this.events = canonicalizeUri(events);
4345
this.analyticsEventPath = analyticsEventPath;
4446
this.diagnosticEventPath = diagnosticEventPath;
4547
this.includeAuthorizationHeader = includeAuthorizationHeader;
48+
this.payloadFilterKey = payloadFilterKey;
49+
}
50+
51+
/**
52+
* Constructs and returns the URI to be used for a streaming connection.
53+
*/
54+
public getStreamingUri(path: string): string {
55+
return this.getFilteredUri(`${this.streaming}${path}`);
56+
}
57+
58+
/**
59+
* Constructs and returns the URI to be used for a polling connection.
60+
*/
61+
public getPollingUri(path: string): string {
62+
return this.getFilteredUri(`${this.polling}${path}`);
63+
}
64+
65+
/**
66+
* If a payload filter was present in the SDK config, this function will
67+
* apply that as a query parameter to the provided URI.
68+
*
69+
* If the provided uri cannot be parsed, this method will return that uri
70+
* unmodified.
71+
*/
72+
public getFilteredUri(uri: string): string {
73+
if (!this.payloadFilterKey) {
74+
return uri;
75+
}
76+
77+
try {
78+
let url = new URL(uri);
79+
url.searchParams.set('filter', this.payloadFilterKey);
80+
return url.toString();
81+
} catch (e) {
82+
return uri;
83+
}
4684
}
4785
}

packages/shared/sdk-server/src/api/options/LDOptions.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,22 @@ export interface LDOptions {
268268
* ASCII digits, period, hyphen, underscore. A string containing any other characters will be ignored.
269269
*/
270270
versionName?: string;
271+
272+
/**
273+
* LaunchDarkly Server SDKs historically downloaded all flag configuration and segments for a particular environment
274+
* during initialization.
275+
*
276+
* For some customers, this is an unacceptably large amount of data, and has contributed to performance issues
277+
* within their products.
278+
*
279+
* Filtered environments aim to solve this problem. By allowing customers to specify subsets of an environment's
280+
* flags using a filter key, SDKs will initialize faster and use less memory.
281+
*
282+
* This payload filter key only applies to the default streaming and polling data sources. It will not affect
283+
* TestData or FileData data sources, nor will it be applied to any data source provided through the featureStore
284+
* config property.
285+
*/
286+
payloadFilterKey?: string;
271287
};
272288

273289
/**

packages/shared/sdk-server/src/data_sources/Requestor.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ export default class Requestor implements LDFeatureRequestor {
3333
private readonly requests: Requests,
3434
) {
3535
this.headers = defaultHeaders(sdkKey, info, config.tags);
36-
this.uri = `${config.serviceEndpoints.polling}/sdk/latest-all`;
36+
this.uri = config.serviceEndpoints.getPollingUri('/sdk/latest-all');
3737
}
3838

3939
/**

0 commit comments

Comments
 (0)