Skip to main content
Version: v3

2. Advanced Messaging Features, Push Notifications, Synchronization, and Multi-Device Sign-on

Introduction

In the previous chapter, Basic Conversations and Messages, we introduced how you can create components in your app that support one-on-one chats and group chats, as well as how you can handle events triggered by the cloud. In this chapter, we will show you how to implement advanced features like:

  • Getting receipts when messages are delivered and read
  • Mentioning people with "@"
  • Recalling and editing messages
  • Push notifications and message synchronization
  • Single-device or multi-device sign-on
  • Sending messages of your custom types

Advanced Messaging Features

If you are building an app for team collaboration or social networking, you may want more features to be included besides basic messaging. For example:

  • To mention someone with "@" so that they can easily find out what messages are important to them.
  • To edit or recall a message that has been sent out.
  • To send status messages like "Someone is typing".
  • To allow the sender of a message to know if it's delivered or read.
  • To synchronize messages sent to a receiver that has been offline.

With Instant Messaging, you can easily implement the functions mentioned above.

Mentioning People

Some group chats have a lot of messages going on and people may easily overlook the information that's important to them. That's why we need a way for senders to get people's attention.

The most commonly used way to mention someone is to type "@ + name" when composing a message. But if we break it down, we'll notice that the "name" here is something determined by the app (it could be the real name or the nickname of the user) and could be totally different from the clientId identifying the user (since one is for people to see and one is for computers to read). A problem could be caused if someone changes their name at the moment another user sends a message with the old name mentioned. Besides this, we also need to consider the way to mention all the members in a conversation. Is it "@all"? "@group"? Or "@everyone"? Maybe all of them will be used, which totally depends on how the UI of the app is designed.

So we cannot mention people by simply adding "@ + name" into a message. To walk through that, two additional properties are given to each message (LCIMMessage):

  • mentionList, an array of strings containing the list of clientIds being mentioned;
  • mentionAll, a Bool indicating whether all the members are mentioned.

Depending on the logic of your app, it's possible for you to have both mentionAll to be set and mentionList to contain a list of members. Your app shall provide the UI that allows users to type in and select the members they want to mention. The only thing you need to do with the SDK is to call the setters of mentionList and mentionAll to set the members being mentioned. Here is a code example:

LCIMTextMessage textMessage = new LCIMTextMessage("@Tom Come back early.") {
MentionIdList = new string[] { "Tom" }
};
await conversation.Send(textMessage);

You can also mention everyone by setting mentionAll:

LCIMTextMessage textMessage = new LCIMTextMessage("@all") {
MentionAll = true
};
await conv.Send(textMessage);

The receiver of the message can call the getters of mentionList and mentionAll to see the members being mentioned:

jerry.onMessage = (conv, msg) => {
List<string> mentionIds = msg.MentionIdList;
};

To make it easier to display things on the UI, the following two flags are offered by LCIMMessage to indicate the status of mentioning:

  • mentionedAll: Whether all the members in the conversation are mentioned. Becomes true only when mentionAll is true, otherwise it remains false.
  • mentioned: Whether the current user is mentioned. Becomes true when mentionList contains the clientId of the current user or when mentionAll is true, otherwise it remains false.

Here is a code example:

client.OnMessage = (conv, msg) => {
bool mentioned = msg.MentionAll || msg.MentionList.Contains("Tom");
};

Modify a Message

To allow users to edit the messages they sent, you need to enable Allow editing messages with SDK on Developer Center > Your game > Game Services > Cloud Services > Instant Messaging > Settings > Instant Messaging settings. There are no limits on the time within which users can perform this operation. However, users are only allowed to edit the messages they sent, not the ones others sent.

To modify a message, what you would do is not update the original message instance, but create a new one and call Conversation#updateMessage(oldMessage, newMessage) to submit the request to the cloud. Here is a code example:

LCIMTextMessage newMessage = new LCIMTextMessage("The new message.");
await conversation.UpdateMessage(oldMessage, newMessage);

If the modification succeeded, other members in the conversation will receive a MESSAGE_UPDATE event:

tom.OnMessageUpdated = (conv, msg) => {
if (msg is LCIMTextMessage textMessage) {
WriteLine($"Content: {textMessage.Text}; Message ID: {textMessage.Id}");
}
};

For Android and iOS SDKs, if caching is enabled (it is enabled by default), the SDKs will first update the modified message in the cache and then trigger an event to the app. When you receive such an event, simply refresh the chatting page to reflect the latest collection of messages.

If a message is modified by the system (for example, due to text moderation or by a hook on Cloud Engine), the sender will receive a MESSAGE_UPDATE event, and other members in the conversation will receive the modified message.

Recall a Message

Besides modifying a sent message, a user can also recall a message they sent. Similarly, you need to enable Allow recalling messages with SDK on Developer Center > Your game > Game Services > Cloud Services > Instant Messaging > Settings > Instant Messaging settings. Also, there are no limits on the time within which users can perform this operation, and users are only allowed to recall the messages they sent, not the ones others sent.

To recall a message, invoke the Conversation#recallMessage method:

await conversation.RecallMessage(message);

Once a message is recalled, other members in the conversation will receive the MESSAGE_RECALL event:

tom.OnMessageRecalled = (conv, recalledMsg) => {
// recalledMsg is the message being recalled
};

For Android and iOS SDKs, if caching is enabled (it is enabled by default), the SDKs will first delete the recalled message from the cache and then trigger an event to the app. This ensures the consistency of data internally. When you receive such an event, simply refresh the chatting page to reflect the latest collection of messages. Depending on your implementation, either the recalled message will simply disappear, or an indicator saying the message has been recalled will take the original message's place.

Transient Messages

Sometimes we need to send status updates like "Someone is typing…" or "Someone changed the group name to XX". Different from messages sent by users, these messages don't need to be stored in the history, nor do they need to be guaranteed to be delivered (if members are offline or there is a network error, it would be okay if these messages are not delivered). Such messages are best sent as transient messages.

Transient message is a special type of message. It has the following differences compared to a basic message:

  • It won't be stored in the cloud so it couldn't be retrieved from history messages.
  • It's only delivered to those who are online. Offline members cannot receive it later or get push notifications about it.
  • It's not guaranteed to be delivered. If there's a network error preventing the message from being delivered, the server won't make a second attempt.

Therefore, transient messages are best for communicating real-time updates of statuses that are changing frequently or implementing simple control protocols.

The way to construct a transient message is the same as that for a basic message. The only difference is the way it is being sent. So far we have shown the following way of sending messages with LCIMConversation:

public async Task<LCIMMessage> Send(LCIMMessage message, LCIMMessageSendOptions options = null);

In fact, an additional parameter LCIMMessageOption can be provided when sending a message. Here is a complete list of interfaces offered by LCIMConversation:

/// <summary>
/// Sends a message in this conversation.
/// </summary>
/// <param name="message">The message to send.</param>
/// <returns></returns>
public async Task<LCIMMessage> Send(LCIMMessage message, LCIMMessageSendOptions options = null);

With LCIMMessageOption, we can specify:

  • Whether it is a transient message (field transient).
  • Whether receipts are needed (field receipt; more details will be covered later).
  • The priority of the message (field priority; more details will be covered later).
  • Whether it is a will message (field will; more details will be covered later).
  • The content for push notification (field pushData; more details will be covered later); if the receiver is offline, a push notification with this content will be triggered.

The code below sends a transient message saying "Tom is typing…" to the conversation when Tom's input box gets focused:

LCIMTextMessage textMessage = new LCIMTextMessage("Tom is typing…");
LCIMMessageSendOptions option = new LCIMMessageSendOptions() {
Transient = true
};
await conversation.Send(textMessage, option);

The procedure for receiving transient messages is the same as that for basic messages. You can run different logic based on the types of messages. The example above sets the type of the message to be text message, but it would be better if you assign a distinct type to it. Our SDK doesn't offer a type for transient messages, so you may build your own depending on what you need. See Custom Message Types for more details.

