Uso de características de audiencia en Fluid Framework

En este tutorial, obtendrá información sobre el uso de la Audiencia de Fluid Framework con React para crear una demostración visual de usuarios que se conectan a un contenedor. El objeto de audiencia contiene información relacionada con todos los usuarios conectados al contenedor. En este ejemplo, se usará la biblioteca cliente de Azure para crear el contenedor y la audiencia.

En la imagen siguiente se muestran los botones de identificador y un campo de entrada de identificador de contenedor. Al dejar en blanco el campo de identificador de contenedor y hacer clic en un botón de identificador de usuario, se creará un contenedor y se unirá como el usuario seleccionado. Como alternativa, el usuario final puede escribir un identificador de contenedor y elegir un identificador de usuario para unirse a un contenedor existente como el usuario seleccionado.

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

En la imagen siguiente se muestran varios usuarios conectados a un contenedor representado por cuadros. El cuadro mostrado en azul representa al usuario que ve al cliente, mientras que los cuadros mostrados en negro representan a los demás usuarios conectados. A medida que los nuevos usuarios se conecten al contenedor con identificadores únicos, aumentará el número de cuadros.

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

Nota:

En este tutorial se da por supuesto que está familiarizado con la información general de Fluid Framework y que ha completado el Inicio rápido. También debe estar familiarizado con los conceptos básicos de React, creando proyectos de React y enlaces de React.

Creación del proyecto

  1. Abra un símbolo del sistema y vaya a la carpeta principal donde desea crear el proyecto; por ejemplo, C:\My Fluid Projects.

  2. Ejecute el siguiente comando en el símbolo del sistema (tenga en cuenta que la CLI es npx, no npm. Se instaló cuando instaló Node.js).

    npx create-react-app fluid-audience-tutorial
    
  3. El proyecto se crea en una subcarpeta denominada fluid-audience-tutorial. Diríjase allí con el comando cd fluid-audience-tutorial.

  4. El proyecto usa las siguientes bibliotecas de Fluid:

    Biblioteca Descripción
    fluid-framework Contiene la estructura de datos distribuidos SharedMap que sincroniza los datos entre los clientes.
    @fluidframework/azure-client Define la conexión a un servidor del servicio Fluid y define el esquema de inicio para el contenedor de Fluid.
    @fluidframework/test-client-utils Define el InsecureTokenProvider necesario para crear la conexión a un servicio Fluid.

    Ejecute el siguiente comando para instalar las bibliotecas.

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

Codificación del proyecto

Configuración de variables de estado y la vista de componentes

  1. Abra el archivo \src\App.js en el editor de código. Elimine todas las instrucciones import predeterminadas. A continuación, elimine todo el marcado de la instrucción return. A continuación, agregue instrucciones de importación para componentes y enlaces de React. Tenga en cuenta que implementaremos los componentes AudienceDisplay y UserIdSelection importados en los pasos posteriores. El archivo debería tener este aspecto:

        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. Reemplaza TODO 1 por el siguiente código. Este código inicializa las variables de estado locales que se usarán en la aplicación. El valor displayAudience determina si representamos el componente AudienceDisplay o el componente UserIdSelection (vea TODO 2). El valor userId es el identificador de usuario con el que conectarse al contenedor y el valor containerId es el contenedor que se va a cargar. Las funciones handleSelectUser y handleContainerNotFound se pasan como devoluciones de llamada a las dos vistas y administran las transiciones de estado. Se llama a handleSelectUser al intentar crear o cargar un contenedor. Se llama a handleContainerNotFound cuando se produce un error al crear o cargar un contenedor.

    Tenga en cuenta que los valores userId y containerId procederán de un componente UserIdSelection a través de la función handleSelectUser.

        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. Reemplaza TODO 2 por el siguiente código. Como se indicó anteriormente, la variable displayAudience determinará si representamos el componente AudienceDisplay o el componente UserIdSelection. Además, las funciones para actualizar las variables de estado se pasan a componentes como propiedades.

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

