Send and receive messages

Updated

Shows how to implement sending and receiving these messages using the Agora Chat SDK.

After logging in to Chat, users can send the following types of messages to a peer user, a chat group, or a chat room:

  • Text messages, including hyperlinks and emojis.

  • Attachment messages, including image, voice, video, and file messages.

  • Location messages.

  • CMD messages.

  • Extended messages.

  • Custom messages.

The Chat message feature is language agnostic. End users can send messages in any language, as long as their devices support input in that language.

In addition to sending messages, users can also forward one or more messages. When forwarding multiple messages, users have the following options:

  • Forward messages one-by-one
  • Forward combined messages as message history

This page shows how to implement sending, receiving, forwarding multiple messages, and modifying sent messages using the Chat SDK.

Understand the tech

The Chat SDK uses the ChatManager and ChatMessage classes to send, receive, and withdraw messages.

The process of sending and receiving a message is as follows:

  1. The message sender creates a text, file, or attachment message using the corresponding Create method.
  2. The message sender calls sendMessage to send the message.
  3. The message recipient listens for ChatMessageEventListener and receives the message in the onMessagesReceived callback.

Prerequisites

Before proceeding, ensure that you meet the following requirements:

  • You have integrated the Chat SDK, initialized the SDK, and implemented the functionality of registering accounts and login. For details, see Chat SDK quickstart.

    Use Chat SDK 1.2 or higher if you intend to enable users to forward multiple messages, or to modify sent messages.

  • You understand the API call frequency limits as described in Limitations.

Implementation

This section shows how to implement sending and receiving the various types of messages.

Send a message

Implement the ChatMessageStatusCallback class to listen for the progress and result of the message sending.

// Implement ChatMessageStatusCallback
class ChatMessageCallback implements ChatMessageStatusCallback {
  onProgress(localMsgId: string, progress: number): void {
    console.log(`sendMessage: onProgress: `, localMsgId, progress);
    // For attachment messages such as voice and video, you can use the percentage value to represent the uploading or downloading progress.
  }
  onError(localMsgId: string, error: ChatError): void {
    console.log(`sendMessage: onError: `, localMsgId, error);
    // Update the message state or add subsequent handling logics after receiving the callback.
  }
  onSuccess(message: ChatMessage): void {
    console.log(`sendMessage: onSuccess: `, message);
    // Update the message state or add subsequent handling logics after receiving the callback.
  }
  // You can set the priority of chat room messages. The default priority is `PriorityNormal`, indicating the normal priority.
  if (ret.chatType === ChatMessageChatType.ChatRoom) {
    ret.messagePriority = priority;
  }
  ChatClient.getInstance()
  .chatManager.sendMessage(msg!, new ChatMessageCallback())
  .then(() => {
  // The sending operation is complete and the log is printed here.
  // The sending operation is returned via callback.
  console.log("send message operation success.");
  })
  .catch((reason) => {
  // The sending operation fails and the log is printed here.
  console.log("send message operation fail.", reason);
  })
  };

Use the ChatMessage class to create a message, and ChannelManager to send the message.

