Introduction

In this topic we are going to cover how to transition Chat and Messaging from GameSparks to these destination platforms. In GameSparks these two features are often the same thing and are commonly grouped together into GameSparks’ Team Chat feature. However, this feature won't exist on destination platforms and may instead be grouped into another feature or separate features (Teams and Chat).

For transitioning general Chat functionality we need the following features to be available on the destination platform:

Chat

This can be any kind of asynchronous messaging between players connected to a common group. This group could be a team as is the case in GameSparks Team Chat, but it could also be a lobby or chat group unrelated to a team. There might also be global chat features.

Chat is not always handled like it is in GameSparks. In GameSparks Chat and Chat Messages are the same. You can get a list of all your Chat Messages at any point in time and see your Chat history. In some alternative platforms Chat is ephemeral, so you might be able to see your history for the session or lobby you are in at that moment in time. After that session is over you will not be able to see that chat history again.

Therefore we need to investigate if there is an alternative to this feature available for the destination platform too.

Messaging & Inbox

This is different from Chat as it involves how to get a list of messages for the player. Like Chat, this feature is often used to send messages between players, but also for the game-team to send messages to players. This feature is often called the player inbox because of this. It is another way to communicate with the player where they can also see a list of past messages.

The main difference with this feature and Chat is it is not designed to get messages in real-time. This feature would use a call to get all the player’s messages and then the frontend would need to sort those messages in the UI to show which are read and unread.

Messaging API

Along with Team Chat, GameSparks exposed a lot of the underlying Chat APIs in Cloud-Code using the SparkMessage API. This allows GameSparks developers to create their own custom Chat and inbox features to fit their requirements.

Examples of this might include profanity filtering, automatic banning from chat groups, inbox systems for player-to-player mail, news and announcements.

This feature may not be available with all alternative platforms, but we will cover any workarounds where possible.

Note - We will not be covering Push Notifications in this topic, though we will point out where that feature is available on the destination platform and provide links to documentation if available.

Beamable

Beamable do not have the same features to GameSparks in terms of chat and messaging, but they do have some features that can be used to replicate much of the same functionality as GameSparks Team Chat and messaging APIs.

Note - This feature is still in development by Beamable and is likely to change and improve over time. Check with Beamable to see what updates have been made to this feature since we completed this topic.

Rooms

Before we can start setting Chat up in Beamable we first have to introduce a Beamble feature called Rooms.

You can think of these as something like a lobby or a chat-group. They are different from Teams in GameSparks all thought in essence, Rooms are a way to group players together. Beamable has a dedicated feature for this called Groups which we cover in another topic here .

A key feature of Rooms is the ability to send messages to other players in a Room and get automatic notifications of all messages sent within a Room. We will cover a simple example of this here.

Rooms have 5 functions:

These are all accessed through the Rooms Service API and in addition to the functionality above there is a “Subscribe” function which allows you to receive notifications from these Rooms. We will go through this functionality now with some simple Unity examples.

Creating New Rooms

Unlike GameSparks, you don't create a config or template for Rooms. Rooms are created on-the-fly by players using the client SDK.

These calls are simple, you just need to provide an ID for your Room and a list of players. You can ignore the “keepSubscribed” parameter and just set it to ‘true’.

/// <summary>
/// Creates a new room with the given name and players
/// </summary>
/// <param name="roomName">Room Name</param>
/// <param name="roomName">Room Name</param>
async void CreateRoom(string roomName, List<long> playerList)
{
   Debug.Log($"Creating New Room {roomName}...");
   RoomInfo newRoomInfo =  await beamableAPI.Experimental.ChatService.CreateRoom(roomName, true, playerList);
   Debug.Log($"New Room ID: {newRoomInfo.id}...");
}

Note - The player who created the group should also be added to the player list.

This API allows you to add any list of players to your new Room. This is therefore a good way to recreate GameSparks’ Team-Chat functionality. Rooms persist, so once a Room is created out of a Group, you can re-subscribe to it at any time to start getting messages from other members.

Searching Rooms

Searching for rooms is a bit more limited than with the Groups features.

You can view all the rooms you are a part of. These are either your rooms or rooms you have been added to.

The API here is simple. Below is a simple example including some of the details you can get from the RoomInfo class.

