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 🙏

© 2025 – Pkg Stats / Ryan Hefner

work-already

v0.0.3

Published

Tools to create web tests for Express and Socket.IO applications with session integration.

Downloads

4

Readme

Work Already

Work Already is a set of tools for building web tests for mixed HTTP/S and WebSocket web applications that are built on Socket.IO, and where sessions are shared between Socket.IO and Express.js or some other similar http.Server-based framework.

Session-Aware Web Tests for Socket.IO Applications

So you've built a single page application with Express.js and Socket.IO, wherein user sessions are created and managed by Express.js, and then shared with Socket.IO. A client first hits a page served by Express.js, which generates a session, and then passes on identifiers for that session when connecting to one or more sockets as specified in the page Javascript.

Those identifiers can be cookies (as is often the case) or query parameters on the namespace path for the Socket.IO connect call (as is preferred by the Socket.IO developers).

With your application up and running, you'd like to run automated web tests against your server, hitting both web pages and Socket.IO connections. This is where you'll find that your options are actually somewhat limited - there are not that many tools out there that can do this. Hence the existence of this small package, Work Already.

Features worth noting in Work Already:

  • Set the order of HTTP/S requests and examine the responses.
  • Make Socket.IO connections that pass the right session information.
  • Emit on sockets and examine or expect server responses.
  • Run as a standalone test or integrate with Vows test suites.

IMPORTANT NOTE: This package works with 0.9.x versions of Socket.IO. It won't do much for earlier or later version in its present form, as some hacking of socket.io-client is required to make it play ball with sessions in a Node.js environment.

Sharing Sessions Between Express.js and Socket.IO?

Very quickly, here is an example of a generic way to share sessions between Express.js and Socket.IO by using cookies to pass around the session ID:

var express = require("express");
var io = require("socket.io");
var SessionMemoryStore = require("connect/lib/middleware/session/memory");
// Set up a minimal Express application, but ensure that the session store,
// cookie parser, and session key are exposed and accessible.
var app = express();
var store = new SessionMemoryStore();
var sessionKey = "sid";
var cookieSecret = "cookieSecret";
var cookieParser = express.cookieParser(cookieSecret);
app.use(cookieParser);
app.use(express.session({
  key: sessionKey,
  secret: cookieSecret,
  store: store
}));
var server = http.createServer(app).listen(10080);
// Now set up Socket.IO.
var socketFactory = io.listen(server);
// And use the authorization hook to attach the session to the socket
// handshake by reading the cookie and loading the session when a
// socket connects. Using the authorization hook means that we can
// deny access to socket connections that arrive without a session - i.e.
// where the user didn't load a site page through Express first.
socketFactory.set("authorization", function (data, callback) {
  if (data && data.headers && data.headers.cookie) {
    cookieParser(data, {}, function (error) {
      if (error) {
        callback("COOKIE_PARSE_ERROR", false);
        return;
      }
      var sessionId = data.signedCookies[sessionKey];
      store.get(sessionId, function (error, session) {
        // Add the sessionId. This will show up in
        // socket.handshake.sessionId.
        //
        // It's useful to set the ID and session separately because of
        // those fun times when you have an ID but no session - it makes
        // debugging that much easier.
        data.sessionId = sessionId;
        if (error) {
          callback("ERROR", false);
        } else if (!session) {
          callback("NO_SESSION", false);
        } else {
          // Add the session. This will show up in
          // socket.handshake.session.
          data.session = session;
          callback(null, true);
        }
      });
    });
  } else {
    callback("NO_COOKIE", false);
  }
});

This is not, however, the recommended way of doing things. At present the Socket.IO folk are leaning towards requiring authentication or identification tokens (such as the session ID in some encrypted form) to be appended to the namespace for the connection.

On the client:

io.connect("/namespace?token=encryptedString");

On the server:

socketFactory.set("authorization", function (data, callback) {
  var token = data.query.token;
  // Now figure out the session based on what was placed inside the token
  // and attach it to the data object.
  // ...
});

See these pages for more on this topic:

The socket.io-client Package Doesn't Allow Cookies To Be Set

The challenge for testing in a Node.js rather than browser environment - only for the situation in which you are using cookies rather than the query string to pass session identifiers - is that the socket.io-client package doesn't permit the setting of cookie data in its requests.

The Work Already package works around this issue in a very crude way, but there is little other option but to hack or rewrite the socket.io-client package code in order to achieve this goal.

Example of Use

See /examples for examples of use, for both simple load tests, and Vows web tests like this one:

var assert = require("assert");
var vows = require("vows");
var workAlready = require("work-already");
var suite = vows.describe("Testing an Express / Socket.IO server.");
var client = new workAlready.Client({
  server: {
    host: "localhost",
    port: 10080,
    protocol: "http"
  },
  sockets: {
    // If namespace is not specified in an action, then this is the namespace
    // used.
    defaultNamespace: ""
  }
});
// Load the main application page, which also obtains the necessary cookies
// to pass along with the Socket.IO connection that follows.
suite.addBatch({
  "Load main page via GET request": {
    topic: function () {
      client.action("/index.html", this.callback);
    },
    "page fetched": function (error, page) {
      // The last retrieved page is stored at client.page for perusal, as well
      // as being passed to this function.
      assert.isNull(error);
      assert.isObject(page);
      assert.isObject(client.page);
      assert.strictEqual(200, page.statusCode);
      assert.include(page.body, "some distinctive string");
    }
  }
});
// Establish a Socket.IO connection with the default namespace, passing over
// the right cookies obtained in the prior request.
suite.addBatch({
  "Connect via Socket.IO": {
    topic: function () {
      client.action({
        type: "socket",
        timeout: 500,
        // Optionally, set Socket.IO connection parameters.
        socketConfig: {
          "reconnect": true
        }
      }, this.callback);
    },
    "socket connected": function (error) {
      assert.isUndefined(error);
      assert.isObject(client.page.sockets);
      assert.isObject(client.page.sockets[client.config.sockets.defaultNamespace]);
    }
  }
});
// Perhaps the server sends an immediate response message via the socket
// connection. If so, the next step is to wait on it.
suite.addBatch({
  "Await emitted server response to connection": {
    topic: function () {
      client.action({
        type: "awaitEmit",
        eventType: "responseOnConnect",
        timeout: 500
      }, this.callback);
    },
    "event emitted": function (error, socketEvent) {
      assert.isNull(error);
      // The socketEvent is stashed in the client as well as being passed
      // to this function.
      assert.isObject(client.socketEvent);
      assert.strictEqual(client.socketEvent.namespace, client.config.sockets.defaultNamespace);
      assert.strictEqual(client.socketEvent.eventType, "responseOnConnect");
    }
  }
});
exports.suite = suite;