Merge pull request #24 from Trivernis/develop

Develop
pull/27/head v0.2.0
Trivernis 6 years ago committed by GitHub
commit 9904bb06ba
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -4,6 +4,33 @@ 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).
## [0.2.0] - 2019-05-23
### Added
- socket.io for real time communication
- compression and minify
- auto replacing image links with images in the chat
- auto replacing urls to urls with link in the chat
- message editing and deleting *(undo your mistakes)*
- changelog to `bingo-create`
### Changed
- frontend to use socket.io instead of graphql for refreshing
- use of socket.io for toggeling binogo fields
- button behaviour on `bingo-create` to respond to the situation *(whatever that means)*
### Removed
- graphql frontend functions to send messages and refresh
### Fixed
- error message when loading `bingo-create`
- chat doesn't scroll down when an image is send *(r/mildlyinfuriating)*
- some style issues
## [0.1.1] - 2019-05-21 ## [0.1.1] - 2019-05-21
### Fixed ### Fixed

@ -1,9 +1,12 @@
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'),
minify = require('express-minify'),
compression = require('compression'),
uglifyEs = require('uglify-es'),
session = require('express-session'), session = require('express-session'),
pgSession = require('connect-pg-simple')(session), pgSession = require('connect-pg-simple')(session),
fsx = require('fs-extra'), fsx = require('fs-extra'),
@ -20,6 +23,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,15 +42,19 @@ 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'));
app.set('view engine', 'pug'); app.set('view engine', 'pug');
app.set('trust proxy', 1); app.set('trust proxy', 1);
app.use(compression());
app.use(minify({
uglifyJsModule: uglifyEs
}));
app.use(logger('dev')); app.use(logger('dev'));
app.use(express.json()); app.use(express.json());
app.use(express.urlencoded({extended: false})); app.use(express.urlencoded({extended: false}));
@ -98,7 +108,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;

@ -6,7 +6,6 @@
const appInit = require('../app'); const appInit = require('../app');
const debug = require('debug')('whooshy:server'); const debug = require('debug')('whooshy:server');
const http = require('http');
const yaml = require('js-yaml'); const yaml = require('js-yaml');
const fsx = require('fs-extra'); const fsx = require('fs-extra');
@ -27,15 +26,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.
*/ */

