Sélecteur de fichiers

Le sélecteur de fichiers v8 vous permet d'utiliser la même fonctionnalité que celle utilisée par le service M365 dans vos solutions. Cela signifie qu'au fur et à mesure que nous itérons et améliorons le service, ces nouvelles capacités apparaissent pour vos utilisateurs !

Ce nouveau « contrôle » est une page hébergée dans le service Microsoft avec laquelle vous interagissez par le biais de messages postaux. La page peut être hébergée soit dans une iframe, soit dans un popup.

Montrez-moi juste l'exemple de code

Vous pouvez trouver la documentation pour le sélecteur 7.2 ici.

Configuration requise

Pour exécuter les échantillons ou utiliser le contrôle dans votre solution, vous devrez créer une application AAD. Vous pouvez suivre les étapes suivantes :

  1. Créez un nouvel enregistrement d'application AAD, notez l'ID de l'application.
  2. Sous authentification, créez un nouveau registre d'application monopage
    1. Définir l'uri de redirection https://localhost (ceci est pour tester les échantillons)
    2. Assurez-vous que les jetons d'accès et les jetons d'identification sont vérifiés.
    3. Vous pouvez éventuellement configurer cette application pour le multitenant, mais cela sort du cadre de cet article.
  3. Sous Autorisations API
    1. AjouterFiles.Read.All , Sites.Read.All, Laisser User.Readpour les permissions déléguées du graphique
    2. AjouterAllSites.Read ,MyFiles.Read pour les permissions déléguées de SharePoint

Si vous développez dans SharePoint Framework, vous pouvez demander ces autorisations dans le manifeste de l'application avec la ressource « SharePoint » et « Microsoft Graph ».

Pour permettre à l’utilisateur de charger des fichiers et de créer des dossiers dans l’expérience du sélecteur, vous pouvez demander l’accès à Files.ReadWrite.All, Sites.ReadWrite.All, AllSites.Write et MyFiles.Write.

Autorisations

Le sélecteur de fichiers fonctionne toujours à l’aide d’autorisations déléguées et, par conséquent, peut uniquement accéder aux fichiers et dossiers auxquels l’utilisateur actuel a déjà accès.

Au minimum, vous devez demander l’autorisation SharePoint MyFiles.Read pour lire des fichiers à partir des sites OneDrive et SharePoint d’un utilisateur.

Consultez le tableau ci-dessous pour comprendre quelle autorisation est requise en fonction des opérations que vous souhaitez effectuer. Toutes les autorisations de ce tableau font référence aux autorisations déléguées.

Lecture Write
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
Canaux Teams Graph.ChannelSettings.Read.All et SharePoint.AllSites.Read Graph.ChannelSettings.Read.All et SharePoint.AllSites.Write

Mode de fonctionnement

Pour utiliser le contrôle, vous devez :

  1. Faites une demande POST à la page « control » hébergée à /_layouts/15/FilePicker.aspx. Cette demande permet de fournir certains paramètres, le principal étant la configuration du sélecteur.
  2. Configurez la messagerie entre votre application hôte et le contrôle en utilisant postMessage et les ports de message.
  3. Une fois le canal de communication établi, vous devez répondre à diverses « commandes », dont la première consiste à fournir des jetons d'authentification.
  4. Enfin, vous devrez répondre à des messages de commande supplémentaires pour fournir des jetons d'authentification nouveaux ou différents, traiter les fichiers prélevés ou fermer la fenêtre contextuelle.

Les sections suivantes expliquent chaque étape.

Nous disposons également d'une série d'échantillons montrant différentes manières d'intégrer le contrôle.

Lancer le sélecteur

Pour lancer le sélecteur, vous devez créer une « fenêtre » qui peut être une iframe ou une popup. Une fois que vous avez une fenêtre, vous devez construire un formulaire et POST le formulaire à l'URL{baseUrl}/_layouts/15/FilePicker.aspx avec les paramètres de la chaîne de requête définis.

La {baseUrl}valeur ci-dessus est soit l 'url SharePoint du site web cible, soit l' onedrive de l'utilisateur. Voici quelques exemples : «https://tenant.sharepoint.com/sites/dev" ; ou « https://tenant-my.sharepoint.com".

