Tinode Instant Messaging Server

This documentation covers the next 0.4 release of Tinode. ETA mid-November 2015.

Instant messaging server. Backend in pure Go (Affero GPL 3.0), client-side binding in Java for Android and Javascript (Apache 2.0), persistent storage RethinkDB, JSON over websocket (long polling is also available). No UI components other than demo apps. Tinode is meant as a replacement for XMPP.

This is alpha-quality software. Bugs should be expected. Version 0.4

Why?

XMPP is a mature specification with support for a very broad spectrum of use cases developed long before mobile became important. As a result most (all?) known XMPP servers are difficult to adapt for the most common use case of a few people messaging each other from mobile devices. Tinode is an attempt to build a modern replacement for XMPP/Jabber focused on a narrow use case of instant messaging between humans with emphasis on mobile communication.

Features

Supported

  • One on one messaging
  • Group chats:
  • Groups (topics) with up to 32 members where every member's access permissions are managed individually
  • Groups with unlimited number of members with bearer token access control
  • Topic access control with separate permissions for various actions (reading, writing, sharing, etc)
  • Server-generated presence notifications for people and topics
  • Persistent message store
  • Android Java bindings (dependencies: jackson, android-websockets)
  • Javascript bindings with no dependencies
  • Websocket & long polling transport
  • JSON wire protocol
  • Server-generated message delivery status
  • Support for client-side content caching
  • Blocking users on the server

Planned

  • iOS client bindings
  • Mobile push notification hooks
  • Clustering
  • Federation
  • Multitenancy
  • Different levels of message persistence (from strict persistence to store until delivered to purely ephemeral messaging)
  • Support for binary wire protocol
  • User search/discovery
  • Anonymous clients
  • Support for other SQL and NoSQL backends
  • Pluggable authentication

How it works?

Tinode is an IM router and a store. Conceptually it loosely follows a publish-subscribe model.

Server connects sessions, users, and topics. Session is a network connection between a client application and the server. User represents a human being who connects to the server with a session. Topic is a named communication channel which routes content between sessions.

Users and topics are assigned unique IDs. User ID is a string with 'usr' prefix followed by base64-URL-encoded pseudo-random 64-bit number, e.g. usr2il9suCbuko. Topic IDs are described below.

Clients such as mobile or web applications create sessions by connecting to the server over a websocket or through long polling. Client authentication is optional (anonymous clients are technically supported but may not fully work as expected yet). Client authenticates the session by sending a {login} packet. Only basic authentication with user name and password is currently supported. Multiple simultaneous sessions may be established by the same user. Logging out is not supported.

Once the session is established, the user can start interacting with other users through topics. The following topic types are available:

  • me is a topic for managing one's profile, receiving invites and requests for approval; 'me' topic exists for every user.
  • Peer to peer topic is a communication channel strictly between two users. It's named as a 'p2p' prefix followed by a base64-URL-encoded numeric part of user IDs concatenated in ascending order, e.g. p2p2il9suCbukqm4P2KFOv9-w. Peer to peer topics must be explicitly created.
  • Group topic is a channel for multi-user communication. It's named as 'grp' followed by 12 pseudo-random characters, i.e. grp1XUtEhjv6HND. Group topics must be explicitly created.

Session joins a topic by sending a {sub} packet. Packet {sub} serves three functions: creating a new topic, subscribing user to a topic, and attaching session to a topic. See {sub} section below for details.

Once the session has joined the topic, the user may start generating content by sending {pub} packets. The content is delivered to other attached sessions as {data} packets.

The user may query or update topic metadata by sending {get} and {set} packets.

Changes to topic metadata, such as changes in topic description, or when other users join or leave the topic, is reported to live sessions with {pres} (presence) packet.

When user's me topic comes online (i.e. an authenticated session attaches to me topic), a {pres} packet is sent to me topics of all other users, who have peer to peer subscriptions with the first user.

General considerations

Timestamps are always represented as RFC 3999-formatted string with precision to milliseconds and timezone always set to UTC, e.g. "2015-10-06T18:07:29.841Z".