@ -1,10 +1,12 @@
const utils = require('./utils'), const utils = require('./utils'),
fsx = require('fs-extra'),
pg = require('pg'); pg = require('pg');
const settings = utils.readSettings('.'); const settings = utils.readSettings('.');
Object.assign(exports, { Object.assign(exports, {
settings: settings, settings: settings,
changelog: fsx.readFileSync('CHANGELOG.md', 'utf-8'),
pgPool: new pg.Pool({ pgPool: new pg.Pool({
host: settings.postgres.host, host: settings.postgres.host,
port: settings.postgres.port, port: settings.postgres.port,
@ -14,7 +16,7 @@ Object.assign(exports, {
}), }),
cookieInfo: { cookieInfo: {
headline: 'This website uses cookies', headline: 'This website uses cookies',
content: 'This website uses cookies to store your session data (like for bingo).', content: "This website uses cookies to store your session data. No data is permanently stored.",
onclick: 'acceptCookies()', onclick: 'acceptCookies()',
id: 'cookie-container', id: 'cookie-container',
button: 'All right!' button: 'All right!'

363
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,58 @@
"delayed-stream": "~1.0.0" "delayed-stream": "~1.0.0"
} }
}, },
"commander": {
"version": "2.20.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.0.tgz",
"integrity": "sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ=="
},
"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="
},
"compressible": {
"version": "2.0.17",
"resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.17.tgz",
"integrity": "sha512-BGHeLCK1GV7j1bSmQQAi26X+GgWcTjLr/0tzSvMCl3LH1w1IJ4PFSPoV5316b30cneTziC+B1a+3OjoSUcQYmw==",
"requires": {
"mime-db": ">= 1.40.0 < 2"
}
},
"compression": {
"version": "1.7.4",
"resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz",
"integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==",
"requires": {
"accepts": "~1.3.5",
"bytes": "3.0.0",
"compressible": "~2.0.16",
"debug": "2.6.9",
"on-headers": "~1.0.2",
"safe-buffer": "5.1.2",
"vary": "~1.1.2"
},
"dependencies": {
"debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"requires": {
"ms": "2.0.0"
}
}
}
},
"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 +1138,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",
@ -1618,6 +1781,32 @@
} }
} }
}, },
"express-minify": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/express-minify/-/express-minify-1.0.0.tgz",
"integrity": "sha512-04/iYxB79jGeNZBBkbAW7L7FMG4Wtu78F1SayXIKiJD6MfqYnOI3DD8no7QOntgedYCdYUpj+Skg8QWR/2WnMQ==",
"requires": {
"clean-css": "^4.1.7",
"on-headers": "^1.0.1",
"uglify-js": "^3.0.28"
},
"dependencies": {
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
},
"uglify-js": {
"version": "3.5.14",
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.5.14.tgz",
"integrity": "sha512-dgyjIw8KFK6AyVl5vm2tEqPewv5TKGEiiVFLI1LbF+oHua/Njd8tZk3lIbF1AWU1rNdEg7scaceADb4zqCcWXg==",
"requires": {
"commander": "~2.20.0",
"source-map": "~0.6.1"
}
}
}
},
"express-session": { "express-session": {
"version": "1.16.1", "version": "1.16.1",
"resolved": "https://registry.npmjs.org/express-session/-/express-session-1.16.1.tgz", "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.16.1.tgz",
@ -2579,6 +2768,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 +2915,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 +3853,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 +4033,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 +5068,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 +5721,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",
@ -5526,6 +5850,27 @@
"resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz",
"integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==" "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA=="
}, },
"uglify-es": {
"version": "3.3.9",
"resolved": "https://registry.npmjs.org/uglify-es/-/uglify-es-3.3.9.tgz",
"integrity": "sha512-r+MU0rfv4L/0eeW3xZrd16t4NZfK8Ld4SWVglYBb7ez5uXFWHuVRs6xCTrf1yirs9a4j4Y27nn7SRfO6v67XsQ==",
"requires": {
"commander": "~2.13.0",
"source-map": "~0.6.1"
},
"dependencies": {
"commander": {
"version": "2.13.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.13.0.tgz",
"integrity": "sha512-MVuS359B+YzaWqjCL/c+22gfryv+mCBPHAv3zyVI2GN8EY6IRP8VwtasXn8jyyhvvq84R4ImN1OKRtcbIasjYA=="
},
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
}
}
},
"uglify-js": { "uglify-js": {
"version": "2.8.29", "version": "2.8.29",
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz",
@ -5777,6 +6122,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 +6175,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="
} }
} }
} }

@ -6,12 +6,14 @@
"start": "node ./bin/www" "start": "node ./bin/www"
}, },
"dependencies": { "dependencies": {
"compression": "^1.7.4",
"connect-pg-simple": "5.0.0", "connect-pg-simple": "5.0.0",
"cookie-parser": "1.4.4", "cookie-parser": "1.4.4",
"debug": "4.1.1", "debug": "4.1.1",
"express": "4.17.0", "express": "4.17.0",
"express-compile-sass": "latest", "express-compile-sass": "latest",
"express-graphql": "0.8.0", "express-graphql": "0.8.0",
"express-minify": "^1.0.0",
"express-session": "latest", "express-session": "latest",
"fs-extra": "8.0.1", "fs-extra": "8.0.1",
"graphql": "14.3.0", "graphql": "14.3.0",
@ -25,7 +27,9 @@
"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",
"uglify-es": "^3.3.9"
}, },
"devDependencies": { "devDependencies": {
"eslint": "^5.16.0", "eslint": "^5.16.0",

File diff suppressed because it is too large Load Diff

@ -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
@ -122,9 +164,15 @@ async function indicateStatus(func, indicatorSelector) {
statusIndicator.setAttribute('status', 'success'); statusIndicator.setAttribute('status', 'success');
else else
statusIndicator.setAttribute('status', 'error'); statusIndicator.setAttribute('status', 'error');
setTimeout(() => {
statusIndicator.setAttribute('status', '');
}, 1000);
} catch (err) { } catch (err) {
console.error(err); console.error(err);
statusIndicator.setAttribute('status', 'error'); statusIndicator.setAttribute('status', 'error');
setTimeout(() => {
statusIndicator.setAttribute('status', '');
}, 1000);
} }
} }

