New product announcement 🎉 Satori - LiveOps to know your players, deliver features, run experiments and schedule events

Cocos2d-x JavaScript Client Guide

The official cocos2d JavaScript client handles all communication in real-time with the server. It implements all features in the server and can be used in cocos2d-x js projects. The client also comes with type definitions so it can be used via TypeScript.

The JavaScript client is open source on GitHub. Please report issues and contribute code to help us improve it.

Download

Download the latest release from the GitHub releases page which contains the Nakama-js module with UMD module loader and polyfill library.

For upgrades you can see changes and enhancements in the CHANGELOG before you update to newer versions.

Setup

When you’ve downloaded a release, extract it to your src folder.

Import into your project.json:

1
2
3
4
5
"jsList" : [
  ...
  "src/NakamaSDK/ThirdParty/polyfill.js",
  "src/NakamaSDK/nakama-js.umd.js"
]

The client object is used to execute all logic against the server:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// Default connection settings for a local Nakama server
var serverkey = "defaultkey";
var host = "127.0.0.1";
var port = "7350";
var useSSL = false;
var timeout = 7000; // ms

var client = new nakamajs.Client(serverkey, host, port, useSSL, timeout);

var client = new nakamajs.Client();
Cocos2d does not support await and promises JavaScript features. You have to use promises which are provided by polyfill third party library.

Authenticate

With a client object you can authenticate against the server. You can register or login a user with one of the authenticate options.

To authenticate you should follow our recommended pattern in your client code.

By default Nakama will try to create a user if it doesn’t exist.

Use the following code to store the session:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
const email = "hello@example.com";
const password = "somesupersecretpassword";

client.authenticateEmail(email, password).then(function(session) {
        cc.log("Authenticated successfully. User id:", session.user_id);
        // Store session token for quick reconnects.
        cc.sys.localStorage.setItem("nakamaToken", session.token);
    },
    function(error) {
        cc.error("authenticate failed:", JSON.stringify(error));
    });

In the code above we use authenticateEmail but for other authentication options have a look at the code examples.

A full example with all code above is here.

Send messages

When a user has been authenticated a session is used to connect with the server. You can then send messages for all the different features in the server.

This could be to add friends, join groups, or submit scores in leaderboards. You can also execute remote code on the server via RPC.

The server also provides a storage engine to keep save games and other records owned by users. We’ll use storage to introduce how messages are sent.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
const objects = [{
  "collection": "collection",
  "key": "key1",
  "value": {"jsonKey": "jsonValue"}
}, {
  "collection": "collection",
  "key": "key2",
  "value": {"jsonKey": "jsonValue"}
}];
client.writeStorageObjects(session, objects)
  .then(function(storageWriteAck) {
        cc.log("Storage write was successful:", JSON.stringify(storageWriteAck));
    },
    function(error) {
        cc.error("Storage write failed:", JSON.stringify(error));
    });

Have a look at other sections of documentation for more code examples.

Real-time data exchange

You can connect to the server over a real-time WebSocket connection to send and receive chat messages, get notifications, and matchmake into a multiplayer match. You can also execute remote code on the server via RPC.

You first need to create a real-time socket to the server:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
const useSSL = false;
const verboseLogging = false;
const createStatus = false;    // set `true` to send presence events to subscribed users.
const socket = client.createSocket(useSSL, verboseLogging);
var session = ""; // obtained by authentication.
socket.connect(session, createStatus)
  .then(
        function() {
            cc.log("connected");
        },
        function(error) {
            cc.error("connect failed:", JSON.stringify(error));
        }
    );

Then proceed to join a chat channel and send a message:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
socket.onchannelmessage = function (channelMessage) {
  cc.log("Received chat message:", JSON.stringify(channelMessage));
};

const channelId = "pineapple-pizza-lovers-room";
const persistence = false;
const hidden = false;

socket.joinChat(channelId, 1, persistence, hidden).then(
      function(response) {
          cc.log("Successfully joined channel:", response.channel.id);
          sendChatMessage(response.channel.id);
      },
      function(error) {
          cc.error("join channel failed:", JSON.stringify(error));
      }
  );

function sendChatMessage(channelId) {
  socket.writeChatMessage(channelId, {"message": "Pineapple doesn't belong on a pizza!"}).then(
      function(messageAck) {
          cc.log("Successfully sent chat message:", JSON.stringify(messageAck));
      },
      function(error) {
          cc.error("send message failed:", JSON.stringify(error));
      }
  );
}

