Describe events, commands, replies in more detail
This commit is contained in:
parent
f05f6882f9
commit
91b89bd297
1 changed files with 499 additions and 63 deletions
562
API.md
562
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" };
|
||||
```
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue