Keeps the set of open sockets and sends them the server's messages.

The WebSocket route (routes/ws.ts) registers each socket and handles what it sends. The services and routes that change dumps, comments, playlists and users call the broadcast* functions here after the change is stored. A broadcast* function sends to everyone unless its doc names an audience, so its caller must not pass it anything private. Its jobs ping every socket, closing the ones that stop answering, and expire the typing indicators.

Functions

f
broadcastCategoriesChanged(): void

Sends everyone categories_changed, after the category manager created, edited or deleted one, so that every client fetches them again.

f
broadcastChatMessage(
message: ChatMessage,
nonce?: string
): void

Sends chat_message to every signed-in socket, the sender's included. nonce comes from the sender's chat_send, and lets it swap its pending copy for the message.

f
broadcastChatMessageDeleted(id: string): void

Sends chat_message_deleted to every signed-in socket.

f
broadcastChatMessageUpdated(message: ChatMessage): void

Sends chat_message_updated to every signed-in socket.

f
broadcastCommentCreated(
comment: Comment,
dump: { isPrivate: boolean; userId: string; }
): void

Sends comment_created to those who may see the comment's dump: everyone, or its owner alone when it is private. The comments route calls it, as it does the two below.

f
broadcastCommentLikeUpdate(
commentId: string,
likeCount: number,
likerId: string,
action: "cast" | "remove",
onlyUserId?: string
): void

Sends comment_likes_update after the route stored a like, to everyone, or with onlyUserId to that user's sockets alone (for a comment on a private dump).

f
broadcastDumpChange(
before: Dump | null,
after: Dump | null
): void

Tells the clients that may see a dump how it changed, as broadcastChange says, for the dump service after every write.

f
broadcastDumpRelatedChanged(
dump: Pick<Dump, "id" | "isPrivate" | "userId">,
others: Pick<Dump, "id" | "isPrivate" | "userId">[]
): void

Sends dump_related_changed after the links between dump and each of others changed, for the backlink service. A link shows only to those who may see both its ends, so a socket is told only when its user may see dump and one of others, and of those alone.

f
broadcastPlaylistChange(
before: Playlist | null,
after: Playlist | null,
members: DatedPlaylistMember[]
): void

Tells the clients that may see a playlist how it changed, as broadcastChange says, for the playlist service after every write to its details. A playlist turned private thus leaves the others' views without their learning what it became. members are the dumps of after: each socket gets the playlist with the count of those its user may see and the date of the latest, the owner's private dumps being theirs alone to count.

f
broadcastPlaylistDumpsUpdated(
playlist: Pick<Playlist, "id" | "isPublic" | "userId">,
members: DatedPlaylistMember[]
): void

Sends playlist_dumps_updated to the playlist's audience, each socket with only the dumps its user may see, as GET would list them. The private dumps in a public playlist stay hidden from everyone but their posters, and so does their number: a client counts the dumps it is sent.

f
broadcastPresence(): void

Sends everyone presence_update. The route calls it whenever a socket opens or closes.

f
broadcastTyping(): void

Sends chat_typing_update to every signed-in socket. The route also calls it when a typing user's socket closes.

f
broadcastUserUpdated(user: PublicUser): void

Sends everyone user_updated, after a profile edit, a role change or an avatar upload.

f
broadcastVoteUpdate(
dumpId: string,
voteCount: number,
voterId: string,
action: "cast" | "remove",
onlyUserId?: string
): void

Sends votes_update after the route stored a vote, to everyone, or with onlyUserId to that user's sockets alone (for a private dump).

f
closeUserSockets(
userId: string,
reason: string
): void

Closes every socket of userId without signing anyone out. The user service calls it on a password change. Each tab reconnects with the token it holds: the tab that made the change has a fresh one and stays signed in, while the others connect anonymously and sign out on their next 401.

f
getOnlineUsers(): OnlineUser[]

Returns each signed-in user with a socket open, once. The route sends it in welcome, and broadcastPresence in presence_update.

f
getTypingUsers(): OnlineUser[]

Returns each user who sent chat_typing true within the last TYPING_TTL, once, for broadcastTyping.

f
handleClientPong(client: WsClient): void

Records a client's pong, which keeps its socket open.

f
isUserChatOpen(userId: string): boolean

Checks whether userId is reading the chat on any of their sockets. The route passes it to notifyMentions, which skips those users: they saw the mention live, and their client chimes for it.

f
register(client: WsClient): void

Adds a client once its socket opens. From then on, it receives the broadcasts, and is shown online if it is signed in.

f
send(
socket: WebSocket,
data: ServerToClientMessage
): void

Sends data to one socket unless it is closing. The WebSocket route answers a client's own message with it: a pong, a vote's ack, an error.

f
sendToUser(
userId: string,
data: ServerToClientMessage
): void

Sends data to every socket of userId. The notification service sends notification_created and notifications_read with it, the follow service follows_changed, and the route chat_read_update.

f
setClientTyping(
client: WsClient,
typing: boolean
): void

Records a client's chat_typing, and broadcasts the typing list when the client starts or stops. The route also clears it when the client sends a message. A client that stops without saying so is expired after TYPING_TTL.

f
takeToken(
key: string,
limit: RateLimit
): boolean

Takes one token from key's bucket, or returns false when it is empty. The caller picks the key, such as typing:<userId>, and keys by user or account so that more tabs or parallel requests don't buy more tokens.

f
unregister(client: WsClient): void

Removes a client when its socket closes. The caller rebroadcasts presence.

f
updateClientAvatar(
userId: string,
avatarMime: string
): void

Records userId's new avatar on their clients, so presence and the typing list show it, and rebroadcasts presence. routes/avatars.ts calls it on an upload.

f
updateClientUsername(
userId: string,
username: string
): void

Records userId's new name on their clients, so presence and the typing list show it, and rebroadcasts presence. The routes that rename a user call it, along with broadcastUserUpdated.

Interfaces

I
RateLimit

Passed to takeToken by each limited action, which declares its own as a constant.

I
WsClient

Created by the WebSocket route for each socket it opens, and kept here until the socket closes. The user fields are read at connect and left undefined for an anonymous socket.

Variables

v
pingJob: Job

Pings every client each PING_INTERVAL, closing the ones that didn't answer the previous ping. main.ts starts it.

v
typingExpiryJob: Job

Expires the typing users who went quiet without a false, such as by closing the tab, every 2 seconds. main.ts starts it.