api arch based on websocket Ru
Interaction between two nodes is organized by Event-Action principle. There exists a central node “Server” and its dependent nodes “Client”. Each node has its own actions and events – action with a specific “Client” on the server “Server” calls an event on the client side. This is also true the other way around. Actions and events are matched by their equivalent class name.
Action - can be called in a code and has a callback function. That is, when a node performs an action in relation to another node, then it can process the response to this action.
Event - unlike an action, cannot be called from the outside and is executed only when “action” is performed on the node. Does not have a callback function.
!NB Use base classes, provided in the library, to create your actions and events. Inherit error classes in the controllers to optimize their handling.
Install module using: npm install ws-core-api
server.js
const coreAPI = require('../lib/ws-core-api');
const { Server } = coreAPI;
(async () => {
const server = new Server(3000);
await server.init();
})();
client.js
const coreAPI = require('../lib/ws-core-api');
const { Client } = coreAPI;
(async () => {
const client = new Client('ws://localhost:3000');
await client.init();
})();
This example demonstrates creating server and a client without adding actions and events
Library structure:
- Server
- Client
- ActionController
- EventController
- Errors
- ActionControllerError
- EventControllerError
- Utils
- DataTransformer
- logger
The class that implements the operation of the "Server" node on the network.
Accepts basic configuration parameters
const server = new Server(port, {
authTimeout,
connectionsLimit,
})
args
port – listening port for incoming connections
authTimeout – time, which client can spend without authorization after connection was established. By default – 60000ms (1 min)
connectionsLimit – max number of clients. By default - 1024
return
Server
instance
Instance methods
Starts port listening
server.init();
return
Promise<void>
Adds action for the server
server.addAction(Action)
args
Action - class, inherited from ActionController
return
undefined
Adds an incoming client event handler for the server
server.addEvent(Event)
args
Event - class, inherited from EventController
return
undefined
Sets up client authorization handler. fn function will process all incoming data from the client until its authorization. Function fn requirements:
- Must be async
- Accept the websocket object of the incoming connection as an argument
- Boolean function ( returns true/false upon execution)
server.setAuthHandler(fn);
args
fn - async function
return
undefined
Sets a function that assigns names to clients for their latter referral. fn function requirements:
- Must be async
- Accept the websocket object of the incoming connection as an argument
- Returns string upon its execution
server.setClientNameGetter(fn);
args
fn - async function
return
undefined
Sets an error handling function. Function has to take in error object as its argument server.setErrorHandler(fn)
args
fn - function (error) {...}
return
undefined
Returns a server client by its name. Functionality of the client will be described further
server.getClient(name)
args
name - string. assigned client name
return
ServerClient
instance
Connection class on the “Server” side. Calls “actions” on the server by incoming data from a client and has an only method – do()
do(actionName, data) - Function, that performs an action on a client.
args
actionName - string. Action class name
data - object. An object that contains data for an “action”
return
Promise<void>
The class that implements the operation of the "Client" node on the network.
Accepts base configuration parameters
const client = new Client(url, {
reconnectTimeout,
reconnectable,
})
args
url - port, listening for incoming connections
reconnectTimeout - time after connection disruption, after which client will try to reconnect. By default - 2000ms (2 s)
reconnectable - boolean. Flag, that defines reconnect. By default true.
return
Client
instance
Instance methods
Connection initialization to the server
server.init();
return
Promise<void>
Sets up authorization function. This function handles all incoming data from the server. fn function requirements:
- Must be async
- Accept the websocket object of the incoming connection as an argument
- Boolean function ( returns true/false upon execution)
client.setAuthAction(fn);
args
fn - async function
return
undefined
Adds action to the server
server.addAction(Action)
args
Action - class, inherited from ActionController
return
undefined
Adds incoming event handler from client to server
server.addEvent(Event)
args
Event - class, inherited from EventController
return
undefined
Calls client added action with its data
client.do(actionName, data)
args
actionName - string. Name of action class
data - object(optional). Object that contains data for "action"
return
Promise<void>
Base class for actions, executed by the node. All actions must be inherited from this class. Child classes must implement method async execute(...params) that defines action logic and returns data that will be transmitted. Data, returned by the "event" on the other side of connection can be processe by the async callback(data) function. Example:
class Ping extends ActionController {
async execute(serverTime) {
return { serverTime }
}
async callback(data) {
console.log(`Client time: ${data.time});
}
}
Upon execution of an action, it is expected that you have a corresponding event on the receiving side of connection. If such event does not exist, unknown event error will be thrown.
Event class that processes data from action. All event classes must be inherited from EventController. Child classes must define async execute(data) method that proceeses incoming data. Example:
class Ping extends EventController {
async execute(data) {
console.log(`Server time: ${data.serverTime}`);
return { time: new Date().getTime() };
}
}
Data, returned by execute
, will be sent to callback function
Library object that contains 2 base error classes: ActionControllerError and EventControllerError. Create your own error classes for your actions and events - it will simplify debugging.
Object that contains library utilities.
DataTransformer - class that encodes/decodes transmitting information. Has two methods encode(json) and decode(string)
logger - built-in logging mechanism with the use of chulk library for output formatting. Function, that accepts argument - AREA
scope and returns an object with 4 functions: debug(), info(), warn(), err()
. Logger outputs information into console in the following format:
[hh:mm:ss:ms] WARN module: <SCOPE> <PASSED_DATA>
module: <SCOPE>
will be skipped if upon logger intialization loggger scope was not sent. Logger has an increased depth of js-objects insepction.
Example of "Server" node.
// server.js
const coreAPI = require('../lib/ws-core-api');
const { Server, Utils, ActionController, EventController, Errors } = coreAPI;
const { DataTransformer, logger }= Utils;
const log = logger('Server');
const authFunction = async (client) => {
return new Promise((resolve, reject) => {
client.on('message', (message) => {
const data = DataTransformer.decode(message);
if (data.login && data.password) {
resolve();
} else {
reject();
}
})
})
.then(() => {
client.send(DataTransformer.encode({ success: true, time: new Date().getTime() }));
return true;
})
.catch(() => {
client.send(DataTransformer.encode({ success: false }));
return false;
});
};
class GetInfo extends EventController {
async execute() {
return { time: new Date().getTime() };
}
}
class Ping extends ActionController {
async execute() {
return {};
}
async callback(data) {
log.debug(data.response);
}
}
(async () => {
const server = new Server(3000, {
authTimeout: 15000,
});
server.setAuthHandler(authFunction);
server.addEvent(GetInfo);
server.addAction(Ping);
await server.init();
// example of action executing
setTimeout(async () => {
try {
await server.getClient('client-0').do('Ping');
} catch (e) {
log.err(e);
}
}, 3000);
})();
Example "Client" node.
// client.js
const coreAPI = require('../lib/ws-core-api');
const { Client, EventController, ActionController, Utils } = coreAPI;
const { DataTransformer, logger } = Utils;
const log = logger('Client');
const authFunction = async (ws) => {
return new Promise((resolve, reject) => {
ws.send(DataTransformer.encode({login: 'client', password: 'qwerty'}))
ws.on('message', (message) => {
const data = DataTransformer.decode(message);
if (data.success) {
resolve();
} else {
reject();
}
})
}).then(() => {
return true;
}).catch(() => {
return false;
});
};
class GetInfo extends ActionController {
async execute() {
return {};
}
async callback(data) {
log.info(data);
}
}
class Ping extends EventController {
async execute() {
log.debug('called ping event')
return { response: 'pong' };
}
}
(async () => {
const client = new Client('ws://localhost:3000');
client.setAuthAction(authFunction);
client.addAction(GetInfo);
client.addEvent(Ping);
await client.init();
setTimeout(async () => {
await client.do('GetInfo');
}, 800);
})();
0.0.2 – bug fixes and documenation
0.0.1 – Base architecture
Copyright (c) 2020 kirill.feofanov
Licensed under the MIT license.