From 1f916d3acd03acdc67699e24015562a3d2422f27 Mon Sep 17 00:00:00 2001 From: Florrie Date: Sun, 12 Jul 2020 17:50:21 -0300 Subject: allow lines spread across multiple data chunks Wrapper functions 4 lyfe. --- socket.js | 165 +++++++++++++++++++++++++++++++++++--------------------------- 1 file changed, 92 insertions(+), 73 deletions(-) diff --git a/socket.js b/socket.js index 52efb4d..e56f999 100644 --- a/socket.js +++ b/socket.js @@ -174,6 +174,29 @@ function validateCommand(command) { return false } +function perLine(handleLine) { + // Wrapper function to run a callback for each line provided to the wrapped + // callback. Maintains a "partial" variable so that a line may be broken up + // into multiple chunks before it is sent. Also supports handling multiple + // lines (including the conclusion to a previously received partial line) + // being received at once. + + let partial = '' + return data => { + const text = data.toString() + const lines = text.split('\n') + if (lines.length === 1) { + partial += text + } else { + handleLine(partial + lines[0]) + for (const line of lines.slice(1, -1)) { + handleLine(line) + } + partial = lines[lines.length - 1] + } + } +} + export function makeSocketServer() { // The socket server has two functions: to maintain a "canonical" backend // and synchronize newly connected clients with the relevent data in this @@ -200,85 +223,83 @@ export function makeSocketServer() { } }) - socket.on('data', data => { + socket.on('data', perLine(line => { // Parse data as a command and validate it. If invalid, drop this data. - for (const line of data.toString().trim().split('\n')) { - let command - try { - command = deserializeDataToCommand(line) - } catch (error) { - return - } + let command + try { + command = deserializeDataToCommand(line) + } catch (error) { + return + } - command.sender = 'client' + command.sender = 'client' - if (!validateCommand(command)) { - return - } + if (!validateCommand(command)) { + return + } - // If it's a status command, respond appropriately, and return so that it - // is not relayed. - - if (command.code === 'status') { - switch (command.status) { - case 'ready-to-resume': { - const readySockets = readyToResume[command.queuePlayer] - if (readySockets && !readySockets.includes(socket)) { - readySockets.push(socket) - if (readySockets.length === sockets.length) { - for (const socket of sockets) { - socket.write(JSON.stringify({ - sender: 'server', - code: 'set-pause', - queuePlayer: command.queuePlayer, - startingTrack: true, - paused: false - }) + '\n') - } - delete readyToResume[command.queuePlayer] - } - } - break - } - case 'sync-playback': - for (const QP of server.canonicalBackend.queuePlayers) { - if (QP.timeData) { - socket.write(JSON.stringify({ - sender: 'server', - code: 'seek-to', - queuePlayer: QP.id, - time: QP.timeData.curSecTotal - }) + '\n') + // If it's a status command, respond appropriately, and return so that it + // is not relayed. + + if (command.code === 'status') { + switch (command.status) { + case 'ready-to-resume': { + const readySockets = readyToResume[command.queuePlayer] + if (readySockets && !readySockets.includes(socket)) { + readySockets.push(socket) + if (readySockets.length === sockets.length) { + for (const socket of sockets) { socket.write(JSON.stringify({ sender: 'server', code: 'set-pause', - queuePlayer: QP.id, + queuePlayer: command.queuePlayer, startingTrack: true, - paused: QP.player.isPaused + paused: false }) + '\n') } + delete readyToResume[command.queuePlayer] } - break + } + break } - return + case 'sync-playback': + for (const QP of server.canonicalBackend.queuePlayers) { + if (QP.timeData) { + socket.write(JSON.stringify({ + sender: 'server', + code: 'seek-to', + queuePlayer: QP.id, + time: QP.timeData.curSecTotal + }) + '\n') + socket.write(JSON.stringify({ + sender: 'server', + code: 'set-pause', + queuePlayer: QP.id, + startingTrack: true, + paused: QP.player.isPaused + }) + '\n') + } + } + break } + return + } - // If it's a 'play' command, set up a new readyToResume array. + // If it's a 'play' command, set up a new readyToResume array. - if (command.code === 'play') { - readyToResume[command.queuePlayer] = [] - } + if (command.code === 'play') { + readyToResume[command.queuePlayer] = [] + } - // Relay the command to client sockets besides the sender. + // Relay the command to client sockets besides the sender. - const otherSockets = sockets.filter(s => s !== socket) + const otherSockets = sockets.filter(s => s !== socket) - for (const socket of otherSockets) { - socket.write(JSON.stringify(command) + '\n') - } + for (const socket of otherSockets) { + socket.write(JSON.stringify(command) + '\n') } - }) + })) const savedBackend = saveBackend(server.canonicalBackend) @@ -311,25 +332,23 @@ export function makeSocketClient() { client.socket.write(data + '\n') } - client.socket.on('data', data => { + client.socket.on('data', perLine(line => { // Same sort of "guarding" deserialization/validation as in the server // code, because it's possible the client and server backends mismatch. - for (const line of data.toString().trim().split('\n')) { - let command - try { - command = deserializeDataToCommand(line) - } catch (error) { - return - } - - if (!validateCommand(command)) { - return - } + let command + try { + command = deserializeDataToCommand(line) + } catch (error) { + return + } - client.emit('command', command) + if (!validateCommand(command)) { + return } - }) + + client.emit('command', command) + })) return client } -- cgit 1.3.0-6-gf8a5