Seletor de Arquivos

O Seletor de Arquivos v8 permite que você use a mesma funcionalidade usada no serviço M365 em suas soluções. Ou seja, à medida que iteramos e aprimoramos o serviço, esses novos recursos aparecem para seus usuários!

Esse novo "controle" é uma página hospedada no serviço Microsoft com a qual você interage por meio de mensagens de postagem. A página pode ser hospedada inserida em um iframe ou como pop-up.

Mostrar-me o código de exemplo

Você pode encontrar a documentação do seletor 7.2 aqui.

Configuração necessária

Para executar os exemplos ou usar o controle em sua solução, você precisará criar um aplicativo do AAD. Você pode seguir estas etapas:

  1. Crie um novo Registro de Aplicativo do AAD e anote a ID do aplicativo
  2. Em autenticação, crie um registro de aplicativo de página única
    1. Defina o URI de redirecionamento como https://localhost (isso é para testar os exemplos)
    2. Verifique se os tokens de acesso e os tokens de ID estão marcados
    3. Opcionalmente, você pode configurar este aplicativo para multilocatário, mas isso está fora do escopo deste artigo
  3. Em permissões de API
    1. AdicionarFiles.Read.All, Sites.Read.All, deixar User.Read para permissões delegadas do Graph
    2. Adicionar AllSites.Read, MyFiles.Read para permissões delegadas do SharePoint

Se você estiver desenvolvendo na Estrutura do SharePoint poderá solicitar essas permissões no manifesto do aplicativo com os recursos "SharePoint" e "Microsoft Graph".

Para permitir que o usuário carregue arquivos e crie pastas na experiência do Picker, você pode solicitar acesso a Files.ReadWrite.All, Sites.ReadWrite.All, AllSites.Write, e MyFiles.Write.

Permissões

O seletor de arquivos sempre opera usando permissões delegadas e, como tal, só pode acessar arquivos e pastas às quais o usuário atual já tem acesso.

No mínimo, você deve solicitar a permissão MyFiles.Read do SharePoint para ler arquivos dos sites do OneDrive e do SharePoint do usuário.

Examine a tabela abaixo para entender qual permissão é necessária com base nas operações que você deseja executar. Todas as permissões nesta tabela referem-se a permissões delegadas.

Ler Gravar
OneDrive SharePoint.MyFiles.Read
ou
Graph.Files.Read
SharePoint.MyFiles.Write
ou
Graph.Files.ReadWrite
SharePoint Sites SharePoint.MyFiles.Read
ou
Graph.Files.Read
ou
SharePoint.AllSites.Read
SharePoint.MyFiles.Write
ou
Graph.Files.ReadWrite
ou
SharePoint.AllSites.Write
Canais do Teams Graph.ChannelSettings.Read.All e SharePoint.AllSites.Read Graph.ChannelSettings.Read.All e SharePoint.AllSites.Write

Como funciona

Para usar o controle, você deve:

  1. Faça uma solicitação POST para a página "controle" hospedada em /_layouts/15/FilePicker.aspx. Ao usar essa solicitação, você fornece alguns parâmetros, o principal é a configuração do seletor.
  2. Configure o sistema de mensagens entre o aplicativo host e o controle usando postMessage e portas de mensagem.
  3. Depois que o canal de comunicação for estabelecido, você deverá responder a vários "comandos", o primeiro deles é fornecer tokens de autenticação.
  4. Por fim, você precisará responder a mensagens de comando adicionais para fornecer tokens de autenticação novos/diferentes, manipular arquivos selecionados ou fechar o pop-up.

As seções a seguir explicam melhor cada cenário.

Também temos uma variedade de exemplos que mostram diferentes maneiras de se integrar ao controle.

Iniciar o Seletor

Para iniciar o seletor, você precisa criar uma "janela", que pode ser um iframe ou um pop-up. Depois de ter uma janela, você deve construir um formulário e fazer POST do formulário para a URL {baseUrl}/_layouts/15/FilePicker.aspx com os parâmetros de cadeia de caracteres de consulta definidos.

O valor {baseUrl} acima é a URL da Web do SharePoint da Web de destino ou o OneDrive do usuário. Alguns exemplos são: "https://tenant.sharepoint.com/sites/dev" ou "https://tenant-my.sharepoint.com".

Configuração do consumidor do OneDrive

nome Descrições
Autoridade https://login.microsoftonline.com/consumers
Escopo OneDrive.ReadWrite ou OneDrive.ReadOnly
baseUrl https://onedrive.live.com/picker

Ao solicitar um token, você usará o OneDrive.ReadOnly ou OneDrive.ReadWrite quando solicitar o token. Ao solicitar as permissões para seu aplicativo, você selecionará para Files.Read ou Files.ReadWrite (ou outro escopo Files.X).

