Azure Communication Service- Chat with File Sharing

Manish Saluja
7 min readFeb 17, 2021

Azure Communication Services (ACS) makes it easy for developers to add chat, SMS text message, voice and video calling capabilities to websites, desktop applications and mobile apps with just a few lines of code. While developer friendly APIs and SDKs make it easy to create personalised communication experiences quickly, without having to worry about complex integrations. These capabilities can be used on virtually any platform and device.

In various discussion around ACS chat scenario, I along with Nafis Zaman and Ramesh Manian identified need for files transfer in chat and we designed simple sample focusing this creating a faster richer experience using ACS. In this blog, let us extend Group chat hero sample to add this feature of file sharing between chat participants in a chat thread(as on today, file sharing is not available as part of ACS Chat) .

Outlining the Use Case

We are using group chat application in which participants need to share files(documents, image and more) with others as part of chat. Along with this baseline, there are few more basic requirements like

  • Limit on the size of file to be shared
  • Preview of image
  • Download option for file if it is not an image
  • Store/persist these files
  • File should be visible, only to chat participants of the specific chat thread.
File Sharing with ACS

In this blog, we shall explore how we can leverage ACS SDKs for chat along with other Azure services to accomplish desired outcome.

Let’s do the white Boarding:

Architectural representation of Azure Communication Services, with integration of Azure Blob storage.

Reference Architecture

In this architecture, we have a sample chat web app built on top of ACS SDKs. The client-side application is a React based user interface which uses Redux for handling complex state while leveraging Microsoft Fluent UI. Powering this front-end is a TypeScript web API powered by Node/Express to connect this application with Azure Communication Services. The Chat web app interact with Trusted service to communicate with Azure communication service. Trusted service is responsible for creating chat threads, managing thread memberships, and providing access tokens to users. After this connection is made, chat web app can send and receive messages. The Chat web app interact with Azure Blob storage for storing files in Blob container and files metadata in table storage.

Lets Hit the Road

We have build a sample app leveraging the Group chat hero sample. Feel free to checkout on GitHub. Let us explore various components/modules which are created on the base repository to implement in file share feature.

So to get started, let us first setup the application and see where the action is. and latter in the blog we shall deep dive in the code.

Setup ACS & Azure Blob Storage and get keys

Click on Deploy to Azure button above to create ACS and Azure Blob Storage resource on Azure Portal.

Get the ACS access keys

The access key, help authorise APIs calls when one use the Communication Services SDKs.

ACS Access Keys

Get the Azure Blob Storage access keys.

Azure Blob Storage Access Keys

Create a .env file in ./Chat/NodeApi with the following keys and fill in the values from your ACS and Storage connection strings:

ACS_CONNECTION_STRING=

STORAGE_CONNECTION_STRING=

When Code will be in action, it creates one Blob Container by name files and a table storage by name fileMetadata. Blob container is used to store file and table storage is used to store metadata information of these files. At high level, for every file, we need to capture fileId, fileName, chatThreadId(PartitionKey)

Azure Storage

Code Walkthrough

Before jumping in to code, we need to figure out a way to send File upload events in ACS, so that participants are aware that a file has been shared. Currently ACS support text message format without message type tagging. To keep our solution simple, let’s leverage existing text chat message.

Now critical question arise how to differentiate between normal chat message and file upload message ? Let us go with popular stringify & serialize JSON object. We shall send this JSON payload in chat message.

Below is sample JSON Payload

{
“event”: “FileUpload”,
“fileId”: “a932f42c-0406–4611-bbf1–31551173ffcb”,
“fileName”: “dummypic.jpg”
}

Now, at high level we have modified/added below components.

  • File upload option in SendBox
  • Create JSON Format Chat Message and Send File to Blob Storage
  • Display Image & Download file in Chat Area

Let us explore how we implemented these in our code base

File Upload option in SendBox

To provide file upload option, we have used paper clip icon in sendBox.

SendBox with File Attachment Option

Chat/ClientApp/src/components/SendBox.tsx is responsible for rendering option to type & send messages. Below is the snippet of code through which we are rendering file upload icon.

<PaperclipIcon outline className={sendActionIconStyle} 
onClick={() => {
if (hiddenFileInput.current !== null) {hiddenFileInput.current.click();}}}
/>

We have also put additional validation of file size here so as to limit file size to conserve Blob storage space. Once we are done with file size validation, props.onSendFile(file) is used to send file.