// Set the message type. The SDK supports 8 message types.
const messageType = ChatMessageType.TXT;
// Set `targetId` to the user ID of the peer user in one-to-one chat, group ID in group chat, and chat room ID in room chat.
const targetId = "tom";
// Set `chatType` as `PeerChat`, `GroupChat`, or `ChatRoom` for one-to-one chat, group chat, or room chat.
const chatType = ChatMessageChatType.PeerChat;
// Construct the message. Parameters vary with message types.
let msg: ChatMessage;
if (messageType === ChatMessageType.TXT) {
  // For a text message, set the message content.
  const content = "This is text message";
  msg = ChatMessage.createTextMessage(targetId, content, chatType);
} else if (messageType === ChatMessageType.IMAGE) {
  // For an image message, set the file path, width, height, and display name of the image file.
  // For the image file path, `file://` is unnecessary.
  const filePath = "/data/.../image.jpg";
  const width = 100;
  const height = 100;
  const displayName = "test.jpg";
  msg = ChatMessage.createImageMessage(targetId, filePath, chatType, {
    displayName,
    width,
    height,
  });
} else if (messageType === ChatMessageType.CMD) {
  // Construct a command message. You can customize the command.
  const action = "writing";
  msg = ChatMessage.createCmdMessage(targetId, action, chatType);
} else if (messageType === ChatMessageType.CUSTOM) {
  // Customize the message. The message content comprises of the event type and the extension field.
  // Allow the chat users to set the event and the extension field.
  const event = "gift";
  const ext = { key: "value" };
  msg = ChatMessage.createCustomMessage(targetId, event, chatType, {
    params: JSON.stringify(ext),
  });
} else if (messageType === ChatMessageType.FILE) {
  // Construct a file message. You need to set the file path and display name of the file.
  // For the file path, `file://` is unnecessary.
  const filePath = "data/.../foo.zip";
  const displayName = "study_data.zip";
  msg = ChatMessage.createFileMessage(targetId, filePath, chatType, {
    displayName,
  });
} else if (messageType === ChatMessageType.LOCATION) {
  // Construct a location message. You need to set the latitude and longitude information of the location, as well as the address of the location.
  const latitude = "114.78";
  const longitude = "39,89";
  const address = "darwin";
  msg = ChatMessage.createLocationMessage(
    targetId,
    latitude,
    longitude,
    chatType,
    { address }
  );
} else if (messageType === ChatMessageType.VIDEO) {
  // Construct a video message, which includes the video file and thumbnail of the video. You need to set the file path, width, height, display name, and duration of the video file.
  // You also need to set the path of the thumbnail on the local device.
  // A video message contains two attachment files.
  // For the video file path and thumbnail path, `file://` is unnecessary.
  const filePath = "data/.../foo.mp4";
  const width = 100;
  const height = 100;
  const displayName = "bar.mp4";
  const thumbnailLocalPath = "data/.../zoo.jpg";
  const duration = 5;
  msg = ChatMessage.createVideoMessage(targetId, filePath, chatType, {
    displayName,
    thumbnailLocalPath,
    duration,
    width,
    height,
  });
} else if (messageType === ChatMessageType.VOICE) {
  // Construct a voice message. You need to set the filepath, display name, and duration of the audio file.
  // For the voice file path, `file://` is unnecessary.
  const filePath = "data/.../foo.wav";
  const displayName = "bar.mp4";
  const duration = 5;
  msg = ChatMessage.createVoiceMessage(targetId, filePath, chatType, {
    displayName,
    duration,
  });
} else {
  // Exceptions occur if the message type you set is not supported.
  throw new Error("Not implemented.");
}
// Implement ChatMessageCallback to listen for the message sending event. The result only indicates the result of this method call, not whether the message sending succeeds or fails.
ChatClient.getInstance()
  .chatManager.sendMessage(msg!, new ChatMessageCallback())
  .then(() => {
    // Print the log here if the method call succeeds.
    console.log("send message success.");
  })
  .catch((reason) => {
    // Print the log here if the method call fails.
    console.log("send message fail.", reason);
  });

Send a message to online users only

Agora Chat supports delivering messages only to users that are currently online. An example of using this function is displaying the votes in a group voting: Only users that are online need to see the changes in real time, while the others only need the final result.

All types of messages in one-to-one chats and chat groups support this function. Compared to an ordinary message, a message that is delivered only to online users has the following differences:

  • Does not support offline storage: If the recipient is offline when sending a message, the message cannot be received, even after logging in again. For ordinary messages, when the recipient is online, the message reminder is received in real time; when the recipient is offline, the offline push message is sent in real time. When the recipient is online again, the Chat server actively pushes the offline message to the client.
  • Support local storage: After a message is successfully sent, it is added to the database.
  • Roaming storage is not supported by default: Sent messages are not stored on the Chat message server by default, so users cannot obtain the messages on other devices. If you need to activate roaming storage of online messages, contact support@agora.io.

