From 91b89bd297f38a32542ea17ee8e3e777675bf343 Mon Sep 17 00:00:00 2001 From: Joscha Date: Thu, 21 Dec 2023 22:03:36 +0100 Subject: [PATCH] Describe events, commands, replies in more detail --- API.md | 562 ++++++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 499 insertions(+), 63 deletions(-) diff --git a/API.md b/API.md index 45407b5..7666ec3 100644 --- a/API.md +++ b/API.md @@ -57,139 +57,575 @@ id, the server must omit it as well. ### EventId -A message id is a string matching `e[0-9a-f]{16}`. In other words, it's a 128 -bit hexadecimal integer prefixed by `e`. +A message id is a string matching the regex `e[0-9A-F]{16}`. It encodes 64 bit +unsigned integer. -Sorting events by their id puts them in chronological order. +Event ids are unique across rooms. Sorting events by their id puts them in +chronological order. ### MessageId -A message id is a string matching `m[0-9a-f]{16}`. In other words, it's a 128 -bit hexadecimal integer prefixed by `m`. +A message id is a string matching the regex `m[0-9A-F]{16}`. It encodes a 64 bit +unsigned integer. -Sorting messages by their id puts them in chronological order. +Message ids are unique across rooms. Sorting messages by their id puts them in +chronological order. ### SessionId -A session id is a string matching `s[0-9a-f]{16}`. In other words, it's a 128 -bit hexadecimal integer prefixed by `s`. +A session id is a string matching the regex `s[0-9A-F]{16}`. It encodes a 64 bit +unsigned integer. ### UserId -A user id is a string matching `[a-z0-9-]+`. It is usually four characters long -for accounts and eight characters plus a dash for other sessions, though this is -not required. A user id uniquely identifies a user. +A user id is a string matching the regex `u[0-9A-F]{16}`. It encodes a 64 bit +unsigned integer. + +### Account + +```ts +type Account = { + email: string; + displayName: string; + pingName: string; +}; +``` + +### User + +A participant in a room. + +```ts +type User = { + id: UserId; + displayName: string; + localDisplayName?: string; + pingName?: string; + account?: true; + bot?: true; + host?: bool; + admin?: bool; +}; +``` + +### Message + +```ts +type Message = { + id: MessageId; + author: User; + content: string; + pinged: User[]; +}; +``` + +### RoomEvent + +```ts +type RoomEvent = + | { + type: "enter"; + id: EventId; + user: User; + } + | { + type: "exit"; + id: EventId; + user: User; + } + | { + type: "user"; + id: EventId; + user: User; + } + | { + type: "send"; + id: EventId; + message: Message; + } + | { + type: "edit"; + id: EventId; + by: User; + message: Message; + } + | { + type: "delete"; + id: EventId; + by: User; + message: Message; + }; +``` ## Auth phase commands -### `c-auth-cookie` +These commands must only be sent during the auth phase. + +### `auth-anon` + +Obtain a unique user id for this session. + +After the client has received the reply, the connection is in the roam phase. + +```ts +type Command = {}; + +type Reply = { + id: UserId; + account?: Account; +}; +``` + +### `auth-cookie` Authenticate via the cookies exchanged during the HTTP handshake portion of the WebSocket connection. This requires the client to store and present the cookies -on subsequent connections. +on subsequent connection handshakes. -### `c-auth-session-id` +After the client has received the reply, the connection is in the roam phase. + +```ts +type Command = {}; + +type Reply = { + id: UserId; + account?: Account; +}; +``` + +### `auth-session-id` Authenticate via session id. This requires the client to store the session id -and present it on subsequent connections via `auth-session-id`. +and present it on subsequent connections via `auth-session-id`. The client must +send the last known id, if any. The server may return a different id from the id +the client sent. -### `c-auth-anon` +After the client has received the reply, the connection is in the roam phase. -Don't authenticate and obtain a unique user id for this session. +```ts +type Command = { + sessionId?: SessionId; +}; + +type Reply = { + sessionId: SessionId; + id: UserId; + account?: Account; +}; +``` ## Roam phase events -### `e-goodbye` +These events may occur during the roam phase. + +### `goodbye` When the server decides to close the connection, it may send a goodbye event explaining the decision beforehand, though this is not required. -Possible reasons: +```ts +type Event = { + reason: string; +}; +``` -- protocol error (e.g. using roam commands during the auth phase) -- spam (the client is sending too many commands) -- login (the client has logged into an account on another connection for the same session) -- logout (the client has logged out of an account on another connection for the same session) +Reasons may include, but are not limited to: + +- `protocol`: The client did not follow the API specification. Example: Using + roam commands during the auth phase. +- `spam`: The client sent too many commands. +- `login`: The client has logged into an account on either this connection or + another connection for the same session. +- `logout`: The client has logged out of an account on either this connection or + another connection for the same session. ## Roam phase commands -### `c-enter` +### `login` + +TODO Describe `login` event + +### `logout` + +TODO Describe `logout` event + +### `display-name` + +TODO Describe `display-name` event + +## Roam phase room events + +### `enter` + +A user has entered the room. + +```ts +type Event = { + id: EventId; + room: string; + user: User; +}; +``` + +### `exit` + +A user has exited the room. + +```ts +type Event = { + id: EventId; + room: string; + user: User; +}; +``` + +### `user` + +A property of a user has changed. + +```ts +type Event = { + id: EventId; + room: string; + user: User; +}; +``` + +### `send` + +A new message has been sent. + +```ts +type Event = { + id: EventId; + room: string; + message: Message; +}; +``` + +### `edit` + +An existing message has been edited. + +```ts +type Event = { + id: EventId; + room: string; + by: User; + message: Message; +}; +``` + +### `delete` + +An existing message has been deleted. + +```ts +type Event = { + id: EventId; + room: string; + by: User; + messageId: MessageId; +}; +``` + +## Roam phase room commands + +### `enter` Enter a room. This is required for a client to receive room events and send room -commands. A user that has entered a room in at least one client will be displayed -in the nick list. +commands. A user that has entered a room in at least one client will be present +in the user list. -As part of the reply to this command, the server will include the client's -current room nick. +TODO Room authentication, failure in reply -### `c-exit` +This command is idempotent. Entering an already entered room is allowed and has +no special effect. + +Note that a client won't witness its own `enter` event. + +```ts +type Command = { + room: string; +}; + +type Reply = { + present: User[]; +}; +``` + +### `exit` Exit a room. After exiting a room, the server will no longer relay events for that room, and the client can no longer issue commands for that room. -### `c-login` +This command is idempotent. Exiting an already exited room is allowed and has no +special effect. -## Roam phase room events +Note that a client won't witness its own `exit` event. -### `e-enter` +```ts +type Command = { + room: string; +}; -A user has entered the room. +type Reply = {}; +``` -### `e-exit` +### `local-display-name` -A user has exited the room. +Change or unset the local display name. -### `e-nick` +The server may reject the command because the user is not in the specified room. +If so, it will return the result `not-present`. -A user has changed their nick. +The server may reject the command for an arbitrary reason. If so, it will return +the result `rejected` and provide a human-readable reason in its reply. -### `e-send` +This command is idempotent. -A new message has been sent. +Note that this will cause a `user` event if the previous local display name was +different, and may cause a `user` event even if the previous local display name +was identical. -### `e-edit` +```ts +type Command = { + room: string; + localDisplayName?: string; +}; -An existing message has been edited. +type Reply = + | { + result: "success"; + user: User; + } + | { result: "not-present" } + | { + result: "rejected"; + reason: string; + }; +``` -### `e-delete` - -An existing message has been deleted. - -## Roam phase room commands - -### `c-nick` - -Change your own nick. - -### `c-send` +### `send` Send a new message. -### `c-edit` +The server may reject the command because the user is not in the specified room. +If so, it will return the result `not-present`. + +The server may reject the command because the parent does not exist. If so, it +will return the result `nonexistent-parent`. + +The server may reject the command for an arbitrary reason. If so, it will return +the result `rejected` and provide a human-readable reason in its reply. + +This command is idempotent if a `token` value is provided. + +Note that this will cause a `send` event. + +```ts +type Command = { + room: string; + token?: string; + parent?: MessageId; + content: string; +}; + +type Reply = + | { + result: "success"; + message: Message; + } + | { result: "not-present" } + | { result: "nonexistent-parent" } + | { + result: "rejected"; + reason: string; + }; +``` + +### `edit` Edit a message. -### `c-delete` +The server may reject the command because the user is not in the specified room. +If so, it will return the result `not-present`. + +The server may reject the command because the user has insufficient permissions +to edit the message. If so, it will return the result +`insufficient-permissions`. + +The server may reject the command because the message does not exist. If so, it +will return the result `nonexistent`. + +The server may reject the command for an arbitrary reason. If so, it will return +the result `rejected` and provide a human-readable reason in its reply. + +This command is idempotent. + +Note that this will cause an `edit` event. + +```ts +type Command = { + room: string; + messageId: MessageId; + content: string; +}; + +type Reply = + | { + result: "success"; + message: Message; + } + | { result: "not-present" } + | { result: "insufficient-permissions" } + | { result: "nonexistent" } + | { + result: "rejected"; + reason: string; + }; +``` + +### `delete` Delete a message. -### `c-get-msg` +TODO Think through how deletion interacts with the event log. + +The server may reject the command because the user is not in the specified room. +If so, it will return the result `not-present`. + +The server may reject the command because the user has insufficient permissions +to delete the message. If so, it will return the result +`insufficient-permissions`. + +This command is idempotent. Deleting a non-existent message does not result in +an error. + +Note that this will cause a `delete` event. + +```ts +type Command = { + room: string; + messageId: MessageId; + content: string; +}; + +type Reply = { + result: "success" | "not-present" | "insufficient-permissions"; +}; +``` + +### `get-users` + +Request a list of users currently present in a room. + +This may be useful for clients like bots that don't want to track who is present +or not. + +The server may reject the command because the user is not in the specified room. +If so, it will return the result `not-present`. + +This command is idempotent because it has no server-side effects. + +```ts +type Command = { + room: string; +}; + +type Reply = + | { + result: "success"; + users: User[]; + } + | { result: "not-present" }; +``` + +### `get-message` Request a specific message. +TODO Consider whether this should include previous edits + This may be useful for clients like bots that don't want to store the entire message history. -### `c-get-thread` +The server may reject the command because the user is not in the specified room. +If so, it will return the result `not-present`. -Request one or more threads, optionally starting before a specific message id. +The server may reject the command because the message does not exist. If so, it +will return the result `nonexistent`. -If no message id is specified, the latest threads are returned. If a message id -is specified, the first few threads older than that id are returned. +This command is idempotent because it has no server-side effects. -### `c-get-log` +```ts +type Command = { + room: string; +}; -Request event log, optionally starting before a specific event id. +type Reply = + | { + result: "success"; + message: Message; + } + | { result: "not-present" } + | { result: "nonexistent" }; +``` -If no event id is specified, the latest events are returned. If an event id is -specified, the first few events older than that id are returned. +### `get-threads` + +Request one or more threads. + +If no amount is specified, the server chooses an arbitrary amount. The server +may return fewer messages than the specified amount. + +If a message id is specified, the youngest few threads before the id are +returned. Otherwise, the youngest few threads are returned. Returned threads are +always consecutive and ordered ascending by id (old to young). + +The server may reject the command because the user is not in the specified room. +If so, it will return the result `not-present`. + +```ts +type Command = { + room: string; + amount?: number; + before?: MessageId; +}; + +type Reply = + | { + result: "success"; + messages: Message[]; + } + | { result: "not-present" }; +``` + +### `get-events` + +Request events from the event log. + +If no amount is specified, the server chooses an arbitrary amount. The server +may return fewer messages than the specified amount. + +If an event id is specified, the youngest few events before the id are returned. +Otherwise, the youngest few events are returned. Returned events are always +consecutive and ordered ascending by id (old to young). + +The server may reject the command because the user is not in the specified room. +If so, it will return the result `not-present`. + +```ts +type Command = { + room: string; + amount?: number; + before?: MessageId; +}; + +type Reply = + | { + result: "success"; + events: RoomEvent[]; + } + | { result: "not-present" }; +```