448 lines
12 KiB
TypeScript
448 lines
12 KiB
TypeScript
/**
|
|
* 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<ServerToClientMessage>`: 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;
|