Browse Source

changed project to Typescript

master
yarrum 4 years ago
parent
commit
c91725408c
  1. 50
      ScratchNotes.txt
  2. 29
      components/objectDefinitions.js
  3. 66
      components/players/playerMgmtFuncs.js
  4. 22
      components/stateMonitor.js
  5. 18
      config/serverConfig.js
  6. 7
      nodemon.json
  7. 2541
      package-lock.json
  8. 10
      package.json
  9. 44
      server.js
  10. 10
      src/components/common/randomString.ts
  11. 0
      src/components/common/sleep.js
  12. 0
      src/components/common/wrtColor.ts
  13. 29
      src/components/objectDefinitions.ts
  14. 22
      src/components/players/playerCleanup.ts
  15. 82
      src/components/players/playerMgmtFuncs.ts
  16. 42
      src/components/serverMgmt.ts
  17. 17
      src/components/serverProcesses/stateMonitor.ts
  18. 41
      src/components/test/playerTestFunctions.ts
  19. 13
      src/config/config.json
  20. 36
      src/config/config.ts
  21. 47
      src/server.ts
  22. 18
      tsconfig.json

50
ScratchNotes.txt

@ -1,50 +0,0 @@ @@ -1,50 +0,0 @@
Recreate Love Letter as Browser Game (not for profit)
Design Envisioned UI
Pick Modules around it
Express
Socket.IO
Design Data Format
- Handle Multiple Games
- Allow Invites to Game
Design Backend
Design FrontEnd
Design an Invite Feature
Make it reconnectable
No logins, but have unique IDs
Make it https
Animations
Design Chat
Design Guide on how to play
Design Bots to play against
#Basic setup with socket.io + Express
https://medium.com/@raj_36650/integrate-socket-io-with-node-js-express-2292ca13d891
#Guide for HTTPs on Express, SSL Cert
https://medium.com/@nitinpatel_20236/how-to-create-an-https-server-on-localhost-using-express-366435d61f28
#Socket.IO handling Cleaner
https://socket.io/docs/v4/server-application-structure/
#Automatic Node Reload on changes
https://stackoverflow.com/questions/45622125/how-can-i-add-live-reload-to-my-nodejs-server
#socket.io, socket attributes
https://socket.io/docs/v4/client-socket-instance/
#MEVN stack. MongoDB, Express, VueJS, NodeJs
https://vegibit.com/vue-js-express-tutorial/
https://mfikri.com/en/blog/nodejs-express-mysql-vue
Install VUE Global
npm install -g @vue/cli
vue --version
___________________________________________________________________

29
components/objectDefinitions.js

@ -1,29 +0,0 @@ @@ -1,29 +0,0 @@
// Object Definitions
class player {
constructor (userid,
username,
gameid,
lastActivity,
connected) {
this.userid = userid
this.username = username
this.gameid = gameid
this.lastActivity = lastActivity
this.connected = connected
}
}
class table {
}
class game {
}
// Object Initialization
let state = {
players:[],
tables:[],
games:[]
}
module.exports = {
state: state,
players: state.players
}

66
components/players/playerMgmtFuncs.js

@ -1,66 +0,0 @@ @@ -1,66 +0,0 @@
// Sends Updates to socket when players change
const wrtColor = require('../common/wrtColor')
function mapRemoveLastActivityProp (player) {
// Make this specific by props to remove
let {lastActivity, ...other} = player
// return {
// id: player.id,
// username: player.username,
// gameid: player.gameid
// }
return other
}
function continuousEmitPlayers (state,io) {
let previousPlayersRelevant = []
let timerId = setTimeout(function tick() {
// get players excluding the "lastActivity" because it is constantly updating
playersRelevant = state.players.map(mapRemoveLastActivityProp)
// console.log(playersRelevant)
// convert to string for comparison
if ((JSON.stringify(playersRelevant)) !== (JSON.stringify(previousPlayersRelevant))) {
// if changes happen, emit to all sockets
// wrtColor.blue('change to Players. Push to Sockets')
io.emit('playersUpdate', playersRelevant)
}
previousPlayersRelevant = playersRelevant
timerId = setTimeout(tick, 1000)
}, 1000)
}
function socketGetPlayers (state,socket) {
playersRelevant = state.players.map(mapRemoveLastActivityProp)
socket.emit('playersUpdate', playersRelevant)
}
function checkValidPlayerName(players,name) {
let response = {
valid: true,
reason: ""
}
// Returns True/False
// 15 character limit
// No special characters
// name not in use already
}
function socketRegisterPlayer (io,state,data) {
wrtColor.warn('socketRegisterPlayer')
console.log(data)
//console.log(state.players)
io.emit('msg',{poop:"salad"})
let checkResult = checkValidPlayerName(state.players, name)
console.log(checkResult)
// add them to state.players
}
module.exports = {
continuousEmitPlayers: continuousEmitPlayers,
socketGetPlayers: socketGetPlayers,
socketRegisterPlayer: socketRegisterPlayer,
}

