npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

moleculer-sc

v0.9.0

Published

API Gateway service for Moleculer framework using SocketCluster

Downloads

355

Readme

LOGO GitHub license npm

moleculer-sc

An API Gateway service for Moleculer framework using SocketCluster

What is SocketCluster? SocketCluster is an open source real-time framework for Node.js. It supports both direct client-server communication and group communication via pub/sub channels. It is designed to easily scale to any number of processes/hosts and is ideal for building chat systems.

Features

  • Call moleculer actions by emiting SocketCluster events.
  • Support SocketCluster authorization (sc socket.authToken => moleculer ctx.meta.user)
  • Whitelist.

Install

$ npm install moleculer-sc

Usage

SocketCluster is a fast, highly scalable HTTP + WebSockets server environment which lets you build multi-process real-time systems that make use of all CPU cores on a machine/instance.

Before you start, you have to create a SocketCluster project, and write the code in worker.js. (See /examples/simple)

Handle socket events

Create your own SocketCluster Gateway service.

// SocketCluster worker.js
const SCWorker = require('socketcluster/scworker');
const { ServiceBroker } = require('moleculer')
const SocketClusterService = require('moleculer-sc')
class Worker extends SCWorker {
  run() {
    console.log('   >> Worker PID:', process.pid);
    var environment = this.options.environment;
    let broker = new ServiceBroker({
      logger: console
    })
    broker.createService({
      name:'sc-gw',
      mixins:[SCService(this)], //create SCService with worker.
    })
    broker.start()
  }
}

By default, moleculer-sc will handle the call event which proxy to moleculer's broker.call Examples:

  • Call test.hello action: socket.emit('call',{action:'test.hello'}, callback)
  • Call math.add action with params: socket.emit('call',{action:'math.add', params:{a:25, b:13}}, callback)
  • Get health info of node: socket.emit('call',{ action: '$node.health' }, callback)
  • List all actions: socket.emit('call', { action: '$node.list'}, callback)

Whitelist

If you don’t want to public all actions, you can filter them with whitelist option. You can use match strings or regexp in list.

broker.createService({
  name:'sc-gw', // SocketCluster GateWay
  mixins:[SocketClusterService(worker)],
  settings: {
    routes: [{
      event: "call",
      whitelist: [
        // Access to any actions in 'posts' service
        "posts.*",
        // Access to call only the `users.list` action
        "users.list",
        // Access to any actions in 'math' service
        /^math\.\w+$/
      ]
    }]
    }
})

Multiple routes

You can create multiple routes with different prefix, whitelist, alias, calling options & authorization.

broker.createService({
  mixins: [SocketClusterService(worker)],
  settings: {
    routes: [
      {
        event: "adminCall",
        whitelist: [
          "$node.*",
          "users.*",
        ]
      },
      {
        event: "call",
        whitelist: [
          "posts.*",
          "math.*",
        ]
      }
    ]
  }
});

Authorization

You can implement authorization. For this you need to do 2 things.

  1. Define the authorization handler in SocketCluster.
  2. Rewrite the getMeta method of sc-gw service. (Optional)

Example authorization:

socket.on('login', function (credentials, respond) {
  broker.call('v1.account.login', data).then(res=>{
    socket.setAuthToken(res) //success
    callback(null,res)
  }).catch(err=>{
    const errObj = _.pick(err, ["name", "message", "code", "type", "data"])
    callback(errObj)
  }) //error
})

For convenience, we did this for you. You could set a handler with login type:

broker.createService({
  mixins: [SocketClusterService(worker)],
  settings: {
    routes: [
      {
        event: "login",
        type:'login', //Set route type to login, when calling success, you are logged in!
        whitelist: [
          "login.password",
          "login.google",
          "login.github"
        ]
      }
    ]
  }
});
// Add an handler service
broker.createService({
  name:'login',
  actions: {
    password(ctx) {
      if(ctx.params.user == 'tiaod' && ctx.params.password == 'pass'){
        return {id: 'tiaod'}
      }else{
        throw new Error('UNAUTH')
      }
    }
  }
})

Then:

socket.emit('login', {action:'login.password', params: {user: 'tiaod', password:'pass'}}, function(err, data){
  console.log('call login.passoword:',data)
  console.log(socket.authToken.id == 'tiaod') //true
})

Also you could overwrite the getMeta method to add more addition meta info. The default getMeta method is:

getMeta(socket){
  return {
    user: socket.authToken
  }
}

Example to add more additional info:

broker.createService({
  name:'sc-gw',
  mixins:[SocketClusterService(worker)],
  methods:{
    getMeta(socket){ //construct the meta object.
      return {
        user: socket.authToken,
        socketId: socket.id
      }
    }
  }
})

