Building with Chat Attachments
This guide is for Flex UI 1.x and channels that use Programmable Chat and Proxy. If you are using Flex UI 2.x or you are starting out, we recommend that you build with Flex Conversations.
Chat attachments allow agents and customers to send non-text content, like images, pdfs, and videos using Flex Webchat. While you can do a lot of cool stuff using chat attachments, you'll need to create rules and safeguards to keep agents safe, customers happy, and your business secure. Fortunately, chat attachments were built in a customizable way. In the following code samples, you can see some of the strategies the Twilio team has cooked up to implement some common scenarios.
There are many other ways to solve these problems, and your problems will likely be more complex than the scenarios presented here. These code samples are meant to be a starting point to help familiarize you with some of the tools you have available - so definitely tweak these to fit your preferred tooling and patterns!
Use Programmable Chat and the Flex UI to delete messages
Each chat attachment is associated with a Message in the Programmable Chat API. To delete a file which has been sent, you can therefore delete the message using the remove
method provided by Programmable Chat SDK.
// Add a delete button. to every MessageListItem
const DeleteMessage = ({ message }) => (
// message is the default prop passed through by the MessageListItem
<button type="button" onClick={() => message.source.remove()}>
delete
</button>
);
Flex.MessageListItem.Content.add(<DeleteMessage key="delete-message" />, { sortOrder: -1 });
Replace Actions to upload files to personal storage
By default, chat attachments uses Twilio Chat's Media Resource for file storage. To use your own storage for message attachments, you’ll need to replace the SendMediaMessage action, which is triggered when a media file is sent.
You’ll need to:
- Upload the file to our own private storage
- Pass on the unique identifier to the replaced sendMessage Action in messageAttributes
- Trigger a regular
sendMessage
action.
// Implement personal storage
const uploadFileToMyStorage = async (file) => {
const formData = new FormData();
formData.append("image", file);
// Upload the file to private storage
const res = await fetch("https://api.imgur.com/3/image", {
method: "POST",
headers: new Headers({
Authorization: "Client-ID 546c25a59c58ad7"
}),
body: formData
});
return res.json();
};
// Replace the action
Flex.Actions.replaceAction("SendMediaMessage", async (payload: Flex.ActionPayload) => {
const { file, messageAttributes, channelSid } = payload;
// Retrieve the uploaded file location
const res = await uploadFileToMyStorage(file);
// Include the new media file when sending the message
return Flex.Actions.invokeAction("SendMessage", {
messageAttributes: {
...messageAttributes,
media: {
url: res.data.link,
filename: file.name,
contentType: file.type,
size: file.size
}
},
body: file.name,
channelSid
});
});
// Now you need to render your uploaded file. First, delete the body of the MessageBubble. Then, add a new body, including appropriate HTML that points to your uploaded file (in this example, an image tag is sufficient for rendering the image. Don’t forget your alt text!)
// Create new message bubble content
const PersonalStorageContent = ({ message }) => (
<div>
<img src={message.source.attributes.media.url) alt=”file uploaded from custom storage” style={{ width: "100%" }} />
</div>
);
Flex.MessageBubble.Content.remove("body", {
if: (props) => !!props.message.source.attributes.media
});
Flex.MessageBubble.Content.add(<PersonalStorageContent key="message-bubble-body" />, {
if: (props) => !!props.message.source.attributes.media
});
Use before Action hooks to filter content and check for viruses
Depending on your use case, content filtering and virus checking can be done in different ways, but most implementations will involve the before
action hooks. For example, you can use it to download the file after content filtering, or run the downloaded file against a set of guidelines before it gets sent as a message.
// Check file content
Flex.Actions.addListener("beforeDownloadMedia", async (payload, cancelAction) => {
const { message } = payload;
const url = await message.media.getContentUrl();
// Validate file before download (note that checkFileContent method needs to be written)
const result = await checkFileContent(url);
if (!result.pass) {
// Failed to validate content of the file
cancelAction();
}
});
Flex.Actions.addListener("beforeSendMediaMessage", async (payload, cancelAction) => {
const { file } = payload;
// Validate file before sending
const result = await checkFileContent(file);
if (!result.pass) {
// Failed to validate content of the file
cancelAction();
}
});
Flex.Actions.addListener("beforeAttachFile", async (payload, cancelAction) => {
const { file } = payload;
// Validate file before attaching
const result = await checkFileContent(file);
if (!result.pass) {
// Failed to validate content of the file
cancelAction();
}
});
Use Programmable Chat to change permissions of inappropriate users
Sometimes a chat user can send inappropriate files or messages and as an agent you might want to block this user from sending messages or media messages. This can be done using programmable chat rest api.
All users in chat are associated with a Role and Permissions. To block users, you'll want to create a blockedUser
Role. Based on your needs, remove the sendMessage
and/or sendMediaMessage
permissions from the new Role
In order to block somebody, you can update the Role of their Member Resource using a Twilio Function. Provide the new blockedUser
SID as the roleSid
parameter. Check out the guide on using Functions from your Plugin for additional support.
Twilio Function Code
exports.handler = function(context, event, callback) {
// Use context parameter to initialize Twilio client and retrieve stored environment variables
const twilioClient = context.getTwilioClient();
const chatServiceSid = context.CHAT_SERVICE_SID; // // Get Chat service sid from https://www.twilio.com/console/chat/dashboard
const blockedUserSid = context.BLOCKED_USER_SID
// Use the event parameter to retrieve dynamic information, like the current chat channel and the member to blockedUserSid
const {chatChannelSid, memberSid} = event
console.log(event)
twilioClient.chat.services(chatServiceSid)
.channels(chatChannelSid)
.members(memberSid)
.update({roleSid: blockedUserSid})
.then(member => callback(null, member.sid))
.catch(err => callback(err, null))
};
Plugin Code
// Create function to block user
const blockUser = (chatChannelSid, memberSid) => {
const body = { chatChannelSid, memberSid };
// Set up the HTTP options for your request
const options = {
method: 'POST',
body: new URLSearchParams(body),
headers: {
'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8'
}
};
// Make the network request using the Fetch API
fetch('https://YOUR_DOMAIN.twil.io/block-user', options)
.then(resp => resp.json())
.then(data => console.log(data));
}
}
// Create a button component to block users
const BlockUserButton = (props) => (
<button type="button" onClick={() => blockUser(props.channelSid, props.member.source.sid)}>
block
</button>
);
// Insert Block User Button into the Flex UI
Flex.MessageBubble.Content.add(<BlockUserButton key="block-user" />, {
if: (props) => !props.message.isFromMe
});
Need some help?
We all do sometimes; code is hard. Get help now from our support team, or lean on the wisdom of the crowd by visiting Twilio's Stack Overflow Collective or browsing the Twilio tag on Stack Overflow.