Skip to content
TheExkaliburg edited this page Jun 25, 2022 · 4 revisions

This will only be accurate from the point of the roundRework and onward

Connection

To connect to the Websocket of Fair Game we use the Stomp over SockJS approach that goes natively with Spring. For this, the client needs to connect to the /fairsocket api.

import SockJs from "sockjs-client";
import Stomp from "stomp-websocket";

let connection = "https://fair.kaliburg.de/fairsocket";
let socket = SockJs(connection);
let stompClient = Stomp.over(socket);

stompClient.connect({},() => connectCallback(), () => disconnectCallback());

After the connection, the client subscribes to destinations on the client side. These destinations are broadly split into 3 categories.

Topic - Destinations

These destinations start with /topic/..., and generally identify that the server openly broadcasts information to all users. For example, the ladder-heartbeat and new chat-messages. But while the client subscribes to the destination, the TopicSubscriptionInterceptor can check the prerequisites for that topic and deny the user access by cancelling the subscription.

Private - Destinations

These destinations start with /queue/..., and generally identify that the server privately sends information back to the user. Generally these either natively identify the user by their UserPrincipal, or get identified by their account-uuid to send private events. We decide in this project between stomp-native private channels, and pseudo-private channel.

stomp-private

If the client targets a queue destination, it is not any different from the topic destination, to be able to receive the data privately, the destination must be derived to /user/queue/..., for stomp to setup the private channel

pseudo-private

Since you can use /topic/... as a way to target destinations publicly, we can use the /topic/{uuid}/... to target a channel group that only the user with his uuid has access too. We then broadcast private information to that message and only the user with the given uuid can receive them, since they are the only one connected to that destination.

The stomp-private destinations are commonly used to be able to react to a message, since you can use the UserPrincipal to target that specific channel, which you commonly have access to in the controller-classes. The pseudo-private destinations are used if there are regular/irregular updates about events, that are not meant for public to see and don't need the UserPrincipal, since the uuid of the account is enough to target that specific client.

pseudo-private vs. stomp-private with caching UserPrincipals

We could also cache the UserPrincipal at the very start, when the first contact between client and server gets created, and use that to later figure out which UserPrincipal needs to be addressed for a given user.

Since some people have the game open in different tabs, a cached map with the uuid as key and the UserPrincipal as value, would overwrite the original UserPrincipal and make the old tab incapable of receiving private messages. In contrary, with pseudo-private both clients would just connect to the same pseudo-private destination and get the same private messages.

App - Destinations

These destinations start with /app/..., and identify that the server accepts messages here. This means that the user cannot subscribe and listen to these destinations. They can only send information to them, like chat-messages or game-events.

For better understanding i recommend the entire chapter 4.4 of the Spring Web socket documentation, but specifically 4.4.5 Flow of Messages.

Messages

WSMessage

Generally the API takes one type of Messages, that's called a variation of WSMessage. The basis is the WSEmptyMessage that only contains the uuid of the account sending the message. If you add content such as a message to it, you get a WSMessage, and if this message also contains metadata, like the mentioning of another user, you can use the WSMetaMessage. Each of these messages also has an observed version of itself (WSEmptyObservedMessage, WSObservedMessage) that also contains the user-generated event to check for illicit activities.

Most of the fields in the Message Classes are Strings, since the backend doesn't need access to the message itself, only to save and redistribute it. The same applies for the event and the metadata of the message.

WSMessageAnswer

This is generally used to send messages back the stomp-private way. Since most of the answers are generated directly in combination with the call, we use an HttpStatus to signal if the message was well received or if there were any problems server side. Besides the status, the content can take form of any Class, which are generally DTO objects that specifically only contain the necessary fields that the client needs.

WSUtils

WSUtils consist out of 2 very important functions (with a variety of overloads):

  • convertAndSendToUser(SimpMessageHeaderAccessor sha, String dest, Object content) takes the sha, reads out the UserPrincipal and sends a new WSMessageAnwser in JSON format with the content to the target destination (f.e. /queue/chat/ for the initial chat-messages)
  • convertAndSendToUser(UUID uuid, String dest, Object content) takes a uuid and just plainly send the Object in a JSON format to the target user-destination
  • convertAndSendToAll(String dest, Object content) takes the destination and just plainly sends the Object in a JSON format without the WSMessageAnwser wrapper to the target destination (f.e. /topic/chat/1 for the messages in Chat 1)