add an interface to monitor the states of the polling connections

This commit is contained in:
Jesse D. McDonald 2020-04-28 19:25:53 -05:00
parent 2bfcc822a3
commit 3f173d70a2
1 changed files with 119 additions and 18 deletions

View File

@ -16,8 +16,12 @@ const meta = {
listeners: {},
nextId: 1,
currentRequest: null,
connectionState: 'initializing',
};
const stateListeners = {};
const stateNextId = 1;
/* One-time request, no caching or polling */
function getGameState(gameId, retries) {
if (arguments.length < 2) {
@ -161,6 +165,70 @@ function sendUpdate(gameId, data, retries) {
});
}
function onConnectionStateChanged(gameId, callback) {
if (arguments.length < 2) {
callback = gameId;
gameId = undefined;
}
if (gameId !== undefined) {
const original = callback;
callback = function(cbGameId, cbState) {
if (cbGameId === gameId) {
original(cbState);
}
};
}
const cbId = stateNextId;
stateNextId += 1;
stateListeners[cbId] = callback;
return function clearConnectionStateChanged() {
delete stateListeners[cbId];
};
}
function getConnectionState(gameId) {
if (arguments.length < 1) {
return meta.connectionState;
} else {
return games[gameId] && games[gameId].connectionState;
}
}
function setConnectionState(game, state) {
let gameId;
if (arguments.length < 2) {
gameId = 'meta';
state = game;
game = meta;
} else if (typeof game === 'string') {
gameId = game;
game = gamePollState(game);
} else {
gameId = game.gameId;
}
if (state !== game.connectionState) {
if (state !== 'initializing' && state !== 'connecting' && state !== 'polling'
&& state !== 'failed' && state !== 'stopped') {
throw new TypeError(`invalid connection state: ${state}`);
}
game.connectionState = state;
for (const cbId in stateListeners) {
try {
stateListeners[cbId](gameId, state);
} catch(err) {
console.error('uncaught exception in callback', err);
}
}
}
}
function gamePollState(gameId) {
if (gameId in games === false) {
games[gameId] = {
@ -168,6 +236,7 @@ function gamePollState(gameId) {
data: { gameId, modified: 0 },
listeners: [],
nextId: 1,
connectionState: 'initializing',
};
}
@ -177,15 +246,21 @@ function gamePollState(gameId) {
function processGame(gameId, data) {
const game = gamePollState(gameId);
if (data && data.modified > game.data.modified) {
if (data && (game.data.modified === 0 || data.modified > game.data.modified)) {
data.gameId = data.gameId || gameId;
game.data = data;
for (const cbId in game.listeners) {
try {
game.listeners[cbId](data, gameId);
game.listeners[cbId](JSON.parse(JSON.stringify(data)), gameId);
} catch(err) {
console.error('uncaught exception in callback', err);
}
}
if (data.modified !== 0) {
processMeta({ games: [data], modified: 0 });
}
}
}
@ -197,24 +272,31 @@ function startGamePoll(gameId, afterTime) {
afterTime = game.data.modified;
}
if (game.connectionState !== 'polling') {
if (game.connectionState !== 'failed') {
setConnectionState(game, 'connecting');
}
}
const thisRequest = game.currentRequest = $.ajax({
dataType: 'json',
contentType: 'application/json',
url: `${API_BASE}/game/${gameId}/poll/${afterTime}`,
url: `${API_BASE}/game/${gameId}${!afterTime ? '' : `/poll/${afterTime}`}`,
cache: false,
timeout: LONG_TIMEOUT,
timeout: afterTime ? LONG_TIMEOUT : SHORT_TIMEOUT,
}).done((data, textStatus, jqXHR) => {
if (game.currentRequest === thisRequest) {
game.currentRequest = null;
if (jqXHR.status == 204) {
startGamePoll(gameId, afterTime);
} else {
if (jqXHR.status !== 204) {
afterTime = data.modified;
processGame(gameId, data);
startGamePoll(gameId, data.modified);
}
setConnectionState(game, 'polling');
startGamePoll(gameId, afterTime);
}
}).fail((jqXHR, textStatus, errorThrown) => {
if (game.currentRequest === thisRequest) {
setConnectionState(game, 'failed');
setTimeout(() => {
if (game.currentRequest === thisRequest) {
game.currentRequest = null;
@ -235,19 +317,28 @@ function stopGamePoll(gameId) {
const request = game.currentRequest;
if (request !== null) {
game.currentRequest = null;
setConnectionState(game, 'stopped');
request.abort();
}
}
function processMeta(data) {
meta.lastModified = data.modified;
if (data.modified > meta.lastModified) {
meta.lastModified = data.modified;
}
for (const game of data.games) {
if (game.gameId in meta.games === false || game.modified > meta.games[game.gameId].modified) {
meta.games[game.gameId] = game;
const gameId = game.gameId;
if (gameId in meta.games === false || game.modified > meta.games[gameId].modified) {
meta.games[gameId] = game;
if (gameId in games === false || games[gameId].data.modified === 0) {
const copy = Object.assign({}, game);
copy.modified = 0;
processGame(gameId, copy);
}
for (const cbId in meta.listeners) {
try {
meta.listeners[cbId](game, game.gameId);
meta.listeners[cbId](JSON.parse(JSON.stringify(game)), gameId);
} catch(err) {
console.error('uncaught exception in callback', err);
}
@ -262,24 +353,31 @@ function startMetaPoll(afterTime) {
afterTime = meta.lastModified;
}
if (meta.connectionState !== 'polling') {
if (meta.connectionState !== 'failed') {
setConnectionState('connecting');
}
}
const thisRequest = meta.currentRequest = $.ajax({
dataType: 'json',
contentType: 'application/json',
url: `https://jessemcdonald.info/pacosako/api/games/poll/${afterTime}`,
url: `${API_BASE}/games${!afterTime ? '' : `/poll/${afterTime}`}`,
cache: false,
timeout: LONG_TIMEOUT,
timeout: afterTime ? LONG_TIMEOUT : SHORT_TIMEOUT,
}).done((data, textStatus, jqXHR) => {
if (meta.currentRequest === thisRequest) {
meta.currentRequest = null;
if (jqXHR.status == 204) {
startMetaPoll(afterTime);
} else {
if (jqXHR.status !== 204) {
afterTime = data.modified;
processMeta(data);
startMetaPoll(data.modified);
}
setConnectionState('polling');
startMetaPoll(afterTime);
}
}).fail((jqXHR, textStatus, errorThrown) => {
if (meta.currentRequest === thisRequest) {
setConnectionState('failed');
setTimeout(() => {
if (meta.currentRequest === thisRequest) {
meta.currentRequest = null;
@ -295,6 +393,7 @@ function stopMetaPoll() {
const request = meta.currentRequest;
if (request !== null) {
meta.currentRequest = null;
setConnectionState('stopped');
request.abort();
}
}
@ -305,6 +404,8 @@ module.exports = {
onGameUpdate,
onMetaUpdate,
sendUpdate,
getConnectionState,
onConnectionStateChanged,
};
/* vim:set expandtab sw=3 ts=8: */