Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
128 changes: 67 additions & 61 deletions src/LiveSplitClient.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const net = require('net');
const EventEmitter = require('events');
const { deprecate } = require('util');
const crypto = require('crypto');

/**
* Node.js client for the LiveSplit Server running instance
Expand All @@ -26,6 +27,7 @@ class LiveSplitClient extends EventEmitter {

this._connected = false;
this.timeout = 100;
this._openRequests = {};

/*
According to: https://github.com/LiveSplit/LiveSplit.Server/blob/a4a57716dce90936606bfc8f8ac84f7623773aa5/README.md#commands
Expand Down Expand Up @@ -54,10 +56,13 @@ class LiveSplitClient extends EventEmitter {
});

this._socket.on('data', (data) => {
this.emit(
'data',
data.toString('utf-8').replace('\r\n', '')
);
// This should catch edge cases where multiple messages are sent by the server
// so fast that this listener only fires once with all of them (concatenated).
// This allows for polling at a much faster rate with fewer errors.
const messages = data.toString('utf-8').split('\r\n').slice(0, -1);
messages.forEach(message => {
this.emit('data', JSON.parse(message));
});
});

this._socket.on('error', (err) => {
Expand Down Expand Up @@ -94,10 +99,10 @@ class LiveSplitClient extends EventEmitter {
/**
* Send command to the LiveSplit Server instance.
* @param {string} command - Existing LiveSplit Server command without linebreaks.
* @param {boolean} [expectResponse=true] - Expect response from the server.
* @returns {Promise|boolean} - Promise if answer was expected, else true.
* @param {object} [data] - Additional data to be sent with the command.
* @returns {Promise} - Command result or null on timeout.
*/
send(command, expectResponse = true) {
send(command, data) {
if (!this._connected)
throw new Error('Client must be connected to the server!');

Expand All @@ -106,28 +111,33 @@ class LiveSplitClient extends EventEmitter {

this._checkDisallowedSymbols(command);

this._socket.write(`${command}\r\n`);
const nonce = crypto.randomUUID();
const request = { command, data, nonce };
this._socket.write(`${JSON.stringify(request)}\r\n`);
this._openRequests[nonce] = request;

if (expectResponse)
return this._waitForResponse();
else
return true;
return this._waitForResponse(request);
}

_waitForResponse() {
_waitForResponse(request) {
let listener = false;

const responseRecieved = new Promise((resolve) => {
listener = (data) => {
resolve(data);
if (data.nonce === request.nonce) {
this.off('data', listener);
delete this._openRequests[request.nonce];
resolve(data);
}
};

this.once('data', listener);
this.on('data', listener);
});

const responseTimeout = new Promise((resolve) => {
setTimeout(() => {
this.removeListener('data', listener);
this.off('data', listener);
delete this._openRequests[request.nonce];
resolve(null);
}, this.timeout);
});
Expand All @@ -144,119 +154,119 @@ class LiveSplitClient extends EventEmitter {

/**
* Start timer
* @returns {boolean}
* @returns {Promise} Command result or null on timeout.
*/
startTimer() {
return this.send('starttimer', false);
return this.send('starttimer');
}

/**
* Start or split
* @returns {boolean}
* @returns {Promise} Command result or null on timeout.
*/
startOrSplit() {
return this.send('startorsplit', false);
return this.send('startorsplit');
}

/**
* Split
* @returns {boolean}
* @returns {Promise} Command result or null on timeout.
*/
split() {
return this.send('split', false);
return this.send('split');
}

/**
* Unsplit
* @returns {boolean}
* @returns {Promise} Command result or null on timeout.
*/
unsplit() {
return this.send('unsplit', false);
return this.send('unsplit');
}

/**
* Skip split
* @returns {boolean}
* @returns {Promise} Command result or null on timeout.
*/
skipSplit() {
return this.send('skipsplit', false);
return this.send('skipsplit');
}

/**
* Pause
* @returns {boolean}
* @returns {Promise} Command result or null on timeout.
*/
pause() {
return this.send('pause', false);
return this.send('pause');
}

/**
* Resume
* @returns {boolean}
* @returns {Promise} Command result or null on timeout.
*/
resume() {
return this.send('resume', false);
return this.send('resume');
}

/**
* Reset
* @returns {boolean}
* @returns {Promise} Command result or null on timeout.
*/
reset() {
return this.send('reset', false);
return this.send('reset');
}

/**
* Init game time. Could be called only once according to LiveSplit Server documentation.
* @returns {boolean}
* @returns {Promise} Command result or null on timeout.
*/
initGameTime() {
if (this._initGameTimeOnce) return false;
this._initGameTimeOnce = true;
return this.send('initgametime', false);
return this.send('initgametime');
}

/**
* Set game time
* @param {string} time - Game time
* @returns {boolean}
* @returns {Promise} Command result or null on timeout.
*/
setGameTime(time) {
return this.send(`setgametime ${time}`, false);
return this.send('setgametime', { time });
}

/**
* Set loading times
* @param {string} time - Game time
* @returns {boolean}
* @returns {Promise} Command result or null on timeout.
*/
setLoadingTimes(time) {
return this.send(`setloadingtimes ${time}`, false);
return this.send('setloadingtimes', { time });
}

/**
* Pause game time
* @returns {boolean}
* @returns {Promise} Command result or null on timeout.
*/
pauseGameTime() {
return this.send('pausegametime', false);
return this.send('pausegametime');
}

/**
* Unpause game time
* @returns {boolean}
* @returns {Promise} Command result or null on timeout.
*/
unpauseGameTime() {
return this.send('unpausegametime', false);
return this.send('unpausegametime');
}

/**
* Set comparison
* @param {string} comparison - Comparison
* @returns {boolean}
* @returns {Promise} Command result or null on timeout.
*/
setComparison(comparison) {
return this.send(`setcomparison ${comparison}`, false);
return this.send('setcomparison', { comparison });
}

/**
Expand All @@ -265,32 +275,31 @@ class LiveSplitClient extends EventEmitter {
* @returns {Promise} Command result or null on timeout.
*/
getDelta(comparison = '') {
if (comparison) comparison = ` ${comparison}`;
return this.send(`getdelta${comparison}`, true);
return this.send('getdelta', { comparison });
}

/**
* Get last split time
* @returns {Promise} Command result or null on timeout.
*/
getLastSplitTime() {
return this.send('getlastsplittime', true);
return this.send('getlastsplittime');
}

/**
* Get comparison split time
* @returns {Promise} Command result or null on timeout.
*/
getComparisonSplitTime() {
return this.send('getcomparisonsplittime', true);
return this.send('getcomparisonsplittime');
}

/**
* Get current time
* @returns {Promise} Command result or null on timeout.
*/
getCurrentTime() {
return this.send('getcurrenttime', true);
return this.send('getcurrenttime');
}

/**
Expand All @@ -299,8 +308,7 @@ class LiveSplitClient extends EventEmitter {
* @returns {Promise} Command result or null on timeout.
*/
getFinalTime(comparison = '') {
if (comparison) comparison = ` ${comparison}`;
return this.send(`getfinaltime${comparison}`, true);
return this.send('getfinaltime', { comparison });
}

/**
Expand All @@ -309,40 +317,39 @@ class LiveSplitClient extends EventEmitter {
* @returns {Promise} Command result or null on timeout.
*/
getPredictedTime(comparison = '') {
if (comparison) comparison = ` ${comparison}`;
return this.send(`getpredictedtime${comparison}`, true);
return this.send('getpredictedtime', { comparison });
}

/**
* Get best pssible time
* @returns {Promise} Command result or null on timeout.
*/
getBestPossibleTime() {
return this.send('getbestpossibletime', true);
return this.send('getbestpossibletime');
}

/**
* Get split index
* @returns {Promise} Command result or null on timeout.
*/
getSplitIndex() {
return this.send('getsplitindex', true);
return this.send('getsplitindex');
}

/**
* Get current split name
* @returns {Promise} Command result or null on timeout.
*/
getCurrentSplitName() {
return this.send('getcurrentsplitname', true);
return this.send('getcurrentsplitname');
}

/**
* Get previous split name
* @returns {Promise} Command result or null on timeout.
*/
getPreviousSplitName() {
return this.send('getprevioussplitname', true);
return this.send('getprevioussplitname');
}

getPreviousSplitname() {
Expand All @@ -354,7 +361,7 @@ class LiveSplitClient extends EventEmitter {
* @returns {Promise} Command result or null on timeout.
*/
getCurrentTimerPhase() {
return this.send('getcurrenttimerphase', true);
return this.send('getcurrenttimerphase');
}

/**
Expand All @@ -365,9 +372,8 @@ class LiveSplitClient extends EventEmitter {
const output = {};

for (let method of ['getCurrentTimerPhase', 'getDelta', 'getLastSplitTime', 'getComparisonSplitTime', 'getCurrentTime', 'getFinalTime', 'getPredictedTime', 'getBestPossibleTime', 'getSplitIndex', 'getCurrentSplitName', 'getPreviousSplitName']) {
output[
method.replace('get', '').charAt(0).toLowerCase() + method.replace('get', '').slice(1)
] = await this[method]();
let response = await this[method]();
Object.assign(output, response.data);
}

return output;
Expand Down