@ -1,23 +1,38 @@
@import ../mixins @import ../mixins
@import ../vars @import ../vars
//@media(max-device-width: 641px) @media(max-device-height: 500px)
div#container-winner
margin: 5% auto !important
@media(max-device-width: 600px)
div#container-bingo-lobby
grid-template: 0 10% 85% 5% / 0 0 0 100% 0 !important
div#container-lobby-settings
display: none !important
div#container-players
display: none !important
//@media(min-device-width: 641px) //@media(min-device-width: 641px)
.popup .popup
@include default-element
position: fixed position: fixed
display: grid top: 0
height: calc(50% - 1rem) left: 0
width: calc(40% - 1rem) height: 100%
top: 25% width: 100%
left: 30%
text-align: center
vertical-align: middle
padding: 1rem
z-index: 1000 z-index: 1000
grid-template: 60% 40% / 100%
#container-winner
@include default-element
position: relative
margin: 20% auto
text-align: center
width: 50%
h1 h1
@include gridPosition(1, 2, 1, 1) @include gridPosition(1, 2, 1, 1)
@ -74,8 +89,12 @@
height: auto height: auto
border-radius: 0.2em border-radius: 0.2em
.chatMessage.selected
background-color: lighten($primary, 15%)
#container-chat #container-chat
height: calc(100% - 2px) height: calc(100% - 2px)
position: relative
#chat-content #chat-content
height: calc(100% - 3rem) height: calc(100% - 3rem)
width: calc(100% - 2px) width: calc(100% - 2px)
@ -222,29 +241,55 @@
#container-bingo-create #container-bingo-create
display: grid display: grid
grid-template: 5% 10% 10% 70% 5% /10% 80% 10% grid-template: 5% 45% 45% 5% /5% 35% 5% 50% 5%
height: 100% height: 100%
width: 100% width: 100%
#username-form #username-form
@include gridPosition(2, 3, 2, 3) @include gridPosition(2, 3, 2, 3)
margin: auto margin: auto
position: relative
h2
text-align: center
margin: 0 0 1rem 0
> *
margin: auto
width: 100%
.statusIndicator
height: 1em
width: 1em
display: inline-block
margin: auto 1em
#input-username #submit-username[status='success']
margin: 0 0 0 3em background-color: $success
transition-duration: 1s
#submit-username[status='pending']
transition-duration: 1s
background-color: $pending
animation-name: pulse-opacity
animation-duration: 4s
animation-iteration-count: infinite
#lobby-form #lobby-form
@include gridPosition(3, 5, 2, 3) @include gridPosition(3, 4, 2, 3)
margin: 10% auto margin: auto
width: 100%
button button
width: 100% width: 100%
margin: auto
button.inactive
background-color: lighten($primary, 10%)
#changelog
@include gridPosition(2, 4, 4, 5)
height: 100%
width: calc(100% - 2rem)
overflow-y: auto
margin: 0 0.5rem
padding: 0 0.5rem
box-shadow: inset 0 0 1rem darken($primary, 10%)
#container-bingo-lobby #container-bingo-lobby
@include fillWindow @include fillWindow
@ -256,7 +301,7 @@
#lobby-title #lobby-title
@include gridPosition(2, 3, 2, 5) @include gridPosition(2, 3, 2, 5)
margin: auto margin: 0.5rem auto
#container-players #container-players
@include gridPosition(3, 4, 2, 3) @include gridPosition(3, 4, 2, 3)

