Compartilhar via


Tutorial: habilitar o suporte de anexo de arquivo em seu aplicativo de Chat

O SDK de Chat funciona perfeitamente com o Microsoft Teams no contexto de uma reunião. Somente um usuário do Teams pode enviar anexos de arquivo para um usuário dos Serviços de Comunicação do Azure. Um usuário dos Serviços de Comunicação do Azure não pode enviar anexos de arquivo para um usuário do Teams. Para ver as funcionalidades atuais, confira Chat de interoperabilidade do Teams.

Adicionar suporte a anexos de arquivo

O SDK de Chat fornece a propriedade previewUrl para cada anexo de arquivo. Especificamente, previewUrl é um link para uma página da Web no SharePoint em que o usuário pode ver o conteúdo do arquivo, editá-lo e, se tiver permissão, baixar o arquivo.

Algumas restrições estão associadas a esse recurso:

  • O administrador do Teams do locatário do remetente pode impor políticas que limitam ou desabilitam totalmente esse recurso. Por exemplo, o administrador do Teams poderá desabilitar determinadas permissões (como Anyone), o que pode fazer com que as URLs de anexo de arquivo (previewUrl) fiquem inacessíveis.

  • Atualmente, só há suporte para duas permissões de arquivo:

    • Anyone
    • People you choose (com endereço de email)

    Permitir que o usuário do Teams saiba que não há suporte para todas as outras permissões (como People in your organization). Os usuários do Teams devem verificar se há suporte para a permissão padrão depois de carregarem o arquivo nos respectivos clientes do Teams.

  • Não há suporte para a URL de download direto (url).

Além de arquivos comuns (com o AttachmentType de file), o SDK de Chat também fornece a propriedade AttachmentType de image. Os usuários dos Serviços de Comunicação do Azure podem anexar imagens de um jeito que imite o comportamento de um cliente do Microsoft Teams convertendo o anexo de imagem em imagens embutidas na camada da interface do usuário. Para obter mais informações, confira Tratar anexos de imagem.

Os usuários dos Serviços de Comunicação do Azure podem adicionar imagens por meio da opção Carregar deste dispositivo, que é renderizada no Teams. Portanto, o SDK de Chat retorna anexos como image. Para imagens carregadas por meio da opção Anexar arquivos de nuvem, as imagens são tratadas como arquivos comuns no Teams. Portanto, o SDK de Chat retorna anexos como file.

Observe também que os usuários dos Serviços de Comunicação do Azure só podem carregar arquivos por meio da funcionalidade do tipo "arrastar e soltar" ou por meio dos comandos do menu de anexos Carregar deste dispositivo e Anexar arquivos de nuvem. No momento, não há suporte para determinados tipos de mensagens com mídia inserida (como clipes de vídeo, mensagens de áudio e cartões meteorológicos).

Este tutorial descreve como habilitar o suporte a anexos de arquivo usando o SDK de Chat dos Serviços de Comunicação do Azure para JavaScript.

Código de exemplo

Encontre o código finalizado desse tutorial no GitHub.

Pré-requisitos

Metas

  • Renderizar o anexo de arquivo no thread de mensagem. Cada cartão de anexo de arquivo tem um botão Abrir.
  • Renderizar anexos de imagem como imagens embutidas.

Manipular anexos de arquivo

O SDK de Chat para JavaScript retorna uma propriedade ChatAttachmentType de file para anexos de arquivo comuns e image para imagens embutidas em mensagens.

export interface ChatMessageReceivedEvent extends BaseChatMessageEvent {
  /**
   * Content of the message.
   */
  message: string;

  /**
   * Chat message attachment.
   */
  attachments?: ChatAttachment[];
  
  ...
}

export interface ChatAttachment {
  /** Id of the attachment */
  id: string;
  /** The type of attachment. */
  attachmentType: AttachmentType;
  /** The name of the attachment content. */
  name?: string;
  /** The URL that is used to provide the original size of the inline images */
  url?: string;
  /** The URL that provides the preview of the attachment */
  previewUrl?: string;
}

/** Type of supported attachments. */
export type ChatAttachmentType = "image" | "file" | "unknown";

Por exemplo, o seguinte JSON mostra o que ChatAttachment pode parecer para um anexo de imagem e um anexo de arquivo:

"attachments": [
    {
        "id": "08a182fe-0b29-443e-8d7f-8896bc1908a2",
        "attachmentType": "file",
        "name": "business report.pdf",
        "previewUrl": "https://contoso.sharepoint.com/:u:/g/user/h8jTwB0Zl1AY"
    },
    {
        "id": "9d89acb2-c4e4-4cab-b94a-7c12a61afe30",
        "attachmentType": "image", 
        "name": "Screenshot.png",
        "url": "https://contoso.communication.azure.com/chat/threads/19:9d89acb29d89acb2@thread.v2/messages/123/images/9d89acb2-c4e4-4cab-b94a-7c12a61afe30/views/original?api-version=2023-11-15-preview",
        "previewUrl": "https://contoso.communication.azure.com/chat/threads/19:9d89acb29d89acb2@thread.v2/messages/123/images/9d89acb2-c4e4-4cab-b94a-7c12a61afe30/views/small?api-version=2023-11-15-preview"
      }
]

Agora, vamos voltar ao manipulador de eventos que você criou no Guia de Início Rápido: ingressar seu aplicativo de Chat em uma reunião do Teams e adicionar uma lógica extra para tratar anexos com a propriedade attachmentType de file:

chatClient.on("chatMessageReceived", (e) => {
  console.log("Notification chatMessageReceived!");
  // Check whether the notification is intended for the current thread
  if (threadIdInput.value != e.threadId) {
     return;
  }
   
  if (e.sender.communicationUserId != userId) {
    renderReceivedMessage(e);
  } else {
    renderSentMessage(e.message);
  }
});

async function renderReceivedMessage(event) {
    messages += `<div class="container lighter"> ${event.message} </div>`;
    messagesContainer.innerHTML = messages;

    // Get the list of attachments and calls renderFileAttachments to construct a file attachment card
    var attachmentHtml = event.attachments
        .filter(attachment => attachment.attachmentType === "file")
        .map(attachment => renderFileAttachments(attachment))
        .join('');
    messagesContainer.innerHTML += attachmentHtml;
}

function renderFileAttachments(attachment) {
    var re = /(?:\.([^.]+))?$/;
    var fileExtension = re.exec(attachment.name)[1];  
    return '<div class="attachment-container">' +
        '<img class="attachment-icon" alt="attachment file icon" />' +
        '<div>' +
        '<p class="attachment-type">' + fileExtension + '</p>' +
        '<p>' + attachment.name + '</p>' +
        '<a href=' + attachment.previewUrl + ' target="_blank" rel="noreferrer">Open</a>' +
        '</div>' +
        '</div>';
}

Adicione um CSS ao cartão de anexo:

  /* Let's make the chat popup scrollable */
  .chat-popup {

     ...

     max-height: 650px;
     overflow-y: scroll;
}

 .attachment-container {
     overflow: hidden;
     background: #f3f2f1;
     padding: 20px;
     margin: 0;
     border-radius: 10px;
}
 .attachment-container img {
     width: 50px;
     height: 50px;
     float: left;
     margin: 0;
}
 .attachment-container p {
     font-weight: 700;
     margin: 0 5px 20px 0;
}
 .attachment-container {
     display: grid;
     grid-template-columns: 100px 1fr;
     margin-top: 5px;
}
 .attachment-icon {
     content: url("data:image/svg+xml;base64, ...");
}
 .attachment-container a {
     background-color: #dadada;
     color: black;
     font-size: 12px;
     padding: 10px;
     border: none;
     cursor: pointer;
     border-radius: 5px;
     text-align: center;
     margin-right: 10px;
     text-decoration: none;
     margin-top: 10px;
}
 .attachment-container a:hover {
     background-color: black;
     color: white;
}
 .attachment-type {
     position: absolute;
     color: black;
     border: 2px solid black;
     background-color: white;
     margin-top: 50px;
     font-family: sans-serif;
     font-weight: 400;
     padding: 2px;
     text-transform: uppercase;
     font-size: 8px;
}

Isso é tudo o que você precisa para tratar anexos de arquivo. Em seguida, vamos executar o código.

Executar o código

No caso do Webpack, você pode usar a propriedade webpack-dev-server para criar e executar seu aplicativo. Execute o seguinte comando para empacotar o host de aplicativos em um servidor Web local:

npx webpack-dev-server --entry ./client.js --output bundle.js --debug --devtool inline-source-map

Ou:

npm start

Demonstração de anexo de arquivo

  1. Abra o navegador e vá parahttp://localhost:8080/. Insira a URL da reunião e a ID do thread.

  2. Envie alguns anexos de arquivo a partir do cliente do Teams.

    Captura de tela que mostra o cliente do Teams com uma mensagem enviada que inclui três anexos de arquivo.

  3. Você deverá ver a nova mensagem sendo renderizada junto com os anexos de arquivo.

    Uma captura de tela que mostra um aplicativo de exemplo com uma mensagem recebida que inclui três anexos de arquivo.