Whenever base64 encoding is mentioned, it means base64 URL encoding with padding characters stripped, see RFC 4648.

Connecting to the server

Client establishes a connection to the server over HTTP. Server offers two end points:

  • /v0/channels for websocket connections
  • /v0/v0/channels/lp for long polling

v0 denotes API version (currently zero). Every HTTP request must include API key in the request. It may be included in the URL as ...?apikey=<YOUR_API_KEY>, in the request body, or in an HTTP header X-Tinode-APIKey.

Once the connection is opened, the server sends a {ctrl} message. The params field of response contains server's protocol version: "params":{"ver":"0.4"}. params may include other values.

Websocket

Messages are sent in text frames, one message per frame. Binary frames are reserved for future use. Server allows connections with any value in the Origin header.

Long polling

Long polling works over HTTP POST (preferred) or GET. In response to client's very first request server sends a {ctrl} message containing sid (session ID) in params. Long polling client must include sid in every subsequent request either in URL or in request body.

Server allows connections from all origins, i.e. Access-Control-Allow-Origin: *

Messages

A message is a logically associated set of data. Messages are passed as JSON-formatted text.

All client to server messages may have an optional id field. It's set by the client as means to receive an aknowledgement from the server that the message was received and processed. The id is expected to be a session-unique string but it can be any string. The server does not attempt to interpret it other than to check JSON validity. It's returned unchanged by the server when it replies to client messages.

For brevity the notation below omits double quotes around field names as well as outer curly brackets.

For messages that update application-defined data, such as {set} private or public fields, in case server-side data needs to be cleared, use a string with a single Unicode DEL character "␡" "\u2421".

Client to server messages

{acc}

Message {acc} is used for creating users or updating authentication credentials. To create a new user set acc.user to string "new". Either authenticated or anonymous session can send an {acc} message to create a new user. To update credentials leave acc.user unset.

acc: {
  id: "1a2b3",     // string, client-provided message id, optional
  user: "new", // string, "new" to create a new user, default: current user, optional
  auth: [   // array of authentication schemes to add, update or delete
    {
      scheme: "basic", // requested authentication scheme for this account, required;
                       // only "basic" (default) is currently supported. The current
                       // basic scheme does not allow changes to username.
      secret: "username:password" // string, secret for the chosen authentication
                      // scheme; to delete a scheme use string with a single DEL
                      // Unicode character "\u2421"; required
    }
  ],
  init: {  // object, user initialization data closely matching that of table
           // initialization; optional
    defacs: {
      auth: "RWS", // string, default access mode for peer to peer conversations
                   // between this user and other authenticated users
      anon: "X"  // string, default access mode for peer to peer conversations
                 // between this user and anonymous (un-authenticated) users
    }, // Default access mode for user's peer to peer topics
    public: { ... }, // application-defined payload to describe user,
                // available to everyone
    private: { ... } // private application-defined payload available only to user
                // through 'me' topic
  }
}

Server responds with a {ctrl} message with ctrl.params containing details of the new user. If init.acs is missing, server will assign server-default access values.

{login}

Login is used to authenticate the current session.

login: {
  id: "1a2b3",     // string, client-provided message id, optional
  scheme: "basic", // string, authentication scheme, optional; only "basic" (default)
                   // is currently supported
  secret: "username:password", // string, secret for the chosen authentication
                               //  scheme, required
  expireIn: "24h", // string, login expiration time in Go's time.ParseDuration
                   //  format, see below, optional
  tag: "some string" // string, client instance ID; tag is used to support caching,
                     // optional
}

Basic authentication scheme expects secret to be a string composed of a user name followed by a colon : followed by a plan text password.

Basic is the only currently supported authentication scheme. Authentication scheme none is planned to support anonymous users in the future.

time.ParseDuration is used to parse expireIn. The recognized format is a possibly signed sequence of decimal numbers, each with optional fraction and a unit suffix, such as "300ms", "-1.5h" or "2h45m". Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".

Server responds to a {login} packet with a {ctrl} packet.

{sub}

The {sub} packet serves three functions:

  • creating a topic
  • subscribing user to a topic
  • attaching session to a topic