// create a new window. The Picker's recommended maximum size is 1080x680, but it can scale down to
// a minimum size of 250x230 for very small screens or very large zoom.
const win = window.open("", "Picker", "width=1080,height=680");

// we need to get an authentication token to use in the form below (more information in auth section)
const authToken = await getToken({
    resource: baseUrl,
    command: "authenticate",
    type: "SharePoint",
});

// to use an iframe you can use code like:
// const frame = document.getElementById("iframe-id");
// const win = frame.contentWindow;

// now we need to construct our query string
// options: These are the picker configuration, see the schema link for a full explaination of the available options
const queryString = new URLSearchParams({
   filePicker: JSON.stringify(options),
   locale: 'en-us'
});

// Use MSAL to get a token for your app, specifying the resource as {baseUrl}.
const accessToken = await getToken(baseUrl);

// we create the absolute url by combining the base url, appending the _layouts path, and including the query string
const url = baseUrl + `/_layouts/15/FilePicker.aspx?${queryString}`);

// create a form
const form = win.document.createElement("form");

// set the action of the form to the url defined above
// This will include the query string options for the picker.
form.setAttribute("action", url);

// must be a post request
form.setAttribute("method", "POST");

// Create a hidden input element to send the OAuth token to the Picker.
// This optional when using a popup window but required when using an iframe.
const tokenInput = win.document.createElement("input");
tokenInput.setAttribute("type", "hidden");
tokenInput.setAttribute("name", "access_token");
tokenInput.setAttribute("value", accessToken);
form.appendChild(tokenInput);

// append the form to the body
win.document.body.append(form);

// submit the form, this will load the picker page
form.submit();

Configuração do Seletor

O seletor é configurado por meio da serialização de um objeto json que contém as configurações desejadas e do acréscimo dele aos valores de querystring, conforme mostrado na seção Iniciar o Seletor. Você também pode exibir o esquema completo. No mínimo, você deve fornecer as configurações de autenticação, entrada e mensagens.

Um exemplo de objeto de configurações mínimas é mostrado abaixo. Isso configura o sistema de mensagens no canal 27, permite que o seletor saiba que podemos fornecer tokens e que queremos que a guia "Meus Arquivos" represente os arquivos do OneDrive do usuário. Essa configuração usaria uma baseUrl do formato "https://{tenant}-my.sharepoint.com";

const channelId = uuid(); // Always use a unique id for the channel when hosting the picker.

const options = {
    sdk: "8.0",
    entry: {
        oneDrive: {}
    },
    // Applications must pass this empty `authentication` option in order to obtain details item data
    // from the picker, or when embedding the picker in an iframe.
    authentication: {},
    messaging: {
        origin: "http://localhost:3000",
        channelId: channelId
    },
}

O seletor foi projetado para funcionar em ambos OneDrive OU SharePoint em uma determinada instância e apenas uma das seções de entrada deve ser incluída.

Localização

A interface do Seletor de Arquivos oferece suporte à localização para o mesmo conjunto de idiomas do SharePoint.

Para definir o idioma do seletor de arquivos, use o parâmetro locale de string de consulta, definido como um dos valores LCID na lista acima.

Estabelecer Mensagens

Depois que a janela for criada e o formulário enviado, você precisará estabelecer um canal de mensagens. Isso é usado para receber os comandos do seletor e responder.

let port: MessagePort;

function initializeMessageListener(event: MessageEvent): void {
    // we validate the message is for us, win here is the same variable as above
    if (event.source && event.source === win) {

        const message = event.data;

        // the channelId is part of the configuration options, but we could have multiple pickers so that is supported via channels
        // On initial load and if it ever refreshes in its window, the Picker will send an 'initialize' message.
        // Communication with the picker should subsequently take place using a `MessageChannel`.
        if (message.type === "initialize" && message.channelId === options.messaging.channelId) {
            // grab the port from the event
            port = event.ports[0];

            // add an event listener to the port (example implementation is in the next section)
            port.addEventListener("message", channelMessageListener);

            // start ("open") the port
            port.start();

            // tell the picker to activate
            port.postMessage({
                type: "activate",
            });
        }
    }
};

// this adds a listener to the current (host) window, which the popup or embed will message when ready
window.addEventListener("message", messageEvent);

Implementação do Ouvinte de Mensagens

Sua solução deve lidar com várias mensagens do seletor, classificadas como notificações ou comandos. As notificações não esperam resposta e podem ser consideradas informações de log. A única exceção é a notificação page-loaded realçada abaixo, que informará que o seletor está pronto.

Os comandos exigem que você reconheça e, dependendo do comando, responda. Esta seção mostra um exemplo de implementação da função channelMessageListener adicionada como um ouvinte de eventos à porta. As próximas seções falam detalhadamente sobre notificações e comandos.