Configuration du consommateur OneDrive

nom Descriptions
Autorité https://login.microsoftonline.com/consumers
Portée OneDrive.ReadWrite ou OneDrive.Read
baseUrl https://onedrive.live.com/picker

Lorsque vous demandez un jeton, vous utilisez ou OneDrive.ReadOneDrive.ReadWrite lorsque vous demandez le jeton. Lorsque vous demandez les autorisations pour votre application, vous sélectionnez pour Files.Read ou Files.ReadWrite (ou une autre étendue 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();

Configuration du sélecteur

Le sélecteur est configuré en sérialisant un objet json contenant les paramètres souhaités, et en l'ajoutant aux valeurs de la chaîne de requête comme indiqué dans la section Initier le sélecteur. Vous pouvez également afficher le schéma complet. Vous devez au moins fournir les paramètres d'authentification, d'entrée et de messagerie.

Un exemple d'objet de paramètres minimaux est présenté ci-dessous. Cela configure la messagerie sur le canal 27, fait savoir au sélecteur que nous pouvons fournir des jetons, et que nous voulons que l'onglet « Mes fichiers » représente les fichiers OneDrive de l’utilisateur. Cette configuration utiliserait un baseUrl de la forme « 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
    },
}

Le sélecteur est conçu pour fonctionner avec OneDrive OU SharePoint dans une instance donnée et une seule des sections de saisie doit être incluse.

Localisation

L’interface du sélecteur de fichiers prend en charge la localisation pour le même ensemble de langues que SharePoint.

Pour définir la langue du sélecteur de fichiers, utilisez le paramètre de chaîne de requête locale, défini sur l’une des valeurs LCID de la liste ci-dessus.

Établir un message

Une fois la fenêtre créée et le formulaire soumis, vous devrez établir un canal de messagerie. Il est utilisé pour recevoir les commandes du sélecteur et y répondre.

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);

Mise en œuvre du récepteur de messages

Votre solution doit traiter divers messages provenant du sélecteur, classés comme des notifications ou des commandes. Les notifications n'attendent aucune réponse et peuvent être considérées comme des informations de journal. La seule exception est la page-loadednotification mise en évidence ci-dessous, qui vous indique que le sélecteur est prêt.

Les commandes exigent que vous accusiez réception et, selon la commande, que vous répondiez. Cette section montre un exemple d'implémentation de la channelMessageListenerfonction ajoutée comme un écouteur d'événement au port. Les sections suivantes traitent en détail des notifications et des commandes.

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;
    }
}

Obtenir un jeton

Le contrôle nécessite que nous puissions lui fournir des jetons d’authentification basés sur la commande envoyée. Pour ce faire, nous créons une méthode qui prend une commande et retourne un jeton comme indiqué ci-dessous. Nous utilisons le @azure/msal-browser package pour gérer le travail d’authentification.

Actuellement, le contrôle s’appuie sur des jetons SharePoint et non sur Graph. Vous devez donc vous assurer que votre ressource est correcte et que vous ne pouvez pas réutiliser les jetons pour les appels 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;
}

Résultats de l’élément sélectionné

Lorsqu’un élément est sélectionné, le sélecteur retourne, via le canal de messagerie, un tableau d’éléments sélectionnés. Bien qu’il existe un ensemble d’informations pouvant être retournées, les éléments suivants sont toujours inclus :

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

À l’aide de cela, vous pouvez construire une URL pour effectuer une requête GET afin d’obtenir toutes les informations dont vous avez besoin sur le fichier sélectionné. Elle se présente généralement sous la forme suivante :

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

Vous devez inclure un jeton valide avec les droits appropriés pour lire le fichier dans la demande.

Chargement de fichiers

Si vous accordez Files.ReadWrite.All des autorisations à l’application que vous utilisez pour les jetons de sélecteur, un widget dans le menu supérieur s’affiche, ce qui vous permet de charger des fichiers et des dossiers dans la bibliothèque de documents OneDrive ou SharePoint. Aucune autre modification de configuration n’est requise, ce comportement est contrôlé par les autorisations d’application + utilisateur. Notez que si l’utilisateur n’a pas accès à l’emplacement de chargement, le sélecteur n’affiche pas l’option .