El objetivo es crear una aplicación de chat con un conjunto completo de funciones que use a Fly RealTime en combinación con otros servicios para almacenar, manipular y compartir datos.
Si tiene alguna pregunta, idea o desea contribuir, plantee un problema o comuníquese con nosotros.
Nodo 16 instalado
Las funciones de Azure funcionan desde NPM. Para instalar esta ejecución:
npm install -g azure-functions-core-tools@4
un archivo .env en ./api:
JWT_SIGNING_KEY=key used to sign tokens for users. Can be anything you decide it to be
# Ably
ABLY_API_KEY=YOURKEY:HERE
APP_ID=[YOUR ABLY APP ID](https://faqs.ably.com/how-do-i-find-my-app-id)
CONTROL_KEY=[YOUR ABLY CONTROL KEY](https://ably.com/documentation/control-api#authentication)
# Azure
COSMOS_ENDPOINT=https://yourcosomsdb.documents.azure.com
COSMOS_KEY=ASK FOR THIS OR MAKE YOUR OWN
COSMOS_DATABASE_ID=metadata
AZURE_STORAGE_CONNECTION_STRING=your string here
AZURE_STORAGE_CONTAINER_NAME=container name here
# Auth0
AUTH0_DOMAIN=yourdomain.auth0.com
AUTH0_CLIENTID=yourclientid
AUTH0_REDIRECT_URI=http://localhost:8080/auth0-landing
# from root folder
npm run init # installs node modules for api & integrations
npm run start # runs the dev serverComo se ve en el archivo .env, hay algunos servicios con los que deberá registrarse para usar el proyecto en su formulario actual.
Para obtener las credenciales hábiles, primero deberá registrarse para obtener una cuenta hábilmente gratuita. Una vez que tenga una cuenta hábil, puede ir a la aplicación predeterminada generada y obtener la tecla API raíz para ella. Esto se utilizará para el entorno ABLY_API_KEY . La variable APP_ID ENV debe establecerse en la primera parte de su tecla API, antes de FullStop. Si su clave API es 12345.jh40fj23jkd0-,32c3-j- , debe configurarla en 12345 .
Para el CONTROL_KEY , que se utiliza para controlar la creación de claves API, aplicaciones y más programáticamente, deberá ir al token de acceso como un usuario registrado y hacer clic en 'Crear nuevo token de acceso'. Déle un nombre, desde el menú desplegable 'Cuenta' Seleccione la cuenta que tiene la aplicación para la que tiene una clave API y asegúrese de seleccionar los permisos read:key and write:key . Cree el token y use su valor como CONTROL_KEY .
COSMOSDB se usa para almacenar datos para esta aplicación. Para obtener una cuenta de Azure y crear un recurso COSMOSDB, siga los pasos en el tutorial de configuración de Azure. Debe establecer el COSMOS_ENDPOINT para que sea el URI proporcionado en su nuevo subacceso. El COSMOS_KEY es la clave principal para la cuenta COSMOSDB, a la que puede acceder según lo descrito por Azure.
COSMOS_DATABASE_ID es el nombre del contenedor que usará dentro de su cuenta COSMOSDB.
Usando la misma cuenta de Azure creada para COSMOSDB, cree un nuevo contenedor de almacenamiento de datos. Una vez que tenga esa configuración, vaya a la sección 'claves de acceso' en la barra lateral para obtener una connection string para AZURE_STORAGE_CONNECTION_STRING , y luego configure AZURE_STORAGE_CONTAINER_NAME en lo que haya llamado el contenedor.
Si desea incluir Auth0 como un método de autenticación, deberá crear una cuenta Auth0. Una vez que tenga una cuenta Auth0, cree una aplicación con 'aplicaciones web regulares' seleccionadas como tipo de aplicación. En esa aplicación, puede copiar el dominio para AUTH0_DOMAIN y el cliente para el AUTH0_CLIENTID . El AUTH0_REDIRECT_URI debe señalar el URI apropiado que Auth0, después de autenticar a un usuario, debe redirigir. El valor predeterminado debería funcionar para ejecutar localmente.
En la pestaña Configuración de su aplicación Auth0, desplácese hacia abajo a la sección llamada 'URIS de aplicación'. En él, debería ver un campo para 'URL de devolución de llamada permitidas' y 'URL de cierre de sesión permitidas'. Para el contexto, el flujo de una página web que usa Auth0 es:
Su sitio vincula a un usuario con la página de inicio de sesión de su aplicación Auth0, donde inicia sesión en la página Auth0 redirige al usuario de regreso a la página de 'devolución de llamada' de su sitio web Cuando el usuario desea iniciar sesión, se dirige a la página de sesión de la aplicación AUTH0 y luego se redirige a la página especificada en la consulta 'returnTo' aprobada a la página de cierre de sesión
Para evitar el mal uso y abuso potencial, debe especificar a qué URLS Auth0 puede redirigir. Al alojar esta aplicación de chat localmente, se aloja en Localhost: 8080, así que configure las URL de devolución de llamada permitidas en 'http: // localhost: 8080/auth0-landing'. Cuando un usuario inicie sesión, tendremos al usuario redirigido a nuestra página principal, por lo que configure las URL de cierre de sesión permitidas en 'http: // localhost: 8080/'.
La aplicación de chat está compuesta por lo siguiente:
Archive API que recibe eventos de Reactor hábilmente y mantenga un historial de chatChat Archive . La aplicación React es una aplicación predeterminada de una sola página. Utiliza una mezcla de react-router-dom y un AppProvider personalizado para proporcionar el contexto de seguridad para la aplicación.
La aplicación usa @hactly-labs/react-ghooks para interactuar con canales hábiles , y la aplicación está compuesta por componentes funcionales React modernos.
La capa de nieve es el servidor de desarrollo, que construirá transparentemente el código ES6 para la producción.
El BFF es una API específica de la aplicación que contiene toda la lógica de Serverside para la aplicación de chat. Debido a que está alojado en aplicaciones web de Azure Static , podemos usar las azure-functions-core-tools ejecutan el servidor API.
Además de esto, el tiempo de ejecución de Azure Static Web Apps Running automáticamente las API para nosotros, por lo que no necesitamos preocuparnos por configurar el alojamiento. El BFF se ejecuta en Infrastrucutre sin servidor, y Azure SWA lo escala automáticamente para satisfacer la demanda.
Para agregar nuevos puntos finales de API, deberá agregar un nuevo directorio a la carpeta api .
Primero, cree un directorio para la nueva API, por ejemplo, api/messages . Luego, cree un archivo function.json en el nuevo directorio.
{
"bindings" : [
{
"route" : " messages " ,
"authLevel" : " function " ,
"type" : " httpTrigger " ,
"direction" : " in " ,
"name" : " req " ,
"methods" : [ " get " , " post " ]
},
{
"type" : " http " ,
"direction" : " out " ,
"name" : " res "
}
],
"scriptFile" : " ../dist/messages/index.js "
} A continuación, deberá crear su API TypeScript :
import "../startup" ;
import { Context , HttpRequest } from "@azure/functions" ;
export default async function ( context : Context , req : HttpRequest ) : Promise < void > {
context . res = { status : 200 , body : "I'm an API" } ;
} Esta API ahora se montará en http://localhost:8080/api/messages .
¡Y eso es todo! La herramienta y el SDK detectarán automáticamente su código a medida que lo cambie y reconstruya sus funciones por usted.
La aplicación utiliza la autenticación de token JWT entre la aplicación web y el BFF. Almacenamos credenciales de usuarios y contraseñas de hash en una forma salada (hechas con BCrypt) en la base de datos COSMOSDB.
Cuando un usuario se autentica, la aplicación firma un token JWT con la ID y el nombre de usuario del usuario que luego se envía al BFF en solicitudes posteriores de datos autenticados. Esto significa que, con una pequeña cantidad de código en las API, podemos asegurarnos de que el usuario sea quién dice ser y que tenga derecho a acceder a los datos de la API.
Podemos expandir este modelo para incluir una colección de roles para la autenticación basada en reclamos a los recursos en la aplicación.
Authenticated User Only Podemos crear una llamada API autenticada de JWT token utilizando los siguientes métodos de conveniencia en la BFF API .
import "../startup" ;
import { Context , HttpRequest } from "@azure/functions" ;
import { authorized , ApiRequestContext } from "../common/ApiRequestContext" ;
export default async function ( context : Context , req : HttpRequest ) : Promise < void > {
await authorized ( context , req , ( ) => {
// This code will only run if the user is authenticated
context . res = {
status : 200 ,
body : JSON . stringify ( "I am validated and authenticated" )
} ;
} ) ;
}Si desea acceder a la información de los usuarios autenticados como parte de una de estas llamadas API, puede hacer lo siguiente:
import "../startup" ;
import { Context , HttpRequest } from "@azure/functions" ;
import { authorized , ApiRequestContext } from "../common/ApiRequestContext" ;
export default async function ( context : Context , req : HttpRequest ) : Promise < void > {
await authorized (
context ,
req ,
( { user } : ApiRequestContext ) => {
// user is the userDetails object retrieved from CosmosDb
context . res = {
status : 200 ,
body : JSON . stringify ( "I am validated and authenticated" )
} ;
} ,
true
) ; // <- true to include the userDetails object in the ApiRequestContext
} La autenticación en la aplicación se implementa en AppProviders.jsx .
Proporciona un gancho React que devolverá el objeto userDetails si el usuario está autenticado (además de garantizar que el usuario esté autenticado en absoluto). Si un usuario determinado no se autentica, se redirigirá a la página de inicio de sesión en todos los casos.
Debido a que el AppProvider se encarga de la autenticación, deberá usar ganchos para acceder a los datos del usuario y hacer llamadas API autenticadas en cualquier componente.
Aquí hay un ejemplo de acceder al objeto userDetails del usuario actualmente autenticado.
import { useAuth } from "../../AppProviders" ;
const MyComponent = ( ) => {
const { user } = useAuth ( ) ;
return < div > { user . username } </ div > ;
} ;
export default MyComponent ; También puede acceder a una instancia de la clase BffApiClient , que le permitirá realizar llamadas API autenticadas y ya contiene el JWT token de usuarios actualmente registrado actualmente.
import { useAuth } from "../../AppProviders" ;
const MyComponent = ( ) => {
const { api } = useAuth ( ) ;
const [ channels , setChannels ] = useState ( [ ] ) ;
useEffect ( ( ) => {
const fetchChannels = async ( ) => {
const response = await api . listChannels ( ) ;
setChannels ( response . channels ) ;
} ;
fetchChannels ( ) ;
} , [ ] ) ;
return < div > ... bind channel data here </ div > ;
} ;
export default MyComponent ; El ejemplo anterior usa el gancho useEffect para obtener los canales cuando se monta el componente: la solicitud API se realiza utilizando la instancia api , proporcionada por el gancho useAuth .
Esta es la única forma en que debe hacer llamadas API al BFF desde un componente, ya que asegurará que el token JWT sea válido y presente.
Si está agregando nuevas BFF APIs a la aplicación, deberá implementar una nueva función en /app/src/sdk/BffApiClient.js para que esté disponible para sus componentes.
Estas llamadas BffApiClient son simples y se ven así:
async listChannels ( ) {
const result = await this . get ( "/api/channels" ) ;
return await result . json ( ) ;
} Algún código de utilidad en el cliente se asegurará de que el JWT token correcto esté presente cuando se realice la solicitud.
Estamos utilizando COSMOSDB para almacenar los metadatos de nuestra aplicación porque es una base de datos escalable, altamente disponible y administrada que no tenemos que administrarnos. Puede ejecutarse en un modo pre-proporcionado o sin servidor, lo que ayuda a mantener los costos bajos cuando la aplicación no está en uso (a costa de algún rendimiento).
Utilizamos una única base de datos COSMOSDB para almacenar todos los metadatos, y en el interior, hemos creado una colección para cada tipo de entidad que estamos almacenando.
Por ejemplo: la recopilación User , almacena nuestros registros de usuario, y se puede consultar utilizando sintaxis similar a SQL. CosmosDB lo facilita al indexar automáticamente los documentos JSON.
Cada una de las entidades de metadatos almacenados tiene una id y un campo type , y estamos utilizando una clase generic repository ( /api/common/dataaccess/CosmosDbMetadataRepository ) para cargar y guardar estas entidades.
Para el desarrollo local, puede usar la versión alojada en la nube de Cosmos o usar una de las docker container images disponibles para ejecutar una copia local de la base de datos.
Estamos utilizando Ably channels para almacenar nuestros mensajes de chat y para llevar los eventos a nuestra aplicación React. Cada usuario conectado recibirá mensajes para canales que están viendo activamente en tiempo real, y estamos utilizando Channel rewind para llenar los mensajes enviados más recientemente.
Los mensajes pueden corrected de manera asincronosa después de que se hayan recibido, por ejemplo, para aplicar el filtrado de blasfemias o para corregir los errores de ortografía. Estos mensajes de corrección serán parte de la secuencia y se aplicarán retroactivamente en la aplicación React. (Desarrollo adicional sobre esto en epopeyas posteriores)
Este diseño nos permite defender las API adicionales que consuman estos eventos y publicar sus propias elaboraciones en los canales para que los clientes respondan.
Debido a que los eventos hábiles desaparecerán con el tiempo, vamos a almacenar copias de eventos entrantes en cada canal en nuestro Chat Archive a través de la Archive API .
La Archive API recibirá mensajes de reactores para todos nuestros canales y los agregará a Azure Storage Blobs específicos de canal. La API agregará a un solo archivo hasta que alcance un umbral de tamaño (~ 500kb) y luego creará un nuevo archivo para mensajes posteriores.
La Archive API mantendrá un registro del archivo de archivo actualmente activo en la Metadata database para cada canal.
La Archive API podrá actualizar un índice de búsqueda a medida que se reciban y archiven los mensajes para exponerlos más tarde en la búsqueda.
Las pruebas se escriben en jest con ts-jest utilizada para ejecutar las pruebas TypeScript API.