launchdarkly-js-sdk-common 4.0.2 → 4.0.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "launchdarkly-js-sdk-common",
3
- "version": "4.0.2",
3
+ "version": "4.0.3",
4
4
  "description": "LaunchDarkly SDK for JavaScript - common code",
5
5
  "author": "LaunchDarkly <team@launchdarkly.com>",
6
6
  "license": "Apache-2.0",
@@ -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';
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/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 = JSON.parse(e.data);
415
+ const data = tryParseData(e.data);
416
+ if (!data) {
417
+ return;
418
+ }
408
419
  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
410
423
  },
411
424
  patch: function(e) {
412
- const data = JSON.parse(e.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 = JSON.parse(e.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 = {};
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. ' +
@@ -201,6 +205,7 @@ module.exports = {
201
205
  httpUnavailable,
202
206
  identifyDisabled,
203
207
  invalidContentType,
208
+ invalidData,
204
209
  invalidKey,
205
210
  invalidUser,
206
211
  localStorageUnavailable,