To deliver messages only to online users, set ChatMessage#deliverOnlineOnly to true when sending the message. The following is an example of sending a text message to only online users:

// Create a text message, `content` is the text content of the message.
// `conversationId` is the message receiver. It is the peer user ID in a single chat and the group ID in a group chat.
// `conversationIdType` Conversation type: Single chat is ChatMessageChatType.PeerChat, group chat is ChatMessageChatType.GroupChat
// Whether the message is only delivered to online users. (Default) `false`: Delivered regardless of whether the user is online or not; `true`: Only delivered to online users. If the user is offline, the message will not be delivered.
const message = createTextMessage(conversationId, content, conversationIdType, {
   deliverOnlineOnly: true,
});
// Send a message.
ChatClient.getInstance().chatManager.sendMessage(msg, {
   onError: (localMsgId: string, error: ChatError) => {
     // Failed to send message
   },
   onSuccess: (message: ChatMessage) => {
     // Message sent successfully,
   },
});

Set message priority

In high-concurrency use-cases, you can set a certain message type or messages from a chat room member as high, normal, or low priority. In this case, low-priority messages are dropped first to reserve resources for the high-priority ones, for example, gifts and announcements, when the server is overloaded. This ensures that the high-priority messages can be dealt with first when loads of messages are being sent in high concurrency or high frequency. Note that this feature can increase the delivery reliability of high-priority messages, but cannot guarantee the deliveries. Even high-priorities messages can be dropped when the server load goes too high.

You can set the priority for all types of messages in the chat room. See code example above.

Receive the message

You can use ChatMessageEventListener to listen for message events. You can add multiple ChatMessageEventListener objects to listen for multiple events. When you no longer listen for an event, ensure that you remove the object. When a message arrives, the recipient receives an onMessgesReceived callback. Each callback contains one or more messages. You can traverse the message list, and parse and render these messages in this callback.

// Inherit and implement ChatMessageEventListener
class ChatMessageEvent implements ChatMessageEventListener {
  onMessagesReceived(messages: ChatMessage[]): void {
    console.log(`onMessagesReceived: `, messages);
  }
  onCmdMessagesReceived(messages: ChatMessage[]): void {
    console.log(`onCmdMessagesReceived: `, messages);
  }
  onMessagesRead(messages: ChatMessage[]): void {
    console.log(`onMessagesRead: `, messages);
  }
  onGroupMessageRead(groupMessageAcks: ChatGroupMessageAck[]): void {
    console.log(`onGroupMessageRead: `, groupMessageAcks);
  }
  onMessagesDelivered(messages: ChatMessage[]): void {
    console.log(`onMessagesDelivered: ${messages.length}: `, messages);
  }
  onMessagesRecalledInfo(info: ChatRecalledMessageInfo[]): void {
    console.log(`onMessagesRecalledInfo: `, info);
  }
  onConversationsUpdate(): void {
    console.log(`onConversationsUpdate: `);
  }
  onConversationRead(from: string, to?: string): void {
    console.log(`onConversationRead: `, from, to);
  }
}
// Listen for the message event.
const listener = new ChatMessageEvent();
ChatClient.getInstance().chatManager.addMessageListener(listener);
// Remove the specified message listener.
ChatClient.getInstance().chatManager.removeMessageListener(listener);
// Remove all the message listeners.
ChatClient.getInstance().chatManager.removeAllMessageListener();

Send and receive a GIF image message

Since SDK v1.4.0, you can send and receive GIF image messages. A GIF image message is a subtype of image message that is not compressed when sent. Thumbnail generation and download are the same as for a regular image message.

To send a GIF image message, set isGif to true in the options when you create the image message with createImageMessage:

