import docs
This commit is contained in:
parent
6437aa51e0
commit
977cae5801
6 changed files with 575 additions and 1 deletions
|
@ -1,3 +1,7 @@
|
|||
# Summary
|
||||
|
||||
- [Chapter 1](./chapter_1.md)
|
||||
- [data](./data.md)
|
||||
- [design](./design.md)
|
||||
- [install](./install.md)
|
||||
- [syncing](./syncing.md)
|
||||
- [api](./api.md)
|
||||
|
|
395
src/api.md
Normal file
395
src/api.md
Normal file
|
@ -0,0 +1,395 @@
|
|||
# api
|
||||
|
||||
list of all api paths for client-server api
|
||||
|
||||
each path is versioned on its own
|
||||
|
||||
### endpoint summary
|
||||
|
||||
WARNING: although a good portion of the api is taken by or inspired by
|
||||
matrix, these endpoints aren't the same! Please read this documentation
|
||||
rather than [spec.matrix.org](https://spec.matrix.org).
|
||||
|
||||
NOTE: The api doesn't necessarily follow REST conventions.
|
||||
|
||||
NOTE: This is the PLANNED api, and isn't what is currently
|
||||
implemented. The endpoints and structs may change.
|
||||
|
||||
#### client
|
||||
|
||||
prefix = `/_jackwagon/client`
|
||||
|
||||
The main apis for clients to interact with servers. Authentication will be outsourced to oidc.
|
||||
|
||||
but QUERY isn't widely supported :(
|
||||
|
||||
| method | path | description |
|
||||
|--------|---------------------------------------------------|----------------------------------------------------|
|
||||
| QUERY | /v1/info | Get info about this server |
|
||||
| QUERY | /v1/sync | Receive new events; see [syncing](/docs/syncing) |
|
||||
| QUERY | /v1/threads | Query for threads, get inbox |
|
||||
| PUT | /v1/threads/participation | Set thread participation |
|
||||
| POST | /v1/ack | Mark messages as read |
|
||||
| GET | /v1/users/me | Get info about this user (profile) (whoami) |
|
||||
| GET | /v1/users/{userId} | Get info about a user |
|
||||
| PATCH | /v1/users/me | Set info about this user |
|
||||
| PATCH | /v1/users/{userId} | Set info about a user |
|
||||
| PUT | /v1/account-data/{key} | Write account data |
|
||||
| GET | /v1/account-data/{key} | Read account data |
|
||||
| POST | /v1/rooms/create | Create a room |
|
||||
| PUT | /v1/rooms/membership | Change room membership for yourself and others |
|
||||
| POST | /v1/rooms/{roomId}/send/{eventType} | Send an event |
|
||||
| POST | /v1/rooms/{roomId}/ephemeral/{eventType} | Send an ephemeral event |
|
||||
| PUT | /v1/rooms/{roomId}/state/{eventType}/{stateKey} | Send a state event, mutating the state of a room |
|
||||
| GET | /v1/rooms/{roomId}/state/{eventType?}/{stateKey?} | Read state from a room |
|
||||
| QUERY | /v1/rooms/{roomId}/events | Fetch events |
|
||||
| QUERY | /v1/rooms/{roomId}/relations | Fetch relations |
|
||||
| QUERY | /v1/rooms/{roomId}/aliases | Get aliases |
|
||||
| PUT | /v1/rooms/{roomId}/aliases | Set aliases |
|
||||
| PUT | /v1/rooms/{roomId}/account-data/{key} | Write account data |
|
||||
| GET | /v1/rooms/{roomId}/account-data/{key} | Read account data |
|
||||
| POST | /v1/aliases/resolve | Resolve aliases to room ids |
|
||||
| QUERY | /v1/spaces/{roomId}/hierarchy | Fetch space children |
|
||||
| QUERY | /v1/search/users | Search for users |
|
||||
| QUERY | /v1/search/messages | Search for messages |
|
||||
| QUERY | /v1/search/threads | Search for threads |
|
||||
| QUERY | /v1/search/rooms | Search for rooms |
|
||||
| ????? | /v1/voip/???? | Anything needed for voip |
|
||||
| POST | /v1/send-to-device | Send a message directly to a device |
|
||||
| POST | /v1/keys/upload | Upload/publish encryption keys |
|
||||
| POST | /v1/keys/upload-cross-keys | Upload/publish cross-signing encryption keys |
|
||||
| POST | /v1/keys/upload-cross-sigs | Upload/publish cross-signing encryption signatures |
|
||||
| POST | /v1/keys/query | Get another user's encryption keys |
|
||||
| POST | /v1/keys/claim | Claim another user's encryption keys |
|
||||
| PATCH | /v1/key-backup | Add/remove keys from the key backup |
|
||||
| QUERY | /v1/key-backup | Retrieve keys from the key backup |
|
||||
| GET | /v1/key-backup/version | Get the latest version |
|
||||
| POST | /v1/key-backup/version | Create a new version |
|
||||
| GET | /v1/key-backup/version/{version} | Get a version |
|
||||
| PUT | /v1/key-backup/version/{version} | Update a version |
|
||||
| DELETE | /v1/key-backup/version/{version} | Delete a version |
|
||||
|
||||
```rust
|
||||
// post /v1/rooms/threads
|
||||
struct GetThreads {
|
||||
room_ids: Array<RoomId>,
|
||||
from: Option<string>,
|
||||
filter: Option<ThreadFilter>,
|
||||
sort: Option<ThreadSort>,
|
||||
}
|
||||
|
||||
struct ThreadFilter {
|
||||
/// add muted threads
|
||||
muted: bool,
|
||||
|
||||
/// remove unwatched threads
|
||||
watching: bool,
|
||||
|
||||
/// remove read threads
|
||||
unread: bool,
|
||||
}
|
||||
|
||||
enum ThreadSort {
|
||||
/// Newest posts first.
|
||||
#[default]
|
||||
New,
|
||||
|
||||
/// Oldest posts first. Only used for inbox.
|
||||
Old,
|
||||
|
||||
/// Sort by the number of +1s (in a time frame)
|
||||
Votes(Option<Date>),
|
||||
|
||||
/// Sort by the number of replies/comments (in a time frame)
|
||||
Comments(Option<Date>),
|
||||
|
||||
/// Sort by the last reply/comment timestamp
|
||||
Activity,
|
||||
|
||||
/// Sort by the "hot algorithm". By far the most complex. Meant for inbox and large communities.
|
||||
Hot,
|
||||
}
|
||||
```
|
||||
|
||||
```
|
||||
post /v1/rooms/membership
|
||||
{
|
||||
rooms: Array<{
|
||||
room: RoomId | RoomAlias,
|
||||
user_id?: UserId,
|
||||
membership?: "join" | "leave" | "invite" | "knock" | "ban",
|
||||
forget?: boolean, // standalone or when membership = leave?
|
||||
}>
|
||||
}
|
||||
|
||||
/events
|
||||
{
|
||||
event_id?: EventId,
|
||||
limit_before?: number,
|
||||
limit_after?: number,
|
||||
}
|
||||
|
||||
/relations
|
||||
{
|
||||
event_id: EventId,
|
||||
rel_type?: string,
|
||||
event_types?: Array<string>,
|
||||
depth?: number, // recursion depth?
|
||||
|
||||
// pagination
|
||||
limit?: number,
|
||||
after?: string,
|
||||
}
|
||||
```
|
||||
|
||||
```rust
|
||||
/// The standard "text" type. Used for all user-facing text.
|
||||
struct Text(Vec<TextBlock>);
|
||||
|
||||
struct TextBlock {
|
||||
/// The content itself.
|
||||
// NOTE: should this be `any` instead of string?
|
||||
body: String,
|
||||
|
||||
/// The language of the text (ie. en, en_US, i forget the name for this convention)
|
||||
lang: Option<String>,
|
||||
|
||||
/// The mime type of the body. May be `text/plain` or `text/html`
|
||||
// NOTE: maybe should it be `text/jackwagon+html` or `text/html+jackwagon`?
|
||||
type: Option<String>,
|
||||
}
|
||||
|
||||
/*
|
||||
{
|
||||
this: "is an example object",
|
||||
text1: [{ body: "the most basic text block"}],
|
||||
text2: [
|
||||
{ body: "now with *formatting*" },
|
||||
{ body: "now with <em>formatting</em>", type: "text/html" },
|
||||
],
|
||||
text3: [
|
||||
{ body: "this may not be exposed in ui, but localization can exist (mostly for bots?)" },
|
||||
{ body: "(pretend i can speak another language)", lang: "another_LANG" },
|
||||
],
|
||||
}
|
||||
*/
|
||||
```
|
||||
|
||||
#### federation
|
||||
|
||||
prefix = `/_jackwagon/federation`
|
||||
|
||||
This is how servers federate with each other.
|
||||
|
||||
| method | path | description |
|
||||
|--------|------ |------ |
|
||||
| QUERY | /v1/info | Get public info about this server |
|
||||
| POST | /v1/send | Send new events (PDUs) |
|
||||
| POST | /v1/make-join | Ask a server to create a join event, since ours doesn't have room state yet |
|
||||
| POST | /v1/make-invite | Same as above for inviting other users |
|
||||
| POST | /v1/make-leave | Same as above for leave (rejecting invites, specifically) |
|
||||
| POST | /v1/make-knock | Same as above for knock |
|
||||
| POST | /v1/send-join | Send the join |
|
||||
| POST | /v1/send-invite | Send the invite |
|
||||
| POST | /v1/send-leave | Send the leave |
|
||||
| POST | /v1/send-knock | Send the knock |
|
||||
| GET | /v1/state/{roomId}/{eventId} | Get a snapshot of room state at a specific event id |
|
||||
| GET | /v1/state-ids/{roomId}/{eventId} | Like /state, but only the ids instead of full events |
|
||||
| GET | /v1/event/{roomId}/{eventId} | Get an event |
|
||||
| GET | /v1/event-auth/{roomId}/{eventId} | Get the full auth chain of an event |
|
||||
| POST | /v1/backfill/{roomId} | Fetch a range of history |
|
||||
| POST | /v1/get-missing-events/{roomId} | Fetch some specific events |
|
||||
| POST | /v1/keys/server | Fetch the server's signing key |
|
||||
| POST | /v1/keys/query | Ask the server for another server's signing key |
|
||||
| GET | /v1/hierarchy/{roomId} | Get a space's hierarchy |
|
||||
| GET | /v1/query/alias/{roomAlias} | Resolve a room alias |
|
||||
| GET | /v1/query/user/{userId} | Get a user's profile |
|
||||
| GET | /v1/query/devices/{userId} | Get a user's devices |
|
||||
|
||||
#### media
|
||||
|
||||
prefix = `/_jackwagon/media`
|
||||
|
||||
Specifically for media, which is transferred out of band. I really want
|
||||
to rework the `blobs` api, since I know it's possible to decentralize
|
||||
media but still allow streaming it with pubkeys...
|
||||
|
||||
| method | path | description |
|
||||
|--------|------ |------------- |
|
||||
| POST | /v1/blobs | Upload a blob |
|
||||
| GET | /v1/blobs/:id | Download a blob |
|
||||
| DELETE | /v1/blobs/:id | Delete a blob (only uploader and admins can use) |
|
||||
| GET | /v1/blobs/:id/thumbnail | Download a blob's server generate thumbnail |
|
||||
| POST | /v1/url | Generate a url preview |
|
||||
|
||||
#### appservice
|
||||
|
||||
prefix = `/_jackwagon/appservice`
|
||||
|
||||
APIs for appservices. Notably, this api exists *on the appservice*,
|
||||
not the homeserver and is used for the homeserver to communicate to
|
||||
the appservice.
|
||||
|
||||
Appservices can pass `?user_id` on any client-server api endpoint to
|
||||
masquerade as that user. They can also use `?timestamp` to set a custom
|
||||
origin_server_ts when sending events (though it doesn't rewrite the dag!).
|
||||
|
||||
| method | path | description |
|
||||
|--------|--------------------------|--------------------------------------------|
|
||||
| GET | /v1/health | A health check for the application service |
|
||||
| PUT | /v1/transactions/{txnId} | Receive some events |
|
||||
| GET | /v1/users/{userId} | Get or create a user's profile |
|
||||
| GET | /v1/aliases/{roomAlias} | Get or create a room |
|
||||
|
||||
#### admin
|
||||
|
||||
prefix = `/_jackwagon/admin`
|
||||
|
||||
APIs for administrating a server.
|
||||
|
||||
| method | path | description |
|
||||
|--------|-------------------------------------|-----------------------------------------------------|
|
||||
| GET | /v1/stats | Get statistics of the server |
|
||||
| GET | /v1/appservices | Get a list of appservices |
|
||||
| PUT | /v1/appservices/:id | Register or update an appservice |
|
||||
| GET | /v1/appservices/:id | Get an appservice's config |
|
||||
| DELETE | /v1/appservices/:id | Delete an appservice |
|
||||
| GET | /v1/users | Get a list of all known (or only ?local=true) users |
|
||||
| GET | /v1/users/{userId} | Get a user |
|
||||
| PUT | /v1/users/{userId} | Update a user |
|
||||
| GET | /v1/rooms | Get a list of rooms |
|
||||
| GET | /v1/rooms/{roomId} | Get a room |
|
||||
| PUT | /v1/rooms/{roomId} | Update a room |
|
||||
| POST | /v1/send-notice | Send a server notice |
|
||||
| POST | /v1/debug/get-pdu/{eventId} | Get a pdu |
|
||||
| POST | /v1/debug/get-auth/{eventId} | Get a pdu's auth chain |
|
||||
| POST | /v1/debug/get-extremities/{eventId} | Get a pdu's forward extremities |
|
||||
| POST | /v1/debug/sign | Sign json |
|
||||
| POST | /v1/debug/verify | Verify a pdu |
|
||||
|
||||
this should probably be migrated out of REST for bulk deactivation
|
||||
|
||||
```ts
|
||||
type AppserviceConfig = {
|
||||
description: string, // a human readable description
|
||||
as_token: string, // secret token the appservice uses to authenticate itself
|
||||
hs_token: string, // secret token the homeserver uses to authenticate itself
|
||||
url: string, // the url the homeserver uses to communicate with the appservice
|
||||
media: string, // the url the homeserver uses to communicate with the appservice
|
||||
namespaces: {
|
||||
users?: Array<{ regex: string }>,
|
||||
aliases?: Array<{ regex: string }>,
|
||||
}
|
||||
}
|
||||
|
||||
// quarantined users cannot dm people among other restrictions
|
||||
type UserConfig = {
|
||||
profile: UserProfile,
|
||||
state: "active" | "quarantined" | "readonly" | "disabled",
|
||||
};
|
||||
|
||||
// quarantined rooms cannot be joined by any new users (but existing members can stay), and will not show up in /hierarchy
|
||||
// exiled rooms cannot be interacted with at all, and any joined users will leave. the server may purge the state.
|
||||
type RoomConfig = {
|
||||
state: "active" | "quarantined" | "exiled",
|
||||
};
|
||||
```
|
||||
|
||||
### primitives
|
||||
|
||||
| name | format | description |
|
||||
|-|-|-|
|
||||
|room id|!opaque:domain.tld|A globally unique identifier for a room. The domain part is purely to ensure uniqueness, and is meaningless otherwise.|
|
||||
|user id|@name:domain.tld|A globally unique identifier for a user.|
|
||||
|event id|$opaque|A globally unique identifier for an event.|
|
||||
|
||||
### event struct
|
||||
|
||||
| field | type | description |
|
||||
|-|-|-|
|
||||
| event_id | event id | a unique identifier for this event |
|
||||
| room_id? | room id | the room this event originated from* |
|
||||
| content | any | arbitrary content; in practice, this probably matches the event type |
|
||||
| sender | user id | the sender of this event |
|
||||
| state_key? | string | if this is a state event |
|
||||
| type | string | the type of this event |
|
||||
| origin_server_ts | number | the timestamp at the server this event originated from |
|
||||
| unsigned | unsigned data | anything extra the server calculated |
|
||||
|
||||
*only present in some endpoints
|
||||
|
||||
### standard types
|
||||
|
||||
```ts
|
||||
// Text
|
||||
// a string is shorthand for a single TextPart of type text/plain
|
||||
type Text = string | Array<TextPart>;
|
||||
type TextPart = {
|
||||
body: string,
|
||||
lang?: string,
|
||||
type?: "text/plain" | "text/html" | string,
|
||||
};
|
||||
|
||||
type File = {
|
||||
url: /mxc/,
|
||||
info: {
|
||||
alt: Text,
|
||||
type: number, // mime type
|
||||
size: number, // in bytes
|
||||
width?: number, // type = image, video
|
||||
height?: number, // type = image, video
|
||||
duration?: number, // type = audio, video
|
||||
},
|
||||
};
|
||||
|
||||
// Events sent to clients
|
||||
type ClientEvent = {
|
||||
event_id: EventId,
|
||||
room_id?: RoomId, // removed in /sync, since the room id is sent anyway
|
||||
content: any,
|
||||
sender: UserId,
|
||||
state_key?: string,
|
||||
type: string,
|
||||
origin_server_ts: number,
|
||||
unsigned: UnsignedData,
|
||||
};
|
||||
|
||||
type ServerEvent = {
|
||||
auth_events: Array<EventId>,
|
||||
content: any,
|
||||
hashes: any,
|
||||
origin_server_ts: number,
|
||||
prev_events: Array<EventId>,
|
||||
room_id: RoomId,
|
||||
sender: UserId,
|
||||
signatures: any,
|
||||
state_key?: string,
|
||||
type: string,
|
||||
// unsigned: UnsignedData,
|
||||
};
|
||||
|
||||
type UnsignedData = {
|
||||
prev_content?: any,
|
||||
redacted_because?: ClientEvent,
|
||||
transaction_id?: string,
|
||||
relations: {
|
||||
"m.thread": {
|
||||
count: number,
|
||||
participation: "participation",
|
||||
},
|
||||
"m.replace": {
|
||||
latest_event: ClientEvent,
|
||||
},
|
||||
"m.plus": {
|
||||
count: number,
|
||||
},
|
||||
"m.annotation": {
|
||||
annotations: Array<ClientEvent>,
|
||||
count: number,
|
||||
},
|
||||
},
|
||||
unreads: Unreads,
|
||||
// per-event account data?
|
||||
// account_data: any,
|
||||
};
|
||||
```
|
11
src/data.md
Normal file
11
src/data.md
Normal file
|
@ -0,0 +1,11 @@
|
|||
# list of data structures
|
||||
|
||||
## events
|
||||
|
||||
`m.message`
|
||||
|
||||
```js
|
||||
{
|
||||
"m.text": "bar"
|
||||
}
|
||||
```
|
31
src/design.md
Normal file
31
src/design.md
Normal file
|
@ -0,0 +1,31 @@
|
|||
# design
|
||||
|
||||
there are some design choices
|
||||
|
||||
## faq
|
||||
|
||||
### no main instance
|
||||
|
||||
i'm a programmer not a moderator and don't want to deal with that
|
||||
|
||||
yeah it will hurt adoption, but the goal isn't to amass a ton of users
|
||||
|
||||
## ui
|
||||
|
||||
### why thread only
|
||||
|
||||
it strikes a nice balance between async and synchronous.
|
||||
|
||||
i *may* add fully async tree-style reply style rooms.
|
||||
|
||||
fully synchronous chat is not planned.
|
||||
|
||||
## technical
|
||||
|
||||
### the api is not restful
|
||||
|
||||
it uses batching
|
||||
|
||||
### why long polling
|
||||
|
||||
http/3
|
68
src/install.md
Normal file
68
src/install.md
Normal file
|
@ -0,0 +1,68 @@
|
|||
# install
|
||||
|
||||
## docker compose
|
||||
|
||||
it's planned to be modular and composed of multiple parts
|
||||
|
||||
```yaml
|
||||
version: '3'
|
||||
|
||||
services:
|
||||
# This is the main jackwagon server
|
||||
backend:
|
||||
# image: matrixconduit/matrix-conduit:latest
|
||||
build:
|
||||
context: .
|
||||
args:
|
||||
CREATED: '2021-03-16T08:18:27Z'
|
||||
VERSION: '0.1.0'
|
||||
LOCAL: 'false'
|
||||
GIT_REF: origin/master
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- 8448:6167
|
||||
volumes:
|
||||
- db:/var/lib/matrix-conduit/
|
||||
environment:
|
||||
CONDUIT_SERVER_NAME: your.server.name # EDIT THIS
|
||||
CONDUIT_DATABASE_PATH: /var/lib/matrix-conduit/
|
||||
CONDUIT_DATABASE_BACKEND: rocksdb
|
||||
CONDUIT_PORT: 6167
|
||||
CONDUIT_MAX_REQUEST_SIZE: 20_000_000 # in bytes, ~20 MB
|
||||
CONDUIT_ALLOW_REGISTRATION: 'true'
|
||||
CONDUIT_ALLOW_FEDERATION: 'true'
|
||||
CONDUIT_ALLOW_CHECK_FOR_UPDATES: 'true'
|
||||
CONDUIT_TRUSTED_SERVERS: '["matrix.org"]'
|
||||
#CONDUIT_MAX_CONCURRENT_REQUESTS: 100
|
||||
#CONDUIT_LOG: warn,rocket=off,_=off,sled=off
|
||||
CONDUIT_ADDRESS: 0.0.0.0
|
||||
CONDUIT_CONFIG: '' # Ignore this
|
||||
|
||||
# the main frontend
|
||||
frontend:
|
||||
image: vectorim/element-web:latest
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- 8009:80
|
||||
|
||||
# handle turn/voip, you can also run this on other servers
|
||||
coturn:
|
||||
image: coturn/coturn
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- 3478:3478
|
||||
- 3478:3478/udp
|
||||
- 5349:5349
|
||||
- 5349:5349/udp
|
||||
- 49152-65535:49152-65535/udp
|
||||
|
||||
# database
|
||||
postgres:
|
||||
image: postgres
|
||||
restart: always
|
||||
environment:
|
||||
POSTGRES_PASSWORD: example
|
||||
|
||||
volumes:
|
||||
db:
|
||||
```
|
65
src/syncing.md
Normal file
65
src/syncing.md
Normal file
|
@ -0,0 +1,65 @@
|
|||
# how to sync
|
||||
|
||||
call `POST /v1/sync` (or `QUERY /v1/sync`?) in a loop
|
||||
|
||||
## goals
|
||||
|
||||
1. Be able to incrementally update a local offline copy
|
||||
2. Be able to fetch only what is needed
|
||||
|
||||
- query and update state, threads, members
|
||||
|
||||
todo: write out
|
||||
|
||||
```ts
|
||||
type Query = {
|
||||
state: Array<[string, string]>, // (event_type, state_key)
|
||||
subscribe: boolean,
|
||||
threads: {
|
||||
limit: number,
|
||||
timeline_limit: number,
|
||||
},
|
||||
};
|
||||
|
||||
type Request = {
|
||||
conn_id: string,
|
||||
pos: string,
|
||||
delta_token: string,
|
||||
|
||||
lists: Record<string, null | {
|
||||
limit: number,
|
||||
filters: {
|
||||
types: Array<string>,
|
||||
purposes: Array<string>,
|
||||
spaces: Array<RoomId>,
|
||||
tombstoned: Array<RoomId>,
|
||||
},
|
||||
} & Query>,
|
||||
|
||||
rooms: Record<RoomId, null | Query>,
|
||||
|
||||
extensions: {
|
||||
presence: {
|
||||
enabled: true,
|
||||
},
|
||||
account_data: {
|
||||
enabled: true,
|
||||
types?: Array<string>,
|
||||
rooms?: Array<RoomId>,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
type Reponse = {
|
||||
lists: Record<string, null | {
|
||||
count: number,
|
||||
}>,
|
||||
|
||||
rooms: Record<RoomId, {
|
||||
initial?: true,
|
||||
state: Array<StateEvent>,
|
||||
threads: Array<Event>,
|
||||
timeline: Array<Event>,
|
||||
}>,
|
||||
}
|
||||
```
|
Loading…
Reference in a new issue