Skip to content
This repository has been archived by the owner on Jul 21, 2022. It is now read-only.

Latest commit

 

History

History
556 lines (424 loc) · 11.9 KB

protocol.md

File metadata and controls

556 lines (424 loc) · 11.9 KB

Melodious Chat Protocol (websocket)

Please note that this project is WIP and the protocol is subject to change.

Communication is established over a WebSocket connection with JSON messages used to exchange data.
The "melodious" subprotocol MUST be specified in the request header, otherwise the server MUST return an HTTP response with code 400 (Bad Request).

Messages

Each message is a JSON document which necessarily has type field which describes message type

Each message MAY have an _id field. All responses to such message MUST contain the same _id.
If length of the id is more than 64 characters, then only first 64 characters are used.

server-info (sent by server)

{
    "type": "server-info",
    "server-name": "<string>",
    "version": "<string>"
}

Describes the server's info.
Sent to client on connect.

quit (sent by server and client)

{
    "type": "quit",
    "message": "<string>",
}

Sent by both server and client to indicate that one of them is no longer interested in the connection.
After sending/receiving server and client MUST close the connection.

fatal (sent by server)

{
    "type": "fatal",
    "message": "<string>",
}

Send by server to indicate that a fatal error has occured and the connection must be closed.
Server MUST close the connection after sending this message.

note (sent by server)

{
    "type": "note",
    "message": "<string>"
}

ok, fail (sent by server)

{
    "type": "ok",
    "message": "<string>"
}
{
    "type": "fail",
    "message": "<string>"
}

These messages are used to notify user about results of operations started by the user.

register

{
    "type": "register",
    "name": "<string>",
    "pass": "<string>"
}

name: Username. MUST match [a-zA-Z0-9\-_\.]{3,32} regex
pass: Password. MUST match [a-zA-Z0-9\-_\.]{3,32} regex

Sent by client: Registers the client on the server.
Sent by server: Indicates a user register event (no "pass" field sent).
If user with username name already exists, server MUST send a fatal message.

Server MAY introduce additional protection like IP duplication protection.

If this is a first user ever registered, server MUST give that user admin permissions and send him a corresponding note message.

It is recommended that server stores hash sum of the hash sum of the password to prevent heavy damage on database leak.

After registering user MUST be treated as logged in.

login

{
    "type": "login",
    "name": "<string>",
    "pass": "<string>"
}

name: Username. MUST match [a-zA-Z0-9\-_\.]{3,32} regex
pass: Password. MUST match [a-zA-Z0-9\-_\.]{3,32} regex

Sent by client: Logs the client in.
Sent by server: Indicates a login (online) event (no "pass" field sent).

If user with username name does not exist or SHA256 hash/checksum hash is invalid, server MUST send a fatal message.

Server MAY introduce additional protection like banning users from connecting.

It is recommended that server stores hash sum of the hash sum of the password to prevent heavy damage on database leak.

new-channel

{
    "type": "new-channel",
    "name": "<string>",
    "topic": "<string>"
}

User needs perms.manage-channels flag or owner status to do that.

name: channel name; maximum 32 characters

Sent by client: Creates a new channel. If such channel already exists or user is not an owner, server MUST return a fail message.
Sent by server: Notifies about a new channel.

delete-channel

{
    "type": "delete-channel",
    "name": "<string>"
}

User needs perms.manage-channels flag or owner status to do that.

name: channel name; maximum 32 characters

Sent by client: Deletes a channel.
Sent by server: Notifies about a deleted channel. May mention non-existing channel.

channel-topic (sent by client)

{
    "type": "channel-topic",
    "name": "<string>",
    "topic": "<string>"
}

User needs perms.manage-channels flag or owner status to do that.

name: channel name; maximum 32 characters
topic: channel topic message; maximum 1024 characters

Changes a channel's topic.

subscribe (sent by client)

{
    "type": "subscribe",
    "name": "<string>",
    "subbed": <bool>
}

User needs perms.subscribe flag or owner status to do that.

name: channel name; maximum 32 characters
subbed: true or false maps to subscribed or unsubscribed respectively

(Un)Subscribes to a channel. "post-message" (below) messages will be sent to the client by the server from other clients accordingly.

post-message

Client:

{
    "type": "post-message",
    "content": "<string>",
    "channel": "<string>"
}

Server:

{
    "type": "post-message",
    "message": {
        "content": "<string>",
        "pings": ["<string>", ...],
        "id": <int>,
        "timestamp": "string",
        "author": "<string>",
        "author_id": <int>
    },
    "channel": "<string>"
}

User needs perms.post-message flag or owner status to do that.

content: message contents; maximum 2048 characters
channel: channel name to send the message to or the channel it was received from
author: username of the user who sent this message
pings: usernames of people that were mentioned in the message
id: message ID
timestamp: ISO 8601 timestamp
author: username of the user who sent the message
author_id: user's ID who sent the message

Sent by client: Posts a message in a specific channel (the "author" field does not need to be sent).
Sent by server: Notifies about a sent message in a specific channel.

get-messages (sent by client)

{
    "type": "get-messages",
    "channel-id": <int>,
    "message-id": <int>,
    "amount": <int>
}

User needs perms.get-messages flag or owner status to do that.