22
components/stateMonitor.js

@ -1,22 +0,0 @@ @@ -1,22 +0,0 @@
const serverConfig = require('../config/serverConfig.js') // Server Config
const wrtColor = require('./common/wrtColor')
const fs = require('fs')
function stateMonitor (state) {
setInterval(function() {
// Convert to string
let stateString = JSON.stringify(state, null, 2)
fs.writeFile(serverConfig.stateJsonPath, stateString, (err) => {
if (err) {
throw err
}
// wrtColor.blue("state.json saved")
})
}, serverConfig.stateMonitorRate)
}
module.exports = {
stateMonitor: stateMonitor
}

18
config/serverConfig.js

@ -1,18 +0,0 @@ @@ -1,18 +0,0 @@
module.exports = {
// Server
certKeyPath: './cert/server.key', //Called by server.js
certPath: './cert/server.cert', //Called by server.js
httpsPort: 9000,
frontEndUrl: 'https://localhost', // Vue running from https
//PlayerCleanup
cleanupInterval: 5000, // runs every 5 seconds
playerExpiration: 60000, //300000, // 5 minutes without activity before being removed
//State Monitor
stateJsonPath: './log/state.json',
stateMonitorRate: 1000,
//Player Settings
playersUpdateCheckRate: 500, // Rate between checks to send player updates to sockets
playerNameMaxLength: 15,
playerIdLength: 6,
}

7
nodemon.json

@ -0,0 +1,7 @@ @@ -0,0 +1,7 @@
{
"verbose": false,
"watch": "src/**/*.ts",
"execMap": {
"ts": "ts-node"
}
}

2541
package-lock.json generated

File diff suppressed because it is too large Load Diff

10
package.json

@ -5,8 +5,9 @@ @@ -5,8 +5,9 @@
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "node server.js",
"live": "nodemon server.js --ignore ./log/"
"start": "node src/server.ts",
"compile": "npx tsc",
"live": "nodemon src/server.ts"
},
"author": "",
"license": "ISC",
@ -15,5 +16,10 @@ @@ -15,5 +16,10 @@
"express": "^4.17.3",
"socket.io": "^4.4.1",
"vue-router": "^4.0.13"
},
"devDependencies": {
"nodemon": "^2.0.15",
"ts-node": "^10.7.0",
"typescript": "^4.6.3"
}
}

44
server.js

@ -1,44 +0,0 @@ @@ -1,44 +0,0 @@
const path = require('path')
const fs = require('fs')
const express = require('express')
const serverConfig = require('./config/serverConfig.js') // Server Config
const wrtColor = require('./components/common/wrtColor')
//Http 80 Route traffic to the frontend VUE application running with HTTPS
const http = require('http')
const httpApp = express()
httpApp.all('*', (req, res) => res.redirect(301, `${serverConfig.frontEndUrl}`)) //301 - Moved Permanently
const httpServer = http.createServer(httpApp)
httpServer.listen(80, () => {
//console.log(`http redirectiong 80 to Vue front end at ${serverConfig.frontEndUrl}`)
wrtColor.green(`http redirectiong 80 to Vue front end at ${serverConfig.frontEndUrl}`)
})
//Https to run socket.io
const https = require('https')
const httpsApp = express()
const keyPath = fs.readFileSync(path.resolve(__dirname, `${serverConfig.certKeyPath}`))
const certPath = fs.readFileSync(path.resolve(__dirname, `${serverConfig.certPath}`))
const servOptions = {
key: keyPath,
cert: certPath
}
const httpsServer = https.createServer(servOptions,httpsApp)
httpsServer.listen(serverConfig.httpsPort, () => {
//console.log(`https listening on ${serverConfig.httpsPort} to handle socket.io requests`)
wrtColor.green(`https listening on ${serverConfig.httpsPort} to handle socket.io requests`)
})
// Attach Socket.IO to https server
const io = require('socket.io') (httpsServer, {
cors: { //https://socket.io/docs/v3/handling-cors/
origin: `${serverConfig.frontEndUrl}`,
methods: ["GET", "POST"]
}
})
//load gameManagement
require('./components/serverMgmt')
//pass socket.io connections to socketHandler
const { socketHandler } = require('./components/serverMgmt.js')
socketHandler(io)