async function channelMessageListener(message: MessageEvent): Promise<void> {
    const payload = message.data;

    switch (payload.type) {

        case "notification":
            const notification = payload.data;

            if (notification.notification === "page-loaded") {
                // here we know that the picker page is loaded and ready for user interaction
            }

            console.log(message.data);
            break;

        case "command":

            // all commands must be acknowledged
            port.postMessage({
                type: "acknowledge",
                id: message.data.id,
            });

            // this is the actual command specific data from the message
            const command = payload.data;

            // command.command is the string name of the command
            switch (command.command) {

                case "authenticate":
                    // the first command to handle is authenticate. This command will be issued any time the picker requires a token
                    // 'getToken' represents a method that can take a command and return a valid auth token for the requested resource
                    try {
                        const token = await getToken(command);

                        if (!token) {
                            throw new Error("Unable to obtain a token.");
                        }

                        // we report a result for the authentication via the previously established port
                        port.postMessage({
                            type: "result",
                            id: message.data.id,
                            data: {
                                result: "token",
                                token: token,
                            }
                        });
                    } catch (error) {
                        port.postMessage({
                            type: "result",
                            id: message.data.id,
                            data: {
                                result: "error",
                                error: {
                                    code: "unableToObtainToken",
                                    message: error.message
                                }
                            }
                        });
                    }

                    break;

                case "close":

                    // in the base of popup this is triggered by a user request to close the window
                    await close(command);

                    break;

                case "pick":

                    try {
                        await pick(command);
    
                        // let the picker know that the pick command was handled (required)
                        port.postMessage({
                            type: "result",
                            id: message.data.id,
                            data: {
                                result: "success"
                            }
                        });
                    } catch (error) {
                        port.postMessage({
                            type: "result",
                            id: message.data.id,
                            data: {
                                result: "error",
                                error: {
                                    code: "unusableItem",
                                    message: error.message
                                }
                            }
                        });
                    }

                    break;

                default:
                    // Always send a reply, if if that reply is that the command is not supported.
                    port.postMessage({
                        type: "result",
                        id: message.data.id,
                        data: {
                            result: "error",
                            error: {
                                code: "unsupportedCommand",
                                message: command.command
                            }
                        }
                    });

                    break;
            }

            break;
    }
}

Obter Token

O controle exige que possamos fornecer tokens de autenticação com base no comando enviado. Para fazer isso, criamos um método que usa um comando e retorna um token, conforme mostrado abaixo. Estamos usando o @azure/msal-browser pacote para lidar com o trabalho de autenticação.

Atualmente, o controle depende de tokens do SharePoint e não do Graph, portanto, você precisará garantir que seu recurso esteja correto e você não possa reutilizar tokens para chamadas do Graph.

import { PublicClientApplication, Configuration, SilentRequest } from "@azure/msal-browser";
import { combine } from "@pnp/core";
import { IAuthenticateCommand } from "./types";

const app = new PublicClientApplication(msalParams);

async function getToken(command: IAuthenticateCommand): Promise<string> {
    let accessToken = "";
    const authParams = { scopes: [`${combine(command.resource, ".default")}`] };

    try {

        // see if we have already the idtoken saved
        const resp = await app.acquireTokenSilent(authParams!);
        accessToken = resp.accessToken;

    } catch (e) {

        // per examples we fall back to popup
        const resp = await app.loginPopup(authParams!);
        app.setActiveAccount(resp.account);

        if (resp.idToken) {

            const resp2 = await app.acquireTokenSilent(authParams!);
            accessToken = resp2.accessToken;

        } else {

            // throw the error that brought us here
            throw e;
        }
    }

    return accessToken;
}

Resultados do item escolhido

Quando um item for selecionado, o seletor retornará, por meio do canal de mensagens, uma matriz de itens selecionados. Embora haja um conjunto de informações que podem ser retornadas, sempre é garantido que o seguinte seja incluído:

{
    "id": string,
    "parentReference": {
        "driveId": string
    },
    "@sharePoint.endpoint": string
}

Usando isso, você pode construir uma URL para fazer uma solicitação GET para obter as informações necessárias sobre o arquivo selecionado. Geralmente, seria do formulário:

@sharePoint.endpoint + /drives/ + parentReference.driveId + /items/ + id

Você precisará incluir um token válido com os direitos apropriados para ler o arquivo na solicitação.

Carregar arquivos

Se você conceder Files.ReadWrite.All permissões ao aplicativo que está usando para tokens de seletor, um widget no menu superior aparecerá permitindo que você carregue arquivos e pastas na biblioteca de documentos do OneDrive ou do SharePoint. Nenhuma outra alteração de configuração é necessária, esse comportamento é controlado pelo aplicativo + permissões de usuário. Observe que, se o usuário não tiver acesso ao local para carregar, o seletor não mostrará a opção.