You can find more information about the various chat features available here.

Handle events

A client socket has event listeners which are called on various events received from the server.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
socket.ondisconnect = function (event) {
  cc.log("Disconnected from the server. Event:", JSON.stringify(event));
};
socket.onnotification = function (notification) {
  cc.log("Received notification:", JSON.stringify(notification));
};
socket.onchannelpresence = function (presence) {
  cc.log("Received presence update:", JSON.stringify(presence));
};
socket.onchannelmessage = function (message) {
  cc.log("Received new chat message:", JSON.stringify(message));
};
socket.onmatchdata = function (matchdata) {
  cc.log("Received match data: ", JSON.stringify(matchdata));
};
socket.onmatchpresence = function (matchpresence) {
  cc.log("Received match presence update:", JSON.stringify(matchpresence));
};
socket.onmatchmakermatched = function (matchmakerMatched) {
  cc.log("Received matchmaker update:", JSON.stringify(matchmakerMatched));
};
socket.onstatuspresence = function (statusPresence) {
  cc.log("Received status presence update:", JSON.stringify(statusPresence));
};
socket.onstreampresence = function (streamPresence) {
  cc.log("Received stream presence update:", JSON.stringify(streamPresence));
};
socket.onstreamdata = function (streamdata) {
  cc.log("Received stream data:", JSON.stringify(streamdata));
};

Some events only need to be implemented for the features you want to use.

CallbacksDescription
onDisconnectReceived when the client is disconnected from the server.
onNotificationReceives live in-app notifications sent from the server.
onChannelMessageReceives real-time chat messages sent by other users.
onChannelPresenceReceives join and leave events within chat.
onMatchStateReceives real-time multiplayer match data.
onMatchPresenceReceives join and leave events within real-time multiplayer.
onMatchmakerMatchedReceived when the matchmaker has found a suitable match.
onStatusPresenceReceives status updates when subscribed to a user status feed.
onStreamPresenceReceives stream join and leave event.
onStreamStateReceives stream data sent by the server.

Logs and errors

The server and the client can generate logs which are helpful to debug code. To log all messages sent by the client you can enable verbose when you build a client.

1
2
3
4
5
const verboseLogging = true;
const useSSL = false;
var client = new nakamajs.Client("defaultkey");
client.verbose = verboseLogging;
socket = client.createSocket(useSSL, verboseLogging);

Full example

An example class used to manage a session with the cocos2d-x JavaScript client.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
var client = new nakamajs.Client("defaultkey");
var currentSession = null;

function storeSession(session) {
  cc.sys.localStorage.setItem("nakamaToken", session.token);
  cc.log("Session stored.");
}

function getSessionFromStorage() {
  return cc.sys.localStorage.getItem("nakamaToken");
}

function restoreSessionOrAuthenticate() {
const email = "hello@example.com";
  const password = "somesupersecretpassword";
  var session = null;
  try {
    var sessionString = getSessionFromStorage();
    if (sessionString && sessionString !== "") {
      session = nakamajs.Session.restore(sessionString);
      var currentTimeInSec = new Date() / 1000;
      if (!session.isexpired(currentTimeInSec)) {
        cc.log("Restored session. User ID: ", session.user_id);
        return Promise.resolve(session);
      }
    }

    return new Promise(function(resolve, reject) {
      client.authenticateEmail(email, password)
        .then(function(session) {
            storeSession(session);
            cc.log("Authenticated successfully. User ID:", session.user_id);
            resolve(session);
          },
          function(error) {
            cc.error("authenticate failed:", JSON.stringify(error));
            reject(error);
          });
    });
  } catch(e) {
    cc.log("An error occurred while trying to restore session or authenticate user:", JSON.stringify(e));
    return Promise.reject(e);
  }
}

restoreSessionOrAuthenticate().then(function(session) {
  currentSession = session;
  return client.writeStorageObjects(currentSession, [{
    "collection": "collection",
    "key": "key1",
    "value": {"jsonKey": "jsonValue"}
  }]);
}).then(function(writeAck) {
  cc.log("Storage write was successful - ack:", JSON.stringify(writeAck));
}).catch(function(e) {
  cc.log("An error occurred:", JSON.stringify(e));
});