add an interface to monitor the states of the polling connections
This commit is contained in:
parent
2bfcc822a3
commit
3f173d70a2
|
|
@ -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) {
|
||||
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: */
|
||||
|
|
|
|||
Loading…
Reference in New Issue