Messaging
This page highlights the steps you will need to follow to begin integrating chat messaging into your products
The message payload is always the same regardless of which Development Kit the user is using. Users also have a choice on what type of message they want to send.‌

Major API simplification

We rewrote the message repository from the ground up, deprecating the old code but keeping it working. If you don't want to refactor your code just yet, don't worry, you can still upgrade just fine.
1
declare class MessageRepository {
2
static getMessage(messageId: string): LiveObject<Message>
3
4
static queryMessages(parameters: {
5
channelId?: string,
6
type?: string,
7
parentId?: string | null,
8
filterByParentId?: boolean,
9
isDeleted?: boolean,
10
tags?: string[],
11
excludeTags?: string[],
12
}): LiveCollection<Message>
13
14
static createMessage(parameters: {
15
channelId: string,
16
type: string,
17
data?: Object,
18
fileId?: string,
19
parentId?: string,
20
tags?: string[],
21
metadata?: Object,
22
}): LiveObject<Message>
23
24
static createTextMessage(parameters: {
25
channelId: string,
26
text: string,
27
parentId: string,
28
tags: string[],
29
metadata: Object,
30
}): LiveObject<Message>
31
32
static createImageMessage(parameters: {
33
channelId: string,
34
caption: string,
35
imageId: string,
36
parentId: string,
37
tags: string[],
38
metadata: Object,
39
}): LiveObject<Message>
40
41
static createFileMessage(parameters: {
42
channelId: string,
43
caption: string,
44
fileId: string,
45
parentId: string,
46
tags: string[],
47
metadata: Object,
48
}): LiveObject<Message>
49
50
static async updateMessage(parameters: {
51
messageId: string,
52
data?: Object,
53
tags?: string[],
54
metadata?: Object,
55
}): Promise<boolean>
56
57
static async deleteMessage(messageId: string): Promise<boolean>
58
59
static async addReaction(parameters: {
60
messageId: string,
61
reactionName: string,
62
}): Promise<boolean>
63
64
static async removeReaction(parameters: {
65
messageId: string,
66
reactionName: string,
67
}): Promise<boolean>
68
69
static async flag(messageId: string): Promise<boolean>
70
71
static async unflag(messageId: string): Promise<boolean>
72
73
static async isFlaggedByMe(messageId: string): Promise<boolean>
74
75
@deprecated
76
constructor()
77
78
@deprecated
79
messagesForId(messageId: string): LiveObject<Message>
80
81
@deprecated
82
messagesForChannel({
83
channelId: string,
84
tags?: Array<string>,
85
parentId?: string,
86
filterByParentId?: boolean,
87
}): LiveCollection<Message>
88
89
@deprecated
90
createTextMessage({
91
channelId: string,
92
text: string,
93
tags: Array<string>,
94
parentId?: string,
95
}): LiveObject<Channel>
96
97
@deprecated
98
async addReaction(parameters: {
99
messageId: string,
100
reactionName: string,
101
}): Promise<boolean>
102
103
@deprecated
104
async removeReaction(parameters: {
105
messageId: string,
106
reactionName: string,
107
}): Promise<boolean>
108
109
@deprecated
110
async flag(messageId: string): Promise<boolean>
111
112
@deprecated
113
async unflag(messageId: string): Promise<boolean>
114
}
115
116
@deprecated
117
declare class MessageEditorRepository {
118
get messageId(): string
119
120
constructor(messageId: string)
121
122
async editText(text: string): Promise<void>
123
124
async delete(): Promise<void>
125
}
126
127
@deprecated
128
declare class MessageFlagRepository {
129
get messageId(): string
130
131
get privileges(): string[]
132
133
constructor(messageId: string)
134
135
async flag(): Promise<void>
136
137
async unflag(): Promise<void>
138
139
async isFlaggedByMe(): Promise<boolean>
140
}
Copied!

List of corresponding methods

