Skip to content

Commit e4bee10

Browse files
prepare 4.0.3 release (#62)
1 parent 2fb8411 commit e4bee10

File tree

4 files changed

+63
-4
lines changed

4 files changed

+63
-4
lines changed

src/__tests__/LDClient-streaming-test.js

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import * as messages from '../messages';
12
import * as utils from '../utils';
23

34
import { AsyncQueue, eventSink, sleepAsync, withCloseable } from 'launchdarkly-js-test-helpers';
@@ -650,6 +651,38 @@ describe('LDClient streaming', () => {
650651
});
651652
});
652653

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+
653686
it('reconnects to stream if the user changes', async () => {
654687
const user2 = { key: 'user2' };
655688
const encodedUser2 = 'eyJrZXkiOiJ1c2VyMiJ9';

src/errors.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ const LDInvalidUserError = createCustomError('LaunchDarklyInvalidUserError');
1818
const LDInvalidEventKeyError = createCustomError('LaunchDarklyInvalidEventKeyError');
1919
const LDInvalidArgumentError = createCustomError('LaunchDarklyInvalidArgumentError');
2020
const LDFlagFetchError = createCustomError('LaunchDarklyFlagFetchError');
21+
const LDInvalidDataError = createCustomError('LaunchDarklyInvalidDataError');
2122

2223
function isHttpErrorRecoverable(status) {
2324
if (status >= 400 && status < 500) {
@@ -32,6 +33,7 @@ module.exports = {
3233
LDInvalidUserError,
3334
LDInvalidEventKeyError,
3435
LDInvalidArgumentError,
36+
LDInvalidDataError,
3537
LDFlagFetchError,
3638
isHttpErrorRecoverable,
3739
};

src/index.js

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -386,6 +386,14 @@ function initialize(env, user, specifiedOptions, platform, extraOptionDefs) {
386386
if (!ident.getUser()) {
387387
return;
388388
}
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+
};
389397
stream.connect(ident.getUser(), hash, {
390398
ping: function() {
391399
logger.debug(messages.debugStreamPing());
@@ -404,12 +412,20 @@ function initialize(env, user, specifiedOptions, platform, extraOptionDefs) {
404412
});
405413
},
406414
put: function(e) {
407-
const data = JSON.parse(e.data);
415+
const data = tryParseData(e.data);
416+
if (!data) {
417+
return;
418+
}
408419
logger.debug(messages.debugStreamPut());
409-
replaceAllFlags(data); // don't wait for this Promise to be resolved
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
410423
},
411424
patch: function(e) {
412-
const data = JSON.parse(e.data);
425+
const data = tryParseData(e.data);
426+
if (!data) {
427+
return;
428+
}
413429
// If both the flag and the patch have a version property, then the patch version must be
414430
// greater than the flag version for us to accept the patch. If either one has no version
415431
// then the patch always succeeds.
@@ -432,7 +448,10 @@ function initialize(env, user, specifiedOptions, platform, extraOptionDefs) {
432448
}
433449
},
434450
delete: function(e) {
435-
const data = JSON.parse(e.data);
451+
const data = tryParseData(e.data);
452+
if (!data) {
453+
return;
454+
}
436455
if (!flags[data.key] || flags[data.key].version < data.version) {
437456
logger.debug(messages.debugStreamDelete(data.key));
438457
const mods = {};

src/messages.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,10 @@ const invalidUser = function() {
6868
return 'Invalid user specified.' + docLink;
6969
};
7070

71+
const invalidData = function() {
72+
return 'Invalid data received from LaunchDarkly; connection may have been interrupted';
73+
};
74+
7175
const bootstrapOldFormat = function() {
7276
return (
7377
'LaunchDarkly client was initialized with bootstrap data that did not include flag metadata. ' +
@@ -201,6 +205,7 @@ module.exports = {
201205
httpUnavailable,
202206
identifyDisabled,
203207
invalidContentType,
208+
invalidData,
204209
invalidKey,
205210
invalidUser,
206211
localStorageUnavailable,

0 commit comments

Comments
 (0)