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.0.
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.
_10// Add a delete button. to every MessageListItem_10_10const DeleteMessage = ({ message }) => (_10 // message is the default prop passed through by the MessageListItem_10 <button type="button" onClick={() => message.source.remove()}>_10 delete_10 </button>_10);_10_10Flex.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.
_56// Implement personal storage_56_56const uploadFileToMyStorage = async (file) => {_56 const formData = new FormData();_56 formData.append("image", file);_56_56 // Upload the file to private storage_56 const res = await fetch("https://api.imgur.com/3/image", {_56 method: "POST",_56 headers: new Headers({_56 Authorization: "Client-ID 546c25a59c58ad7"_56 }),_56 body: formData_56 });_56 return res.json();_56};_56_56// Replace the action_56Flex.Actions.replaceAction("SendMediaMessage", async (payload: Flex.ActionPayload) => {_56 const { file, messageAttributes, channelSid } = payload;_56_56 // Retrieve the uploaded file location_56 const res = await uploadFileToMyStorage(file);_56_56 // Include the new media file when sending the message_56 return Flex.Actions.invokeAction("SendMessage", {_56 messageAttributes: {_56 ...messageAttributes,_56 media: {_56 url: res.data.link,_56 filename: file.name,_56 contentType: file.type,_56 size: file.size_56 }_56 },_56 body: file.name,_56 channelSid_56 });_56});_56_56// 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!)_56_56// Create new message bubble content_56const PersonalStorageContent = ({ message }) => (_56 <div>_56 <img src={message.source.attributes.media.url) alt=”file uploaded from custom storage” style={{ width: "100%" }} />_56 </div>_56);_56_56Flex.MessageBubble.Content.remove("body", {_56 if: (props) => !!props.message.source.attributes.media_56});_56_56Flex.MessageBubble.Content.add(<PersonalStorageContent key="message-bubble-body" />, {_56 if: (props) => !!props.message.source.attributes.media_56});
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.
_46// Check file content_46_46Flex.Actions.addListener("beforeDownloadMedia", async (payload, cancelAction) => {_46_46 const { message } = payload;_46_46 const url = await message.media.getContentUrl();_46_46 // Validate file before download (note that checkFileContent method needs to be written)_46_46 const result = await checkFileContent(url);_46_46 if (!result.pass) {_46_46 // Failed to validate content of the file_46_46 cancelAction();_46_46 }_46_46});_46_46Flex.Actions.addListener("beforeSendMediaMessage", async (payload, cancelAction) => {_46 const { file } = payload;_46_46 // Validate file before sending_46 const result = await checkFileContent(file);_46_46 if (!result.pass) {_46 // Failed to validate content of the file_46 cancelAction();_46 }_46});_46_46Flex.Actions.addListener("beforeAttachFile", async (payload, cancelAction) => {_46_46 const { file } = payload;_46_46 // Validate file before attaching_46 const result = await checkFileContent(file);_46_46 if (!result.pass) {_46 // Failed to validate content of the file_46 cancelAction();_46 }_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
_19exports.handler = function(context, event, callback) {_19 // Use context parameter to initialize Twilio client and retrieve stored environment variables_19 const twilioClient = context.getTwilioClient();_19 const chatServiceSid = context.CHAT_SERVICE_SID; // // Get Chat service sid from https://www.twilio.com/console/chat/dashboard_19 const blockedUserSid = context.BLOCKED_USER_SID_19_19_19 // Use the event parameter to retrieve dynamic information, like the current chat channel and the member to blockedUserSid_19 const {chatChannelSid, memberSid} = event_19_19 console.log(event)_19_19 twilioClient.chat.services(chatServiceSid)_19 .channels(chatChannelSid)_19 .members(memberSid)_19 .update({roleSid: blockedUserSid})_19 .then(member => callback(null, member.sid))_19 .catch(err => callback(err, null))_19};
Plugin Code
_31// Create function to block user_31const blockUser = (chatChannelSid, memberSid) => {_31 const body = { chatChannelSid, memberSid };_31_31 // Set up the HTTP options for your request_31 const options = {_31 method: 'POST',_31 body: new URLSearchParams(body),_31 headers: {_31 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8'_31 }_31 };_31_31 // Make the network request using the Fetch API_31 fetch('https://YOUR_DOMAIN.twil.io/block-user', options)_31 .then(resp => resp.json())_31 .then(data => console.log(data));_31 }_31}_31_31// Create a button component to block users_31const BlockUserButton = (props) => (_31 <button type="button" onClick={() => blockUser(props.channelSid, props.member.source.sid)}>_31 block_31 </button>_31);_31_31// Insert Block User Button into the Flex UI_31Flex.MessageBubble.Content.add(<BlockUserButton key="block-user" />, {_31 if: (props) => !props.message.isFromMe_31});