@ -7,14 +7,6 @@
.hidden .hidden
display: None !important display: None !important
.popup
height: 60%
width: 40%
z-index: 1000
position: fixed
top: 20%
left: 30%
.grid .grid
display: grid display: grid
@ -45,6 +37,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

@ -4,7 +4,10 @@ const express = require('express'),
mdEmoji = require('markdown-it-emoji'), mdEmoji = require('markdown-it-emoji'),
mdMark = require('markdown-it-mark'), mdMark = require('markdown-it-mark'),
mdSmartarrows = require('markdown-it-smartarrows'), mdSmartarrows = require('markdown-it-smartarrows'),
md = require('markdown-it')() md = require('markdown-it')({
linkify: true,
typographer: true
})
.use(mdEmoji) .use(mdEmoji)
.use(mdMark) .use(mdMark)
.use(mdSmartarrows), .use(mdSmartarrows),
@ -12,6 +15,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.
@ -138,7 +142,16 @@ class BingoDataManager {
* @returns {Promise<*>} * @returns {Promise<*>}
*/ */
async updateLobbyExpiration(lobbyId) { async updateLobbyExpiration(lobbyId) {
return await this._queryDatabase(this.queries.updateLobbyExpire.sql, [lobbyId]); return await this._queryFirstResult(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);
} }
/** /**
@ -430,6 +443,34 @@ class BingoDataManager {
return await this._queryFirstResult(this.queries.addUserMessage.sql, [playerId, lobbyId, messageContent]); return await this._queryFirstResult(this.queries.addUserMessage.sql, [playerId, lobbyId, messageContent]);
} }
/**
* Edits a message
* @param messageId {Number} - the id of the message
* @param messageContent {String} - the new content of the message
* @returns {Promise<*>}
*/
async editMessage(messageId, messageContent) {
return await this._queryFirstResult(this.queries.editMessage.sql, [messageId, messageContent]);
}
/**
* Deletes a message
* @param messageId {Number} - the id of the message
* @returns {Promise<*>}
*/
async deleteMessage(messageId) {
return await this._queryFirstResult(this.queries.deleteMessage.sql, [messageId]);
}
/**
* Returns the data of a message
* @param messageId {Number} - the id of the message
* @returns {Promise<*>}
*/
async getMessageData(messageId) {
return await this._queryFirstResult(this.queries.getMessageData.sql, [messageId]);
}
/** /**
* Adds a message of type "INFO" to the lobby * Adds a message of type "INFO" to the lobby
* @param lobbyId {Number} - the id of the lobby * @param lobbyId {Number} - the id of the lobby
@ -650,12 +691,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;
} }
} }
@ -668,7 +709,7 @@ class MessageWrapper {
constructor(row) { constructor(row) {
this.id = row.id; this.id = row.id;
this.content = row.content; this.content = row.content;
this.htmlContent = md.renderInline(this.content); this.htmlContent = md.renderInline(preMarkdownParse(this.content));
this.author = new PlayerWrapper(row.player_id); this.author = new PlayerWrapper(row.player_id);
this.lobby = new LobbyWrapper(row.lobby_id); this.lobby = new LobbyWrapper(row.lobby_id);
this.type = row.type; this.type = row.type;
@ -881,6 +922,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', await resolvePlayer(new PlayerWrapper(winnerId)));
return true; return true;
} }
} }
@ -894,6 +936,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);
@ -907,7 +950,7 @@ class LobbyWrapper {
*/ */
async _loadLobbyInfo(force) { async _loadLobbyInfo(force) {
if (!this._infoLoaded && !force) { if (!this._infoLoaded && !force) {
let row = await bdm.getLobbyInfo(this.id); let row = await bdm.updateLobbyExpiration(this.id);
this._assignProperties(row); this._assignProperties(row);
} }
} }
@ -928,6 +971,14 @@ class LobbyWrapper {
} }
} }
/**
* Emits an event is a socket exists for the lobby
*/
emit() {
if (this.socket)
this.socket.emit(...arguments);
}
/** /**
* Returns if the lobby exists (based on one loaded attribute) * Returns if the lobby exists (based on one loaded attribute)
* @returns {Promise<boolean>} * @returns {Promise<boolean>}
@ -1061,6 +1112,7 @@ class LobbyWrapper {
await this._createRound(); await this._createRound();
await this._createGrids(); await this._createGrids();
await this.setRoundStatus('ACTIVE'); await this.setRoundStatus('ACTIVE');
this.emit('statusChange', 'ACTIVE');
} }
} }
@ -1115,6 +1167,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.emit('wordsChange');
} }
} }
@ -1144,6 +1197,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.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 +1214,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.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 +1229,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.emit('playerLeave', playerId);
await this.addInfoMessage(`${username} left.`);
await this._loadLobbyInfo(true); await this._loadLobbyInfo(true);
} }
@ -1185,7 +1251,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.emit('statusChange', status);
if (status === 'FINISHED') if (status === 'FINISHED')
await bdm.clearGrids(this.id); await bdm.clearGrids(this.id);
@ -1333,6 +1400,28 @@ function checkBingo(fg) {
return diagonalBingo || verticalCheck || horizontalCheck; return diagonalBingo || verticalCheck || horizontalCheck;
} }
/**
* Parses the message and replaces all links with markdown-links and images with markdown-images.
* @param message {String} - the raw message
*/
function preMarkdownParse(message) {
let linkMatch = /(^|[^(])https?:\/\/((([\w-]+\.)+[\w-]+)(\S*))([^)]|$)/g;
let imageMatch = /.*\.(\w+)/g;
let imageExtensions = ['png', 'jpg', 'jpeg', 'gif', 'bmp', 'svg'];
let links = message.match(linkMatch);
if (links)
for (let link of links) {
let linkGroups = linkMatch.exec(link);
let imgGroups = imageMatch.exec(link);
if (imgGroups && imgGroups[1] && imageExtensions.includes(imgGroups[1]))
message = message.replace(link, `![${linkGroups[1]}](${link})`);
}
return message;
}
/** /**
* Gets player data for a lobby * Gets player data for a lobby
* @param lobbyWrapper * @param lobbyWrapper
@ -1395,6 +1484,53 @@ 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})
};
}
/**
* Resolves a fieldWrapper object
* @param fieldWrapper
* @returns {Promise<{submitted: (Object.submitted|*), column: *, bingo: boolean, row: (*)}>}
*/
async function resolveGridField(fieldWrapper) {
return {
row: fieldWrapper.row,
column: fieldWrapper.column,
submitted: fieldWrapper.submitted,
bingo: await fieldWrapper.grid.bingo()
};
}
/** /**
* Returns resolved message data. * Returns resolved message data.
* @param lobbyId * @param lobbyId
@ -1405,18 +1541,79 @@ async function getMessageData(lobbyId) {
let messages = await lobbyWrapper.messages({limit: 20}); let messages = await lobbyWrapper.messages({limit: 20});
let msgReturn = []; let msgReturn = [];
for (let message of messages) for (let message of messages)
msgReturn.push(Object.assign(message, {username: await message.author.username()})); msgReturn.push(Object.assign(message, {
playerId: message.author.id,
username: await message.author.username()
}));
return msgReturn; return msgReturn;
} }
// -- 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.error(err);
}
});
socket.on('messageEdit', async (context, message, messageId) => {
try {
let row = await bdm.getMessageData(messageId);
if (row.player_id === Number(context.playerId)) {
let result = await bdm.editMessage(messageId, message);
let messageWrapper = new MessageWrapper(result);
lobbySocket.emit('messageEdit', await resolveMessage(messageWrapper));
} else {
socket.emit('userError', "You are only allowed to edit your messages.");
}
} catch (err) {
console.error(err);
}
});
socket.on('messageDelete', async (context, messageId) => {
try {
let row = await bdm.getMessageData(messageId);
if (row.player_id === Number(context.playerId)) {
await bdm.deleteMessage(messageId);
lobbySocket.emit('messageDelete', messageId);
}
} catch (err) {
console.error(err);
}
});
socket.on('fieldToggle', async (context, location) => {
let {row, column} = location;
let result = await (await (new PlayerWrapper(context.playerId)).grid({lobbyId: lobbyId}))
.toggleField(row, column);
socket.emit('fieldChange', await resolveGridField(result));
});
});
}
}
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 +1629,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))
@ -1469,7 +1667,9 @@ router.get('/', async (req, res) => {
} else { } else {
res.render('bingo/bingo-create', { res.render('bingo/bingo-create', {
info: info, info: info,
username: await playerWrapper.username() username: await playerWrapper.username(),
changelog: md.render(globals.changelog),
primaryJoin: (req.query.g && await lobbyWrapper.exists())
}); });
} }
}); });
@ -1506,8 +1706,15 @@ 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);
if (await lobbyWrapper.exists()) {
lobbyWrapper.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,7 +1726,8 @@ 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);
return new LobbyWrapper(result.id); createSocketIfNotExist(io, result.id);
return new LobbyWrapper(result.id, result);
} else { } else {
res.status(413); res.status(413);
} }
@ -1527,6 +1735,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 +1849,6 @@ router.graphqlResolver = async (req, res) => {
} }
}; };
}; };
};
module.exports = router; module.exports = router;

@ -1,17 +1,14 @@
const express = require('express'), const express = require('express'),
router = express.Router(), router = express.Router(),
globals = require('../lib/globals'), globals = require('../lib/globals'),
fsx = require('fs-extra'),
mdEmoji = require('markdown-it-emoji'), mdEmoji = require('markdown-it-emoji'),
md = require('markdown-it')() md = require('markdown-it')()
.use(mdEmoji); .use(mdEmoji);
let changelog = fsx.readFileSync('CHANGELOG.md', 'utf-8');
/* GET home page. */ /* GET home page. */
router.get('/', (req, res) => { router.get('/', (req, res) => {
let info = req.session.acceptedCookies? null: globals.cookieInfo; let info = req.session.acceptedCookies? null: globals.cookieInfo;
res.render('changelog/changes', { changelog: md.render(changelog), info: info}); res.render('changelog/changes', { changelog: md.render(globals.changelog), info: info});
}); });
module.exports = router; module.exports = router;

