Overview

For the source code refer to the official Gitlab Repository

Introduction

The Freechat protocol is based on HTTP and currently outlines a json-based API hereafter referred to as just, the “JSON API”. Due to the standards already in place with HTTP and JSON it is safe to assume that this protocol be more of an outline for what HTTP endpoints to have and what kind of data is to be expected in exchanges.

Connections

Most servers will likely run on one of three ports, 80 for basic HTTP, 443 for secure HTTP traffic, or 4536 for most others. If using a special port then servers should expect user applications to specify the port.

For connections over SSH or other protocols it really is up to server owners to let new users know about this important detail. SSH based servers are not discouraged, in fact they are highly encouraged to exist and operate :^)

Security

DO NOT STORE PLAINTEXT SECRETS. For developers that aren’t rarted this isn’t hard but if you think about doing this, don’t.

With the secret value compromised there is nothing you can do(that I encourage anyways) to tell if that account has been stolen. If for some reason you(an admin) believes an account has been stolen, then lock the account by removing its permissions.

JSON Web Tokens are used as a kind of session/cookie system as user applications are not assumed to be browsers ever. Freechat servers do not assume user apps to be browser to give people maximum freedom in both application choice and developers freedom to use whatever hipster libraries/frameworks languages they want.

  • JWT’s should never last more than 1 week

    • 24 hours is fine but they should always expire
  • JWT’s should always be re assignable with the /login route

I hate jwt

Sucks that what we use until a more flexible standard is designed/developed/doc’d.

User Applications

As long as the application is ready to deal with receiving data in the forms specified in the structures section.

App developers should keep in mind that data received is nearly always JSON. The only exception is with empty body responses.

For this reason there is really two things to look for on server response:

  1. HTTP Status Code

  2. Content-Type header which if present is application/json; signifying the body contains some JSON data.

It’s safe to assume that if this isn’t present the HTTP body should be ignored.

Authorizing

To authorize on most routes you need to put the following in the query string:

  • id - The user id for that server

  • jwt - JSON web token

    • It’s suggested that this be refreshed when you application starts to not get hit with random HTTP:400’s on accident

To get a valid JSON web token to use check the /login route details under the authorization section.

Making requests

Most request data goes in the query string however, large payloads are the exception. For example the /messages/send expects the content of the message to be sent in the request body while the query string is used to describe what the request body is supposed be.

  • Request Headers

    Are not required since Freechat servers assume the worst of clients so they don’t bother ever looking there. Save yourself the bandwidth and don’t put anything there(or do idc tbh).

  • Response Headers

    As of now the only thing to look for is a present Content-Type: application/json.

JSON API Endpoints

Authorization

To make things easy: user id, secret, and jwt values are to be put in the query string. Headers are largely ignored as they offer no guarantees in security. The biggest reason for this however is for simplicity of implementation for servers.

POST /login

This route can be used to

  • Required query string parameters:

    • id: u64
    • secret: String
  • Returns:

    • jwt: String

Example

	> POST /login?id=123&secret=super-secret-token

	< {"jwt": "asdf.asdf.asdf"}

Permissions

Due to the nature of many endpoints, some endpoints require a user to have certain permissions setup.

User permissions are stored as an unsigned 64-bit bit mask alongside other user data. So far the current implemented permissions are listed below as follows.

NOTE: due to the rapid changing of features this list is subject to change until the protocol reaches 1.0 status.

Name Value Short Description
JOIN_VOICE 0x01 Ability to join a voice channel via
SEND_MESSAGES 0x02 Ability to send text messages in any text channel
CREATE_TMP_INVITES 0x04 Creation of invite codes which expire
CREATE_PERM_INVITES 0x08 Create of invite codes which do not expire
CHANGE_NICK 0x10 Can change nickname
ALLOW_PFP 0x20 Allowed to upload their own profile picture to the server
CREATE_CHANNEL 0x40 Can create channels
DELETE_CHANNEL 0x80 Can delete channels
ADMIN 0x4000000000000000 Can modify any permission beneath them, but not other admins
OWNER 0x8000000000000000 Can modify anything

Typically Admins and Owners will have every role beneath them alongside their own admin/owner flag for the sake of implementation simplicity although this is of no importance to those wanting to write their own client software.

Channels

GET /channels/list

  • Required query string parameters:

    • id: u64
    • jwt: String
  • Required Permissions

    • None
  • Returns:

    • channels: Array<Channel>

Example

	> GET /channels/list?id=123&jwt=...

	< {
	< 	"channels": [
	< 		{
	<			"id": 1
	<			"name": "general",,
	<			"kind": 2,
	<			"description": "this could be null"
	<		},
	<		...
	<	]
	< }

POST /channels/create

  • Required Permissions:

    • CREATE_CHANNEL
  • Required query string parameters:

    • id: u64

    • jwt: String

    • name: String

    • kind: u32

      • Voice channel = 1
      • Text channel = 2
    • description: String

      • Optional field
  • Returns

    • Channel
      • id: u64
      • name: String
      • description: String | null
      • kind: i32
	> POST /channels/create?id=123&jwt=...

	< { 
	<	"id": 3, 
	<	"name": "new-channel", 
	<	"description": null, 
	<	"kind": 2 
	< }