Manipular anexos de imagem

Os anexos de imagem precisam ser tratados de maneira diferente dos anexos padrão file. Os anexos de imagem têm a propriedadeattachmentType de image, o que exige que o token de comunicação recupere as imagens de versão prévia ou em tamanho real.

Antes de continuar, conclua o tutorial que demonstra como habilitar o suporte de imagem embutida no seu aplicativo de Chat. Este tutorial descreve como buscar imagens que exigem um token de comunicação no cabeçalho da solicitação. Depois de receber o blob da imagem, você precisará criar uma propriedade ObjectUrl que aponte para esse blob. Em seguida, você injeta essa URL no atributo src de cada imagem embutida.

Agora que você já se familiarizou com o funcionamento das imagens embutidas, poderá renderizar anexos de imagem como uma imagem embutida comum.

Primeiro, injete uma marca image no conteúdo da mensagem sempre que houver um anexo de imagem:

async function renderReceivedMessage(event) {
    messages += `<div class="container lighter"> ${event.message} </div>`;
    messagesContainer.innerHTML = messages;
    console.log(event);
    // Filter out inline images from attachments
    const imageAttachments = event.attachments?.filter(
        (attachment) =>
        attachment.attachmentType === "image" && !messages.includes(attachment.id)
    );
    // Inject image tag for all image attachments
    var imageAttachmentHtml =
        imageAttachments
        .map((attachment) => renderImageAttachments(attachment))
        .join("") ?? "";
    messagesContainer.innerHTML += imageAttachmentHtml;

    // Get list of attachments and calls renderFileAttachments to construct a file attachment card
    var attachmentHtml =
        event.attachments
        ?.filter((attachment) => attachment.attachmentType === "file")
        .map((attachment) => renderFileAttachments(attachment))
        .join("") ?? "";
    messagesContainer.innerHTML += attachmentHtml;

    // Fetch and render preview images
    fetchPreviewImages(imageAttachments);
}

function renderImageAttachments(attachment) {
    return `<img alt="image" src="" itemscope="png" id="${attachment.id}" style="max-width: 100px">`
}

Agora, vamos pegar o recurso de fetchPreviewImages() emprestado do Tutorial: Habilitar o suporte à imagem embutida e usá-lo no estado em que se encontra, sem alterações:

function fetchPreviewImages(attachments) {
    if (!attachments.length > 0) {
        return;
    }
    Promise.all(
        attachments.map(async (attachment) => {
            const response = await fetch(attachment.previewUrl, {
                method: 'GET',
                headers: {
                    'Authorization': 'Bearer ' + tokenString,
                },
            });
            return {
                id: attachment.id,
                content: await response.blob(),
            };
        }),
    ).then((result) => {
        result.forEach((imageRef) => {
            const urlCreator = window.URL || window.webkitURL;
            const url = urlCreator.createObjectURL(imageRef.content);
            document.getElementById(imageRef.id).src = url;
        });
    }).catch((e) => {
        console.log('error fetching preview images');
    });
}

Essa função precisa de uma propriedade tokenString, portanto, você precisa ter uma cópia global inicializada em init(), conforme mostrado no trecho de código a seguir:

var tokenString = '';

async function init() {
    ...
    const {
        token,
        expiresOn
    } = tokenResponse;
    
    tokenString = token;
    ...
}

Agora você tem um suporte para anexo de imagem. Continue para executar o código e vê-lo em ação.

Demonstração de anexo de imagem

  1. Envie alguns anexos de imagem a partir do cliente do Teams.

    Captura de tela do cliente do Teams mostrando uma caixa de envio com um anexo de imagem carregado.

  2. Depois de enviar o anexo de imagem, observe que ele se torna uma imagem embutida no cliente do Teams.

    Captura de tela do cliente do Teams mostrando uma mensagem com o anexo de imagem enviado ao outro participante.

  3. Volte ao aplicativo de exemplo e verifique se a mesma imagem está renderizada.

    Captura de tela do aplicativo de exemplo mostrando uma mensagem de entrada com uma imagem embutida renderizada.

Este tutorial descreve como habilitar o suporte a anexos de arquivo usando o SDK dos Serviços de Comunicação do Azure de Chat para C#.

Neste tutorial, você aprenderá a:

  • Manipular anexos de arquivo.
  • Manipular anexos de imagem.

Pré-requisitos

Código de exemplo

Encontre o código finalizado deste tutorial no GitHub.

Manipular anexos de arquivo

O SDK de Chat para C# retorna uma propriedade ChatAttachmentType de file para anexos de arquivo comuns e image para imagens embutidas.