Before Refactoring
After Refactoring
messageRepo.messageForId
MessageRepository.getMessage
messageRepo.messagesForChannel
MessageRepository.queryMessages
messageRepo.createTextMessage
MessageRepository.createTextMessage
messageRepo.addReaction
MessageRepository.addReaction
messageRepo.removeReaction
MessageRepository.removeReaction
messageRepo.flag
MessageRepository.flag
messageRepo.unflag
MessageRepository.unflag
editRepo.editText
MessageRepository.updateMessage
editRepo.delete
MessageRepository.deleteMessage
flagRepo.flag
MessageRepository.flag
flagRepo.unflag
MessageRepository.unflag
flagRepo.isFlaggedByMe
MessageRepository.isFlaggedByMe

Message Types

Amity supports the sending and receiving of 5 types of messages. They are Text, Image, Audio, File, and Custom.

Image

Amity will automatically optimize the image and when queried, will return the image in small, medium and large sizes. If the image is marked as isFull on upload, the original size of the image can also be returned.

Specifications on image size

When an image is uploaded, it is automatically resized into multiple sizing options. The size of the image is determined by its longest dimension (in pixels) with the aspect ratios being unchanged.
The maximum file size of an image cannot exceed 1 GB.‌

Message Format

When a user is observing messages, the message will always appear in this format.
1
{
2
"status": "string",
3
"data": {
4
"messages": [
5
{
6
"messageId": "string",
7
"parentId": "string",
8
"childrenNumber": 0,
9
"channelId": "string",
10
"userId": "string",
11
"type": "string",
12
"tags": [
13
"string"
14
],
15
"data": {
16
"text": "string"
17
},
18
"isDeleted": true,
19
"channelSegment": 0,
20
"createdAt": "2020-01-02T06:54:59.244Z",
21
"updatedAt": "2020-01-02T06:54:59.244Z",
22
"editedAt": "2020-01-02T06:54:59.244Z",
23
"flagCount": 0,
24
"hashFlag": {
25
"bits": 0,
26
"hashes": 0,
27
"hash": [
28
0
29
]
30
},
31
"reactions": {
32
"reactionName": 1,
33
},
34
"reactionsCount": 0,
35
"myReactions": [
36
"string"
37
]
38
}
39
],
40
"users": [
41
{
42
"userId": "string",
43
"roles": [
44
"string"
45
],
46
"displayName": "string",
47
"flagCount": 0,
48
"metadata": {},
49
"hashFlag": {
50
"bits": 0,
51
"hashes": 0,
52
"hash": [
53
0
54
]
55
},
56
"createdAt": "2020-01-02T06:54:59.244Z",
57
"updatedAt": "2020-01-02T06:54:59.244Z"
58
}
59
]
60
}
61
}
Copied!

Messages Description

Name
Data Type
Description
Attributes
messageId
string
The id of this message
​Content
parentId
string
The messageId of the parent of this message
​Content
childrenNumber
integer
The number of messages with parentId of this message
​Content
channelId
string
The name of the channel this message was created in
​Content
userId
string
The name of the user this message was created by
​Content
type
string
The message type
enum*: text custom image file
tags
Array.<string>
The message tags
​Content
data
Object
The message data (any text will be stored in text key)
text: Text message
isDeleted
boolean
The message has been marked as deleted
​Content
channelSegment
integer
The sequence number of a message in channel
​Content
createdAt
date
The date/time the message was created at
​Content
updatedAt
date
The date/time the message was updated at
​Content
editedAt
date
The date/time the message was edited at
​Content
flagCount
integer
The number of users that have flagged this message
​Content
hashFlag
Object
A hash for checking internally if this message was flagged by the user
​Content
reactions
Object
The reaction data (stored as a reactionName and counter key/value pair)
Example: { like: 1, dislike: 2 }
reactionsCount
integer
The total number of reactions on this message
​Content
myReactions
Array.<string>
List of user's reactions on this message
​Content
mentionees
Array.<Object>
List of mentionees
Example: [{ type: 'channel' }, { type: 'user', userIds: 'userId1', 'userId2' }]
Messages are JSON content containers that can weigh up to 100KB and will be synchronized among all channel users in real-time. If a message requires larger binary data (such as when sending files), we recommend to upload the data to another cloud storage service, such as AWS S3, and store the URL to the content in the message data.
In addition the JSON message type, the SDK also provides support for common text and image message types. These additional types are built on top of the standard JSON message layer.
In case of image messages, the SDK freely provides a cloud storage service that will process and store all images uploaded: you don't need to setup and manage a separate cloud storage service for this common case.
All messaging methods are contained in a MessageRepository class. Before calling any messaging methods, you must ensure to first instantiate a repository instance using the ASCClient instance you created on setup.
1
import { MessageRepository } from '@amityco/js-sdk';
2
const messageRepo = new MessageRepository();
Copied!

