-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Store users in session #231
base: main
Are you sure you want to change the base?
Conversation
edde8c5
to
5f88793
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Only had time to look through the first commit for now, sorry!
@@ -102,6 +99,10 @@ const addName = async (page: Page, name: Player["name"]) => { | |||
// Click join | |||
const joinButton = page.getByRole("button", { name: "Join game" }); | |||
await joinButton.click(); | |||
|
|||
// Verify colour is visible | |||
await page.getByText("Connected 🟢"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we maybe consolidate connect
and addName
? connect
no longer connecting is a bit misleading
@@ -45,7 +45,7 @@ const generateSocketUrl = (): string => { | |||
const socket: Socket< | |||
ClientboundSocketServerEvents, | |||
ServerboundSocketServerEvents | |||
> = io(generateSocketUrl()); | |||
> = io(generateSocketUrl(), { autoConnect: false }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it worth adding a brief explanation in the commit message of why we're moving towards having 'sessions'? I.e. to allow users to reconnect after refreshing their page/network issues etc?
It might also be useful to link to the guide that we followed for this, or some docs that explain all the socket.auth
/socket.handshake.auth
/socket.data
stuff
client/utils/socketUtils.ts
Outdated
@@ -7,9 +7,11 @@ import type { | |||
|
|||
const addPlayer = ( | |||
socket: Socket<ClientboundSocketServerEvents, ServerboundSocketServerEvents>, | |||
name: string, | |||
username: string, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The Player
type isn't updated to use username
in this commit, so I think this might introduce multiple names for the same thing?
I'd be tempted to leave it as name
until/unless we start enforcing uniqueness, although I can see the issue here:
socket.data.userId = randomId();
socket.data.username = username;
Another option:
socket.data.user = { id: randomId(), name: }
server/socketServer.ts
Outdated
@@ -1,3 +1,4 @@ | |||
import crypto from "node:crypto"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like Bun implements this, so we should be able to drop the import
server/socketServer.ts
Outdated
@@ -10,6 +11,8 @@ import { Lobby } from "./models/lobby"; | |||
import { Round } from "./models/round"; | |||
import { logWithTime } from "./utils/loggingUtils"; | |||
|
|||
const randomId = () => crypto.randomBytes(8).toString("hex"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And then is there a reason for not using a UUID here, e.g. crypto.randomUUID()
? That could probably just be used inline below without creating a wrapper method
server/socketServer.ts
Outdated
this.server.on("connection", (socket) => { | ||
logWithTime(`Socket connected: ${socket.id}`); | ||
logWithTime(`Socket connected: ${socket.handshake.auth.username}`); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should update socket.on("disconnect"
too, and maybe include one of session ID and user ID since the name might not be unique (at least for now)?
logWithTime(
"User connected",
[
`Name: ${socket.data.username}`,
`ID: ${socket.data.userId}`
].join("\n")
)
server/socketServer.ts
Outdated
socket.data.sessionId = randomId(); | ||
socket.data.userId = randomId(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In this commit I'm not too clear on how user ID and session ID will be used differently. Are they both needed? Is it worth explaining in the commit message?
7b955f5
to
c166342
Compare
This will allow us to fetch the session later by accessing the socket.data object in the server or client. We no longer autoconnect but only connect once a user has submitted their name. This prevents users being added to the session before they have comitted to the game. This allows users to retain their gamestate if their connection drops for any reason. This is following the guidance outlined here: https://socket.io/get-started/private-messaging-part-1/ Co-authored-by: Ynda Jas <[email protected]> Co-authored-by: George Eaton <[email protected]> Co-authored-by: Liz Daly <[email protected]>
We want this logic in two places - when a player clicks join game (listening to the 'players:post') event and when restoring session of a previously joined player. It makes sense to abstract it to a function. I think it would be good if we extracted all event handlers like this to improve readability.
This will be used to store sessions serverside. This could be replaced by something like Redis or SQLite in future but this class is good enough for now. We save the session on connection and disconnection so it should always be up to date
We need a way for users to reconnect to their serverside session if their connection drops or they leave the page and return. We use sessionStorage so that the session is not shared across tabs as it would be for localStorage. We receive the "session:set" event from the server which tells us the server has save the session and now it's time for the client to do the same. We use the "DOMContentLoaded" event to attempt to read the sessionId from localStorage, if it exists then we connect. If it doesn't exist the user will have to manually connect by entering their name. Co-authored-by: Ynda Jas <[email protected]> Co-authored-by: George Eaton <[email protected]> Co-authored-by: Liz Daly <[email protected]>
Now we have a reliable way of tracking users we can use it across the app. Co-authored-by: Liz Daly <[email protected]> Co-authored-by: Rich James <[email protected]> Co-authored-by: George Eaton <[email protected]>
c166342
to
d4f38c7
Compare
We need a way of reliably identifying users.
Previously we used the socket's ID as a users ID but a socket ID relates to a users connection not to a user themselves. If a users connection drops momentarily then they will be assigned a new socket ID and kicked out of the game. Some info in the docs here: https://socket.io/docs/v4/server-api/#socketid
In order to address this we followed the approach used in the Socket.IO private messaging guide parts 1 & 2: https://socket.io/get-started/private-messaging-part-2/
Our approach is a bit simpler in that we don't need user -> user communication only client -> server and server -> client communication.
We add a session store server side where we keep our session data. Once we store this session serverside we tell the client to save the session ID too. When the browser connects to the server it checks to see if a session ID exists in sessionStorage, if it does then it uses it to connect to the server, the server then uses it to retrieve the session. If a session ID doesn't exist then a new session is started.