const message = ChatMessage.createImageMessage(targetId, filePath, chatType, {
  isGif: true,
  displayName: displayName,
  width: 100,
  height: 100,
});
ChatClient.getInstance().chatManager.sendMessage(message, {
  onError(localMsgId, error) {},
  onSuccess(msg) {},
});

As with a regular message, the recipient receives the onMessagesReceived event. After confirming that the message is an image message, read the isGif property of the message body. If it returns true, the message is a GIF image message.

ChatClient.getInstance().chatManager.addMessageListener({
  onMessagesReceived(messages) {
    for (const message of messages) {
      if (message.body.type === ChatMessageType.IMAGE) {
        const body = message.body as ChatImageMessageBody;
        if (body.isGif === true) {
          // Download the attachment to display the GIF image message.
          ChatClient.getInstance().chatManager.downloadAttachment(message);
        }
      }
    }
  },
});

Recall a message

After a message is sent, you can recall it. The recallMessage method recalls a message that is saved both locally and on the server, whether it is a historical message, offline message or a roaming message on the server, or a message in the memory or local database of the message sender or recipient.

The default time limit for recalling a message is two minutes. You can extend this time frame to up to 7 days in Agora Console. To do so, select a project that enables Agora Chat, then click Configure > Features > Message recall.

The following permission rules apply to message recall:

  • In one-to-one chats, only the message sender can recall a message they sent. If the message has expired, the recall fails.
  • In chat groups and chat rooms, regular members can recall only the messages they sent, and the recall fails if the message has expired. Since SDK v1.4.0, the chat group owner, chat room owner, and admins can recall messages sent by other members, even after those messages have expired.

  1. Except CMD messages, you can recall all types of message. 2. If an attachment message, like an image, voice, video, or file message, is recalled, the attachment of the message is also deleted.
ChatClient.getInstance()
  .chatManager.recallMessage(this.state.lastMessage.msgId)
  .then(() => {
    console.log("recall message success");
  })
  .catch((reason) => {
    console.log("recall message fail.", reason);
  });

You can also use ChatMessageEventListener to listen for the state of recalling the message. Since SDK v1.4.0, the onMessagesRecalled event is removed; use onMessagesRecalledInfo instead:

// Occurs when a received message is recalled. info contains the recalled message information.
onMessagesRecalledInfo(info: ChatRecalledMessageInfo[]): void;

Use message extensions

If the message types listed above do not meet your requirements, you can use message extensions to add attributes to the message. This can be applied in more complicated messaging use-cases.

const msg = ChatMessage.createTextMessage(targetId, 'textmessage', chatType);
msg.attributes = {
  key: "value",
  {
    key2: 100
  }
};
EMClient.getInstance().chatManager().sendMessage(msg, callback).then().catch();

Forward a single message

You can use the ChatMessage#body and ChatMessage#attributes property to create a message that is exactly the same as the original message by passing in the message body and extended fields of the original message (if any), and then call the ChatManager#sendMessage method to forward the message.

You can forward all types of messages in individual chats, chat groups, chat rooms, and sub-areas. For messages with attachments, there's no need to re-upload the attachments during forwarding.

However, if the original message expires (for example, deleted from the server due to storage limitations), the recipient can view the attachment address after forwarding but won't be able to download the attachment.

To forward a single message, refer to the following code:

const msg: ChatMessage = ChatMessage.createTextMessage("A", "hello", 0); // Original message
const newMsg: ChatMessage = ChatMessage.createSendMessage({
   ...msg,
   targetId: "B",
   chatType: 0,
   isChatThread: false,
}); // New message
newMsg.body = msg.body;
newMsg.attributes = msg.attributes;
ChatClient.getInstance()
   .chatManager.sendMessage(newMsg, {
     onSuccess(message) {
       // Sent successfully
     },
     onError(localMsgId, error) {
       // Failed to send
     },
   } as ChatMessageStatusCallback)
   .catch();

Forward multiple messages

