launchdarkly-js-sdk-common 4.0.1 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.yaml +0 -1
- package/CHANGELOG.md +12 -0
- package/docs/typedoc.js +1 -1
- package/jest.config.js +0 -3
- package/package.json +1 -3
- package/src/EventProcessor.js +1 -1
- package/src/EventSender.js +3 -2
- package/src/Requestor.js +4 -3
- package/src/Stream.js +4 -3
- package/src/__tests__/.eslintrc.yaml +0 -1
- package/src/__tests__/LDClient-streaming-test.js +33 -0
- package/src/__tests__/LDClient-test.js +0 -8
- package/src/__tests__/Stream-test.js +1 -1
- package/src/__tests__/configuration-test.js +39 -0
- package/src/__tests__/headers-test.js +117 -0
- package/src/__tests__/utils-test.js +10 -75
- package/src/configuration.js +62 -1
- package/src/diagnosticEvents.js +2 -1
- package/src/errors.js +2 -0
- package/src/headers.js +38 -0
- package/src/index.js +23 -7
- package/src/messages.js +8 -0
- package/src/utils.js +9 -24
- package/test-types.ts +5 -3
- package/typings.d.ts +33 -10
package/.eslintrc.yaml
CHANGED
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to the `launchdarkly-js-sdk-common` package will be documented in this file. Changes that affect the dependent SDKs such as `launchdarkly-js-client-sdk` should also be logged in those projects, in the next release that uses the updated version of this package. This project adheres to [Semantic Versioning](http://semver.org).
|
|
4
4
|
|
|
5
|
+
## [4.0.3] - 2022-02-16
|
|
6
|
+
### Fixed:
|
|
7
|
+
- If the SDK receives invalid JSON data from a streaming connection (possibly as a result of the connection being cut off), it now uses its regular error-handling logic: the error is emitted as an `error` event or, if there are no `error` event listeners, it is logged. Previously, it would be thrown as an unhandled exception.
|
|
8
|
+
|
|
9
|
+
## [4.0.2] - 2022-01-25
|
|
10
|
+
### Removed:
|
|
11
|
+
- Removed the `version` export which was originally a constant inserted by the Rollup build, but was no longer usable since Rollup is no longer being used. The SDKs never used this export, since they have `version` properties of their own; the version string of `launchdarkly-js-sdk-common` was never meant to be exposed to applications.
|
|
12
|
+
|
|
5
13
|
## [4.0.1] - 2022-01-21
|
|
6
14
|
### Changed:
|
|
7
15
|
- This package is now published as a regular Node module. Previously, it was published as minified bundles created by Rollup. There was no need for this since Rollup is only needed for web code, and the `js-client-sdk` build already runs Rollup to embed the `js-sdk-common` code. Using Rollup caused the platform-dependent behavior of `uuid` to fail because the code for only one platform (browser or Node) was embedded.
|
|
@@ -15,6 +23,10 @@ All notable changes to the `launchdarkly-js-sdk-common` package will be document
|
|
|
15
23
|
- Removed the type `NonNullableLDEvaluationReason`, which was a side effect of the `LDEvaluationDetail.reason` being incorrectly defined before.
|
|
16
24
|
- Removed all types, properties, and functions that were deprecated as of the last 3.x release.
|
|
17
25
|
|
|
26
|
+
## [3.5.1] - 2022-02-17
|
|
27
|
+
### Fixed:
|
|
28
|
+
- If the SDK receives invalid JSON data from a streaming connection (possibly as a result of the connection being cut off), it now uses its regular error-handling logic: the error is emitted as an `error` event or, if there are no `error` event listeners, it is logged. Previously, it would be thrown as an unhandled exception.
|
|
29
|
+
|
|
18
30
|
## [3.5.0] - 2022-01-14
|
|
19
31
|
### Added:
|
|
20
32
|
- New configurable logger factory `commonBasicLogger` and `BasicLoggerOptions`. The `commonBasicLogger` method is not intended to be exported directly in the SDKs, but wrapped to provide platform-specific behavior.
|
package/docs/typedoc.js
CHANGED
|
@@ -4,7 +4,7 @@ module.exports = {
|
|
|
4
4
|
'**/node_modules/**',
|
|
5
5
|
'test-types.ts'
|
|
6
6
|
],
|
|
7
|
-
name: "LaunchDarkly Javascript SDK Core Components (4.0.
|
|
7
|
+
name: "LaunchDarkly Javascript SDK Core Components (4.0.2)",
|
|
8
8
|
readme: 'none', // don't add a home page with a copy of README.md
|
|
9
9
|
entryPoints: "/tmp/project-releaser/project/typings.d.ts",
|
|
10
10
|
entryPointStrategy: "expand"
|
package/jest.config.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "launchdarkly-js-sdk-common",
|
|
3
|
-
"version": "4.0
|
|
3
|
+
"version": "4.1.0",
|
|
4
4
|
"description": "LaunchDarkly SDK for JavaScript - common code",
|
|
5
5
|
"author": "LaunchDarkly <team@launchdarkly.com>",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -40,8 +40,6 @@
|
|
|
40
40
|
"launchdarkly-js-test-helpers": "1.1.0",
|
|
41
41
|
"prettier": "1.11.1",
|
|
42
42
|
"readline-sync": "^1.4.9",
|
|
43
|
-
"semver": "^5.5.0",
|
|
44
|
-
"semver-compare": "^1.0.0",
|
|
45
43
|
"typescript": "~4.4.4"
|
|
46
44
|
},
|
|
47
45
|
"dependencies": {
|
package/src/EventProcessor.js
CHANGED
|
@@ -15,7 +15,7 @@ function EventProcessor(
|
|
|
15
15
|
) {
|
|
16
16
|
const processor = {};
|
|
17
17
|
const eventSender = sender || EventSender(platform, environmentId, options);
|
|
18
|
-
const mainEventsUrl = options.eventsUrl
|
|
18
|
+
const mainEventsUrl = utils.appendUrlPath(options.eventsUrl, '/events/bulk/' + environmentId);
|
|
19
19
|
const summarizer = EventSummarizer();
|
|
20
20
|
const userFilter = UserFilter(options);
|
|
21
21
|
const inlineUsers = options.inlineUsersInEvents;
|
package/src/EventSender.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
const errors = require('./errors');
|
|
2
2
|
const utils = require('./utils');
|
|
3
3
|
const { v1: uuidv1 } = require('uuid');
|
|
4
|
+
const { getLDHeaders, transformHeaders } = require('./headers');
|
|
4
5
|
|
|
5
6
|
const MAX_URL_LENGTH = 2000;
|
|
6
7
|
|
|
7
8
|
function EventSender(platform, environmentId, options) {
|
|
8
9
|
const imageUrlPath = '/a/' + environmentId + '.gif';
|
|
9
|
-
const baseHeaders = utils.extend({ 'Content-Type': 'application/json' },
|
|
10
|
+
const baseHeaders = utils.extend({ 'Content-Type': 'application/json' }, getLDHeaders(platform, options));
|
|
10
11
|
const httpFallbackPing = platform.httpFallbackPing; // this will be set for us if we're in the browser SDK
|
|
11
12
|
const sender = {};
|
|
12
13
|
|
|
@@ -34,7 +35,7 @@ function EventSender(platform, environmentId, options) {
|
|
|
34
35
|
'X-LaunchDarkly-Payload-ID': payloadId,
|
|
35
36
|
});
|
|
36
37
|
return platform
|
|
37
|
-
.httpRequest('POST', url,
|
|
38
|
+
.httpRequest('POST', url, transformHeaders(headers, options), jsonBody)
|
|
38
39
|
.promise.then(result => {
|
|
39
40
|
if (!result) {
|
|
40
41
|
// This was a response from a fire-and-forget request, so we won't have a status.
|
package/src/Requestor.js
CHANGED
|
@@ -2,6 +2,7 @@ const utils = require('./utils');
|
|
|
2
2
|
const errors = require('./errors');
|
|
3
3
|
const messages = require('./messages');
|
|
4
4
|
const promiseCoalescer = require('./promiseCoalescer');
|
|
5
|
+
const { transformHeaders, getLDHeaders } = require('./headers');
|
|
5
6
|
|
|
6
7
|
const jsonContentType = 'application/json';
|
|
7
8
|
|
|
@@ -31,7 +32,7 @@ function Requestor(platform, options, environment) {
|
|
|
31
32
|
}
|
|
32
33
|
|
|
33
34
|
const method = body ? 'REPORT' : 'GET';
|
|
34
|
-
const headers =
|
|
35
|
+
const headers = getLDHeaders(platform, options);
|
|
35
36
|
if (body) {
|
|
36
37
|
headers['Content-Type'] = jsonContentType;
|
|
37
38
|
}
|
|
@@ -45,7 +46,7 @@ function Requestor(platform, options, environment) {
|
|
|
45
46
|
activeRequests[endpoint] = coalescer;
|
|
46
47
|
}
|
|
47
48
|
|
|
48
|
-
const req = platform.httpRequest(method, endpoint,
|
|
49
|
+
const req = platform.httpRequest(method, endpoint, transformHeaders(headers, options), body);
|
|
49
50
|
const p = req.promise.then(
|
|
50
51
|
result => {
|
|
51
52
|
if (result.status === 200) {
|
|
@@ -75,7 +76,7 @@ function Requestor(platform, options, environment) {
|
|
|
75
76
|
// Performs a GET request to an arbitrary path under baseUrl. Returns a Promise which will resolve
|
|
76
77
|
// with the parsed JSON response, or will be rejected if the request failed.
|
|
77
78
|
requestor.fetchJSON = function(path) {
|
|
78
|
-
return fetchJSON(baseUrl
|
|
79
|
+
return fetchJSON(utils.appendUrlPath(baseUrl, path), null);
|
|
79
80
|
};
|
|
80
81
|
|
|
81
82
|
// Requests the current state of all flags for the given user from LaunchDarkly. Returns a Promise
|
package/src/Stream.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const messages = require('./messages');
|
|
2
|
-
const {
|
|
2
|
+
const { appendUrlPath, base64URLEncode, objectHasOwnProperty } = require('./utils');
|
|
3
|
+
const { getLDHeaders, transformHeaders } = require('./headers');
|
|
3
4
|
|
|
4
5
|
// The underlying event source implementation is abstracted via the platform object, which should
|
|
5
6
|
// have these three properties:
|
|
@@ -20,7 +21,7 @@ function Stream(platform, config, environment, diagnosticsAccumulator) {
|
|
|
20
21
|
const baseUrl = config.streamUrl;
|
|
21
22
|
const logger = config.logger;
|
|
22
23
|
const stream = {};
|
|
23
|
-
const evalUrlPrefix = baseUrl
|
|
24
|
+
const evalUrlPrefix = appendUrlPath(baseUrl, '/eval/' + environment);
|
|
24
25
|
const useReport = config.useReport;
|
|
25
26
|
const withReasons = config.evaluationReasons;
|
|
26
27
|
const streamReconnectDelay = config.streamReconnectDelay;
|
|
@@ -98,7 +99,7 @@ function Stream(platform, config, environment, diagnosticsAccumulator) {
|
|
|
98
99
|
options.body = JSON.stringify(user);
|
|
99
100
|
} else {
|
|
100
101
|
// if we can't do REPORT, fall back to the old ping-based stream
|
|
101
|
-
url = baseUrl
|
|
102
|
+
url = appendUrlPath(baseUrl, '/ping/' + environment);
|
|
102
103
|
query = '';
|
|
103
104
|
}
|
|
104
105
|
} else {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import * as messages from '../messages';
|
|
1
2
|
import * as utils from '../utils';
|
|
2
3
|
|
|
3
4
|
import { AsyncQueue, eventSink, sleepAsync, withCloseable } from 'launchdarkly-js-test-helpers';
|
|
@@ -650,6 +651,38 @@ describe('LDClient streaming', () => {
|
|
|
650
651
|
});
|
|
651
652
|
});
|
|
652
653
|
|
|
654
|
+
describe('emits error if malformed JSON is received', () => {
|
|
655
|
+
const doMalformedJsonEventTest = async (eventName, eventData) => {
|
|
656
|
+
// First, verify that there isn't an unhandled rejection if we're not listening for an error
|
|
657
|
+
await withClientAndServer({}, async client => {
|
|
658
|
+
await client.waitForInitialization();
|
|
659
|
+
client.setStreaming(true);
|
|
660
|
+
|
|
661
|
+
const stream = await expectStreamConnecting(fullStreamUrlWithUser);
|
|
662
|
+
stream.eventSource.mockEmit(eventName, { data: eventData });
|
|
663
|
+
});
|
|
664
|
+
|
|
665
|
+
// Then, repeat the test using a listener to observe the error event
|
|
666
|
+
await withClientAndServer({}, async client => {
|
|
667
|
+
const errorEvents = new AsyncQueue();
|
|
668
|
+
client.on('error', e => errorEvents.add(e));
|
|
669
|
+
|
|
670
|
+
await client.waitForInitialization();
|
|
671
|
+
client.setStreaming(true);
|
|
672
|
+
|
|
673
|
+
const stream = await expectStreamConnecting(fullStreamUrlWithUser);
|
|
674
|
+
stream.eventSource.mockEmit(eventName, { data: eventData });
|
|
675
|
+
|
|
676
|
+
const e = await errorEvents.take();
|
|
677
|
+
expect(e.message).toEqual(messages.invalidData());
|
|
678
|
+
});
|
|
679
|
+
};
|
|
680
|
+
|
|
681
|
+
it('in put event', async () => doMalformedJsonEventTest('put', '{no'));
|
|
682
|
+
it('in patch event', async () => doMalformedJsonEventTest('patch', '{no'));
|
|
683
|
+
it('in delete event', async () => doMalformedJsonEventTest('delete', '{no'));
|
|
684
|
+
});
|
|
685
|
+
|
|
653
686
|
it('reconnects to stream if the user changes', async () => {
|
|
654
687
|
const user2 = { key: 'user2' };
|
|
655
688
|
const encodedUser2 = 'eyJrZXkiOiJ1c2VyMiJ9';
|
|
@@ -2,7 +2,6 @@ import * as LDClient from '../index';
|
|
|
2
2
|
import * as messages from '../messages';
|
|
3
3
|
import * as utils from '../utils';
|
|
4
4
|
|
|
5
|
-
import semverCompare from 'semver-compare';
|
|
6
5
|
import { eventSink, promisifySingle, sleepAsync, withCloseable, AsyncQueue } from 'launchdarkly-js-test-helpers';
|
|
7
6
|
|
|
8
7
|
import { respond, respondJson } from './mockHttp';
|
|
@@ -106,13 +105,6 @@ describe('LDClient', () => {
|
|
|
106
105
|
});
|
|
107
106
|
});
|
|
108
107
|
|
|
109
|
-
it('should contain package version', () => {
|
|
110
|
-
const version = LDClient.version;
|
|
111
|
-
// All client bundles above 1.0.7 should contain package version
|
|
112
|
-
const result = semverCompare(version, '1.0.6');
|
|
113
|
-
expect(result).toEqual(1);
|
|
114
|
-
});
|
|
115
|
-
|
|
116
108
|
async function verifyCustomHeader(sendLDHeaders, shouldGetHeaders) {
|
|
117
109
|
await withServers(async (baseConfig, pollServer) => {
|
|
118
110
|
await withClient(user, { ...baseConfig, sendLDHeaders }, async client => {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { DiagnosticsAccumulator } from '../diagnosticEvents';
|
|
2
2
|
import * as messages from '../messages';
|
|
3
3
|
import Stream from '../Stream';
|
|
4
|
-
import { getLDHeaders } from '../
|
|
4
|
+
import { getLDHeaders } from '../headers';
|
|
5
5
|
|
|
6
6
|
import { sleepAsync } from 'launchdarkly-js-test-helpers';
|
|
7
7
|
import EventSource from './EventSource-mock';
|
|
@@ -214,4 +214,43 @@ describe('configuration', () => {
|
|
|
214
214
|
expect(config.extraFunctionOption).toBe(fn);
|
|
215
215
|
await listener.expectError(messages.wrongOptionType('extraNumericOptionWithoutDefault', 'number', 'string'));
|
|
216
216
|
});
|
|
217
|
+
|
|
218
|
+
it('handles a valid application id', async () => {
|
|
219
|
+
const listener = errorListener();
|
|
220
|
+
const configIn = { application: { id: 'test-application' } };
|
|
221
|
+
expect(configuration.validate(configIn, listener.emitter, null, listener.logger).application.id).toEqual(
|
|
222
|
+
'test-application'
|
|
223
|
+
);
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
it('logs a warning with an invalid application id', async () => {
|
|
227
|
+
const listener = errorListener();
|
|
228
|
+
const configIn = { application: { id: 'test #$#$#' } };
|
|
229
|
+
expect(configuration.validate(configIn, listener.emitter, null, listener.logger).application.id).toBeUndefined();
|
|
230
|
+
await listener.expectWarningOnly(messages.invalidTagValue('application.id'));
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
it('handles a valid application version', async () => {
|
|
234
|
+
const listener = errorListener();
|
|
235
|
+
const configIn = { application: { version: 'test-version' } };
|
|
236
|
+
expect(configuration.validate(configIn, listener.emitter, null, listener.logger).application.version).toEqual(
|
|
237
|
+
'test-version'
|
|
238
|
+
);
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
it('logs a warning with an invalid application version', async () => {
|
|
242
|
+
const listener = errorListener();
|
|
243
|
+
const configIn = { application: { version: 'test #$#$#' } };
|
|
244
|
+
expect(
|
|
245
|
+
configuration.validate(configIn, listener.emitter, null, listener.logger).application.version
|
|
246
|
+
).toBeUndefined();
|
|
247
|
+
await listener.expectWarningOnly(messages.invalidTagValue('application.version'));
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
it('includes application id and version in tags when present', async () => {
|
|
251
|
+
expect(configuration.getTags({ application: { id: 'test-id', version: 'test-version' } })).toEqual({
|
|
252
|
+
'application-id': ['test-id'],
|
|
253
|
+
'application-version': ['test-version'],
|
|
254
|
+
});
|
|
255
|
+
});
|
|
217
256
|
});
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { getLDHeaders, transformHeaders } from '../headers';
|
|
2
|
+
import { getLDUserAgentString } from '../utils';
|
|
3
|
+
import * as stubPlatform from './stubPlatform';
|
|
4
|
+
|
|
5
|
+
describe('getLDHeaders', () => {
|
|
6
|
+
it('sends no headers unless sendLDHeaders is true', () => {
|
|
7
|
+
const platform = stubPlatform.defaults();
|
|
8
|
+
const headers = getLDHeaders(platform, {});
|
|
9
|
+
expect(headers).toEqual({});
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it('adds user-agent header', () => {
|
|
13
|
+
const platform = stubPlatform.defaults();
|
|
14
|
+
const headers = getLDHeaders(platform, { sendLDHeaders: true });
|
|
15
|
+
expect(headers).toMatchObject({ 'User-Agent': getLDUserAgentString(platform) });
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('adds user-agent header with custom name', () => {
|
|
19
|
+
const platform = stubPlatform.defaults();
|
|
20
|
+
platform.userAgentHeaderName = 'X-Fake-User-Agent';
|
|
21
|
+
const headers = getLDHeaders(platform, { sendLDHeaders: true });
|
|
22
|
+
expect(headers).toMatchObject({ 'X-Fake-User-Agent': getLDUserAgentString(platform) });
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('adds wrapper info if specified, without version', () => {
|
|
26
|
+
const platform = stubPlatform.defaults();
|
|
27
|
+
const headers = getLDHeaders(platform, { sendLDHeaders: true, wrapperName: 'FakeSDK' });
|
|
28
|
+
expect(headers).toMatchObject({
|
|
29
|
+
'User-Agent': getLDUserAgentString(platform),
|
|
30
|
+
'X-LaunchDarkly-Wrapper': 'FakeSDK',
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('adds wrapper info if specified, with version', () => {
|
|
35
|
+
const platform = stubPlatform.defaults();
|
|
36
|
+
const headers = getLDHeaders(platform, { sendLDHeaders: true, wrapperName: 'FakeSDK', wrapperVersion: '9.9' });
|
|
37
|
+
expect(headers).toMatchObject({
|
|
38
|
+
'User-Agent': getLDUserAgentString(platform),
|
|
39
|
+
'X-LaunchDarkly-Wrapper': 'FakeSDK/9.9',
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('sets the X-LaunchDarkly-Tags header with valid id and version.', () => {
|
|
44
|
+
const platform = stubPlatform.defaults();
|
|
45
|
+
const headers = getLDHeaders(platform, {
|
|
46
|
+
sendLDHeaders: true,
|
|
47
|
+
application: {
|
|
48
|
+
id: 'test-application',
|
|
49
|
+
version: 'test-version',
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
expect(headers).toMatchObject({
|
|
53
|
+
'User-Agent': getLDUserAgentString(platform),
|
|
54
|
+
'x-launchdarkly-tags': 'application-id/test-application application-version/test-version',
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('sets the X-LaunchDarkly-Tags header with just application id', () => {
|
|
59
|
+
const platform = stubPlatform.defaults();
|
|
60
|
+
const headers = getLDHeaders(platform, {
|
|
61
|
+
sendLDHeaders: true,
|
|
62
|
+
application: {
|
|
63
|
+
id: 'test-application',
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
expect(headers).toMatchObject({
|
|
67
|
+
'User-Agent': getLDUserAgentString(platform),
|
|
68
|
+
'x-launchdarkly-tags': 'application-id/test-application',
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('sets the X-LaunchDarkly-Tags header with just application version.', () => {
|
|
73
|
+
const platform = stubPlatform.defaults();
|
|
74
|
+
const headers = getLDHeaders(platform, {
|
|
75
|
+
sendLDHeaders: true,
|
|
76
|
+
application: {
|
|
77
|
+
version: 'test-version',
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
expect(headers).toMatchObject({
|
|
81
|
+
'User-Agent': getLDUserAgentString(platform),
|
|
82
|
+
'x-launchdarkly-tags': 'application-version/test-version',
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
describe('transformHeaders', () => {
|
|
88
|
+
it('does not modify the headers if the option is not available', () => {
|
|
89
|
+
const inputHeaders = { a: '1', b: '2' };
|
|
90
|
+
const headers = transformHeaders(inputHeaders, {});
|
|
91
|
+
expect(headers).toEqual(inputHeaders);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('modifies the headers if the option has a transform', () => {
|
|
95
|
+
const inputHeaders = { c: '3', d: '4' };
|
|
96
|
+
const outputHeaders = { c: '9', d: '4', e: '5' };
|
|
97
|
+
const headerTransform = input => {
|
|
98
|
+
const output = { ...input };
|
|
99
|
+
output['c'] = '9';
|
|
100
|
+
output['e'] = '5';
|
|
101
|
+
return output;
|
|
102
|
+
};
|
|
103
|
+
const headers = transformHeaders(inputHeaders, { requestHeaderTransform: headerTransform });
|
|
104
|
+
expect(headers).toEqual(outputHeaders);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('cannot mutate the input header object', () => {
|
|
108
|
+
const inputHeaders = { f: '6' };
|
|
109
|
+
const expectedInputHeaders = { f: '6' };
|
|
110
|
+
const headerMutate = input => {
|
|
111
|
+
input['f'] = '7'; // eslint-disable-line no-param-reassign
|
|
112
|
+
return input;
|
|
113
|
+
};
|
|
114
|
+
transformHeaders(inputHeaders, { requestHeaderTransform: headerMutate });
|
|
115
|
+
expect(inputHeaders).toEqual(expectedInputHeaders);
|
|
116
|
+
});
|
|
117
|
+
});
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
|
+
appendUrlPath,
|
|
2
3
|
base64URLEncode,
|
|
3
|
-
getLDHeaders,
|
|
4
|
-
transformHeaders,
|
|
5
4
|
getLDUserAgentString,
|
|
6
5
|
wrapPromiseCallback,
|
|
7
6
|
chunkUserEventsForUrl,
|
|
@@ -10,6 +9,13 @@ import {
|
|
|
10
9
|
import * as stubPlatform from './stubPlatform';
|
|
11
10
|
|
|
12
11
|
describe('utils', () => {
|
|
12
|
+
it('appendUrlPath', () => {
|
|
13
|
+
expect(appendUrlPath('http://base', '/path')).toEqual('http://base/path');
|
|
14
|
+
expect(appendUrlPath('http://base', 'path')).toEqual('http://base/path');
|
|
15
|
+
expect(appendUrlPath('http://base/', '/path')).toEqual('http://base/path');
|
|
16
|
+
expect(appendUrlPath('http://base/', '/path')).toEqual('http://base/path');
|
|
17
|
+
});
|
|
18
|
+
|
|
13
19
|
describe('wrapPromiseCallback', () => {
|
|
14
20
|
it('should resolve to the value', done => {
|
|
15
21
|
const promise = wrapPromiseCallback(Promise.resolve('woohoo'));
|
|
@@ -48,83 +54,12 @@ describe('utils', () => {
|
|
|
48
54
|
});
|
|
49
55
|
});
|
|
50
56
|
|
|
51
|
-
describe('getLDHeaders', () => {
|
|
52
|
-
it('sends no headers unless sendLDHeaders is true', () => {
|
|
53
|
-
const platform = stubPlatform.defaults();
|
|
54
|
-
const headers = getLDHeaders(platform, {});
|
|
55
|
-
expect(headers).toEqual({});
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
it('adds user-agent header', () => {
|
|
59
|
-
const platform = stubPlatform.defaults();
|
|
60
|
-
const headers = getLDHeaders(platform, { sendLDHeaders: true });
|
|
61
|
-
expect(headers).toMatchObject({ 'User-Agent': getLDUserAgentString(platform) });
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
it('adds user-agent header with custom name', () => {
|
|
65
|
-
const platform = stubPlatform.defaults();
|
|
66
|
-
platform.userAgentHeaderName = 'X-Fake-User-Agent';
|
|
67
|
-
const headers = getLDHeaders(platform, { sendLDHeaders: true });
|
|
68
|
-
expect(headers).toMatchObject({ 'X-Fake-User-Agent': getLDUserAgentString(platform) });
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
it('adds wrapper info if specified, without version', () => {
|
|
72
|
-
const platform = stubPlatform.defaults();
|
|
73
|
-
const headers = getLDHeaders(platform, { sendLDHeaders: true, wrapperName: 'FakeSDK' });
|
|
74
|
-
expect(headers).toMatchObject({
|
|
75
|
-
'User-Agent': getLDUserAgentString(platform),
|
|
76
|
-
'X-LaunchDarkly-Wrapper': 'FakeSDK',
|
|
77
|
-
});
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
it('adds wrapper info if specified, with version', () => {
|
|
81
|
-
const platform = stubPlatform.defaults();
|
|
82
|
-
const headers = getLDHeaders(platform, { sendLDHeaders: true, wrapperName: 'FakeSDK', wrapperVersion: '9.9' });
|
|
83
|
-
expect(headers).toMatchObject({
|
|
84
|
-
'User-Agent': getLDUserAgentString(platform),
|
|
85
|
-
'X-LaunchDarkly-Wrapper': 'FakeSDK/9.9',
|
|
86
|
-
});
|
|
87
|
-
});
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
describe('transformHeaders', () => {
|
|
91
|
-
it('does not modify the headers if the option is not available', () => {
|
|
92
|
-
const inputHeaders = { a: '1', b: '2' };
|
|
93
|
-
const headers = transformHeaders(inputHeaders, {});
|
|
94
|
-
expect(headers).toEqual(inputHeaders);
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
it('modifies the headers if the option has a transform', () => {
|
|
98
|
-
const inputHeaders = { c: '3', d: '4' };
|
|
99
|
-
const outputHeaders = { c: '9', d: '4', e: '5' };
|
|
100
|
-
const headerTransform = input => {
|
|
101
|
-
const output = { ...input };
|
|
102
|
-
output['c'] = '9';
|
|
103
|
-
output['e'] = '5';
|
|
104
|
-
return output;
|
|
105
|
-
};
|
|
106
|
-
const headers = transformHeaders(inputHeaders, { requestHeaderTransform: headerTransform });
|
|
107
|
-
expect(headers).toEqual(outputHeaders);
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
it('cannot mutate the input header object', () => {
|
|
111
|
-
const inputHeaders = { f: '6' };
|
|
112
|
-
const expectedInputHeaders = { f: '6' };
|
|
113
|
-
const headerMutate = input => {
|
|
114
|
-
input['f'] = '7'; // eslint-disable-line no-param-reassign
|
|
115
|
-
return input;
|
|
116
|
-
};
|
|
117
|
-
transformHeaders(inputHeaders, { requestHeaderTransform: headerMutate });
|
|
118
|
-
expect(inputHeaders).toEqual(expectedInputHeaders);
|
|
119
|
-
});
|
|
120
|
-
});
|
|
121
|
-
|
|
122
57
|
describe('getLDUserAgentString', () => {
|
|
123
|
-
it('uses platform user-agent and
|
|
58
|
+
it('uses platform user-agent and unknown version by default', () => {
|
|
124
59
|
const platform = stubPlatform.defaults();
|
|
125
60
|
platform.version = undefined;
|
|
126
61
|
const ua = getLDUserAgentString(platform);
|
|
127
|
-
expect(ua).toEqual('stubClient
|
|
62
|
+
expect(ua).toEqual('stubClient/?');
|
|
128
63
|
});
|
|
129
64
|
|
|
130
65
|
it('uses platform user-agent and platform version if provided', () => {
|
package/src/configuration.js
CHANGED
|
@@ -38,8 +38,38 @@ const baseOptionDefs = {
|
|
|
38
38
|
wrapperVersion: { type: 'string' },
|
|
39
39
|
stateProvider: { type: 'object' }, // not a public option, used internally
|
|
40
40
|
autoAliasingOptOut: { default: false },
|
|
41
|
+
application: { validator: applicationConfigValidator },
|
|
41
42
|
};
|
|
42
43
|
|
|
44
|
+
/**
|
|
45
|
+
* Expression to validate characters that are allowed in tag keys and values.
|
|
46
|
+
*/
|
|
47
|
+
const allowedTagCharacters = /^(\w|\.|-)+$/;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Verify that a value meets the requirements for a tag value.
|
|
51
|
+
* @param {Object} config
|
|
52
|
+
* @param {string} tagValue
|
|
53
|
+
*/
|
|
54
|
+
function validateTagValue(name, config, tagValue, logger) {
|
|
55
|
+
if (typeof tagValue !== 'string' || !tagValue.match(allowedTagCharacters)) {
|
|
56
|
+
logger.warn(messages.invalidTagValue(name));
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
59
|
+
return tagValue;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function applicationConfigValidator(name, config, value, logger) {
|
|
63
|
+
const validated = {};
|
|
64
|
+
if (value.id) {
|
|
65
|
+
validated.id = validateTagValue(`${name}.id`, config, value.id, logger);
|
|
66
|
+
}
|
|
67
|
+
if (value.version) {
|
|
68
|
+
validated.version = validateTagValue(`${name}.version`, config, value.version, logger);
|
|
69
|
+
}
|
|
70
|
+
return validated;
|
|
71
|
+
}
|
|
72
|
+
|
|
43
73
|
function validate(options, emitter, extraOptionDefs, logger) {
|
|
44
74
|
const optionDefs = utils.extend({ logger: { default: logger } }, baseOptionDefs, extraOptionDefs);
|
|
45
75
|
|
|
@@ -104,7 +134,15 @@ function validate(options, emitter, extraOptionDefs, logger) {
|
|
|
104
134
|
reportArgumentError(messages.unknownOption(name));
|
|
105
135
|
} else {
|
|
106
136
|
const expectedType = optionDef.type || typeDescForValue(optionDef.default);
|
|
107
|
-
|
|
137
|
+
const validator = optionDef.validator;
|
|
138
|
+
if (validator) {
|
|
139
|
+
const validated = validator(name, config, config[name], logger);
|
|
140
|
+
if (validated !== undefined) {
|
|
141
|
+
ret[name] = validated;
|
|
142
|
+
} else {
|
|
143
|
+
delete ret[name];
|
|
144
|
+
}
|
|
145
|
+
} else if (expectedType !== 'any') {
|
|
108
146
|
const allowedTypes = expectedType.split('|');
|
|
109
147
|
const actualType = typeDescForValue(value);
|
|
110
148
|
if (allowedTypes.indexOf(actualType) < 0) {
|
|
@@ -145,7 +183,30 @@ function validate(options, emitter, extraOptionDefs, logger) {
|
|
|
145
183
|
return config;
|
|
146
184
|
}
|
|
147
185
|
|
|
186
|
+
/**
|
|
187
|
+
* Get tags for the specified configuration.
|
|
188
|
+
*
|
|
189
|
+
* If any additional tags are added to the configuration, then the tags from
|
|
190
|
+
* this method should be extended with those.
|
|
191
|
+
* @param {Object} config The already valiated configuration.
|
|
192
|
+
* @returns {Object} The tag configuration.
|
|
193
|
+
*/
|
|
194
|
+
function getTags(config) {
|
|
195
|
+
const tags = {};
|
|
196
|
+
if (config) {
|
|
197
|
+
if (config.application && config.application.id !== undefined && config.application.id !== null) {
|
|
198
|
+
tags['application-id'] = [config.application.id];
|
|
199
|
+
}
|
|
200
|
+
if (config.application && config.application.version !== undefined && config.application.id !== null) {
|
|
201
|
+
tags['application-version'] = [config.application.version];
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return tags;
|
|
206
|
+
}
|
|
207
|
+
|
|
148
208
|
module.exports = {
|
|
149
209
|
baseOptionDefs,
|
|
150
210
|
validate,
|
|
211
|
+
getTags,
|
|
151
212
|
};
|
package/src/diagnosticEvents.js
CHANGED
|
@@ -5,6 +5,7 @@ const { v1: uuidv1 } = require('uuid');
|
|
|
5
5
|
|
|
6
6
|
const { baseOptionDefs } = require('./configuration');
|
|
7
7
|
const messages = require('./messages');
|
|
8
|
+
const { appendUrlPath } = require('./utils');
|
|
8
9
|
|
|
9
10
|
function DiagnosticId(sdkKey) {
|
|
10
11
|
const ret = {
|
|
@@ -80,7 +81,7 @@ function DiagnosticsManager(
|
|
|
80
81
|
) {
|
|
81
82
|
const combinedMode = !!platform.diagnosticUseCombinedEvent;
|
|
82
83
|
const localStorageKey = 'ld:' + environmentId + ':$diagnostics';
|
|
83
|
-
const diagnosticEventsUrl = config.eventsUrl
|
|
84
|
+
const diagnosticEventsUrl = appendUrlPath(config.eventsUrl, '/events/diagnostic/' + environmentId);
|
|
84
85
|
const periodicInterval = config.diagnosticRecordingInterval;
|
|
85
86
|
const acc = accumulator;
|
|
86
87
|
const initialEventSamplingInterval = 4; // used only in combined mode - see start()
|
package/src/errors.js
CHANGED
|
@@ -18,6 +18,7 @@ const LDInvalidUserError = createCustomError('LaunchDarklyInvalidUserError');
|
|
|
18
18
|
const LDInvalidEventKeyError = createCustomError('LaunchDarklyInvalidEventKeyError');
|
|
19
19
|
const LDInvalidArgumentError = createCustomError('LaunchDarklyInvalidArgumentError');
|
|
20
20
|
const LDFlagFetchError = createCustomError('LaunchDarklyFlagFetchError');
|
|
21
|
+
const LDInvalidDataError = createCustomError('LaunchDarklyInvalidDataError');
|
|
21
22
|
|
|
22
23
|
function isHttpErrorRecoverable(status) {
|
|
23
24
|
if (status >= 400 && status < 500) {
|
|
@@ -32,6 +33,7 @@ module.exports = {
|
|
|
32
33
|
LDInvalidUserError,
|
|
33
34
|
LDInvalidEventKeyError,
|
|
34
35
|
LDInvalidArgumentError,
|
|
36
|
+
LDInvalidDataError,
|
|
35
37
|
LDFlagFetchError,
|
|
36
38
|
isHttpErrorRecoverable,
|
|
37
39
|
};
|
package/src/headers.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
const { getLDUserAgentString } = require('./utils');
|
|
2
|
+
const configuration = require('./configuration');
|
|
3
|
+
|
|
4
|
+
function getLDHeaders(platform, options) {
|
|
5
|
+
if (options && !options.sendLDHeaders) {
|
|
6
|
+
return {};
|
|
7
|
+
}
|
|
8
|
+
const h = {};
|
|
9
|
+
h[platform.userAgentHeaderName || 'User-Agent'] = getLDUserAgentString(platform);
|
|
10
|
+
if (options && options.wrapperName) {
|
|
11
|
+
h['X-LaunchDarkly-Wrapper'] = options.wrapperVersion
|
|
12
|
+
? options.wrapperName + '/' + options.wrapperVersion
|
|
13
|
+
: options.wrapperName;
|
|
14
|
+
}
|
|
15
|
+
const tags = configuration.getTags(options);
|
|
16
|
+
const tagKeys = Object.keys(tags);
|
|
17
|
+
if (tagKeys.length) {
|
|
18
|
+
h['x-launchdarkly-tags'] = tagKeys
|
|
19
|
+
.sort()
|
|
20
|
+
.flatMap(
|
|
21
|
+
key => (Array.isArray(tags[key]) ? tags[key].sort().map(value => `${key}/${value}`) : [`${key}/${tags[key]}`])
|
|
22
|
+
)
|
|
23
|
+
.join(' ');
|
|
24
|
+
}
|
|
25
|
+
return h;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function transformHeaders(headers, options) {
|
|
29
|
+
if (!options || !options.requestHeaderTransform) {
|
|
30
|
+
return headers;
|
|
31
|
+
}
|
|
32
|
+
return options.requestHeaderTransform({ ...headers });
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
module.exports = {
|
|
36
|
+
getLDHeaders,
|
|
37
|
+
transformHeaders,
|
|
38
|
+
};
|
package/src/index.js
CHANGED
|
@@ -386,6 +386,14 @@ function initialize(env, user, specifiedOptions, platform, extraOptionDefs) {
|
|
|
386
386
|
if (!ident.getUser()) {
|
|
387
387
|
return;
|
|
388
388
|
}
|
|
389
|
+
const tryParseData = jsonData => {
|
|
390
|
+
try {
|
|
391
|
+
return JSON.parse(jsonData);
|
|
392
|
+
} catch (err) {
|
|
393
|
+
emitter.maybeReportError(new errors.LDInvalidDataError(messages.invalidData()));
|
|
394
|
+
return undefined;
|
|
395
|
+
}
|
|
396
|
+
};
|
|
389
397
|
stream.connect(ident.getUser(), hash, {
|
|
390
398
|
ping: function() {
|
|
391
399
|
logger.debug(messages.debugStreamPing());
|
|
@@ -404,12 +412,20 @@ function initialize(env, user, specifiedOptions, platform, extraOptionDefs) {
|
|
|
404
412
|
});
|
|
405
413
|
},
|
|
406
414
|
put: function(e) {
|
|
407
|
-
const data =
|
|
415
|
+
const data = tryParseData(e.data);
|
|
416
|
+
if (!data) {
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
408
419
|
logger.debug(messages.debugStreamPut());
|
|
409
|
-
replaceAllFlags(data);
|
|
420
|
+
replaceAllFlags(data);
|
|
421
|
+
// Don't wait for this Promise to be resolved; note that replaceAllFlags is guaranteed
|
|
422
|
+
// never to have an unhandled rejection
|
|
410
423
|
},
|
|
411
424
|
patch: function(e) {
|
|
412
|
-
const data =
|
|
425
|
+
const data = tryParseData(e.data);
|
|
426
|
+
if (!data) {
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
413
429
|
// If both the flag and the patch have a version property, then the patch version must be
|
|
414
430
|
// greater than the flag version for us to accept the patch. If either one has no version
|
|
415
431
|
// then the patch always succeeds.
|
|
@@ -432,7 +448,10 @@ function initialize(env, user, specifiedOptions, platform, extraOptionDefs) {
|
|
|
432
448
|
}
|
|
433
449
|
},
|
|
434
450
|
delete: function(e) {
|
|
435
|
-
const data =
|
|
451
|
+
const data = tryParseData(e.data);
|
|
452
|
+
if (!data) {
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
436
455
|
if (!flags[data.key] || flags[data.key].version < data.version) {
|
|
437
456
|
logger.debug(messages.debugStreamDelete(data.key));
|
|
438
457
|
const mods = {};
|
|
@@ -760,13 +779,10 @@ function initialize(env, user, specifiedOptions, platform, extraOptionDefs) {
|
|
|
760
779
|
};
|
|
761
780
|
}
|
|
762
781
|
|
|
763
|
-
const version = VERSION;
|
|
764
|
-
|
|
765
782
|
module.exports = {
|
|
766
783
|
initialize,
|
|
767
784
|
commonBasicLogger,
|
|
768
785
|
errors,
|
|
769
786
|
messages,
|
|
770
787
|
utils,
|
|
771
|
-
version,
|
|
772
788
|
};
|
package/src/messages.js
CHANGED
|
@@ -68,6 +68,10 @@ const invalidUser = function() {
|
|
|
68
68
|
return 'Invalid user specified.' + docLink;
|
|
69
69
|
};
|
|
70
70
|
|
|
71
|
+
const invalidData = function() {
|
|
72
|
+
return 'Invalid data received from LaunchDarkly; connection may have been interrupted';
|
|
73
|
+
};
|
|
74
|
+
|
|
71
75
|
const bootstrapOldFormat = function() {
|
|
72
76
|
return (
|
|
73
77
|
'LaunchDarkly client was initialized with bootstrap data that did not include flag metadata. ' +
|
|
@@ -176,6 +180,8 @@ const debugPostingDiagnosticEvent = function(event) {
|
|
|
176
180
|
return 'sending diagnostic event (' + event.kind + ')';
|
|
177
181
|
};
|
|
178
182
|
|
|
183
|
+
const invalidTagValue = name => `Config option "${name}" must only contain letters, numbers, ., _ or -.`;
|
|
184
|
+
|
|
179
185
|
module.exports = {
|
|
180
186
|
bootstrapInvalid,
|
|
181
187
|
bootstrapOldFormat,
|
|
@@ -201,7 +207,9 @@ module.exports = {
|
|
|
201
207
|
httpUnavailable,
|
|
202
208
|
identifyDisabled,
|
|
203
209
|
invalidContentType,
|
|
210
|
+
invalidData,
|
|
204
211
|
invalidKey,
|
|
212
|
+
invalidTagValue,
|
|
205
213
|
invalidUser,
|
|
206
214
|
localStorageUnavailable,
|
|
207
215
|
networkError,
|
package/src/utils.js
CHANGED
|
@@ -3,6 +3,13 @@ const fastDeepEqual = require('fast-deep-equal');
|
|
|
3
3
|
|
|
4
4
|
const userAttrsToStringify = ['key', 'secondary', 'ip', 'country', 'email', 'firstName', 'lastName', 'avatar', 'name'];
|
|
5
5
|
|
|
6
|
+
function appendUrlPath(baseUrl, path) {
|
|
7
|
+
// Ensure that URL concatenation is done correctly regardless of whether the
|
|
8
|
+
// base URL has a trailing slash or not.
|
|
9
|
+
const trimBaseUrl = baseUrl.endsWith('/') ? baseUrl.substring(0, baseUrl.length - 1) : baseUrl;
|
|
10
|
+
return trimBaseUrl + (path.startsWith('/') ? '' : '/') + path;
|
|
11
|
+
}
|
|
12
|
+
|
|
6
13
|
// See http://ecmanaut.blogspot.com/2006/07/encoding-decoding-utf8-in-javascript.html
|
|
7
14
|
function btoa(s) {
|
|
8
15
|
const escaped = unescape(encodeURIComponent(s));
|
|
@@ -146,31 +153,10 @@ function chunkUserEventsForUrl(maxLength, events) {
|
|
|
146
153
|
}
|
|
147
154
|
|
|
148
155
|
function getLDUserAgentString(platform) {
|
|
149
|
-
const version = platform.version ||
|
|
156
|
+
const version = platform.version || '?';
|
|
150
157
|
return platform.userAgent + '/' + version;
|
|
151
158
|
}
|
|
152
159
|
|
|
153
|
-
function getLDHeaders(platform, options) {
|
|
154
|
-
if (options && !options.sendLDHeaders) {
|
|
155
|
-
return {};
|
|
156
|
-
}
|
|
157
|
-
const h = {};
|
|
158
|
-
h[platform.userAgentHeaderName || 'User-Agent'] = getLDUserAgentString(platform);
|
|
159
|
-
if (options && options.wrapperName) {
|
|
160
|
-
h['X-LaunchDarkly-Wrapper'] = options.wrapperVersion
|
|
161
|
-
? options.wrapperName + '/' + options.wrapperVersion
|
|
162
|
-
: options.wrapperName;
|
|
163
|
-
}
|
|
164
|
-
return h;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
function transformHeaders(headers, options) {
|
|
168
|
-
if (!options || !options.requestHeaderTransform) {
|
|
169
|
-
return headers;
|
|
170
|
-
}
|
|
171
|
-
return options.requestHeaderTransform({ ...headers });
|
|
172
|
-
}
|
|
173
|
-
|
|
174
160
|
function extend(...objects) {
|
|
175
161
|
return objects.reduce((acc, obj) => ({ ...acc, ...obj }), {});
|
|
176
162
|
}
|
|
@@ -196,18 +182,17 @@ function sanitizeUser(user) {
|
|
|
196
182
|
}
|
|
197
183
|
|
|
198
184
|
module.exports = {
|
|
185
|
+
appendUrlPath,
|
|
199
186
|
base64URLEncode,
|
|
200
187
|
btoa,
|
|
201
188
|
chunkUserEventsForUrl,
|
|
202
189
|
clone,
|
|
203
190
|
deepEquals,
|
|
204
191
|
extend,
|
|
205
|
-
getLDHeaders,
|
|
206
192
|
getLDUserAgentString,
|
|
207
193
|
objectHasOwnProperty,
|
|
208
194
|
onNextTick,
|
|
209
195
|
sanitizeUser,
|
|
210
|
-
transformHeaders,
|
|
211
196
|
transformValuesToVersionedValues,
|
|
212
197
|
transformVersionedValuesToValues,
|
|
213
198
|
wrapPromiseCallback,
|
package/test-types.ts
CHANGED
|
@@ -4,8 +4,6 @@
|
|
|
4
4
|
|
|
5
5
|
import * as ld from 'launchdarkly-js-sdk-common';
|
|
6
6
|
|
|
7
|
-
var ver: string = ld.version;
|
|
8
|
-
|
|
9
7
|
var userWithKeyOnly: ld.LDUser = { key: 'user' };
|
|
10
8
|
var anonUserWithNoKey: ld.LDUser = { anonymous: true };
|
|
11
9
|
var anonUserWithKey: ld.LDUser = { key: 'anon-user', anonymous: true };
|
|
@@ -49,7 +47,11 @@ var allBaseOptions: ld.LDOptionsBase = {
|
|
|
49
47
|
sendEventsOnlyForVariation: true,
|
|
50
48
|
flushInterval: 1,
|
|
51
49
|
streamReconnectDelay: 1,
|
|
52
|
-
logger: logger
|
|
50
|
+
logger: logger,
|
|
51
|
+
application: {
|
|
52
|
+
version: 'version',
|
|
53
|
+
id: 'id'
|
|
54
|
+
}
|
|
53
55
|
};
|
|
54
56
|
|
|
55
57
|
var client: ld.LDClientBase = {} as ld.LDClientBase; // wouldn't do this in real life, it's just so the following statements will compile
|
package/typings.d.ts
CHANGED
|
@@ -2,12 +2,6 @@
|
|
|
2
2
|
* Basic LaunchDarkly JavaScript client interfaces, shared between the browser SDK and the Electron SDK.
|
|
3
3
|
*/
|
|
4
4
|
declare module 'launchdarkly-js-sdk-common' {
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* The current version string of the SDK.
|
|
8
|
-
*/
|
|
9
|
-
export const version: string;
|
|
10
|
-
|
|
11
5
|
/**
|
|
12
6
|
* The types of values a feature flag can have.
|
|
13
7
|
*
|
|
@@ -121,10 +115,14 @@ declare module 'launchdarkly-js-sdk-common' {
|
|
|
121
115
|
/**
|
|
122
116
|
* Whether or not to include custom HTTP headers when requesting flags from LaunchDarkly.
|
|
123
117
|
*
|
|
124
|
-
*
|
|
125
|
-
*
|
|
126
|
-
*
|
|
127
|
-
*
|
|
118
|
+
* These are used to send metadata about the SDK (such as the version). They
|
|
119
|
+
* are also used to send the application.id and application.version set in
|
|
120
|
+
* the options.
|
|
121
|
+
*
|
|
122
|
+
* This defaults to true (custom headers will be sent). One reason you might
|
|
123
|
+
* want to set it to false is that the presence of custom headers causes
|
|
124
|
+
* browsers to make an extra OPTIONS request (a CORS preflight check) before
|
|
125
|
+
* each flag request, which could affect performance.
|
|
128
126
|
*/
|
|
129
127
|
sendLDHeaders?: boolean;
|
|
130
128
|
|
|
@@ -261,6 +259,31 @@ declare module 'launchdarkly-js-sdk-common' {
|
|
|
261
259
|
* The default value is `false`.
|
|
262
260
|
*/
|
|
263
261
|
autoAliasingOptOut?: boolean;
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Information about the application where the LaunchDarkly SDK is running.
|
|
265
|
+
*/
|
|
266
|
+
application?: {
|
|
267
|
+
/**
|
|
268
|
+
* A unique identifier representing the application where the LaunchDarkly SDK is running.
|
|
269
|
+
*
|
|
270
|
+
* This can be specified as any string value as long as it only uses the following characters: ASCII letters,
|
|
271
|
+
* ASCII digits, period, hyphen, underscore. A string containing any other characters will be ignored.
|
|
272
|
+
*
|
|
273
|
+
* Example: `authentication-service`
|
|
274
|
+
*/
|
|
275
|
+
id?: string;
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* A unique identifier representing the version of the application where the LaunchDarkly SDK is running.
|
|
279
|
+
*
|
|
280
|
+
* This can be specified as any string value as long as it only uses the following characters: ASCII letters,
|
|
281
|
+
* ASCII digits, period, hyphen, underscore. A string containing any other characters will be ignored.
|
|
282
|
+
*
|
|
283
|
+
* Example: `1.0.0` (standard version string) or `abcdef` (sha prefix)
|
|
284
|
+
*/
|
|
285
|
+
version?: string;
|
|
286
|
+
}
|
|
264
287
|
}
|
|
265
288
|
|
|
266
289
|
/**
|