Средство выбора файлов

Средство выбора файлов v8 позволяет использовать те же функции, что и служба M365, в решениях. То есть по мере итерации и улучшения службы эти новые возможности появляются у ваших пользователей!

Этот новый «элемент управления» представляет собой страницу, размещенную в службе Майкрософт, с которой вы взаимодействуете с помощью сообщений записи. Страница может быть размещена либо встроенной в iframe, либо в виде всплывающего окна.

Просто показать пример кода

Документацию по средству выбора 7.2 можно найти здесь.

Обязательная настройка

Чтобы запустить примеры или использовать элемент управления в решении, необходимо создать приложение AAD. Вы можете выполнить следующие действия:

  1. Создайте регистрацию приложения AAD, запишите идентификатор приложения.
  2. В разделе проверки подлинности создайте одностраничный реестр приложения.
    1. Установите для URI перенаправления https://localhost (это для тестирования образцов)
    2. Убедитесь, что отмечены как маркеры доступа, так и маркеры идентификатора.
    3. При желании можно настроить это приложение для работы с несколькими клиентами, но это выходит за рамки этой статьи.
  3. В разделе разрешений API
    1. Добавьте Files.Read.All, Sites.Read.All, оставьте User.Read для делегированных разрешений Graph
    2. Добавьте AllSites.Read, MyFiles.Read для делегированных разрешений SharePoint

Если вы разрабатываете в SharePoint Framework, вы можете запросить эти разрешения в манифесте приложения с помощью ресурса «SharePoint» и «Microsoft Graph».

Чтобы разрешить пользователю отправлять файлы и создавать папки в интерфейсе средства выбора, можно запросить доступ к Files.ReadWrite.All, Sites.ReadWrite.All, AllSites.Write и MyFiles.Write.

Разрешения

Средство выбора файлов всегда работает с делегированными разрешениями и, таким образом, может получать доступ только к файлам и папкам, к которым у текущего пользователя уже есть доступ.

Как минимум необходимо запросить разрешение SharePoint MyFiles.Read на чтение файлов с сайтов OneDrive и SharePoint пользователя.

Ознакомьтесь с приведенной ниже таблицей, чтобы понять, какие разрешения требуются в зависимости от операций, которые вы хотите выполнить. Все разрешения в этой таблице относятся к делегированным разрешениям.

Чтение Запись
OneDrive SharePoint.MyFiles.Read
или
Graph.Files.Read
SharePoint.MyFiles.Write
или
Graph.Files.ReadWrite
Сайты SharePoint SharePoint.MyFiles.Read
или
Graph.Files.Read
или
SharePoint.AllSites.Read
SharePoint.MyFiles.Write
или
Graph.Files.ReadWrite
или
SharePoint.AllSites.Write
Команды и каналы Graph.ChannelSettings.Read.All и SharePoint.AllSites.Read Graph.ChannelSettings.Read.All и SharePoint.AllSites.Write

Принципы действия

Чтобы использовать элемент управления, выполните следующие действия:

  1. Отправьте запрос POST на «управляющую» страницу, размещенную в /_layouts/15/FilePicker.aspx. При использовании этого запроса вы указываете некоторые параметры, ключевым из которых является конфигурация средства выбора.
  2. Настройте обмен сообщениями между хост-приложением и элементом управления, используя порты postMessage и message.
  3. После установки канала связи необходимо ответить на различные «команды», первая из которых — предоставить маркеры проверки подлинности.
  4. Наконец, вам потребуется ответить на дополнительные командные сообщения, чтобы предоставить новые или другие маркеры проверки подлинности, обработать выбранные файлы или закрыть всплывающее окно.

В следующих разделах объясняется каждый шаг.

Кроме того, у нас есть множество примеров, демонстрирующих различные способы интеграции с элементом управления.

Запуск средства выбора

Чтобы запустить средство выбора, вам нужно создать «окно», которое может быть либо iframe, либо всплывающим окном. После создания окна необходимо создать форму и отправить ее по URL-адресу {baseUrl}/_layouts/15/FilePicker.aspx с определенными параметрами строки запроса.