User creates a new group topic by sending {sub} packet with the topic field set to new. Server will create a topic and respond back to session with the name of the newly created topic.

User creates a new peer to peer topic by sending {sub} packet with topic set to peer's user ID.

The user is always subscribed to and the sessions is attached to the newly created topic.

If the user had no relationship with the topic, sending {sub} packet creates it. Subscribing means to establish a relationship between session's user and the topic when no relationship existed in the past.

Joining (attaching to) a topic means for the session to start consuming content from the topic. Server automatically differentiates between subscribing and joining/attaching based on context: if the user had no prior relationship with the topic, the server subscribes the user then attaches the current session to the topic. If relationship existed, the server only attaches the session to the topic. When subscribing, the server checks user's access permissions against topic's access control list. It may grant immediate access, deny access, may generate a request for approval from topic managers.

Server replies to the {sub} with a {ctrl}.

The {sub} message may include a get and browse fields which mirror what and browse fields of a {get} message. If included, server will treat them as a subsequent {get} message on the same topic. In that case the reply may also include {meta} and {data} messages.

sub: {
  id: "1a2b3",  // string, client-provided message id, optional
  topic: "me",   // topic to be subscribed or attached to

  // Object with topic initialization data, new topics & new
  // subscriptions only, mirrors {set info}
  init: {
    defacs: {
      auth: "RWS", // string, default access for new authenticated subscribers
      anon: "X"    // string, default access for new anonymous (un-authenticated)
                   // subscribers
    }, // Default access mode for the new topic
    public: { ... }, // application-defined payload to describe topic
    private: { ... } // per-user private application-defined content
  }, // object, optional

  // Subscription parameters, mirrors {set sub}; sub.user must
  // not be provided
  sub: {
    mode: "RWS", // string, requested access mode, optional;
                 // default: server-defined
    info: { ... }  // application-defined payload to pass to the topic manager
  }, // object, optional

  // Metadata to request from the topic; space-separated list, valid strings
  // are "info", "sub", "data"; default: request nothing; unknown strings are
  // ignored; see {get  what} for details
  get: "info sub data", // string, optional

  // Optional parameters for get: "info", see {get what="data"} for details
  browse: {
    ascnd: true, // boolean, sort in ascending order by time, otherwise
                 // descending (default), optional
    since: "2015-09-06T18:07:30.134Z", // datetime as string,
                    // load objects newer than this (inclusive/closed), optional
    before: "2015-10-06T18:07:30.134Z", // datetime as string,
                    // load objects older than this (exclusive/open), optional
    limit: 20, // integer, limit the number of returned objects,
               // default: 32, optional
  } // object, optional
}

{leave}

This is a counterpart to {sub} message. It also serves two functions:

  • leaving the topic without unsubscribing (unsub=false)
  • unsubscribing (unsub=true)

Server responds to {leave} with a {ctrl} packet. Leaving without unsubscribing affects just the current session. Leaving with unsubscribing will affect all user's sessions.