Calling options

The route has a callOptions property which is passed to broker.call. So you can set timeout, retryCount or fallbackResponse options for routes.

broker.createService({
  mixins: [SocketClusterService(worker)],
  settings: {
    routes: [{
      event:'call',
      whitelist: [
        "posts.*",
        "math.*",
      ],
      callOptions: {
        timeout: 500,
        retryCount: 0,
        fallbackResponse(ctx, err) { ... }
      }
    }]		
  }
});

Note: If you provie a meta field here, it replace the getMeta method's result.

broker.createService({
  mixins: [SocketClusterService(worker)],
  settings: {
    routes: [{
      event:'call',
      callOptions: {
        meta: { abc:123 }
      }
    }]		
  },
  methods:{
    getMeta(socket){
      return {
        user: socket.authToken
      } //This will be replaced by callOptions.meta
    }
  }
});

Access control lists

If you want to do a role-based access control, you can do it on SocketCluster way. Here is an example using node_acl:

let acl = require('acl')
acl = new acl(new acl.memoryBackend())
acl.allow('admin', 'math', 'add') // allow admin to call
acl.addUserRoles('user id here', 'admin')
scServer.addMiddleware(scServer.MIDDLEWARE_EMIT,
  async function (req, next) {
    if(!data || typeof data.action !== 'string') next(new Error('invaild request'))
    let [service, action] = req.data.action.split('.', 2)
    if (!await acl.isAllowed(req.socket.authToken.id, service, action)) {
      next(); // Allow
    } else {
      var err = MyCustomEmitFailedError(req.socket.id + ' is not allowed to call action ' + req.event);
      next(err); // Block
      // next(true); // Passing true to next() blocks quietly (without raising a warning on the server-side)
    }
  }
);

Error parser

You can rewrite global-level error handler:

In handler, you must call the respond. Otherwise, the request is unhandled.

broker.createService({
  mixins: [SocketClusterService(worker)],
  settings: {
    routes: [{
      event:'call',
      callOptions: {
        meta: { abc:123 }
      }
    }]		
  },
  methods:{
    onError(err, respond){ //This is the default handler
      const errObj = _.pick(err, ["name", "message", "code", "type", "data"]);
      return respond(errObj)
    }
  }
});

SocketCluster transporter

You can also use SocketCluster as moleculer's transporter!

// worker.js
const { ServiceBroker } = require('moleculer')
const SCTransporter = require('moleculer-sc/transporter')
let broker = new ServiceBroker({
  nodeID: "node-1",
  logger: console,
  transporter: new SCTransporter({
    exchange:this.exchange
  })
})
broker.createService({
  name:'math',
  actions: {
    add(ctx) {
      return Number(ctx.params.a) + Number(ctx.params.b);
    }
  }
})
broker.start().then(()=>{
  console.log('broker1 started!')
})
// external-service.js
const { ServiceBroker } = require('moleculer')
const SCTransporter = require('moleculer-sc/transporter')
let broker2 = new ServiceBroker({
  nodeID: "node-2",
  logger: console,
  transporter: new SCTransporter({
    hostname:'localhost',
    port:8000
  })
})
broker2.start()
  .then(() => broker2.call("math.add", { a: 5, b: 3 }))
  .then(res => console.log("5 + 3 =", res))

Warning: You should add a SocketCluster middleware to apply access control with MOL. channel prefix.

You can also pass an socket object or SCExchange instance:

new SCTransporter({
  socket:socket
})
// or
new SCTransporter({
  exchange:exchange
})

Publish to scChannel

Just do:

let data = {
  hello: 'world'
}
broker.call('sc-gw.publish',{
  topic: 'your.topic.here',
  data: data
})

Change logs

0.9.0 - Add publish action. 0.8.1 - Fix getMeta error. 0.8.0 - Add login handler type.

0.7.0 - Add onError handler

0.6.1 - You can pass socket or exchange object to SCTransporter now.

0.6.0 - Breaking change: Don't pass worker in settings anymore, you should pass the worker when initerlized the service.

// old:
broker.createService({
  name:'sc-gw', // SocketCluster GateWay
  mixins:[SCService], //This will not work anymore
  settings:{
    worker,
  }
})
// new:
broker.createService({
  name:'sc-gw',
  mixins:[SCService(worker)], //create SCService with worker.
})

This is because the settings is also obtainable on remote nodes, it is transferred during service discovering, which will cause a TypeError: Converting circular structure to JSON when serializing it.

0.5.0 - Add transporter.

0.4.0 - Add multiple routes support.

0.3.0 - Doesn't integrate node_acl anymore. If you need access control lists, you can do it on socketcluster side.