Sending Messages

All message sending methods are designed to be robust enough to work under any network conditions. When you send any message, that message will automatically be put into a queue incase of any unforeseen unstable network conditions. Once the SDK reconnects to the server, it will automatically resend all the queued messages.
Additionally, sent messages are always returned in message queries, even before they have been delivered to the server. This provides the user with a fluid messaging flow: when a user sends a message, that sent message will appear in the message stream right away (instead of waiting until it has been confirmed by the server). To check or display the current status of message delivery for your application, use the syncState property in the message model; for web you should useMessage.getState() method in the Message object.

Text Messages

Initiate the messaging with the following scripts, depending on your platform of choice
1
const messageLiveObject = messageRepo.createTextMessage({
2
channelId: 'channel1',
3
text: 'Hello World!',
4
});
Copied!
The limit for sending text messages is 20,000 characters per text message. Messages exceeding that limit will return an error and will not be sent.

Create an image (or file) message

To do this, you will need to leverage the FileRepository and create a file, and pass simply the file id into the createImageMessage later on. This method also accepts an optional caption parameter to be able to display additional text with the message. You can add up to 1,000 characters of text caption per message.
Here's a small example on how to create a message with an image attached. The process is pretty simple:
    1.
    Upload a file
    2.
    Create a message with the uploaded file id.
1
import { FileRepository, MessageRepository } from '@amityco/js-sdk';
2
3
// this function takes in input a File from a <input type="file" />
4
const createImageMessage = (file: File) => {
5
// first, create the file object.
6
const liveFile = FileRepository.createFile({ file })
7
8
// second, create the message object with the fileId from the liveFile
9
const liveMessage = MessageRepository.createImageMessage({
10
channelId: 'my-channel',
11
imageId: liveFile.model.fileId,
12
caption: 'have a look!',
13
})
14
}
Copied!

Message Query

To query for a list of all messages in a channel:
1
import { MessageRepository } from '@amityco/js-sdk';
2
3
const messageRepo = new MessageRepository();
4
const messages = messageRepo.messagesForChannel({ channelId: 'channel1' });
5
6
messages.on('dataUpdated', data => {
7
// reload messages table
8
});
9
10
messages.on('dataError', error => {
11
console.log('Message LiveCollections can not query/get/sync data from server');
12
});
13
14
// unobserve data changes once you are finished
15
messages.removeAllListeners('dataUpdated');
16
messages.removeAllListeners('dataError');
Copied!
This method will return a LiveCollection of all messages in the specified channel. You can observe the LiveCollection in order to update your view whenever you receive new messages.
Since v1.3.0, messages can be organized in threads thanks to the parentId property.

Querying messages