10
components/common/randomString.js → src/components/common/randomString.ts

@ -1,14 +1,14 @@ @@ -1,14 +1,14 @@
let validCharacters = ['0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z']
genRandomString = (stringLength) => {
export function genRandomString (stringLength) {
let newString = ''
for (let i = 0; i < stringLength; i++) {
$index = Math.floor(Math.random() * 35)
let $index = Math.floor(Math.random() * 35)
newString += validCharacters[$index]
}
return newString
}
module.exports = {
genRandomString: genRandomString
}
// module.exports = {
// genRandomString: genRandomString
// }

0
components/common/sleep.js → src/components/common/sleep.js

0
components/common/wrtColor.js → src/components/common/wrtColor.ts

29
src/components/objectDefinitions.ts

@ -0,0 +1,29 @@ @@ -0,0 +1,29 @@
export interface Player {
name: String
gameId: String
lastActivity: Number
connected: Boolean
}
export interface Table {}
export interface Game {}
export interface State {
players: Player[]
tables: Table[]
games: Game[]
}
export let state : State = {
players: [
// {
// name: "Charlie",
// gameId: "0",
// lastActivity: 0,
// connected: false
// }
],
tables: [],
games: [],
}

22
components/players/playerCleanup.js → src/components/players/playerCleanup.ts

@ -1,32 +1,34 @@ @@ -1,32 +1,34 @@
const serverConfig = require('../../config/serverConfig.js') // Server Config
import { Config } from "../../config/config"
const config: Config = require('../../config/config.json')
const wrtColor = require('../common/wrtColor')
// const { sleep } = require('./common/sleep')
function filterExpired(player) {
// let diff = ((new Date())-player.lastActivity)
// console.log(`${player.username} diff: ${diff}`)
if (((new Date())-player.lastActivity) <= serverConfig.playerExpiration) {
if (((new Date().valueOf())-player.lastActivity) <= config.playerExpiration) {
// if (config.playerExpiration >= ((new Date())-player.lastActivity)) {
return player
} else {
// console.log(`Expired Session: ${player.username}`)
wrtColor.magenta(`Expired Session: ${player.username}`)
wrtColor.magenta(`Expired Session: ${player.name}`)
}
}
function playerCleanup (state) {
export function playerCleanup (state) {
// setInterval(function() { //<--This works, but I'm concerned about Memory Leak. I need to learn more.
// console.log(state)
// state.players = state.players.filter(checkExpired)
// }, serverConfig.cleanupInterval)
// }, config.cleanupInterval)
//setTimeout Version self-calling function
let timerId = setTimeout(function tick() {
// console.log(state)
state.players = state.players.filter(filterExpired)
timerId = setTimeout(tick, serverConfig.cleanupInterval)
}, serverConfig.cleanupInterval)
timerId = setTimeout(tick, config.cleanupInterval)
}, config.cleanupInterval)
}
module.exports = {
playerCleanup: playerCleanup
}
// module.exports = {
// playerCleanup: playerCleanup
// }

82
src/components/players/playerMgmtFuncs.ts

@ -0,0 +1,82 @@ @@ -0,0 +1,82 @@
// Sends Updates to socket when players change
const wrtColor = require('../common/wrtColor')
function mapRemoveLastActivityProp (player) {
// Make this specific by props to remove
let {lastActivity, ...other} = player
// return {
// id: player.id,
// name: player.username,
// gameid: player.gameid
// }
return other
}
export function continuousEmitPlayers (state,io) {
let previousPlayersRelevant = []
let timerId = setTimeout(function tick() {
// get players excluding the "lastActivity" because it is constantly updating
let playersRelevant = state.players.map(mapRemoveLastActivityProp)
// console.log(playersRelevant)
// convert to string for comparison
if ((JSON.stringify(playersRelevant)) !== (JSON.stringify(previousPlayersRelevant))) {
// if changes happen, emit to all sockets
// wrtColor.blue('change to Players. Push to Sockets')
io.emit('playersUpdate', playersRelevant)
}
previousPlayersRelevant = playersRelevant
timerId = setTimeout(tick, 1000)
}, 1000)
}
export function socketGetPlayers (state,socket) {
let playersRelevant = state.players.map(mapRemoveLastActivityProp)
socket.emit('playersUpdate', playersRelevant)
}
function checkValidPlayerName(players, playerName) {
//let playerNameString = String(playerName)
let response = {
valid: true,
fails: []
}
// 20 character limit
if (playerName.length > 20) {
response.valid = false
response.fails.push('more than 20 characters')
}
// Check for unallowed characters
if (!/^[a-z]+$/i.test(playerName)) {
response.valid = false
response.fails.push('unallowed characters')
}
// Name not in use already (case sensitive)
// if (players.some(p => p.name.toString().toUpperCase() === playerName.toUpperCase())) {
// response.valid = false
// response.fails.push('name in use')
// }
if (players.filter(p => p.name.toUpperCase() === playerName.toUpperCase()).length > 0) {
response.valid = false
response.fails.push('name in use')
}
return response
}
export function socketRegisterPlayer (io,state,data) {
wrtColor.warn('socketRegisterPlayer')
console.log(data)
//console.log(state.players)
// io.emit('msg',{poop:"salad"})
let checkResult = checkValidPlayerName(state.players, data.playerName)
//console.log(checkResult)
io.emit('msg',checkResult)
// add them to state.players
}
// module.exports = {
// continuousEmitPlayers: continuousEmitPlayers,
// socketGetPlayers: socketGetPlayers,
// socketRegisterPlayer: socketRegisterPlayer,
// }

42
components/serverMgmt.js → src/components/serverMgmt.ts

@ -1,25 +1,22 @@ @@ -1,25 +1,22 @@
// const serverConfig = require('../config/serverConfig.js')
// import { Config } from "../config/config"
// const config: Config = require('../config/config.json')
const wrtColor = require('./common/wrtColor')
//Load Object Definitions and Initialize them
require('./objectDefinitions')
let { state } = require('./objectDefinitions')
// Loab Object Definitions and Initialize state (Holds players/tables/games)
import { state } from "./objectDefinitions"
console.log(state)
// TODO: TEST FUNCTIONS
require('./testFunctions')
const { addTestPlayers, addFluxPlayers } = require('./testFunctions')
wrtColor.test('adding Test Players')
// TODO: TEST Functions
//require('./test/playerTestFunctions')
const { addTestPlayers, addFluxPlayers } = require('./test/playerTestFunctions')
addTestPlayers(state.players)
addFluxPlayers(state)
// --- Socket Handling ---
const { socketGetPlayers } = require('./players/playerMgmtFuncs')
const { continuousEmitPlayers } = require ('./players/playerMgmtFuncs')
const { socketRegisterPlayer } = require ('./players/playerMgmtFuncs')
function socketHandler (io) {
const { socketGetPlayers } = require('./players/playerMgmtFuncs.ts')
const { continuousEmitPlayers } = require ('./players/playerMgmtFuncs.ts')
const { socketRegisterPlayer } = require ('./players/playerMgmtFuncs.ts')
export function socketHandler (io) {
io.on('connection', function (socket) {
//--- Connection ---
wrtColor.cyan(`socket connection established, id ${socket.id}`)
@ -32,21 +29,16 @@ function socketHandler (io) { @@ -32,21 +29,16 @@ function socketHandler (io) {
//--- Disconnection ---
socket.on('disconnect', (reason) => { wrtColor.yellow(`socket disconnected, ${socket.id}, ${reason}`)})
})
//--- Continuous Processes for Sockets ---
continuousEmitPlayers(state,io)
}
//--- Continuous Server Side Processes ---
//--- Continuous Non-Socket Processes ---
// State Monitor
const { stateMonitor } = require('./serverProcesses/stateMonitor')
stateMonitor(state)
// Player Cleanup
const { playerCleanup } = require('./players/playerCleanup')
const { stateMonitor } = require('./stateMonitor')
playerCleanup(state)
stateMonitor(state)
module.exports = {
socketHandler: socketHandler
}

17
src/components/serverProcesses/stateMonitor.ts

@ -0,0 +1,17 @@ @@ -0,0 +1,17 @@
import { Config } from "../../config/config"
const config: Config = require('../../config/config.json')
// const wrtColor = require('./common/wrtColor')
const fs = require('fs')
export function stateMonitor (state) {
setInterval(function() {
// Convert to string
let stateString = JSON.stringify(state, null, 2)
fs.writeFile(config.stateJsonPath, stateString, (err) => {
if (err) {
throw err
}
// wrtColor.blue("state.json saved")
})
}, config.stateMonitorRate)
}

41
components/testFunctions.js → src/components/test/playerTestFunctions.ts

@ -1,7 +1,8 @@ @@ -1,7 +1,8 @@
const { genRandomString } = require('./common/randomString')
const serverConfig = require('../config/serverConfig.js')
const wrtColor = require('./common/wrtColor')
const { state } = require('./objectDefinitions')
const { genRandomString } = require('../common/randomString')
import { Config } from "../../config/config"
const config: Config = require('../../config/config.json')
// const wrtColor = require('./common/wrtColor')
// const { state } = require('./objectDefinitions')
let t = new Date()
let m15 = t.setSeconds(t.getSeconds()-15)
@ -10,52 +11,52 @@ let p45 = t.setSeconds(t.getSeconds()+45) @@ -10,52 +11,52 @@ let p45 = t.setSeconds(t.getSeconds()+45)
let p60 = t.setSeconds(t.getSeconds()+60)
function addTestPlayers(players) {
export function addTestPlayers(players) {
players.push({
id: genRandomString(serverConfig.playerIdLength),
username: 'Charlie',
id: genRandomString(config.playerIdLength),
name: 'Charlie',
gameid: 222,
lastActivity: p30,
connected: false
})
players.push({
id: genRandomString(serverConfig.playerIdLength),
username: 'Dee',
id: genRandomString(config.playerIdLength),
name: 'Dee',
gameid: 222,
lastActivity: p45,
connected: false
})
players.push({
id: genRandomString(serverConfig.playerIdLength),
username: 'Dennis',
id: genRandomString(config.playerIdLength),
name: 'Dennis',
gameid: 222,
lastActivity: p60,
connected: false
})
players.push({
id: genRandomString(serverConfig.playerIdLength),
username: 'Frank',
id: genRandomString(config.playerIdLength),
name: 'Frank',
gameid: 222,
lastActivity: m15,
connected: true
})
players.push({
id: genRandomString(serverConfig.playerIdLength),
username: 'Mac',
id: genRandomString(config.playerIdLength),
name: 'Mac',
gameid: 222,
lastActivity: p30,
connected: true
})
}
function addFluxPlayers (state) {
export function addFluxPlayers (state) {
// Add and remove Pondy from players every 5 seconds
setInterval(function () {
// PONDY
// console.log(players)
let pondyFound = false
for (let i = 0; i < state.players.length; i++) {
if (state.players[i].username === 'Pondy') {
if (state.players[i].name === 'Pondy') {
pondyFound = true
break
}
@ -63,7 +64,7 @@ function addFluxPlayers (state) { @@ -63,7 +64,7 @@ function addFluxPlayers (state) {
if (pondyFound) {
// wrtColor.yellow('Pondy Found, removing')
let pondyFilteredPlayers = state.players.filter( function (player) {
if (player.username !== 'Pondy') {
if (player.name !== 'Pondy') {
return player
}
})
@ -74,8 +75,8 @@ function addFluxPlayers (state) { @@ -74,8 +75,8 @@ function addFluxPlayers (state) {
let t = new Date()
let p30 = t.setSeconds(t.getSeconds()+30)
state.players.push({
id: genRandomString(serverConfig.playerIdLength),
username: 'Pondy',
id: genRandomString(config.playerIdLength),
name: 'Pondy',
gameid: 222,
lastActivity: p30,
connected: true

13
src/config/config.json

@ -0,0 +1,13 @@ @@ -0,0 +1,13 @@
{
"certKeyPath": "../cert/server.key",
"certPath": "../cert/server.cert",
"httpsPort": 9000,
"frontEndUrl": "https://localhost",
"cleanupInterval": 5000,
"playerExpiration": 60000,
"stateJsonPath": "./log/state.json",
"stateMonitorRate": 1000,
"playersUpdateCheckRate": 500,
"playerNameMaxLength": 20,
"playerIdLength": 6
}

36
src/config/config.ts

@ -0,0 +1,36 @@ @@ -0,0 +1,36 @@
export interface Config {
// Server
certKeyPath: string
certPath: string
httpsPort: number
frontEndUrl: string
//PlayerCleanup
cleanupInterval: number
playerExpiration: number
//State Monitor
stateJsonPath: string
stateMonitorRate: number
//Player Settings
playersUpdateCheckRate: number
playerNameMaxLength: number
playerIdLength: number
}
// // --- Moved to a separate file ---
// export let config : Config = {
// // Server
// certKeyPath: "../cert/server.key",
// certPath: "../cert/server.cert",
// httpsPort: 9000,
// frontEndUrl: "https://localhost",
// //Player Cleanup
// cleanupInterval: 5000,
// playerExpiration: 60000,
// //State Monitor
// stateJsonPath: "./log/state.json",
// stateMonitorRate: 1000,
// //Player Settings
// playersUpdateCheckRate: 500,
// playerNameMaxLength: 20,
// playerIdLength: 6,
// }

47
src/server.ts

@ -0,0 +1,47 @@ @@ -0,0 +1,47 @@
import { Config } from './config/config'
const config: Config = require('./config/config.json')
console.log('config:',config)
const wrtColor = require('./components/common/wrtColor')
const fs = require('fs')
const express = require('express')
//Http 80 Route traffic to the frontend VUE application running with HTTPS
const http = require('http')
const httpApp = express()
httpApp.all('*', (req, res) => res.redirect(301, `${config.frontEndUrl}`)) //301 - Moved Permanently
const httpServer = http.createServer(httpApp)
httpServer.listen(80, () => {
wrtColor.green(`http redirectiong 80 to Vue front end at ${config.frontEndUrl}`)
})
//Https to run socket.io
const https = require('https')
const httpsApp = express()
const path = require('path')
const keyPath = fs.readFileSync(path.resolve(__dirname, `${config.certKeyPath}`))
const certPath = fs.readFileSync(path.resolve(__dirname, `${config.certPath}`))
const servOptions = {
key: keyPath,
cert: certPath
}
const httpsServer = https.createServer(servOptions,httpsApp)
httpsServer.listen(config.httpsPort, () => {
wrtColor.green(`https listening on ${config.httpsPort} to handle socket.io requests`)
})
//Attach Socket.IO to https
const io = require('socket.io') (httpsServer, {
cors: { //https://socket.io/docs/v3/handling-cors/
origin: `${config.frontEndUrl}`,
methods: ["GET", "POST"]
}
})
//load gameManagement
require('./components/serverMgmt')
// pass socket.io connections to socketHandler
const { socketHandler } = require('./components/serverMgmt')
socketHandler(io)

18
tsconfig.json

@ -0,0 +1,18 @@ @@ -0,0 +1,18 @@
{
"compilerOptions": {
"target": "es6",
"module": "commonjs",
"rootDir": "src",
"outDir": "dist",
"sourceMap": true,
"resolveJsonModule": true,
"lib": ["es6", "dom"],
"esModuleInterop": true
},
"include": [
"src/**/*.ts"
, "src/components/common/randomString.js", "src/components/common/randomString.js" ],
"exclude": [
"node_modules"
]
}
Loading…
Cancel
Save