From ca1ae336c99bef09026b36b17bcfcac0b7c25c69 Mon Sep 17 00:00:00 2001 From: Diamond Lewis Date: Wed, 19 Feb 2020 03:30:23 -0600 Subject: [PATCH] Websocket: unhandle rejection (#6418) * Websocket: unhandle rejection Closes: https://github.com/parse-community/parse-server/issues/6413, https://github.com/parse-community/parse-server/issues/6173 Prevent crashing on websocket error. Bonus points to anybody who can post a specific payload that the client sends that returns an error. * log the socket * fix tests * fix payload reference link --- spec/ParseLiveQuery.spec.js | 37 +++++++++++++++++++++++++++ spec/ParseWebSocketServer.spec.js | 13 +++++----- src/LiveQuery/ParseWebSocketServer.js | 4 +++ 3 files changed, 48 insertions(+), 6 deletions(-) diff --git a/spec/ParseLiveQuery.spec.js b/spec/ParseLiveQuery.spec.js index 74faa9ae..fa83588b 100644 --- a/spec/ParseLiveQuery.spec.js +++ b/spec/ParseLiveQuery.spec.js @@ -24,6 +24,43 @@ describe('ParseLiveQuery', function() { await object.save(); }); + it('handle invalid websocket payload length', async done => { + await reconfigureServer({ + liveQuery: { + classNames: ['TestObject'], + }, + startLiveQueryServer: true, + verbose: false, + silent: true, + websocketTimeout: 100, + }); + const object = new TestObject(); + await object.save(); + + const query = new Parse.Query(TestObject); + query.equalTo('objectId', object.id); + const subscription = await query.subscribe(); + + // All control frames must have a payload length of 125 bytes or less. + // https://tools.ietf.org/html/rfc6455#section-5.5 + // + // 0x89 = 10001001 = ping + // 0xfe = 11111110 = first bit is masking the remaining 7 are 1111110 or 126 the payload length + // https://tools.ietf.org/html/rfc6455#section-5.2 + const client = await Parse.CoreManager.getLiveQueryController().getDefaultLiveQueryClient(); + client.socket._socket.write(Buffer.from([0x89, 0xfe])); + + subscription.on('update', async object => { + expect(object.get('foo')).toBe('bar'); + done(); + }); + // Wait for Websocket timeout to reconnect + setTimeout(async () => { + object.set({ foo: 'bar' }); + await object.save(); + }, 1000); + }); + afterEach(async function(done) { const client = await Parse.CoreManager.getLiveQueryController().getDefaultLiveQueryClient(); client.close(); diff --git a/spec/ParseWebSocketServer.spec.js b/spec/ParseWebSocketServer.spec.js index c9416f4c..7bf8a5c5 100644 --- a/spec/ParseWebSocketServer.spec.js +++ b/spec/ParseWebSocketServer.spec.js @@ -1,11 +1,12 @@ const { ParseWebSocketServer, } = require('../lib/LiveQuery/ParseWebSocketServer'); +const EventEmitter = require('events'); describe('ParseWebSocketServer', function() { beforeEach(function(done) { // Mock ws server - const EventEmitter = require('events'); + const mockServer = function() { return new EventEmitter(); }; @@ -22,11 +23,11 @@ describe('ParseWebSocketServer', function() { onConnectCallback, { websocketTimeout: 5 } ).server; - const ws = { - readyState: 0, - OPEN: 0, - ping: jasmine.createSpy('ping'), - }; + const ws = new EventEmitter(); + ws.readyState = 0; + ws.OPEN = 0; + ws.ping = jasmine.createSpy('ping'); + parseWebSocketServer.onConnection(ws); // Make sure callback is called diff --git a/src/LiveQuery/ParseWebSocketServer.js b/src/LiveQuery/ParseWebSocketServer.js index 9e0d18d2..606056fc 100644 --- a/src/LiveQuery/ParseWebSocketServer.js +++ b/src/LiveQuery/ParseWebSocketServer.js @@ -13,6 +13,10 @@ export class ParseWebSocketServer { logger.info('Parse LiveQuery Server starts running'); }; wss.onConnection = ws => { + ws.on('error', error => { + logger.error(error.message); + logger.error(JSON.stringify(ws)); + }); onConnect(new ParseWebSocket(ws)); // Send ping to client periodically const pingIntervalId = setInterval(() => {