public readonly partial struct ChatAttachmentType : IEquatable<ChatAttachmentType>
{
        private const string ImageValue = "image";
        private const string FileValue = "file";
        /// <summary> image. </summary>
        public static ChatAttachmentType Image { get; } = new ChatAttachmentType(ImageValue);
        /// <summary> file. </summary>
        public static ChatAttachmentType File { get; } = new ChatAttachmentType(FileValue);
}


Por exemplo, o JSON a seguir mostra a aparência de ChatAttachment para um anexo de imagem e um anexo de arquivo quando você recebe solicitações do lado do servidor:

"attachments": [
    {
        "id": "08a182fe-0b29-443e-8d7f-8896bc1908a2",
        "attachmentType": "file",
        "name": "business report.pdf",
        "previewUrl": "https://contoso.sharepoint.com/:u:/g/user/h8jTwB0Zl1AY"
    },
    {
        "id": "9d89acb2-c4e4-4cab-b94a-7c12a61afe30",
        "attachmentType": "image", 
        "name": "Screenshot.png",
        "url": "https://contoso.communication.azure.com/chat/threads/19:9d89acb29d89acb2@thread.v2/messages/123/images/9d89acb2-c4e4-4cab-b94a-7c12a61afe30/views/original?api-version=2023-11-15-preview",
        "previewUrl": "https://contoso.communication.azure.com/chat/threads/19:9d89acb29d89acb2@thread.v2/messages/123/images/9d89acb2-c4e4-4cab-b94a-7c12a61afe30/views/small?api-version=2023-11-15-preview"
      }
]

Agora, volte ao manipulador de eventos que você criou no guia de início rápido anterior e adicione uma lógica extra para tratar os anexos com a propriedadeChatAttachmentType de file:


await foreach (ChatMessage message in allMessages)
{
    // Get message attachments that are of type 'file'
    IEnumerable<ChatAttachment> fileAttachments = message.Content.Attachments.Where(x => x.AttachmentType == ChatAttachmentType.File);
    var chatAttachmentFileUris = new List<Uri>();
    foreach (var file in fileAttachments) 
    {
        chatAttachmentFileUris.Add(file.PreviewUri);
    }

    // Build message list
    if (message.Type == ChatMessageType.Html || message.Type == ChatMessageType.Text)
    {
        textMessages++;
        var userPrefix = message.Sender.Equals(currentUser) ? "[you]:" : "";
        var strippedMessage = StripHtml(message.Content.Message);
      


        var chatAttachments = fileAttachments.Count() > 0 ? "[Attachments]:\n" + string.Join(",\n", chatAttachmentFileUris) : "";
        messageList.Add(long.Parse(message.SequenceId), $"{userPrefix}{strippedMessage}\n{chatAttachments}");
    }
}

Especificamente, para cada anexo de arquivo, obtemos a propriedade previewUrl e construímos uma lista de URLs no for loop. Em seguida, você insere a cadeia de caracteres junto com o conteúdo da mensagem de Chat.

Manipular anexos de imagem

Você precisa tratar os anexos de imagem de maneira diferente dos anexos de file padrão. Os anexos de imagem têm a propriedade ChatAttachmentType de image, o que exige que o token de comunicação recupere as imagens de versão prévia ou em tamanho real.

Antes de continuar, termine o tutorial Habilitar suporte à imagem embutida. Para identificar os anexos de imagem, você precisa descobrir se o conteúdo da mensagem contém a mesma ID de imagem dos anexos.

bool isImageAttachment = message.Content.Message.Contains(x.Id);

Caso esse sinalizador seja verdadeiro, aplique a lógica de imagem embutida para renderizá-lo:

IEnumerable<ChatAttachment> imageAttachments = message.Content.Attachments.Where(x => x.AttachmentType == ChatAttachmentType.Image);
// Fetch image and render
var chatAttachmentImageUris = new List<Uri>();
foreach (ChatAttachment imageAttachment in imageAttachments)
{
    client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", communicationTokenCredential.GetToken().Token);
    var response = await client.GetAsync(imageAttachment.PreviewUri);
    var randomAccessStream = await response.Content.ReadAsStreamAsync();
    await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, async () =>
    {
        var bitmapImage = new BitmapImage();
        await bitmapImage.SetSourceAsync(randomAccessStream.AsRandomAccessStream());
        InlineImage.Source = bitmapImage;
    });
    chatAttachmentImageUris.Add(imageAttachment.PreviewUri);
}

Agora seu aplicativo já dá suporte a anexos de imagem.

Próximas etapas