lots of updates
This commit is contained in:
commit
50fb1d56f8
11
.gitignore
vendored
Normal file
11
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
/tmp
|
||||
/out-tsc
|
||||
|
||||
/node_modules
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
.vscode/*
|
||||
8
.idea/.gitignore
vendored
Normal file
8
.idea/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
||||
6
.idea/vcs.xml
Normal file
6
.idea/vcs.xml
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
6308
assets/blacks.txt
Normal file
6308
assets/blacks.txt
Normal file
File diff suppressed because it is too large
Load Diff
22440
assets/whites.txt
Normal file
22440
assets/whites.txt
Normal file
File diff suppressed because it is too large
Load Diff
29
package.json
Normal file
29
package.json
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
{
|
||||
"name": "game-networking",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "dist/index.js",
|
||||
"scripts": {
|
||||
"start": "bun run src/index.ts"
|
||||
},
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@types/amqplib": "^0.10.7",
|
||||
"@types/cors": "^2.8.17",
|
||||
"@types/express": "^5.0.0",
|
||||
"@types/express-ws": "^3.0.5",
|
||||
"amqplib": "^0.10.5",
|
||||
"bson": "^6.10.3",
|
||||
"colors": "^1.4.0",
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^16.4.7",
|
||||
"express": "^4.21.2",
|
||||
"express-ws": "^5.0.2",
|
||||
"ts-events": "^3.4.1",
|
||||
"uuid": "^11.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5.5.3"
|
||||
},
|
||||
"private": true
|
||||
}
|
||||
14
src/cards.ts
Normal file
14
src/cards.ts
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
import fs from "fs"
|
||||
|
||||
async function parseCards(file: string) {
|
||||
const fileData = fs.readFileSync(file).toString("utf8")
|
||||
return fileData.split("\n")
|
||||
}
|
||||
|
||||
export const blacks = (await parseCards("assets/blacks.txt")).filter(card => card.split("{{fill}}").length < 2)
|
||||
export const whites = await parseCards("assets/whites.txt")
|
||||
|
||||
export function randomCard(cards: string[]) {
|
||||
const randomCard = Math.floor(Math.random() * cards.length)
|
||||
return cards[randomCard]
|
||||
}
|
||||
71
src/errors.ts
Normal file
71
src/errors.ts
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
const UserAuthenticationErrorBase = 93000
|
||||
const GenericErrorBase = 18000
|
||||
const GameErrorBase = 12000
|
||||
|
||||
export const GenericErrorCodes = {
|
||||
MissingField: GenericErrorBase,
|
||||
RouteDoesNotSupportMethod: GenericErrorBase + 1,
|
||||
}
|
||||
|
||||
export const GenericErrors = {
|
||||
MissingRequiredFields: createErrorBuilder(GenericErrorCodes.MissingField, "Required fields not found"),
|
||||
RouteDoesNotSupportMethod: createError(GenericErrorCodes.RouteDoesNotSupportMethod, "Unsupported method on route")
|
||||
}
|
||||
|
||||
export const GameErrorCodes = {
|
||||
}
|
||||
|
||||
export const GameErrors = {
|
||||
}
|
||||
|
||||
export const UserAuthenticationErrorCodes = {
|
||||
}
|
||||
|
||||
export const UserAuthenticationErrors = {
|
||||
}
|
||||
|
||||
type ErrorDetail = {
|
||||
code: number,
|
||||
message: string,
|
||||
for?: string,
|
||||
possibleFix?: string
|
||||
}
|
||||
|
||||
class ErrorBuilder {
|
||||
code: number
|
||||
|
||||
message: string
|
||||
|
||||
details: {
|
||||
[key: string]: ErrorDetail
|
||||
} | null = null
|
||||
|
||||
constructor(code: number, baseMessage: string) {
|
||||
this.code = code
|
||||
this.message = baseMessage
|
||||
}
|
||||
|
||||
with(name: string, detail: ErrorDetail) {
|
||||
if (this.details == null) {
|
||||
this.details = {}
|
||||
}
|
||||
this.details[name] = detail
|
||||
return this
|
||||
}
|
||||
|
||||
build() {
|
||||
return {
|
||||
message: this.message,
|
||||
code: this.code,
|
||||
details: this.details
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function createErrorBuilder(code: number, baseMessage: string) {
|
||||
return () => new ErrorBuilder(code, baseMessage)
|
||||
}
|
||||
|
||||
export function createError(code: number, message: string) {
|
||||
return { code, message }
|
||||
}
|
||||
23
src/hostManager.ts
Normal file
23
src/hostManager.ts
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
import {v4} from "uuid";
|
||||
import {Logger} from "./logger";
|
||||
|
||||
export class HostManager {
|
||||
logger = Logger.get("HostManager");
|
||||
activeCodes: Map<string, string> = new Map();
|
||||
|
||||
get nextCode() {
|
||||
let code = ""
|
||||
let pos = Math.floor(Math.random() * 12)
|
||||
while (this.activeCodes.has((code = v4().replace(/-+/gm, "").slice(pos, pos + 4)))) {
|
||||
this.logger.debug(`Code ${code} already in use. Trying next one`)
|
||||
}
|
||||
|
||||
return code
|
||||
}
|
||||
|
||||
removeCode(code: string) {
|
||||
this.activeCodes.delete(code);
|
||||
}
|
||||
}
|
||||
|
||||
export const hostManager = new HostManager();
|
||||
95
src/index.ts
Normal file
95
src/index.ts
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
import express, { Router } from "express"
|
||||
import { ILayer } from "express-serve-static-core"
|
||||
import color from "colors"
|
||||
import cors from "cors"
|
||||
import {v1} from "./routes/v1";
|
||||
import {Logger} from "./logger";
|
||||
import {GenericErrors} from "./errors";
|
||||
import {configDotenv} from "dotenv";
|
||||
import expressWs from "express-ws";
|
||||
import {joinGameInstance, startGameServer} from "./routes/v1/gameServer";
|
||||
configDotenv()
|
||||
|
||||
|
||||
const Global = Logger.get("App")
|
||||
|
||||
async function main() {
|
||||
const app = express() as unknown as expressWs.Application;
|
||||
expressWs(app)
|
||||
app.use(express.json())
|
||||
app.use(cors({
|
||||
origin: "*"
|
||||
}))
|
||||
|
||||
let start = performance.now()
|
||||
Global.wait("Building Application")
|
||||
|
||||
let router = Router()
|
||||
|
||||
router.use("/api/v1", v1)
|
||||
router.use("/api", v1)
|
||||
|
||||
app.use(router)
|
||||
|
||||
app.ws("/live/start-game", startGameServer)
|
||||
app.ws("/live/join-game/:channel", joinGameInstance)
|
||||
|
||||
Global.info(`Built Application in ${(performance.now() - start).toPrecision(2)}ms`)
|
||||
|
||||
function recursivePath(layer: ILayer, out: [string, string][], base: string) {
|
||||
if (layer.name == "router") {
|
||||
(layer.handle as Router).stack.forEach(l => recursivePath(l, out, base + layer.regexp.toString()
|
||||
.replace('/^', '')
|
||||
.replace('?(?=\\/|$)/i', '')
|
||||
.replace(/\\\//g, '/')
|
||||
.replace('(?:/(?=$))', '')))
|
||||
} else if (layer.name == "bound dispatch") {
|
||||
for (let key of Object.keys((layer.route! as any).methods)) {
|
||||
out.push([key, (base + layer.route?.path).replace(/\/+/g, "/")])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let out: [string, string][] = []
|
||||
|
||||
app._router.stack.forEach((l: ILayer) => recursivePath(l, out, "/"))
|
||||
|
||||
function colorMethod(method: string) {
|
||||
switch (method) {
|
||||
case "get":
|
||||
return color.green
|
||||
case "post":
|
||||
return color.yellow
|
||||
case "put":
|
||||
return color.blue
|
||||
case "patch":
|
||||
return color.magenta
|
||||
case "delete":
|
||||
return color.red
|
||||
}
|
||||
return color.stripColors
|
||||
}
|
||||
|
||||
console.log()
|
||||
Global.debug("Routes:\n")
|
||||
|
||||
out.forEach((route) => {
|
||||
console.log(colorMethod(route[0])(`${"[App Router]".cyan} [${route[0].toUpperCase()}] ${route[1]}`))
|
||||
app.all(route[1], (req, res) => {
|
||||
res.status(400)
|
||||
.json(GenericErrors.RouteDoesNotSupportMethod)
|
||||
.end()
|
||||
})
|
||||
})
|
||||
|
||||
console.log()
|
||||
|
||||
Global.wait("Starting Application")
|
||||
|
||||
app.listen(8080, () => {
|
||||
Global.info("Application successfully started.")
|
||||
Global.info("Started on http://localhost:8080")
|
||||
})
|
||||
}
|
||||
|
||||
main().then()
|
||||
53
src/logger.ts
Normal file
53
src/logger.ts
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
import colors from "colors"
|
||||
|
||||
const LogLevel: Record<string, (str: string) => string> = {
|
||||
Error: (str: string) => colors.bgRed(colors.black(str)),
|
||||
Info: (str: string) => colors.green(str),
|
||||
Debug: (str: string) => colors.cyan(str),
|
||||
Warn: (str: string) => colors.bgYellow.black(str),
|
||||
Wait: (str: string) => colors.gray(str)
|
||||
}
|
||||
|
||||
export class Logger {
|
||||
protected constructor(name: string, opts?: {
|
||||
debug?: boolean
|
||||
}) {
|
||||
this._name = name
|
||||
this._debug = opts?.debug ?? true
|
||||
}
|
||||
|
||||
private _print(level: string, ...args: any[]) {
|
||||
console.log(`[${new Date().toLocaleString(Intl.DateTimeFormat().resolvedOptions().locale, {
|
||||
hour12: false,
|
||||
dateStyle: "medium",
|
||||
timeStyle: "long"
|
||||
})}] ${LogLevel[level](`[${this._name} / ${level}]`)} ->`, ...args.map(v => typeof v === "string" ? LogLevel[level](v) : v))
|
||||
}
|
||||
|
||||
info(...args: string[]) {
|
||||
this._print("Info", ...args)
|
||||
}
|
||||
|
||||
wait(...args: string[]) {
|
||||
this._print("Wait", ...args)
|
||||
}
|
||||
|
||||
error(...args: any[]) {
|
||||
this._print("Error", ...args)
|
||||
}
|
||||
|
||||
debug(...args: any[]) {
|
||||
if (this._debug) this._print("Debug", ...args)
|
||||
}
|
||||
|
||||
warn(...args: any[]) {
|
||||
this._print("Warn", ...args)
|
||||
}
|
||||
|
||||
private readonly _name: string
|
||||
private _debug: boolean
|
||||
|
||||
static get(name: string) {
|
||||
return new Logger(name)
|
||||
}
|
||||
}
|
||||
35
src/requiredElements.ts
Normal file
35
src/requiredElements.ts
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
import {NextFunction, Response, Request} from "express";
|
||||
import {GenericErrorCodes, GenericErrors} from "./errors";
|
||||
|
||||
export const requiredElements = (elements: string[]) => {
|
||||
return (req: Request, res: Response, next: NextFunction) => {
|
||||
let missing: string[] = []
|
||||
elements.forEach((el) => {
|
||||
let path = el.split(".")
|
||||
let idx = req.body
|
||||
for (let pathItem of path) {
|
||||
if (idx[pathItem] != null) {
|
||||
idx = idx[pathItem]
|
||||
} else {
|
||||
missing.push(el)
|
||||
break
|
||||
}
|
||||
}
|
||||
})
|
||||
if (missing.length > 0) {
|
||||
let requirementError = GenericErrors.MissingRequiredFields()
|
||||
missing.forEach((el) => {
|
||||
requirementError.with(el, {
|
||||
code: GenericErrorCodes.MissingField,
|
||||
message: `Missing field ${el}`,
|
||||
possibleFix: "Include this field in the request",
|
||||
})
|
||||
})
|
||||
res.status(400)
|
||||
.json(requirementError.build())
|
||||
.end()
|
||||
} else {
|
||||
next()
|
||||
}
|
||||
}
|
||||
}
|
||||
36
src/routes/v1.ts
Normal file
36
src/routes/v1.ts
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
import {Router} from "express";
|
||||
import {requiredElements} from "../requiredElements";
|
||||
import {gameRelations, startGameServer} from "./v1/gameServer";
|
||||
import expressWs from "express-ws";
|
||||
import {blacks, whites} from "../cards";
|
||||
|
||||
export const v1 = Router();
|
||||
|
||||
// game
|
||||
{
|
||||
v1.get("/pick-card/:color", (req, res) => {
|
||||
const color = req.params.color;
|
||||
switch (color) {
|
||||
case "black":
|
||||
res.json(blacks)
|
||||
return
|
||||
case "white":
|
||||
res.json(whites)
|
||||
return
|
||||
}
|
||||
})
|
||||
|
||||
v1.get("/play/:code", (req, res) => {
|
||||
if (!gameRelations.has(req.params.code.toLowerCase())) {
|
||||
res.status(400)
|
||||
.json({
|
||||
message: "Code not found"
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
res.json({
|
||||
channel: gameRelations.get(req.params.code.toLowerCase()),
|
||||
})
|
||||
})
|
||||
}
|
||||
132
src/routes/v1/gameServer.ts
Normal file
132
src/routes/v1/gameServer.ts
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
import {NextFunction, Request, Response} from "express";
|
||||
import amqp from "amqplib";
|
||||
import { WebSocket } from "ws"
|
||||
import {BSON, serialize} from "bson"
|
||||
import {hostManager} from "../../hostManager";
|
||||
import {createPacket, GameConnectionHash, handlePacket, HandlerFunc, Packet} from "../../types/socket";
|
||||
import {v4} from "uuid";
|
||||
import {SyncEvent} from "ts-events";
|
||||
|
||||
export const gameRelations = new Map<string, string>();
|
||||
|
||||
type Event = {
|
||||
channel: string,
|
||||
data: any
|
||||
}
|
||||
|
||||
const toClient = new SyncEvent<Event>()
|
||||
const toServer = new SyncEvent<Event>()
|
||||
|
||||
export async function startGameServer(ws: WebSocket, req: Request) {
|
||||
const handlers: Map<string, HandlerFunc> = new Map()
|
||||
const myCode = hostManager.nextCode
|
||||
let messageChannel = "";
|
||||
|
||||
function toServerHandler(ev: Event) {
|
||||
console.log(ev)
|
||||
if (ev.channel == messageChannel) {
|
||||
ws.send(BSON.serialize(ev.data))
|
||||
}
|
||||
}
|
||||
|
||||
handlers.set("cfg.gameConnectionHash", async (data: GameConnectionHash, send) => {
|
||||
messageChannel = `humanityBox.live.${data.hash}`
|
||||
|
||||
console.log(`Queue Created:`, messageChannel)
|
||||
|
||||
gameRelations.set(myCode, messageChannel)
|
||||
|
||||
toServer.attach(toServerHandler)
|
||||
})
|
||||
|
||||
ws.send(BSON.serialize(createPacket("cfg.gameSecrets", {
|
||||
gameCode: myCode,
|
||||
})))
|
||||
|
||||
ws.on("message", async (msg: Buffer) => {
|
||||
console.log(msg)
|
||||
const data = BSON.deserialize(new Uint8Array(msg.buffer));
|
||||
|
||||
if (messageChannel.length > 0) {
|
||||
console.log(`publishing to clients on ${messageChannel}: `, data);
|
||||
|
||||
toClient.post({
|
||||
channel: messageChannel,
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
handlePacket(handlers, data, (data) => {
|
||||
ws.send(BSON.serialize(data))
|
||||
})
|
||||
})
|
||||
|
||||
ws.on("close", () => {
|
||||
hostManager.removeCode(myCode);
|
||||
toServer.detach(toServerHandler)
|
||||
toClient.post({
|
||||
channel: messageChannel,
|
||||
data: createPacket("game.forceQuit", {
|
||||
reason: "Host disconnected"
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
export async function joinGameInstance(ws: WebSocket, req: Request){
|
||||
const handlers: Map<string, HandlerFunc> = new Map()
|
||||
const messageChannel = req.params.channel
|
||||
if (![...gameRelations.values()].includes(messageChannel)) {
|
||||
ws.send(serialize(createPacket("game.forceQuit", {
|
||||
reason: "Game no longer exists"
|
||||
})))
|
||||
}
|
||||
|
||||
let uuid = ""
|
||||
|
||||
const connectionId = v4()
|
||||
|
||||
const toClientHandler = (msg: Event) => {
|
||||
if (msg.channel != messageChannel) return;
|
||||
if (!msg) return;
|
||||
const data = msg.data
|
||||
if (data.conn == undefined || (data.conn && data.conn == connectionId)) {
|
||||
console.log(`for me! - ${connectionId} ${data.conn}`)
|
||||
ws.send(BSON.serialize(msg.data))
|
||||
}
|
||||
}
|
||||
|
||||
toClient.attach(toClientHandler)
|
||||
|
||||
handlers.set("game.playerJoin", (data) => {
|
||||
uuid = data.uuid
|
||||
console.log(uuid)
|
||||
})
|
||||
|
||||
ws.on("message", async (msg: Buffer) => {
|
||||
// BSON: const data = BSON.deserialize(new Uint8Array(msg.buffer)) as Packet;
|
||||
// JSON:
|
||||
try {
|
||||
const data = BSON.deserialize(msg) as Packet
|
||||
data.conn = connectionId;
|
||||
|
||||
toServer.post({
|
||||
channel: messageChannel,
|
||||
data: data
|
||||
})
|
||||
} finally {
|
||||
console.log("invalid data received")
|
||||
}
|
||||
})
|
||||
|
||||
ws.on("close", () => {
|
||||
toClient.detach(toClientHandler)
|
||||
console.log(connectionId)
|
||||
toServer.post({
|
||||
channel: messageChannel,
|
||||
data: createPacket("game.playerLeave", {
|
||||
uuid,
|
||||
}, connectionId)
|
||||
})
|
||||
})
|
||||
}
|
||||
121
src/testCli.ts
Normal file
121
src/testCli.ts
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
import amqp from "amqplib";
|
||||
import * as readline from "node:readline";
|
||||
type AssertQueue = amqp.Replies.AssertQueue;
|
||||
import {BSON} from "bson";
|
||||
import {createPacket} from "./types/socket";
|
||||
|
||||
const amqpConn = await amqp.connect("amqp://localhost")
|
||||
const chan = await amqpConn.createChannel()
|
||||
const exch = await chan.assertExchange("humanitybox.live", "direct")
|
||||
|
||||
const rl = readline.createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout
|
||||
})
|
||||
|
||||
let closed = false
|
||||
|
||||
const commands: Map<string, (args: string[]) => void> = new Map()
|
||||
|
||||
commands.set("close", () => {
|
||||
closed = true
|
||||
})
|
||||
|
||||
let queue: AssertQueue | null = null
|
||||
|
||||
const variables: Map<string, string> = new Map()
|
||||
|
||||
commands.set("connect", async (args) => {
|
||||
const gameHash = args.shift()
|
||||
|
||||
queue = await chan.assertQueue(`humanityBox.live.${gameHash}`, {
|
||||
autoDelete: true
|
||||
})
|
||||
chan.bindQueue(queue.queue, exch.exchange, "")
|
||||
})
|
||||
|
||||
commands.set("set", (args) => {
|
||||
const variable = args.shift()!
|
||||
const value = args.join(" ")
|
||||
variables.set(variable, value)
|
||||
})
|
||||
|
||||
function send(data: any) {
|
||||
if (!queue) {
|
||||
console.log("Queue not connected. Please connect before sending")
|
||||
return
|
||||
}
|
||||
chan.sendToQueue(queue.queue, Buffer.from(BSON.serialize(data)))
|
||||
}
|
||||
|
||||
commands.set("join", () => {
|
||||
if (!variables.has("playerName")) {
|
||||
console.log("playerName is missing. Please set playerName")
|
||||
return
|
||||
}
|
||||
|
||||
send(createPacket("game.playerJoin", {
|
||||
playerName: variables.get("playerName"),
|
||||
}))
|
||||
})
|
||||
|
||||
commands.set("leave", () => {
|
||||
if (!variables.has("playerName")) {
|
||||
console.log("playerName is missing. Please set playerName")
|
||||
return
|
||||
}
|
||||
|
||||
send(createPacket("game.playerLeave", {
|
||||
playerName: variables.get("playerName"),
|
||||
}))
|
||||
})
|
||||
|
||||
commands.set("start", () => {
|
||||
send(createPacket("vip.startGame", {}))
|
||||
})
|
||||
|
||||
commands.set("playCard", (args) => {
|
||||
const card = args.join(" ")
|
||||
if (!variables.has("uuid")) {
|
||||
console.log("Variable uuid is missing. Please set uuid to run this command")
|
||||
}
|
||||
send(createPacket("game.action.playCard", {
|
||||
uuid: variables.get("uuid"),
|
||||
cardValue: card
|
||||
}))
|
||||
})
|
||||
|
||||
commands.set("pickCard", (args) => {
|
||||
const card = args.shift()
|
||||
if (!variables.has("uuid")) {
|
||||
console.log("Variable uuid is missing. Please set uuid to run this command")
|
||||
}
|
||||
send(createPacket("game.action.pickCard", {
|
||||
uuid: variables.get("uuid"),
|
||||
picked: card
|
||||
}))
|
||||
})
|
||||
|
||||
function promptCommand() {
|
||||
rl.question("cmd: ", (ans) => {
|
||||
const args = ans.trim().split(" ")
|
||||
const cmd = args.shift()
|
||||
|
||||
if (commands.has(cmd)) {
|
||||
commands.get(cmd ?? "")?.call(null, args)
|
||||
} else {
|
||||
console.log(`Command ${cmd} not found`)
|
||||
}
|
||||
if (!closed) {
|
||||
promptCommand()
|
||||
} else {
|
||||
send(createPacket("game.playerLeave", {
|
||||
playerName: variables.get("playerName"),
|
||||
}))
|
||||
chan.close()
|
||||
rl.close()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
promptCommand()
|
||||
18
src/types/game-instances.ts
Normal file
18
src/types/game-instances.ts
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
export type StartGameRequest = {}
|
||||
|
||||
export type StartGameResponse = {
|
||||
gameHash: string,
|
||||
gameCode: string,
|
||||
}
|
||||
|
||||
export type ConfirmJoinRequest = {
|
||||
gameCode: string
|
||||
}
|
||||
|
||||
export type ConfirmJoinResponse = {
|
||||
canJoin: boolean,
|
||||
info?: {
|
||||
gameCode: string,
|
||||
gameHash: string
|
||||
}
|
||||
}
|
||||
1
src/types/index.ts
Normal file
1
src/types/index.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
export * from "./game-instances"
|
||||
41
src/types/socket.ts
Normal file
41
src/types/socket.ts
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
export function createPacket(op: string, data: object, conn?: string) {
|
||||
return {
|
||||
op, data, conn
|
||||
}
|
||||
}
|
||||
|
||||
type GameSecretsCfg = {
|
||||
gameCode: string
|
||||
}
|
||||
|
||||
type GamePlayerJoin = {
|
||||
playerName: string
|
||||
}
|
||||
|
||||
type GamePlayerLeave = {
|
||||
uuid: string
|
||||
}
|
||||
|
||||
export type SendFunction = (data: any, conn?: string) => void;
|
||||
export type HandlerFunc = (data: any, sock: SendFunction) => void;
|
||||
|
||||
export type Packet = {
|
||||
op?: string,
|
||||
data?: object,
|
||||
conn?: string,
|
||||
}
|
||||
|
||||
export function handlePacket(handlers: Map<string, HandlerFunc>, data: Packet, send: SendFunction) {
|
||||
if (data.op && data.data) {
|
||||
let handler: HandlerFunc | undefined;
|
||||
if ((handler = handlers.get(data.op)) != undefined) {
|
||||
handler(data.data, send)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type GameConnectionHash = {
|
||||
hash: string
|
||||
}
|
||||
|
||||
export type {GameSecretsCfg, GameConnectionHash, GamePlayerJoin, GamePlayerLeave }
|
||||
13
tsconfig.json
Normal file
13
tsconfig.json
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2023",
|
||||
"module": "ESNext",
|
||||
"esModuleInterop": true,
|
||||
"moduleResolution": "Node",
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"outDir": "dist"
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user