Welcome to the Turntable LIVE Socket Client! This package provides a TypeScript client to communicate with rooms on Turntable.
npm install ttfm-socket
To join a room, you'll need to retrieve the UUID for that room using this endpoint. Then create an instance of the SocketClient
and run the joinRoom
method:
import { SocketClient } from "ttfm-socket";
const client = new SocketClient("https://socket.prod.tt.fm");
const token = "{your Turntable JWT}";
const { state } = await client.joinRoom(token, {
roomUuid: "{room uuid}",
password: "{optional room password}",
});
Assuming you are allowed to join the room (e.g. it's not full, the password is correct, etc.), joinRoom
will resolve with an object containing state
, an object representing the current room state. In this response, allUserData
will be an empty object, to be filled in later (see updatedUserData
). If your join request is rejected for any reason, joinRoom
will throw an error.
When the SocketClient
receives a message broadcast from the server, it emits a corresponding event, which can be listened to.
This is emitted on all messages broadcast from the server. The value emitted here is the full message object, which contains both our custom message
as well as other values such as messageId
and sentAt
.
This is emitted when the SocketClient
receives a server message that contains statePatch
. The value emitted here is only our custom message without any of the extra values. The type of the message is narrowed so that the listener knows that statePatch
will exist.
This is emitted when the SocketClient
receives a server message that contains params
instead of statePatch
. The value emitted here is only our custom message without any of the extra values. The type of the message is narrowed so that the listener knows that params
will exist.
This is when the Primus connection has an error, or when the server returns an error after trying to run an Action. The value emitted here is a string
error message, or potentially undefined
if there is no error message available.
There are four possible events here: connected
, disconnected
, reconnecting
, and timeout
. These are emitted when the connection state changes. There is no value emitted with these, so the listener will have 0 arguments.
Once a room has been joined, you should attach listeners to the SocketClient
to handle incoming messages. To compute the state updates, we recommend the fast-json-patch package.
import { applyPatch } from "fast-json-patch";
client.on("statefulMessage", (message) => {
const newState = applyPatch(
prevState,
message.statePatch,
true,
false,
).newDocument;
// Save the new state
});
client.on("statelessMessage", (message) => {
// Respond to the message based on its name and params
});
Listeners can also be added for specific messages:
client.on(ServerMessageName.kickedFromRoom, () => {
// Clear local state and destroy the client
});
When a client wants to make some sort of update to the room, it can do so with the action
method in the SocketClient
class. Documentation for these actions can be found here. For example, to take the stage in a room a client would run the addDj
action:
client.action<AddDjAction>(ActionName.addDj, { song: { ... } })
In this case, song
is optional but should match the type MinimalCrateSongResDTO.
After a client or the server successfully runs an action in a room, the server will broadcast a message to every client connected to that room, including the one that ran the Action. As a convention, the names of these messages take the past-tense form of the name of the Action that spawned them. For example, the addDj
Action correlates to the addedDj
server message.
Most of the messages sent by the server come after some sort of room state update. For these, the message will take this shape:
{
name: StatefulServerMessageNameType;
statePatch: Operation[];
}
statePatch
is a JSON-encoded series of operations for the client to perform in order to update its local state to stay in sync with the server. This adheres the JSON Patch format.
These are all of the stateful messages:
Sent to the server after a client successfully joins a room, only to that single client. This will contain a statePatch
that fills in the allUserData
piece of the client state, which is not included in the initial state
sent in response to the joinRoom
action.
Sent by the server to all clients when a user joins the room. This comes after a client successfully runs the joinRoom
Action and is added to the Actionhero chatRoom
.
Sent by the server to all clients when a user leaves the room. When a client sends the Actionhero roomLeave
message, or when its connection is closed, we enqueue a 20-second delayed Task. If the user has no active connections after that delay, the server will broadcast this userLeft
message.
Sent by the server to all clients when a user has been successfully added to the DJ lineup. This comes after a client runs the addDj
Action.
Sent by the server to all clients when a user has been successfully removed from the DJ lineup. This comes after a client or server runs the removeDj
Action.
Sent by the server to all clients when nowPlaying
changes. This can be triggered by add/removing DJs, skips, kicks, bans, and the current song ending. This same message is sent when there is no next song to play, in which case nowPlaying
will be null
. The only Action that broadcasts this message is playSong
.
Sent by the server to all clients when a DJ updates their next song via the updateNextSong
Action. This might be followed by the playedSong
message, but only if a DJ sends a non-null
song and there isn't already something playing.
Sent by the server to all clients when a user updates their song votes via the voteOnSong
Action. This currently handles likes/dislikes and stars. The client state will have per-user voting information, as well as a computed vibeMeter
value and an allVotes
object that contains arrays of user UUIDs for each type of vote.
Sent by the server to all clients after a successful song lookup. This happens if a client sends a song placeholder with the addDj
or updateNextSong
Actions. It's unlikely the clients will need to do anything in response to this message, but it exists to keep the client state in sync with the server state to avoid errors on subsequent patches.
If a message does not accompany a room state update, the shape is slightly different:
{
name: StatelessServerMessageNameType;
params?: StatelessServerMessageParams;
}
These are all of the stateless messasges:
Sent by the server to all clients when a user sends a one-time animation. For this message, params
takes the following shape:
{
userUuid: string;
animation: OneTimeAnimationType;
emoji?: string;
}
emoji
here is only defined if animation
is OneTimeAnimation.emoji
, and will only ever be a string with a single emoji character in it.
Sent by the server only to the connection(s) for one specific user in a room. This indicates that the user was just kicked (or banned). Immediately following this message, the server will close the connection. This message has no params
.
Sent by the server after someone hits the /resetRoom
endpoint for that room. This means that the room's state will be cleared and the server will close all connections that are currenly in the room. In response to this, the clients will need to open a new connection and rejoin the room via the joinRoom
Action. This message has no params
.
If you have any questions or feedback about this package, please reach out to us on Discord or at help@turntabletlive.com.