Core components
Networking & multiplayer infra...
FeathersJS application & services
22 min
overview the feathersjs application & services component forms the foundation of the ir engine's multiplayer infrastructure, providing a structured framework for building and organizing server side functionality it enables the creation of modular, reusable services that handle specific aspects of the multiplayer system, such as user management, instance tracking, and real time communication by leveraging feathersjs's architecture, the ir engine achieves a clean separation of concerns, standardized api patterns, and robust real time capabilities this chapter explores the implementation, structure, and benefits of the feathersjs framework within the ir engine core concepts application structure the feathersjs application provides a structured environment for server side code application instance the central app object that coordinates all functionality service registration a mechanism for adding services to the application middleware functions that process requests and responses configuration settings that control application behavior hooks logic that runs before, after, or during service method execution this structure creates a consistent and organized approach to server development service oriented architecture services are the primary building blocks of a feathersjs application resource encapsulation each service manages a specific type of data or functionality standard methods consistent interfaces ( find , get , create , update , patch , remove ) data access abstraction over database operations business logic implementation of domain specific rules event emission automatic events when data changes this service oriented approach promotes modularity and reusability real time capabilities feathersjs includes built in support for real time communication websocket integration seamless support for persistent connections event broadcasting automatic publication of service events channels selective distribution of events to specific clients real time updates instant notification of data changes bi directional communication both server to client and client to server messaging these capabilities are essential for responsive multiplayer experiences implementation application initialization the feathersjs application is initialized during server startup // simplified from src/start ts import { createfeatherskoaapp } from '@ir engine/server core/src/createapp'; import { servermode } from '@ir engine/server core/src/serverstate'; export const start = async () => { // create the feathers application with koa as the http framework const app = await createfeatherskoaapp(servermode instance, { // configuration options logger createlogger('instance server'), authentication { entity 'identity provider', service 'identity provider', authstrategies \['jwt'] }, paginate { default 10, max 100 } }); // configure services app configure(configureservices); // configure channels for real time events app configure(configurechannels); // configure middleware app configure(configuremiddleware); // start listening for connections const server = app listen(app get('port')); // log successful startup app get('logger') info(`server started on port ${app get('port')}`); return app; }; this function creates a feathersjs application using koa as the http framework configures the application with appropriate settings sets up services, channels, and middleware starts the server listening for connections returns the initialized application service definition services are defined to handle specific resources or functionality // simplified service definition import { servicemethods } from '@feathersjs/feathers'; import { application } from ' /declarations'; // interface for the service methods interface userservice extends servicemethods\<any> { // additional custom methods can be defined here } // implementation of the service class userserviceimpl implements userservice { app application; options any; constructor(options = {}, app application) { this options = options; this app = app; } // find multiple users async find(params? any) promise\<any> { const { query = {} } = params || {}; // database query logic const users = await this app get('knex')('user') where(query) select(' '); return { total users length, limit query $limit || 10, skip query $skip || 0, data users }; } // get a single user by id async get(id string, params? any) promise\<any> { const user = await this app get('knex')('user') where({ id }) first(); if (!user) { throw new error('user not found'); } return user; } // create a new user async create(data any, params? any) promise\<any> { // validation logic if (!data email) { throw new error('email is required'); } // create the user const \[id] = await this app get('knex')('user') insert(data) returning('id'); // return the created user return this get(id, params); } // update a user (replace) async update(id string, data any, params? any) promise\<any> { // update logic await this app get('knex')('user') where({ id }) update(data); // return the updated user return this get(id, params); } // patch a user (partial update) async patch(id string, data any, params? any) promise\<any> { // patch logic await this app get('knex')('user') where({ id }) update(data); // return the patched user return this get(id, params); } // remove a user async remove(id string, params? any) promise\<any> { // get the user before removal const user = await this get(id, params); // remove the user await this app get('knex')('user') where({ id }) delete(); // return the removed user return user; } } // function to register the service with the application export default function(app application) void { // create an instance of the service const service = new userserviceimpl({}, app); // register the service on a path app use('/users', service); // get the registered service const userservice = app service('users'); // set up hooks userservice hooks({ before { all \[/ hooks that run before all methods /], find \[/ hooks that run before find /], get \[/ hooks that run before get /], create \[/ hooks that run before create /], update \[/ hooks that run before update /], patch \[/ hooks that run before patch /], remove \[/ hooks that run before remove /] }, after { all \[/ hooks that run after all methods /], // other method hooks }, error { all \[/ hooks that run on errors /], // other method hooks } }); } this code defines a service interface extending feathersjs's servicemethods implements the standard service methods ( find , get , create , etc ) includes database interaction logic for each method registers the service with the application on a specific path sets up hooks for additional processing before and after method execution hook implementation hooks provide a way to add logic before, after, or during service method execution // example authentication hook import { hook, hookcontext } from '@feathersjs/feathers'; // hook to authenticate users export const authenticate hook = async (context hookcontext) => { // get the authentication service const { app, params } = context; const authservice = app service('authentication'); // check if the request includes an authentication token if (!params headers? authorization) { throw new error('authentication token missing'); } try { // verify the token const authresult = await authservice strategies jwt authenticate({ accesstoken params headers authorization replace('bearer ', '') }, {}); // add the authenticated user to the context context params user = authresult user; // continue with the request return context; } catch (error) { throw new error('invalid authentication token'); } }; // example validation hook export const validateuserdata hook = (context hookcontext) => { const { data } = context; // check required fields if (!data email) { throw new error('email is required'); } if (!data password) { throw new error('password is required'); } // validate email format const emailregex = /^\[^\s@]+@\[^\s@]+\\ \[^\s@]+$/; if (!emailregex test(data email)) { throw new error('invalid email format'); } // continue with the request return context; }; these hooks implement specific functionality that can be applied to service methods modify the context object to affect the request or response can be combined and reused across different services provide a clean way to separate concerns like authentication and validation channel configuration channels are configured to manage real time event distribution // simplified from src/channels ts export default function(app application) void { app on('connection', (connection any) => { // when a client connects console log('new connection', connection); // on successful login connection on('login', (authresult any) => { // add this connection to the user's channel app channel(`userids/${authresult user id}`) join(connection); // if the user is connected to an instance if (connection instanceid) { // add this connection to the instance's channel app channel(`instanceids/${connection instanceid}`) join(connection); } }); // on disconnection connection on('disconnect', () => { // remove this connection from all channels app channel(app channels) leave(connection); }); }); // configure event publishing app publish((data any, context any) => { const { service, path, method, result } = context; // determine which channels should receive this event if (path === 'messages' && method === 'create') { // if this is a new message, send it to the instance channel return app channel(`instanceids/${result instanceid}`); } if (path === 'users' && (method === 'patch' || method === 'update')) { // if a user is updated, send it to that user's channel return app channel(`userids/${result id}`); } // default no publishing return null; }); } this configuration sets up event handlers for client connections, logins, and disconnections manages channel membership based on user authentication and instance association defines publishing rules to determine which clients receive which events creates a selective real time communication system service usage services are used throughout the application to access and manipulate data // example of using services in the application async function handleuserconnection(app application, userid string, instanceid string) { try { // get user information const user = await app service('users') get(userid); // get instance information const instance = await app service('instances') get(instanceid); // check if the user is banned const bans = await app service('bans') find({ query { userid userid, active true } }); if (bans total > 0) { throw new error('user is banned'); } // record the user's connection to the instance await app service('user instances') create({ userid userid, instanceid instanceid, connectedat new date() }); // notify other users in the instance await app service('messages') create({ type 'user joined', instanceid instanceid, userid userid, content `${user name} has joined the instance` }); return { user, instance }; } catch (error) { console error('error handling user connection ', error); throw error; } } this function uses multiple services to retrieve and manipulate data follows a consistent pattern for service method calls leverages the standard service methods ( get , find , create ) demonstrates how services work together to implement complex functionality service workflow the complete service workflow follows this sequence sequencediagram participant client participant app as feathersjs app participant hooks as before hooks participant service participant db as database participant afterhooks as after hooks client >>app request (e g , post /users) app >>hooks run before hooks alt hook validation fails hooks >>app throw error app >>client error response else hooks pass hooks >>service pass validated request service >>db database operation db >>service database result service >>afterhooks run after hooks afterhooks >>app final result alt real time event app >>app publish to channels app >>client event (websocket) end app >>client http response end this diagram illustrates the client sends a request to the feathersjs application before hooks run to validate and process the request if hooks pass, the service method executes the core logic the service interacts with the database after hooks process the result real time events are published to appropriate channels the response is sent back to the client integration with other components the feathersjs application integrates with several other components of the multiplayer infrastructure instance lifecycle management services manage instance lifecycle data // example of instance lifecycle integration class instanceservice { // create a new instance async create(data any, params? any) promise\<any> { // create the instance record const instance = await this app get('knex')('instance') insert({ name data name, sceneid data sceneid, maxusers data maxusers || 10, isprivate data isprivate || false, createdat new date(), status 'initializing' }) returning(' ') then(rows => rows\[0]); // request instance server allocation if (this app get('kubernetes') enabled) { await this allocateinstanceserver(instance id); } return instance; } // update instance status async updatestatus(id string, status string) promise\<any> { return this patch(id, { status }); } // end an instance async end(id string) promise\<any> { return this patch(id, { status 'ended', endedat new date() }); } } this integration provides services for creating, updating, and ending instances maintains instance state in the database coordinates with the instance lifecycle management system triggers server allocation when needed user connection and authorization services handle user authentication and authorization // example of user connection integration class authenticationservice { // verify a token async verifytoken(token string) promise\<any> { return this app service('authentication') strategies jwt authenticate({ accesstoken token }, {}); } } class instanceauthorizationservice { // check if a user is authorized for an instance async checkauthorization(userid string, instanceid string) promise\<boolean> { const instance = await this app service('instances') get(instanceid); // public instances are open to all if (!instance isprivate) { return true; } // check for explicit authorization const authorizations = await this find({ query { userid, instanceid } }); return authorizations total > 0; } } this integration provides services for authentication and authorization verifies user identity through token validation checks instance specific permissions supports the user connection process webrtc communication services coordinate with the webrtc communication system // example of webrtc integration class mediasoupservice { // create a webrtc transport async createtransport(data any) promise\<any> { const { userid, instanceid, direction } = data; // ensure the user is connected to the instance await this app service('user instances') get({ userid, instanceid }); // create the transport const transport = await handlewebrtctransportcreate({ userid, direction }); // store the transport return this app service('transports') create({ userid, instanceid, direction, transportid transport id, iceparameters transport iceparameters, icecandidates transport icecandidates, dtlsparameters transport dtlsparameters }); } } this integration provides services for managing webrtc components coordinates with the mediasoup webrtc system stores transport information for connected users ensures users are authorized for webrtc operations benefits of feathersjs the feathersjs application & services component provides several key advantages modularity organizes server functionality into discrete, focused services standardization provides consistent interfaces for data operations real time capabilities enables instant updates through websocket integration middleware support allows for request/response processing with hooks authentication includes built in support for user authentication scalability supports horizontal scaling through stateless service design extensibility makes it easy to add new functionality through additional services these benefits make feathersjs an ideal foundation for the ir engine's multiplayer infrastructure next steps with an understanding of how the application is structured and services are organized, the next chapter explores how the system manages state and actions next hyperflux state & action system docid\ sg w8mk nxqafyncepuy