provide REST APIs to read from the SQLite DB and poll for updates

This commit is contained in:
Jesse D. McDonald 2020-04-27 02:24:01 -05:00
parent 6ea10ef1e3
commit 5542cb0ab5
5 changed files with 635 additions and 60 deletions

26
gun-with-cancel.js Normal file
View File

@ -0,0 +1,26 @@
module.exports = function patchGunWithCancel(Gun) {
Gun.chain.onWithCancel = (function() {
function cancelCallback(data,key,msg,ev) {
if (ev && typeof ev.off === 'function') {
ev.off();
}
}
return function(tag, arg, eas, as) {
if (typeof tag === 'function') {
let callback = tag;
const cancelEv = function() {
callback = cancelCallback;
};
const wrapper = function() {
return callback.apply(this, arguments);
};
this.on(wrapper, arg, eas, as);
return cancelEv;
} else {
this.on(tag, arg, eas, as);
return null;
}
};
})();
};

392
index.js
View File

@ -1,45 +1,34 @@
var config = { port: process.env.OPENSHIFT_NODEJS_PORT || process.env.VCAP_APP_PORT || process.env.PORT || process.argv[2] || 8765 }; 'use strict';
var fs = require('fs'); var fs = require('fs');
var Gun = require('gun'); var Gun = require('gun');
var SEA = require('gun/sea'); var SEA = require('gun/sea');
var NTS = require('gun/nts'); var NTS = require('gun/nts');
var rtc = require('gun/lib/webrtc'); var rtc = require('gun/lib/webrtc');
var sqlite3 = require('./sqlite3-promises'); var sqlite3 = require('./sqlite3-promises');
var express = require('express');
require('./gun-with-cancel')(Gun);
const POLLING_TIMEOUT = 60000/*ms*/;
const app = express();
var config = { port: process.env.OPENSHIFT_NODEJS_PORT || process.env.VCAP_APP_PORT || process.env.PORT || process.argv[2] || 8765 };
if (process.env.HTTPS_KEY) { if (process.env.HTTPS_KEY) {
config.key = fs.readFileSync(process.env.HTTPS_KEY); config.key = fs.readFileSync(process.env.HTTPS_KEY);
config.cert = fs.readFileSync(process.env.HTTPS_CERT); config.cert = fs.readFileSync(process.env.HTTPS_CERT);
config.server = require('https').createServer(config, Gun.serve(__dirname)); config.server = require('https').createServer(config, app);
} else { } else {
config.server = require('http').createServer(Gun.serve(__dirname)); config.server = require('http').createServer(app);
} }
Gun.chain.onWithCancel = (function() { var gun = Gun({
function cancelCallback(data,key,msg,ev) { web: config.server,
if (ev && typeof ev.off === 'function') { peers: ['https://jessemcdonald.info/gun'],
ev.off(); });
} console.log('Relay peer started on port ' + config.port + ' with /gun');
}
return function(tag, arg, eas, as) {
if (typeof tag === 'function') {
let callback = tag;
const cancelEv = function() {
callback = cancelCallback;
};
const wrapper = function() {
return callback.apply(this, arguments);
};
this.on(wrapper, arg, eas, as);
return cancelEv;
} else {
this.on(tag, arg, eas, as);
return null;
}
};
})();
var appendJournal = (function() { var appendJournal = (function() {
let lastJournalText = null; let lastJournalText = null;
@ -51,12 +40,6 @@ var appendJournal = (function() {
}; };
})(); })();
var gun = Gun({
web: config.server.listen(config.port),
peers: ['https://jessemcdonald.info/gun'],
});
console.log('Relay peer started on port ' + config.port + ' with /gun');
function logIn(msg){ function logIn(msg){
console.log(`in msg:${JSON.stringify(msg)}.........`); console.log(`in msg:${JSON.stringify(msg)}.........`);
} }
@ -88,72 +71,181 @@ function logDbError(label) {
const dbInit = (async function dbInit() { const dbInit = (async function dbInit() {
const db = await sqlite3.openAsync('./pacosako.db'); const db = await sqlite3.openAsync('./pacosako.db');
await db.runAsync(` try {
CREATE TABLE IF NOT EXISTS games ( await db.runAsync(`VACUUM`);
gameId TEXT PRIMARY KEY, } catch(err) {
board TEXT logDbError('vacuum')(err);
) }
`);
await db.runAsync(` await db.runAsync(`
CREATE TABLE IF NOT EXISTS meta ( CREATE TABLE IF NOT EXISTS games (
gameId TEXT PRIMARY KEY, gameId TEXT PRIMARY KEY,
lightName TEXT NOT NULL, lightName TEXT NOT NULL,
darkName TEXT NOT NULL, darkName TEXT NOT NULL,
moves INTEGER NOT NULL, moves INTEGER NOT NULL,
status TEXT, status TEXT NOT NULL,
timestamp INTEGER NOT NULL timestamp INTEGER NOT NULL,
board TEXT NOT NULL,
added INTEGER NOT NULL,
modified INTEGER NOT NULL
) )
`); `);
await db.runAsync(`
CREATE INDEX IF NOT EXISTS games_timestamp ON games(timestamp)
`);
await db.runAsync(`
CREATE INDEX IF NOT EXISTS games_modified ON games(modified)
`);
console.log('Connected to the SQLite database.'); console.log('Connected to the SQLite database.');
return db; return db;
})().catch(logDbError('dbInit')); })().catch(logDbError('dbInit'));
function pruneEmpty(obj) {
const copy = {};
for (const key in obj) {
const val = obj[key];
if (val !== undefined && val !== null && val !== '') {
copy[key] = obj[key];
}
}
return copy;
}
async function logAllMeta() { async function logAllMeta() {
const db = await dbInit; const db = await dbInit;
try { try {
for await (const row of db.eachAsync(`SELECT * FROM meta ORDER BY timestamp DESC`)) { const querySql = `
console.log(JSON.stringify(row)); SELECT gameId, lightName, darkName, moves, status, timestamp FROM games ORDER BY timestamp DESC
`;
for await (const row of db.eachAsync(querySql)) {
console.log(JSON.stringify(pruneEmpty(row)));
} }
} catch(err) { } catch(err) {
logDbError('logAllMeta')(err); logDbError('logAllMeta')(err);
} }
} }
let waitingAnyGame = null;
const waitingGames = {};
function signalGameUpdate(gameId) {
const promise = waitingGames[gameId];
if (promise) {
delete waitingGames[gameId];
promise.resolve();
}
const anyPromise = waitingAnyGame;
if (anyPromise) {
waitingAnyGame = null;
anyPromise.resolve();
}
}
function waitForGameUpdate(gameId) {
let promise = waitingGames[gameId];
if (promise === undefined) {
let resolve;
promise = waitingGames[gameId] = new Promise((res) => { resolve = res; });
promise.resolve = resolve;
}
return promise;
}
function waitForAnyGameUpdate() {
if (waitingAnyGame === null) {
let resolve;
waitingAnyGame = new Promise((res) => { resolve = res; });
waitingAnyGame.resolve = resolve;
}
return waitingAnyGame;
}
function waitFor(duration) {
return new Promise((resolve, reject) => setTimeout(() => { resolve(); }, duration));
}
//logAllMeta(); //logAllMeta();
async function updateMeta(gameId, meta) { function checkString(value, label, dflt) {
try {
if (arguments.length >= 3 && (value === undefined || value === null)) {
return dflt;
} else if (typeof value === 'string') {
return value;
}
} catch (err) {}
throw { message: `${label || 'value'} should be a string` };
}
function checkInteger(value, label, dflt) {
try {
if (arguments.length >= 3 && (value === undefined || value === null)) {
return dflt;
} else if (Number.isInteger(value)) {
return value;
}
} catch(err) {}
throw { message: `${label || 'value'} should be an integer` };
}
async function updateMeta(gameId, meta, time) {
if (arguments.length < 3) {
time = +new Date();
}
const db = await dbInit; const db = await dbInit;
const insertSql = const insertSql = `
`INSERT OR REPLACE INTO meta INSERT INTO games (gameId, lightName, darkName, moves, status, timestamp, board, added, modified)
( gameId, lightName, darkName, moves, status, timestamp) VALUES ($gameId, $lightName, $darkName, $moves, $status, $timestamp, '', $time, $time)
VALUES ON CONFLICT (gameId) DO UPDATE
($gameId, $lightName, $darkName, $moves, $status, $timestamp)`; SET lightName = $lightName, darkName = $darkName, moves = $moves,
status = $status, timestamp = $timestamp, modified = $time
WHERE (lightName <> $lightName OR darkName <> $darkName OR moves <> $moves OR
status <> $status OR timestamp <> $timestamp)
AND modified < $time
`;
await db.runAsync(insertSql, { await db.runAsync(insertSql, {
$gameId: gameId, $gameId: gameId,
$lightName: String(meta.lightName || ''), $lightName: checkString(meta.lightName, 'meta.lightName', ''),
$darkName: String(meta.darkName || ''), $darkName: checkString(meta.darkName, 'meta.darkName', ''),
$moves: Number(meta.moves) || 0, $moves: checkInteger(meta.moves, 'meta.moves', 0),
$status: (meta.status ? String(meta.status) : null), $status: checkString(meta.status, 'meta.status', ''),
$timestamp: Number(meta.timestamp) || 0, $timestamp: checkInteger(meta.timestamp, 'meta.timestamp', 0),
$time: time,
}); });
signalGameUpdate(gameId);
return db; return db;
} }
async function updateGame(gameId, moves) { async function updateGame(gameId, moves, time) {
if (arguments.length < 3) {
time = +new Date();
}
const db = await dbInit; const db = await dbInit;
const insertSql = const insertSql = `
`INSERT OR REPLACE INTO games (gameId, board) VALUES ($gameId, $board)`; INSERT INTO games (gameId, lightName, darkName, moves, status, timestamp, board, added, modified)
VALUES ($gameId, '', '', 0, '', 0, '', $time, $time)
ON CONFLICT (gameId) DO UPDATE
SET board = $board, modified = $time
WHERE (board <> $board) AND modified < $time
`;
await db.runAsync(insertSql, { await db.runAsync(insertSql, {
$gameId: gameId, $gameId: gameId,
$board: JSON.stringify(moves), $board: JSON.stringify(moves),
$time: time,
}); });
signalGameUpdate(gameId);
return db; return db;
} }
@ -211,3 +303,185 @@ gun.get(PacoSakoUUID + '/meta').on(function(meta) {
} }
} }
}, { change: true }); }, { change: true });
function internalErrorJson(err, res) {
res.status(500);
if (err && 'message' in err) {
console.error(err.message);
res.json({ message: 'internal error: ' + err.message });
} else {
console.error(err);
res.json({ message: 'internal error' });
}
}
async function getGameListHandler(req, res, next) {
res.set('Cache-Control', 'no-store');
try {
const afterTime = req.params.afterTime;
if (afterTime !== undefined && !afterTime.match(/^\d+$/)) {
res.status(400).json({ message: 'malformed time' });
return;
}
const pollTimeout = waitFor(POLLING_TIMEOUT).then(() => 'timeout').catch(()=>{});
while (true) {
/* Save the async promise _before_ the query so we don't miss any updates while suspended. */
const gameUpdate = waitForAnyGameUpdate().then(() => 'update');
const cutoff = (+new Date()) - (2 * 7 * 24 * 3600 * 1000); /* 2 weeks ago */
const whereClause = (afterTime === undefined) ? `true` : `modified > $afterTime`;
const querySql = `
SELECT gameId, lightName, darkName, moves, status, timestamp, modified
FROM games WHERE timestamp >= $cutoff AND ${whereClause} ORDER BY timestamp DESC
LIMIT 1000
`;
const results = await (await dbInit).allAsync(querySql, {
$afterTime: checkInteger(Number(afterTime), 'afterTime', 0),
$cutoff: cutoff,
});
if (afterTime === undefined || results.length > 0) {
let lastModified = afterTime || 0;
for (const result of results) {
if (result.modified > lastModified) {
lastModified = result.modified;
}
}
res.json({ games: results.map(pruneEmpty), modified: lastModified });
return;
}
if (await Promise.race([gameUpdate, pollTimeout]) === 'timeout') {
res.status(204).json({ retry: true });
return;
}
}
} catch (err) {
internalErrorJson(err, res);
}
}
async function getGameHandler(req, res, next) {
res.set('Cache-Control', 'no-store');
try {
const gameId = req.params.gameId;
const afterTime = req.params.afterTime;
if (!gameId.match(/^[0-9a-f]{16}$/)) {
res.status(400).json({ message: 'malformed game ID' });
return;
}
if (afterTime !== undefined && !afterTime.match(/^\d+$/)) {
res.status(400).json({ message: 'malformed time' });
return;
}
const pollTimeout = waitFor(POLLING_TIMEOUT).then(() => 'timeout').catch(()=>{});
while (true) {
/* Save the async promise _before_ the query so we don't miss any updates while suspended. */
const gameUpdate = waitForGameUpdate(gameId).then(() => 'update');
const querySql = `
SELECT board, modified FROM games WHERE gameId = $gameId
`;
const result = await (await dbInit).getAsync(querySql, { $gameId: gameId });
if (!result) {
res.status(404).json({ message: 'unknown game ID' });
return;
}
if (afterTime === undefined || result.modified > afterTime) {
const parsed = (result.board === '') ? null : JSON.parse(result.board);
res.json({ board: parsed, modified: result.modified });
return;
}
if (await Promise.race([gameUpdate, pollTimeout]) === 'timeout') {
res.status(204).json({ retry: true });
return;
}
}
} catch(err) {
internalErrorJson(err, res);
}
}
async function getMetaHandler(req, res, next) {
res.set('Cache-Control', 'no-store');
try {
const gameId = req.params.gameId;
const afterTime = req.params.afterTime;
if (!gameId.match(/^[0-9a-f]{16}$/)) {
res.status(400).json({ message: 'malformed game ID' });
return;
}
if (afterTime !== undefined && !afterTime.match(/^\d+$/)) {
res.status(400).json({ message: 'malformed time' });
return;
}
const pollTimeout = waitFor(POLLING_TIMEOUT).then(() => 'timeout').catch(()=>{});
while (true) {
/* Save the async promise _before_ the query so we don't miss any updates while suspended. */
const metaUpdate = waitForGameUpdate(gameId).then(() => 'update');
const querySql = `
SELECT lightName, darkName, moves, status, timestamp, modified FROM games
WHERE gameId = $gameId
`;
const result = await (await dbInit).getAsync(querySql, { $gameId: gameId });
if (!result) {
res.status(404).json({ message: 'unknown game ID' });
return;
}
if (afterTime === undefined || result.modified > afterTime) {
res.json(pruneEmpty(result));
return;
}
if (await Promise.race([metaUpdate, pollTimeout]) === 'timeout') {
res.status(204).json({ retry: true });
return;
}
}
} catch (err) {
internalErrorJson(err, res);
}
}
app.get('/pacosako/api/games/poll/:afterTime', getGameListHandler);
app.get('/pacosako/api/games', getGameListHandler);
app.get('/pacosako/api/game/:gameId/poll/:afterTime', getGameHandler);
app.get('/pacosako/api/game/:gameId', getGameHandler);
app.get('/pacosako/api/meta/:gameId/poll/:afterTime', getMetaHandler);
app.get('/pacosako/api/meta/:gameId', getMetaHandler);
/* TODO: Add APIs for posting updates. */
app.use('/pacosako/api/posttest', express.json());
app.post('/pacosako/api/posttest', async function(req, res, next) {
try {
res.json(req.body);
} catch (err) {
internalErrorJson(err, res);
}
});
app.use('/pacosako/api', express.static('public'));
app.use('/gun', Gun.serve(__dirname));
config.server.listen(config.port);

