This guide is for Flex UI 1.x.x and channels that use Programmable Chat and Proxy. If you are using Flex UI 2.x.x or you are starting out, we recommend that you build with Webchat 3.x.x.
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!
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.
1// Add a delete button. to every MessageListItem23const DeleteMessage = ({ message }) => (4// message is the default prop passed through by the MessageListItem5<button type="button" onClick={() => message.source.remove()}>6delete7</button>8);910Flex.MessageListItem.Content.add(<DeleteMessage key="delete-message" />, { sortOrder: -1 });
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:
sendMessage
action.1// Implement personal storage23const uploadFileToMyStorage = async (file) => {4const formData = new FormData();5formData.append("image", file);67// Upload the file to private storage8const res = await fetch("https://api.imgur.com/3/image", {9method: "POST",10headers: new Headers({11Authorization: "Client-ID 546c25a59c58ad7"12}),13body: formData14});15return res.json();16};1718// Replace the action19Flex.Actions.replaceAction("SendMediaMessage", async (payload: Flex.ActionPayload) => {20const { file, messageAttributes, channelSid } = payload;2122// Retrieve the uploaded file location23const res = await uploadFileToMyStorage(file);2425// Include the new media file when sending the message26return Flex.Actions.invokeAction("SendMessage", {27messageAttributes: {28...messageAttributes,29media: {30url: res.data.link,31filename: file.name,32contentType: file.type,33size: file.size34}35},36body: file.name,37channelSid38});39});4041// 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!)4243// Create new message bubble content44const PersonalStorageContent = ({ message }) => (45<div>46<img src={message.source.attributes.media.url) alt=”file uploaded from custom storage” style={{ width: "100%" }} />47</div>48);4950Flex.MessageBubble.Content.remove("body", {51if: (props) => !!props.message.source.attributes.media52});5354Flex.MessageBubble.Content.add(<PersonalStorageContent key="message-bubble-body" />, {55if: (props) => !!props.message.source.attributes.media56});
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.
1// Check file content23Flex.Actions.addListener("beforeDownloadMedia", async (payload, cancelAction) => {45const { message } = payload;67const url = await message.media.getContentUrl();89// Validate file before download (note that checkFileContent method needs to be written)1011const result = await checkFileContent(url);1213if (!result.pass) {1415// Failed to validate content of the file1617cancelAction();1819}2021});2223Flex.Actions.addListener("beforeSendMediaMessage", async (payload, cancelAction) => {24const { file } = payload;2526// Validate file before sending27const result = await checkFileContent(file);2829if (!result.pass) {30// Failed to validate content of the file31cancelAction();32}33});3435Flex.Actions.addListener("beforeAttachFile", async (payload, cancelAction) => {3637const { file } = payload;3839// Validate file before attaching40const result = await checkFileContent(file);4142if (!result.pass) {43// Failed to validate content of the file44cancelAction();45}46});
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
1exports.handler = function(context, event, callback) {2// Use context parameter to initialize Twilio client and retrieve stored environment variables3const twilioClient = context.getTwilioClient();4const chatServiceSid = context.CHAT_SERVICE_SID; // // Get Chat service sid from https://www.twilio.com/console/chat/dashboard5const blockedUserSid = context.BLOCKED_USER_SID678// Use the event parameter to retrieve dynamic information, like the current chat channel and the member to blockedUserSid9const {chatChannelSid, memberSid} = event1011console.log(event)1213twilioClient.chat.services(chatServiceSid)14.channels(chatChannelSid)15.members(memberSid)16.update({roleSid: blockedUserSid})17.then(member => callback(null, member.sid))18.catch(err => callback(err, null))19};
Plugin Code
1// Create function to block user2const blockUser = (chatChannelSid, memberSid) => {3const body = { chatChannelSid, memberSid };45// Set up the HTTP options for your request6const options = {7method: 'POST',8body: new URLSearchParams(body),9headers: {10'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8'11}12};1314// Make the network request using the Fetch API15fetch('https://YOUR_DOMAIN.twil.io/block-user', options)16.then(resp => resp.json())17.then(data => console.log(data));18}19}2021// Create a button component to block users22const BlockUserButton = (props) => (23<button type="button" onClick={() => blockUser(props.channelSid, props.member.source.sid)}>24block25</button>26);2728// Insert Block User Button into the Flex UI29Flex.MessageBubble.Content.add(<BlockUserButton key="block-user" />, {30if: (props) => !props.message.isFromMe31});32