A popular request was to have a isDeleted boolean filter to avoid querying deleted messages to display in a chatroom. It is now available and can be used as:
1
import { MessageRepository } from '@amityco/js-sdk';
2
3
const getChannelMessages = (channelId) => {
4
return MessageRepository.queryMessages({ channelId, isDeleted: false })
5
}
Copied!
The overall rule for the isDeleted parameter is as following:
    isDeleted = ndefined : both deleted and not deleted messages will be returned
    isDeleted = true : only deleted messages will be returned
    isDeleted = false : only non deleted messages will be returned

Messages with attachments

We recently added in web the capability to add an image and or a file to a message. There are few new methods such as createImageMessage or createFileMessage which will help you for this task.

Displaying an image message

Once uploaded, your message can displayed simply by fetching the file from the FileRepository and use the file.fileUrl property to display the image.
Here's a small React component which achieves that:
1
import React, { useEffect, useState } from 'react';
2
import { FileRepository } from '@amityco/js-sdk';
3
4
const ImageMessageContent = ({ data, fileId }) => {
5
const [file, setFile] = useState();
6
7
useEffect(() => {
8
const liveObject = FileRepository.getFile(fileId);
9
10
liveObject.on('dataUpdated', setFile);
11
liveObject.model && setFile(liveObject.model);
12
13
return () => liveObject.dispose()
14
}, [fileId])
15
16
return (
17
<>
18
{data?.caption && <p>{data!.caption}</p>}
19
{file && <img src={file.fileUrl} />}
20
</>
21
)
22
}
Copied!

Displaying various types of message

In the context of a chat room, messages can sometimes be of type image or file, or simply text. The simpliest strategy is to have one renderer per type, and use them when appropriate.
You'll need:
    1.
    A specific renderer for each type (TextContent, ImageContent, and FileContent)
    2.
    A dictionary to match the message.type to those renderers
    3.
    A single-point-of-entry component MessageContent.
1
import React, { useEffect, useState } from 'react';
2
import { FileRepository, MessageType } from '@amityco/js-sdk';
3
4
// renders a text message
5
const TextContent = ({ data, fileId }) => {
6
return data?.text ? <p>{data!.text}</p> : null;
7
}
8
9
// renders an image message
10
const ImageContent = ({ data, fileId }) => {
11
const [file, setFile] = useState();
12
13
useEffect(() => {
14
const liveObject = FileRepository.getFile(fileId);
15
16
liveObject.on('dataUpdated', setFile);
17
liveObject.model && setFile(liveObject.model);
18
19
return () => liveObject.dispose()
20
}, [fileId])
21
22
return (
23
<>
24
{data?.caption && <p>{data!.caption}</p>}
25
{file && <img src={file.fileUrl} />}
26
</>
27
)
28
}
29
30
// renders a file message
31
const FileContent = ({ data, fileId }) => {
32
const [file, setFile] = useState();
33
34
useEffect(() => {
35
const liveObject = FileRepository.getFile(fileId);
36
37
liveObject.on('dataUpdated', setFile);
38
liveObject.model && setFile(liveObject.model);
39
40
return () => liveObject.dispose()
41
}, [fileId])
42
43
return (
44
<>
45
{file && <a href={file.fileUrl} download>{file.attributes.name}</a>}
46
{data?.caption && <p>{data!.caption}</p>}
47
</>
48
)
49
}
50
51
// a Record<MessageType, MessageRenderer>
52
const MessageRenderers = {
53
[MessageType.Text]: TextContent,
54
[MessageType.Image]: ImageContent,
55
[MessageType.File]: FileContent,
56
}
57
58
// Your React 'switch/case' component
59
const MessageContent = ({ type, data, fileId, isDeleted }) => {
60
if (isDeleted)
61
return 'message deleted';
62
63
const Renderer = MessageRenderers[type]
64
65
return Renderer
66
? <Renderer data={data} fileId={fileId} />
67
: 'unsupported message type';
68
}
Copied!

Threaded Messages

