Changed to socket.io

- changed from graphql to socket.io for events and information
- removed refreshing graphql functions from frontend
pull/23/head
Trivernis 6 years ago
parent 5c7bf97995
commit 38420bd0dd

@ -4,6 +4,20 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
### Added
- socket.io for real time communication
## Changed
- frontend to use socket.io instead of graphql for refreshing
### Removed
- graphql frontend functions to send messages and refresh
## [0.1.0] - 2019-05-19 ## [0.1.0] - 2019-05-19
### Added ### Added

@ -1,6 +1,6 @@
const createError = require('http-errors'), const createError = require('http-errors'),
express = require('express'),
path = require('path'), path = require('path'),
express = require('express'),
cookieParser = require('cookie-parser'), cookieParser = require('cookie-parser'),
logger = require('morgan'), logger = require('morgan'),
compileSass = require('express-compile-sass'), compileSass = require('express-compile-sass'),
@ -20,6 +20,9 @@ const createError = require('http-errors'),
changelogRouter = require('./routes/changelog'), changelogRouter = require('./routes/changelog'),
bingoRouter = require('./routes/bingo'); bingoRouter = require('./routes/bingo');
let app = require('express')(),
server = require('http').Server(app),
io = require('socket.io')(server);
async function init() { async function init() {
// grapql default resolver // grapql default resolver
@ -36,9 +39,9 @@ async function init() {
// database setup // database setup
let pgPool = globals.pgPool; let pgPool = globals.pgPool;
await pgPool.query(fsx.readFileSync('./sql/init.sql', 'utf-8')); await pgPool.query(fsx.readFileSync('./sql/init.sql', 'utf-8'));
await bingoRouter.init();
let app = express(); let bingoIo = io.of('/bingo');
await bingoRouter.init(bingoIo, io);
// view engine setup // view engine setup
app.set('views', path.join(__dirname, 'views')); app.set('views', path.join(__dirname, 'views'));
@ -98,7 +101,7 @@ async function init() {
res.status(err.status || 500); res.status(err.status || 500);
res.render('error'); res.render('error');
}); });
return app; return [app, server];
} }
module.exports = init; module.exports = init;

