2021-01-03 23:41:13 +01:00
|
|
|
# Galène's protocol
|
|
|
|
|
|
|
|
## Data structures
|
|
|
|
|
|
|
|
### Group
|
|
|
|
|
|
|
|
A group is a set of clients. It is identified by a human-readable name
|
2021-10-28 20:01:11 +02:00
|
|
|
that must not start or end with a slash "`/`", must not start with
|
|
|
|
a period "`.`", and must not contain the substrings "`/../`" or "`/./`".
|
2021-01-03 23:41:13 +01:00
|
|
|
|
|
|
|
### Client
|
|
|
|
|
|
|
|
A client is a peer that may originate offers and chat messages. It is
|
|
|
|
identified by an id, an opaque string that is assumed to be unique. Peers
|
|
|
|
that do not originate messages (servers) do not need to be assigned an id.
|
|
|
|
|
|
|
|
### Stream
|
|
|
|
|
|
|
|
A stream is a set of related tracks. It is identified by an id, an opaque
|
2021-01-05 22:10:26 +01:00
|
|
|
string. Streams in Galène are unidirectional. A stream is carried by
|
2021-01-03 23:41:13 +01:00
|
|
|
exactly one peer connection (PC) (multiple streams in a single PC are not
|
|
|
|
allowed). The offerer is also the RTP sender (i.e. all tracks sent by the
|
|
|
|
offerer are of type `sendonly`).
|
|
|
|
|
2021-10-26 22:22:48 +02:00
|
|
|
Galène uses a symmetric, asynchronous protocol. In client-server
|
|
|
|
usage, some messages are only sent in the client to server or in the
|
|
|
|
server to client direction.
|
|
|
|
|
|
|
|
## Before connecting
|
|
|
|
|
|
|
|
Before it connects and joins a group, a client may perform an HTTP GET
|
|
|
|
request on the URL `/public-groups.json`. This yields a JSON array of
|
|
|
|
objects, one for each group that has been marked public in its
|
|
|
|
configuration file. Each object has the following fields:
|
|
|
|
|
|
|
|
- `name`: the group's name
|
|
|
|
- `displayName` (optional): a longer version of the name used for display;
|
|
|
|
- `description` (optional): a user-readable description.
|
|
|
|
- `locked`: true if the group is locked;
|
|
|
|
- `clientCount`: the number of clients currently in the group.
|
|
|
|
|
|
|
|
A client may also fetch the URL `/group/name/.status.json` to retrieve the
|
|
|
|
status of a single group. If the group has not been marked as public,
|
|
|
|
then the fields `locked` and `clientCount` are omitted.
|
|
|
|
|
|
|
|
## Connecting
|
|
|
|
|
|
|
|
The client connects to the websocket at `/ws`. Galene uses a symmetric,
|
|
|
|
asynchronous protocol: there are no requests and responses, and most
|
|
|
|
messages may be sent by either peer.
|
|
|
|
|
|
|
|
## Message syntax
|
|
|
|
|
|
|
|
All messages are sent as JSON objects. All fields except `type` are
|
|
|
|
optional; however, there are some fields that are common across multiple
|
|
|
|
message types:
|
|
|
|
|
|
|
|
- `type`, the type of the message;
|
|
|
|
- `kind`, the subtype of the message;
|
|
|
|
- `id`, the id of the object being manipulated;
|
|
|
|
- `source`, the client-id of the originating client;
|
|
|
|
- `username`, the username of the originating client;
|
|
|
|
- `dest`, the client-id of the destination client;
|
|
|
|
- `privileged`, set by the server to indicate that the originating client
|
|
|
|
had the `op` privilege at the time when it sent the message.
|
|
|
|
|
2021-01-03 23:41:13 +01:00
|
|
|
## Establishing and maintaining a connection
|
|
|
|
|
|
|
|
The peer establishing the connection (the WebSocket client) sends
|
|
|
|
a handshake message. The server replies with another handshake message.
|
|
|
|
The client may wait for the server's handshake, or it may immediately
|
|
|
|
start pipelining messages to the server.
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
{
|
|
|
|
type: 'handshake',
|
|
|
|
id: id
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
If the field `id` is absent, then the peer doesn't originate streams (it
|
|
|
|
is a server).
|
|
|
|
|
|
|
|
A peer may, at any time, send a `ping` message.
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
{
|
|
|
|
type: 'ping'
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
The receiving peer must reply with a `pong` message within 30s.
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
{
|
|
|
|
type: 'pong'
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
## Joining and leaving
|
|
|
|
|
|
|
|
The `join` message requests that the sender join or leave a group:
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
{
|
|
|
|
type: 'join',
|
|
|
|
kind: 'join' or 'leave',
|
|
|
|
group: group,
|
|
|
|
username: username,
|
|
|
|
password: password
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
When the sender has effectively joined the group, the peer will send
|
|
|
|
a 'joined' message of kind 'join'; it may then send a 'joined' message of
|
|
|
|
kind 'change' at any time, in order to inform the client of a change in
|
2021-01-05 22:10:26 +01:00
|
|
|
its permissions or in the recommended RTC configuration.
|
2021-01-03 23:41:13 +01:00
|
|
|
|
|
|
|
```javascript
|
|
|
|
{
|
|
|
|
type: 'joined',
|
|
|
|
kind: 'join' or 'fail' or 'change' or 'leave',
|
|
|
|
group: group,
|
|
|
|
username: username,
|
|
|
|
permissions: permissions,
|
2021-07-16 19:41:00 +02:00
|
|
|
status: status,
|
2021-01-03 23:41:13 +01:00
|
|
|
rtcConfiguration: RTCConfiguration
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
The `permissions` field is an array of strings that may contain the values
|
2021-07-16 19:41:00 +02:00
|
|
|
`present`, `op` and `record`. The `status` field is a dictionary that
|
|
|
|
contains random information that can be usefully displayed in the user
|
|
|
|
interface.
|
2021-01-03 23:41:13 +01:00
|
|
|
|
|
|
|
## Maintaining group membership
|
|
|
|
|
|
|
|
Whenever a user joins or leaves a group, the server will send all other
|
|
|
|
users a `user` message:
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
{
|
|
|
|
type: 'user',
|
2021-04-27 18:58:21 +02:00
|
|
|
kind: 'add' or 'change' or 'delete',
|
2021-01-03 23:41:13 +01:00
|
|
|
id: id,
|
2021-04-27 18:58:21 +02:00
|
|
|
username: username,
|
2021-04-28 14:45:45 +02:00
|
|
|
permissions: permissions,
|
|
|
|
status: status
|
2021-01-03 23:41:13 +01:00
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
## Requesting streams
|
|
|
|
|
|
|
|
A peer must explicitly request the streams that it wants to receive.
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
{
|
|
|
|
type: 'request',
|
|
|
|
request: requested
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
The field `request` is a dictionary that maps the labels of requested
|
2021-04-29 17:03:25 +02:00
|
|
|
streams to a list containing either 'audio', or one of 'video' or
|
|
|
|
'video-low'. The empty key `''` serves as default. For example:
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
{
|
|
|
|
type: 'request',
|
|
|
|
request: {
|
|
|
|
camera: ['audio', 'video-low'],
|
|
|
|
'': ['audio', 'video']
|
|
|
|
}
|
|
|
|
}
|
2021-05-22 16:58:09 +02:00
|
|
|
```
|
2021-01-03 23:41:13 +01:00
|
|
|
|
|
|
|
## Pushing streams
|
|
|
|
|
|
|
|
A stream is created by the sender with the `offer` message:
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
{
|
|
|
|
type: 'offer',
|
|
|
|
id: id,
|
2021-04-28 17:00:50 +02:00
|
|
|
label: label,
|
2021-01-31 19:00:09 +01:00
|
|
|
replace: id,
|
2021-01-03 23:41:13 +01:00
|
|
|
source: source-id,
|
|
|
|
username: username,
|
2021-05-22 16:58:09 +02:00
|
|
|
sdp: sdp
|
2021-01-03 23:41:13 +01:00
|
|
|
}
|
|
|
|
```
|
|
|
|
|
2021-05-22 16:58:09 +02:00
|
|
|
If a stream with the same id exists, then this is a renegotiation;
|
2021-01-31 19:00:09 +01:00
|
|
|
otherwise this message creates a new stream. If the field `replace` is
|
|
|
|
not empty, then this request additionally requests that an existing stream
|
2021-04-29 17:03:25 +02:00
|
|
|
with the given id should be closed, and the new stream should replace it;
|
|
|
|
this is used most notably when changing the simulcast envelope.
|
2021-01-03 23:41:13 +01:00
|
|
|
|
2021-04-29 17:03:25 +02:00
|
|
|
The field `label` is one of `camera`, `screenshare` or `video`, and will
|
|
|
|
be matched against the keys sent by the receiver in their `request` message.
|
2021-04-28 17:00:50 +02:00
|
|
|
|
2021-01-03 23:41:13 +01:00
|
|
|
The field `sdp` contains the raw SDP string (i.e. the `sdp` field of
|
|
|
|
a JSEP session description). Galène will interpret the `nack`,
|
|
|
|
`nack pli`, `ccm fir` and `goog-remb` RTCP feedback types, and act
|
|
|
|
accordingly.
|
|
|
|
|
2021-04-29 17:03:25 +02:00
|
|
|
The sender may either send a single stream per media section in the SDP,
|
|
|
|
or use rid-based simulcasting. In the latter case, it should send two
|
|
|
|
video streams, one with rid 'h' and high throughput, and one with rid 'l'
|
|
|
|
and throughput limited to roughly 100kbit/s.
|
|
|
|
|
2021-01-03 23:41:13 +01:00
|
|
|
The receiver may either abort the stream immediately (see below), or send
|
|
|
|
an answer.
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
{
|
|
|
|
type: 'answer',
|
|
|
|
id: id,
|
|
|
|
sdp: SDP
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
2021-01-05 22:10:26 +01:00
|
|
|
Both peers may then trickle ICE candidates with `ice` messages.
|
2021-01-03 23:41:13 +01:00
|
|
|
|
|
|
|
```javascript
|
|
|
|
{
|
|
|
|
type: 'ice',
|
2021-03-29 15:29:09 +02:00
|
|
|
id: id,
|
2021-01-03 23:41:13 +01:00
|
|
|
candidate: candidate
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
The answerer may request a new offer of kind `renegotiate` and an ICE
|
|
|
|
restart by sending a `renegotiate` message:
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
{
|
|
|
|
type: 'renegotiate',
|
|
|
|
id: id
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
2021-05-14 18:39:39 +02:00
|
|
|
At any time after answering, the client may change the set of streams
|
|
|
|
being offered by sending a 'requestStream' request:
|
|
|
|
```javascript
|
|
|
|
{
|
2021-05-22 16:58:09 +02:00
|
|
|
type: 'requestStream'
|
2021-05-14 18:39:39 +02:00
|
|
|
id: id,
|
|
|
|
request: [audio, video]
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
2021-01-03 23:41:13 +01:00
|
|
|
## Closing streams
|
|
|
|
|
|
|
|
The offerer may close a stream at any time by sending a `close` message.
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
{
|
|
|
|
type: 'close',
|
|
|
|
id: id
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
The answerer may request that the offerer close a stream by sending an
|
|
|
|
`abort` message.
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
{
|
|
|
|
type: 'abort',
|
|
|
|
id: id
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
The stream will not be effectively closed until the offerer sends
|
|
|
|
a matching `close`.
|
|
|
|
|
|
|
|
## Sending messages
|
|
|
|
|
|
|
|
A chat message may be sent using a `chat` message.
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
{
|
|
|
|
type: 'chat',
|
|
|
|
kind: '' or 'me',
|
|
|
|
source: source-id,
|
|
|
|
username: username,
|
|
|
|
dest: dest-id,
|
|
|
|
privileged: boolean,
|
2021-01-11 16:22:04 +01:00
|
|
|
noecho: false,
|
2021-01-03 23:41:13 +01:00
|
|
|
value: message
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
If `dest` is empty, the message is a broadcast message, destined to all of
|
|
|
|
the clients in the group. If `source` is empty, then the message was
|
|
|
|
originated by the server. The message is forwarded by the server without
|
|
|
|
interpretation, the server only validates that the `source` and `username`
|
|
|
|
fields are authentic. The field `privileged` is set to true by the server
|
2021-01-11 16:22:04 +01:00
|
|
|
if the message was originated by a client with the `op` permission. The
|
|
|
|
field `noecho` is set by the client if it doesn't wish to receive a copy
|
|
|
|
of its own message.
|
2021-01-03 23:41:13 +01:00
|
|
|
|
2021-07-31 14:42:26 +02:00
|
|
|
The `chathistory` message is similar to the `chat` message, but carries
|
|
|
|
a message taken from the chat history. Most clients should treat
|
|
|
|
`chathistory` similarly to `chat`.
|
|
|
|
|
2021-01-03 23:41:13 +01:00
|
|
|
A user message is similar to a chat message, but is not conserved in the
|
|
|
|
chat history, and is not expected to contain user-visible content.
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
{
|
|
|
|
type: 'usermessage',
|
|
|
|
kind: kind,
|
|
|
|
source: source-id,
|
|
|
|
username: username,
|
|
|
|
dest: dest-id,
|
|
|
|
privileged: boolean,
|
|
|
|
value: value
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
2021-07-30 19:38:02 +02:00
|
|
|
Currently defined kinds include `error`, `warning`, `info`, `kicked`,
|
|
|
|
`clearchat` (not to be confused with the `clearchat` group action), and
|
|
|
|
`mute`.
|
2021-01-03 23:41:13 +01:00
|
|
|
|
|
|
|
A user action requests that the server act upon a user.
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
{
|
|
|
|
type: 'useraction',
|
|
|
|
kind: kind,
|
|
|
|
source: source-id,
|
|
|
|
username: username,
|
|
|
|
dest: dest-id,
|
|
|
|
value: value
|
|
|
|
}
|
|
|
|
```
|
2021-04-28 14:45:45 +02:00
|
|
|
Currently defined kinds include `op`, `unop`, `present`, `unpresent`,
|
|
|
|
`kick` and `setstatus`.
|
2021-01-03 23:41:13 +01:00
|
|
|
|
|
|
|
Finally, a group action requests that the server act on the current group.
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
{
|
|
|
|
type: 'groupaction',
|
|
|
|
kind: kind,
|
|
|
|
source: source-id,
|
|
|
|
username: username,
|
|
|
|
value: value
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
Currently defined kinds include `clearchat` (not to be confused with the
|
|
|
|
`clearchat` user message), `lock`, `unlock`, `record`, `unrecord` and
|
|
|
|
`subgroups`.
|