0.0.2 • Published 3 years ago

ws-core-api v0.0.2

Weekly downloads
13
License
-
Repository
github
Last release
3 years ago

lib-logo.png

ws-core-api

api arch based on websocket Ru

General Concept

Interaction principle

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.

Actions and Events

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.

For development

!NB Use base classes, provided in the library, to create your actions and events. Inherit error classes in the controllers to optimize their handling.

Getting started

Install module using: npm install ws-core-api

Server and Client example

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

Documentation

Library structure:

Server

The class that implements the operation of the "Server" node on the network.

Consturctor

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

init()

Starts port listening server.init();

return
Promise<void>

addAction( controller )

Adds action for the server server.addAction(Action)

args
Action - class, inherited from ActionController

return
undefined

addEvent( controller )

Adds an incoming client event handler for the server server.addEvent(Event)

args
Event - class, inherited from EventController

return
undefined

setAuthHandler( fn )

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

setClientNameGetter( fn )

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

setErrorHandler( fn )

Sets an error handling function. Function has to take in error object as its argument server.setErrorHandler(fn)

args
fn - function (error) {...}

return
undefined

getClient( name )

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

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>

Client

The class that implements the operation of the "Client" node on the network.

Constructor

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

init()

Connection initialization to the server
server.init();

return
Promise<void>

setAuthAction( fn )

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

addAction( controller )

Adds action to the server
server.addAction(Action)

args
Action - class, inherited from ActionController

return
undefined

addEvent( controller )

Adds incoming event handler from client to server server.addEvent(Event)

args
Event - class, inherited from EventController

return
undefined

do( actionName, data )

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>

ActionController

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.

EventController

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

Errors

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.

Utils

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.

Examples

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);
})();

Release History

0.0.2 – bug fixes and documenation

0.0.1 – Base architecture

License

Copyright (c) 2020 kirill.feofanov
Licensed under the MIT license.