Configuración del componente AudienceDisplay

  1. Cree y abra un archivo \src\AudienceDisplay.js en el editor de código. Agregue las instrucciones siguientes import :

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

    Tenga en cuenta que los objetos importados de la biblioteca de Fluid Framework son necesarios para definir usuarios y contenedores. En los pasos siguientes, AzureClient e InsecureTokenProvider se usarán para configurar el servicio de cliente (vea TODO 1), mientras que SharedMap se usará para configurar un elemento containerSchema necesario para crear un contenedor (vea TODO 2).

  2. Agregue los siguientes componentes funcionales y funciones auxiliares:

        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
        }
    

    Tenga en cuenta que AudienceDisplay y AudienceList son componentes funcionales que controlan la obtención y representación de datos de audiencia, mientras que el método tryGetAudienceObject controla la creación de servicios de contenedor y audiencia.

Obtención del contenedor y la audiencia

Puede usar una función auxiliar para obtener los datos de Fluid del objeto de audiencia en el nivel de vista (el estado React). Se llama al método tryGetAudienceObject cuando se carga el componente de vista después de seleccionarse un identificador de usuario. El valor devuelto se asigna a una propiedad de estado React.

  1. Reemplaza TODO 1 por el siguiente código. Tenga en cuenta que los valores de userIduserNamecontainerId se pasarán en el componente App. Si no hay ningún containerId, se crea un nuevo contenedor. Además, tenga en cuenta que containerId se almacena en el hash de la dirección URL. Un usuario que entra en una sesión de un nuevo explorador puede copiar la dirección URL de un explorador de sesión existente o navegar a localhost:3000 y escribir manualmente el identificador de contenedor. Con esta implementación, queremos encapsular la getContainer llamada en un intento en caso de que el usuario escriba un identificador de contenedor que no exista. Visite la documentación de contenedores para obtener más información.

        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;
    

Obtención de la audiencia en el montaje de componentes

Ahora que hemos definido cómo obtener la audiencia de Fluid, es necesario indicar a React que llame a tryGetAudienceObject cuando se monte el componente AudienceDisplay.

  1. Reemplaza TODO 2 por el siguiente código. Tenga en cuenta que el identificador de usuario procederá del componente principal como user1user2 o random. Si el identificador es random, usamos Math.random() para generar un número aleatorio como identificador. Además, se asignará un nombre al usuario en función de su identificador, tal como se especifica en userNameList. Por último, definimos las variables de estado que almacenarán los miembros conectados, así como el usuario actual. fluidMembers almacenará una lista de todos los miembros conectados al contenedor, mientras que currentMember contendrá el objeto miembro que representa al usuario actual que ve el contexto del explorador.

        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. Reemplaza TODO 3 por el siguiente código. Este llamará a tryGetAudienceObject cuando se monte el componente y establezca los miembros de la audiencia devueltos en fluidMembers y currentMember. Tenga en cuenta que se comprueba si se devuelve un objeto de audiencia en caso de que un usuario escriba un containerId que no exista y necesitamos devolverlos a la vista UserIdSelection (props.onContainerNotFound() controlará el cambio de la vista). Además, se recomienda anular el registro de los controladores de eventos cuando el componente React se desmonta devolviendo audience.off.

        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. Reemplaza TODO 4 por el siguiente código. Tenga en cuenta que si no se han inicializado fluidMembers ni currentMember, se representa una pantalla en blanco. El componente AudienceList representará los datos de miembros con la aplicación de estilos (que se implementarán en la sección siguiente).

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

    Nota:

    Las transiciones de conexión pueden dar lugar a ventanas de tiempo breves en las que getMyself devuelve undefined. Esto se debe a que la conexión de cliente actual aún no se habrá agregado a la audiencia, por lo que no se encuentra un identificador de conexión coincidente. Para evitar que React represente una página sin miembros de la audiencia, agregamos una escucha para llamar a updateMembers en membersChanged. Esto funciona porque la audiencia del servicio emite un evento membersChanged cuando el contenedor está conectado.