@ -27,15 +27,9 @@ try {
let port = normalizePort(process.env.PORT || settings.port || '3000'); let port = normalizePort(process.env.PORT || settings.port || '3000');
appInit().then((app) => { appInit().then(([app, server]) => {
app.set('port', port); app.set('port', port);
/**
* Create HTTP server.
*/
let server = http.createServer(app);
/** /**
* Listen on provided port, on all network interfaces. * Listen on provided port, on all network interfaces.
*/ */

279
package-lock.json generated

@ -77,6 +77,11 @@
"integrity": "sha512-HJ7CfNHrfJLlNTzIEUTj43LNWGkqpRLxm3YjAlcD0ACydk9XynzYsCBHxut+iqt+1aBXkx9UP/w/ZqMr13XIzg==", "integrity": "sha512-HJ7CfNHrfJLlNTzIEUTj43LNWGkqpRLxm3YjAlcD0ACydk9XynzYsCBHxut+iqt+1aBXkx9UP/w/ZqMr13XIzg==",
"dev": true "dev": true
}, },
"after": {
"version": "0.8.2",
"resolved": "https://registry.npmjs.org/after/-/after-0.8.2.tgz",
"integrity": "sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8="
},
"ajv": { "ajv": {
"version": "6.10.0", "version": "6.10.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.0.tgz", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.0.tgz",
@ -219,6 +224,11 @@
"resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz",
"integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=" "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg="
}, },
"arraybuffer.slice": {
"version": "0.0.7",
"resolved": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz",
"integrity": "sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog=="
},
"asap": { "asap": {
"version": "2.0.6", "version": "2.0.6",
"resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
@ -258,6 +268,11 @@
"resolved": "https://registry.npmjs.org/async-foreach/-/async-foreach-0.1.3.tgz", "resolved": "https://registry.npmjs.org/async-foreach/-/async-foreach-0.1.3.tgz",
"integrity": "sha1-NhIfhFwFeBct5Bmpfb6x0W7DRUI=" "integrity": "sha1-NhIfhFwFeBct5Bmpfb6x0W7DRUI="
}, },
"async-limiter": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz",
"integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg=="
},
"asynckit": { "asynckit": {
"version": "0.4.0", "version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
@ -347,6 +362,11 @@
"resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz",
"integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==" "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ=="
}, },
"backo2": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz",
"integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc="
},
"balanced-match": { "balanced-match": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
@ -402,6 +422,16 @@
} }
} }
}, },
"base64-arraybuffer": {
"version": "0.1.5",
"resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz",
"integrity": "sha1-c5JncZI7Whl0etZmqlzUv5xunOg="
},
"base64id": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/base64id/-/base64id-1.0.0.tgz",
"integrity": "sha1-R2iMuZu2gE8OBtPnY7HDLlfY5rY="
},
"basic-auth": { "basic-auth": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz",
@ -418,11 +448,24 @@
"tweetnacl": "^0.14.3" "tweetnacl": "^0.14.3"
} }
}, },
"better-assert": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/better-assert/-/better-assert-1.0.2.tgz",
"integrity": "sha1-QIZrnhueC1W0gYlDEeaPr/rrxSI=",
"requires": {
"callsite": "1.0.0"
}
},
"binary-extensions": { "binary-extensions": {
"version": "1.13.1", "version": "1.13.1",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz",
"integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==" "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw=="
}, },
"blob": {
"version": "0.0.5",
"resolved": "https://registry.npmjs.org/blob/-/blob-0.0.5.tgz",
"integrity": "sha512-gaqbzQPqOoamawKg0LGVd7SzLgXS+JH61oWprSLH+P+abTczqJbhTR8CmJ2u9/bUYNmHTGJx/UEmn6doAvvuig=="
},
"block-stream": { "block-stream": {
"version": "0.0.9", "version": "0.0.9",
"resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz", "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz",
@ -566,6 +609,11 @@
} }
} }
}, },
"callsite": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz",
"integrity": "sha1-KAOY5dZkvXQDi28JBRU+borxvCA="
},
"callsites": { "callsites": {
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
@ -752,11 +800,21 @@
"delayed-stream": "~1.0.0" "delayed-stream": "~1.0.0"
} }
}, },
"component-bind": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz",
"integrity": "sha1-AMYIq33Nk4l8AAllGx06jh5zu9E="
},
"component-emitter": { "component-emitter": {
"version": "1.3.0", "version": "1.3.0",
"resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz",
"integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==" "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg=="
}, },
"component-inherit": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/component-inherit/-/component-inherit-0.0.3.tgz",
"integrity": "sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM="
},
"concat-map": { "concat-map": {
"version": "0.0.1", "version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@ -1043,6 +1101,74 @@
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
"integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k="
}, },
"engine.io": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/engine.io/-/engine.io-3.3.2.tgz",
"integrity": "sha512-AsaA9KG7cWPXWHp5FvHdDWY3AMWeZ8x+2pUVLcn71qE5AtAzgGbxuclOytygskw8XGmiQafTmnI9Bix3uihu2w==",
"requires": {
"accepts": "~1.3.4",
"base64id": "1.0.0",
"cookie": "0.3.1",
"debug": "~3.1.0",
"engine.io-parser": "~2.1.0",
"ws": "~6.1.0"
},
"dependencies": {
"debug": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
"integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
"requires": {
"ms": "2.0.0"
}
}
}
},
"engine.io-client": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-3.3.2.tgz",
"integrity": "sha512-y0CPINnhMvPuwtqXfsGuWE8BB66+B6wTtCofQDRecMQPYX3MYUZXFNKDhdrSe3EVjgOu4V3rxdeqN/Tr91IgbQ==",
"requires": {
"component-emitter": "1.2.1",
"component-inherit": "0.0.3",
"debug": "~3.1.0",
"engine.io-parser": "~2.1.1",
"has-cors": "1.1.0",
"indexof": "0.0.1",
"parseqs": "0.0.5",
"parseuri": "0.0.5",
"ws": "~6.1.0",
"xmlhttprequest-ssl": "~1.5.4",
"yeast": "0.1.2"
},
"dependencies": {
"component-emitter": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz",
"integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY="
},
"debug": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
"integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
"requires": {
"ms": "2.0.0"
}
}
}
},
"engine.io-parser": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-2.1.3.tgz",
"integrity": "sha512-6HXPre2O4Houl7c4g7Ic/XzPnHBvaEmN90vtRO9uLmwtRqQmTOw0QMevL1TOfL2Cpu1VzsaTmMotQgMdkzGkVA==",
"requires": {
"after": "0.8.2",
"arraybuffer.slice": "~0.0.7",
"base64-arraybuffer": "0.1.5",
"blob": "0.0.5",
"has-binary2": "~1.0.2"
}
},
"entities": { "entities": {
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz",
@ -2579,6 +2705,26 @@
"ansi-regex": "^2.0.0" "ansi-regex": "^2.0.0"
} }
}, },
"has-binary2": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has-binary2/-/has-binary2-1.0.3.tgz",
"integrity": "sha512-G1LWKhDSvhGeAQ8mPVQlqNcOB2sJdwATtZKl2pDKKHfpf/rYj24lkinxf69blJbnsvtqqNU+L3SL50vzZhXOnw==",
"requires": {
"isarray": "2.0.1"
},
"dependencies": {
"isarray": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz",
"integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4="
}
}
},
"has-cors": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz",
"integrity": "sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk="
},
"has-flag": { "has-flag": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
@ -2706,6 +2852,11 @@
"repeating": "^2.0.0" "repeating": "^2.0.0"
} }
}, },
"indexof": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz",
"integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10="
},
"inflight": { "inflight": {
"version": "1.0.6", "version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
@ -3639,6 +3790,11 @@
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
}, },
"object-component": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/object-component/-/object-component-0.0.3.tgz",
"integrity": "sha1-8MaapQ78lbhmwYb0AKM3acsvEpE="
},
"object-copy": { "object-copy": {
"version": "0.1.0", "version": "0.1.0",
"resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz",
@ -3814,6 +3970,22 @@
"error-ex": "^1.2.0" "error-ex": "^1.2.0"
} }
}, },
"parseqs": {
"version": "0.0.5",
"resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.5.tgz",
"integrity": "sha1-1SCKNzjkZ2bikbouoXNoSSGouJ0=",
"requires": {
"better-assert": "~1.0.0"
}
},
"parseuri": {
"version": "0.0.5",
"resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.5.tgz",
"integrity": "sha1-gCBKUNTbt3m/3G6+J3jZDkvOMgo=",
"requires": {
"better-assert": "~1.0.0"
}
},
"parseurl": { "parseurl": {
"version": "1.3.3", "version": "1.3.3",
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
@ -4833,6 +5005,90 @@
} }
} }
}, },
"socket.io": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/socket.io/-/socket.io-2.2.0.tgz",
"integrity": "sha512-wxXrIuZ8AILcn+f1B4ez4hJTPG24iNgxBBDaJfT6MsyOhVYiTXWexGoPkd87ktJG8kQEcL/NBvRi64+9k4Kc0w==",
"requires": {
"debug": "~4.1.0",
"engine.io": "~3.3.1",
"has-binary2": "~1.0.2",
"socket.io-adapter": "~1.1.0",
"socket.io-client": "2.2.0",
"socket.io-parser": "~3.3.0"
}
},
"socket.io-adapter": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-1.1.1.tgz",
"integrity": "sha1-KoBeihTWNyEk3ZFZrUUC+MsH8Gs="
},
"socket.io-client": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-2.2.0.tgz",
"integrity": "sha512-56ZrkTDbdTLmBIyfFYesgOxsjcLnwAKoN4CiPyTVkMQj3zTUh0QAx3GbvIvLpFEOvQWu92yyWICxB0u7wkVbYA==",
"requires": {
"backo2": "1.0.2",
"base64-arraybuffer": "0.1.5",
"component-bind": "1.0.0",
"component-emitter": "1.2.1",
"debug": "~3.1.0",
"engine.io-client": "~3.3.1",
"has-binary2": "~1.0.2",
"has-cors": "1.1.0",
"indexof": "0.0.1",
"object-component": "0.0.3",
"parseqs": "0.0.5",
"parseuri": "0.0.5",
"socket.io-parser": "~3.3.0",
"to-array": "0.1.4"
},
"dependencies": {
"component-emitter": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz",
"integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY="
},
"debug": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
"integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
"requires": {
"ms": "2.0.0"
}
}
}
},
"socket.io-parser": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.3.0.tgz",
"integrity": "sha512-hczmV6bDgdaEbVqhAeVMM/jfUfzuEZHsQg6eOmLgJht6G3mPKMxYm75w2+qhAQZ+4X+1+ATZ+QFKeOZD5riHng==",
"requires": {
"component-emitter": "1.2.1",
"debug": "~3.1.0",
"isarray": "2.0.1"
},
"dependencies": {
"component-emitter": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz",
"integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY="
},
"debug": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
"integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
"requires": {
"ms": "2.0.0"
}
},
"isarray": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz",
"integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4="
}
}
},
"source-map": { "source-map": {
"version": "0.4.4", "version": "0.4.4",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz",
@ -5402,6 +5658,11 @@
"os-tmpdir": "~1.0.2" "os-tmpdir": "~1.0.2"
} }
}, },
"to-array": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/to-array/-/to-array-0.1.4.tgz",
"integrity": "sha1-F+bBH3PdTz10zaek/zI46a2b+JA="
},
"to-fast-properties": { "to-fast-properties": {
"version": "1.0.3", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz",
@ -5777,6 +6038,19 @@
"mkdirp": "^0.5.1" "mkdirp": "^0.5.1"
} }
}, },
"ws": {
"version": "6.1.4",
"resolved": "https://registry.npmjs.org/ws/-/ws-6.1.4.tgz",
"integrity": "sha512-eqZfL+NE/YQc1/ZynhojeV8q+H050oR8AZ2uIev7RU10svA9ZnJUddHcOUZTJLinZ9yEfdA2kSATS2qZK5fhJA==",
"requires": {
"async-limiter": "~1.0.0"
}
},
"xmlhttprequest-ssl": {
"version": "1.5.5",
"resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz",
"integrity": "sha1-wodrBhaKrcQOV9l+gRkayPQ5iz4="
},
"xtend": { "xtend": {
"version": "4.0.1", "version": "4.0.1",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz",
@ -5817,6 +6091,11 @@
"integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=" "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo="
} }
} }
},
"yeast": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz",
"integrity": "sha1-AI4G2AlDIMNy28L47XagymyKxBk="
} }
} }
} }

