sync-v3

Run an experimental sync v3 server using an existing Matrix account. This is possible because, for the most part, v3 sync is a strict subset of v2 sync.

Usage

$ createdb syncv3
$ go build ./cmd/syncv3
$ ./syncv3 -server "https://matrix-client.matrix.org" -db "user=$(whoami) dbname=syncv3 sslmode=disable"

Then issue Sync v3 calls.

API overview

Sync v3 is implemented used POST requests. The URI is mostly constant:

  • Authorization: Bearer MDA... : The access token for your account. ?access_token= is unsupported.
  • /_matrix/client/v3/sync : The path. Constant.
  • ?since= : The sync v3 token. To begin a new sync session, don't supply a since value.

The body of the request consists of different streams:

{
    "typing": {
        ...
    },
    "room_members": {
        ...
    },
    "to_device": {
        ...
    },
    ...
}

Specify as many streams as you are interested in. For example, if you are on a room member page for Matrix HQ, you probably only need the room_members stream.

Each stream has request parameters which control what data gets returned, for example typing has a room_id which specifies which room to track typing notifications in. These request parameters are given inside the JSON object for the respective stream. For example:

"typing": {
    "room_id": "!foo:bar"
}

These parameters are "sticky" and are remembered across requests so you don't need to constantly add them and use up needless bandwidth. The server remembers this by associating a unique session ID to the since token returned, so make sure you keep using the same since token.

A session is created when a client calls sync v3 without a since token. Just like how a user can have multiple devices, a device can have multiple sessions. to_device messages are only purged from the server when ALL sessions for that device have received the message. This resolves a longstanding iOS E2EE bug whereby a device has 2 sync streams which are implicitly racing with each other and clearing to_device messages, when both streams actually want the message. Sessions can be deleted at the whim of the server (e.g to save resources). When this happens, the response will be:

{
    "errcode": "M_UNKNOWN_SYNC_SESSION",
    "error": "Session unknown or expired"
}

Clients MUST call /sync again without a ?since= value when this happens in order to make a new session.

Responses are split stream-by-stream:

{
    "typing": {
        "events": [ Typing EDUs ]
    },
    "room_members": {
        "events": [ Room Member Events]
    },
    "next": "sync_v3_token"
}

Some streams are paginatable. Paginatable streams accept a p JSON object which contains pagination parameters. The p object looks like:

{
    "room_members": {
        "p": {
            "limit": 10,
            "sort": "enum_value:_the_sort_order_varies_by_stream"
        }
    }
}

Servers can also return a p JSON object in the stream response which contains the pagination parameters it is going to use. This allows servers to restrict the response to conserve resources (e.g. reducing the limit value, modifying the sort order if it's unknown). This is called negotiation and is frequently used by streams.

When paginated streams are returned, the next page of results are contained within p:

{
    "room_members": {
        "p": {
            "next": "$token_to_use_for_next_page"
        },
        "events": [ Room Member Events ]
    },
    "next": "$token_to_use_to_get_new_events"
}

To visualise this, the value of ?since= is a snapshot of the room, and p.next paginates through this snapshot. This means that when paginating you MUST NOT advance your ?since= value. Clients can request the next page by specifying since inside p:

{
    "room_members": {
        "p": {
            "since": "$token_from_p_next"
        }
    }
}

Clients are not required to paginate through all entries in a paginated stream. If you are only interested in the first 5 room members for example, then you can set limit: 5 and ignore p.next. In order to differentiate clients which are keeping up-to-date with room members and those who are just interested in the first page of results, clients MUST specify p.since = "1" to indicate the first page of results. This makes "1" a sentinel constant value in pagination to indicate "the first page of results". If you don't do this, the server will just send back deltas from the last since value, which won't necessarily contain the first 5 members of your chosen sort order (e.g alphabetically).

Streams

Typing Stream

Request Parameters:

  • room_id (string): The room ID to track typing notifications in.

Response fields:

  • events ([]EDU): A list of typing EDUs.

To Device Stream

TODO

Room Member Stream

TODO

Room Stream

TODO

Room List Stream

TODO

Description
Proxy implementation of MSC3575's sync protocol.
Readme Apache-2.0 4.7 MiB
Languages
Go 96.2%
JavaScript 3.4%
HTML 0.2%
CSS 0.1%