UNPKG

@hp4k1h5/terminordle

Version:

> multiplayer [wordle](https://www.powerlanguage.co.uk/wordle/) clone in your terminal

166 lines (137 loc) 3.45 kB
import * as fs from 'fs' import * as path from 'path' import { createServer, Server } from 'https' import * as dotenv from 'dotenv' const p = path.resolve(__dirname, '../../../../.env') dotenv.config({ path: p }) import { WebSocketServer } from 'ws' import { WS, ClientMessage, ClientMsgType, ServerMessage, ServerMsgType, } from '../../lib/structs' import { validateMsg, msg, err } from './msg' import { remove, createSession, join, guess, again } from './session' import { getRand, names, Log } from '../../util' const server: Server | null = process.env.NODE_ENV === 'production' ? createServer({ key: fs.readFileSync('' + process.env.SSL_KEY_PATH), cert: fs.readFileSync('' + process.env.SSL_FULLCHAIN_PATH), }) : null const msgTypeToFn: { [key in ServerMsgType]: ( ws: WS, msg: ServerMessage, log?: Log, ) => ClientMessage | void | undefined } = { create: createSession, join: join, guess: guess, again, } const MAX_CNX = 1_000 export function createWSS( port = 8080, host = 'localhost', log: Log | undefined, ) { const opts = { host, backlog: 100, maxPayload: 256, clientTracking: true, ...(server ? { server } : { port }), } const wss = new WebSocketServer(opts) wss.on('error', function (e) { log && log.log({ err: e.toString() }) }) // heartbeat const cycle = setInterval(function () { wss.clients.forEach((client: WS) => { if (client.is_alive === false) { remove(client) return } // reset client.is_alive = false client.ping() }) }, 30_000) wss.on('close', function close() { clearInterval(cycle) }) wss.on('connection', function (cnx: WS) { if (wss.clients.size >= MAX_CNX) { err(cnx, `overload : ${cnx}`, log) cnx.close() return } cnx.on('error', e => { log && log.log({ err: e.toString(), user_id: cnx.user_id }) }) cnx.on('close', function () { remove(cnx, log) }) cnx.on('pong', heartbeat) cnx.on('message', function (data: string) { let message: ServerMessage try { message = validateMsg(cnx, data) } catch (e) { msg(cnx, { type: ClientMsgType.error, content: 'bad message' }) err(cnx, e, log) return } let response: ClientMessage | undefined | void try { response = msgTypeToFn[message.type](cnx, message, log) } catch (e) { err(cnx, e, log) return } response && msg(cnx, response) }) try { cnx.user_id = userId() } catch (e) { err(cnx, e, log) remove(cnx) return } log && log.log({ connection: 'established', with: cnx.user_id }) // send user id back msg(cnx, { type: ClientMsgType.user_id, content: cnx.user_id, user_id: cnx.user_id, }) }) // ssl support server && server.listen(port) return wss } function heartbeat(this: WS) { this.is_alive = true return heartbeat } const nameList = Object.keys(names) export function userId(): string | undefined { let attempts = 0 const MAX_ATTEMPTS = 10 const _nameList = nameList.filter(name => names[name] === false) let name while (!name && attempts++ < MAX_ATTEMPTS) { name = getRand(_nameList) // double check names if (names[name] === false) { names[name] = true return name } } }