Implémentation de serveur de messagerie de salle qui utilise un protocole RPC bidirectionnel pour implémenter une communication de type CHAT. Conçu pour gérer les problèmes de messagerie public de réseau public comme une livraison fiable, plusieurs connexions à partir d'un seul utilisateur, des autorisations et une présence en temps réel. Le traitement des demandes RPC et un format de messages de salle sont personnalisables via des crochets, permettant d'implémenter tout, d'un serveur de salles de chat à une application collaborative avec une résolution de conflit complexe. Les messages en salle peuvent également être utilisés pour créer des API publiques ou pour tunnel les communications M2M pour les appareils IoT.
Messagerie de pièce fiable à l'aide d'un stockage d'historique côté serveur et d'une API de synchronisation.
Format de messages arbitraires via une fonction de validation (crochet), permettant des formats de messages personnalisés / hétérogènes (y compris une données binaires à l'intérieur des messages).
API de présence utilisateur par salle avec notifications.
API de gestion des utilisateurs de salles en temps réel et permissions par les utilisateurs. Prise en charge des modes d'accès basés sur la liste noire ou la liste blanche et un groupe d'administrateurs facultatifs.
Prise en charge transparente des connexions de plusieurs utilisateurs de divers Devises à n'importe quelle instance de service.
Écrit en microservice sans état, utilise Redis (prend également en charge les configurations de cluster) en tant que magasin d'État, peut être à l'échelle horizontalement à la demande.
Support de personnalisation étendu. Les fonctionnalités personnalisées peuvent être ajoutées via des crochets avant / après pour tout traitement de demande du client. Et les gestionnaires de demandes (commandes) peuvent être invoqués côté serveur via une API.
Transport de réseautage pluginable. La communication client-serveur se fait via un protocole RPC bidirectionnel. La mise en œuvre du transport de socket.io est incluse.
Magasin d'État pluginable. Les magasins de mémoire et de redis sont inclus.
Prend en charge l'utilisateur en ligne léger vers la messagerie utilisateur en ligne.
Lisez cet article pour plus d'informations générales.
Ce projet est un module de nœud disponible via NPM. Allez-les voir si vous ne les avez pas installés localement.
$ npm i chat-serviceDéfinissez d'abord une configuration de serveur. Sur un côté serveur, définissez un crochet de connexion à socket, car le service s'appuie sur une implémentation d'authentification externe. Un utilisateur a juste besoin de passer une vérification d'automne, aucune étape d'ajout d'utilisateur explicite n'est requise.
const ChatService = require ( 'chat-service' )
const port = 8000
function onConnect ( service , id ) {
// Assuming that auth data is passed in a query string.
let { query } = service . transport . getHandshakeData ( id )
let { userName } = query
// Actually check auth data.
// ...
// Return a promise that resolves with a login string.
return Promise . resolve ( userName )
} La création d'un serveur est une instanciation d'objet simple. Remarque: la méthode close doit être appelée pour arrêter correctement une instance de service (voir les échecs de récupération).
const chatService = new ChatService ( { port } , { onConnect } )
process . on ( 'SIGINT' , ( ) => chatService . close ( ) . finally ( ( ) => process . exit ( ) ) ) Le serveur s'exécute maintenant sur le port 8000 , en utilisant l'état memory . Par défaut, l'espace de noms '/chat-service' est utilisé. Ajoutez une chambre avec l'utilisateur admin comme propriétaire de la salle. Toutes les chambres doivent être créées explicitement (l'option pour permettre la création de chambres d'un côté client est également fournie).
// The room configuration and messages will persist if redis state is
// used. addRoom will reject a promise if the room is already created.
chatService . hasRoom ( 'default' ) . then ( hasRoom => {
if ( ! hasRoom ) {
return chatService . addRoom ( 'default' , { owner : 'admin' } )
}
} ) Sur un client, une implémentation socket.io-client est requise. Pour envoyer une demande (commande) Utilisez la méthode emit , le résultat (ou une erreur) sera renvoyé dans Socket.io ACK Rappel. Pour écouter les messages du serveur à utiliser on la méthode.
const io = require ( 'socket.io-client' )
// Use https or wss in production.
let url = 'ws://localhost:8000/chat-service'
let userName = 'user' // for example and debug
let token = 'token' // auth token
let query = `userName= ${ userName } &token= ${ token } `
let opts = { query }
// Connect to a server.
let socket = io . connect ( url , opts )
// Rooms messages handler (own messages are here too).
socket . on ( 'roomMessage' , ( room , msg ) => {
console . log ( ` ${ msg . author } : ${ msg . textMessage } ` )
} )
// Auth success handler.
socket . on ( 'loginConfirmed' , userName => {
// Join room named 'default'.
socket . emit ( 'roomJoin' , 'default' , ( error , data ) => {
// Check for a command error.
if ( error ) { return }
// Now we will receive 'default' room messages in 'roomMessage' handler.
// Now we can also send a message to 'default' room:
socket . emit ( 'roomMessage' , 'default' , { textMessage : 'Hello!' } )
} )
} )
// Auth error handler.
socket . on ( 'loginRejected' , error => {
console . error ( error )
} ) Il s'agit d'un code exécutable, les fichiers sont dans l' example de répertoire.
Il est possible d'utiliser d'autres transports autres que Socket.io. Il y a une preuve de transport de concept, qui utilise une connexion WebSocket avec une couche API API minimale WS-Messaging et un simple courtier Emitte-Pubsub comme abstraction de fanout de messagerie backend.
Voici les principales choses qu'un transport doit permettre de faire:
Envoyez des messages d'un serveur vers des groupes de clients (en fonction des critères de correspondance complète d'une seule chaîne, aka Messaging Room).
Implémentez la communication de demande d'un client vers un serveur.
Implémentez une sorte de connexion persistante (ou sémantiquement équivalent), il est nécessaire pour un suivi de présence.
Le service de chat utilise Redis comme magasin partagé avec persistance. Dans une application réelle, certaines de ces informations peuvent être nécessaires par d'autres services, mais il n'est pas pratique de réimplémenter pleinement le magasin d'État. Une meilleure approche alternative consiste à utiliser des crochets. Par exemple, pour enregistrer tous les messages de la salle dans une autre base de données, un crochet roomMessageAfter peut être utilisé. ServiceAPI peut également être exposé via des bus de messagerie backend vers d'autres serveurs internes.
Dans des circonstances normales, toutes les erreurs qui sont renvoyées à un utilisateur de service (via les réponses de la demande, les messages loginConfirmed ou loginRejected ) sont des instances de ChatServiceError . Toutes les autres erreurs indiquent un bogue de programme ou un échec dans une infrastructure de service. Pour activer la journalisation de débogage de ces erreurs, utilisez export NODE_DEBUG=ChatService . La bibliothèque utilise la mise en œuvre de Bluebird ^3.0.0 promet, afin d'activer les traces de pile longues, utilisez export BLUEBIRD_DEBUG=1 . Il est fortement recommandé d'utiliser des versions Promise of API pour les crochets et les sous-classes ChatServiceError pour retourner les erreurs personnalisées de crochets.
L'API côté serveur et la documentation RPC sont disponibles en ligne.
Le service résume complètement un concept de connexion à partir d'un concept d'utilisateur, donc un seul utilisateur peut avoir plus d'une connexion (y compris les connexions sur différents nœuds). Pour la présence de l'utilisateur, le nombre de prises jointes doit être juste supérieure à zéro. Toutes les API conçues pour fonctionner au niveau de l'utilisateur, en gérant plusieurs connexions de l'utilisateur de manière transparente.
Les connexions sont complètement indépendantes, aucune prise en charge côté client supplémentaire n'est requise. Mais il existe des messages et des commandes d'informations qui peuvent être utilisées pour obtenir des informations sur les connexions d'autres utilisateurs. Il est possible de réaliser des modèles de synchronisation côté client, comme garder toutes les connexions à joindre dans les mêmes pièces.
Chaque pièce a un système d'autorisations. Il y a un seul utilisateur du propriétaire, qui a tous les privilèges d'administrateur et peut attribuer des utilisateurs au groupe des administrateurs. Les administrateurs peuvent gérer les autorisations d'accès des autres utilisateurs. Deux modes sont pris en charge: la liste noire et la liste blanche. Après les listes d'accès / modifications du mode, le service supprime automatiquement les utilisateurs qui ont perdu une autorisation d'accès.
Si les options de enableRoomsManagement sont activées, les utilisateurs peuvent créer des pièces via la commande roomCreate . Le créateur d'une chambre sera son propriétaire et peut également le supprimer via la commande roomDelete .
Avant que les crochets ne puissent être utilisés pour implémenter des systèmes d'autorisations supplémentaires.
Lorsqu'un utilisateur envoie un message de pièce, en réponse RPC, l' id de message est renvoyé. Cela signifie que le message a été enregistré dans un magasin (dans un tampon circulaire d'ajout uniquement de la structure). Les ID de message de pièce sont une séquence à partir de 1 , qui augmente de un pour chaque message envoyé avec succès dans la pièce. Un client peut toujours vérifier le dernier ID de message de pièce via la commande roomHistoryInfo et utiliser la commande roomHistoryGet pour obtenir des messages manquants. Cette approche garantit qu'un message peut être reçu, sauf s'il est supprimé en raison de la rotation.
Par défaut, un client peut envoyer des messages limités à un seul {textMessage: 'Some string'} . Pour activer le format de messages personnalisés, fournissez des crochets directMessagesChecker ou roomMessagesChecker . Lorsqu'un crochet se résout, un format de message est accepté. Les messages peuvent être des données arbitraires avec quelques restrictions. Le niveau supérieur doit être un Object , sans champs timestamp , author ou aux champs id (le service remplira ces champs avant d'envoyer des messages). Les niveaux imbriqués peuvent inclure des types de données arbitraires (même binaires), mais aucun objet imbriqué avec un type champ défini sur 'Buffer' (utilisé pour les manipulations de données binaires).
Chaque commande utilisateur prend en charge avant et après l'ajout de crochets, et les crochets de connexion / de déconnexion du client sont également pris en charge. La commande et les crochets sont exécutés séquentiellement: Avant Hook - Commande - après Hook (il sera également appelé sur les erreurs de commande). La terminaison de séquence avant les crochets est possible. Les clients peuvent envoyer des arguments de commande supplémentaires, les crochets peuvent les lire et répondre avec des arguments supplémentaires.
Pour exécuter un serveur de commandes utilisateur, execUserCommand est fourni. Il existe également d'autres méthodes du côté serveur uniquement fournies par ServiceAPI et TransportInterface . Recherchez certains cas de personnalisation dans des exemples de personnalisation.
Le service maintient les données de présence et de connexion des utilisateurs dans un magasin, qui peuvent être persistants ou partagés. Ainsi, si une instance est incorrecte (sans appeler ou attendre la finition de la méthode de close ) ou perdre une connexion entièrement réseau à un magasin, les données de présence deviendront incorrectes. Pour corriger ce cas, une méthode instanceRecovery est fournie.
Il existe également des cas plus subtils concernant la cohérence des données dépendantes de la connexion. Les instances de communication de transport et les instances de stockage peuvent ressentir divers types d'échecs de réseau, de logiciels ou de matériel. Dans certains cas de bord (comme l'opération sur plusieurs utilisateurs), de telles échecs peuvent provoquer des incohérences (pour la plupart des erreurs seront renvoyées aux émetteurs de la commande). Ces événements sont signalés via un émetteur d'instance (comme l'événement storeConsistencyFailure ), et les données peuvent être synchronisées via des méthodes RecoveryAPI .
Par défaut, chaque utilisateur est supposé avoir une connexion unique (nom d'utilisateur). Au lieu de gérer la génération de noms, une intégration avec un transport séparé peut être utilisée (ou une connexion multiplexée, par exemple un autre espace de noms socket.io). Les messages de la chambre peuvent être transmis à partir de roomMessage après un crochet à un transport, qui est accessible sans connexion. Et vice versa, certaines commandes de service peuvent être exécutées par des utilisateurs anonymes via execUserCommand avec l'option de permis de contournement activée.
Un roomMessage après le crochet peut également être utilisé pour transmettre les messages d'une pièce à l'autre. Ainsi, les chambres peuvent être utilisées pour l'agrégation des messages dans une autre pièce. Étant donné que les crochets ne sont que des fonctions et ont un accès complet au contenu des messages, il permet d'implémenter les règles de transfert basées sur le contenu arbitraire. Y compris la mise en œuvre de systèmes avec des flux spécifiques utilisateur (client) hautement personnalisés.
Par défaut, il n'y a aucun moyen pour les autres utilisateurs de connaître le nombre et les types de connexions utilisateur joints à une pièce. Ces informations peuvent être transmises, par exemple dans une chaîne de requête, puis enregistrées via un crochet de connexion. L'annonce peut être faite dans les crochets onJoin et onLeave , en utilisant la méthode sendToChannel directement directement. Des informations supplémentaires concernant les types de périphériques jointes doivent également être envoyées à partir de roomGetAccessList après Hook (lorsque le nom de la liste est égal à 'userlist' ).
Il n'y a pas d'opération de suppression ou de modification, car ils feront des incohérences dans l'histoire d'une pièce. Une alternative commune pour la suppression et l'édition consiste à utiliser des messages de pièce avec une signification particulière que les clients utiliseront pour masquer ou modifier les messages.
Si vous rencontrez un bogue dans ce package, veuillez soumettre un rapport de bogue aux problèmes de repo GitHub.
Les PR sont également acceptés.
Mit