Приведенное выше значение {baseUrl} — это либо URL-адрес SharePoint целевого веб-сайта, либо OneDrive пользователя. Вот некоторые примеры: "https://tenant.sharepoint.com/sites/dev" или "https://tenant-my.sharepoint.com".

Конфигурация потребителя OneDrive

name Описания
authority https://login.microsoftonline.com/consumers
Область OneDrive.ReadWrite или OneDrive.Read
baseUrl https://onedrive.live.com/picker

При запросе маркера вы будете OneDrive.Read использовать или OneDrive.ReadWrite при запросе маркера. При запросе разрешений для приложения вы выбираете или Files.ReadFiles.ReadWrite (или другой область 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();

Конфигурация средства выбора

Средство выбора настраивается путем сериализации объекта JSON, содержащего нужные параметры, и добавления его к значениям строки запроса, как показано в разделе Запуск средства выбора. Вы также можете просмотреть полную схему. Как минимум необходимо указать параметры проверки подлинности, ввода и обмена сообщениями.

Ниже приведен пример объекта с минимальными настройками. Это действие настраивает обмен сообщениями на канале 27, сообщает средству выбора, что мы можем предоставить маркеры, и что мы хотим, чтобы вкладка «Мои файлы» представляла файлы OneDrive пользователя. В этой конфигурации будет использоваться baseUrl в формате «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
    },
}

Средство выбора предназначено для работы либо с OneDrive, либо с SharePoint в данном экземпляре, и должен быть включен только один из разделов ввода.

Локализация

Интерфейс средства выбора файлов поддерживает локализацию для того же набора языков, что и SharePoint.

Чтобы задать язык для средства выбора файлов, используйте параметр строки запроса locale, для которого задано одно из значений кода языка из приведенного выше списка.

Установка обмена сообщениями

После создания окна и отправки формы необходимо установить канал обмена сообщениями. Используется для получения команд от средства выбора и ответа.

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

Реализация прослушивателя сообщений

Решение должно обрабатывать различные сообщения средства выбора, классифицируемые как уведомления или команды. Уведомления не ожидают ответа и могут считаться сведениями журнала. Единственным исключением является уведомление page-loaded, выделенное ниже, которое сообщает, что средство выбора готово.

Команды требуют подтверждения и, в зависимости от команды, ответа. В этом разделе показан пример реализации функции channelMessageListener, добавленной в порт в качестве прослушивателя событий. В следующих разделах подробно рассказывается об уведомлениях и командах.

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

Получение маркера

Элемент управления требует, чтобы мы могли предоставить ему маркеры проверки подлинности на основе отправленной команды. Для этого мы создадим метод, который принимает команду и возвращает маркер, как показано ниже. Мы используем пакет для @azure/msal-browser обработки проверки подлинности.

В настоящее время элемент управления использует маркеры SharePoint, а не Graph, поэтому необходимо убедиться, что ресурс работает правильно, и вы не сможете повторно использовать маркеры для вызовов 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;
}

Результаты выбора элемента

При выборе элемента средство выбора вернет через канал обмена сообщениями массив выбранных элементов. Несмотря на то, что существует набор сведений, которые могут быть возвращены, всегда гарантируется включение следующего:

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

С его помощью можно создать URL-адрес для выполнения запроса GET, чтобы получить все необходимые сведения о выбранном файле. Как правило, он будет иметь следующий вид:

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

Необходимо включить допустимый маркер с соответствующими правами для чтения файла в запросе.

Отправка файлов

Если вы предоставите Files.ReadWrite.All разрешения приложению, которое используете для маркеров средства выбора, в верхнем меню появится мини-приложение, позволяющее отправлять файлы и папки в библиотеку документов OneDrive или SharePoint. Другие изменения конфигурации не требуются. Это поведение контролируется разрешениями приложения и пользователя. Обратите внимание, что если у пользователя нет доступа к расположению для отправки, средство выбора не будет отображать параметр .