Selector de archivos

El Selector de archivos v8 permite usar la misma funcionalidad que se usa en el servicio M365 dentro de las soluciones. Es decir, a medida que iteramos y mejoramos el servicio, esas nuevas funcionalidades aparecen para los usuarios.

Este nuevo "control" es una página hospedada en el servicio de Microsoft con la que usted interactúa a través de mensajes de publicación. La página se puede hospedar incrustada en un iframe o como un elemento emergente.

Enséñame el código de ejemplo

Puede encontrar la documentación del selector 7.2 aquí.

Configuración necesaria

Para ejecutar los ejemplos o usar el control en la solución, deberá crear una aplicación de AAD. Luego, haga lo siguiente:

  1. Cree un nuevo registro de aplicación de AAD y anote el identificador de la aplicación.
  2. En autenticación, cree un nuevo registro de aplicación de página única
    1. Establezca el identificador URI de redireccionamiento en https://localhost (esto es para probar los ejemplos)
    2. Asegúrese de comprobar los tokens de acceso y los tokens de identificador.
    3. Opcionalmente, puede configurar esta aplicación para varios inquilinos, pero esto no se tratará en el presente artículo
  3. Agregar permisos de API
    1. Agregue Files.Read.All, Sites.Read.All, deje User.Read para los permisos delegados de Graph
    2. Agregue AllSites.Read, MyFiles.Read para permisos delegados de SharePoint

Si está desarrollando en SharePoint Framework, puede solicitar estos permisos en el manifiesto de aplicación con el recurso "SharePoint" y "Microsoft Graph".

Para permitir que el usuario cargue archivos y cree carpetas en la experiencia del selector, puede solicitar acceso a Files.ReadWrite.All, Sites.ReadWrite.All, AllSites.Write y MyFiles.Write.

Permisos

El selector de archivos siempre funciona con permisos delegados y, como tal, solo puede acceder a archivos y carpetas a los que el usuario actual ya tiene acceso.

Como mínimo, debe solicitar el permiso MyFiles.Read de SharePoint para leer archivos de los sitios de OneDrive y SharePoint de un usuario.

Revise la tabla siguiente para comprender qué permisos son necesarios en función de las operaciones que desea realizar. Todos los permisos de esta tabla hacen referencia a los permisos delegados.

Leer Escritura
OneDrive SharePoint.MyFiles.Read
Otra posibilidad:
Graph.Files.Read
SharePoint.MyFiles.Write
Otra posibilidad:
Graph.Files.ReadWrite
Sitios de SharePoint SharePoint.MyFiles.Read
Otra posibilidad:
Graph.Files.Read
Otra posibilidad:
SharePoint.AllSites.Read
SharePoint.MyFiles.Write
Otra posibilidad:
Graph.Files.ReadWrite
Otra posibilidad:
SharePoint.AllSites.Write
Canales de Teams Graph.ChannelSettings.Read.All y SharePoint.AllSites.Read Graph.ChannelSettings.Read.All y SharePoint.AllSites.Write

Cómo funciona

Para usar el control, debe:

  1. Realice una solicitud POST en la página de "control" hospedada en /_layouts/15/FilePicker.aspx. Con esta solicitud se proporcionan algunos parámetros, el principal es la configuración del selector.
  2. Configure los mensajes entre la aplicación host y el control mediante postMessage y los puertos de mensaje.
  3. Una vez establecido el canal de comunicación, debe responder a varios "comandos", el primero de ellos consiste en proporcionar tokens de autenticación.
  4. Por último, tendrá que responder a mensajes de comando adicionales para proporcionar tokens de autenticación nuevos o diferentes, controlar los archivos elegidos o cerrar el elemento emergente.

En las próximas secciones se explica cada paso.

También tenemos una variedad de ejemplos que muestran diferentes formas de integrarse con el control.

Iniciar el selector

Para iniciar el selector, debe crear una "ventana". que puede ser un iframe o un elemento emergente. Una vez que tenga una ventana, debe construir un formulario y publicar el formulario en la dirección URL {baseUrl}/_layouts/15/FilePicker.aspx con los parámetros de cadena de consulta definidos.

