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

awilix-socketio

v0.0.1

Published

Awilix helpers for Socket.io

Downloads

69

Readme

awilix-socketio

gitter NPM version Build codecov.io js-standard-style Twitter Follow

Awilix helpers and scope-instantiating middleware for Socket.io. 🐨

Table of Contents

Installation

npm install awilix-socketio

Requires Node v6 or above

Basic Usage

Add the middleware to your Socket.io app.

const { asClass, asValue, createContainer } = require('awilix')
const { scopePerRequest } = require('awilix-socketio')

const container = createContainer()
container.register({
  // Scoped lifetime = new instance per request
  // Imagine the TodosService needs a `user`.
  // class TodosService { constructor({ user }) { } }
  todosService: asClass(TodosService).scoped()
})

// Add the middleware, passing it your Awilix container.
// This will attach a scoped container on the socket instance.
io.use(scopePerRequest(container))

// Now you can add request-specific data to the scope.
io.use((socket, next) => {
  socket.container.register({
    user: asValue(socket.request.user) // from some authentication middleware..
  })
  return next()
})

Then in your event handlers...

const { makeInvoker } = require('awilix-socketio')

function makeAPI({ todosService }) {
  return {
    find: (socket) => {
      return todosService.find().then(result => {
        socket.emit('response', result)
      })
    }
  }
}

const api = makeInvoker(makeAPI)

// Creates middleware that will invoke `makeAPI`
// for each request, giving you a scoped instance.
io.on('find', api('find'))

Why do I need it?

You can certainly use Awilix with Socket.io without this library, but follow along and you might see why it's useful.

Imagine this simple imaginary Todos app, written in ES6:

// A totally framework-independent piece of application code.
// Nothing here is remotely associated with HTTP, Koa or anything.
class TodosService {
  constructor({ currentUser, db }) {
    // We depend on the current user!
    this.currentUser = currentUser
    this.db = db
  }

  getTodos() {
    // use your imagination ;)
    return this.db('todos').where('user', this.currentUser.id)
  }
}

// Here's a Koa API that calls the service
class TodoAPI {
  constructor({ todosService }) {
    this.todosService = todosService
  }
  getTodos(socket) {
    return this.todosService.getTodos().then(todos => socket.emit('response', todos))
  }
}

So the problem with the above is that the TodosService needs a currentUser for it to function. Let's first try solving this manually, and then with awilix-socketio.

Manual

This is how you would have to do it without Awilix at all.

import db from './db'

io.on('todos', () => {
  // We need a new instance for each request,
  // else the currentUser trick wont work.
  // this should point to the socket instance
  const api = new TodoAPI({
    todosService: new TodosService({
      db,
      // current user is request specific.
      currentUser: this.request.user
    })
  })

  // invoke the method.
  return api.getTodos(this)
})

Let's do this with Awilix instead. We'll need a bit of setup code.

import { asValue, createContainer, Lifetime } from 'awilix'

const container = createContainer()

// The `TodosService` lives in services/TodosService
container.loadModules(['services/*.js'], {
  // we want `TodosService` to be registered as `todosService`.
  formatName: 'camelCase',
  resolverOptions: {
    // We want instances to be scoped to the Socket.io event.
    // We need to set that up.
    lifetime: Lifetime.SCOPED
  }
})

// imagination is a wonderful thing.
io.use(someAuthenticationMethod())

// We need a middleware to create a scope per request.
// Hint: that's the scopePerRequest middleware in `awilix-socketio` ;)
io.use((socket, next) => {
  // We want a new scope for each request!
  socket.container = container.createScope()
  // The `TodosService` needs `currentUser`
  socket.container.register({
    currentUser: asValue(socket.request.user) // from auth middleware.. IMAGINATION!! :D
  })
  return next()
})

Okay! Let's try setting up that API again!

io.on('todos', () => {
  // We have our scope available!
  const api = new TodoAPI(this.container.cradle) // Awilix magic!
  return api.getTodos(this)
})

A lot cleaner, but we can make this even shorter!

// Just invoke `api` with the method name and
// you've got yourself a middleware that instantiates
// the API and calls the method.
const api = methodName => {
  // create our handler
  return function() {
    const controller = new TodoAPI(this.container.cradle)
    return controller[method](this)
  }
}

// adding more events is way easier!
io.on('todos', api('getTodos'))

Using awilix-socketio

In our event handler, do the following:

import { makeInvoker } from 'awilix-socketio'

const api = makeInvoker(TodoAPI)
io.on('todos', api('getTodos'))

And in your Socket.io setup:

import { asValue, createContainer, Lifetime } from 'awilix'
import { scopePerRequest } from 'awilix-socketio'

const container = createContainer()

// The `TodosService` lives in services/TodosService
container.loadModules(
  [
    ['services/*.js', Lifetime.SCOPED] // shortcut to make all services scoped
  ],
  {
    // we want `TodosService` to be registered as `todosService`.
    formatName: 'camelCase'
  }
)

// imagination is a wonderful thing.
app.use(someAuthenticationMethod())

// Woah!
app.use(scopePerRequest(container))
app.use((socket, next) => {
  // We still want to register the user!
  // socket.container is a scope!
  socket.container.register({
    currentUser: asValue(socket.request.user) // from auth middleware.. IMAGINATION!! :D
  })
})

Now that is way simpler!

import { makeInvoker } from 'awilix-socketio'

function makeTodoAPI({ todosService }) {
  return {
    getTodos: socket => {
      return todosService.getTodos().then(todos => socket.emit('response', todos))
    }
  }
}

const api = makeInvoker(makeTodoAPI)
io.on('todos', api('getTodos'))

That concludes the tutorial! Hope you find it useful, I know I have.

API

The package exports the following Socket.io handler factories:

  • scopePerRequest(container): creates a scope per request.
  • makeInvoker(functionOrClass, opts)(methodName): using isClass, calls either makeFunctionInvoker or makeClassInvoker.
  • makeClassInvoker(Class, opts)(methodName): resolves & calls methodName on the resolved instance, passing it the socket and the arguments you put in.
  • makeFunctionInvoker(function, opts)(methodName): resolves & calls methodName on the resolved instance, passing it the socket and the arguments you put in.
  • makeResolverInvoker(resolver, opts): used by the other invokers, exported for convenience.
  • adaptToMiddleware(memberInvoker): Handlers in Socket.io receive any kind of argument and have this to reference the socket. Middleware, on the other hand, receive (socket, next). This turns handlers into middleware seamlessly.
  • inject(middlewareFactory): resolves the middleware per request.
    app.use(
      inject(({ userService }) => (socket, ...args) => {
        /**/
      })
    )

Contributing

npm run scripts

  • npm run test: Runs tests once
  • npm run lint: Lints + formats the code once
  • npm run cover: Runs code coverage using istanbul

Author

Alvaro Nicoli - @xmr_nkr

Based on