ファイル ピッカー

File Picker v8 を使用すると、ソリューション内の Microsoft 365 サービス内で使用されるのと同じ機能を使用できます。 つまり私たちが反復し、サービスを改善するのと同じような意味で、それらの新しい機能があなたのユーザーに表示されます!

この新しい "コントロール" は、メッセージの後過程を通じて操作する Microsoft サービス内でホストされるページです。 ページは、iframe で埋め込むか、ポップアップとしてホストできます。

Just Show Me The Sample Code

7.2 ピッカーのドキュメントについては、こちらを参照してください。

必要なセットアップ

サンプルを実行したり、ソリューションでコントロールを使用したりするには、AAD アプリケーションを作成する必要があります。 以下の手順を辿ることができます:

  1. 新しい AAD App Registeration を作成し、アプリケーションの ID をメモします
  2. 認証の下で、新しいシングルページ アプリケーション レジストリを作成します
    1. リダイレクト URI を https://localhost に設定します (これは、サンプルのテスト用です)
    2. Access トークンと ID トークンの両方がチェックされていることを確認します
    3. 必要に応じて、マルチテナント用にこのアプリケーションを構成することもできますが、これはこの記事の範囲外です
  3. API のアクセス許可の下
    1. Graph の委任されたアクセス許可について、Files.Read.AllSites.Read.Allを追加し、User.Read をそのままにする
    2. SharePoint の委任されたアクセス許可について、AllSites.ReadMyFiles.Readを追加する

SharePoint Framework で開発している場合は、リソース "SharePoint" と "Microsoft Graph" を使用して、アプリケーション マニフェストでこれらのアクセス許可を要求できます。

ユーザーがピッカー エクスペリエンス内でファイルのアップロードやフォルダーの作成を行えるようにするには、Files.ReadWrite.AllSites.ReadWrite.AllAllSites.WriteMyFiles.Write へのアクセス許可を要求できます。

アクセス許可

ファイル ピッカーは常に委任されたアクセス許可を使用して動作するため、現在のユーザーが既にアクセスできるファイルとフォルダーにのみアクセスできます。

少なくとも、ユーザーの OneDrive サイトと SharePoint サイトからファイルを読み取るために、SharePoint MyFiles.Read アクセス許可を要求する必要があります。

次の表を参照して、実行する操作に基づいて必要なアクセス許可を理解してください。 この表のすべてのアクセス許可は、委任されたアクセス許可を参照します。

読み取り 書き込み
OneDrive SharePoint.MyFiles.Read
または
Graph.Files.Read
SharePoint.MyFiles.Write
or
Graph.Files.ReadWrite
SharePoint サイト SharePoint.MyFiles.Read
または
Graph.Files.Read
または
SharePoint.AllSites.Read
SharePoint.MyFiles.Write
or
Graph.Files.ReadWrite
または
SharePoint.AllSites.Write
Teams チャネル Graph.ChannelSettings.Read.All と SharePoint.AllSites.Read Graph.ChannelSettings.Read.All と SharePoint.AllSites.Write

メカニズム

コントロールを使用するには、次が必要です:

  1. /_layouts/15/FilePicker.aspx でホストされている "コントロール" ページに POST 要求を行います。 この要求を使用して、いくつかのパラメーターを指定し、その 1 つのキーは ピッカー構成です。
  2. postMessage ポートとメッセージ ポートを使用して、ホスト アプリケーションとコントロールの間でメッセージングを設定します。
  3. 通信チャネルが確立されたら、さまざまな "コマンド" に応答する必要があり、まず始めに認証トークンを提供する必要があります。
  4. 最後に、新規/別個の認証トークンを提供したり、選択したファイルを処理したり、ポップアップを閉じたりするには、追加のコマンド メッセージに応答する必要があります。

以降のセクションでは、各手順について説明します。

また、コントロールと統合する異なる方法を示す 多様なサンプル もあります。

Picker を初期化する

ピッカーを初期化するには、iframe またはポップアップのいずれかを含む "ウィンドウ" を作成する必要があります。 ウィンドウを保有したら、フォームを作成し、クエリ文字列パラメーターが定義された URL {baseUrl}/_layouts/15/FilePicker.aspx にフォームを POST する必要があります。

上記の値 {baseUrl} は、ターゲット Web の SharePoint Web URL か、ユーザーの 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.ReadWrite トークンをOneDrive.Read要求するときに を使用します。 アプリケーションのアクセス許可を要求すると、または Files.ReadWrite (または別の Files.X スコープ) をFiles.Read選択します。

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

Picker Configuration

ピッカーは、目的の設定を含む JSON オブジェクトをシリアル化し、[Picker の初期化] セクションで示すようにクエリ文字列値に追記することで構成されます。 全スキーマを表示することもできます。 少なくとも、認証、エントリ、およびメッセージングの設定を指定する必要があります。

最小限の設定オブジェクトの例を次に示します。 チャネル 27 についてのメッセージングを設定し、ピッカーはトークンを指定できること、およびユーザーの OneDrive ファイルを表す [マイ ファイル] タブを希望します。 この構成では、"https://{tenant}-my.sharepoint.com" という形式の baseUrl を使用します;

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 の両方を操作できるように設計されており、エントリ セクションの 1 つだけを含める必要があります。

ローカリゼーション

ファイル ピッカーのインターフェイスは、SharePoint と同じ言語セットでのローカライズをサポートしています。

ファイル ピッカーの言語を設定するには、クエリ文字列パラメーター locale を使用して、上記の一覧のいずれかの LCID 値に設定します。

Establish Messaging

ウィンドウが作成され、フォームが送信されたら、メッセージング チャネルを確立する必要があります。 これは、ピッカーからコマンドを受信して応答するために使用されます。

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

Message Listener Implementation

ソリューションでは、通知またはコマンドとして分類されたピッカーからのさまざまなメッセージを処理する必要があります。 通知は応答を期待されず、ログ情報と見なすことができます。 1 つの例外は、以下で 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 使用して認証作業を処理しています。

現在、コントロールは Graph ではなく SharePoint トークンに依存しているため、リソースが正しく、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
}

これを使用して、選択したファイルに関する必要な情報を取得するための GET 要求を行う URL を作成できます。 一般に、次の形式になります。

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

要求でファイルを読み取る適切な権限を持つ有効なトークンを含める必要があります。

ファイルのアップロード

ピッカー トークンに使用しているアプリケーションにアクセス許可を付与 Files.ReadWrite.All すると、上部のメニューにウィジェットが表示され、ファイルとフォルダーを OneDrive または SharePoint ドキュメント ライブラリにアップロードできます。 他の構成の変更は必要ありません。この動作は、アプリケーションとユーザーのアクセス許可によって制御されます。 ユーザーがアップロードする場所にアクセスできない場合、ピッカーにはオプションが表示されないことに注意してください。