Crear la vista

  1. Reemplaza TODO 5 por el siguiente código. Tenga en cuenta que representamos un componente de lista para cada miembro pasado desde el componente AudienceDisplay. Para cada miembro, primero comparamos member.userId con currentMember.userId para comprobar si ese miembro es isSelf. De este modo, podemos diferenciar al usuario del cliente de los demás usuarios y mostrar el componente con un color distinto. A continuación, insertamos el componente de lista en una matriz list. Cada componente mostrará datos de miembros como userIduserName y additionalDetails.

        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. Reemplaza TODO 6 por el siguiente código. Esto representará todos y cada uno de los elementos de miembro insertados en la matriz list.

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

Configuración del componente UserIdSelection

  1. Cree y abra un archivo \src\UserIdSelection.js en el editor de código. Este componente incluirá botones de identificador de usuario y campos de entrada de identificador de contenedor que permiten a los usuarios finales elegir su identificador de usuario y sesión colaborativa. Agregue las instrucciones import y los componentes funcionales siguientes:

    import { useState } from 'react';
    
    export const UserIdSelection = (props) => {
        // TODO 1: Define styles and handle user inputs
        return (
        // TODO 2: Return view components
        );
    }
    
  2. Reemplaza TODO 1 por el siguiente código. Tenga en cuenta que la función onSelectUser actualizará las variables de estado en el componente App principal y solicitará un cambio de vista. El método handleSubmit se desencadena mediante elementos de botón que se implementarán en TODO 2. Además, el método handleChange se usa para actualizar la variable de estado containerId. Se llamará a este método desde una escucha de eventos de elementos de entrada implementada en TODO 2. Además, tenga en cuenta que actualizamos containerId obteniendo el valor de un elemento HTML con el identificador containerIdInput (definido en TODO 2).

        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. Reemplaza TODO 2 por el siguiente código. Esto representará los botones de identificador de usuario, así como el campo de entrada de identificador de contenedor.

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

Inicio del servidor de Fluid y ejecución de la aplicación

Nota:

A fin de coincidir con el resto de las instrucciones, en esta sección se usan los comandos npx y npm para iniciar un servidor de Fluid. Sin embargo, el código de este artículo también se puede ejecutar en un servidor de Azure Fluid Relay. Para obtener más información, consulte Procedimiento: Aprovisionamiento de un servicio Azure Fluid Realy y Procedimiento: Conexión a un servicio Azure Fluid Relay.

En el símbolo del sistema, ejecute el siguiente comando para iniciar el servicio Fluid.

npx @fluidframework/azure-local-service@latest

Abra un nuevo símbolo del sistema y vaya a la raíz del proyecto; por ejemplo, C:/My Fluid Projects/fluid-audience-tutorial. Inicie el servidor de aplicaciones con el siguiente comando. La aplicación se abre en el explorador. Esta operación puede tardar unos minutos.

npm run start

Vaya a localhost:3000 en una pestaña de explorador para ver la aplicación en ejecución. Para crear un contenedor, seleccione un botón de identificador de usuario mientras deja en blanco la entrada de identificador de contenedor. Para simular que un nuevo usuario se una a la sesión de contenedor, abra una nueva pestaña de explorador y vaya a localhost:3000. Esta vez, escriba el valor de identificador de contenedor que se puede encontrar en la dirección URL de la primera pestaña de explorador que sigue a http://localhost:3000/#.

Nota:

Es posible que tenga que instalar una dependencia adicional para que esta demostración sea compatible con Webpack 5. Si recibe un error de compilación relacionado con un paquete "buffer" o "url", ejecute npm install -D buffer url e inténtelo de nuevo. Esto se resolverá en una versión futura de Fluid Framework.

Pasos siguientes

  • Intente ampliar la demostración con más pares clave-valor en el campo additionalDetails de userConfig.
  • Considere la posibilidad de integrar la audiencia en una aplicación colaborativa que utilice estructuras de datos distribuidas como SharedMap o SharedString.
  • Obtenga más información sobre la audiencia.

Sugerencia

Cuando realice cambios en el código, el proyecto se recompilará automáticamente y el servidor de aplicaciones se volverá a cargar. Sin embargo, si realiza cambios en el esquema de contenedor, solo surtirán efecto si cierra y reinicia el servidor de aplicaciones. Para ello, ponga el enfoque en el símbolo del sistema y presione Ctrl-C dos veces. A continuación, vuelva a ejecutar npm run start.