-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fix issues with client packets and add tests for it
- Loading branch information
Showing
6 changed files
with
157 additions
and
90 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,77 +1,9 @@ | ||
import {Server, ServerWebSocket} from "bun"; | ||
import {ClientData} from "@/types.ts"; | ||
import {handleHealthEndpoints} from "@/routes/health.ts"; | ||
import {Logger} from "@/lib/logger.ts"; | ||
import {ClientPacket, JoinPacket, Packet, QuitPacket, ServerPacket, WelcomePacket} from "@/lib/packet.ts"; | ||
import RoomManager from "@/lib/room_manager.ts"; | ||
import startServer from "@/lib/server.ts"; | ||
|
||
const logger = new Logger(); | ||
const roomManager = new RoomManager(4) | ||
const server = Bun.serve<ClientData>({ | ||
port: 3000, | ||
development: false, | ||
fetch(request: Request, server: Server): undefined | Response { | ||
const url = new URL(request.url) | ||
let routes = url.pathname.split("/") | ||
// Rooms will be available via /room/<room-uuid> | ||
if (routes[1].toLowerCase() === 'server') { | ||
|
||
if (routes.length < 3) { | ||
return new Response(JSON.stringify({message: 'Page not found'}), {status: 404}); | ||
} | ||
|
||
if (routes[2].toLowerCase() === 'room') { | ||
if (routes.length < 4) { | ||
return new Response(JSON.stringify({message: "No roomId provided"}), {status: 400}) | ||
} | ||
|
||
let roomId = routes[3].toLowerCase(); | ||
if (!roomId.match(/[a-z\d-]{6,}/)) { | ||
return new Response(JSON.stringify({message: "Invalid roomId provided"}), {status: 400}) | ||
} | ||
|
||
const success = server.upgrade(request, { | ||
data: { | ||
roomId: roomId, | ||
uuid: crypto.randomUUID() | ||
} | ||
}); | ||
return success ? undefined : new Response(JSON.stringify({message: "WebSocket upgrade error"}), {status: 400}); | ||
} | ||
} else if(routes[1].toLowerCase() === 'health') { | ||
return handleHealthEndpoints(routes) | ||
} | ||
return new Response(JSON.stringify({message: 'Page not found'}), {status: 404}); | ||
}, | ||
websocket: { | ||
open(webSocket: ServerWebSocket<ClientData>): void | Promise<void> { | ||
if (roomManager.joinRoom(webSocket.data.roomId, webSocket.data.uuid)) { | ||
logger.info(`${webSocket.data.uuid} connected to room ${webSocket.data.roomId}`) | ||
|
||
server.publish(webSocket.data.roomId, new JoinPacket(webSocket.data.uuid).toString()) | ||
webSocket.subscribe(webSocket.data.roomId) | ||
webSocket.send(new WelcomePacket(webSocket.data.uuid, roomManager.getRoom(webSocket.data.roomId)).toString()) | ||
} else { | ||
webSocket.close(1011, new ServerPacket("LIMIT", webSocket.data.roomId).toString()) | ||
} | ||
}, | ||
message: function (webSocket: ServerWebSocket<ClientData>, message: string): void | Promise<void> { | ||
let packet = JSON.parse(message) as Packet; | ||
if(packet !== undefined && packet.getType().startsWith("CLIENT_")) { | ||
let clientPacket = new ClientPacket(webSocket.data.uuid, packet.getType(), packet.getData()); | ||
webSocket.publish(webSocket.data.roomId, clientPacket.toString()) | ||
} else { | ||
webSocket.send(new ServerPacket("ERROR", {'message': `Invalid packet type: ${packet.getType()}`}).toString()) | ||
} | ||
}, | ||
close(webSocket: ServerWebSocket<ClientData>, code: number, reason: string): void | Promise<void> { | ||
roomManager.leaveRoom(webSocket.data.roomId, webSocket.data.uuid) | ||
logger.info(`${webSocket.data.uuid} disconnected from room ${webSocket.data.roomId}`) | ||
|
||
webSocket.unsubscribe(webSocket.data.roomId) | ||
server.publish(webSocket.data.roomId, new QuitPacket(webSocket.data.uuid).toString()) | ||
} | ||
} | ||
}); | ||
|
||
const server = startServer(logger, roomManager) | ||
logger.info(`Listening on ${server.hostname}:${server.port}`) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
import RoomManager from "@/lib/room_manager.ts"; | ||
import {ClientData} from "@/types.ts"; | ||
import {Server, ServerWebSocket} from "bun"; | ||
import {handleHealthEndpoints} from "@/routes/health.ts"; | ||
import {ClientPacket, JoinPacket, Packet, QuitPacket, ServerPacket, WelcomePacket} from "@/lib/packet.ts"; | ||
import {Logger} from "@/lib/logger.ts"; | ||
|
||
export default function startServer(logger: Logger, roomManager: RoomManager, port: number = 3000) : Server { | ||
const server = Bun.serve<ClientData>({ | ||
port: port, | ||
development: false, | ||
fetch(request: Request, server: Server): undefined | Response { | ||
const url = new URL(request.url) | ||
let routes = url.pathname.split("/") | ||
// Rooms will be available via /room/<room-uuid> | ||
if (routes[1].toLowerCase() === 'server') { | ||
|
||
if (routes.length < 3) { | ||
return new Response(JSON.stringify({message: 'Page not found'}), {status: 404}); | ||
} | ||
|
||
if (routes[2].toLowerCase() === 'room') { | ||
if (routes.length < 4) { | ||
return new Response(JSON.stringify({message: "No roomId provided"}), {status: 400}) | ||
} | ||
|
||
let roomId = routes[3].toLowerCase(); | ||
if (!roomId.match(/[a-z\d-]{6,}/)) { | ||
return new Response(JSON.stringify({message: "Invalid roomId provided"}), {status: 400}) | ||
} | ||
|
||
const success = server.upgrade(request, { | ||
data: { | ||
roomId: roomId, | ||
uuid: crypto.randomUUID() | ||
} | ||
}); | ||
return success ? undefined : new Response(JSON.stringify({message: "WebSocket upgrade error"}), {status: 400}); | ||
} | ||
} else if(routes[1].toLowerCase() === 'health') { | ||
return handleHealthEndpoints(routes) | ||
} | ||
return new Response(JSON.stringify({message: 'Page not found'}), {status: 404}); | ||
}, | ||
websocket: { | ||
open(webSocket: ServerWebSocket<ClientData>): void | Promise<void> { | ||
if (roomManager.joinRoom(webSocket.data.roomId, webSocket.data.uuid)) { | ||
logger.info(`${webSocket.data.uuid} connected to room ${webSocket.data.roomId}`) | ||
|
||
server.publish(webSocket.data.roomId, JSON.stringify(new JoinPacket(webSocket.data.uuid))) | ||
webSocket.subscribe(webSocket.data.roomId) | ||
webSocket.sendText(JSON.stringify(new WelcomePacket(webSocket.data.uuid, roomManager.getRoom(webSocket.data.roomId))), true) | ||
} else { | ||
webSocket.close(1011, JSON.stringify(new ServerPacket("LIMIT", webSocket.data.roomId))) | ||
} | ||
}, | ||
message: function (webSocket: ServerWebSocket<ClientData>, message: string): void | Promise<void> { | ||
let packet = JSON.parse(message) as Packet; | ||
if(packet !== undefined && packet.type.startsWith("CLIENT_")) { | ||
let clientPacket = new ClientPacket(webSocket.data.uuid, packet.type, packet.data); | ||
webSocket.publish(webSocket.data.roomId, JSON.stringify(clientPacket)) | ||
} else { | ||
webSocket.send(JSON.stringify(new ServerPacket("ERROR", {'message': `Invalid packet type: ${packet.type}`}))) | ||
} | ||
}, | ||
close(webSocket: ServerWebSocket<ClientData>, code: number, reason: string): void | Promise<void> { | ||
roomManager.leaveRoom(webSocket.data.roomId, webSocket.data.uuid) | ||
logger.info(`${webSocket.data.uuid} disconnected from room ${webSocket.data.roomId}`) | ||
|
||
webSocket.unsubscribe(webSocket.data.roomId) | ||
server.publish(webSocket.data.roomId, JSON.stringify(new QuitPacket(webSocket.data.uuid))) | ||
} | ||
} | ||
}); | ||
return server; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
import {afterAll, beforeAll, beforeEach, describe, expect, test} from "bun:test"; | ||
import {Logger} from "@/lib/logger.ts"; | ||
import RoomManager from "@/lib/room_manager.ts"; | ||
import startServer from "@/lib/server.ts"; | ||
import {Server} from "bun"; | ||
import {Packet} from "@/lib/packet.ts"; | ||
|
||
describe('join', () => { | ||
|
||
let server: Server; | ||
|
||
beforeAll(() => { | ||
const logger = new Logger(); | ||
const roomManager = new RoomManager(4); | ||
server = startServer(logger, roomManager, 3000) | ||
}) | ||
|
||
afterAll(() => { | ||
server.stop(true); | ||
}) | ||
|
||
test('Server allows connection to room', async () => { | ||
const clientOne: WebSocket = new WebSocket("ws://localhost:3000/server/room/abc-123") | ||
|
||
let lastPacket = new Packet('ERROR', 'none') | ||
clientOne.addEventListener("message", function (event) { | ||
lastPacket = JSON.parse(<string>event.data) as Packet; | ||
clientOne.terminate() | ||
}) | ||
|
||
let error: number = 0; | ||
clientOne.addEventListener("error", event => { | ||
error += 1; | ||
}) | ||
|
||
await waitForSocketState(clientOne, WebSocket.CLOSED); | ||
|
||
expect(lastPacket.type).toBe("ROOM_WELCOME"); | ||
expect(error).toBe(0); | ||
}) | ||
|
||
test('Join packet is broadcasted', async () => { | ||
const clientOne: WebSocket = new WebSocket("ws://localhost:3000/server/room/abc-123") | ||
|
||
let lastPacket= new Packet('ERROR', 'none') | ||
clientOne.addEventListener("message", function (event) { | ||
lastPacket = JSON.parse(<string>event.data) as Packet; | ||
}) | ||
|
||
const clientTwo: WebSocket = new WebSocket("ws://localhost:3000/server/room/abc-123") | ||
await waitForSocketState(clientTwo, WebSocket.OPEN) | ||
|
||
clientOne.close() | ||
clientTwo.close() | ||
|
||
await waitForSocketState(clientOne, WebSocket.CLOSED); | ||
await waitForSocketState(clientTwo, WebSocket.CLOSED); | ||
|
||
expect(lastPacket.type).toBe("ROOM_JOIN") | ||
}) | ||
}) | ||
|
||
function waitForSocketState(socket: WebSocket, state: WebSocketReadyState) { | ||
return new Promise<void>(function (resolve) { | ||
setTimeout(function () { | ||
if (socket.readyState === state) { | ||
resolve(); | ||
} else { | ||
waitForSocketState(socket, state).then(resolve); | ||
} | ||
}, 5); | ||
}); | ||
} |