@ -11,7 +11,7 @@ CREATE TABLE IF NOT EXISTS bingo.lobbys (
admin_id serial references bingo.players(id) ON DELETE SET NULL, admin_id serial references bingo.players(id) ON DELETE SET NULL,
grid_size integer DEFAULT 3 NOT NULL, grid_size integer DEFAULT 3 NOT NULL,
current_round integer, current_round integer,
expire timestamp DEFAULT (NOW() + interval '1 hour' ) expire timestamp DEFAULT (NOW() + interval '4 hour' )
); );
-- lobbys-players table -- lobbys-players table

@ -48,7 +48,7 @@ addLobby:
# params: # params:
# - {Number} - the id of the lobby # - {Number} - the id of the lobby
updateLobbyExpire: updateLobbyExpire:
sql: UPDATE bingo.lobbys SET expire = (NOW() + interval '1 hours') WHERE id = $1; sql: UPDATE bingo.lobbys SET expire = (NOW() + interval '4 hours') WHERE id = $1 RETURNING *;
# inserts a player into a lobby # inserts a player into a lobby
# params: # params:
@ -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
@ -259,9 +262,28 @@ getWordsForGridId:
addUserMessage: addUserMessage:
sql: INSERT INTO bingo.messages (player_id, lobby_id, content) VALUES ($1, $2, $3) RETURNING *; sql: INSERT INTO bingo.messages (player_id, lobby_id, content) VALUES ($1, $2, $3) RETURNING *;
# edits a message
# params:
# - {Number} - the id of the message
# - {Number} - the new content of the message
editMessage:
sql: UPDATE bingo.messages SET content = $2 WHERE id = $1 RETURNING *;
# inserts a info message # inserts a info message
# params: # params:
# - {Number} - the id of the lobby # - {Number} - the id of the lobby
# - {String} - the content of the message # - {String} - the content of the message
addInfoMessage: addInfoMessage:
sql: INSERT INTO bingo.messages (type, lobby_id, content) VALUES ('INFO', $1, $2) RETURNING *; sql: INSERT INTO bingo.messages (type, lobby_id, content) VALUES ('INFO', $1, $2) RETURNING *;
# returns the data of a message
# params:
# - {Number} - the id of the message
getMessageData:
sql: SELECT * from bingo.messages WHERE id = $1;
# deletes a message
# params:
# - {Number} - the id of the message
deleteMessage:
sql: DELETE FROM bingo.messages WHERE id = $1;