get-messages-result (sent by server)

{
    "type": "get-messages-result",
    "messages": [{
        "content": "<string>",
        "pings": ["<string>", ...],
        "id": <int>,
        "timestamp": "<string>",
        "author": "<string>",
        "author_id": <int>
    }, ...]
}

Sent by client: requests messages from the server.
Sent by server: returns a list of messages.

list-channels

{
    "type": "list-channels",
    "channels": [{
        "id": <int>,
        "name": "<string>",
        "topic": "<string>"
    }, ...]
}

User needs perms.list-channels flag or owner status to do that.

channels: an array of channel objects

Sent by client: Tells the server to fetch all channels that exist (the "channels" field does not need to be sent).
Sent by server: Returns the client an array of channels

list-users

{
    "type": "list-users",
    "users": [
        {
            "user": {
                "id": <int>,
                "username": "<string>",
                "owner": <bool>
            },
            "online": <bool>
        },
        ...
    ]
}

User needs perms.list-users flag or owner status to do that.

users: an array:
user: an user.
online: whether or not the user is connected to the server.

Sent by client: Tells the server to fetch all users that are registered (the "users" field does not need to be sent).
Sent by server: Returns the client an array of users with their status of connection.

user-quit (sent by server)

{
    "type": "user-quit",
    "username": "<string>"
}

username: user's name

Indicates a user disconnect (offline) event.

kick

{
    "type": "kick",
    "id": <int>, 
    "username": "<string>",
    "ban": <bool>
}

id: user ID
username: user's name
ban: whether or not to set the user's banned flag to true

Sent by client: kicks and optionally bans a user. You MUSTN'T have both id and username fields.
Sent by server: indicates a user ban event. Sent ONLY IF ban is true.

The kicked user will be logged off with a user-quit event.

new-group (sent by client)

{
    "type": "new-group",
    "name": "<string>"
}

name: group name

Creates a new group with a specified name.

delete-group (sent by client)

{
    "type": "new-group",
    "id": <int>
}

id: group ID

Deletes a group with a specified ID.

set-flag (sent by client)

{
    "type": "set-flag",
    "group": "<string>",
    "name": "<string>",
    "flag": {
        "<any-json>": "<here>",
        ...
    }
}

group: group name
name: flag name
flag: any JSON for additional data

Sets/creates a flag for the specified group with optional additional data. Required for setting up permissions for a group.

delete-flag (sent by client)

{
    "type": "set-flag",
    "group": "<string>",
    "name": "<string>"
}

group: group name
name: flag name

Removes/deletes a flag from the specified group.

typing

{
    "type": "typing",
    "channel": "<string>",
    "username": "<string>"
}

channel: channel name
username: user's name

Sent by client: sends a typing status to a channel (the "username" field does not need to be sent).
Sent by server: indicates a typing event from someone.

new-group-holder (sent by client)

{
    "type": "new-group-holder",
    "group": "<string>",
    "user": "<string>",
    "channel": "<string>"
}

group: group name
user: user's name
channel: channel name

Makes a link (called a group holder) of a group and a user and/or a channel.

The logic is following:
If both user and channel ids are empty, then the group is assigned to everyone (ala Discord's @everyone).
If user is set only, then it'll assign that group to that user with these permissions on all channels.
If channel is set only, then it'll get flags/permissions from that group and set them up for the specified channel.
If both user and channel are set, then it'll get the permissions from that group and set them up for the individial user on that specified channel.

delete-group-holder (sent by client)

{
    "type": "delete-group-holder",
    "id": <int>
}

id: group holder id

Removes/deletes an already made link (group holder).

get-group-holders

{
    "type": "get-group-holders",
    "group-holders": [{
        "id": <int>,
        "group": "<string>",
        "user": "<string>",
        "channel": "<string>"
    }, ...]
}

Sent by client: requests a list of group holders from the server (the "group-holders" field does not need to be sent).
Sent by server: returns a list of group holders.

ping (sent by server)

{
    "type": "ping",
    "message": {
        "content": "<string>",
        "pings": ["<string>", ...],
        "id": <int>,
        "timestamp": "string",
        "author": "<string>",
        "author_id": <int>
    },
    "channel": "<string>"
}

message: a message object
channel: name of the channel it's coming from

Pings/mentions a mentioned user when the author sends a post-message event if message content contains a mention in the format of <@USERID>, regardless of the pinged/metioned user's subscription status.

delete-message (sent by client)

{
    "type": "delete-message",
    "id": <int>
}

id: message id

Deletes a message with a specified id permanently.

get-groups

{
    "type": "get-groups",
    "groups": [{
        "id": <int>,
        "name": "<string>"
    }, ...]
}

groups: an array of group objects
id: group ID
name: group name

Sent by client: requests a list of groups (the "group" field does not need to be sent).
Sent by server: returns a list of groups.

get-flags

Client:

{
    "type": "get-flags",
    "group-id": <int>
}

Server:

{
    "type": "get-flags",
    "flags": [{
        "id": <int>,
        "group": "<string>",
        "name": "<string>",
        "flag": {
            "<any-json>": "<here>",
            ...
        }
    }, ...]
}

Sent by client: requests a list of flags of the specified group (by its ID).
Sent by server: returns a list of flags.