/** * The socket protocol. Every message has a `type` discriminant, so each union * narrows with a switch. The server sends these as JSON, so the client reads * `Wire`: its Dates arrive as strings. * * @module */ import type { ChatMessage } from "./chat.ts"; import type { Comment } from "./comment.ts"; import type { Dump } from "./dump.ts"; import type { Notification } from "./notification.ts"; import type { Playlist } from "./playlist.ts"; import type { PublicUser } from "./user.ts"; // ── Client → Server ────────────────────────────────────────────────────────── /** A keepalive; the other side answers {@link PongMessage}. Both directions. */ export interface PingMessage { /** The discriminant. */ type: "ping"; } /** The answer to a {@link PingMessage}. Both directions. */ export interface PongMessage { /** The discriminant. */ type: "pong"; } /** Upvotes a dump; answered by {@link VoteAckMessage}. */ export interface VoteCastMessage { /** The discriminant. */ type: "vote_cast"; /** The dump's id. */ dumpId: string; } /** Withdraws an upvote; answered by {@link VoteAckMessage}. */ export interface VoteRemoveMessage { /** The discriminant. */ type: "vote_remove"; /** The dump's id. */ dumpId: string; } /** Likes a comment; answered by {@link CommentLikeAckMessage}. */ export interface CommentLikeCastMessage { /** The discriminant. */ type: "comment_like_cast"; /** The comment's id. */ commentId: string; } /** Withdraws a like; answered by {@link CommentLikeAckMessage}. */ export interface CommentLikeRemoveMessage { /** The discriminant. */ type: "comment_like_remove"; /** The comment's id. */ commentId: string; } /** Posts a chat message; broadcast back as {@link ChatMessageMessage}. */ export interface ChatSendMessage { /** The discriminant. */ type: "chat_send"; /** The message, in Markdown. */ body: string; /** Id of the message being replied to, if this is a reply. */ replyToId?: string; /** * Client-chosen id echoed on the broadcast (or on the error), so the * sender can match the outcome to its pending message. */ nonce?: string; } /** * Sent while the user is composing (true) and when they stop (false). The * server rebroadcasts the live set of typers to everyone as * {@link ChatTypingUpdateMessage}. */ export interface ChatTypingMessage { /** The discriminant. */ type: "chat_typing"; /** Whether the user is composing. */ typing: boolean; } /** * Tells the server whether this client currently has the chatbox open, so that * mention notifications can be skipped for users who are already reading chat. */ export interface ChatFocusMessage { /** The discriminant. */ type: "chat_focus"; /** Whether the chatbox is open. */ open: boolean; } /** Edits a chat message: the user's own, or any with `chat:moderate`. */ export interface ChatEditMessage { /** The discriminant. */ type: "chat_edit"; /** The message's id. */ id: string; /** The new text, in Markdown. */ body: string; } /** Deletes a chat message: the user's own, or any with `chat:moderate`. */ export interface ChatDeleteMessage { /** The discriminant. */ type: "chat_delete"; /** The message's id. */ id: string; } /** The user has seen the chat up to the message created at `at`. */ export interface ChatReadMessage { /** The discriminant. */ type: "chat_read"; /** The creation time of the last message seen, as an ISO string. */ at: string; } /** Any message a client sends. */ export type ClientToServerMessage = | PingMessage | PongMessage | VoteCastMessage | VoteRemoveMessage | CommentLikeCastMessage | CommentLikeRemoveMessage | ChatSendMessage | ChatTypingMessage | ChatFocusMessage | ChatEditMessage | ChatDeleteMessage | ChatReadMessage; // ── Server → Client ────────────────────────────────────────────────────────── /** A signed-in user with at least one socket open. */ export interface OnlineUser { /** The user's id. */ userId: string; /** The user's name. */ username: string; /** Whether the user has an avatar. */ hasAvatar: boolean; /** When the avatar last changed, to bust the client's cache. */ avatarVersion?: number; } /** The first message on a new socket: the state the client starts from. */ export interface WelcomeMessage { /** The discriminant. */ type: "welcome"; /** Who is online. */ users: OnlineUser[]; /** The ids of the dumps the user upvoted; empty when signed out. */ myVotes: string[]; /** The ids of the comments the user likes; empty when signed out. */ myCommentLikes: string[]; /** How many notifications the user hasn't read. */ unreadNotificationCount: number; /** * The user's chat read marker (signed-in users only), as an ISO string. */ chatReadAt?: string; /** How many chat messages came after the read marker. */ unreadChatCount: number; } /** Who is online, sent to everyone whenever it changes. */ export interface PresenceUpdateMessage { /** The discriminant. */ type: "presence_update"; /** Who is online. */ users: OnlineUser[]; } /** * A dump's vote count changed; sent to everyone, or only to the poster for a * private dump. */ export interface VotesUpdateMessage { /** The discriminant. */ type: "votes_update"; /** The dump's id. */ dumpId: string; /** Its new vote count. */ voteCount: number; /** Who voted. */ voterId: string; /** Whether the vote was cast or withdrawn. */ action: "cast" | "remove"; } /** Confirms the sender's own vote, with the count it led to. */ export interface VoteAckMessage { /** The discriminant. */ type: "vote_ack"; /** The dump's id. */ dumpId: string; /** Whether the vote was cast or withdrawn. */ action: "cast" | "remove"; /** The dump's new vote count. */ voteCount: number; } /** A comment's like count changed. */ export interface CommentLikesUpdateMessage { /** The discriminant. */ type: "comment_likes_update"; /** The comment's id. */ commentId: string; /** Its new like count. */ likeCount: number; /** Who liked it. */ likerId: string; /** Whether the like was cast or withdrawn. */ action: "cast" | "remove"; } /** Confirms the sender's own like, with the count it led to. */ export interface CommentLikeAckMessage { /** The discriminant. */ type: "comment_like_ack"; /** The comment's id. */ commentId: string; /** Whether the like was cast or withdrawn. */ action: "cast" | "remove"; /** The comment's new like count. */ likeCount: number; } /** A dump was posted. */ export interface DumpCreatedMessage { /** The discriminant. */ type: "dump_created"; /** The new dump. */ dump: Dump; } /** A dump changed. */ export interface DumpUpdatedMessage { /** The discriminant. */ type: "dump_updated"; /** The dump as it is now. */ dump: Dump; } /** A dump was deleted. */ export interface DumpDeletedMessage { /** The discriminant. */ type: "dump_deleted"; /** The deleted dump's id. */ dumpId: string; } /** A playlist was created; sent to whoever may see it. */ export interface PlaylistCreatedMessage { /** The discriminant. */ type: "playlist_created"; /** The new playlist. */ playlist: Playlist; } /** A playlist changed; sent to whoever may see it, or could before. */ export interface PlaylistUpdatedMessage { /** The discriminant. */ type: "playlist_updated"; /** The playlist as it is now. */ playlist: Playlist; } /** A playlist was deleted; sent to whoever could see it. */ export interface PlaylistDeletedMessage { /** The discriminant. */ type: "playlist_deleted"; /** The deleted playlist's id. */ playlistId: string; /** Its owner's id. */ userId: string; } /** A playlist's dumps changed; each recipient gets the ones they may see. */ export interface PlaylistDumpsUpdatedMessage { /** The discriminant. */ type: "playlist_dumps_updated"; /** The playlist's id. */ playlistId: string; /** The members this recipient can see, in order. */ dumpIds: string[]; /** Every member, as Playlist.dumpCount counts them. */ dumpCount: number; } /** A user's public profile changed. */ export interface UserUpdatedMessage { /** The discriminant. */ type: "user_updated"; /** The user as they are now. */ user: PublicUser; } /** A comment was posted. */ export interface CommentCreatedMessage { /** The discriminant. */ type: "comment_created"; /** The new comment. */ comment: Comment; } /** A comment was edited. */ export interface CommentUpdatedMessage { /** The discriminant. */ type: "comment_updated"; /** The comment as it is now. */ comment: Comment; } /** A comment was deleted. */ export interface CommentDeletedMessage { /** The discriminant. */ type: "comment_deleted"; /** The deleted comment's id. */ commentId: string; /** The id of the dump it was under. */ dumpId: string; } /** A notification for the recipient. */ export interface NotificationCreatedMessage { /** The discriminant. */ type: "notification_created"; /** The notification. */ notification: Notification; } /** An action the client sent failed. */ export interface ErrorMessage { /** The discriminant. */ type: "error"; /** What went wrong. */ message?: string; /** * The dump of a failed vote. With `commentId` and `action`, it gives the * client the context of an optimistic-action failure, so it can revert the * exact pending vote or like immediately instead of waiting for its ACK * timeout. */ dumpId?: string; /** The comment of a failed like. */ commentId?: string; /** The failed vote's or like's action. */ action?: "cast" | "remove"; /** Set when a chat action failed, so the client can surface it in the chat. */ scope?: "chat"; /** Echo of the failed chat_send's nonce. */ nonce?: string; } /** The session ended — the account was deleted or its token revoked. */ export interface ForceLogoutMessage { /** The discriminant. */ type: "force_logout"; } /** A chat message was posted. */ export interface ChatMessageMessage { /** The discriminant. */ type: "chat_message"; /** The new message. */ message: ChatMessage; /** The sender's nonce, so its pending copy can be retired. */ nonce?: string; } /** A chat message was edited. */ export interface ChatMessageUpdatedMessage { /** The discriminant. */ type: "chat_message_updated"; /** The message as it is now. */ message: ChatMessage; } /** A chat message was deleted. */ export interface ChatMessageDeletedMessage { /** The discriminant. */ type: "chat_message_deleted"; /** The deleted message's id. */ id: string; } /** * The current set of users composing a chat message. Reuses * {@link OnlineUser} so the client can render the same avatars as the * presence row. */ export interface ChatTypingUpdateMessage { /** The discriminant. */ type: "chat_typing_update"; /** Who is composing. */ users: OnlineUser[]; } /** The user's read marker moved (from any of their tabs or devices). */ export interface ChatReadUpdateMessage { /** The discriminant. */ type: "chat_read_update"; /** The new marker, as an ISO string. */ readAt: string; /** How many messages came after it. */ unread: number; } /** Any message the server sends. */ export type ServerToClientMessage = | PingMessage | PongMessage | WelcomeMessage | PresenceUpdateMessage | VotesUpdateMessage | VoteAckMessage | CommentLikesUpdateMessage | CommentLikeAckMessage | DumpCreatedMessage | DumpUpdatedMessage | DumpDeletedMessage | PlaylistCreatedMessage | PlaylistUpdatedMessage | PlaylistDeletedMessage | PlaylistDumpsUpdatedMessage | UserUpdatedMessage | CommentCreatedMessage | CommentUpdatedMessage | CommentDeletedMessage | NotificationCreatedMessage | ErrorMessage | ForceLogoutMessage | ChatMessageMessage | ChatMessageUpdatedMessage | ChatMessageDeletedMessage | ChatTypingUpdateMessage | ChatReadUpdateMessage;