201
package-lock.json generated
View File

@ -1201,6 +1201,11 @@
"resolved": "https://registry.npmjs.org/array-filter/-/array-filter-0.0.1.tgz", "resolved": "https://registry.npmjs.org/array-filter/-/array-filter-0.0.1.tgz",
"integrity": "sha1-fajPLiZijtcygDWB/SH2fKzS7uw=" "integrity": "sha1-fajPLiZijtcygDWB/SH2fKzS7uw="
}, },
"array-flatten": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
"integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI="
},
"array-map": { "array-map": {
"version": "0.0.0", "version": "0.0.0",
"resolved": "https://registry.npmjs.org/array-map/-/array-map-0.0.0.tgz", "resolved": "https://registry.npmjs.org/array-map/-/array-map-0.0.0.tgz",
@ -1463,6 +1468,52 @@
"file-uri-to-path": "1.0.0" "file-uri-to-path": "1.0.0"
} }
}, },
"body-parser": {
"version": "1.19.0",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz",
"integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==",
"requires": {
"bytes": "3.1.0",
"content-type": "~1.0.4",
"debug": "2.6.9",
"depd": "~1.1.2",
"http-errors": "1.7.2",
"iconv-lite": "0.4.24",
"on-finished": "~2.3.0",
"qs": "6.7.0",
"raw-body": "2.4.0",
"type-is": "~1.6.17"
},
"dependencies": {
"bytes": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz",
"integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg=="
},
"http-errors": {
"version": "1.7.2",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz",
"integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==",
"requires": {
"depd": "~1.1.2",
"inherits": "2.0.3",
"setprototypeof": "1.1.1",
"statuses": ">= 1.5.0 < 2",
"toidentifier": "1.0.0"
}
},
"inherits": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
},
"qs": {
"version": "6.7.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz",
"integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ=="
}
}
},
"bplist-creator": { "bplist-creator": {
"version": "0.0.8", "version": "0.0.8",
"resolved": "https://registry.npmjs.org/bplist-creator/-/bplist-creator-0.0.8.tgz", "resolved": "https://registry.npmjs.org/bplist-creator/-/bplist-creator-0.0.8.tgz",
@ -1848,6 +1899,19 @@
"resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
"integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4="
}, },
"content-disposition": {
"version": "0.5.3",
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz",
"integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==",
"requires": {
"safe-buffer": "5.1.2"
}
},
"content-type": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz",
"integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA=="
},
"convert-source-map": { "convert-source-map": {
"version": "1.7.0", "version": "1.7.0",
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz",
@ -1856,6 +1920,16 @@
"safe-buffer": "~5.1.1" "safe-buffer": "~5.1.1"
} }
}, },
"cookie": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz",
"integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg=="
},
"cookie-signature": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
"integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw="
},
"copy-descriptor": { "copy-descriptor": {
"version": "0.1.1", "version": "0.1.1",
"resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz",
@ -2247,6 +2321,50 @@
"base64-js": "^1.3.0" "base64-js": "^1.3.0"
} }
}, },
"express": {
"version": "4.17.1",
"resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz",
"integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==",
"requires": {
"accepts": "~1.3.7",
"array-flatten": "1.1.1",
"body-parser": "1.19.0",
"content-disposition": "0.5.3",
"content-type": "~1.0.4",
"cookie": "0.4.0",
"cookie-signature": "1.0.6",
"debug": "2.6.9",
"depd": "~1.1.2",
"encodeurl": "~1.0.2",
"escape-html": "~1.0.3",
"etag": "~1.8.1",
"finalhandler": "~1.1.2",
"fresh": "0.5.2",
"merge-descriptors": "1.0.1",
"methods": "~1.1.2",
"on-finished": "~2.3.0",
"parseurl": "~1.3.3",
"path-to-regexp": "0.1.7",
"proxy-addr": "~2.0.5",
"qs": "6.7.0",
"range-parser": "~1.2.1",
"safe-buffer": "5.1.2",
"send": "0.17.1",
"serve-static": "1.14.1",
"setprototypeof": "1.1.1",
"statuses": "~1.5.0",
"type-is": "~1.6.18",
"utils-merge": "1.0.1",
"vary": "~1.1.2"
},
"dependencies": {
"qs": {
"version": "6.7.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz",
"integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ=="
}
}
},
"extend": { "extend": {
"version": "3.0.2", "version": "3.0.2",
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
@ -2528,6 +2646,11 @@
"mime-types": "^2.1.12" "mime-types": "^2.1.12"
} }
}, },
"forwarded": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz",
"integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ="
},
"fragment-cache": { "fragment-cache": {
"version": "0.2.1", "version": "0.2.1",
"resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz",
@ -3402,6 +3525,11 @@
"resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz",
"integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=" "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY="
}, },
"ipaddr.js": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
"integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="
},
"is-accessor-descriptor": { "is-accessor-descriptor": {
"version": "0.1.6", "version": "0.1.6",
"resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz",
@ -4078,6 +4206,11 @@
"object-visit": "^1.0.0" "object-visit": "^1.0.0"
} }
}, },
"media-typer": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
"integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g="
},
"mem": { "mem": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/mem/-/mem-1.1.0.tgz", "resolved": "https://registry.npmjs.org/mem/-/mem-1.1.0.tgz",
@ -4086,6 +4219,11 @@
"mimic-fn": "^1.0.0" "mimic-fn": "^1.0.0"
} }
}, },
"merge-descriptors": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
"integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E="
},
"merge-stream": { "merge-stream": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-1.0.1.tgz", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-1.0.1.tgz",
@ -4094,6 +4232,11 @@
"readable-stream": "^2.0.1" "readable-stream": "^2.0.1"
} }
}, },
"methods": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
"integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4="
},
"metro": { "metro": {
"version": "0.56.4", "version": "0.56.4",
"resolved": "https://registry.npmjs.org/metro/-/metro-0.56.4.tgz", "resolved": "https://registry.npmjs.org/metro/-/metro-0.56.4.tgz",
@ -5024,6 +5167,11 @@
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz",
"integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==" "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw=="
}, },
"path-to-regexp": {
"version": "0.1.7",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
"integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w="
},
"path-type": { "path-type": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz",
@ -5199,6 +5347,15 @@
"react-is": "^16.8.1" "react-is": "^16.8.1"
} }
}, },
"proxy-addr": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz",
"integrity": "sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==",
"requires": {
"forwarded": "~0.1.2",
"ipaddr.js": "1.9.1"
}
},
"pseudomap": { "pseudomap": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz",
@ -5254,6 +5411,41 @@
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
"integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="
}, },
"raw-body": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz",
"integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==",
"requires": {
"bytes": "3.1.0",
"http-errors": "1.7.2",
"iconv-lite": "0.4.24",
"unpipe": "1.0.0"
},
"dependencies": {
"bytes": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz",
"integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg=="
},
"http-errors": {
"version": "1.7.2",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz",
"integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==",
"requires": {
"depd": "~1.1.2",
"inherits": "2.0.3",
"setprototypeof": "1.1.1",
"statuses": ">= 1.5.0 < 2",
"toidentifier": "1.0.0"
}
},
"inherits": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
}
}
},
"rc": { "rc": {
"version": "1.2.8", "version": "1.2.8",
"resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
@ -6450,6 +6642,15 @@
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.7.1.tgz", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.7.1.tgz",
"integrity": "sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg==" "integrity": "sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg=="
}, },
"type-is": {
"version": "1.6.18",
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
"integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
"requires": {
"media-typer": "0.3.0",
"mime-types": "~2.1.24"
}
},
"typedarray": { "typedarray": {
"version": "0.0.6", "version": "0.0.6",
"resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",

View File

@ -5,6 +5,7 @@
"main": "index.js", "main": "index.js",
"dependencies": { "dependencies": {
"core-js": "^3.6.4", "core-js": "^3.6.4",
"express": "^4.17.1",
"gun": "^0.2020.301", "gun": "^0.2020.301",
"gun-db": "^1.0.571", "gun-db": "^1.0.571",
"react": "^16.9.0", "react": "^16.9.0",

73
public/poll.html Normal file
View File

@ -0,0 +1,73 @@
<html>
<head>
<meta charset="UTF-8">
<title>Polling Test</title>
</head>
<body>
<div id="current"></div>
<script src="https://code.jquery.com/jquery-3.5.0.min.js"></script>
<script>
let gameId = '5b687c23df28945e';
let currentRequest = null;
function startPoll(fromTime) {
const thisRequest = currentRequest = $.ajax({
dataType: 'json',
contentType: 'application/json',
//url: `https://jessemcdonald.info/pacosako/api/meta/${gameId}/poll/${fromTime || 0}`,
url: `https://jessemcdonald.info/pacosako/api/games/poll/${fromTime || 0}`,
cache: false,
timeout: 0,
}).done((data, textStatus, jqXHR) => {
if (currentRequest === thisRequest) {
if (jqXHR.status == 204) {
startPoll(fromTime);
} else {
$('<p></p>').text(JSON.stringify(data)).appendTo('#current');
startPoll(data.modified);
}
}
}).fail((jqXHR, textStatus, errorThrown) => {
if (currentRequest === thisRequest) {
$('#current').empty().text(JSON.stringify({ textStatus, errorThrown }));
setTimeout(() => {
if (currentRequest === thisRequest) {
startPoll(0);
}
}, 3000);
}
});
}
function stopPoll() {
const request = currentRequest;
if (request !== null) {
currentRequest = null;
request.abort();
}
}
function doPost(data) {
stopPoll();
$.ajax({
dataType: 'json',
contentType: 'application/json',
url: `https://jessemcdonald.info/pacosako/api/posttest`,
method: 'POST',
cache: false,
data: JSON.stringify(data),
timeout: 5000,
}).done((responseData, textStatus, jqXHR) => {
$('#current').empty().text(JSON.stringify(responseData));
}).fail((jqXHR, textStatus, errorThrown) => {
$('#current').empty().text(JSON.stringify({ textStatus, errorThrown }));
});
}
$(() => {
let counter = 0;
$('<button>Start Polling</button>').appendTo('body').on('click', () => startPoll());
$('<button>Stop Polling</button>').appendTo('body').on('click', () => stopPoll());
$('<button>Do Post</button>').appendTo('body').on('click', () => doPost({ counter: ++counter }));
});
</script>
</body>
</html>
<!-- vim:set noexpandtab sw=2 ts=2: -->