DELETE /channels/delete

  • Required permissions:

    • DELETE_CHANNEL
  • Required query string parameters:

    • id: u64
    • jwt: String
    • channel_id: u64
  • Returns:

    • None

Members

POST /members/me/nickname

  • Required permissions:

    • CHANGE_NICK
  • Required query string parameters:

    • id: u64
    • jwt: String
    • name: String
      • New nickname to change to(if allowed)
  • Returns

    • None

GET /members/me

  • Required query string parameters:

    • id: u64
    • jwt: String
  • Returns:

    • Member
      • Contains all public data about your account on the server

Example

	> GET /members/me?id=123&jwt=...

	< { 
	<	"name": "nickname-here", 
	<	"id": 123, 
	<	"permissions": <64-bit-mask>
	< }

GET /members/single

  • Required query string parameters:

    • id: u64

    • jwt: String

    • member_id: u64

  • Returns

    • Full Public User
      • Behind the key ‘member`

Example

	> GET /members/single

	< { 
	<	"name": "nickname-here", 
	<	"id": 0, // always 0
	<   "status": 1|2
	<	"permissions": <64-bit-mask>
	< }

GET /members/online

  • Required query string parameters:

    • id: u64
    • jwt: String
    • limit: Optional
      • Internal default: 100
  • Returns

    • members: Array

      • Each member contains: [name, id, permissions]
    • count: u64

Example

	> GET /members/online?id=123&jwt=...

	< {  "members": [...] }

Messages

Messages

POST /message/send

  • Required permissions:

    • SEND_MESSAGES
  • Required query string parameters:

    • id: u64

    • jwt: String

    • channel_id: u64

  • Required headers: Note that for any content that it is required that non-text/plain content be base64 encoded

    • content-type: String
      • text/plain
      • image/jpg | image/jpeg
      • image/png
      • application/webm
      • application/mp4
      • application/mp3
  • Required body:

    • Content itself should always go in the body
    • Empty bodies result in an HTTP 400 response
  • Returns:

    • None

GET /message/get_range

  • Required query string parameters:

    • id: u64

    • jwt: string

    • channel_id: u64

    • start_time: i64

      • Unix timestamp (milli-seconds)
    • end_time: i64

      • Unix timestamp (milli-seconds)
    • limit: Optional

      • Maximum = 1000
  • Returns

    • messages: Array

Example

	> GET /message/get_range?id=123&jwt=...

	< { "mesages": [...] }

GET /message/from_id

  • Required query string parameters:

    • id: u64

    • jwt: string

    • channel_id: u64

    • start: u64

    • limit: Optional

      • Maximum = 1000
  • Returns

    • messages: Array

Example

	> GET /message/from_id?id=123&jwt=...

	< { "mesages": [...] }

Invites

POST /invite/create

  • Required permissions:

    • CREATE_TMP_INVITES
      • Base requirement
    • CREATE_PERM_INVITES
      • Additional requirement for permanent invite codes
  • Required query string parameters:

    • id: u64
    • jwt: string
  • Returns

    • id: i64

    • uses: Optional This field is really meant for servers and really doesn’t have much use to users. It just to keep track of whether or not an invite has been used too many times or not.

    • expires: boolean

Example

	> POST /invite/create?id=123&jwt=

	< { 
	<	"id": <unix-timestamp ms>, 
	<	"uses": 3, 
	<	"expires": true
	< }

GET /join

  • Required Permissions

    • None
  • Required query string parameters:

    • code: i64
  • Returns a new member account - The account details are not behind a key in the JSON object, so the resulting data is completely flat, for JSON.

    • id: u64
    • secret: String
    • name: String
    • status: i32
    • permissions: u64
  • Default values

    • status: 0
    • permissions: 51
    • name: Anonymous

Example

	> GET /join?code=123456

	< { 
	<	"id": 123,
	<	"secret": "super secret",
	<	"name": "Anonymous",
	<	"status": 0,
	<	"permissions": 51
	< } 

Neighbors

GET /neighbor/list

  • Required query string parameters

    • jwt: String
  • Returns

    • neighbors: Array<Neighbor>

Example

	> GET /neighbor/list?jwt=...

	< {
	< 	"neighbors": [
	< 		{
	<			"name": "Instance Name",
	<			"url": "https://instance.xyz",
	<			"wsurl": "wss://instance.xyz",
	<			"description": "Interesting description",
	<			"tags": ["tech", "code", "docs"]
	<		},
	<		...
	<	]
	< }

POST /neighbor/add

  • Required query string parameters

    • jwt: String
  • Required HTTP Headers

    • content-type: Must be of application/json
    • content-length
  • Required Body

    • Neighbor structure data(json encoded)
  • Returns

    • On sucess: 200 OK

Example

	> POST /neighbor/add
	> content-type: application/json
	> content-length: ...
	> {
	>	"name": "Instance Name",
	>	"url": "https://instance.xyz",
	>	"wsurl": "wss://instance.xyz",
	>	"description": "Interesting description",
	>	"tags": ["tech", "code", "docs"]
	> }

	< 200 OK

PUT /neighbor/update

This route requires the full neighbor structure in the body with all the fields modified to satisfaction. The url parameter in the query string is used to uniquely identify a listed neighbor. Note that the url can also be changed but the original/previous url must be used to identify what is currently listed.

  • Required query string parameters

    • jwt: String
    • url: String := target url of the neighbor to update
  • Required headers

    • content-type: application/json
    • content-length

Example

	> PUT /neighbor/update
	> content-type: application/json
	> content-length: ...
	> {
	>	"name": "New Instance Name",
	>	"url": "https://new.instance.xyz",
	>	"wsurl": "wss://new.instance.xyz",
	>	"description": "New Interesting description",
	>	"tags": ["new", "tags", "tech", "code", "docs"]
	> }

	< 200 OK

DELETE /neighbor/delete

As long as the permissions are correct this route will basically always 200. Even if nothing is found the endpoint will still 200.

  • Required query string parameters
    • jwt: String
    • url: String

Example

	> PUT /neighbor/update?url=https%3A%2F%2Finstance.xyz

	< 200 OK

Structures

This section details what kind of data structures are returned by the json-api.

IMPORTANT: All structures are returned in JSON form.

General Data Notation

  • u64

    • Unsigned 64 bit integer
  • i64

    • Signed 64 bit integer
  • String

Channels

Channels are made up of the following components

Name Type
id u64
name String
kind i32
description String

Channels have two valid types or (more semantically) kind values:

  • Voice Channel = 1

  • Text Channel = 2

Messages

Name Type
id u64
time i64
content String
type String
author_id u64
channel_id u64

Acceptable values

Invites

When requesting a code from /invite/create successful data contains:

Name Type
id i64
uses `null
expires bool