File Size Limit Message along with File Image Preview

Create JSON Format Chat Message and Send File to Blob Storage

Once user, has uploaded a file using paperclip icon, internally sendFile() is called from sideEffects.ts. In this function it create a JSON for custom file upload event, which we discussed earlier.

Another critical step is tagging file with a specific chatThreadId. This allows file to visible only to participants of chatThread. Internally it call POST:REST API /thread/${threadId}/files. In message body it sends JSON which we created. This REST API is published as part of NodeApi module.

const sendFile = (file: File) => async (dispatch: Dispatch, getState: () => State) => {
const state = getState();
const userId = state.contosoClient.user.identity;
const userDisplayName = state.contosoClient.user.displayName;
const threadId = state.thread.threadId;
if (threadId === undefined) {
return false;
}
const data = new FormData();
data.append(‘file’, file);
data.append(‘fileName’, file.name);
data.append(‘userId’, userId);
data.append(‘userDisplayName’, userDisplayName);
const sendFileRequestOptions: RequestInit = {
method: ‘POST’,
body: data,
};
try {
const sendFileResponse = await fetch(`/thread/${threadId}/files`, sendFileRequestOptions);
return sendFileResponse.ok;
} catch (error) {
console.error(‘Failed to send file: ‘, error);
return false;}};

In NodeApi module there is a file with name azureStorageFileService.ts. This file is responsible for interacting with Azure Blob Storage.

In azureStorageFileService.ts, we define methods uploadFile(), downloadFile() to upload and download file from Blob container.

Methods addFileMetadata(), getFileMetadata() to add and get file metadata from table storage.

Display Image & Download file in Chat Area

In ChatThread.tsx, we have created an interface for custom file event message, this interface is aligned to our JSON format.

interface FileEventMessage {
event: ‘FileUpload’;
fileId: string;
fileName: string;
}

After getting list of messages from server, we need to segregate file event message from the normal text message sent by user. We will parse the message content and check if its having event as FileUpload.

const getFileEventFromMessage = (messageContent: string): FileEventMessage | null => {
try {
const messageContentJson = JSON.parse(messageContent);
if (messageContentJson
&& typeof messageContentJson === ‘object’
&& messageContentJson[‘event’] === ‘FileUpload’
&& typeof messageContentJson[‘fileId’] === ‘string’
&& typeof messageContentJson[‘fileName’] === ‘string’) {
return messageContentJson as FileEventMessage;
}return null;
} catch (e)
{
// Not a file upload event
return null;
}};

In chat element, we will call getFileEventFromMessage() to get message type, if this is a FileUpload event we will render it using FileAttachmentMessage.

const fileEventMessage = getFileEventFromMessage(message.content);
const messageContentItem = fileEventMessage !== null
? (
<div>
<LiveMessage
message={`${message.mine ? ‘You ‘ : message.senderDisplayName} sent a file called ${fileEventMessage.fileName}`}
aria-live=”polite”
/>
<FileAttachmentMessage fileId={fileEventMessage.fileId} fileName={fileEventMessage.fileName} />
</div>
)
: (
<div>

We created a new file FileAttachmentMessage.tsx. Based on the file name, check if it is a JPEG/PNG then file is image and we need to provide preview of the image. For other categories option to download file is given.

File Image Preview & File Download

In sideEffects.ts, getFile() gets file based on fileId and chatThreadId. In this function we are calling REST API `thread/${threadId}/files/${fileId}` to get file object. To get more details about REST API explore NodeApi module in Chat folder.

Summary

Azure Communication Services (ACS) provides a managed platform and communication APIs to build real-time voice, video, SMS, and Chat functionality to solution at scale. In the previous blogs Fully Managed Communication Platform — Azure Communication Service, we had explored how to leverage the Azure Portal to create ACS resource and in the blog Azure Communication Services Explore Chat SDK, we had explored JavaScript SDK for building chat application. In this blog, we have seen how we can leverage ACS SDKs for chat along with other Azure services like Azure Blob Storage to create functionality like files transfer in chat.

In the next blog, we shall explore another ACS feature SMS which enables you to send and receive SMS text messages using the Communication Services SMS client libraries.

References

--

--

Manish Saluja

A curious technology enthusiast with deep expertise in designing, developing and architecting cloud solutions for Public Cloud, Private clouds & Hybrid Clouds.