/// <summary>
/// Gets a list of Rooms available to the player
/// </summary>
/// <returns>roomList</returns>
public async List<RoomInfo> GetRooms()
{
   List<RoomInfo> roomList = await beamableAPI.Experimental.ChatService.GetMyRooms();
   foreach (RoomInfo room in roomList)
   {
       Debug.Log($"ID: {room.id}, name: {room.name}, players: {room.players.Count}");
   }
   return roomList;
}

Using this example you will be able to see your new room in the console, but you will also see another room that you haven't created.

This general.chat room is a global room which can be used for global chat.

Subscribing to a Room

Next we need to subscribe to the room, so that we can get notifications when anything changes about the room. Specifically, we want to get notifications for messages or for when new members subscribe or leave.

/// <summary>
/// Subscribe to a room
/// </summary>
/// <param name="roomId">Id of the room</param>
private void Subscribe(string roomId)
{
   Debug.Log($"Subscribing to room {roomId}...");
   // We can do this by assigning a message on player-leave callback, along with a subscriber to listen for changes //
   beamableAPI.Experimental.ChatService.Subscribe((chatView) =>
   {
       foreach (var room in chatView.roomHandles)
       {
           if (room.Id == roomId)
           {
               Debug.Log($"Subscribed To Room {room.Id}...");
               room.OnRemoved += OnPlayerLeave;
               room.OnMessageReceived += OnMessage;
               room.Subscribe().Then(_ =>
               {
                   Debug.Log("Room Updated...");
                   foreach (var pid in room.Players)
                   {
                       Debug.Log(pid);
                   }

               });
               return;
           }
       }
   });
}

You can see here we have callbacks assigned for receiving messages or when a player leaves the room (the OnRemoved callback).

These methods are straightforward. For the OnMessage callback, you can get some basic information about the message including the player’s Id which is shown as gamerTag field below.

private void OnMessage(Message newMess)
{
   Debug.Log($"Received Message {newMess.content} to RoomId: {newMess.gamerTag}...");
}

private void OnPlayerLeave()
{
   Debug.Log($"Player Left...");
}

You can also create an “Unsubscribe” method using those options in the RoomHandle class, for example…

foreach (var room in chatView.roomHandles)
{
   if (room.Id == roomId)
   {
       room.LeaveRoom();
       room.Unsubscribe();
       return;
   }
}

Now you can test this player-to-player to see if the messages are being passed through. Remember, if you create a room, you need both players to be added in order to pass messages between them both. The global chat room can be subscribed to by anyone.

There are more features available for Chat. For example there is a profanity filter available through the Chat Service API. We won't cover that in this topic but there is more information available on that here.

Mail

Although it is possible to receive and list messages using Beamable’s Chat Service, it is not intended for long term storage of messages for example in the case of an inbox feature. However, Beamable’s Mail Service does allow you to create a system where you can send and receive messages and list them for long term storage. Unlike GameSparks, players will not receive notifications for these messages. Instead they are meant to be refreshed infrequently.

Beamable’s Mail Service has the following functionality:

It is important to note that sending mail is restricted to admin users only.

This means that any player can get or update their own mail but they cannot send mail from the client.

This is because Beamable’s Mail Service is intended to be used to send mail to players directly from the developers and live-ops managers. It can be used for announcements, news or messaging campaigns.

Note - There is another feature of Beamable called announcement which differs from mail. You can check that out here.

Sending Mail

As already mentioned, mail is only supposed to be sent by an Admin user. This is because the Mail Service is designed to send bulk messages to players. You can see an example of this here.

However, Microservices also act in an Admin capacity for the Mail Service and therefore you can create a custom Microservice that will allow players to send messages P2P.

We will show an example below of how to send these messages. It is very similar to the client example on Beamable’s documentation site here so you can refer to that example for some of the other functionality of the Mail Service that we won't cover here. We also won't show how to create new Microservices here, there is a guide on that in our Cloud-Code topic here.

For this example we will send a subject and body into our Microservice function, along with the player Id of the player we want to send the message to.

public async void SendMail(long recipientId, string subject, string body)
{
  Debug.Log("Sending Mail...");
  // Construct our mail message and send it //
  var mailSendRequest = new MailSendRequest();
  var mailSendEntry = new MailSendEntry();
  mailSendEntry.category = "player";
  mailSendEntry.senderGamerTag = Context.UserId;
  mailSendEntry.receiverGamerTag = recipientId;
  mailSendEntry.subject = subject;
  mailSendEntry.body = body;
  mailSendRequest.Add(mailSendEntry);

  // Call may fail if sender lacks permissions
  bool isSuccess = true;
  try
  {
     var emptyResponse = await Services.Mail.SendMail(mailSendRequest);
  }
  catch (Exception e)
  {
     Debug.LogError(e.Message);
     isSuccess = false;
  }

  if (isSuccess)
  {
     Debug.Log("Mail Sent...");
  }
}

Fetching Mail

After executing the call above, we need to be able to see if the recipient got the message. The easiest way to do this is with the GetMail() method of the Mail Service API. There are other methods available such as GetCurrent() which gets you the count of unread messages and SearchMail() which lets you filter messages.

We will show a simple example using the GetMail() method here. Note that this is done through the client, not the Microservice.

/// <summary>
/// Fetches the player's messages
/// </summary>
async void GetMail()
{
   Debug.Log("Fetching Mail...");
   // get the top 100 messages //
   ListMailResponse messageList = await beamableAPI.MailService.GetMail("player");
   // go through the message list //
   foreach (var message in messageList.result)
   {
       Debug.Log($"Message Id: {message.id}");
       Debug.Log($"Sender: {message.senderGamerTag}");
       Debug.Log($"Message Subject: {message.subject}");
       Debug.Log($"Message Body: {message.body}");
   }
}

There are other attributes of these mail messages you can get from this response but we only need to show a few here.

Additional Mail Features

We have only covered the basics of Beamable’s Mail Service here as it relates to GameSparks feature parity. However, there are other features that are not included in GameSparks.

An important one is the ability to send bulk messages using this service. This is not done through a Microservice but instead uses the REST API linked in the section above.

Another feature that might be useful is the ability to deliver Items and Currency through mail. This is a parameter object set on the mailSendEntry we created in the Microservice. There is more information on that option here.

Push Notifications

Push notifications are only available through Firebase Cloud Messaging. The process works similarly to GameSparks where the client needs to register before notifications can be sent.

beamableAPI.PushService.Register(PushProvider.Apple, token);

You can use the PushProvider enum to choose iOS or Android notifications. These notifications can then be sent from a Microservice.

AccelByte

Although AccelByte does have a chat feature, it does differ from GameSparks. The biggest difference is that chat is separate from the general “messaging” API and are not coupled together like GameSparks’s Team-Chat and SparkMessage API.

The Party-Chat feature works the same as GameSparks’ Team-Chat. We have already covered parties in some detail in the Matchmaking topic here, but we will go through this again briefly with some examples. Personal Chat is pretty much the same, but is used for player-to-player messaging.

All of these features work through the Lobby Service which we have covered in the Matchmaking service and which allows players to get notifications through a websocket.

It is important to note that this Party-Chat and Global-Cat is ephemeral, so you cannot get a list of messages or chat history like you can using GameSparks’ requests or SparkMessage API. This is specifically for chat-groups or chat-lobbies. However, Personal-Chat does allow you to get chat history so it could be used to replace GameSparks messaging APIs if you have something like a player-inbox feature custom-built.

Party Chat

For Party-Chat, players can send messages to other players in a party which is similar to Team-Chat or Chat messages in GameSparks.

Players should already be grouped into a party to enable this functionality. We have already covered the tutorial on how to set up parties in the MatchMaking topic linked above. Players in a party need to send party invitations and players can join the room. There are other features available in Party service and if you want to read more about it, you can refer here.

Note - Players need to be connected to the Lobby service to send and receive invitations.

Just as a recap of the other topics where we dealt with parties, players can send an invite to a specific user with the below API call.

abLobby = AccelBytePlugin.GetLobby();
abLobby.Connect();

//Sending an invite
abLobby.InviteToParty(inviteeUserId, InviteToPartyCallback);

/// <summary>
/// Invitation callback after sending out an invite
/// </summary>
private void InviteToPartyCallback(Result result)
{

       if (result.IsError)
       {
          Debug.Log($"Error. Code: {result.Error.Code} Reason: {result.Error.Message}”);
       }
       else
       {
           Debug.Log("Successfully invite an invitee");
       }        
}

Next, we need some way to notify players that they have been invited to the party. This can be done using the InvitedToParty delegate below.

// Method subscription to notification event
abLobby.InvitedToParty += InvitedToPartyCallback;

//Notification information regarding party invitation
private void InvitedToPartyCallback(Result<PartyInvitation> result)
{
    partyInvitation = result.Value;
    if (!result.IsError)
    {
            Debug.Log($"[Notification] Invited by:{result.Value.from}");
    }
}

Below is the response we received when we sent an invitation to another player.

A player with an invitation can accept the invitation and join the group with the below API call.

abLobby.JoinParty(partyInvitation.partyID, partyInvitation.invitationToken, JoinPartyCallback);

/// <summary>
/// Party Information from join party callback
/// </summary>
/// <param name="result">PartyInfo like party leader, party members, invitees etc.</param>
private void JoinPartyCallback(Result<PartyInfo> result)
{
      if (result.IsError)
      {
           Debug.Log($"Error. Code: {result.Error.Code} , Reason: {result.Error.Message}");
      }
      else
      {
           Debug.Log("Successfully join a party");
            int i = 0;
           foreach(string partyMember in result.Value.members)
            {
                i++;
                Debug.Log($"Party Member{i}: {partyMember}");
            }
      }
}

If the request was a success, you will see the two members printed in the console.

Now that we have created a party, those party members can chat with each other.

As you might expect, we can do two things in Party-Chat,

There is an example of these API calls below...

//Sending a party message
abLobby.SendPartyChat("Hello, party people!!", SendPartyChatCallback);

/// <summary>
/// Callback after sending out a message to other party members
/// </summary>
private void SendPartyChatCallback(Result result)
{
     if (result.IsError)
     {
         Debug.Log($"Failed to send a private message! Code: {result.Error.Code}");

     }
     else
     {
         Debug.Log("Successfully send a party message!");
     }
}

Other members who are in the party will receive the message. Party members need some way to notify themselves when the message arrives. There is an example below of how to subscribe to the party to get notifications.

//Method subscribing to message received notification event
abLobby.PartyChatReceived += OnPartyChatReceived;

/// <summary>
/// Method will trigger when the player receives a party message
/// </summary>
/// <param name="result">Sender, receiver and actual message</param>
private void OnPartyChatReceived(Result<ChatMessage> result)
{
     Debug.Log($"{result.Value.from} sent a private message to you {result.Value.to}");
     Debug.Log($"Message : {result.Value.payload}");
}

In our example you will remember that we only have two players so below is the message received by a second party member.

Note - Your game namespace must have chat enabled in Lobby configuration under the “Lobby and Matchmaking” section in the Admin portal to send messages.

Personal Chat

Although Party-Chat can send messages to party members, players cannot keep track of chat history. This is by design, as chat is meant to be real-time and ephemeral as we already mentioned. In other-words the player must be connected to the Lobby Service in order to get messages.

With the Personal-Chat feature, we also get real-time notifications, but we can also request chat history as messages are stored after your player disconnects from the Lobby Service.

However, there is no client API for getting chat-history from the client, so in order to do this we need to request chat history using a HTTP.

As with Party-Chat, there are two requests we can make from the client for Personal-Chat.

The player can send a personal chat message to any desired player with their userid as shown in the below API call.

string userId = "d380d40a08d1481ba06b1dcdceXXXXX";
string chatMessage = "Hi! How are you?";

// Sending a personal chat message to the player
abLobby.SendPersonalChat(userId, chatMessage, OnSendPersonalChatToPlayer);

/// <summary>
/// Callback after sending a personal chat message to any player
/// </summary>
private void OnSendPersonalChatToPlayer(Result result)
{
     if (!result.IsError)
     {
         Debug.Log("Message sent successfully.");
     }
     else
     {
            Debug.Log($"Failed to send a private message! Code: {result.Error.Code}");
     }
}

The player who is entitled to receive the message needs to subscribe to receive notifications by assigning a delegate to the PersonalChatReceived callback.

We can easily subscribe to the notification event as shown below.

//Method subscription to the nofication
abLobby.PersonalChatReceived += OnPersonalChatReceived;

/// <summary>
/// Method will trigger when the player receives any personal chat message
/// </summary>
/// <param name="result">Information regarding sender, receiver, actual message..etc</param>
private void OnPersonalChatReceived(Result<ChatMessage> result)
{
       Debug.Log($"{result.Value.from} send a private message to you                    {result.Value.to}");
       Debug.Log("Message: " + result.Value.payload + "\n");
}

Using this example we can see the message in the Unity console

As mentioned already, accessing is not done using the client SDK. There is another flow to achieve this functionality as shown in the diagram below.

Note - We have used HTTP web-requests from system.net class and NewtonSoft JSON for deserialization. We will not go into details about this process but you can see more information about web requests here.

Getting The Access Token

We need to call the POST method to send data to retrieve an access token of the user. There are two important parameters highlighted in the code-example below@

/// <summary>
/// Retrieving an accesstoken for the user
/// </summary>
public void TokenGrant()
{
        WebRequest request = WebRequest.Create("https://demo.accelbyte.io/iam/oauth/token");
        request.Method = "POST";
        request.Headers.Add("Authorization", "Basic" +  MWU2xxxxxxxxxx);
        request.ContentType = "application/x-www-form-urlencoded";

        string postData = "grant_type=password&username=xxxxxxxx&password=xxxxxxxxxx&namespace=xxxxxxxx";

        byte[] byteArray = Encoding.UTF8.GetBytes(postData);
        request.ContentLength = byteArray.Length;
        Stream dataStream = request.GetRequestStream();
        dataStream.Write(byteArray, 0, byteArray.Length);
        dataStream.Close();
        WebResponse response = request.GetResponse();
        Debug.Log(((HttpWebResponse)response).StatusDescription);

        using (dataStream = response.GetResponseStream())
        {
            StreamReader reader = new StreamReader(dataStream);
            string responseFromServer = reader.ReadToEnd();
            Debug.Log(responseFromServer);
            var parsedObject = JObject.Parse(responseFromServer);
            var accesstoken = parsedObject["access_token"].ToString();
            Debug.Log(accesstoken);
            LoadPersonalChatHistory(accesstoken);
        }
        response.Close();
}

Once we have received the access token, we need to get the chat history. We have an example of this using a custom method called LoadPersonalChatHistory() which we will cover below.

As with the previous HTTP call there are two parameters highlighted in this request:

/// <summary>
/// Model for chat history JSON response
/// </summary>
class ResponseDataAB
{
        public int id { get; set; }
        public string from { get; set; }
        public string to { get; set; }
        public string payload { get; set; }
        public int receivedAt { get; set; }
}

/// <summary>
/// Loading the chat history
/// </summary>
/// <param name="accesstoken"> Access token from TokenGrant function</param>
public void LoadPersonalChatHistory(string accesstoken)
{
        Debug.Log("Loading personal chat history");
        WebRequest request = WebRequest.Create("https://demo.accelbyte.io/chat/namespaces/{your-namespace}/users/{CallerUserId}/friend/{ReceiverUserId}");

        request.Headers.Add("Authorization", "Bearer " + accesstoken);
        request.ContentType = "application/json";
        HttpWebResponse response = (HttpWebResponse)request.GetResponse();
        Stream dataStream = response.GetResponseStream();
        StreamReader reader = new StreamReader(dataStream);
        string responseFromServer = reader.ReadToEnd();
        var responseData =    JsonConvert.DeserializeObject<List<ResponseDataAB>>(responseFromServer);
        responseData.ForEach(res => Debug.Log(res.payload));

        reader.Close();
        dataStream.Close();
        response.Close();
}

Note - The above HTTP request can only work if the user has the permission NAMESPACE:{namespace}:USER:{userId}:CHAT [READ].

You can also see in the example above that we have created a new class called “ResponseDataAB”. This is so that we can parse the JSON string response to an object we can work with in AB. You can create your own custom object of course, but, if you have gone through all the steps mentioned above, you will see your player’s chat-history in the Unity console.

Global Chat

As a minor note which we won't cover in this topic, AccelByte provides another feature of their Chat service called Global Chat. This feature allows all players in the game to communicate.

Nakama

Nakama approaches chat differently to GameSparks Team Chat, however you can achieve the same functionality as GameSparks Team Chat using Nakama’s chat system.

Nakama also offers several chat features which are not available in GameSparks which you may find useful.

Chat Rooms

The first thing we will look at is chat rooms. These use the same system as Nakama’s Group Chat feature which is similar to GameSparks’ Team Chat, however anyone can make or join a chat room while Group Chat is only for members of a specific Group.

Chat rooms can also be used for global chat or for chat rooms that you have setup per country or region.

Once chat rooms are created there is no API for getting a list of these rooms so that users can choose which rooms to join. So if this functionality is needed you should consider creating an entry in the Nakama Storage Engine which you can query later. Check out the topic on Cloud-Code here for more details on how to achieve that.

The client calls for joining a chat room and sending messages are very simple and are covered here in Nakama’s documentation so we wont show them again here but there is a modified example of this request in the Unity Example section below.

Remember to set up the socket.ReceivedChannelMessage callback so you will start to receive messages over the socket once your rooms are created.

Group Chat

Group chat in Nakama is the closest thing to GameSpark Team Chat. Groups are another name for the Teams feature in Nakama.

The same features we discussed above on chat-rooms apply to Group chat, however Group chat is private. Only other members of the Group will be able to communicate with each other.

The APIs for Group chat are similar to rooms mentioned above and are covered in Nakama’s documentation here.

The Channel ID for any Group chat messages picked up by the ReceivedChannelMessage listener will be the Group ID.

Private Chat

With Nakama it is also possible to send private messages directly to players. This works the same as chat rooms but with the player’s ID in place of the channel ID.

This means that you need to use the JoinChatAsync() method before being able to send or receive messages. This will therefore mean some refactoring of your GameSparks code as the registration and callback need to be set up first.

You can check out some examples in Nakama’s documentation page on Chat here.

Inbox & Notifications

Nakama stores all chat history provided you set the ‘persistence’ parameter to ‘true’ when joining a channel.

This allows you to get the channel's history and sync it with the client if needed. There are some examples of how to work with message history here.

However, this might not be suitable for something like an inbox system where the player needs a history of all messages sent to them from direct messages to in-app rewards or event announcements.

For this you can use notifications instead of the chat system. Nakama has excellent documentation and examples on this feature here so we won't cover it again in detail in this topic.

There is an example on how to use this feature to send Achievement notifications in our topic on Achievements here.

Server-Side Message Validation

A common use-case for GameSparks Cloud-Code is to use server-side validation on chat messages before sending them onto players so let's take a look at how we can achieve that using some message hooks.

What we will do is create a function which will be called whenever a message is posted to a channel. We will extract our message data, validate it, and then allow it to be passed into the channel or stop it from being passed on and return an error to the client.

Note - There is already an example in the Cloud-Code on how to reach out to a 3rd party service using Nakama’s HTTP API, so in this example we will instead do a random coin-flip so we quickly cover how to intercept messages, pass on valid messages and return custom errors to the client, without adding the complexity of the HTTP request. Check out the Cloud-Code topic for an example on profanity validation.

/**
* This hook is raised whenever a message is sent to a channel
* @param context
* @param logger
* @param nk
* @param envelope - message payload //envelope.channelMessageSend.content//
* @returns {Envelope}
*/
function onChannelMessageSend(context: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, envelope: any): nkruntime.Envelope {
   var messageData = JSON.parse(envelope.channelMessageSend.content);
   logger.info(`Validating message [${messageData.message}]`);

   // << VALIDATE MESSAGE HERE >> //
   // https://heroiclabs.com/docs/nakama/server-framework/function-reference/#http //

   // for this example we just want to show the successful case or the error case //
   // so we'll do something simple like a coin toss //
   let isValid = (Math.floor(Math.random() * 2) == 0);
   if (isValid) {
       // pass on the envelope to allow the message through //
       logger.info("Validation check passed...");
       return envelope;
   } else {
       // throw an error to stop the message from being sent //
       logger.info("Validation check passed...");
       throw JSON.stringify({
           "code": 123,
           "error": "validation-failed"
       });
   }
}

You can see here, if we want to pass through the message we return the envelope parameter and if we want to return an error instead we can throw an error. We will take a look at how to detect that error on the client later.

The next thing we need to do is register this callback in the main.ts script’s InitModule function

// Set message hooks //
   initializer.registerRtBefore("ChannelMessageSend", onChannelMessageSend);

Reminder - Remember to add file references to the tsconfig.json file and rebuild and redeploy the server before testing.

You can see from the docs here that there are a number of reserved message hooks and instead of registering them directly like we would for one of the before and after hooks, we need to give the specific name of the hook in order to register them.

Keep this list in mind as it will be a useful reference for porting other hooks later in your transition.

Unity Example

We can pick these errors up using a simple try-catch. Because we sent back a JSON string for the error you will be able to parse that to handle all the different server error cases you need.

/// <summary>
/// Send a chat message to the given channel
/// </summary>
/// <param name="channelID"></param>
/// <param name="message"></param>
private async void SendChatRoomMessage(string channelID, string message)
{
   Debug.Log($"Sending Message {message} Chat Room {channelID}");
   var content = new Dictionary<string, string> {{"message", message}};
   var payload = Nakama.TinyJson.JsonWriter.ToJson(content);
   try
   {
       var sendMessage = await sessionSocket.WriteChatMessageAsync(channelID, payload);
   }
   catch (Exception e)
   {
       Debug.Log(e.Message);
       // Handle error how you wish //
   }
}