leave: {
  id: "1a2b3",  // string, client-provided message id, optional
  topic: "grp1XUtEhjv6HND",   // string, topic to leave, unsubscribe, or
                              // delete, required
  unsub: true // boolean, leave and unsubscribe, optional, default: false

{pub}

The message is used to distribute content to topic subscribers.

pub: {
  id: "1a2b3", // string, client-provided message id, optional
  topic: "grp1XUtEhjv6HND", // topic to publish to, required
  content: { ... }  // object, application-defined content to publish
               // to topic subscribers, required
}

Topic subscribers receive the content in the {data} message.

{get}

Query topic for metadata, such as description or a list of subscribers, or query message history.

get: {
  what: "sub info data", // string, space-separated list of parameters to query;
                        // unknown strings are ignored; required
  browse: {
    ascnd: true, // boolean, sort in ascending order by time, otherwise
                 // descending (default), optional
    since: "2015-09-06T18:07:30.134Z", // datetime as string,
                        // load objects newer than this (inclusive/closed), optional
    before: "2015-10-06T18:07:30.134Z", // datetime as string,
                        // load objects older than this (exclusive/open), optional
    limit: 20, // integer, limit the number of returned objects, default: 32,
               // optional
  } // object, what=data query parameters
}
  • {get what="info"}

Query topic description. Server responds with a {meta} message containing requested data. See {meta} for details.

  • {get what="sub"}

Get a list of subscribers. Server responds with a {meta} message containing a list of subscribers. See {meta} for details. For me topic the request returns a list of user's subscriptions.

  • {get what="data"}

Query message history. Server sends {data} messages matching parameters provided in the browse field of the query. The id field of the data messages is not provided as it's common for data messages.

{set}

Update topic metadata, delete messages or topic.

set: {
  id: "1a2b3", // string, client-provided message id, optional
  topic: "grp1XUtEhjv6HND", // string, topic to publish to, required
  what: "sub info", // string, space separated list of data to update,
                        // unknown strings are ignored
  info: {
    defacs: { // new default access mode
      auth: "RWP",  // access permissions for authenticated users
      anon: "X" // access permissions for anonymous users
    },
    public: { ... }, // application-defined payload to describe topic
    private: { ... } // per-user private application-defined content
  }, // object, payload for what == "info"
  sub: {
    user: "usr2il9suCbuko", // string, user affected by this request;
                            // default (empty) means current user
    mode: "RWP", // string, access mode change, either given or requested
                 // depending on context
    info: { ... } // object, application-defined payload to pass to
                  // the invited user or to the topic manager in {data}
                  // message on 'me' topic
  } // object, payload for what == "sub"
}

{del}

Delete messages or topic.

del: {
  id: "1a2b3", // string, client-provided message id, optional
  topic: "grp1XUtEhjv6HND", // string, topic affect, required
  what: "msg", // string, either "topic" or "msg" (default); what to delete - the
               // entire topic or just the messages, optional, default: "msg"
  hard: false, // boolean, request to delete messages for all users, default: false
  before: "2015-10-06T18:07:30.134Z", // datetime as string, delete messages
              // older than this (exclusive of the value itself), optional
}

No special permission is needed to soft-delete messages hard=false. Soft-deleting messages hides them from the requesting user. D permission is needed to hard-delete messages. Only owner can delete a topic.

Server to client messages

Messages to a session generated in response to a specific request contain an id field equal to the id of the originating message. The id is not interpreted by the server.

Most server to client messages have a ts field which is a timestamp when the message was generated by the server.

{data}

Content published in the topic. These messages are the only messages persisted in database; {data} messages are broadcast to all topic subscribers with an R permission.

data: {
  topic: "grp1XUtEhjv6HND", // string, topic which distributed this message,
                            // always present
  from: "usr2il9suCbuko", // string, id of the user who published the
                          // message; could be missing if the message was
                          // generated by the server
  ts: "2015-10-06T18:07:30.038Z", // string, timestamp
  content: { ... } // object, application-defined content exactly as published
              // by the user in the {pub} message
}

{ctrl}

Generic response indicating an error or a success condition. The message is sent to the originating session.

ctrl: {
  id: "1a2b3", // string, client-provided message id, optional
  topic: "grp1XUtEhjv6HND", // string, topic name, if this is a response in context
                            // of a topic, optional
  code: 200, // integer, code indicating success or failure of the request, follows
             // the HTTP status codes model, always present
  text: "OK", // string, text with more details about the result, always present
  params: { ... }, // object, generic response parameters, context-dependent,
                   // optional
  ts: "2015-10-06T18:07:30.038Z", // string, timestamp
}

{meta}

Information about topic metadata or subscribers, sent in response to {set} or {sub} message to the originating session.

ctrl: {
  id: "1a2b3", // string, client-provided message id, optional
  topic: "grp1XUtEhjv6HND", // string, topic name, if this is a response in
                            // context of a topic, optional
	info: {
    created: "2015-10-24T10:26:09.716Z",
    updated: "2015-10-24T10:26:09.716Z",
    defacs: { // topic's default access permissions; present only if the current
              //user has 'S' permission
      auth: "RWP", // default access for authenticated users
      anon: "X" // default access for anonymous users
    },
    acs: {  // user's actual access permissions
      "want":"RWP", // string, requested access permission
      "given":"RWP" // string, granted access permission
    },
    lastMsg: "2015-10-29T16:19:15.03Z", // timestamp of the last {data} message
    public: { ... }, // application-defined data that's available to all topic
                     // subscribers
    private: { ...} // application-deinfed data that's available to the current
                    // user only
  }, // object, topic description, optional
	sub:  [
    {
      user: "usr2il9suCbuko", // string, ID of the user this subscription describes
      updated: "2015-10-24T10:26:09.716Z", // timestamp of the last change in the
                                           // subscription, present only for
                                           // requester's own subscriptions
      mode: "RWPSDO",  // string, user's access permission, equal to bitwise
                       // AND (info.given & info.want)
      public: { ... }, // application-defined user's 'public' object
      private: { ... } // application-defined user's 'private' object, present only
                       // for the requester's own subscriptions
    },
    ...
  ] // array of objects, topic subscribers, optional
  ts: "2015-10-06T18:07:30.038Z", // string, timestamp
}

{pres}

Tinode uses {pres} message to inform users of important events. The following events are tracked by the server and will generate {pres} messages provided user has appropriate access permissions:

  • A user joins me. User receives presence notifications for each of his/her subscriptions: {pres topic="me" src="<user ID or topic ID>" what="on"}. Only online status is reported.
  • A user came online or went offline. The user triggers this event by joining/leaving the me topic. The message is sent to all users who have P2P topics with the first user. Users receive this event on the me topic, src field contains user ID src: "usr2il9suCbuko", what contains "on" or "off": {pres topic="me" src="<user ID>" what="on|off"}.
  • User's public is updated. The event is sent to all users who have P2P topics with the first user. Users receive {pres topic="me" src="<user ID>" what="upd"}.
  • User joins/leaves a topic. This event is sent to other users who currently joined the topic: {pres topic="<topic name>" src="<user ID>" what="on|off"}.
  • A group topic is activated/deactivated. Topic becomes active when at least one user joins it. The topic becomes inactive when all users leave it (possibly after some delay). The event is sent to all topic subscribers. They will receive it on their me topics: {pres topic="me" src="<topic name>" what="on|off"}.
  • Topic's public is updated. The event is sent to all topic subscribers. Topic's subscribers receive {pres topic="me" src="<topic name>" what="upd"}.
  • A message is sent to the topic. The event is sent to users who have subscribed to the topic but currently not joined {pres topic="me" src="<topic name>" what="msg"}.
pres: {
  topic: "grp1XUtEhjv6HND", // string, topic affected by the change, always present
  src: "usr2il9suCbuko", // user or topic affected by the change, always present
  what: "on"  // string, what's changed, always present
}

The {pres} messages are purely transient. No attempt is made to store a {pres} message or deliver it later if the target is unavailable.

Timestamp is not present in {pres} messages.

Users

User is meant to represent a person, an end-user: producer and consumer of messages.

There are two types of users: authenticated and anonymous. When a connection is first established, the client application can only send either an {acc} or a {login} message. Sending a {login} message will authenticate user or allow him to continue as an anonymous. Anonymous users are not supported as of the time of this writing.

Each user is assigned a unique ID. The IDs are composed as usr followed by base64-encoded 64-bit numeric value, e.g. usr2il9suCbuko. Users also have the following properties:

  • created: timestamp when the user record was created
  • updated: timestamp of when user's public was last updated
  • username: unique string used in basic authentication; username is not accessible to other users
  • defacs: object describing user's default access mode for peer to peer conversations with authenticated and anonymous users; see [Access control][] for details
  • auth: default access mode for authenticated users
  • anon: default access for anonymous users
  • public: an application-defined object that describes the user. Anyone who can query user for public data.
  • private: an application-defined object that is unique to the current user and accessible only by the user.

A user may maintain multiple simultaneous connections (sessions) with the server. Each session can be tagged with a device ID. See [Support for Client-Side Caching][] foir details.

Logging out is not supported by design. If an application needs to change the user, it should open a new connection and authenticate it with the new user credentials.

Access control

Access control manages user's access to topics through access control lists (ACLs) or bearer tokens (bearer tokens are not implemented as of version 0.4).

Access control is mostly usable for group topics. Its usability for me and P2P topics is limited to managing presence notifications and banning uses from initiating or continuing P2P conversations.

User's access to a topic is defined by two sets of permissions: user's desired permissions "want", and permissions granted to user by topic's manager(s) "given". Each permission is represented by a bit in a bitmap. It can be either present or absent. The actual access is determined as a bitwise AND of wanted and given permissions. The permissions are communicated in messages as a set of ASCII characters, where presence of a character means a set permission bit:

  • No access: N is not a permission per se but an indicator that permissions are explicitly cleared/not set. It usually indicates that the default permissions should not be applied.
  • Read: R, permission to subscribe to topic and receive {data} packets
  • Write: W, permission to {pub} to topic
  • Presence: P, permission to receive presence updates {pres}
  • Sharing: S, permission to invite other people to join the topic and to approve requests to join; a user with such permission is topic's manager
  • Delete: D, permission to hard-delete messages; only owners can completely delete topics
  • Owner: O, user is the topic owner; topic may have a single owner only
  • Banned: X, user has no access, requests to share/gain access are silently ignored

Topic's default access is established at the topic creation time by {sub.init.defacs} and can be subsequently modified by {set} messages. Default access is defined for two categories of users: authenticated and anonymous. This value is applied as a default "given" permission to all new subscriptions.

Client may replace explicit permissions in {sub} and {set} messages with an empty string to tell Tinode to use default permissions. If client specifies no default access permissions at topic creation time, authenticated users will receive a RWP permission, anonymous users will receive and empty permission which means every subscription request must be explicitly approved by the topic manager.

Access permissions can be assigned on a per-user basis by {set} messages.

Topics

Topic is a named communication channel for one or more people. Topics have persistent properties. These following topic properties can be queried by {get what="info"} message.

Topic properties independent of the user making the query:

  • created: timestamp of topic creation time
  • updated: timestamp of when topic's public or private was last updated
  • defacs: object describing topic's default access mode for authenticated and anonymous users; see [Access control][] for details
  • auth: default access mode for authenticated users
  • anon: default access for anonymous users
  • lastMsg: timestamp when last {data} message was sent through the topic
  • public: an application-defined object that describes the topic. Anyone who can subscribe to topic can receive topic's public data.

User-dependent topic properties:

  • acs: object describing given user's current access permissions; see [Access control][] for details
  • want: access permission requested by this user
  • given: access permissions given to this user
  • seen: an object describing when the topic was last accessed by the current user from any client instance. This should be useful if the client implements data caching. See [Support for Client-Side Caching][] for more details.
  • when": timestamp of the last access
  • tag: string provided by the client instance when it accessed the topic.
  • seenTag: timestamp when the topic was last accessed from a session with the current client instance. See [Support for Client-Side Caching][] for more details
  • private: an application-defined object that is unique to the current user.

Topic usually have subscribers. One the the subscribers may be designated as topic owner (O access permission) with full access permissions. The list of subscribers can be queries with a {get what="sub"} message. The list of subscribers is returned in a sub section of a {meta} message.

me topic

Topic me is automatically created for every user at the account creation time. It serves as means for account updates, receiving presence notification from people and topics of interest, invites to join topics, requests to approve subscription for topics where this user is a manager (has S permission). Topic me has no owner. The topic cannot be deleted or unsubscribed from. One can leave the topic which will stop all relevant communication and indicate that the user is offline (although the user may still be logged in and may continue to use other topics).

Joining or leaving me generates a {pres} presence update sent to all users who have peer to peer topics with the given user and P permissions set.

Topic me is read-only. {pub} messages to me are rejected.

The {data} message represents invites and requests to confirm a subscription. The from field of the message contains ID of the user who originated the request, for instance, the user who asked current user to join a topic or the user who requested an approval for subscription. The content field of the message contains the following information:

  • act: request action as string; possible actions are:
  • "info" to notify the user that user's request to subscribe was approved; in case of peer to peer topics this could be a notification that the peer has subscribed to the topic
  • "join" is an invitation to subscribe to a topic
  • "appr" is a request to approve a subscription
  • topic: the name of the topic, in case of an invite the current user is invited to this topic; in case of a request to approve, another user wants to subscribe to this topic where the current user is a manager (has S permission)
  • user: user ID as a string of the user who is the target of this request. In case of an invite this is the ID of the current user; in case of an approval request this is the ID of the user who is being subscribed.
  • acs: object describing access permissions of the subscription, see [Access control][] for details
  • info: object with a free-form payload. It's passed unchanged from the originating {sub} or {set} message.

Message {get what="info"} to me is automatically replied with a {meta} message containing info section with the topic parameters (see intro to [Topics][] section). The public parameter of me topic is associated with the user. Changing it changes public not just for the me topic, but also everywhere where user's public is shown, such as 'public' of all user's peer to peer topics.

Message {get what="sub"} to me is different from any other topic as it returns the list of topics that the current user is subscribed to as opposite to the user's subscription to me.

Message {get what="data"} to me queries the history of invites/notifications. It's handled the same way as to any other topic.

Peer to Peer Topics

Peer to peer (P2P) topics represent communication channels between strictly two users. The name of the topic is p2p followed by base64 URL-encoded concatenation of two 8-byte IDs of participating users. Lower user ID comes first. For example p2pOj0B3-gSBSshT8s5XBE2xw is a topic for users usrOj0B3-gSBSs and usrIU_LOVwRNsc. The P2P topic has no owner.

A P2P topic is created by one user subscribing to topic with the name equal to the ID of the other user. For instance, user usrOj0B3-gSBSs can establish a P2P topic with user usrIU_LOVwRNsc by sending a {sub topic="usrIU_LOVwRNsc"}. Tinode will respond with a {ctrl} packet with the name of the newly created topic, p2pOj0B3-gSBSshT8s5XBE2xw in this example. The other user will receive a {data} message on me topic with either a request to confirm the subscription or a notification of a successful subscription, depending on user's default permissions.

The 'public' parameter of P2P topics is user-dependent. For instance a P2P topic between users A and B would show user A's 'public' to user B and vice versa. If a user updates 'public', all user's P2P topics will automatically update 'public' too.

The 'private' parameter of a P2P topic is defined by each participant individually as with any other topic type.

Group Topics

Group topics represent communication channels between multiple users. The name of a group topic is grp followed by a string of characters from base64 URL-encoding set. No other assumptions can be made about internal structure or length of the group name.

A group topic is created by sending a {sub} message with the topic field set to "new". Tinode will respond with a {ctrl} message with the name of the newly created topic, i.e. {sub topic="new"} is replied with {ctrl topic="grpmiKBkQVXnm3P"}. The user who created the topic becomes topic owner. Ownership can be transferred to another user with a {set} message but at least one user must remain the topic owner.

A user joining or leaving the topic generates a {pres} message to all other users who are currently in the joined state with the topic.

Support for Client-Side Caching

Tinode provides basic mechanism for implementing client-side caching.

It's assumed that a single user may connect to the server with multiple client applications. Some of them may be able to cache certain information in client-side storage. An identifier of an instance of the client application is communicated to the server in the tag field of the {login} message. The tag is a string which identifies an instance of the client-side cache. For instance if the user flushes the cache the app should change the application tag. The combination of user ID and tag should be unique, i.e. it's acceptable for different users to have the same tag. If the application does not implement caching it should not provide the tag.

Server tracks timestamps of user's login, topic join and leave requests keyed by tag. When the user issues a {get what="data"} (or an equivalent {sub get="data"}), the browse may be left empty. Then the server will assume that the browse.since is the time when the user left the topic, i.e. "fetch all data since last visit".

Description
Languages
Go 86.9%
Python 8.5%
templ 2%
Shell 1.5%
Scala 0.6%
Other 0.4%