Quick start
This example is using JavaScript.
First we connect to the server:
socket = io.connect(
'http://' + document.domain + ':' + location.port + '/chat',
{transports:['websocket']}
);
We'll receive a connect
event back after successfully connecting. Now we have to send the login
event to provide the
server with some extra user information and to do authentication:
socket.on('connect', function() {
socket.emit('login', {
verb: 'login',
actor: {
id: '<user ID>',
attachments: [
{
objectType: 'token',
content: '<auth token>'
}
]
}
});
});
All events sent to the server will get a response with the same name plus a prefix of gn_
. For example, the login
event sent above will get the following response, gn_login
, meaning we've successfully authenticated with the server.
Now we can start joining rooms, chatting, sending events etc.
socket.on('gn_login', function(response) {
socket.emit('list_channels', {
verb: 'list'
});
});
The response from the server will be in JSON format. If no data is expected for the events, only a status code will be
in the response. For example, sending the join
event to join a room won't return any data, but only the following
(if successful):
{
"status_code": 200
}
Failure to execute an event on the server will return an error code:
{
"status_code": 423,
"data": "<an error message, always a string>"
}
If an internal server error occurs, code 500 is returned:
{
"status_code": 500,
"data": "<an error message, always a string>"
}
The format of the response can be configured, e.g. to return key "error" for error messages and use "data" only for json data.
For events that contains data in the response, for example when sending the event list_channels
, we expect to get a list
of channels in the response. For these events the data part is always a JSON in the ActivityStreams 1.0 format:
{
"status_code": 200,
"data": {
"object": {
"objectType": "channels"
"attachments": [
{
"id": "<channel ID 1>",
"content": "<channel name 1 in base64>"
},
{
"id": "<channel ID 2>",
"content": "<channel name 2 in base64>"
},
{
"id": "<channel ID 3>",
"content": "<channel name 3 in base64>"
}
]
},
"verb": "list"
}
}
Encoding
All user names, room names, channel names and chat messages are expected to be base64 encoded unicode strings. All responses and events originating from the server will also follow this practice, so when listing rooms/channels/users all names will always be in base64.
Authentication
If the redis
authentication method is configured, then when clients send the login
event to the server, the
supplied token
and actor.id
parameter must already exist in Redis. When the server gets the login event it will
check if the token matches the one stored in Redis for this user ID, otherwise it will not authenticate the session.
Therefor, before a client can login, these two values (and any other possible values used for permissions) needs to
first be set in the Redis hset
with key user:auth:<user ID>
.
Example:
$ redis-cli
127.0.0.1:6379> hset user:auth:1234 token 302fe6be-a72f-11e6-b5fc-330653beb4be
127.0.0.1:6379> hset user:auth:1234 age 35
127.0.0.1:6379> hset user:auth:1234 gender m
Private messaging
Sometimes private messaging should be identified by the unique combination of two user IDs, say 1
and 2
, so
that the history between them can be accesses by both parties. In this case, the client implementation should
generate an identifiable "name" for this combination, and create a room to group these messages in.
For example, the implementer generates a thread_id
or conversation_id
on their side, then call the
create
API with the name set as this generated ID. For example, if the ID 42
is generated
for the conversation assiciated with the users 1
and 2
:
socket.emit('create', {
verb: 'create'
object: {
url: '<channel uuid>'
},
target: {
displayName: '42',
objectType: 'private',
attachments: [{
objectType: 'owners',
summary: '1,2'
}]
}
}, function(status_code, data, error_msg) {
// callback method, check create api for format of the data param
});
The callback method will contain the generated UUID of this room (e,g, 4b90eae8-c82b-11e7-98ba-43d525dbbb29
),
which should be used when joining, sending message etc. It is the responsibility of the implementer to keep track
of the room IDs associated with conversations.
All users specied as the "owners" will receive the gn_room_created
event if
they are online, otherwise they would get it as history later.
To send a message in this room
, first join
the room (will return the history of this room):
socket.emit('join', {
verb: 'join',
target: {
id: '4b90eae8-c82b-11e7-98ba-43d525dbbb29'
}
}, function(status_code, data, error_msg) {
// callback method
});
Alternatively, a room can be joined by the display_name
instead of by id
, in case that the UUID is not known
on the client side a the time of joining. If multiple rooms exists with the same display_name
, the join
event
will fail with the error code 715, though in reality that should not happen unless the
uniqueness of room names per channel during creation has been disabled.
Example of joining using display_name
:
socket.emit('join', {
verb: 'join',
target: {
display_name: '42'
}
}, function(status_code, data, error_msg) {
// callback method, generated room uuid is data.target.id
});
Use the message
API to send a message to this room:
socket.emit('message', {
verb: 'send',
target: {
id: '4b90eae8-c82b-11e7-98ba-43d525dbbb29',
},
object: {
content: '<the message, base64 encoded>',
}
}, function(status_code, data, error_msg) {
// callback method
});
If the other user is online, he/she will get the message received event.
Java client
Using the Java socket.io library, you have to use http
instead of ws
and https
instead of wss
(it's the same thing).
Create your object and use Gson to serialize it to json for a JSONObject (you cannot do a toString
of the
obejct, it needs to be a json object):
Gson gson = new Gson();
try {
JSONObject obj = new JSONObject(gson.toJson(o));
s.emit("login", obj);
} catch (JSONException e) {
e.printStackTrace();
}
Delivery acknowledgment
All APIs will invoke the callback (if specified) with a status_code
and possibly error_message
(if any
errors). These should be be retrieved in the callback defined on the client side. If there was no error, the
second argument will be nil. Examples of callbacks on client side in JavaScript:
socket.emit('message', '<omitted json message>', function(status_code, error_msg) {
// do something
});
Messages should also be awknowledged by the client when received. An awknowledgement can also be sent by the client when a messages as been read, to let other clients know if the message has been seen or not.
Unawknowledged (no received ack sent by client) messages will in future version be redelivered since it might indicate a loss during transmission.
Example of sending acknowledgement of received message as well as listening for the OK
server response to
the ack:
socket.on('gn_message', function(response) {
if (response.status_code !== 200) {
// handle error some way
return;
}
// acknowledge that we got the message
socket.emit('received', {
verb: 'receive',
target: {
id: room_id
},
object: {
attachments: [{
// response.data.id is the generated uuid of the message, see api docs
id: response.data.id
}]
}
}, function(status_code, error_msg) {
// server "acks our ack"
console.log('callback for received api: ' + status_code)
});
// finally handle the message
handle_message(data);
});
Limited sessions
The session handler can be configured to either allow only one simultaneous connection per user or an unlimited amount. If only one session is allowed, then whenever a new session by the same user is started, the previous connection will be disconnected.