El valor {baseUrl} anterior es la dirección URL web de SharePoint de la web de destino o el OneDrive del usuario. Algunos ejemplos son: "https://tenant.sharepoint.com/sites/dev" o "https://tenant-my.sharepoint.com".

Configuración del consumidor de OneDrive

name Descripciones
Autoridad https://login.microsoftonline.com/consumers
Ámbito OneDrive.ReadWrite o OneDrive.Read
baseUrl https://onedrive.live.com/picker

Cuando solicite un token, usará OneDrive.Read o OneDrive.ReadWrite cuando solicite el token. Cuando solicite los permisos para la aplicación, seleccionará para Files.Read o Files.ReadWrite (u otro ámbito 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();

Configuración del selector

El selector se configura serializando un objeto JSON que contenga la configuración deseada y anexándole a los valores de cadena de consulta como se muestra en la sección Iniciar el selector. También puede ver el esquema completo. Como mínimo, debe proporcionar la configuración de autenticación, entrada y mensajería.

A continuación se muestra un ejemplo de objeto de configuración mínima. Con esto se configura la mensajería en el canal 27, se permite que el selector sepa que podemos proporcionar tokens y que queremos la pestaña " Mis archivos" para representar los archivos de OneDrive del usuario. Esta configuración usaría una baseUrl del formulario "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
    },
}

El selector está diseñado para funcionar tanto con OneDrive como con SharePoint en una instancia determinada y solo se debe incluir una de las secciones de entrada.

Localización

La interfaz del selector de archivos admite la localización del mismo conjunto de idiomas que SharePoint.

Para establecer el idioma del selector de archivos, use el parámetro de cadena de consulta locale establecido en uno de los valores LCID de la lista anterior.

Establecer mensajería

Una vez creada la ventana y el formulario enviado, deberá establecer un canal de mensajería. Se usa para recibir los comandos del selector y 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);

Implementación del agente de escucha de mensajes

La solución debe controlar varios mensajes del selector, clasificados como notificaciones o comandos. Las notificaciones no esperan respuesta y se pueden considerar información de registro. La única excepción es la notificación page-loaded resaltada a continuación, que le indicará que el selector está listo.

Los comandos requieren su confirmación y, en función del comando, una respuesta. En esta sección se muestra una implementación de ejemplo de la función channelMessageListener agregada como agente de escucha de eventos al puerto. En las secciones siguientes se habla en detalle sobre las notificaciones y los 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;
    }
}

Obtener token

El control requiere que podamos proporcionarle tokens de autenticación en función del comando enviado. Para ello, creamos un método que toma un comando y devuelve un token como se muestra a continuación. Estamos usando el @azure/msal-browser paquete para controlar el trabajo de autenticación.

Actualmente, el control se basa en tokens de SharePoint y no en Graph, por lo que deberá asegurarse de que el recurso es correcto y no puede reutilizar tokens para llamadas de 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 de elementos seleccionados

Cuando se selecciona un elemento, el selector devolverá, a través del canal de mensajería, una matriz de elementos seleccionados. Aunque hay un conjunto de información que se puede devolver, siempre se garantiza que se incluye lo siguiente:

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

Con esto puede construir una dirección URL para realizar una solicitud GET para obtener cualquier información que necesite sobre el archivo seleccionado. Por lo general, tendría el siguiente formato:

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

Tendrá que incluir un token válido con los derechos adecuados para leer el archivo en la solicitud.

Carga de archivos

Si concede Files.ReadWrite.All permisos a la aplicación que usa para los tokens de selector, aparecerá un widget en el menú superior que le permitirá cargar archivos y carpetas en la biblioteca de documentos de OneDrive o SharePoint. No se requieren otros cambios de configuración, este comportamiento se controla mediante los permisos de aplicación y usuario. Tenga en cuenta que si el usuario no tiene acceso a la ubicación para cargar, el selector no mostrará la opción.