@ -25,7 +25,8 @@
"morgan": "1.9.1", "morgan": "1.9.1",
"node-sass": "4.12.0", "node-sass": "4.12.0",
"pg": "7.11.0", "pg": "7.11.0",
"pug": "2.0.3" "pug": "2.0.3",
"socket.io": "^2.2.0"
}, },
"devDependencies": { "devDependencies": {
"eslint": "^5.16.0", "eslint": "^5.16.0",

@ -1,66 +1,13 @@
/* eslint-disable no-unused-vars, no-undef */ /* eslint-disable no-unused-vars, no-undef */
/** class BingoGraphqlHelper {
* Returns the value of the url-param 'g'
* @returns {string}
*/
function getLobbyParam() {
let matches = window.location.href.match(/\??&?g=(\d+)/);
if (matches)
return matches[1];
else
return '';
}
/**
* REturns the value of the r url param
* @returns {string}
*/
function getRoundParam() {
let matches = window.location.href.match(/\??&?r=(\d+)/);
if (matches)
return matches[1];
else
return '';
}
/**
* Spawns a notification when the window is inactive (hidden).
* @param body
* @param title
*/
function spawnNotification(body, title) {
if (Notification.permission !== 'denied' && document[getHiddenNames().hidden]) {
let options = {
body: body,
icon: '/favicon.ico'
};
let n = new Notification(title, options);
}
}
/**
* Submits the value of the username-input to set the username.
* @returns {Promise<Boolean>}
*/
async function submitUsername() {
let unameInput = document.querySelector('#input-username');
let username = unameInput.value;
if (username.length > 1 && username.length <= 30) {
return await setUsername(username);
} else {
showError('You need to provide a username (min. 2 characters, max. 30)!');
return false;
}
}
/** /**
* Sets the username for a user * Sets the username for a user
* @param username {String} - the username * @param username {String} - the username
* @returns {Promise<boolean>} * @returns {Promise<boolean>}
*/ */
async function setUsername(username) { static async setUsername(username) {
username = username.replace(/^\s+|\s+$/g, ''); username = username.replace(/^\s+|\s+$/g, '');
let uname = username.replace(/[\n\t👑🌟]|^\s+|\s+$/gu, ''); let uname = username.replace(/[\n\t👑🌟]|^\s+|\s+$/gu, '');
if (uname.length === username.length) { if (uname.length === username.length) {
@ -80,6 +27,7 @@ async function setUsername(username) {
showError(response.errors[0].message); showError(response.errors[0].message);
else else
showError(`Failed to submit username.`); showError(`Failed to submit username.`);
console.error(response); console.error(response);
return false; return false;
} }
@ -89,38 +37,10 @@ async function setUsername(username) {
} }
/** /**
* Function that displays the ping in the console. * Creates a lobby via the graphql endpoint
* @returns {Promise<number>}
*/
async function ping() {
let start = new Date().getTime();
let response = await postGraphqlQuery(`
query {
time
}`);
console.log(`Ping: ${(new Date().getTime()) - start} ms`);
return (new Date().getTime()) - start;
}
/**
* Joins a lobby or says to create one if none is found
* @returns {Promise<void>}
*/
async function joinLobby() {
if (getLobbyParam()) {
if (await submitUsername())
window.location.reload();
} else {
showError('No lobby found. Please create one.');
}
}
/**
* Creates a lobby and redirects to the lobby.
* @returns {Promise<boolean>} * @returns {Promise<boolean>}
*/ */
async function createLobby() { static async createLobby() {
if (await submitUsername()) {
let response = await postGraphqlQuery(` let response = await postGraphqlQuery(`
mutation { mutation {
bingo { bingo {
@ -139,13 +59,12 @@ async function createLobby() {
return false; return false;
} }
} }
}
/** /**
* Lets the player leave the lobby * Leaves a lobby via the graphql endpoint
* @returns {Promise<void>} * @returns {Promise<void>}
*/ */
async function leaveLobby() { static async leaveLobby() {
let response = await postGraphqlQuery(` let response = await postGraphqlQuery(`
mutation($lobbyId:ID!){ mutation($lobbyId:ID!){
bingo { bingo {
@ -164,11 +83,11 @@ async function leaveLobby() {
} }
/** /**
* Kicks a player by id. * Kicks a player
* @param pid * @param pid
* @returns {Promise<void>} * @returns {Promise<void>}
*/ */
async function kickPlayer(pid) { static async kickPlayer(pid) {
let response = await postGraphqlQuery(` let response = await postGraphqlQuery(`
mutation ($lobbyId: ID!, $playerId:ID!) { mutation ($lobbyId: ID!, $playerId:ID!) {
bingo { bingo {
@ -189,6 +108,199 @@ async function kickPlayer(pid) {
} }
} }
/**
* Loads information about the rounds winner and the round stats.
* @returns {Promise<boolean>}
*/
static async loadWinnerInfo() {
let response = await postGraphqlQuery(`
query($lobbyId:ID!) {
bingo {
lobby(id:$lobbyId) {
currentRound {
status
winner {
id
username
}
start
finish
}
}
}
}`, {lobbyId: getLobbyParam()});
if (response.status === 200) {
let roundInfo = response.data.bingo.lobby.currentRound;
if (roundInfo.winner)
displayWinner(roundInfo);
else
window.location.reload();
} else {
console.error(response);
showError('Failed to get round information');
}
}
/**
* Loads the lobby wors in the words element via graphql.
* @returns {Promise<void>}
*/
static async loadLobbyWords() {
let response = await postGraphqlQuery(`
query($lobbyId:ID!){
bingo {
lobby(id:$lobbyId) {
words {
content
}
}
}
}`, {lobbyId: getLobbyParam()});
if (response.status === 200) {
let wordContainer = document.querySelector('#bingo-words');
if (wordContainer)
wordContainer.innerHTML = `<span class="bingoWord">
${response.data.bingo.lobby.words.map(x => x.content).join('</span><span class="bingoWord">')}</span>`;
} else {
showError('Failed to load words.');
}
}
/**
* Sets the settings of the lobby
* @param words
* @param gridSize
* @returns {Promise<LobbyWrapper.words|*|properties.words|{default, type}|boolean>}
*/
static async setLobbySettings(words, gridSize) {
gridSize = Number(gridSize);
let response = await postGraphqlQuery(`
mutation ($lobbyId: ID!, $words: [String!]!, $gridSize:Int!) {
bingo {
mutateLobby(id: $lobbyId) {
setWords(words: $words) {
words {
content
}
}
setGridSize(gridSize: $gridSize) {
gridSize
}
}
}
}
`, {lobbyId: getLobbyParam(), words: words, gridSize: gridSize});
if (response.status === 200) {
return response.data.bingo.mutateLobby.setWords.words;
} else {
console.error(response);
if (response.errors)
showError(response.errors[0].message);
else
showError('Error when submitting lobby settings.');
}
}
}
/**
* Returns the value of the url-param 'g'
* @returns {string}
*/
function getLobbyParam() {
let matches = window.location.href.match(/\??&?g=(\d+)/);
if (matches)
return matches[1];
else
return '';
}
/**
* Spawns a notification when the window is inactive (hidden).
* @param body
* @param title
*/
function spawnNotification(body, title) {
if (Notification.permission !== 'denied' && document[getHiddenNames().hidden]) {
let options = {
body: body,
icon: '/favicon.ico'
};
let n = new Notification(title, options);
}
}
/**
* Submits the value of the username-input to set the username.
* @returns {Promise<Boolean>}
*/
async function submitUsername() {
let unameInput = document.querySelector('#input-username');
let username = unameInput.value;
if (username.length > 1 && username.length <= 30) {
return await BingoGraphqlHelper.setUsername(username);
} else {
showError('You need to provide a username (min. 2 characters, max. 30)!');
return false;
}
}
/**
* Function that displays the ping in the console.
* @returns {Promise<number>}
*/
async function ping() {
let start = new Date().getTime();
let response = await postGraphqlQuery(`
query {
time
}`);
console.log(`Ping: ${(new Date().getTime()) - start} ms`);
return (new Date().getTime()) - start;
}
/**
* Joins a lobby or says to create one if none is found
* @returns {Promise<void>}
*/
async function joinLobby() {
if (getLobbyParam()) {
if (await submitUsername())
window.location.reload();
} else {
showError('No lobby found. Please create one.');
}
}
/**
* Creates a lobby and redirects to the lobby.
* @returns {Promise<boolean>}
*/
async function createLobby() {
if (await submitUsername())
await BingoGraphqlHelper.createLobby();
}
/**
* Lets the player leave the lobby
* @returns {Promise<void>}
*/
async function leaveLobby() {
await BingoGraphqlHelper.leaveLobby();
}
/**
* Kicks a player by id.
* @param pid
* @returns {Promise<void>}
*/
async function kickPlayer(pid) {
await BingoGraphqlHelper.kickPlayer(pid);
}
/** /**
* Executes a command * Executes a command
* @param message {String} - the message * @param message {String} - the message
@ -197,6 +309,7 @@ async function executeCommand(message) {
function reply(content) { function reply(content) {
addChatMessage({content: content, htmlContent: content, type: 'INFO'}); addChatMessage({content: content, htmlContent: content, type: 'INFO'});
} }
let jsStyle = document.querySelector('#js-style'); let jsStyle = document.querySelector('#js-style');
message = message.replace(/\s+$/g, ''); message = message.replace(/\s+$/g, '');
let command = /(\/\w+) ?(.*)?/g.exec(message); let command = /(\/\w+) ?(.*)?/g.exec(message);
@ -228,7 +341,7 @@ async function executeCommand(message) {
break; break;
case '/username': case '/username':
if (command[2]) { if (command[2]) {
let uname = await setUsername(command[2]); let uname = await BingoGraphqlHelper.setUsername(command[2]);
reply(`Your username is <b>${uname}</b> now.`); reply(`Your username is <b>${uname}</b> now.`);
} else { } else {
reply('You need to provide a username'); reply('You need to provide a username');
@ -252,67 +365,11 @@ async function sendChatMessage() {
if (messageInput.value && messageInput.value.length > 0) { if (messageInput.value && messageInput.value.length > 0) {
let message = messageInput.value; let message = messageInput.value;
messageInput.value = ''; messageInput.value = '';
if (/^\/\.*/g.test(message)) {
await executeCommand(message);
} else {
let response = await postGraphqlQuery(`
mutation($lobbyId:ID!, $message:String!){
bingo {
mutateLobby(id:$lobbyId) {
sendMessage(message:$message) {
id
htmlContent
type
author {
username
}
}
}
}
}`, {message: message, lobbyId: getLobbyParam()});
if (response.status === 200) {
addChatMessage(response.data.bingo.mutateLobby.sendMessage);
} else {
messageInput.value = message;
console.error(response);
showError('Error when sending message.');
}
}
}
}
/** if (/^\/\.*/g.test(message))
* Sets the words for the lobby await executeCommand(message);
* @param words
* @param gridSize
* @returns {Promise<LobbyWrapper.words|*|properties.words|{default, type}|boolean>}
*/
async function setLobbySettings(words, gridSize) {
gridSize = Number(gridSize);
let response = await postGraphqlQuery(`
mutation ($lobbyId: ID!, $words: [String!]!, $gridSize:Int!) {
bingo {
mutateLobby(id: $lobbyId) {
setWords(words: $words) {
words {
content
}
}
setGridSize(gridSize: $gridSize) {
gridSize
}
}
}
}
`, {lobbyId: getLobbyParam(), words: words, gridSize: gridSize});
if (response.status === 200) {
return response.data.bingo.mutateLobby.setWords.words;
} else {
console.error(response);
if (response.errors)
showError(response.errors[0].message);
else else
showError('Error when submitting lobby settings.'); socket.emit('message', message);
} }
} }
@ -325,7 +382,7 @@ async function startRound() {
let words = getLobbyWords(); let words = getLobbyWords();
if (words.length > 0) { if (words.length > 0) {
let gridSize = document.querySelector('#input-grid-size').value || 3; let gridSize = document.querySelector('#input-grid-size').value || 3;
let resultWords = await setLobbySettings(words, gridSize); let resultWords = await BingoGraphqlHelper.setLobbySettings(words, gridSize);
if (resultWords) { if (resultWords) {
textinput.value = resultWords.map(x => x.content).join('\n'); textinput.value = resultWords.map(x => x.content).join('\n');
let response = await postGraphqlQuery(` let response = await postGraphqlQuery(`
@ -393,6 +450,7 @@ async function submitFieldToggle(wordPanel) {
document.querySelector('#container-bingo-button').setAttribute('class', ''); document.querySelector('#container-bingo-button').setAttribute('class', '');
else else
document.querySelector('#container-bingo-button').setAttribute('class', 'hidden'); document.querySelector('#container-bingo-button').setAttribute('class', 'hidden');
} else { } else {
console.error(response); console.error(response);
showError('Error when submitting field toggle'); showError('Error when submitting field toggle');
@ -506,43 +564,11 @@ async function statusWrap(func) {
} }
} }
/**
* Loads information about the rounds winner and the round stats.
* @returns {Promise<boolean>}
*/
async function loadWinnerInfo() {
let response = await postGraphqlQuery(`
query($lobbyId:ID!) {
bingo {
lobby(id:$lobbyId) {
currentRound {
status
winner {
id
username
}
start
finish
}
}
}
}`, {lobbyId: getLobbyParam()});
if (response.status === 200) {
let roundInfo = response.data.bingo.lobby.currentRound;
if (roundInfo.winner)
displayWinner(roundInfo);
else
window.location.reload();
} else {
console.error(response);
showError('Failed to get round information');
}
}
/** /**
* Adds a message to the chat * Adds a message to the chat
* @param messageObject {Object} - the message object returned by graphql * @param messageObject {Object} - the message object returned by graphql
* @param player {Number} - the id of the player * @param [player] {Number} - the id of the player
*/ */
function addChatMessage(messageObject, player) { function addChatMessage(messageObject, player) {
let msgSpan = document.createElement('span'); let msgSpan = document.createElement('span');
@ -558,8 +584,10 @@ function addChatMessage(messageObject, player) {
msgSpan.innerHTML = ` msgSpan.innerHTML = `
<span class="chatMessageContent ${messageObject.type}">${messageObject.htmlContent}</span>`; <span class="chatMessageContent ${messageObject.type}">${messageObject.htmlContent}</span>`;
if (messageObject.author && messageObject.author.id !== player) if (messageObject.author && messageObject.author.id !== player)
spawnNotification(messageObject.content, messageObject.author.username); spawnNotification(messageObject.content, messageObject.author.username);
let chatContent = document.querySelector('#chat-content'); let chatContent = document.querySelector('#chat-content');
chatContent.appendChild(msgSpan); chatContent.appendChild(msgSpan);
chatContent.scrollTop = chatContent.scrollHeight; // auto-scroll to bottom chatContent.scrollTop = chatContent.scrollHeight; // auto-scroll to bottom
@ -577,231 +605,121 @@ function addPlayer(player, options) {
if (options.isAdmin && player.id !== options.admin) if (options.isAdmin && player.id !== options.admin)
playerContainer.innerHTML = `<button class="kickPlayerButton" onclick="kickPlayer(${player.id})"></button>`; playerContainer.innerHTML = `<button class="kickPlayerButton" onclick="kickPlayer(${player.id})"></button>`;
playerContainer.innerHTML += `<span class="playernameSpan">${player.username}</span>`; playerContainer.innerHTML += `<span class="playernameSpan">${player.username}</span>`;
if (player.id === options.admin) if (player.id === options.admin)
playerContainer.innerHTML += "<span class='adminSpan'> 👑</span>"; playerContainer.innerHTML += "<span class='adminSpan'> 👑</span>";
document.querySelector('#player-list').appendChild(playerContainer); document.querySelector('#player-list').appendChild(playerContainer);
} }
/** /**
* Refreshes the bingo chat * Returns the current player id
* @returns {Promise<void>} * @returns {Promise<*>}
*/ */
async function refreshChat() { async function getPlayerInfo() {
try { let result = await postGraphqlQuery(`
let response = await postGraphqlQuery(`
query ($lobbyId:ID!) { query ($lobbyId:ID!) {
bingo { bingo {
player { player {
id id
}
lobby(id:$lobbyId) {
messages {
id
type
htmlContent
content
author {
id
username username
} }
}
}
}
}`, {lobbyId: getLobbyParam()});
if (response.status === 200) {
let messages = response.data.bingo.lobby.messages;
for (let message of messages)
if (!document.querySelector(`.chatMessage[msg-id="${message.id}"]`))
addChatMessage(message, response.data.bingo.player.id);
} else {
showError('Failed to refresh messages');
console.error(response);
}
} catch (err) {
showError('Failed to refresh messages');
console.error(err);
}
}
/**
* Refreshes the player list
* @returns {Promise<void>}
*/
async function refreshPlayers() {
try {
let response = await postGraphqlQuery(`
query ($lobbyId: ID!) {
bingo {
player {
id
}
lobby(id:$lobbyId) { lobby(id:$lobbyId) {
players {
id id
username
wins(lobbyId: $lobbyId)
}
admin { admin {
id id
} }
} }
} }
} }`, {lobbyId: getLobbyParam()});
`, {lobbyId: getLobbyParam()}); if (result.status === 200) {
if (response.status === 200) { let bingoData = result.data.bingo;
let players = response.data.bingo.lobby.players; return {
let adminId = response.data.bingo.lobby.admin.id; id: bingoData.player.id,
let isAdmin = response.data.bingo.player.id === adminId; username: bingoData.player.username,
for (let player of players) isAdmin: bingoData.lobby.admin.id === bingoData.player.id
if (!document.querySelector(`.playerEntryContainer[b-pid="${player.id}"]`)) };
addPlayer(player, {admin: adminId, isAdmin: isAdmin});
} else { } else {
showError('Failed to refresh players'); showError('Failed to fetch player Id');
console.error(response); console.error(result);
}
} catch (err) {
showError('Failed to refresh players');
console.error(err);
} }
} }
/** /**
* Removes players that are not existent in the player array * Initializes all socket events
* @param players {Array<Object>} - player id response of graphql * @param data
*/ */
function removeLeftPlayers(players) { function initSocketEvents(data) {
for (let playerEntry of document.querySelectorAll('.playerEntryContainer')) let playerId = data.id;
if (!players.find(x => (x.id === playerEntry.getAttribute('b-pid')))) let indicator = document.querySelector('#status-indicator');
playerEntry.remove(); indicator.setAttribute('status', 'error');
}
/** socket.on('connect', () => {
* Refreshes if a player-refresh is needed. indicator.setAttribute('socket-status', 'connected');
* Removes players that are not in the lobby anyomre. });
* @param players
*/
function checkPlayerRefresh(players) {
let playerRefresh = false;
removeLeftPlayers(players);
for (let player of players)
if (!document.querySelector(`.playerEntryContainer[b-pid="${player.id}"]`))
playerRefresh = true;
if (playerRefresh)
statusWrap(refreshPlayers);
}
/** socket.on('reconnect', () => {
* Checks if messages need to be refreshed and does it if it needs to. indicator.setAttribute('socket-status', 'connected');
* @param messages });
*/
function checkMessageRefresh(messages) {
let messageRefresh = false;
for (let message of messages)
if (!document.querySelector(`.chatMessage[msg-id="${message.id}"]`))
messageRefresh = true;
if (messageRefresh)
statusWrap(refreshChat);
}
/** socket.on('disconnect', () => {
* refreshes the lobby and calls itself with a timeout indicator.setAttribute('socket-status', 'disconnected');
* @returns {Promise<void>} showError('Disconnected from socket!');
*/ });
async function refreshLobby() {
try {
let response = await postGraphqlQuery(`
query($lobbyId:ID!){
bingo {
lobby(id:$lobbyId) {
players {
id
}
messages {
id
}
currentRound {
id
status
}
words {
content
}
}
}
}`, {lobbyId: getLobbyParam()});
if (response.status === 200) {
let {players, messages, currentRound} = response.data.bingo.lobby;
checkPlayerRefresh(players);
checkMessageRefresh(messages);
let wordContainer = document.querySelector('#bingo-words');
if (wordContainer) socket.on('reconnecting', () => {
wordContainer.innerHTML = `<span class="bingoWord"> indicator.setAttribute('socket-status', 'reconnecting');
${response.data.bingo.lobby.words.map(x => x.content).join('</span><span class="bingoWord">')}</span>`; });
if (currentRound && currentRound.status === 'ACTIVE' && Number(currentRound.id) !== Number(getRoundParam())) { socket.on('error', (error) => {
insertParam('r', currentRound.id); showError(`Socket Error: ${JSON.stringify(error)}`);
spawnNotification('The round started!', 'Bingo'); });
}
} else { socket.on('message', (msg) => {
showError('Failed to refresh lobby'); console.log(msg);
console.error(response); addChatMessage(msg, playerId);
} });
socket.on('statusChange', (status) => {
console.log(`Status changed to ${status}`);
if (status === 'FINISHED')
BingoGraphqlHelper.loadWinnerInfo();
else
window.location.reload();
});
socket.on('playerJoin', (playerObject) => {
addPlayer(playerObject, data);
});
socket.on('playerLeave', (playerId) => {
document.querySelector(`.playerEntryContainer[b-pid='${playerId}']`).remove();
});
socket.on('usernameChange', (playerObject) => {
console.log(playerObject);
document.querySelector(`.playerEntryContainer[b-pid='${playerObject.id}'] .playerNameSpan`).innerText = playerObject.username;
});
socket.on('wordsChange', async () => {
try {
await BingoGraphqlHelper.loadLobbyWords();
} catch (err) { } catch (err) {
showError('Failed to refresh lobby'); showError('Failed to load new lobby words.');
console.error(err);
} finally {
setTimeout(refreshLobby, 1000);
} }
});
} }
/** /**
* Checks the status of the lobby and the current round. * Initializes the lobby refresh with sockets or graphql
* @returns {Promise<void>}
*/ */
async function refreshRound() { function initRefresh() {
let roundOver = false; getPlayerInfo().then((data) => {
try { socket = new SimpleSocket(`/bingo/${getLobbyParam()}`, {playerId: data.id});
let response = await postGraphqlQuery(` initSocketEvents(data);
query($lobbyId:ID!) { });
bingo {
lobby(id:$lobbyId) {
players {
id
}
messages {
id
}
currentRound {
id
status
}
}
}
}`, {lobbyId: getLobbyParam()});
if (response.status === 200) {
let {players, messages, currentRound} = response.data.bingo.lobby;
checkPlayerRefresh(players);
checkMessageRefresh(messages);
if (!currentRound || currentRound.status === "FINISHED") {
roundOver = true;
await loadWinnerInfo();
}
} else {
showError('Failed to refresh round');
console.error(response);
}
} catch (err) {
showError('Failed to refresh round');
console.error(err);
} finally {
if (!roundOver)
setTimeout(refreshRound, 1000);
}
} }
window.addEventListener("unhandledrejection", function (promiseRejectionEvent) { window.addEventListener("unhandledrejection", function (promiseRejectionEvent) {
@ -815,7 +733,7 @@ window.addEventListener("keydown", async (e) => {
e.preventDefault(); e.preventDefault();
if (document.querySelector('#input-bingo-words')) { if (document.querySelector('#input-bingo-words')) {
let gridSize = document.querySelector('#input-grid-size').value || 3; let gridSize = document.querySelector('#input-grid-size').value || 3;
await statusWrap(async () => await setLobbySettings(getLobbyWords(), gridSize)); await statusWrap(async () => await BingoGraphqlHelper.setLobbySettings(getLobbyWords(), gridSize));
} }
} }
}, false); }, false);
@ -830,3 +748,5 @@ window.onload = async () => {
} }
} }
}; };
let socket = null;

@ -1,5 +1,47 @@
/* eslint-disable no-unused-vars, no-undef */ /* eslint-disable no-unused-vars, no-undef */
/**
* A simple WebSocket
*/
class SimpleSocket {
/**
* Constructor
* @param url
* @param emitContext
*/
constructor(url, emitContext) {
this.socket = io.connect(url);
this.context = emitContext;
}
/**
* Wrapper for the emit function
* @param event
* @param data
* @param callback
*/
emit(event, data, callback) {
this.socket.emit(event, this.context, data, callback);
}
/**
* Wrapper for on event function
* @param event
* @param callback
*/
on(event, callback) {
this.socket.on(event, callback);
}
/**
* Returns if the socket is connected
* @returns {*|boolean}
*/
get connected() {
return this.socket.connected;
}
}
/** /**
* HTTP POST to an url with a post body * HTTP POST to an url with a post body
* @param url {String} - the url to post to * @param url {String} - the url to post to

@ -45,6 +45,21 @@
animation-duration: 5s animation-duration: 5s
animation-iteration-count: infinite animation-iteration-count: infinite
.socketStatusIndicator[socket-status='connected']
background-color: $success
.socketStatusIndicator[socket-status='reconnecting']
background-color: mix($pending, $error)
animation-name: pulse-opacity
animation-duration: 5s
animation-iteration-count: infinite
.socketStatusIndicator[socket-status='disconnected']
background-color: $error
animation-name: pulse-opacity
animation-duration: 2s
animation-iteration-count: infinite
.pending .pending
background-color: $pending !important background-color: $pending !important
animation-name: pulse-opacity animation-name: pulse-opacity

@ -12,6 +12,7 @@ const express = require('express'),
globals = require('../lib/globals'); globals = require('../lib/globals');
let pgPool = globals.pgPool; let pgPool = globals.pgPool;
let sockets = {};
/** /**
* Class to manage the bingo data in the database. * Class to manage the bingo data in the database.
@ -141,6 +142,15 @@ class BingoDataManager {
return await this._queryDatabase(this.queries.updateLobbyExpire.sql, [lobbyId]); return await this._queryDatabase(this.queries.updateLobbyExpire.sql, [lobbyId]);
} }
/**
* Returns all lobby ids
* @returns {Promise<*>}
*/
async getLobbyIds() {
let results = await this._queryAllResults(this.queries.getLobbyIds.sql, []);
return results.map(x => x.id);
}
/** /**
* Returns the row of the lobby. * Returns the row of the lobby.
* @param lobbyId {Number} - the id of the lobby * @param lobbyId {Number} - the id of the lobby
@ -650,12 +660,12 @@ class GridWrapper {
let gridField = new GridFieldWrapper(result); let gridField = new GridFieldWrapper(result);
let username = await (await this.player()).username(); let username = await (await this.player()).username();
let word = await gridField.word.content(); let word = await gridField.word.content();
let lobbyWrapper = await this.lobby();
if (gridField.submitted) if (gridField.submitted)
await bdm.addInfoMessage(this.lobbyId, await lobbyWrapper.addInfoMessage(`${username} toggled "${word}"`);
`${username} toggled "${word}"`);
else else
await bdm.addInfoMessage(this.lobbyId, await lobbyWrapper.addInfoMessage(`${username} untoggled "${word}"`);
`${username} untoggled "${word}"`);
return gridField; return gridField;
} }
} }
@ -881,6 +891,7 @@ class RoundWrapper {
let updateResult = await bdm.setRoundWinner(this.id, winnerId); let updateResult = await bdm.setRoundWinner(this.id, winnerId);
if (updateResult) if (updateResult)
await this.setFinished(); await this.setFinished();
(await this.lobby()).socket.emit('statusChange', 'FINISHED');
return true; return true;
} }
} }
@ -894,6 +905,7 @@ class LobbyWrapper {
*/ */
constructor(id, row) { constructor(id, row) {
this.id = id; this.id = id;
this.socket = sockets[id];
this._infoLoaded = false; this._infoLoaded = false;
if (row) if (row)
this._assignProperties(row); this._assignProperties(row);
@ -1061,6 +1073,7 @@ class LobbyWrapper {
await this._createRound(); await this._createRound();
await this._createGrids(); await this._createGrids();
await this.setRoundStatus('ACTIVE'); await this.setRoundStatus('ACTIVE');
this.socket.emit('statusChange', 'ACTIVE');
} }
} }
@ -1115,6 +1128,7 @@ class LobbyWrapper {
await this.addWord(word); await this.addWord(word);
for (let word of removedWords) for (let word of removedWords)
await this.removeWord(word.id); await this.removeWord(word.id);
this.socket.emit('wordsChange');
} }
} }
@ -1144,6 +1158,16 @@ class LobbyWrapper {
}; };
} }
/**
* Adds an info message and emits the message event.
* @param message {String} - the info messages content
* @returns {Promise<void>}
*/
async addInfoMessage(message) {
let result = await bdm.addInfoMessage(this.id, message);
this.socket.emit('message', await resolveMessage(new MessageWrapper(result)));
}
/** /**
* Adds a player to the lobby. * Adds a player to the lobby.
* @param playerId * @param playerId
@ -1151,8 +1175,10 @@ class LobbyWrapper {
*/ */
async addPlayer(playerId) { async addPlayer(playerId) {
await bdm.addPlayerToLobby(playerId, this.id); await bdm.addPlayerToLobby(playerId, this.id);
let username = await new PlayerWrapper(playerId).username(); let playerWrapper = new PlayerWrapper(playerId);
await bdm.addInfoMessage(this.id, `${username} joined.`); this.socket.emit('playerJoin', await resolvePlayer(playerWrapper));
let username = await playerWrapper.username();
await this.addInfoMessage(`${username} joined.`);
await this._loadLobbyInfo(true); await this._loadLobbyInfo(true);
} }
@ -1164,7 +1190,8 @@ class LobbyWrapper {
async removePlayer(playerId) { async removePlayer(playerId) {
await bdm.removePlayerFromLobby(playerId, this.id); await bdm.removePlayerFromLobby(playerId, this.id);
let username = await new PlayerWrapper(playerId).username(); let username = await new PlayerWrapper(playerId).username();
await bdm.addInfoMessage(this.id, `${username} left.`); this.socket.emit('playerLeave', playerId);
await this.addInfoMessage(`${username} left.`);
await this._loadLobbyInfo(true); await this._loadLobbyInfo(true);
} }
@ -1185,7 +1212,8 @@ class LobbyWrapper {
async setRoundStatus(status) { async setRoundStatus(status) {
let currentRound = await this.currentRound(); let currentRound = await this.currentRound();
await currentRound.updateStatus(status); await currentRound.updateStatus(status);
await bdm.addInfoMessage(this.id, `Admin set round status to ${status}`); await this.addInfoMessage(`Admin set round status to ${status}`);
this.socket.emit('statusChange', status);
if (status === 'FINISHED') if (status === 'FINISHED')
await bdm.clearGrids(this.id); await bdm.clearGrids(this.id);
@ -1395,6 +1423,39 @@ async function getGridData(lobbyId, playerId) {
return {fields: fieldGrid, bingo: await grid.bingo()}; return {fields: fieldGrid, bingo: await grid.bingo()};
} }
/**
* Resolves a message wrapper object
* @param msgWrapper
* @returns {Promise<{author: {id: (*|MessageWrapper.author.id), username: String}, id: *, content: *, timestamp: Timestamp | * | number, htmlContent: *}>}
*/
async function resolveMessage(msgWrapper) {
return {
id: msgWrapper.id,
type: msgWrapper.type,
content: msgWrapper.content,
timestamp: msgWrapper.timestamp,
htmlContent: msgWrapper.htmlContent,
author: {
id: msgWrapper.author.id,
username: await msgWrapper.author.username()
}
};
}
/**
* Resolves a player wrapper object
* @param playerWrapper
* @param lobbyId
* @returns {Promise<{wins: PlayerWrapper.wins, id: *, username: (String|*)}>}
*/
async function resolvePlayer(playerWrapper, lobbyId) {
return {
id: playerWrapper.id,
username: await playerWrapper.username(),
wins: await playerWrapper.wins({lobbyId: lobbyId})
};
}
/** /**
* Returns resolved message data. * Returns resolved message data.
* @param lobbyId * @param lobbyId
@ -1412,11 +1473,37 @@ async function getMessageData(lobbyId) {
// -- Router stuff // -- Router stuff
/**
* Creates a lobby socket if none exists.
* @param io
* @param lobbyId
*/
function createSocketIfNotExist(io, lobbyId) {
if (!sockets[lobbyId]) {
let lobbySocket = io.of(`/bingo/${lobbyId}`);
sockets[lobbyId] = lobbySocket;
lobbySocket.on('connection', (socket) => {
socket.on('message', async (context, message) => {
try {
let result = await bdm.addUserMessage(lobbyId, context.playerId, message);
let messageWrapper = new MessageWrapper(result);
lobbySocket.emit('message', await resolveMessage(messageWrapper));
} catch (err) {
console.log(err);
}
});
});
}
}
let bdm = new BingoDataManager(pgPool); let bdm = new BingoDataManager(pgPool);
router.init = async () => { router.init = async (bingoIo, io) => {
await bdm.init(); await bdm.init();
};
for (let id of await bdm.getLobbyIds())
createSocketIfNotExist(io, id);
router.use(async (req, res, next) => { router.use(async (req, res, next) => {
if (req.session.bingoPlayerId) if (req.session.bingoPlayerId)
@ -1432,6 +1519,7 @@ router.get('/', async (req, res) => {
if (playerId && await playerWrapper.exists() && req.query.g && await lobbyWrapper.exists()) { if (playerId && await playerWrapper.exists() && req.query.g && await lobbyWrapper.exists()) {
let lobbyId = req.query.g; let lobbyId = req.query.g;
createSocketIfNotExist(io, lobbyId);
if (!(await lobbyWrapper.roundActive() && await playerWrapper.hasGrid(lobbyId))) { if (!(await lobbyWrapper.roundActive() && await playerWrapper.hasGrid(lobbyId))) {
if (!await lobbyWrapper.hasPlayer(playerId)) if (!await lobbyWrapper.hasPlayer(playerId))
@ -1506,8 +1594,13 @@ router.graphqlResolver = async (req, res) => {
} else { } else {
let oldName = await playerWrapper.username(); let oldName = await playerWrapper.username();
await bdm.updatePlayerUsername(playerId, username); await bdm.updatePlayerUsername(playerId, username);
if (req.query.g)
await bdm.addInfoMessage(req.query.g, `${oldName} changed username to ${username}`); if (req.query.g) {
let lobbyWrapper = new LobbyWrapper(req.query.g);
lobbyWrapper.socket.emit('usernameChange',
await resolvePlayer(new PlayerWrapper(playerId), req.query.g));
await lobbyWrapper.addInfoMessage(`${oldName} changed username to ${username}`);
}
} }
return new PlayerWrapper(playerId); return new PlayerWrapper(playerId);
} else { } else {
@ -1519,6 +1612,7 @@ router.graphqlResolver = async (req, res) => {
if (playerId) if (playerId)
if (gridSize > 0 && gridSize < 10) { if (gridSize > 0 && gridSize < 10) {
let result = await bdm.createLobby(playerId, gridSize); let result = await bdm.createLobby(playerId, gridSize);
createSocketIfNotExist(io, result.id);
return new LobbyWrapper(result.id); return new LobbyWrapper(result.id);
} else { } else {
res.status(413); res.status(413);
@ -1527,6 +1621,7 @@ router.graphqlResolver = async (req, res) => {
}, },
mutateLobby: async ({id}) => { mutateLobby: async ({id}) => {
let lobbyId = id; let lobbyId = id;
createSocketIfNotExist(io, lobbyId);
await bdm.updateLobbyExpiration(lobbyId); await bdm.updateLobbyExpiration(lobbyId);
let lobbyWrapper = new LobbyWrapper(lobbyId); let lobbyWrapper = new LobbyWrapper(lobbyId);
return { return {
@ -1640,5 +1735,6 @@ router.graphqlResolver = async (req, res) => {
} }
}; };
}; };
};
module.exports = router; module.exports = router;

@ -77,6 +77,9 @@ getPlayerInLobby:
getLobbyPlayers: getLobbyPlayers:
sql: SELECT * FROM bingo.lobby_players WHERE lobby_players.lobby_id = $1; sql: SELECT * FROM bingo.lobby_players WHERE lobby_players.lobby_id = $1;
getLobbyIds:
sql: SELECT lobbys.id FROM bingo.lobbys;
# returns all direct information about the lobby # returns all direct information about the lobby
# params: # params:
# - {Number} - the id of the lobby # - {Number} - the id of the lobby

@ -19,4 +19,4 @@ block content
include includes/bingo-chat include includes/bingo-chat
include includes/bingo-statusbar include includes/bingo-statusbar
script(type='text/javascript') refreshLobby(); script(type='text/javascript') initRefresh();

@ -23,4 +23,4 @@ block content
b-column=field.column b-column=field.column
b-sub=`${field.submitted}`) b-sub=`${field.submitted}`)
span= field.word span= field.word
script(type='text/javascript') refreshRound(); script(type='text/javascript') initRefresh();

@ -1,5 +1,5 @@
div(id='statusbar') div(id='statusbar')
div(id='status-indicator' class='statusIndicator' status='idle') div(id='status-indicator' class='statusIndicator socketStatusIndicator' status='idle')
span(id='error-message') span(id='error-message')
span(id='container-info') span(id='container-info')
a(href='https://github.com/Trivernis/whooshy/issues') Bug/Feature a(href='https://github.com/Trivernis/whooshy/issues') Bug/Feature

@ -1,2 +1,3 @@
link(rel='stylesheet', href='/sass/style.sass') link(rel='stylesheet' href='/sass/style.sass')
script(type='text/javascript', src='/javascripts/common.js') script(type='text/javascript' src='/javascripts/common.js')
script(type='text/javascript' src='/socket.io/socket.io.js')

Loading…
Cancel
Save