launchdarkly-js-sdk-common
Advanced tools
Comparing version 4.3.1 to 4.3.2
@@ -5,2 +5,6 @@ # Change log | ||
## [4.3.1] - 2022-10-17 | ||
### Fixed: | ||
- Fixed an issue that prevented the `flag-used` inspector from being called. | ||
## [4.3.0] - 2022-10-17 | ||
@@ -7,0 +11,0 @@ ### Added: |
{ | ||
"name": "launchdarkly-js-sdk-common", | ||
"version": "4.3.1", | ||
"version": "4.3.2", | ||
"description": "LaunchDarkly SDK for JavaScript - common code", | ||
@@ -5,0 +5,0 @@ "author": "LaunchDarkly <team@launchdarkly.com>", |
@@ -173,2 +173,3 @@ import { DiagnosticsAccumulator } from '../diagnosticEvents'; | ||
es.mockError('test error'); | ||
await sleepAsync(10); | ||
const created1 = await platform.testing.expectStream(); | ||
@@ -189,2 +190,36 @@ const es1 = created1.eventSource; | ||
it.each([401, 403])('does not reconnect after an unrecoverable error', async status => { | ||
const config = { ...defaultConfig, streamReconnectDelay: 1, useReport: false }; | ||
const stream = new Stream(platform, config, envName); | ||
stream.connect(user); | ||
const created = await platform.testing.expectStream(); | ||
const es = created.eventSource; | ||
expect(es.readyState).toBe(EventSource.CONNECTING); | ||
es.mockOpen(); | ||
expect(es.readyState).toBe(EventSource.OPEN); | ||
es.mockError({ status }); | ||
await sleepAsync(10); | ||
expect(platform.testing.eventSourcesCreated.length()).toEqual(0); | ||
}); | ||
it.each([400, 408, 429])('does reconnect after a recoverable error', async status => { | ||
const config = { ...defaultConfig, streamReconnectDelay: 1, useReport: false }; | ||
const stream = new Stream(platform, config, envName); | ||
stream.connect(user); | ||
const created = await platform.testing.expectStream(); | ||
const es = created.eventSource; | ||
expect(es.readyState).toBe(EventSource.CONNECTING); | ||
es.mockOpen(); | ||
expect(es.readyState).toBe(EventSource.OPEN); | ||
es.mockError({ status }); | ||
await sleepAsync(10); | ||
expect(platform.testing.eventSourcesCreated.length()).toEqual(1); | ||
}); | ||
it('logs a warning for only the first failed connection attempt', async () => { | ||
@@ -202,2 +237,3 @@ const config = { ...defaultConfig, streamReconnectDelay: 1 }; | ||
es.mockError('test error'); | ||
await sleepAsync(10); | ||
const created1 = await platform.testing.expectStream(); | ||
@@ -227,2 +263,3 @@ es = created1.eventSource; | ||
es.mockError('test error #1'); | ||
await sleepAsync(10); | ||
const created1 = await platform.testing.expectStream(); | ||
@@ -239,2 +276,3 @@ es = created1.eventSource; | ||
es.mockError('test error #2'); | ||
await sleepAsync(10); | ||
const created1 = await platform.testing.expectStream(); | ||
@@ -247,4 +285,4 @@ es = created1.eventSource; | ||
expect(logger.output.warn).toEqual([ | ||
messages.streamError('test error #1', 1), | ||
messages.streamError('test error #2', 1), | ||
expect.stringContaining('test error #1'), | ||
expect.stringContaining('test error #2'), | ||
]); | ||
@@ -251,0 +289,0 @@ }); |
@@ -20,5 +20,6 @@ const { getLDUserAgentString } = require('./utils'); | ||
.sort() | ||
.flatMap( | ||
.map( | ||
key => (Array.isArray(tags[key]) ? tags[key].sort().map(value => `${key}/${value}`) : [`${key}/${tags[key]}`]) | ||
) | ||
.reduce((flattened, item) => flattened.concat(item), []) | ||
.join(' '); | ||
@@ -25,0 +26,0 @@ } |
@@ -126,3 +126,3 @@ const errors = require('./errors'); | ||
errorString(err) + | ||
', will continue retrying every ' + | ||
', will continue retrying after ' + | ||
streamReconnectDelay + | ||
@@ -135,2 +135,4 @@ ' milliseconds.' | ||
const unrecoverableStreamError = err => `Error on stream connection ${errorString(err)}, giving up permanently`; | ||
const wrongOptionType = (name, expectedType, actualType) => | ||
@@ -233,2 +235,3 @@ 'Config option "' + name + '" should be of type ' + expectedType + ', got ' + actualType + ', using default value'; | ||
unknownOption, | ||
unrecoverableStreamError, | ||
userNotSpecified, | ||
@@ -235,0 +238,0 @@ wrongOptionType, |
const messages = require('./messages'); | ||
const { appendUrlPath, base64URLEncode, objectHasOwnProperty } = require('./utils'); | ||
const { getLDHeaders, transformHeaders } = require('./headers'); | ||
const { isHttpErrorRecoverable } = require('./errors'); | ||
@@ -19,2 +20,4 @@ // The underlying event source implementation is abstracted via the platform object, which should | ||
const streamReadTimeoutMillis = 5 * 60 * 1000; // 5 minutes | ||
const maxRetryDelay = 30 * 1000; // Maximum retry delay 30 seconds. | ||
const jitterRatio = 0.5; // Delay should be 50%-100% of calculated time. | ||
@@ -28,3 +31,3 @@ function Stream(platform, config, environment, diagnosticsAccumulator) { | ||
const withReasons = config.evaluationReasons; | ||
const streamReconnectDelay = config.streamReconnectDelay; | ||
const baseReconnectDelay = config.streamReconnectDelay; | ||
const headers = getLDHeaders(platform, config); | ||
@@ -38,3 +41,19 @@ let firstConnectionErrorLogged = false; | ||
let handlers = null; | ||
let retryCount = 0; | ||
function backoff() { | ||
const delay = baseReconnectDelay * Math.pow(2, retryCount); | ||
return delay > maxRetryDelay ? maxRetryDelay : delay; | ||
} | ||
function jitter(computedDelayMillis) { | ||
return computedDelayMillis - Math.trunc(Math.random() * jitterRatio * computedDelayMillis); | ||
} | ||
function getNextRetryDelay() { | ||
const delay = jitter(backoff()); | ||
retryCount += 1; | ||
return delay; | ||
} | ||
stream.connect = function(newUser, newHash, newHandlers) { | ||
@@ -69,4 +88,22 @@ user = newUser; | ||
function handleError(err) { | ||
// The event source may not produce a status. But the LaunchDarkly | ||
// polyfill can. If we can get the status, then we should stop retrying | ||
// on certain error codes. | ||
if (err.status && typeof err.status === 'number' && !isHttpErrorRecoverable(err.status)) { | ||
// If we encounter an unrecoverable condition, then we do not want to | ||
// retry anymore. | ||
closeConnection(); | ||
logger.error(messages.unrecoverableStreamError(err)); | ||
// Ensure any pending retry attempts are not done. | ||
if (reconnectTimeoutReference) { | ||
clearTimeout(reconnectTimeoutReference); | ||
reconnectTimeoutReference = null; | ||
} | ||
return; | ||
} | ||
const delay = getNextRetryDelay(); | ||
if (!firstConnectionErrorLogged) { | ||
logger.warn(messages.streamError(err, streamReconnectDelay)); | ||
logger.warn(messages.streamError(err, delay)); | ||
firstConnectionErrorLogged = true; | ||
@@ -76,3 +113,3 @@ } | ||
closeConnection(); | ||
tryConnect(streamReconnectDelay); | ||
tryConnect(delay); | ||
} | ||
@@ -131,2 +168,7 @@ | ||
es.onerror = handleError; | ||
es.onopen = () => { | ||
// If the connection is a success, then reset the retryCount. | ||
retryCount = 0; | ||
}; | ||
} | ||
@@ -133,0 +175,0 @@ } |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
400381
9148