Supported types for forwarded messages include text, images, audio & video files, attachment, and custom messages. A user can create a combined message with a list of original messages and send it. When receiving a combined message, the recipient can select it and other messages to create a new layered combined message. A combined message can contain up to 10 layers of messages, with at most 300 messages at each layer.

To forward and receive combined messages, refer to the following code:

  1. Create a combined message using multiple message IDs:

    // Construct a combined message.
    const msg = ChatMessage.createCombineMessage(targetId, msgIdList, chatType, {
      title,
      summary,
      compatibleText,
    });
    EMClient.getInstance().chatManager().sendMessage(msg, callback).then().catch();
  2. Download and parse combined messages:

    // message: The combined message object.
    // Asynchronously return the list of original messages.
    ChatClient.getInstance()
      .chatManager.fetchCombineMessageDetail(message)
      .then((messages: ChatMessage[]) => {
        console.log("success: ", messages);
      })
      .catch((error) => {
        console.log("fail: ", error);
      });

For further details see Multiple messages forwarding limitations.

Modify sent messages

For messages that have been sent successfully, the SDK supports modifying the message content.

  • Before SDK v1.4.0, you could only modify text messages sent in one-to-one chats and chat groups.
  • Since SDK v1.4.0, you can modify various message types sent in one-to-one chats, chat groups, and chat rooms:
    • Text and custom messages: modify both the message body and the extension fields (ext).
    • File, video, voice, image, location, and combined messages: modify the extension fields (ext) only.
    • Command messages: modification is not supported.

There is no time limit for modifying a message, that is, it can be modified as long as the message is still stored on the server. After the message is modified, the message life cycle, that is, its storage time on the server, is recalculated. For example, a message can be stored on the server for 180 days, and the user modifies it on the 30th day after the message was sent. Instead of remaining 150 days, the message can be now stored on the server for 180 days after successful modification.

In the modified message, the message ID remains unchanged. The message body, the extension fields, or both are edited and the following items are added:

  • The operator ID of the user performing the action.
  • The operation time that indicates when the message was edited.
  • The number of times a message is edited (up to 10 times).

For the edited message, other information included in the message, such as the message sender and recipient, remains unchanged.

To modify a sent message, refer to the following code. Since SDK v1.4.0, call modifyMsgBody, which replaces the deprecated modifyMessageBody.

  1. Call modifyMsgBody with the message ID, the new message body, and the new extension fields:
    // Text message: you can modify both the message body and the extension fields.
    const body = new ChatTextMessageBody({ content: "Updated message content" });
    const ext = { customKey: "customValue" };
    // body and ext cannot both be null.
    ChatClient.getInstance()
      .chatManager.modifyMsgBody({ msgId, body, ext })
      .then((message) => {
        console.log("modify success:", message);
      })
      .catch((error) => {
        console.warn(error);
      });
    
    // Custom message: you can modify both the message body and the extension fields.
    const customBody = new ChatCustomMessageBody({
      event: "<CUSTOM_EVENT>",
      params: { key1: "value1", key2: "value2" },
    });
    ChatClient.getInstance().chatManager.modifyMsgBody({ msgId, body: customBody });
    
    // File, video, voice, image, location, or combined message: you can modify the extension fields only. Omit the message body.
    ChatClient.getInstance().chatManager.modifyMsgBody({ msgId, ext });
  2. Receive notification of messages modified by other users:
    ChatClient.getInstance().chatManager.addMessageListener({
      onMessageContentChanged: (
        message: ChatMessage,
        lastModifyOperatorId: string,
        lastModifyTime: number
      ): void => {
        console.log(
          `${QuickTestScreenChat.TAG}: onMessageContentChanged: `,
          JSON.stringify(message),
          lastModifyOperatorId,
          lastModifyTime
        );
      },
    } as ChatMessageEventListener);

For further details see Sent message modification limitations.

Next steps

After implementing sending and receiving messages, you can refer to the following documents to add more messaging functionalities to your app: