3. Security, Chat Rooms, and Temporary Conversations
Introduction
In the previous chapter, Advanced Messaging Features, Push Notifications, Synchronization, and Multi-Device Sign-on, we introduced a number of bonus features that you can implement beyond basic messaging. In this chapter, we will introduce more features from the perspectives of system security and permission management, including:
- How to verify the requests made by clients with a third-party signing mechanism
- How to control the permissions each user has
- How to build a chat room with an unlimited number of people
- How to enforce text moderation on the messages sent by users
- How to implement temporary conversations
Signing Mechanism
Instant Messaging is decoupled from the account system offered by Data Storage. This makes it possible for you to use Instant Messaging even though the account system of your app is not built with Data Storage. To ensure the security of your app, we offer a third-party signing mechanism that helps your app verify all the requests sent from clients.
The mechanism comes with an authentication server (the so-called "third party") deployed between clients and the cloud. Each time a client wants to make a request involving sensitive operations (like logging in, creating conversations, joining conversations, or inviting users), it has to obtain a signature from the authentication server. The signature gets attached to the request and will be verified by the cloud according to a predefined protocol. Only those requests with valid signatures will be accepted by the cloud.
The signing mechanism is turned off by default. You can turn it on by going to Developer Center > Your game > Game Services > Cloud Services > Instant Messaging > Settings > Instant Messaging settings:
- Verify signatures for logging in: Verify all the activities of logging in
- Verify signatures for conversation operations: Verify all the activities of creating conversations, joining conversations, inviting users, and removing users
- Verify signatures for retrieving history messages: Verify all the activities of retrieving history messages
You are free to change the settings here based on your app's actual needs, though we highly recommend that you at least keep verifying signatures for logging in on, which guarantees the basic security of your app.
- When the client performs operations like logging in or creating conversations, the SDK applies for a signature by calling
SignatureFactorywith the user's information and a request containing the operations to be done. - The authentication server checks if the operations are performed with enough permissions. If that's true, the server will follow the signing algorithm that will be mentioned later to generate the timestamp, nonce, and signature, and send them back to the client.
- The client attaches the signature to the request and sends them to the cloud.
- The cloud verifies the signature along with the request to ensure that the operations in the request are allowed. The request will be accepted only if the signature is valid.
The algorithm used for the signing process is HMAC-SHA1 and the output would be the hex dump of a byte stream. For different requests, different strings with different UTC timestamps and nonces need to be constructed.
If you are using LCUser in your app, you can get signatures for logging in through our REST API.
Formats of Signatures
Below we will introduce the formats of strings used to obtain signatures for different types of operations.
Signatures for Logging in
Below is the format of strings for logging in. Notice that there are two colons between clientid and timestamp:
appid:clientid::timestamp:nonce
| Parameter | Description |
|---|---|
appid | Your App ID. |
clientid | The clientId used for logging in. |
timestamp | The number of milliseconds that have elapsed since Unix epoch (UTC). |
nonce | A random string. |
Note: The key for signing has to be the Master Key of your app. You can find it from Developer Center > Your game > Game Services > Configuration. Make sure your Master Key is well-protected and doesn't get leaked.
You may implement your own SignatureFactory to retrieve signatures from remote servers. If you don't have your own server, you may use the web hosting service provided by Cloud Engine. Generating signatures within your mobile app is extremely dangerous since your Master Key can get exposed.
This signature expires in 6 hours, but it becomes invalid immediately once the client has been forced to log out. The signature invalidness does not affect the currently connected clients.
Signatures for Creating Conversations
Below is the format of strings for creating conversations:
appid:clientid:sorted_member_ids:timestamp:nonce
appid,clientid,timestamp, andnonceare the same as above.sorted_member_idsis a list ofclientIds (users being invited to the conversation) arranged in ascending order and divided by colon (:).
Signatures for Group Operations
Below is the format of strings for joining conversations, inviting users, and removing users:
appid:clientid:convid:sorted_member_ids:timestamp:nonce:action
appid,clientid,sorted_member_ids,timestamp, andnonceare the same as above.sorted_member_idswould be an empty string if you are creating a new conversation.convidis the conversation ID.actionis the operation being performed:invitemeans joining a conversation or inviting users andkickmeans removing users.
Signatures for Retrieving Message Histories
appid:client_id:convid:nonce:timestamp
The meanings of these parameters are the same as above.
This signature is only used for REST API. It is not applicable to client-side SDKs.
Demo for Generating Signatures on Cloud Engine
To help you better understand the signing algorithm, we made a server-side signing program based on Node.js and Cloud Engine. It's available here for you to study and use.
Supporting Signatures on the Client Side
So far we have been talking about the protocol used by the authentication server to generate signatures. Now let's see what we need to do with the client side to make the entire signing mechanism work.
The SDK reserves a factory interface Signature for each AVIMClient instance. To enable signing, implement the interface with a class that calls the signing method on the authentication server to get signatures, and then bind the class to the AVIMClient instance:
- Unity
- Android
- iOS
- JavaScript
public class LocalSignatureFactory : ILCIMSignatureFactory {
const string MasterKey = "pyvbNSh5jXsuFQ3C8EgnIdhw";
public Task<LCIMSignature> CreateConnectSignature(string clientId) {
long timestamp = DateTimeOffset.Now.ToUnixTimeSeconds();
string nonce = NewNonce();
string signature = GenerateSignature(LCApplication.AppId, clientId, string.Empty, timestamp.ToString(), nonce);
return Task.FromResult(new LCIMSignature {
Signature = signature,
Timestamp = timestamp,
Nonce = nonce
});
}
public Task<LCIMSignature> CreateStartConversationSignature(string clientId, IEnumerable<string> memberIds) {
string sortedMemberIds = string.Empty;
if (memberIds != null) {
List<string> sortedMemberList = memberIds.ToList();
sortedMemberList.Sort();
sortedMemberIds = string.Join(":", sortedMemberList);
}
long timestamp = DateTimeOffset.Now.ToUnixTimeSeconds();
string nonce = NewNonce();
string signature = GenerateSignature(LCApplication.AppId, clientId, sortedMemberIds, timestamp.ToString(), nonce);
return Task.FromResult(new LCIMSignature {
Signature = signature,
Timestamp = timestamp,
Nonce = nonce
});
}
public Task<LCIMSignature> CreateConversationSignature(string conversationId, string clientId, IEnumerable<string> memberIds, string action) {
string sortedMemberIds = string.Empty;
if (memberIds != null) {
List<string> sortedMemberList = memberIds.ToList();
sortedMemberList.Sort();
sortedMemberIds = string.Join(":", sortedMemberList);
}
long timestamp = DateTimeOffset.Now.ToUnixTimeSeconds();
string nonce = NewNonce();
string signature = GenerateSignature(LCApplication.AppId, clientId, conversationId, sortedMemberIds, timestamp.ToString(), nonce, action);
return Task.FromResult(new LCIMSignature {
Signature = signature,
Timestamp = timestamp,
Nonce = nonce
});
}
private static string SignSHA1(string key, string text) {
HMACSHA1 hmac = new HMACSHA1(Encoding.UTF8.GetBytes(key));
byte[] bytes = hmac.ComputeHash(Encoding.UTF8.GetBytes(text));
string signature = BitConverter.ToString(bytes).Replace("-", string.Empty);
return signature;
}
private static string NewNonce() {
byte[] bytes = new byte[10];
using (RandomNumberGenerator generator = RandomNumberGenerator.Create()) {
generator.GetBytes(bytes);
}
return Convert.ToBase64String(bytes);
}
private static string GenerateSignature(params string[] args) {
string text = string.Join(":", args);
string signature = SignSHA1(MasterKey, text);
return signature;
}
}
// Specify the signature factory
LCIMClient tom = new LCIMClient("tom", signatureFactory: new LocalSignatureFactory());
// An example of performing signing with Cloud Engine
public class KeepAliveSignatureFactory implements SignatureFactory {
@Override
public Signature createSignature(String peerId, List<String> watchIds) throws SignatureException {
Map<String,Object> params = new HashMap<String,Object>();
params.put("self_id",peerId);
params.put("watch_ids",watchIds);
try{
Object result = LCCloud.callFunction("sign",params);
if(result instanceof Map){
Map<String,Object> serverSignature = (Map<String,Object>) result;
Signature signature = new Signature();
signature.setSignature((String)serverSignature.get("signature"));
signature.setTimestamp((Long)serverSignature.get("timestamp"));
signature.setNonce((String)serverSignature.get("nonce"));
return signature;
}
}catch(LCException e){
throw (SignatureFactory.SignatureException) e;
}
return null;
}
@Override
public Signature createConversationSignature(String convId, String peerId,
List<String> targetPeerIds,String action) throws SignatureException{
Map<String,Object> params = new HashMap<String,Object>();
params.put("client_id",peerId);
params.put("conv_id",convId);
params.put("members",targetPeerIds);
params.put("action",action);
try{
Object result = LCCloud.callFunction("sign2",params);
if(result instanceof Map){
Map<String,Object> serverSignature = (Map<String,Object>) result;
Signature signature = new Signature();
signature.setSignature((String)serverSignature.get("signature"));
signature.setTimestamp((Long)serverSignature.get("timestamp"));
signature.setNonce((String)serverSignature.get("nonce"));
return signature;
}
}catch(LCException e){
throw (SignatureFactory.SignatureException) e;
}
return null;
}
}
// Bind an instance of the signature factory class to LCIMClient
LCIMOptions.getGlobalOptions().setSignatureFactory(new KeepAliveSignatureFactory());
// Implement the LCIMSignatureDataSource protocol
- (void)client:(LCIMClient *)client
action:(LCIMSignatureAction)action
conversation:(LCIMConversation * _Nullable)conversation
clientIds:(NSArray<NSString *> * _Nullable)clientIds
signatureHandler:(void (^)(LCIMSignature * _Nullable))handler
{
if ([action isEqualToString:LCIMSignatureActionOpen]) {
// For modules with signing enabled, return the corresponding signature
LCIMSignature *signature;
/*
...
...
See "Demo for Generating Signatures on Cloud Engine"
*/
handler(signature);
} else {
// For modules with signing disabled, return nil
handler(nil);
}
}
// Set the protocol delegator
NSError *error;
LCIMClient *imClient = [[LCIMClient alloc] initWithClientId:@"Tom" error:&error];
if (!error) {
imClient.signatureDataSource = signatureDelegator;
}
// A Cloud Engine-based signature factory for signing requests for logging in
var signatureFactory = function (clientId) {
return AV.Cloud.rpc("sign", { clientId: clientId }); // AV.Cloud.rpc returns a Promise
};
// A Cloud Engine-based signature factory for signing requests for creating conversations, joining conversations, inviting users, and removing users
var conversationSignatureFactory = function (
conversationId,
clientId,
targetIds,
action
) {
return AV.Cloud.rpc("sign-conversation", {
conversationId: conversationId,
clientId: clientId,
targetIds: targetIds,
action: action,
});
};
realtime
.createIMClient("Tom", {
signatureFactory: signatureFactory,
conversationSignatureFactory: conversationSignatureFactory,
})
.then(function (tom) {
console.log("Tom logged in.");
})
.catch(function (error) {
// Errors thrown by signatureFactory or for invalid signatures will be caught here
});
You should never perform signing using your Master Key on the client side. If your Master Key gets leaked, the data in your app would be accessible by anyone who has the key. Therefore, we highly recommend that you host the signing program on a server that is well-secured (like Cloud Engine).
Signing Mechanism for User
User is the built-in account system coming with Data Storage. If your users have their accounts signed up or logged in with User, they can skip the signing process when logging in to Instant Messaging. The code below shows how a user can log in to Instant Messaging with User:
- Unity
- Android
- iOS
- JavaScript
LCUser user = await LCUser.Login("username", "password");
CIMClient client = new LCIMClient(user);
await client.Open();
// Log in to the account system with the username and password of an LCUser
LCUser.logInInBackground("username", "password", new LogInCallback<LCUser>() {
@Override
public void done(LCUser user, LCException e) {
if (null != e) {
return;
}
// Create a client with the LCUser instance
LCIMClient client = LCIMClient.getInstance(user);
// Log in to Instant Messaging
client.open(new LCIMClientCallback() {
@Override
public void done(final LCIMClient avimClient, LCIMException e) {
// Do something as you need
}
});
}
});
// Log in to the account system with the username and password of an LCUser
[LCUser logInWithUsernameInBackground:username password:password block:^(LCUser * _Nullable user, NSError * _Nullable error) {
// Create a client with the LCUser instance
NSError *err;
LCIMClient *client = [[LCIMClient alloc] initWithUser:user error:&err];
if (!err) {
// Log in to Instant Messaging
[client openWithCallback:^(BOOL succeeded, NSError * _Nullable error) {
// Do something as you need
}];
}
}];
var AV = require("leancloud-storage");
// Log in to the account system with the username and password of an LCUser
AV.User.logIn("username", "password")
.then(function (user) {
// Log in to Instant Messaging with the LCUser instance
return realtime.createIMClient(user);
})
.catch(console.error.bind(console));
When creating IMClient with an LCUser instance that has completed the logIn process, the user's signature information can be directly accessed by Instant Messaging from the account system. This allows Instant Messaging to automatically verify the client being logged in and the process of applying for signatures from the third-party server can be skipped.
Once IMClient is logged in, all the other features work in the same way as discussed earlier.
Chat Rooms
We have compared different types of scenarios and conversations in our service overview. Now let's learn how to build a chat room.
Creating Chat Rooms
IMClient has the createChatRoom method for creating chat rooms:
- Unity
- Android
- iOS
- JavaScript
// Pass in the name of the chat room
tom.CreateChatRoom("Chat Room");
tom.createChatRoom("Chat Room", null,
new LCIMConversationCreatedCallback() {
@Override
public void done(LCIMConversation conv, LCIMException e) {
if (e == null) {
// Chat room created
}
}
});
[client createChatRoomWithCallback:^(LCIMChatRoom * _Nullable chatRoom, NSError * _Nullable error) {
if (chatRoom && !error) {
LCIMTextMessage *textMessage = [LCIMTextMessage messageWithText:@"This is a message." attributes:nil];
[chatRoom sendMessage:textMessage callback:^(BOOL success, NSError *error) {
if (success && !error) {
}
}];
}
}];
tom.createChatRoom({ name: "Chat Room" }).catch(console.error);
When creating a chat room, you can specify its name and optional attributes. The interface for creating chat rooms has the following differences compared to that for creating basic conversations:
- A chat room doesn't have a member list, so there is no need to specify
members. - For the same reason, there is no need to specify
unique(the cloud doesn't need to merge conversations by member lists).
Although it's possible to create a chat room by passing
{ transient: true }intocreateConversation, we still recommend that you usecreateChatRoomdirectly.
Finding Chat Rooms
In the first chapter, we have discussed how you can use ConversationsQuery to look for conversations with your custom conditions. This works for chat rooms as well, as long as you add transient = true as a constraint.
- Unity
- Android
- iOS
- JavaScript
LCIMConversationQuery query = new LCIMConversationQuery(tom);
query.WhereEqualTo("tr", true);
LCIMConversationsQuery query = tom.getChatRoomQuery();
query.findInBackground(new LCIMConversationQueryCallback() {
@Override
public void done(List<LCIMConversation> conversations, LCIMException e) {
if (null == e) {
// Success
} else {
// Error handling
}
}
});
LCIMConversationQuery *query = [tom conversationQuery];
[query whereKey:@"tr" equalTo:@(YES)];
var query = tom.getQuery().equalTo("tr", true); // Restrict to chat rooms
query
.find()
.then(function (conversations) {
// conversations contains all the results
})
.catch(console.error);
The Java (Android) SDK offers the
LCIMClient#getChatRoomQuerymethod that is dedicated to querying chat rooms. By using this method, you won't need to deal with thetransientattribute of conversations.
Joining and Leaving Chat Rooms
When coming to the interfaces for joining or leaving conversations, group chats are the same as basic conversations. See Group Chats in the first chapter for more details.
However, there are several differences in the ways members are managed and notifications are delivered:
- A user cannot be invited to or removed from a chat room. They are only able to join or leave on their own.
- If a user logs out, this user will be automatically removed from the chat room they are already in. An exception is that if the user gets offline unexpectedly, they will be added back to the chat room they are previously in as long as they get back within 30 minutes.
- The cloud will not deliver notifications for users joining or leaving chat rooms.
- The list of members in a chat room cannot be retrieved. Only the count of members is available.
As a side note, functions like push notifications, message synchronization, and receipts are also not supported by chat rooms.
Getting Member Counts
The LCIMConversation#memberCount method lets you get the count of members in a conversation. When used on a chat room, you get the number of people in it at that moment:
- Unity
- Android
- iOS
- JavaScript
int membersCount = await conversation.GetMembersCount();
private void TomQueryWithLimit() {
LCIMClient tom = LCIMClient.getInstance("Tom");
tom.open(new LCIMClientCallback() {
@Override
public void done(LCIMClient client, LCIMException e) {
if (e == null) {
// Successfully logged in
LCIMConversationsQuery query = tom.getConversationsQuery();
query.setLimit(1);
// Get the first conversation
query.findInBackground(new LCIMConversationQueryCallback() {
@Override
public void done(List<LCIMConversation> convs, LCIMException e) {
if (e == null) {
if (convs != null && !convs.isEmpty()) {
LCIMConversation conv = convs.get(0);
// Get the count of people in the first conversation
conv.getMemberCount(new LCIMConversationMemberCountCallback() {
@Override
public void done(Integer count, LCIMException e) {
if (e == null) {
Log.d("Tom & Jerry has " + count + " people online");
}
}
});
}
}
}
});
}
}
});
}
// Get the count of people
[conversation countMembersWithCallback:^(NSInteger number, NSError *error) {
NSLog(@"%ld",number);
}];
chatRoom
.count()
.then(function (count) {
console.log("Count: " + count);
})
.catch(console.error.bind(console));
Message Priorities
To ensure that important messages get delivered promptly, the server would selectively discard a certain amount of messages with lower priorities when the network connection is bad. Below are the priorities supported:
| Priority | Description |
|---|---|
MessagePriority.HIGH | High priority. Used for messages that need to be delivered promptly. |
MessagePriority.NORMAL | Normal priority. Used for ordinary text messages. |
MessagePriority.LOW | Low priority. Used for messages that are less important. |
The default priority is NORMAL.
The priority of a message can be set when sending the message. The code below shows how you can send a message with high priority:
- Unity
- Android
- iOS
- JavaScript
LCIMTextMessage message = new LCIMTextMessage("The score is still 0:0. China definitely needs a substitution for the second half.");
LCIMMessageSendOptions options = new LCIMMessageSendOptions {
Priority = LCIMMessagePriority.High
};
await chatRoom.Send(message, options);
LCIMClient tom = LCIMClient.getInstance("Tom");
tom.open(new LCIMClientCallback() {
@Override
public void done(LCIMClient client, LCIMException e) {
if (e == null) {
// Create a conversation named "Tom & Jerry"
client.createConversation(Arrays.asList("Jerry"), "Tom & Jerry", null,
new LCIMConversationCreatedCallback() {
@Override
public void done(LCIMConversation conv, LCIMException e) {
if (e == null) {
LCIMTextMessage msg = new LCIMTextMessage();
msg.setText("Get up, Jerry!");
LCIMMessageOption messageOption = new LCIMMessageOption();
messageOption.setPriority(LCIMMessageOption.MessagePriority.High);
conv.sendMessage(msg, messageOption, new LCIMConversationCallback() {
@Override
public void done(LCIMException e) {
if (e == null) {
// Sent
}
}
});
}
}
});
}
}
});
LCIMMessageOption *option = [[LCIMMessageOption alloc] init];
option.priority = LCIMMessagePriorityHigh;
[chatRoom sendMessage:[LCIMTextMessage messageWithText:@"Get up, Jerry!" attributes:nil] option:option callback:^(BOOL succeeded, NSError * _Nullable error) {
// Things to do after the message is sent
}];
var { Realtime, TextMessage, MessagePriority } = require("leancloud-realtime");
var realtime = new Realtime({
appId: "GDBz24d615WLO5e3OM3QFOaV-gzGzoHsz",
appKey: "dlCDCOvzMnkXdh2czvlbu3Pk",
});
realtime
.createIMClient("host")
.then(function (host) {
return host.createConversation({
members: ["broadcast"],
name: "2094 FIFA World Cup - Vatican City vs China",
transient: true,
});
})
.then(function (conversation) {
console.log(conversation.id);
return conversation.send(
new TextMessage(
"The score is still 0:0. China definitely needs a substitution for the second half."
),
{ priority: MessagePriority.HIGH }
);
})
.then(function (message) {
console.log(message);
})
.catch(console.error);
Note:
This feature is only available for chat rooms. There won't be an effect if you set priorities for messages in basic conversations, since these messages will never get discarded.
Muting Conversations
If a user doesn't want to get notifications for new messages in a conversation but still wants to stay in the conversation, they can mute the conversation.
For example, Tom is getting busy and wants to mute a conversation:
- Unity
- Android
- iOS
- JavaScript
await chatRoom.Mute();
LCIMClient tom = LCIMClient.getInstance("Tom");
tom.open(new LCIMClientCallback(){
@Override
public void done(LCIMClient client,LCIMException e){
if(e==null){
// Logged in
LCIMConversation conv = client.getConversation("551260efe4b01608686c3e0f");
conv.mute(new LCIMConversationCallback(){
@Override
public void done(LCIMException e){
if(e==null){
// Muted
}
}
});
}
}
});
// Tom mutes the conversation
[conversation muteWithCallback:^(BOOL succeeded, NSError *error) {
if (succeeded) {
NSLog(@"Muted!");
}
}];
tom
.getConversation("CONVERSATION_ID")
.then(function (conversation) {
return conversation.mute();
})
.then(function (conversation) {
console.log("Muted!");
})
.catch(console.error.bind(console));
After a conversation is muted, the current user will not get push notifications from it anymore. To unmute a conversation, use Conversation#unmute.
Tips:
- Both chat rooms and basic conversations can be muted.
muteandunmuteoperations will change themufield in the_Conversationclass. Do not change themufield directly in your app's dashboard, otherwise push notifications may not work properly.
Text Moderation
Instant Messaging offers a built-in text moderation function that allows you to filter cuss words out from the messages sent by users. You can enable it for one-on-one chats by going to Developer Center > Your game > Game Services > Cloud Services > Instant Messaging > Settings.
Matched keywords will be replaced with ***.
Text moderation will lead to message modifications at the system level, so the message sender will receive a MESSAGE_UPDATE event, which the clients can listen to. Please refer to the "Modify a Message" section of the previous chapter for code samples.
If you have upgraded to the Business Plan, you can customize the keywords. To do so, go to Developer Center > Your game > Game Services > Cloud Services > Instant Messaging > Settings and upload your keywords file. The uploaded file must be UTF-8 encoded with one keyword in each line. For now, the Enable sensitive keyword filtering against group chats option on Developer Center > Your game > Game Services > Cloud Services > Instant Messaging > Settings is always enabled, but if you don't upload a file of keywords, the text moderation function will not function in the end.
Filtering rules for sensitive words: If the message is a rich media message type (the _lctype attribute has a value), only the contents of the _lctext field are filtered. If the message is not a rich media message type (the message does not have the _lctype attribute), then the entire message body is filtered.
If you have more complicated requirements regarding text moderation, we recommend that you make use of the _messageReceived hook of Cloud Engine. You can define your own logic for controlling messages.
Temporary Conversations
Temporary conversations can be used for special scenarios with:
- Short TTL
- Fewer members (10
clientIds maximum) - No message history needed
What makes temporary conversations different from other conversations is that they expire very quickly. This helps you reduce the space needed for storing conversations and lower the cost of maintaining your app. Temporary conversations are best used for customer service systems.
Creating Temporary Conversations
IMConversation has its createTemporaryConversation method for creating temporary conversations:
- Unity
- Android
- iOS
- JavaScript
LCIMTemporaryConversation temporaryConversation = await tom.CreateTemporaryConversation(new string[] { "Jerry", "William" });
tom.createTemporaryConversation(Arrays.asList(members), 3600, new LCIMConversationCreatedCallback(){
@Override
public void done(LCIMConversation conversation, LCIMException e) {
if (null == e) {
LCIMTextMessage msg = new LCIMTextMessage();
msg.setText("This is a temporary conversation.");
conversation.sendMessage(msg, new LCIMConversationCallback(){
@Override
public void done(LCIMException e) {
}
});
}
}
});
[self createTemporaryConversationWithClientIds:@[@"Jerry", @"William"] callback:^(LCIMTemporaryConversation * _Nullable temporaryConversation, NSError * _Nullable error) {
if (temporaryConversation) {
// success
}
}];
realtime
.createIMClient("Tom")
.then(function (tom) {
return tom.createTemporaryConversation({
members: ["Jerry", "William"],
});
})
.then(function (conversation) {
return conversation.send(
new AV.TextMessage("This is a temporary conversation.")
);
})
.catch(console.error);
Temporary conversations have an important attribute that differentiates them from others: TTL. It is set to 1 day by default, but you can change it to any time no longer than 30 days. If you want a conversation to survive for more than 30 days, make it a basic conversation instead. The code below creates a temporary conversation with a custom TTL:
- Unity
- Android
- iOS
- JavaScript
LCIMTemporaryConversation temporaryConversation = await tom.CreateTemporaryConversation(new string[] { "Jerry", "William" },
ttl: 3600);
LCIMClient client = LCIMClient.getInstance("Tom");
client.open(new LCIMClientCallback() {
@Override
public void done(LCIMClient avimClient, LCIMException e) {
if (null == e) {
String[] members = {"Jerry", "William"};
avimClient.createTemporaryConversation(Arrays.asList(members), 3600, new LCIMConversationCreatedCallback(){
@Override
public void done(LCIMConversation conversation, LCIMException e) {
if (null == e) {
LCIMTextMessage msg = new LCIMTextMessage();
msg.setText("This is a temporary conversation. It will expire in 1 hour.");
conversation.sendMessage(msg, new LCIMConversationCallback(){
@Override
public void done(LCIMException e) {
}
});
}
}
});
}
}
});
LCIMConversationCreationOption *option = [LCIMConversationCreationOption new];
option.timeToLive = 3600;
[self createTemporaryConversationWithClientIds:@[@"Jerry", @"William"] option:option callback:^(LCIMTemporaryConversation * _Nullable temporaryConversation, NSError * _Nullable error) {
if (temporaryConversation) {
// success
}
}];
realtime
.createIMClient("Tom")
.then(function (tom) {
return tom.createTemporaryConversation({
members: ["Jerry", "William"],
ttl: 3600,
});
})
.then(function (conversation) {
return conversation.send(
new AV.TextMessage(
"This is a temporary conversation. It will expire in 1 hour."
)
);
})
.catch(console.error);
Besides this, a temporary conversation shares the same functionality as a basic conversation.