Il s'agit d'une démonstration et d'un exemple d'application conçue pour être un simple système de chat Web multi-utilisateurs.
Il fournit des discussions de groupe persistantes, des discussions privées d'utilisateur à utilisateur, une liste d'utilisateurs, une détection d'inactivité (loin du clavier) et plusieurs autres fonctionnalités.
Il repose sur plusieurs technologies Azure, notamment : Web PubSub, Static Web Apps et Table Storage.
?? Note. Cela a été créé comme un projet personnel, créé pour faciliter l'apprentissage tout en construisant quelque chose d'intéressant. Le code comporte toutes les mises en garde que vous pouvez attendre d'un tel projet.
Objectifs:
Cas d'utilisation et fonctionnalités clés :
Il s'agit de la principale interface Web utilisée par les utilisateurs finaux via le navigateur.
La source de cela se trouve dans client/ et consiste en une application ES6 JS pure et autonome, aucun regroupement ni Node.js n'est requis. Il est écrit en utilisant Vue.js comme framework de support et Bulma comme framework CSS.
Quelques remarques :
client/js/app.js
montre comment créer une application Vue.js avec des composants enfants en utilisant cette approche. La majorité de la logique client est ici.client/js/components/chat.js
est un composant Vue.js utilisé pour héberger chaque onglet de discussion dans l'application.auth/
spécial fourni par Static Web Apps est utilisé pour connecter les utilisateurs et récupérer leurs détails utilisateur, tels que l'ID utilisateur.Il s'agit du backend, qui gère les événements Websocket vers et depuis Azure Web PubSub et fournit l'API REST pour certaines opérations.
La source se trouve dans api/ et consiste en une application de fonction Azure Node.js. Il se connecte à Azure Table Storage pour conserver les discussions de groupe et les données utilisateur (Table Storage a été choisi car il est simple et bon marché). Ceci n'est pas hébergé dans une application de fonction Azure autonome, mais plutôt déployé dans l'application Web statique dans le cadre de sa prise en charge d'API sans serveur.
Il existe quatre fonctions HTTP, toutes servies à partir du chemin /api/
par défaut
eventHandler
- Récepteur Webhook pour les événements « en amont » envoyés depuis le service Azure Web PubSub, contient la majorité de la logique de l'application. Non appelé directement par le client, uniquement Azure WebPub Sub.getToken
- Appelé par le client pour obtenir un jeton d'accès et une URL pour se connecter via WebSockets au service Azure Web PubSub. Doit être appelé avec userId dans la requête URL, par exemple GET /api/getToken?userId={user}
getUsers
- Renvoie une liste des utilisateurs connectés, notez que l'itinéraire pour cette fonction est /api/users
getChats
- Renvoie une liste des discussions de groupe actives, notez que l'itinéraire pour cette fonction est /api/chats
L'état est géré avec state.js
qui est un module ES6 exportant des fonctions prenant en charge l'état CRUD pour les utilisateurs et les discussions. Ce module effectue toutes les interactions avec Azure Tables et fournit une interface relativement transparente, de sorte qu'un backend de stockage différent puisse être remplacé.
Il existe un flux de messages bidirectionnel entre les clients et le serveur via Azure Web PubSub et les gestionnaires d'événements
Le sous-protocole json.webpubsub.azure.v1 est utilisé à la place des WebSockets de base, il offre un certain nombre de fonctionnalités : des utilisateurs peuvent être ajoutés à des groupes, les clients peuvent envoyer des événements personnalisés (en utilisant type: event
) et également envoyer des messages directement à d'autres clients. sans passer par le serveur (en utilisant type: sendToGroup
)
Remarques :
Les événements et les discussions sont envoyés à l'aide du sous-protocole json.webpubsub.azure.v1.
Les messages de discussion envoyés par le client utilisent sendToGroup
et une charge utile JSON personnalisée avec trois champs message
, fromUserId
& fromUserName
, ces messages sont relayés de client à client par Azure Web PubSub, le serveur n'en est jamais informé :
{
type : 's endToGroup ',
group : < chatId > ,
dataType : 'j son ',
data : {
message : < message text > ,
fromUserId : < userId > ,
fromUserName : < userName > ,
},
}
Les événements destinés au serveur principal sont envoyés sous forme de messages WebSocket depuis le client via le même sous-protocole avec le type event
et un sous-type spécifique à l'application, par exemple
{
type : 'e vent ',
event : 'j oinChat ',
dataType : 't ext ',
data : < chatId > ,
}
Les types d'événements sont :
La fonction eventHandler
de l'API backend comporte des cas pour chacun de ces événements utilisateur, ainsi que des gestionnaires pour les événements système de connexion et de déconnexion.
Les messages envoyés depuis le serveur ont une charge utile spécifique à l'application Chatr personnalisée, comme suit :
{
chatEvent : < eventType > ,
data : < JSON object type dependant >
}
Où eventType
est l'un des éléments suivants :
Le code client dans client/js/app.js
gère ces messages au fur et à mesure qu'ils sont reçus par le client et réagit en conséquence.
Le plan de ce projet était d'utiliser Azure Web PubSub et Azure Static Web Apps et d'héberger le composant côté serveur en tant qu'ensemble de fonctions sans serveur dans la prise en charge de l'API Static Web Apps (qui est en fait Azure Functions sous le capot). Azure Static Web Apps a été sélectionné car il offre une prise en charge incroyable de la connexion et de l'authentification des utilisateurs sans code et sans configuration, que je souhaitais exploiter.
Quelques commentaires sur cette approche :
webPubSubConnection
. Pour renvoyer des messages à Web PubSub, le SDK du serveur peut simplement être utilisé dans le code de fonction plutôt que d'utiliser la liaison de sortie webPubSub
. L'état dans Azure Tables se compose de deux tables (collections) nommées chats
et users
Comme chaque discussion contient des objets imbriqués dans le champ membres, chaque discussion est stockée sous forme de chaîne JSON dans un champ appelé data
. La PartitionKey n'est pas utilisée et codée en dur dans une chaîne "chatr". Le RowKey et le champ id à l’intérieur de l’objet de données sont identiques.
Exemple d'entité de données de chat
{
"id" : " eab4b030-1a3d-499a-bd89-191578395910 " ,
"name" : " This is a group chat " ,
"members" : {
"0987654321" : {
"userId" : " 0987654321 " ,
"userName" : " Another Guy "
},
"1234567890" : {
"userId" : " 1234567890 " ,
"userName" : " Ben "
}
},
"owner" : " 1234567890 "
}
Les utilisateurs sont stockés en tant qu'entités avec les champs (colonnes) décrits ci-dessous. Comme il n’y a pas de champs imbriqués, il n’est pas nécessaire d’encoder sous forme de chaîne JSON. Encore une fois, la PartitionKey n'est pas utilisée et codée en dur dans une chaîne "chatr".
userId
renvoyé par le point de terminaison d'authentification des applications Web statiquestwitter
, aad
ou github
Voir le makefile
$ make
help This help message
lint ? Lint & format, will not fix but sets exit code on error
lint-fix Lint & format, will try to fix errors and modify code
run ? Run server locally using Static Web Apps CLI
clean ? Clean up project
deploy Deploy everything to Azure using Bicep
tunnel ? Start loophole tunnel to expose localhost
Le déploiement est légèrement complexe en raison du nombre de composants et de la configuration entre eux. Le deploy
cible du makefile devrait tout déployer pour vous en une seule étape à l'aide des modèles Bicep trouvés dans le dossier déployer/
Voir le fichier Lisez-moi dans le dossier de déploiement pour plus de détails et d'instructions.
Ceci est possible mais nécessite un petit effort car le service Azure Web PubSub doit pouvoir appeler le point de terminaison HTTP sur votre machine de localisation, un tunnel a donc été utilisé.
Lors de l'exécution locale, la CLI Static Web Apps est utilisée, ce qui nous fournit un faux point de terminaison d'authentification utilisateur.
Un résumé des étapes est le suivant :
api/local.settings.sample.json
dans api/local.settings.json
et modifiez les valeurs des paramètres requises.loophole http 7071 --hostname chatr
https://{{hostname-of-tunnel-service}}/api/eventHandler
make run
http://localhost:4280/index.html