@ -3,17 +3,25 @@ extends includes/bingo-layout
block content block content
div(id='container-bingo-create') div(id='container-bingo-create')
div(id='username-form') div(id='username-form')
h2 Please enter a username
input(id='input-username' input(id='input-username'
type='text' type='text'
placeholder='Enter your name' placeholder='Enter your name'
value=username value=username
maxlength=30 maxlength=30
onkeydown='submitOnEnter(event, () => indicateStatus(submitUsername, "#username-status"))') onkeydown='submitOnEnter(event, () => indicateStatus(submitUsername, "#username-status"))')
if primaryJoin
button( button(
id='submit-username' id='submit-username'
onclick='indicateStatus(submitUsername, "#username-status")') Set Username onclick='joinLobby()') Join Lobby
div(id='username-status' class='statusIndicator') else
button(
id='submit-username'
onclick='indicateStatus(submitUsername, "#submit-username")') Set Username
div(id='lobby-form') div(id='lobby-form')
button(id='join-lobby' onclick='joinLobby()') Join Lobby if primaryJoin
button(id='create-lobby' class='inactive' onclick='createLobby()') Create Lobby
else
button(id='create-lobby' onclick='createLobby()') Create Lobby button(id='create-lobby' onclick='createLobby()') Create Lobby
div(id='changelog')!= changelog
include includes/bingo-statusbar include includes/bingo-statusbar

@ -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();

@ -2,7 +2,12 @@ div(id='container-chat')
style(id='js-style') style(id='js-style')
div(id='chat-content') div(id='chat-content')
for message in messages for message in messages
span.chatMessage(msg-type=message.type msg-id=message.id) span.chatMessage(
msg-type=message.type
msg-id=message.id
msg-pid=message.playerId
msg-raw=message.content)
if message.type === 'USER' if message.type === 'USER'
span.chatUsername= `${message.username}: ` span.chatUsername= `${message.username}: `
span(class=`chatMessageContent ${message.type}`)!= message.htmlContent span(class=`chatMessageContent ${message.type}`)!= message.htmlContent
@ -10,6 +15,6 @@ div(id='container-chat')
id='chat-input' id='chat-input'
type='text' type='text'
placeholder='send message' placeholder='send message'
onkeypress='submitOnEnter(event, () => statusWrap(sendChatMessage))' onkeypress='onInputKeypress(event)'
maxlength="250" maxlength="250"
autocomplete='off') autocomplete='off')

@ -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