A message can be the root for a thread. To query the children of a message thread, you can add the parentId parameter in a message query, along with the filterByParentId flag.
1
import { MessageRepository } from '@amityco/js-sdk';
2
3
const messageRepo = new MessageRepository();
4
const messages = messageRepo.messagesForChannel({
5
channelId: 'channel1',
6
parentId: 'exampleParentMessageId',
7
filterByParentId: true,
8
});
Copied!
When creating a message, we can also pass the parentId to make it appear under a parent.
1
const messageLiveObject = messageRepo.createTextMessage({
2
channelId: 'channel1',
3
text: 'Hello World!',
4
parentId: 'exampleParentMessageId,
5
});
Copied!

Flag & Unflag Messages

Users can flag messages and unflag messages that they have flagged using the MessageFlagRepository class.
1
import { MessageFlagRepository } from '@amityco/js-sdk';
2
const flagRepo = new MessageFlagRepository('message1');
Copied!
To flag a message, call the following method:
1
flagRepo.flag()
Copied!
To unflag a message, call the following method:
1
flagRepo.unflag()
Copied!
The User can also check if they have previously flagged the message before by calling the following asynchronous method:
1
const result = await flagRepo.isFlaggedByMe()
Copied!
If this method has been called before in the current session, the user can also check the cached result on the message payload itself.
1
// Note. If the value is undefined, it means the data has not been cached
2
message.isFlaggedByMeCache
Copied!

Editing Messages

A special MessageEditorRepository class is also provided for you to perform actions on messages you've sent or received. These actions include editing and deleting an existing message, as well as marking a message as being read by you.
To start, first instantiate a MessageEditor instance with the ASCClient instance you created on setup, as well as a valid messageId.
1
import { MessageEditorRepository } from '@amityco/js-sdk';
2
const editor = new MessageEditorRepository('message1');
Copied!

Edit and Delete

You can only perform edit and delete operations on your own messages. Once the operation is complete, the message's editedAtDate will be set to the current time. This allows you to provide UI to the user to inform the user of specific messages that has been edited, if needed. An optional completion block can be provided to notify you of operation success.
1
// edit current message with new text
2
editor.editText('New edited text').catch(error => {
3
...
4
});
5
6
// delete current message
7
editor.delete().catch(error => {
8
...
9
});
Copied!

Mark Message as Read

To mark a message as being read by you, simply call the following method with an optional completion block:
1
editor.markRead().catch(error => {
2
...
3
});
Copied!
Marking a message as read will also retroactively mark all previous messages as read by you as well. You will only need to call this method once on the latest message.

A full chatroom example

In this code example, we'll demonstrate the creation of messages with text, image or file and the display of them in a single chat room.

Known Limitation

Missing Previous Page with Real-time Event when filter is applied

When a filter is applied, if you have already reached the first page and there is a real-time event, you may not be able to go further. You need to recreate the LiveCollection or refresh the cache to address this.

Mention

The mention feature in chat can be used to call attention to specific members. By @mentioning their names, this will promptly notify them so you can get a faster response to your message. Mention is only supported on TEXT message type.
Mention is supported in these channel types:
    Community
    Live
Mention is not supported in the Conversation channel type.

Create a message with mentions

There are 2 types of mentions:
    1.
    USER - When using mention of this type, a specific member being mentioned will receive a push notification. Up to 30 channel members can be mentioned per message.
    1
    import { MentionType, MessageRepository, MessageTools } from '@amityco/js-sdk';
    2
    3
    MessageRepository.createTextMessage({
    4
    channelId: 'channelId',
    5
    text: 'hi @user1 @user2',
    6
    mentionees: [{ type: MentionType.User, userIds: ['userId1', 'userId2'] }],
    7
    metadata: MessageTools.createMentionMetadata([
    8
    { type: MentionType.User, userId: 'userId1', index: 3, length: 5 },
    9
    { type: MentionType.User, userId: 'userId2', index: 10, length: 5 },
    10
    ]),
    11
    });
    Copied!
    2.
    CHANNEL - When using mention of this type, all channel members will receive push notification.
    1
    // mention the whole channel
    2
    val mentionees = mutableListOf<AmityMentionee>()
    3
    mentionees.add(AmityMentionee.CHANNEL)
    4
    5
    AmityChatClient
    6
    .newMessageRepository()
    7
    .createMessage("channelId")
    8
    .with()
    9
    .text("text")
    10
    .metadata(AmityMentionMetadataCreator(mentionMetaData).create())
    11
    .mention(mentionees)
    12
    .build()
    13
    .send()
    Copied!

