Core components
Server core
Real-time communication (Primus)
24 min
overview the real time communication component is a critical element of the ir engine's server core that enables instant, bidirectional data exchange between the server and connected clients it provides a robust infrastructure for pushing updates to clients without requiring them to poll the server, creating a responsive and interactive user experience by leveraging websockets through the primus library, this component supports features like live updates, messaging, and collaborative interactions this chapter explores the implementation, configuration, and event distribution system of real time communication within the ir engine core concepts websockets websockets provide a persistent connection for bidirectional communication persistent connections maintains an open connection between client and server full duplex communication allows simultaneous data transmission in both directions low latency minimizes delay in message delivery reduced overhead eliminates the need for repeated http headers event based messaging facilitates real time event notifications this protocol creates the foundation for efficient real time communication primus primus serves as an abstraction layer for real time communication transport agnostic works with various underlying technologies (websockets, socket io, etc ) consistent api provides a unified interface regardless of the transport automatic reconnection handles connection interruptions gracefully plugin system supports extensions for additional functionality cross browser compatibility works across different browsers and environments this abstraction simplifies the implementation of real time features channels channels organize and target real time event distribution selective broadcasting directs events to specific groups of connections dynamic membership allows connections to join and leave channels context aware routing routes events based on data and context scalable distribution efficiently manages event delivery to many clients access control limits event reception to authorized connections this system ensures that events reach only the appropriate clients implementation primus configuration primus is configured in a dedicated module // simplified from src/util/primus ts import { socket } from '@feathersjs/transport commons'; import http from 'http'; import primus from 'primus'; import emitter from 'primus emitter'; import { application } from ' /declarations'; / configures primus for real time communication @param options primus configuration options @param configurer optional function to further configure primus @returns configuration function for feathers / export default function(options any = {}, configurer? function) { return function(app application) { // promise that resolves when primus is fully set up const done = new promise\<primus>((resolve) => { // get the original setup method const { setup } = app; // override the setup method to initialize primus app setup = function(this application, server http server = http createserver()) { // only create primus instance if it doesn't exist if (!this primus) { // create a new primus instance const primus = new primus(server, { transformer 'websockets', options }); // add the emitter plugin for event based communication primus plugin('emitter', emitter); // handle new connections primus on('connection', (spark) => { // a new client has connected console log('new connection established'); // handle authentication events spark on('authenticate', (data) => { // process authentication data console log('client attempting to authenticate'); }); }); // handle disconnections primus on('disconnection', (spark) => { console log('connection closed'); }); // store the primus instance on the app this primus = primus; } // call the custom configurer if provided if (typeof configurer === 'function') { configurer call(this, this primus); } // resolve the promise with the primus instance resolve(this primus); // call the original setup method return setup call(this, server); }; }); // configure feathers to use socket transport with primus app configure( socket({ done, socketkey 'primus', getparams(spark) { // extract parameters from the spark connection return spark request feathers; } }) ); }; } this function returns a configuration function for feathers overrides the application's setup method to initialize primus creates a primus instance with websockets as the transformer adds the emitter plugin for event based communication sets up handlers for connection and disconnection events configures feathers to use socket transport with primus channel configuration channels are configured to manage event distribution // simplified from src/channels ts import { hookcontext } from '@feathersjs/feathers'; import { application } from ' /declarations'; / configures channels for event distribution @param app feathers application / export default function(app application) void { if (typeof app channel !== 'function') { // channels are not available return; } / event handler for new connections / app on('connection', (connection any) => { // add the connection to the anonymous channel app channel('anonymous') join(connection); // handle authentication events connection on('authenticate', (auth any) => { // remove the connection from the anonymous channel app channel('anonymous') leave(connection); // add the connection to the authenticated channel app channel('authenticated') join(connection); // add the connection to a user specific channel app channel(`user/${auth user id}`) join(connection); // add the connection to role specific channels if (auth user roles) { auth user roles foreach((role string) => { app channel(`role/${role}`) join(connection); }); } }); }); / event publisher for service events / app publish((data any, context hookcontext) => { // get the service and method that generated the event const { service, path, method } = context; // for user specific events, publish to the user's channel if (data userid) { return app channel(`user/${data userid}`); } // for project specific events, publish to the project's channel if (data projectid) { return app channel(`project/${data projectid}`); } // for chat messages, publish to the room's channel if (path === 'messages' && data roomid) { return app channel(`room/${data roomid}`); } // default publish to the authenticated channel return app channel('authenticated'); }); } this function sets up an event handler for new connections organizes connections into channels based on authentication status creates user specific and role specific channels defines a publisher function that determines which channels receive events routes events based on the data and context of the service method application integration real time communication is integrated into the main application // simplified from src/createapp ts import { feathers } from '@feathersjs/feathers'; import { koa } from '@feathersjs/koa'; import primus from ' /util/primus'; import channels from ' /channels'; import { application } from ' /declarations'; / creates a feathers application with real time capabilities @returns configured feathers application / export const createfeatherskoaapp = async () promise\<application> => { // create the base feathers application const app = koa(feathers()); // other configurations // configure primus for real time communication app configure( primus({ transformer 'websockets', pinginterval 10000, cors { origin ' ' } }) ); // configure channels for event distribution app configure(channels); // more configurations return app; }; this function creates a feathers application with koa as the http provider configures primus with websockets as the transformer sets up channels for event distribution returns the configured application with real time capabilities service events services automatically emit events for real time updates // example of service events import { application } from ' /declarations'; / creates a new message @param app feathers application @param data message data @returns promise resolving to the created message / async function createmessage(app application, data any) promise\<any> { // create the message using the messages service const message = await app service('messages') create(data); // the service automatically emits a 'created' event // no additional code needed for real time updates return message; } / updates a message @param app feathers application @param id message id @param data message data @returns promise resolving to the updated message / async function updatemessage(app application, id string, data any) promise\<any> { // update the message using the messages service const message = await app service('messages') patch(id, data); // the service automatically emits a 'patched' event // no additional code needed for real time updates return message; } these functions use standard service methods to create or update data rely on the automatic event emission by feathers services don't require additional code for real time updates custom events custom events can be sent directly through primus // example of custom events import { application } from ' /declarations'; / sends a custom event to all connected clients @param app feathers application @param eventname event name @param data event data / function broadcastevent(app application, eventname string, data any) void { // check if primus is available if (!app primus) { console error('primus not initialized'); return; } // send the event to all connected clients app primus foreach((spark any) => { spark send(eventname, data); }); } / sends a custom event to a specific client @param app feathers application @param sparkid client connection id @param eventname event name @param data event data / function sendeventtoclient(app application, sparkid string, eventname string, data any) void { // check if primus is available if (!app primus) { console error('primus not initialized'); return; } // find the client connection const spark = app primus spark(sparkid); if (spark) { // send the event to the client spark send(eventname, data); } else { console error(`client ${sparkid} not found`); } } these functions access the primus instance directly send custom events to clients provide more control over event distribution than service events real time workflow the complete real time workflow follows this sequence sequencediagram participant client1 as client (sender) participant server as feathers server participant service as service participant publisher as event publisher participant channels as channels participant client2 as client (receiver) client1 >>server connect via websocket server >>channels add to 'anonymous' channel client1 >>server authenticate server >>channels move to 'authenticated' channel server >>channels add to user specific channel client2 >>server connect via websocket server >>channels add to 'anonymous' channel client2 >>server authenticate server >>channels move to 'authenticated' channel server >>channels add to user specific channel client1 >>server service('messages') create(data) server >>service create message service >>service save to database service >>server return created message server >>publisher emit 'created' event publisher >>channels determine target channels channels >>publisher return target connections publisher >>client1 send 'messages created' event publisher >>client2 send 'messages created' event client1 >>client1 update ui client2 >>client2 update ui this diagram illustrates clients connect to the server via websockets clients authenticate and join appropriate channels a client creates a new message through a service the service emits a 'created' event the publisher determines which channels should receive the event the event is sent to all clients in the target channels clients update their ui based on the event client side integration clients connect to the server and listen for events // example of client side integration import io from 'socket io client'; import feathers from '@feathersjs/feathers'; import socketio from '@feathersjs/socketio client'; // create a feathers client const client = feathers(); // connect to the server using socket io const socket = io('http //localhost 3030'); client configure(socketio(socket)); // get the messages service const messagesservice = client service('messages'); // listen for real time events messagesservice on('created', (message) => { console log('new message created ', message); // update ui with the new message }); messagesservice on('updated', (message) => { console log('message updated ', message); // update ui with the updated message }); messagesservice on('patched', (message) => { console log('message patched ', message); // update ui with the patched message }); messagesservice on('removed', (message) => { console log('message removed ', message); // remove the message from ui }); // create a new message async function createmessage(text) { try { const message = await messagesservice create({ text }); console log('message created ', message); } catch (error) { console error('error creating message ', error); } } // authenticate with the server async function login(email, password) { try { const response = await client authenticate({ strategy 'local', email, password }); console log('authenticated ', response); return response user; } catch (error) { console error('authentication error ', error); } } this code creates a feathers client connects to the server using socket io gets a reference to the messages service sets up event listeners for real time updates provides functions for creating messages and authenticating integration with other components the real time communication system integrates with several other components of the server core services services emit events for real time updates // example of service integration import { servicemethods } from '@feathersjs/feathers'; import { application } from ' / /declarations'; / chat message service / export class messageservice implements servicemethods\<any> { app application; constructor(app application) { this app = app; } / creates a new message @param data message data @param params request parameters @returns promise resolving to the created message / async create(data any, params? any) promise\<any> { // create the message in the database const message = { id date now() tostring(), text data text, userid params user id, roomid data roomid, createdat new date() }; // store the message const db = this app get('knexclient'); await db('messages') insert(message); // the service automatically emits a 'created' event // which will be distributed to the appropriate channels return message; } // other service methods } this integration leverages the automatic event emission by feathers services relies on the channel system to distribute events enables real time updates without additional code authentication & authorization authentication affects channel membership // example of authentication integration app on('connection', (connection) => { // add to anonymous channel initially app channel('anonymous') join(connection); connection on('authenticate', (auth) => { // remove from anonymous channel app channel('anonymous') leave(connection); // add to authenticated channel app channel('authenticated') join(connection); // add to user specific channel app channel(`user/${auth user id}`) join(connection); // add to channels based on user permissions auth user permissions foreach(permission => { app channel(`permission/${permission}`) join(connection); }); }); }); this integration updates channel membership based on authentication status creates user specific channels for targeted updates uses permission based channels for authorization ensures events are only sent to authorized clients hooks hooks can modify event distribution // example of hook integration import { hookcontext } from '@feathersjs/feathers'; / hook to customize event distribution @param context hook context @returns modified context / export const customizeeventdistribution = (context hookcontext) => { // only run after successful service method execution if (context type !== 'after') { return context; } // add custom dispatch information context dispatch = context dispatch || {}; context dispatch customdata = 'this will be included in the event'; // specify which channels should receive this event context result channels = \[`room/${context data roomid}`]; return context; }; this integration modifies the event data before distribution specifies custom channels for event distribution enhances events with additional information provides fine grained control over real time updates benefits of real time communication the real time communication component provides several key advantages responsiveness delivers updates to clients instantly without polling efficiency reduces server load by eliminating unnecessary requests interactivity enables collaborative features and live interactions flexibility supports various communication patterns (broadcasts, direct messages, etc ) scalability efficiently manages connections and event distribution integration works seamlessly with the service oriented architecture consistency ensures all clients have the latest data these benefits make real time communication an essential component for creating dynamic and interactive applications conclusion the real time communication component provides a robust foundation for instant, bidirectional data exchange between the server and clients by leveraging websockets through primus and integrating with the service oriented architecture of feathersjs, it enables a wide range of interactive features while maintaining efficiency and scalability this concludes our exploration of the ir engine's server core we've covered the key components that make up this powerful backend system, from application configuration to real time communication each component plays a vital role in creating a flexible, secure, and responsive server that can support complex applications