summaryrefslogtreecommitdiff
path: root/docs
diff options
context:
space:
mode:
Diffstat (limited to 'docs')
-rw-r--r--docs/api/authentication.md35
-rw-r--r--docs/api/channels-messages.md68
-rw-r--r--docs/api/events.md39
-rw-r--r--docs/api/initial-setup.md20
-rw-r--r--docs/api/invitations.md32
-rw-r--r--docs/internal-server-errors.md30
6 files changed, 190 insertions, 34 deletions
diff --git a/docs/api/authentication.md b/docs/api/authentication.md
index d4c1f70..135e91b 100644
--- a/docs/api/authentication.md
+++ b/docs/api/authentication.md
@@ -13,6 +13,23 @@ stateDiagram-v2
Authentication associates each authenticated request with a login.
+To create logins, see [initial setup](./initial-setup.md) and [invitations](./invitations.md).
+
+
+## Names
+
+<!-- This prose is duplicated in channels-messages.md. If you change it here, consider changing it there, too. -->
+The service handles login names using two separate forms.
+
+The first form is as given in the request used to create the login. This form of the login name is used throughout the API, and the service will preserve the name as entered (other than applying normalization), so that users' preferences around capitalization and accent marks are preserved.
+
+The second form is a "canonical" form, used internally by the service to control uniqueness and match names to logins. The canonical form is both case-folded and normalized.
+
+The canonical form is not available to API clients, but its use has practical consequences:
+
+* Names that differ only by case or only by code point sequence are treated as the same name. If the name is in use, changing the capitalization or changing the sequence of combining marks will not allow the creation of a second "identical" login.
+* The login API accepts any name that canonicalizes to the form stored in the database, making login names effectively case-insensitive.
+
## Identity tokens
@@ -32,8 +49,6 @@ Unless the endpoint's documentation says otherwise, all endpoints require authen
Authenticates the user using their login name and password. The login must exist before calling this endpoint.
-To create logins, see [initial setup](./initial-setup.md) and [invitations](./invitations.md).
-
**This endpoint does not require an `identity` cookie.**
### Request
@@ -56,7 +71,21 @@ The request must have the following fields:
<!-- This prose is duplicated by 03-initial-setup.md and in 04-invitations.md, with small changes for context. If you edit it here, edit it there, too. -->
-This endpoint will respond with a status of `204 No Content` when successful.
+This endpoint will respond with a status of `200 Okay` when successful. The body of the response will be a JSON object describing the authenticated login:
+
+```json
+{
+ "id": "Labcd1234",
+ "name": "Andrea"
+}
+```
+
+The response will include the following fields:
+
+| Field | Type | Description |
+|:------------|:-------|:--|
+| `id` | string | The authenticated login's ID. |
+| `name` | string | The authenticated login's name. |
The response will include a `Set-Cookie` header for the `identity` cookie, providing the client with a newly-minted identity token associated with the login identified in the request. This token's value must be kept confidential.
diff --git a/docs/api/channels-messages.md b/docs/api/channels-messages.md
index d032ac1..9ef4e66 100644
--- a/docs/api/channels-messages.md
+++ b/docs/api/channels-messages.md
@@ -8,7 +8,7 @@ stateDiagram-v2
[*] --> Active : POST /api/channels
Active --> Deleted : DELETE /api/channels/C1234
Active --> Deleted : Expiry
- Deleted --> [*]
+ Deleted --> [*] : Purge
```
```mermaid
@@ -19,14 +19,31 @@ stateDiagram-v2
[*] --> Sent : POST /api/channels/C1234
Sent --> Deleted : DELETE /api/messages/Mabcd
Sent --> Deleted : Expiry
- Deleted --> [*]
+ Deleted --> [*] : Purge
```
Messages allow logins to communicate with one another. Channels are the conversations to which those messages are sent.
Every channel has a unique name, chosen when the channel is created.
-Both channels and messages expire after a time, to prevent the service from consuming unlimited amounts of storage. Messages expire 90 days after being sent. Channels expire 90 days after the last message sent to them, or after creation if no messages are sent in that time.
+
+## Names
+
+<!-- This prose is duplicated in authentication.md. If you change it here, consider changing it there, too. -->
+The service handles channel names using two separate forms.
+
+The first form is as given in the request used to create the channel. This form of the channel name is used throughout the API, and the service will preserve the name as entered (other than applying normalization), so that users' preferences around capitalization and accent marks are preserved.
+
+The second form is a "canonical" form, used internally by the service to control uniqueness and match names to channels. The canonical form is both case-folded and normalized.
+
+The canonical form is not available to API clients, but its use has practical consequences. Names that differ only by case or only by code point sequence are treated as the same name. If the name is in use, changing the capitalization or changing the sequence of combining marks will not allow the creation of a second "identical" channel.
+
+
+## Expiry and purging
+
+Both channels and messages expire after a time. Messages expire 90 days after being sent. Channels expire 90 days after the last message sent to them, or after creation if no messages are sent in that time.
+
+Deleted channels and messages, including those that have expired, are temporarily retained by the service, to allow clients that are not connected to receive the corresponding deletion [events](./events.md). To limit storage growth, deleted channels and messages are purged from the service seven days after they were deleted.
## `POST /api/channels`
@@ -49,12 +66,12 @@ The request must have the following fields:
### Success
-This endpoint will respond with a status of `200 Okay` when successful. The body of the response will be a JSON object describing the new channel:
+This endpoint will respond with a status of `202 Accepted` when successful. The body of the response will be a JSON object describing the new channel:
```json
{
- "name": "a unique channel name",
"id": "C9876cyyz"
+ "name": "a unique channel name",
}
```
@@ -62,8 +79,12 @@ The response will have the following fields:
| Field | Type | Description |
|:-------|:-------|:--|
+| `id` | string | A unique identifier for the channel. This can be used to associate the channel with events, or to make API calls targeting the channel. |
| `name` | string | The channel's name. |
-| `id` | string | A unique identifier for the channel. This can be used to associate the channel with other events, or to make API calls targeting the channel. |
+
+The returned name may not be identical to the name requested, as the name will be converted to [normalization form C](http://www.unicode.org/reports/tr15/) automatically. The returned name will include this normalization; the service will use the normalized name elsewhere, and does not store the originally requested name.
+
+When completed, the service will emit a [channel created](events.md#channel-created) event with the channel's ID.
### Duplicate channel name
@@ -96,7 +117,31 @@ The request must have the following fields:
### Success
-This endpoint will respond with a status of `202 Accepted` when successful. The response will not include a body.
+This endpoint will respond with a status of `202 Accepted` when successful. The body of the response will be a JSON object describing the newly-sent message:
+
+```json
+{
+ "at": "2024-10-19T04:37:09.467325Z",
+ "channel": "Cfqdn1234",
+ "sender": "Labcd1234",
+ "id": "Mgh98yp75",
+ "body": "an elaborate example message"
+}
+```
+
+The response will have the following fields:
+
+| Field | Type | Description |
+|:----------|:----------|:--|
+| `at` | timestamp | The moment the message was sent. |
+| `channel` | string | The ID of the channel the message was sent to. |
+| `sender` | string | The ID of the login that sent the message. |
+| `id` | string | A unique identifier for the message. This can be used to associate the message with events, or to make API calls targeting the message. |
+| `body` | string | The message's body. |
+
+The returned message body may not be identical to the body as sent, as the body will be converted to [normalization form C](http://www.unicode.org/reports/tr15/) automatically. The returned body will include this normalization; the service will use the normalized body elsewhere, and does not store the originally submitted body.
+
+When completed, the service will emit a [message sent](events.md#message-sent) event with the message's ID.
### Invalid channel ID
@@ -105,7 +150,9 @@ This endpoint will respond with a status of `404 Not Found` if the channel ID is
## `DELETE /api/channels/:id`
-Deletes a channel (and all messages in it).
+Deletes a channel.
+
+Deleting a channel prevents it from receiving any further messages, and deletes the messages it contains at that point.
This endpoint requires the following path parameter:
@@ -117,6 +164,9 @@ This endpoint requires the following path parameter:
This endpoint will respond with a status of `202 Accepted` when successful. The response will not include a body.
+When completed, the service will emit a [channel deleted](events.md#channel-deleted) event with the channel's ID. In addition, the service will emit a [message deleted](events.md#message-deleted) event for each message deleted.
+
+
### Invalid channel ID
This endpoint will respond with a status of `404 Not Found` if the channel ID is not valid.
@@ -136,6 +186,8 @@ This endpoint requires the following path parameter:
This endpoint will respond with a status of `202 Accepted` when successful. The response will not include a body.
+When completed, the service will emit a [message deleted](events.md#message-deleted) event with the channel's ID.
+
### Invalid message ID
This endpoint will respond with a status of `404 Not Found` if the message ID is not valid.
diff --git a/docs/api/events.md b/docs/api/events.md
index 9fe9ca9..b08e971 100644
--- a/docs/api/events.md
+++ b/docs/api/events.md
@@ -31,6 +31,11 @@ sequenceDiagram
The core of the service is to facilitate conversations between logins. Conversational activity is delivered to clients using _events_. Each event notifies interested clients of activity sent to the service through its API.
+## Asynchronous completion
+
+A number of endpoints return `202 Accepted` responses. The actions performed by those endpoints will be completed before events are delivered. To await the completion of an operation which returns this response, clients must monitor the event stream for the corresponding event.
+
+
## `GET /api/events`
Subscribes to events.
@@ -133,11 +138,16 @@ Sent whenever a new channel is created.
These events have the `event` field set to `"created"`. They include the following additional fields:
-| Field | Type | Description |
-|:-------|:----------|:--|
-| `at` | timestamp | The moment the channel was created. |
-| `id` | string | A unique identifier for the newly-created channel. This can be used to associate the channel with other events, or to make API calls targeting the channel. |
-| `name` | string | The channel's name. |
+| Field | Type | Description |
+|:-------------|:--------------------|:--|
+| `at` | timestamp | The moment the channel was created. |
+| `id` | string | A unique identifier for the newly-created channel. This can be used to associate the channel with other events, or to make API calls targeting the channel. |
+| `name` | string | The channel's name. |
+| `deleted_at` | timestamp, optional | If set, the moment the channel was deleted. |
+
+When a channel is deleted or expires, the `"created"` event is replaced with a tombstone `"created"` event, so that the original channel cannot be trivially recovered from the event stream. Tombstone events have a `deleted_at` field, and a `name` of `""`. Tombstone events for channels use an empty string as the name, and not `null` or with the `name` field removed entirely, to simplify client development. While clients _should_ treat deleted channels specially, for example by rendering them as "channel deleted" markers, they don't have to be - they make sense if interpreted as channels with empty names, too.
+
+Once a deleted channel is [purged](./channels-messages.md#expiry-and-purging), these tombstone events are removed from the event stream.
### Channel deleted
@@ -184,13 +194,18 @@ Sent whenever a message is sent by a client.
These events have the `event` field set to `"sent"`. They include the following additional fields:
-| Field | Type | Description |
-|:----------|:----------|:--|
-| `at` | timestamp | The moment the message was sent. |
-| `channel` | string | The ID of the channel the message was sent to. |
-| `sender` | string | The ID of the login that sent the message. |
-| `id` | string | A unique identifier for the message. This can be used to associate the message with other events, or to make API calls targeting the message. |
-| `body` | string | The text of the message. |
+| Field | Type | Description |
+|:-------------|:--------------------|:--|
+| `at` | timestamp | The moment the message was sent. |
+| `channel` | string | The ID of the channel the message was sent to. |
+| `sender` | string | The ID of the login that sent the message. |
+| `id` | string | A unique identifier for the message. This can be used to associate the message with other events, or to make API calls targeting the message. |
+| `body` | string | The text of the message. |
+| `deleted_at` | timestamp, optional | If set, the moment the message was deleted. |
+
+When a message is deleted or expires, the `"sent"` event is replaced with a tombstone `"sent"` event, so that the original message cannot be trivially recovered from the event stream. Tombstone events have a `deleted_at` field, and a `body` of `""`. Tombstone events for messages use an empty string as the `body`, and not `null` or with the `body` field removed entirely, to simplify client development. While clients _should_ treat deleted messages specially, for example by rendering them as "message deleted" markers, they don't have to be - they make sense if interpreted as messages with empty bodies, too.
+
+Once a deleted message is [purged](./channels-messages.md#expiry-and-purging), these tombstone events are removed from the event stream.
### Message deleted
diff --git a/docs/api/initial-setup.md b/docs/api/initial-setup.md
index ad57089..306d798 100644
--- a/docs/api/initial-setup.md
+++ b/docs/api/initial-setup.md
@@ -55,7 +55,25 @@ The request must have the following fields:
<!-- This prose is duplicated from authentication.md, with small changes for context. If you edit it here, edit it there, too. -->
-This endpoint will respond with a status of `204 No Content` when successful.
+This endpoint will respond with a status of `200 Okay` when successful. The body of the response will be a JSON object describing the newly-created login:
+
+```json
+{
+ "id": "Labcd1234",
+ "name": "Andrea"
+}
+```
+
+The response will include the following fields:
+
+| Field | Type | Description |
+|:------------|:-------|:--|
+| `id` | string | A unique identifier for the newly-created login. This can be used to associate the login with other events, or to make API calls targeting the login. |
+| `name` | string | The login's name. |
+
+The returned name may not be identical to the name requested, as the name will be converted to [normalization form C](http://www.unicode.org/reports/tr15/) automatically. The returned name will include this normalization; the service will use the normalized name elsewhere, and does not store the originally requested name.
+
+The provided password will also be converted to normalization form C. However, the normalized password is not returned to the client.
The response will include a `Set-Cookie` header for the `identity` cookie, providing the client with a newly-minted identity token associated with the initial login created for this request. See the [authentication](./authentication) section for details on how this cookie may be used.
diff --git a/docs/api/invitations.md b/docs/api/invitations.md
index 0f21a0e..ddbef8a 100644
--- a/docs/api/invitations.md
+++ b/docs/api/invitations.md
@@ -91,17 +91,11 @@ The response will include the following fields:
| Field | Type | Description |
|:------------|:-------|:--|
-| `issuer` | object | The details of the login that issued the invitation. |
+| `id` | string | The ID of the invitation. |
+| `issuer` | string | The login name of the invitation's issuer. |
| `issued_at` | string | The timestamp from which the invitation will expire. |
-The `issuer` object will include the following fields:
-
-| Field | Type | Description |
-|:-------|:-------|:--|
-| `id` | string | The login ID of the invitation's issuer. |
-| `name` | string | The login name of the invitation's issuer. |
-
-Clients should present the issuer's name to the user when presenting an invitation, so as to personalize the invitation and help them understand their connection with the service.
+Clients should present the `issuer` to the user when presenting an invitation, so as to personalize the invitation and help them understand their connection with the service.
### Invitation not found
@@ -140,7 +134,25 @@ The request must have the following fields:
<!-- This prose is duplicated from authentication.md, with small changes for context. If you edit it here, edit it there, too. -->
-This endpoint will respond with a status of `204 No Content` when successful.
+This endpoint will respond with a status of `200 Okay` when successful. The body of the response will be a JSON object describing the newly-created login:
+
+```json
+{
+ "id": "Labcd1234",
+ "name": "Andrea"
+}
+```
+
+The response will include the following fields:
+
+| Field | Type | Description |
+|:------------|:-------|:--|
+| `id` | string | A unique identifier for the newly-created login. This can be used to associate the login with other events, or to make API calls targeting the login. |
+| `name` | string | The login's name. |
+
+The returned name may not be identical to the name requested, as the name will be converted to [normalization form C](http://www.unicode.org/reports/tr15/) automatically. The returned name will include this normalization; the service will use the normalized name elsewhere, and does not store the originally requested name.
+
+The provided password will also be converted to normalization form C. However, the normalized password is not returned to the client.
The response will include a `Set-Cookie` header for the `identity` cookie, providing the client with a newly-minted identity token associated with the login created for this request. See the [authentication](./authentication.md) section for details on how this cookie may be used.
diff --git a/docs/internal-server-errors.md b/docs/internal-server-errors.md
new file mode 100644
index 0000000..4f679b7
--- /dev/null
+++ b/docs/internal-server-errors.md
@@ -0,0 +1,30 @@
+# Internal Server Errors
+
+When `hi` encounters a problem that prevents a request from completing, it may report a `500 Internal Server Error` to clients, along with an error code. The actual error will be printed to standard error, with the error code. The following sections describe errors we've encountered, the likely operational consequences, and recommend approaches for addressing them.
+
+## database is locked
+
+The server attempted two write transactions at the same time, and encountered [sqlite's write locks](https://www.sqlite.org/rescode.html#busy). This is unfortunately unavoidable, but generally only occurs as a result of extremely bad luck, or very high load.
+
+This error will almost always resolve itself if clients re-try their requests; no further action is needed.
+
+This is a known issue. If you are encountering this consistently (or if you can trigger it on demand), let us know. We are aware of sqlite's features for mitigating this issue but have been unsuccessful in applying them; we're working on it, but patches _are_ welcome, if you have the opportunity.
+
+## stored canonical form […] does not match computed canonical form […] for name […]
+
+When `hi` applies the `migrations/20241019191531_canonical_names.sql` migration (from commit `3f9648eed48cd8b6cd35d0ae2ee5bbe25fa735ac`), this can leave existing names in a state where the stored canonical form is not the correct canonicalization of the stored display names of channels and logins. `hi` will abort requests when it encounters this situation, to avoid incorrect behaviours such as duplicate channels or duplicate logins.
+
+As channel and login names may be presented during client startup, this can render the service unusable until repaired. Treat this as an immediate outage if you see it.
+
+You can verify that login names are unique by running the following commands as the user the `hi` server runs as:
+
+* `sqlite3 .hi 'select display_name from login'`
+* `sqlite3 .hi 'select display_name from channel_name'`
+
+Substitute `.hi` with the path to your `hi` database if it differs from the default.
+
+If the names are unique, you can repair the database:
+
+* Stop the `hi` server.
+* Run `hi-recanonicalize`, as the same user the `hi` server runs as, with the same database options.
+* Start the `hi` server.