方法: Fluid Framework で視聴者機能を使用する

このチュートリアルでは、Fluid Framework の対象ユーザーReact とともに使用して、コンテナーに接続するユーザーの視覚的なデモを作成する方法について説明します。 対象ユーザー オブジェクトには、コンテナーに接続されているすべてのユーザーに関連する情報が保持されます。 この例では、Azure クライアント ライブラリを使用して、コンテナーと対象ユーザーを作成します。

次の画像は、ID ボタンとコンテナー ID 入力フィールドを示しています。 コンテナー ID フィールドを空白のままにし、ユーザー ID ボタンをクリックすると、新しいコンテナーが作成され、選択したユーザーとして参加します。 または、エンドユーザーがコンテナー ID を入力し、ユーザー ID を選択して、選択したユーザーとして既存のコンテナーに参加することもできます。

A screenshot of a browser with buttons for selecting a user.

次の画像は、コンテナーに接続されている複数のユーザーをボックスごとに表しています。 青で囲んだボックスは、クライアントを表示しているユーザーを表し、黒で囲んだボックスは接続されている他のユーザーを表します。 新しいユーザーが一意の ID でコンテナーにアタッチすると、ボックスの数が増えます。

A screenshot of a browser showing information for four different container users.

Note

このチュートリアルは、Fluid Framework の概要に精通していること、およびクイックスタートを完了していることを前提としています。 また、ReactReact プロジェクトの作成React のフックの基本についても理解している必要があります。

プロジェクトを作成する

  1. コマンド プロンプトを開き、プロジェクトを作成する親フォルダー (例: C:\My Fluid Projects) に移動します。

  2. プロンプトで次のコマンドを実行します。 (CLI が npm ではなく npx であることに注目してください。これは、Node.js をインストールしたときにインストールされています。)

    npx create-react-app fluid-audience-tutorial
    
  3. プロジェクトは、fluid-audience-tutorial という名前のサブフォルダーに作成されます。 コマンド cd fluid-audience-tutorial を使用して、それに移動します。

  4. プロジェクトでは、次の Fluid ライブラリが使用されます。

    ライブラリ 説明
    fluid-framework クライアント間でデータを同期する SharedMap 分散データ構造 が含まれます。
    @fluidframework/azure-client Fluid サービス サーバーへの接続を定義し、Fluid コンテナーの開始スキーマが定義されます。
    @fluidframework/test-client-utils Fluid サービスへの接続を作成するために必要な InsecureTokenProvider が定義されます。

    次のコマンドを実行して、ライブラリをインストールします。

    npm install @fluidframework/azure-client @fluidframework/test-client-utils fluid-framework
    

プロジェクトをコーディングする

状態ライブラリとコンポーネント ビューを設定する

  1. コード エディターで、ファイル \src\App.js を開きます。 すべての既定の import ステートメントを削除します。 次に、return ステートメントからすべてのマークアップを削除します。 次に、コンポーネントと React のフックの import ステートメントを追加します。 後の手順で、インポートした AudienceDisplay コンポーネントと UserIdSelection コンポーネントを実装します。 ファイルは次のようになります。

        import { useState, useCallback } from "react";
        import { AudienceDisplay } from "./AudienceDisplay";
        import { UserIdSelection } from "./UserIdSelection";
    
        export const App = () => {
        // TODO 1: Define state variables to handle view changes and user input
        return (
        // TODO 2: Return view components
        );
        }
    
  2. TODO 1 を次のコードに置き換えます。 このコードにより、アプリケーション内で使用されるローカル状態変数が初期化されます。 displayAudience の値は、AudienceDisplay コンポーネントと UserIdSelection コンポーネントのどちらをレンダリングするかを決定します (TODO 2 を参照)。 userId の値はコンテナーに接続するときに使用するユーザー識別子であり、containerId の値は読み込むコンテナーです。 handleSelectUserhandleContainerNotFound の関数は、2 つのビューへのコールバックとして渡され、状態遷移を管理します。 handleSelectUser は、コンテナーの作成または読み込みを試みたときに呼び出されます。 handleContainerNotFound は、コンテナーの作成または読み込みに失敗したときに呼び出されます。

    userId と containerId の値は、handleSelectUser 関数を介して UserIdSelection コンポーネントから取得されます。

        const [displayAudience, setDisplayAudience] = useState(false);
        const [userId, setUserId] = useState();
        const [containerId, setContainerId] = useState();
    
        const handleSelectUser = useCallback((userId, containerId) => {
        setDisplayAudience(true)
        setUserId(userId);
        setContainerId(containerId);
        }, [displayAudience, userId, containerId]);
    
        const handleContainerNotFound = useCallback(() => {
        setDisplayAudience(false)
        }, [setDisplayAudience]);
    
  3. TODO 2 を次のコードに置き換えます。 前述のように、displayAudience 変数は AudienceDisplay コンポーネントと UserIdSelection コンポーネントのどちらをレンダリングするかを決定します。 状態変数を更新する関数も、プロパティとしてコンポーネントに渡されます。

        (displayAudience) ?
        <AudienceDisplay userId={userId} containerId={containerId} onContainerNotFound={handleContainerNotFound}/> :
        <UserIdSelection onSelectUser={handleSelectUser}/>
    