Mentioning banned users

Banned users cannot be mentioned. However, admins can still find the banned users' names in the suggestion list during search when composing the message. But once the message is sent, the banned users’ information will not be included in the message payload anymore.
Banned users will not be notified nor receive any push notification.
Users who are not admins will not be able to search for the banned users' names as the latter will not appear in the suggestion list.

Update mentions for a text message

1
import { MentionType, MessageRepository, MessageTools } from '@amityco/js-sdk';
2
3
MessageRepository.updateMessage({
4
messageId: 'messageId',
5
data: { text: 'hi @user3' },
6
mentionees: [{ type: MentionType.User, userIds: ['userId3'] }],
7
metadata: MessageTools.createMentionMetadata([{ type: MentionType.User, userId: 'userId3', index: 3, length: 5 }]),
8
});
Copied!

Search members

To search members when mentioning, you must use AmityChannelParticipation’s searchMembers function:
1
import { ChannelRepository, MemberFilter } from '@amityco/js-sdk';
2
3
const liveCollection = ChannelRepository.queryMembers({
4
channelId: 'channelId',
5
memberships: [MemberFilter.Member, MemberFilter.Muted],
6
search: 'Mik'
7
});
8
9
liveCollection.on('dataUpdated', members => {
10
console.log(members);
11
});
Copied!

Viewing mentions

SDK only sends push notifications according to mention input during message creation. The system doesn't handle any data related to UI rendering. However, AmityMessage has a property called metadata which can hold an object and will not be tampered by the system. So, it can be utilized as a storage for UI-related data.

UI helper

SDK provides a helper which helps in generating a metadata containing sufficient data to highlight text with indices.
1
import { MentionType, MessageRepository, MessageTools } from '@amityco/js-sdk';
2
3
let messages = [];
4
5
const liveObject = MessageRepository.queryMessages({ channelId: channelId });
6
7
liveObject.on('dataUpdated', models => {
8
messages = models;
9
});
10
11
messages = liveObject.models;
12
13
messages.forEach(message => {
14
const channelMentions = MessageToos.getMentionChannels(message.metadata);
15
16
channelMentions.forEach(mention => {
17
mention.index;
18
mention.length;
19
});
20
21
const userMentions = MessageToos.getMentionUsers(message.metadata);
22
23
userMentions.forEach(mention => {
24
mention.index;
25
mention.length;
26
});
27
});
Copied!

Mention notifications

When users are being mentioned, they will receive push notifications. You can customize the push notification content such as the title and the body using the notification setting API.
Provide the notification title and body in the titleTemplate and bodyTemplate parameters respectively. Here is a sample model:
1
{
2
"level": "network",
3
"isPushNotifiable": true,
4
"notifiableEvents": [
5
{
6
"name": "text-mention-message.created",
7
"isPushNotifiable": true,
8
"titleTemplate": "{{UserDisplayName}} mentioned you in {{ChannelName}}",
9
"bodyTemplate": "{{Message}}"
10
}
11
]
12
}
Copied!
Name
Data Type
Description
name
String
Event name
isPushNotifiable
Boolean
If set to true,push notification will be received for all text messages. If set to false, only messages with mention will have push notifications.
titleTemplate
String
Notification title
bodyTemplate
String
Notification body
Last modified 7d ago