Receipts

When the cloud is delivering messages, it follows the sequence the messages are pushed to the cloud and delivers the former messages before the latter ones (FIFO). Our internal protocol also requires that the SDK sends an acknowledgment (ack) back to the cloud for every single message received by it. If a message is received by the SDK but the ack is not received by the cloud due to a packet loss, the cloud would assume that the message is not successfully delivered and will keep redelivering it until an ack is received. Correspondingly, the SDK also does its work to make duplicate messages insensible by the app. The entire mechanism ensures that no messages will be lost in the entire delivery process.

However, in certain scenarios, functionality beyond the one mentioned above is demanded. For example, a sender may want to know when the receiver got the message and when they opened the message. In a product for team collaboration or private communication, a sender may even want to monitor the real-time status of every message sent out by them. Such requirements can be satisfied with the help of receipts.

Similar to the way of sending transient messages, if you want receipts to be given back, you need to specify an option in LCIMMessageOption:

LCIMTextMessage textMessage = new LCIMTextMessage("A very important message.");
LCIMMessageSendOptions option = new LCIMMessageSendOptions {
Receipt = true
};
await conversation.Send(textMessage, option);

Note:

Receipts are not enabled by default. You need to manually turn that on when sending each message. Receipts are only available for conversations with no more than 2 members.

So how do senders handle the receipts they get?

Delivery Receipts

When a message is delivered to the receiver, the cloud will give a delivery receipt to the sender. Keep in mind that this is not the same as a read receipt.

// Tom creates an LCIMClient with his name as clientId
LCIMClient client = new LCIMClient("Tom");
// Tom logs in
await client.Open();

// Enable delivery receipt
client.OnMessageDelivered = (conv, msgId) => {
// Things to do after messages are delivered
};
// Send message
LCIMTextMessage textMessage = new LCIMTextMessage("Wanna go to the bakery tonight?");
await conversation.Send(textMessage);

The content included in the receipt will not be a specific message. Instead, it will be the time the messages in the current conversation are last delivered (lastDeliveredAt). We have mentioned earlier that messages are delivered according to the sequence they are pushed to the cloud. Therefore, given the time of the last delivery, we can infer that all the messages sent before it are delivered. On the UI of the app, you can mark all the messages sent before lastDeliveredAt to be "delivered".

Read Receipts

When we say a message is delivered, what we mean is that the message is received by the client from the cloud. At this time, the actual user might not have the conversation page or even the app open (Android apps can receive messages in the background). So we cannot assume that a message is read just because it is delivered.

Therefore, we offer another kind of receipt showing if a receiver has actually seen a message.

Again, since messages are delivered in time order, we don't have to check if every single message is being read. Think about a scenario like this:

A pop-up with the title &quot;Welcome Back&quot; says &quot;You have 5002 unread messages. Would you like to skip them all? (Select &#39;Yes&#39; to mark them as read)&quot;. There are two buttons on the bottom: &quot;Yes&quot; and &quot;No&quot;.

When a user opens a conversation, we can say that the user has read all the messages in it. You can use the following interface of Conversation to mark all the messages in it as read:

/// <summary>
/// Mark the last message of this conversation as read.
/// </summary>
/// <returns></returns>
public Task Read();

After the receiver has read the latest messages, the sender will get a receipt indicating that the messages they have sent out are read.

So if Tom is chatting with Jerry and wants to know if Jerry has read the messages, the following procedure would apply:

  1. Tom sends a message to Jerry and requests receipts on it:

    LCIMTextMessage textMessage = new LCIMTextMessage("A very important message.");
    LCIMMessageSendOptions options = new LCIMMessageSendOptions {
    Receipt = true
    };
    await conversation.Send(textMessage);
  2. Jerry reads Tom's message and calls read on the conversation to mark the latest messages as read:

    await conversation.Read();
  3. Tom gets a read receipt with the conversation's lastReadAt updated. The UI can be updated to mark all messages sent before lastReadAt to be read:

    tom.OnLastReadAtUpdated = (conv) => {
    // The message is read by Jerry; the time Jerry read messages for the last time can be retrieved by calling conversation.LastReadAt
    };

Note:

To use read receipts, turn on notifications on updates of unread message count when initializing your app.

Muting Conversations

If a user doesn't want to receive notifications from a conversation but still wants to stay in it, they can mute the conversation. See Muting Conversations in the next chapter for more details.

Will Messages

Will message can be used to automatically notify other members in a conversation when a user goes offline unexpectedly. It gets its name from the wills filed by testators, giving people a feeling that the last messages of a person should always be heard. It looks like the message saying "Tom is offline and cannot receive messages" in this image:

In a conversation named &quot;Tom &amp; Jerry&quot;, Jerry receives a will message saying &quot;Tom is offline and cannot receive messages&quot;. The will message looks like a system notification and shares a different style with other messages.

A will message needs to be composed ahead of time and cached on the cloud. The cloud doesn't send it out immediately after receiving it. Instead, it waits until the sender of it goes offline unexpectedly. You can implement your own logic to handle such an event.

LCIMTextMessage message = new LCIMTextMessage("I am a will message. I will be sent out to other members in the conversation when the sender goes offline unexpectedly.");
LCIMMessageSendOptions options = new LCIMMessageSendOptions {
Will = true
};
await conversation.Send(message, options);

Once the sender goes offline unexpectedly, other members will immediately receive the will message. You can design your own way to display it on the UI.

Will message has the following restrictions:

  • Each user can only have one will message set up at a time. This means that if a user sets will messages for multiple conversations or multiple will messages for the same conversation, only the last one will take effect.
  • Will messages don't get stored in the history.
  • If a user logs out proactively, the will message set by this user will not be sent out (if there is one).

Text Moderation

If your app allows users to create group chats, you might consider filtering cuss words from the messages sent by users. Instant Messaging offers a built-in component that helps you easily implement this function. See Text Moderation in the next chapter for more details.

Handling Undelivered Messages

Sometimes you may need to store the messages that are not successfully sent out into a local cache and handle them later. For example, if a client's connection to the server is lost and a message cannot be sent out due to this, you may still keep the message locally. Perhaps you can add an error icon and a button for retrying next to the message displayed on the UI. The user may tap on the button when the connection is recovered to make another attempt to send the message.

By default, both Android and iOS SDKs enable a local cache for storing messages. The cache stores all the messages that are already sent to the cloud and keeps itself updated with the data in the cloud. To make things easier, undelivered messages can also be stored in the same cache.

The code below adds a message to the cache:

// Not supported yet

The code below removes a message from the cache:

// Not supported yet

When reading messages from the cache, you can make messages look different on the UI based on the property message.status. If the status of a message is LCIMMessageStatusFailed, it means the message cannot be sent out, so you can add a button for retrying on the UI. An additional benefit of using the local cache is that the SDK will make sure the same message only gets sent out once. This ensures that there won't be any duplicate messages on the cloud.

Push Notifications

If your users are using your app on mobile devices, they might close the app at any time, which prevents you from delivering new messages to them in the ordinary way. At this time, using push notifications becomes a good alternative to get users notified when new messages are coming in.

If you are building an iOS or Android app, you can utilize the built-in push notification services offered by these operating systems, as long as you have your certificates configured for iOS or have the function enabled for Android. Check the following pages for more details:

  1. Instant Messaging Overview
  2. Android Push Notification Guide / iOS Push Notification Guide

The cloud will associate the clientIds of users with the data in the _Installation table that keeps track of the devices. When a user sends a message to a conversation, the cloud will automatically convert the message to a push notification and send it to those who are offline but are using iOS devices or using Android devices with push notification services enabled. We also allow you to connect third-party push notification services to your app.

The highlight of this feature is that you can customize the contents of push notifications. You have the following three ways to specify the contents:

  1. Setting up a static message

    You can fill in a global static JSON string on Developer Center > Your game > Game Services > Cloud Services > Instant Messaging > Settings > Push notifications for delivering push notifications with a static message. For example, if you put:

    { "alert": "New message received", "badge": "Increment" }

    Then whenever there is a new message going to an offline user, the user will receive a push notification saying "New message received".

    Keep in mind that badge is for iOS devices only which means to increase the number displayed on the badge of the app. Its value, Increment, is case-sensitive. Typically, when an end user opens or closes the application, you need to set the value of the badge field of the _Installation class to zero, which clears the badge number.

    Besides, you can also customize the sounds of push notifications for iOS devices.

  2. Specifying contents when sending messages from a client

    When using the first way introduced above, the content included in each push notification is the same regardless of the message being sent out. Is it possible to dynamically generate these contents to make them relevant to the actual messages?

    Remember how we specified LCIMMessageOption when sending transient messages? The same parameter takes in a pushData property which allows you to specify the contents of push notifications. Here is a code example:

LCIMTextMessage message = new LCIMTextMessage("Hey Jerry, me and Kate are gonna watch a game at a bar tonight. Wanna come with us?");
LCIMMessageSendOptions sendOptions = new LCIMMessageSendOptions {
PushData = new Dictionary<string, object> {
{ "alert", "New message received"},
{ "category", "Message"},
{ "badge", 1},
{ "sound", "message.mp3"}, // The name of the file for the sound; has to be present in the app
{ "custom-key", "This is a custom attribute with custom-key being the name of the key. You can use your own names for keys."}
}
};
  1. Generating contents dynamically on the server side

    The second way introduced above allows you to compose the contents of push notifications based on the messages being sent, but the logic needs to be predefined on the client side, which makes things less flexible.

    So we offer a third way that allows you to define the logic of push notifications on the server side with the help of hooks. Check this page for more details.

Here is a comparison of the priorities of the three methods mentioned above: Generating contents dynamically on the server side > Specifying contents when sending messages from a client > Setting up a static message.

If more than one of these methods are implemented at the same time, the push notifications generated on the server side will always get the highest priority. Those sent from clients will get a lower priority, and the static message set up on the dashboard will get the lowest priority.

Implementations and Restrictions

If your app is using push notification services together with Instant Messaging, whenever a client logs in, the SDK will automatically associate the clientId with the device information (stored in the Installation table) by having the device subscribe to the channel with clientId as its name. The association can be found in the channels field of the _Installation table. By doing so, when the cloud wants to send a push notification to a client, the client's device can be targeted by the clientId associated with it.

Since Instant Messaging generates way more push notifications than other sources, the cloud will not keep any records of them, nor can you find them on Developer Center > Your game > Game Services > Cloud Services > Push Notification > Push records.

Each push notification is only valid for 7 days. This means that if a device doesn't connect to the cloud for more than 7 days, it will not receive this push notification anymore.

Other Settings for Push Notifications

By default, push notifications are sent to the production environment of APNs for iOS devices. Use "_profile": "dev" if you want to switch to the development environment of APNs (the development certificate will be used if certificate-based authentication is selected):

{
"alert": "New message received",
"_profile": "dev"
}

When push notifications are sent via Token Authentication, if your app has private keys for multiple Team IDs, please confirm the one that should be used for your target devices and fill it into the _apns_team_id parameter, since Apple doesn't allow a single request to include push notifications sent to devices belonging to different Team IDs.

{
"alert": "New message received",
"_apns_team_id": "my_fancy_team_id"
}

The _profile and _apns_team_id attributes are used internally by the push service and neither will actually be used. When specifying additional push messages, different push messages are supported for different kinds of devices (e.g. ios, android). Keep in mind that the internal attributes, _profile and _apns_team_id, should not be specified inside the ios object, otherwise they will not take effect. As an example, a push message like this will cause the message to be pushed to APNs’ production environment:

{
"ios": {
"badge": "Increment",
"category": "NEW_CHAT_MESSAGE",
"sound": "default",
"thread-id": "chat",
"alert": {
"title": "New message received",
"body": "This message will still be pushed to the APNs production environment because of the incorrect location of the internal property _profile."
},
"_profile": "dev"
},
"android": {
"title": "New message received",
"alert": ""
}
}

To push to the development environment:

{
"_profile": "dev",
"ios": {
/* … */
},
"android": {
/* … */
}
}

You can insert certain built-in variables into the content you enter on Developer Center > Your game > Game Services > Cloud Services > Instant Messaging > Settings > Push notifications. By doing this, you can embed certain context into the content of push notifications:

  • ${convId} The ID of the conversation
  • ${timestamp} The Unix timestamp when the push notification is triggered
  • ${fromClientId} The clientId of the sender

Message Synchronization

Push notification seems to be a good way to remind users of new messages, but the actual messages won't get delivered until the user goes online. If a user hasn't been online for an extremely long time, there will be tons of messages piled up on the cloud. How can we make sure that all these messages will be properly delivered once the user goes online?

Instant Messaging provides a way for clients to pull messages from the cloud. The cloud keeps track of the last message each user receives from each conversation. When a user goes online, the conversations containing new messages as well as the number of unread messages in each of them will be computed and the client will receive a notification indicating that there is an update on the total number of unread messages. The client can then proactively fetch these messages.

Notifications on Updates of Unread Message Count

When the client goes online, the cloud will compute the numbers of unread messages of all the conversations the client belongs to.

To receive such notifications, the client needs to indicate that it is using the method of pulling messages from the cloud. As mentioned earlier, the JavaScript, Android, and iOS SDKs use this method by default, so there isn't any configuration that needs to be done.

The SDK will maintain an unreadMessagesCount field on each IMConversation to track the number of unread messages in the conversation.

When the client goes online, the cloud will drop in a series of <Conversation, UnreadMessageCount, LastMessage> in the format of events indicating updates on the numbers of unread messages. Each of them matches a conversation containing new messages and serves as the initial value of a <Conversation, UnreadMessageCount> maintained on the client side. After this, whenever a message is received by the SDK, the corresponding unreadMessageCount will be automatically increased. When the number of unread messages of a conversation is cleared, both <Conversation, UnreadMessageCount> on the cloud and maintained by the SDK will be reset.

If the count of unread messages is enabled, it will keep increasing until you explicitly reset it. It will not be reset automatically if the client goes offline again. Even if a message is received when the client is online, the count will still increase. Make sure to reset the count by marking conversations as read whenever needed.

When the number of <Conversation, UnreadMessageCount> changes, the SDK will send an UNREAD_MESSAGES_COUNT_UPDATE event to the app through IMClient. You can listen to this event and make corresponding changes to the number of unread messages on the UI. We recommend you cache unread counts at the application level, and whenever there are two different counts available for the same conversation, replace the older data with the newer one.

tom.OnUnreadMessagesCountUpdated = (convs) => {
foreach (LCIMConversation conv in convs) {
// conv.Unread is the number of unread messages in conversation
}
};

When responding to an UNREAD_MESSAGES_COUNT_UPDATE event, you get a Conversation object containing the lastMessage property which is the last message received by the current user from the conversation. To display the actual unread messages, fetch the messages that come after it.

The only way to clear the number of unread messages is to mark the messages as read with Conversation#read. You may do so when:

  • The user opens a conversation
  • The user is already in a conversation and a new message comes in

Implementation details on unread message counts for iOS and Android SDKs:

iOS SDKs (Objective-C and Swift) will fetch all UNREAD_MESSAGES_COUNT_UPDATE events provided by the cloud on login, while Android SDK only fetches the latest events generated after the previous fetch (Android SDK remembers the timestamp of the last fetch).

Therefore, Android developers need to cache the events for unread messages at the application level, because the events of some conversations have been fetched on previous logins, but not the current one. For iOS developers, they need to do the same thing because the cloud tracks at most 50 conversations containing unread messages, and the events for unread messages are only available for those conversations. If the events for unread messages are not cached, some conversations may have inaccurate counts of unread messages.

Multi-Device Sign-on and Single-Device Sign-on

In some scenarios, a user can stay logged in on multiple devices at the same time. In other ones, a user can be logged in on only one device at a time. With Instant Messaging, you can easily implement both multi-device sign-on and single-device sign-on depending on your needs.

When creating an IMClient instance, you can provide a tag parameter besides clientId to have the cloud check the uniqueness of <ClientId, Tag> when logging the user in. If the user is already logged in on another device with the same tag, the cloud will log the user out from that device, otherwise the user will stay logged in on all their devices. When a user is logged in on multiple devices, the messages coming to this user will be delivered to all these devices and the numbers of unread messages will be synchronized. If the user sends a message from one of these devices, the message will appear on other devices as well.

Based on the above mechanism, a variety of requirements can be met with Instant Messaging:

  1. Multi-Device Sign-on: If tag is not specified when logging in, a user will be able to have any number of devices logged in at the same time.
  2. Single-Device Sign-on: If all the clients share the same tag, a user will be able to stay logged in on only one device at a time.
  3. Multi-Device Sign-on With Restrictions: You can assign a unique tag for each type of device. For example, if you have Mobile for phones, Pad for tablets, and Web for desktop computers, a user will be able to stay logged in on three devices with different types, but not two desktop computers.

Setting Tags

The code below sets a tag called Mobile when creating IMClient, which can be used for the mobile client of your app:

LCIMClient client = new LCIMClient(clientId, "Mobile", "your-device-id");

With the code above, if a user logs in on one mobile device and then logs in on another one (with the same tag), the user will be logged out from the former one.

Handling Conflicts

When the cloud encounters the same <ClientId, Tag> for a second time, the device used for the earlier one will be logged out and receive a CONFLICT event:

tom.OnClose = (code, detail) => {

};

The reason a device gets logged out will be included in the event so that you can display a message to the user with that.

All the "logins" mentioned above refer to users' explicit logins. If the user has already logged in, when the application restarts or reconnects, the SDK will relogin automatically. Under these scenarios, if a login conflict is encountered, the cloud will not log out earlier devices. The device trying to relogin will receive an error instead.

Similarly, if you want an explicit login to receive an error when encountering a conflict, you can pass a special parameter on login:

await tom.Open(false);

Custom Message Types

Although Instant Messaging already supports a number of common message types by default, you can still define your own types as you need. For example, if you want your users to send messages containing payments and contacts, you can implement that with custom message types.

Custom Message Attributes

The following message types are offered by default:

  • TextMessage Text message
  • ImageMessage Image message
  • AudioMessage Audio message
  • VideoMessage Video message
  • FileMessage File message (.txt, .doc, .md, etc.)
  • LocationMessage Location message

When composing messages with these types, you can include additional information by attaching custom attributes in the format of key-value pairs. For example, if you are sending a message and need to include city information, you can put it into attributes of the message rather than create your own message type.

LCIMTextMessage messageWithCity = new LCIMTextMessage("It is getting cold.");
messageWithCity["city"] = "Montreal";

Creating Your Own Message Types

If the built-in types cannot fulfill your requirements at all, you can implement your custom message types.

By inheriting from LCIMTypedMessage, you can define your own types of messages. The basic steps include:

  • Define a subclass inherited from LCIMTypedMessage.
  • Register the subclass when initializing.
class EmojiMessage : LCIMTypedMessage {
public const int EmojiMessageType = 1;

public override int MessageType => EmojiMessageType;

public string Ecode {
get {
return data["ecode"] as string;
} set {
data["ecode"] = value;
}
}
}

// Register subclass
LCIMTypedMessage.Register(EmojiMessage.EmojiMessageType, () => new EmojiMessage());

See Back to Receiving Messages in the previous chapter for more details on how to receive messages with custom types.

Continue Reading