AudienceDisplay コンポーネントを設定する

  1. コード エディターでファイル \src\AudienceDisplay.js を作成して開きます。 次の import ステートメントを追加します。

        import { useEffect, useState } from "react";
        import { SharedMap } from "fluid-framework";
        import { AzureClient } from "@fluidframework/azure-client";
        import { InsecureTokenProvider } from "@fluidframework/test-client-utils";
    

    ユーザーとコンテナーを定義するには、Fluid Framework ライブラリからインポートされたオブジェクトが必要です。 以後のステップでは、AzureClientInsecureTokenProvider を使用してクライアント サービスが構成され (TODO 1 を参照)、SharedMap を使用してコンテナーを作成するために必要な containerSchema が構成されます (TODO 2 を参照)。

  2. 次の関数コンポーネントとヘルパー関数を追加します。

        const tryGetAudienceObject = async (userId, userName, containerId) => {
        // TODO 1: Create container and return audience object
        }
    
        export const AudienceDisplay = (props) => {
        //TODO 2: Configure user ID, user name, and state variables
        //TODO 3: Set state variables and set event listener on component mount
        //TODO 4: Return list view
        }
    
        const AudienceList = (data) => {
        //TODO 5: Append view elements to list array for each member
        //TODO 6: Return list of member elements
        }
    

    AudienceDisplayAudienceList は、対象ユーザー データの取得とレンダリングを処理する機能コンポーネントであり、tryGetAudienceObject メソッドは、コンテナーと対象ユーザーのサービスの作成を処理しています。

コンテナーと対象ユーザーの取得

ヘルパー関数を使用すると、対象ユーザー オブジェクトからビュー レイヤー (React state) に Fluid のデータを取得できます。 tryGetAudienceObject メソッドは、ユーザー ID が選択された後、ビュー コンポーネントが読み込まれるときに呼び出されます。 戻り値は、React state プロパティに割り当てられます。

  1. TODO 1 を次のコードに置き換えます。 userIduserNamecontainerId の値は App コンポーネントから渡されます。 存在しない containerId場合は、新しいコンテナーが作成されます。 containerId は URL ハッシュにも格納されます。 新しいブラウザーからセッションを入力するユーザーは、既存のセッション ブラウザーから URL をコピーするか、localhost:3000 に移動して、コンテナー ID を手動で入力できます。 この実装では、ユーザーが getContainer 存在しないコンテナー ID を入力した場合に、try catch で呼び出しをラップします。 詳細については、コンテナードキュメントを参照してください。

        const userConfig = {
            id: userId,
            name: userName,
            additionalDetails: {
                email: userName.replace(/\s/g, "") + "@example.com",
                date: new Date().toLocaleDateString("en-US"),
            },
        };
    
        const serviceConfig = {
            connection: {
                type: "local",
                tokenProvider: new InsecureTokenProvider("", userConfig),
                endpoint: "http://localhost:7070",
            },
        };
    
        const client = new AzureClient(serviceConfig);
    
        const containerSchema = {
            initialObjects: { myMap: SharedMap },
        };
    
        let container;
        let services;
        if (!containerId) {
            ({ container, services } = await client.createContainer(containerSchema));
            const id = await container.attach();
            location.hash = id;
        } else {
            try {
                ({ container, services } = await client.getContainer(containerId, containerSchema));
            } catch (e) {
                return;
            }
        }
        return services.audience;
    

コンポーネント マウントでの対象ユーザーの取得

