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
|
### EventId
|
||||||
|
|
||||||
A message id is a string matching `e[0-9a-f]{16}`. In other words, it's a 128
|
A message id is a string matching the regex `e[0-9A-F]{16}`. It encodes 64 bit
|
||||||
bit hexadecimal integer prefixed by `e`.
|
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
|
### MessageId
|
||||||
|
|
||||||
A message id is a string matching `m[0-9a-f]{16}`. In other words, it's a 128
|
A message id is a string matching the regex `m[0-9A-F]{16}`. It encodes a 64 bit
|
||||||
bit hexadecimal integer prefixed by `m`.
|
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
|
### SessionId
|
||||||
|
|
||||||
A session id is a string matching `s[0-9a-f]{16}`. In other words, it's a 128
|
A session id is a string matching the regex `s[0-9A-F]{16}`. It encodes a 64 bit
|
||||||
bit hexadecimal integer prefixed by `s`.
|
unsigned integer.
|
||||||
|
|
||||||
### UserId
|
### UserId
|
||||||
|
|
||||||
A user id is a string matching `[a-z0-9-]+`. It is usually four characters long
|
A user id is a string matching the regex `u[0-9A-F]{16}`. It encodes a 64 bit
|
||||||
for accounts and eight characters plus a dash for other sessions, though this is
|
unsigned integer.
|
||||||
not required. A user id uniquely identifies a user.
|
|
||||||
|
### 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
|
## 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
|
Authenticate via the cookies exchanged during the HTTP handshake portion of the
|
||||||
WebSocket connection. This requires the client to store and present the cookies
|
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
|
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
|
## 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
|
When the server decides to close the connection, it may send a goodbye event
|
||||||
explaining the decision beforehand, though this is not required.
|
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)
|
Reasons may include, but are not limited to:
|
||||||
- spam (the client is sending too many commands)
|
|
||||||
- login (the client has logged into an account on another connection for the same session)
|
- `protocol`: The client did not follow the API specification. Example: Using
|
||||||
- logout (the client has logged out of an account on another connection for the same session)
|
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
|
## 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
|
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
|
commands. A user that has entered a room in at least one client will be present
|
||||||
in the nick list.
|
in the user list.
|
||||||
|
|
||||||
As part of the reply to this command, the server will include the client's
|
TODO Room authentication, failure in reply
|
||||||
current room nick.
|
|
||||||
|
|
||||||
### `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
|
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.
|
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`
|
### `send`
|
||||||
|
|
||||||
An existing message has been deleted.
|
|
||||||
|
|
||||||
## Roam phase room commands
|
|
||||||
|
|
||||||
### `c-nick`
|
|
||||||
|
|
||||||
Change your own nick.
|
|
||||||
|
|
||||||
### `c-send`
|
|
||||||
|
|
||||||
Send a new message.
|
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.
|
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.
|
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.
|
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
|
This may be useful for clients like bots that don't want to store the entire
|
||||||
message history.
|
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
|
This command is idempotent because it has no server-side effects.
|
||||||
is specified, the first few threads older than that id are returned.
|
|
||||||
|
|
||||||
### `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
|
### `get-threads`
|
||||||
specified, the first few events older than that id are returned.
|
|
||||||
|
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