remove GUN from the server since there are no more clients

This commit is contained in:
Jesse D. McDonald 2020-04-28 14:51:31 -05:00
parent 674b460cbe
commit 931eb6a134
6 changed files with 198 additions and 6298 deletions

4
.gitignore vendored
View File

@ -1,9 +1,5 @@
node_modules
journal.txt
bak/
radata/
journal/
stats.radata
pacosako.db
.*.swp
.*.swo

View File

@ -1,26 +0,0 @@
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;
}
};
})();
};

227
index.js
View File

@ -1,35 +1,11 @@
'use strict';
var fs = require('fs');
var Gun = require('gun');
var SEA = require('gun/sea');
var NTS = require('gun/nts');
var rtc = require('gun/lib/webrtc');
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) {
config.key = fs.readFileSync(process.env.HTTPS_KEY);
config.cert = fs.readFileSync(process.env.HTTPS_CERT);
config.server = require('https').createServer(config, app);
} else {
config.server = require('http').createServer(app);
}
var gun = Gun({
web: config.server,
peers: ['https://jessemcdonald.info/gun'],
});
console.log('Relay peer started on port ' + config.port + ' with /gun');
var appendJournal = (function() {
let lastJournalText = null;
return function appendJournal(text) {
@ -154,119 +130,10 @@ function checkInteger(value, label, dflt) {
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 insertSql = `
INSERT INTO games (gameId, lightName, darkName, moves, status, timestamp, board, added, modified)
VALUES ($gameId, $lightName, $darkName, $moves, $status, $timestamp, '', $time, $time)
ON CONFLICT (gameId) DO UPDATE
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, {
$gameId: gameId,
$lightName: checkString(meta.lightName, 'meta.lightName', ''),
$darkName: checkString(meta.darkName, 'meta.darkName', ''),
$moves: checkInteger(meta.moves, 'meta.moves', 0),
$status: checkString(meta.status, 'meta.status', ''),
$timestamp: checkInteger(meta.timestamp, 'meta.timestamp', 0),
$time: time,
});
signalGameUpdate(gameId);
return db;
}
async function updateGame(gameId, moves, time) {
if (arguments.length < 3) {
time = +new Date();
}
const db = await dbInit;
const insertSql = `
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, {
$gameId: gameId,
$board: JSON.stringify(moves),
$time: time,
});
signalGameUpdate(gameId);
return db;
}
const PacoSakoUUID = 'b425b812-6bdb-11ea-9414-6f946662bac3';
let cancellers = {};
gun.get(PacoSakoUUID + '/meta').on(function(meta) {
for (const gameId in meta) { /* use of 'in' here is deliberate */
/* 'gameId' may include extra GUN fields like '_' */
if (gameId.match(/^[0-9a-f]{16}$/)) {
if (!Gun.obj.is(meta[gameId])) {
appendJournal(JSON.stringify({ meta: { [gameId]: null } }));
if (gameId in cancellers) {
cancellers[gameId]();
delete cancellers[gameId];
}
} else if (!(gameId in cancellers)) {
let cancelMeta = gun.get(meta[gameId]).onWithCancel(function(data) {
updateMeta(gameId, data).catch(logDbError('updateMeta'));
let text;
try {
let clean = null;
if (data !== null) {
clean = {};
for (const k of Object.keys(data).sort()) {
if (k !== '_') {
clean[k] = data[k];
}
}
}
text = JSON.stringify({ meta: { [gameId]: clean } });
} catch(err) {}
if (text) {
appendJournal(text);
}
});
let cancelGame = gun.get(PacoSakoUUID + '/game/' + gameId).onWithCancel(function(data) {
if (data && typeof data.board === 'string') {
let text;
try {
const parsed = JSON.parse(data.board);
updateGame(gameId, parsed).catch(logDbError('updateGame'));
text = JSON.stringify({ game: { [gameId]: { board: parsed } } });
} catch(err) {}
if (text) {
appendJournal(text);
}
}
});
cancellers[gameId] = function() { cancelMeta(); cancelGame(); };
}
}
}
}, { change: true });
function internalErrorJson(err, res) {
function catchExceptionsJson(wrapped) {
const context = this;
return function(req, res, next) {
function internalErrorJson(err) {
res.status(500);
if (err && 'message' in err) {
console.error(err.message);
@ -275,11 +142,22 @@ function internalErrorJson(err, res) {
console.error(err);
res.json({ message: 'internal error' });
}
}
try {
const result = wrapped.call(context, req, res, next);
if (result instanceof Promise) {
return result.catch(internalErrorJson);
}
} catch (err) {
internalErrorJson(err);
}
}
}
async function getGameListHandler(req, res, next) {
res.set('Cache-Control', 'no-store');
try {
const afterTime = req.params.afterTime;
if (afterTime !== undefined && !afterTime.match(/^\d+$/)) {
@ -322,14 +200,11 @@ async function getGameListHandler(req, res, next) {
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;
@ -375,9 +250,6 @@ async function getGameHandler(req, res, next) {
return;
}
}
} catch(err) {
internalErrorJson(err, res);
}
}
const updateTemplate = {
@ -413,7 +285,7 @@ function validateUpdate(body) {
async function postGameHandler(req, res, next) {
res.set('Cache-Control', 'no-store');
try {
const time = +new Date();
const gameId = req.params.gameId;
const body = validateUpdate(req.body);
@ -485,18 +357,18 @@ async function postGameHandler(req, res, next) {
WHERE (${whereClause}) AND modified = $modified
`;
const selectSql = `SELECT * FROM games WHERE gameId = $gameId`
let beginP, insertP, selectP, commitP;
let transactionP;
db.serialize(() => {
/* Important: We need to start all these queries without waiting in between. */
/* Otherwise db.serialize() will not have the desired effect. */
beginP = db.execAsync(`BEGIN TRANSACTION`);
insertP = db.runAsync(insertSql, params);
selectP = db.getAsync(selectSql, { $gameId: gameId });
commitP = db.execAsync(`COMMIT TRANSACTION`);
const beginP = db.execAsync(`BEGIN TRANSACTION`);
const insertP = db.runAsync(insertSql, params);
const selectP = db.getAsync(selectSql, { $gameId: gameId });
const commitP = db.execAsync(`COMMIT TRANSACTION`);
transactionP = Promise.all([beginP, insertP, commitP]).then(() => selectP);
});
/* Now wait for all the queries to finish. */
await Promise.all([beginP, insertP, commitP]);
const result = await selectP;
const result = await transactionP;
if (!result || result.modified !== params.$time) {
res.status(409).json({ message: 'update failed', modified: (result || {}).modified });
@ -506,44 +378,25 @@ async function postGameHandler(req, res, next) {
signalGameUpdate(gameId);
res.json({ success: true, modified: result.modified });
(async () => {
if (hasBoard) {
gun.get(PacoSakoUUID + '/game/' + gameId).get('board').put(result.board);
}
if (hasMeta) {
const meta = {
lightName: result.lightName,
darkName: result.darkName,
moves: result.moves,
timestamp: result.timestamp,
status: (result.status === '') ? null : result.status,
};
const metaRef = gun.get(PacoSakoUUID + '/meta/' + gameId).put(meta);
if (meta.lightName !== '' || meta.darkName !== '' || meta.moves !== 0) {
gun.get(PacoSakoUUID + '/meta').get(gameId).put(metaRef);
} else {
gun.get(PacoSakoUUID + '/meta').get(gameId).put(null);
}
}
})().catch(console.error);
} catch (err) {
internalErrorJson(err, res);
}
}
app.get('/pacosako/api/games/poll/:afterTime', getGameListHandler);
app.get('/pacosako/api/games', getGameListHandler);
const app = express();
app.get('/pacosako/api/:type(game|meta)/:gameId/poll/:afterTime', getGameHandler);
app.get('/pacosako/api/:type(game|meta)/:gameId', getGameHandler);
app.post('/pacosako/api/:type(game|meta)/:gameId', express.json(), postGameHandler);
app.get('/pacosako/api/games/poll/:afterTime', catchExceptionsJson(getGameListHandler));
app.get('/pacosako/api/games', catchExceptionsJson(getGameListHandler));
app.use('/pacosako/api', express.static('public'));
app.get('/pacosako/api/:type(game|meta)/:gameId/poll/:afterTime', catchExceptionsJson(getGameHandler));
app.get('/pacosako/api/:type(game|meta)/:gameId', catchExceptionsJson(getGameHandler));
app.post('/pacosako/api/:type(game|meta)/:gameId', express.json(), catchExceptionsJson(postGameHandler));
app.use('/gun', Gun.serve(__dirname));
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) {
config.key = fs.readFileSync(process.env.HTTPS_KEY);
config.cert = fs.readFileSync(process.env.HTTPS_CERT);
config.server = require('https').createServer(config, app);
} else {
config.server = require('http').createServer(app);
}
config.server.listen(config.port);

5821
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -4,12 +4,7 @@
"description": "",
"main": "index.js",
"dependencies": {
"core-js": "^3.6.4",
"express": "^4.17.1",
"gun": "^0.2020.301",
"gun-db": "^1.0.571",
"react": "^16.9.0",
"react-native": "^0.61.5",
"sqlite3": "^4.1.1"
},
"devDependencies": {},

View File

@ -1,97 +0,0 @@
<html>
<head>
<meta charset="UTF-8">
<title>Polling Test</title>
</head>
<body>
<script src="https://code.jquery.com/jquery-3.5.0.min.js"></script>
<script>
let gameId = '5b687c23df28945e';
let currentRequest = null;
function logResult(result) {
$('<p></p>').text(JSON.stringify(result)).prependTo('#current');
}
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 {
logResult(data);
startPoll(data.modified);
}
}
}).fail((jqXHR, textStatus, errorThrown) => {
if (currentRequest === thisRequest) {
logResult({ textStatus, errorThrown });
setTimeout(() => {
if (currentRequest === thisRequest) {
startPoll(0);
}
}, 3000);
}
});
}
function stopPoll() {
const request = currentRequest;
if (request !== null) {
currentRequest = null;
request.abort();
}
}
function doUpdate(gameId, data) {
$.ajax({
dataType: 'json',
contentType: 'application/json',
url: `https://jessemcdonald.info/pacosako/api/game/${gameId}`,
method: 'POST',
cache: false,
data: JSON.stringify(data),
timeout: 5000,
}).done((responseData, textStatus, jqXHR) => {
logResult({ [jqXHR.status]: responseData });
if ('modified' in responseData) {
data.modified = responseData.modified;
$('#json').val(JSON.stringify(data));
}
}).fail((jqXHR, textStatus, errorThrown) => {
logResult({ status: jqXHR.status, textStatus, errorThrown });
});
}
$(() => {
let counter = 0;
let row1 = $('<div></div>').appendTo('body');
$('<button id="start">Start Polling</button>').appendTo('body').on('click', () => {
$('#start').prop('disabled', true);
$('#stop').prop('disabled', false);
startPoll();
});
$('<button id="stop">Stop Polling</button>').prop('disabled', true).appendTo('body').on('click', () => {
$('#start').prop('disabled', false);
$('#stop').prop('disabled', true);
stopPoll()
});
let row2 = $('<div></div>').appendTo('body');
$('<input id="gameId">').appendTo(row2);
$('<input id="json">').appendTo(row2);
$('<button id="update">Update</button>').appendTo(row2).on('click', () => {
try {
doUpdate($('#gameId').val(), JSON.parse($('#json').val()));
} catch(err) {
logResult({ exception: err });
}
});
$('<div id="current"></div>').appendTo('body');
});
</script>
</body>
</html>
<!-- vim:set noexpandtab sw=2 ts=2: -->