Fluid の対象ユーザーの取得方法を定義したので、Audience Display コンポーネントがマウントされたときに tryGetAudienceObject を呼び出すように React に指示する必要があります。

  1. TODO 2 を次のコードに置き換えます。 ユーザー ID は、user1user2 または random で親コンポーネントから取得されます。 ID が random の場合は、Math.random() を使用して ID として乱数を生成します。 さらに、userNameList で指定された ID に基づいて、名前がユーザーにマップされます。 最後に、接続されたメンバーだけでなく現在のユーザーも格納する状態変数を定義します。 fluidMembers はコンテナーに接続されているすべてのメンバーのリストを格納しますが、currentMember はブラウザー コンテキストを表示している現在のユーザーを表すメンバー オブジェクトを含みます。

        const userId = props.userId == "random" ? Math.random() : props.userId;
        const userNameList = {
        "user1" : "User One",
        "user2" : "User Two",
        "random" : "Random User"
        };
        const userName = userNameList[props.userId];
    
        const [fluidMembers, setFluidMembers] = useState();
        const [currentMember, setCurrentMember] = useState();
    
  2. TODO 3 を次のコードに置き換えます。 これにより、コンポーネントがマウントされたときに tryGetAudienceObject が呼び出され、返された対象ユーザーのメンバーが fluidMemberscurrentMember に設定されます。 ユーザーが存在しない containerId を入力し、UserIdSelection ビューに戻す必要がある場合に対象ユーザー オブジェクトが返される場合にチェックします (props.onContainerNotFound()ビューの切り替えを処理します)。 また、audience.off を返して、React コンポーネントのマウントを解除するときに、イベント ハンドラーを登録解除することをお勧めします。

        useEffect(() => {
        tryGetAudienceObject(userId, userName, props.containerId).then(audience => {
            if(!audience) {
            props.onContainerNotFound();
            alert("error: container id not found.");
            return;
            }
    
            const updateMembers = () => {
            setFluidMembers(audience.getMembers());
            setCurrentMember(audience.getMyself());
            }
    
            updateMembers();
    
            audience.on("membersChanged", updateMembers);
    
            return () => { audience.off("membersChanged", updateMembers) };
        });
        }, []);
    
  3. TODO 4 を次のコードに置き換えます。 fluidMembers または currentMember が初期化されていない場合は、空白の画面がレンダリングされます。 AudienceList コンポーネントは、(次のセクションで実装される) スタイルを使用してメンバー データをレンダリングします。

        if (!fluidMembers || !currentMember) return (<div/>);
    
        return (
            <AudienceList fluidMembers={fluidMembers} currentMember={currentMember}/>
        )
    

    Note

    接続の遷移により、getMyselfundefined を返す、短いタイミング ウィンドウが発生する可能性があります。 これは、現在のクライアント接続がまだ対象ユーザーに追加されていなく、一致する接続 ID が見つからないためです。 React が対象ユーザーのメンバーがいないページをレンダリングしないようにするには、membersChangedupdateMembers を呼び出すリスナーを追加します。 これが動作するのは、コンテナーが接続されたときにサービス対象ユーザーが membersChanged イベントを生成するためです。

ビューの作成

  1. TODO 5 を次のコードに置き換えます。 AudienceDisplay コンポーネントから渡された各メンバーのリスト コンポーネントがレンダリングされています。 各メンバーについて、最初に member.userIdcurrentMember.userId を比較して、そのメンバーが isSelf かどうかを確認します。 これにより、クライアント ユーザーを他のユーザーと区別し、別の色でコンポーネントを表示できます。 次に、リスト コンポーネントを list 配列にプッシュします。 各コンポーネントには、userIduserNameadditionalDetails などのメンバー データが表示されます。

        const currentMember = data.currentMember;
        const fluidMembers = data.fluidMembers;
    
        const list = [];
        fluidMembers.forEach((member, key) => {
            const isSelf = (member.userId === currentMember.userId);
            const outlineColor = isSelf ? 'blue' : 'black';
    
            list.push(
            <div style={{
                padding: '1rem',
                margin: '1rem',
                display: 'flex',
                outline: 'solid',
                flexDirection: 'column',
                maxWidth: '25%',
                outlineColor
            }} key={key}>
                <div style={{fontWeight: 'bold'}}>Name</div>
                <div>
                    {member.userName}
                </div>
                <div style={{fontWeight: 'bold'}}>ID</div>
                <div>
                    {member.userId}
                </div>
                <div style={{fontWeight: 'bold'}}>Connections</div>
                {
                    member.connections.map((data, key) => {
                        return (<div key={key}>{data.id}</div>);
                    })
                }
                <div style={{fontWeight: 'bold'}}>Additional Details</div>
                { JSON.stringify(member.additionalDetails, null, '\t') }
            </div>
            );
        });
    
  2. TODO 6 を次のコードに置き換えます。 これにより、list 配列にプッシュしたすべてのメンバー要素がレンダリングされます。

        return (
            <div>
                {list}
            </div>
        );
    