Most clients should format this code for usage as https://domain.net:port/join?code=<id>. The other data is merely for tracking the server’s end.

Neighbors

Neighbors contain the following structure

Name Type
url* String
wsurl String
name String
description String
tags Array<String>
  • url: is used as the primary key for identifying neighbors from one another

Users

Personal User

If you need to get your own user data, with /users/me then the data received on success has the following fields:

Name Type
id u64
secret String
status i32
permissions u64

JWT

This data is retrieved after a sucessful /login hit.

Name Type
jwt String

Full Public User

Name Type
id u64
name String
status i32
permissions u64

For users without the USER_FETCH permission the only two fields with actual data is id and name. The other fields will be set to 0.

WebRTC

Configuration

By default web socket servers are bound to port 5648. The public URL for this server is also defined in the .env configuration file under the key value of PUBLIC_WS_URL.

Due to how events are propagated to users the web socket server also requires a special wss-hmac.secret file to be in the same directory as the required hmac.secret file. This HMAC is what is used to discern server connections from user connections

Data structure

Event messages come serialized in JSON form. They have the structure of:

{
	"type": <event-type>,
	<event-type>: <data-structure>
}

The full list of supported events are as follows:

  • new-message
  • delete-channel
  • create-channel
  • update-nick

RTC Authorization

For server devs

If you would like to follow the same model that the current build uses for RTC notifications then simply have the REST API server use the wss-hmac.secret to authenticate a “special” connection to the web socket server which is allowed to broadcast messages to the user connections. It should be noted that currently user connections that send messages after the connection has been established are ignored. The server is really the only one that can actually broadcast messages to users.

The architecture for the web socket server is as follows:

Basic Architecture

For client devs

To authenticate properly you must use the following url structure:

<wsurl>/text?jwt=<jwt>

Example ws://localhost:1234/text?jwt=asdf123

The JWT in the query string is the same JWT used for requests against the REST API. The /text path listens for changes on all available text channels so even if a

Events

Pertinent Event Data Structures

This section describes the data you probably care about; the data behind <data-type>.

new-message

Each valid new-message is completely flat and contains the following fields:

  • id: u64 Public user id
  • time: i64

Unix time-stamp (generated on the server’s end)

  • content: String

If the content type is text/plain then the content is all there is for that message. If the content type is another valid type then this field will contain null. A second /message/get?id=<id> is required. This is done to avoid forcing tons of file data across a network when you don’t want it.

This also gives client devs the choice of putting that network hit behind a button(or some user action) or doing automatically.

  • content_type: String

For valid content-types refer to /message/send documentation

  • author_id: u64

Public Member id

  • channel_id: u64

Text channel id. It is safe to assume that this field will point to a valid text channel and not a misconfigured voice channel.

  • name: String

Public username for that given user

delete-channel

Contains only 1 field:

  • id: u64

The id of the deleted channel

create-channel

  • id: u64

The id of the new channel

  • name: String

Name of channel

  • description: String | null

Optional description of channel

  • kind: i32

Recall:

  • Voice channels := 1
  • Text channels : 2

update-nick

  • id: u64

Id of affected user

  • name: String

New nickname of user