UserIdSelection コンポーネントを設定する

  1. コード エディターでファイル \src\UserIdSelection.js を作成して開きます。 このコンポーネントには、エンドユーザーがユーザー ID とコラボレーション セッションを選択できるようにする、ユーザー ID ボタンとコンテナー ID 入力フィールドが含まれます。 次の import ステートメントと機能コンポーネントを追加します。

    import { useState } from 'react';
    
    export const UserIdSelection = (props) => {
        // TODO 1: Define styles and handle user inputs
        return (
        // TODO 2: Return view components
        );
    }
    
  2. TODO 1 を次のコードに置き換えます。 onSelectUser 関数は、親 App コンポーネントの状態変数を更新し、ビューの変更を求めるプロンプトを表示します。 handleSubmit メソッドは、TODO 2 で実装されるボタン要素によってトリガーされます。 また、handleChange メソッドは containerId 状態変数を更新するためにも使用されます。 このメソッドは、TODO 2 で実装されている入力要素イベント リスナーから呼び出されます。 また、containerId も ID containerIdInput (TODO 2 で定義されています) で HTML 要素から値を取得するように更新します。

        const selectionStyle = {
        marginTop: '2rem',
        marginRight: '2rem',
        width: '150px',
        height: '30px',
        };
    
        const [containerId, setContainerId] = (location.hash.substring(1));
    
        const handleSubmit = (userId) => {
        props.onSelectUser(userId, containerId);
        }
    
        const handleChange = () => {
        setContainerId(document.getElementById("containerIdInput").value);
        };
    
  3. TODO 2 を次のコードに置き換えます。 これにより、ユーザー ID ボタンとコンテナー ID 入力フィールドがレンダリングされます。

        <div style={{display: 'flex', flexDirection:'column'}}>
        <div style={{marginBottom: '2rem'}}>
            Enter Container Id:
            <input type="text" id="containerIdInput" value={containerId} onChange={() => handleChange()} style={{marginLeft: '2rem'}}></input>
        </div>
        {
            (containerId) ?
            (<div style={{}}>Select a User to join container ID: {containerId} as the user</div>)
            : (<div style={{}}>Select a User to create a new container and join as the selected user</div>)
        }
        <nav>
            <button type="submit" style={selectionStyle} onClick={() => handleSubmit("user1")}>User 1</button>
            <button type="submit" style={selectionStyle} onClick={() => handleSubmit("user2")}>User 2</button>
            <button type="submit" style={selectionStyle} onClick={() => handleSubmit("random")}>Random User</button>
        </nav>
        </div>
    

Fluid サーバーを起動してアプリケーションを実行する

Note

このハウツーの残りの部分と一致させるために、このセクションでは npxnpm のコマンドを使用して Fluid サーバーを起動します。 ただし、この記事のコードは、Azure Fluid Relay サーバーに対して実行することもできます。 詳細については、「方法: Azure Fluid Relay サービスをプロビジョニングする」および「方法: Azure Fluid Relay サービスに接続する」を参照してください

コマンド プロンプトで、次のコマンドを実行して Fluid サービスを開始します。

npx @fluidframework/azure-local-service@latest

新しいコマンド プロンプトを開き、プロジェクトのルート (例: C:/My Fluid Projects/fluid-audience-tutorial) に移動します。 次のコマンドを使用して、アプリケーション サーバーを起動します。 ブラウザーでアプリケーションが開きます。 これには数分かかることがあります。

npm run start

ブラウザー タブで localhost:3000 に移動して、実行中のアプリケーションを表示します。 新しいコンテナーを作成するには、コンテナー ID の入力を空白にしたまま、ユーザー ID ボタンを選択します。 コンテナー セッションに参加する新しいユーザーをシミュレートするには、新しいブラウザー タブを開き、localhost:3000 に移動します。 今回は、最初のブラウザー タブの http://localhost:3000/# に進む URL にあるコンテナー ID 値を入力します。

Note

このデモを Webpack 5 と互換性のあるものにするには、追加の依存関係をインストールする必要がある場合があります。 "buffer" または "url" パッケージに関連するコンパイル エラーが発生した場合は、npm install -D buffer url を実行して、もう一度やり直してください。 これは、Fluid Framework の今後のリリースで解決される予定です。

次のステップ

  • userConfigadditionalDetails フィールドでより多くのキーと値のペアを使用してデモを拡張してみてください。
  • SharedMap や SharedString などの分散データ構造を利用するコラボレーション アプリケーションに対象ユーザーを統合することを検討してください。
  • 対象ユーザーに関する詳細情報を参照してください。

ヒント

コードを変更すると、プロジェクトが自動的に再構築され、アプリケーション サーバーが再読み込みされます。 ただし、コンテナー スキーマを変更すると、アプリケーション サーバーを閉じて再起動した場合にのみ有効になります。 これを行うには、コマンド プロンプトにフォーカスを設定し、Ctrl キーを押しながら C キーを 2 回押します。 その後、npm run start をもう一度実行してください。