Giter VIP home page Giter VIP logo

primus's Introduction

Primus

Version npmCICoverage Status

Primus, the creator god of transformers but now also known as universal wrapper for real-time frameworks. There are a lot of real-time frameworks available for Node.js and they all have different opinions on how real-time should be done. Primus provides a common low level interface to communicate in real-time using various real-time frameworks.

Advantages

  1. Effortless switching between real-time frameworks by changing one single line of code. No more API rewrites needed when your project requirements change, the framework gets abandoned or simply breaks down.
  2. Built-in reconnect, it just works. The reconnect is controlled by a randomised exponential back-off algorithm to reduce server stress.
  3. Offline detection, Primus is smart enough to detect when users drop their internet connection (switching WIFI points/cell towers for example) and reconnects when they are back online.
  4. Automatically encodes and decodes messages using custom parsers. Can be easily switched for binary encoding for example.
  5. A clean, stream-compatible interface for the client and server. You can just stream#pipe data around. In addition to that, the client works on Node.js as well, write once, run it everywhere.
  6. Fixes various of bugs in the supported frameworks and additional stability patches to improve real-time communication.
  7. Comes with an amazing plugin interface to keep the core library as fast and lean as possible while still allowing the server and the client to be extended.
  8. Last but not least, Primus is built with love, passion and dedication to the real-time web.

Installation

Primus is released on npm and can be installed using:

npm install primus --save

Before Starting

If you deploy your application behind a reverse proxy (Nginx, HAProxy, etc.) you might need to add WebSocket specific settings to its configuration files. If you intend to use WebSockets, please ensure that these settings have been added. There are some example configuration files available in the observing/balancerbattle repository.

Table of Contents

Getting started

Primus doesn't ship with real-time frameworks as dependencies, it assumes that you as user add them yourself as a dependency. This is done to keep the module as lightweight as possible. This works because require in will walk through your directories searching for node_module folders that have these matching dependencies.

Primus needs to be "attached" to a HTTP compatible server. These includes the built-in http and https servers but also the spdy module as it has the same API as node servers. Creating a new Primus instance is relatively straightforward:

'use strict';

var Primus = require('primus')
  , http = require('http');

var server = http.createServer(/* request handler */)
  , primus = new Primus(server, {/* options */});

The following options can be provided:

Name Description Default
authorization Authorization handler null
pathname The URL namespace that Primus can own /primus
parser Message encoder for all communication JSON
transformer The transformer we should use internally websockets
plugin The plugins that should be applied {}
pingInterval Interval at which heartbeats are sent 30000
global Set a custom client class / global name Primus
compression Use permessage-deflate / HTTP compression false
maxLength Maximum allowed packet size, in bytes 10485760
transport Transformer specific configuration {}
idGenerator Custom spark id generator function undefined
origins cors List of origins *
methods cors List of accepted HTTP methods GET,HEAD,PUT,POST,DELETE,OPTIONS
credentials cors Allow sending of credentials true
maxAge cors Cache duration of CORS preflight 30 days
headers cors Allowed headers false
exposed cors Headers exposed to the client false

The options that are prefixed with cors are supplied to our access-control module which handles HTTP Access Control (CORS), so for a more detailed explanation of these options check it out.

The transport option allows you to use any configuration option supported by the underlying real-time framework. Its use is discouraged as these options are framework specific and no longer work if you change transformer. Our advise is to use it only if you know what you are doing and if you need fine-grained control over the real-time framework. Please also keep in mind that some of these options are overriden by Primus.

The pingInterval option specifies the interval at which heartbeats are transmitted. It is possible to completely disable the heartbeats by setting the value of the pingInterval option to false.

The idGenerator option can be used to define a function which will be called to set each spark.id. The generator function should return a unique string each time it is invoked. If idGenerator is not defined, Primus will try to use ids provided by the transformer. If the transformer does not provide ids, Primus will use nanoid to generate Spark ids.

If you don't have a pre-existing server where you want or can attach your Primus server to you can also use the Primus.createServer convenience method. The createServer method will automatically:

  • Setup a HTTP, HTTPS or SPDY server for you on the given port number.
  • Setup your Primus server with the given configuration.
  • Listen on the HTTP, HTTPS, SPDY server.
  • Attach a primus.on('connection') listener.
  • Return the created Primus instance.
Primus.createServer(function connection(spark) {

}, { port: 8080, transformer: 'websockets' });

In the above example we automatically create a HTTP server which will listen on port 8080, a primus instance with the websockets transformer and start listening for incoming connections. The supplied function in the Primus.createServer method is optional. You can just listen for incoming connections your self using the returned Primus instance. If you want to listen to a HTTPS or SPDY server, which is recommended, you can directly pass the SPDY and HTTPS certs/keys/pfx files in the options object:

var primus = Primus.createServer({
  port: 443,
  root: '/folder/with/https/cert/files',
  cert: 'myfilename.cert',
  key: 'myfilename.cert',
  ca: 'myfilename.ca',
  pfx: 'filename.pfx',
  passphrase: 'my super sweet password'
});

primus.on('connection', function (spark) {
  spark.write('hello connnection');
});

Primus.createServer returns a warning when it starts a HTTP server. The warning advises you to use a HTTPS server and can be disabled setting the option iknowhttpsisbetter to true.

Client library

As most libraries come with their own client-side framework for making the connection we've also created a small wrapper for this. The library can be retrieved using:

primus.library();

Which returns the client-side library as a string (which can then be minified or even have more code added to it). It does not come pre-minified as that is out of the scope of this project. You can store this on a CDN or on your static server. Do whatever you want with it, but remember to regenerate it every time you change Primus server options. This is important because some properties of the client are set using the server configuration. For example if you change the pathname, the client should be regenerated to reflect that change and work correctly. We advise you to regenerate the library every time you redeploy so you always have a client compatible with your back-end. To save the file you can use:

primus.save(__dirname +'/primus.js');

This will store the compiled library in your current directory. If you want to save it asynchronously, you can supply the method with a callback method:

primus.save(__dirname +'/primus.js', function save(err) {

});

But to make it easier for you during development we've automatically added an extra route to the supplied HTTP server, this will serve the library for you so you don't have to save it. Please note, that this route isn't optimised for serving static assets and should only be used during development. In your HTML page add:

<script src="/primus/primus.js"></script>

As you can see, it will use the /primus pathname by default. Primus needs to own the whole path/namespace in order to function properly as it will forward all other requests directly in to the transformers so they can work their magic. If you already have a static folder with the name primus you can change the pathname to something different and still make this work. But you would of course need to update the src attribute of the script tag to set the correct location. It's always available at:

<protocol>://<server location>/<pathname>/primus.js

Here <pathname> is the pathname set in server options above. The client is cross domain compatible so you don't have to serve it from the same domain you're running Primus on. But please note, that the real-time framework you're using might be tied to same domain restrictions.

Once you're all set up you can start listening for connections. These connections are announced through the connection event.

primus.on('connection', function (spark) {
  // spark is the new connection.
});

Disconnects are announced using a disconnection event:

primus.on('disconnection', function (spark) {
  // the spark that disconnected
});

The spark argument is the actual real-time socket/connection. Sparks have a really low level interface and only expose a couple properties that are cross engine supported. The interface is modeled towards a Node.js stream compatible interface. So this will include all methods that are available on the stream interface including Spark#pipe.

spark.headers

The spark.headers property contains the headers of either the request that started a handshake with the server or the headers of the actual real-time connection. This depends on the module you are using.

Please note that sending custom headers from the client to the server is impossible as not all transports that these transformers support can add custom headers to a request (JSONP for example). If you need to send custom data, use a query string when connecting

spark.address

The spark.address property contains the ip and port of the connection. If you're running your server behind a reverse proxy it will automatically use the x-forwarded-for header. This way you will always have the address of the connecting client and not the IP address of your proxy.

Please note that the port is probably out of date by the time you're going to read it as it's retrieved from an old request, not the request that is active at the time you access this property.

spark.query

The spark.query contains the query string you used to connect to the server. It's parsed as an object. Please note that this may not be available for all supported transformers.

spark.socket

The spark.socket is set to the underlying socket of the transformer. This is not necessarily a raw Socket and will differ from transformer to transformer.

spark.id

This is a unique id that we use to identify this single connection with. Normally the frameworks refer to this as a sessionid, which is confusing as it's only used for the duration of one single connection. You should not see this as a "session id", and rather expect it to change between disconnects and reconnects.

spark.request

The spark.request gives you access to the HTTP request that was used to initiate the real-time connection with the server. Please note that this request is already answered and closed (in most cases) so do not attempt to write or answer it anyway. But it might be useful to access methods that get added by middleware layers, etc.

spark.write(data)

You can use the spark.write method to send data over the socket. The data is automatically encoded for you using the parser that you've set while creating the Primus server instance. This method always returns true on success and false on failure so back pressure isn't handled.

spark.write({ foo: 'bar' });

spark.end(data, options)

You can use spark.end to close the connection. This method takes two optional arguments. The first, if provided, is the data to send to the client before closing the connection. The second is an options object used to customize the behavior of the method. By default the spark.end method closes the connection in a such way that the client knows it was intentional and it doesn't attempt a reconnection.

spark.end(); // the client doesn't reconnect automatically

You can change this behavior and trigger a client-side reconnection using the reconnect option.

spark.end(undefined, { reconnect: true }); // trigger a client-side reconnection

spark.emits(event, parser)

This method is mostly used internally. It works similarly to the native bind function, returning a function that emits the assigned event every time it's called. If the last argument is a function, it will be used to parse the arguments of the returned function. The parser is optional and always async, its first argument is a callback that follows the usual error first pattern, all successive arguments are the ones to parse. Using the parser you can reduce the arguments down to a single value, remove them completely or prevent the event from being emitted. See emits for detailed usage instructions.

spark.emits('event', function parser(next, structure) {
  next(undefined, structure.data);
});

Please note that the data that is received here isn't decoded yet.

spark.on('data')

The data event is emitted when a message is received from the client. It's automatically decoded by the specified decoder.

spark.on('data', function message(data) {
  // the message we've received.
});

spark.on('end')

The end event is emitted when the client has disconnected.

primus.on('connection', function (spark) {
  console.log('connection has the following headers', spark.headers);
  console.log('connection was made from', spark.address);
  console.log('connection id', spark.id);

  spark.on('data', function (data) {
    console.log('received data from the client', data);

    //
    // Always close the connection if we didn't receive our secret imaginary
    // handshake.
    //
    if ('foo' !== data.secrethandshake) spark.end();
    spark.write({ foo: 'bar' });
    spark.write('banana');
  });

  spark.write('Hello world');
})

Connecting from the Browser

Primus comes with its client framework which can be compiled using primus.library() as mentioned above. To create a connection you can simply create a new Primus instance:

var primus = new Primus(url, { options });

//
// But it can be easier, with some syntax sugar.
//
var primus = Primus.connect(url, { options });

The URL should confirm the following conditions:

  • It should include the protocol it needs to connect with. This can either be http or https. We recommend that you're using HTTPS for all your connections as this prevents connection blocking by firewalls and anti-virus programs.
  • The URL should not include a pathname. The pathname is configured by the server (See: getting-started) and needs to be configured there as it will be compiled in to the primus.js client file.

If no url argument is passed, it will default to the current URL.

The following options can be provided:

Name Description Default
reconnect Configures the exponential back off {}
timeout Connect time out 10000 ms
pingTimeout Max time to wait for a server ping 45000 ms
strategy Our reconnect strategies "disconnect,online,timeout"
manual Manually open the connection false
websockets Should we use WebSockets Boolean, is detected
network Use native online/offline detection Boolean, is feature detected
transport Transport specific configuration {}
queueSize Number of messages that can be queued Infinity

There are 2 important options that we're going to look a bit closer at.

Reconnect

When the connection goes down unexpectedly an automatic reconnect process is started. It uses a randomised exponential back-off algorithm to prevent clients from DDoSing your server when you reboot as they will all be re-connecting at different times. The reconnection can be configured using the options argument in Primus and you should add these options to the reconnect property:

Name Description Default
max Maximum delay for a reconnection attempt Infinity
min Minimum delay for a reconnection attempt 500 ms
retries Maximum amount of attempts 10
reconnect timeout Maximum time for an attempt to complete 30000 ms
factor Exponential back off factor 2
primus = Primus.connect(url, {
  reconnect: {
      max: Infinity // Number: The max delay before we try to reconnect.
    , min: 500 // Number: The minimum delay before we try reconnect.
    , retries: 10 // Number: How many times we should try to reconnect.
  }
});

When you're going to customize min please note that it will grow exponentially e.g. 500 -> 1000 -> 2000 -> 4000 -> 8000 and is randomized so expect to have slightly higher or lower values.

Please note that when we reconnect, we will receive a new connection event on the server and a new open event on the client, as the previous connection was completely dead and should therefore be considered a new connection.

If you are interested in learning more about the backoff algorithm you might want to read http://dthain.blogspot.nl/2009/02/exponential-backoff-in-distributed.html

Strategy

The strategy allows you to configure when you want a reconnect operation to kick in. We're providing some sane defaults for this but we still want to provide users with highest level of customization:

disconnect
Reconnect when we detect an unintentional disconnect in the connection.
online
Reconnect when the browser went from an offline event to an online event.
timeout
Reconnect when we failed to establish our initial connection. This can happen because we took too long to connect or because there was an error while we tried to connect (which happens when you connect to a dead server)

You can supply these options as a comma-separated String:

var primus = new Primus(url, { strategy: 'online, timeout, disconnect' })

Or as an Array:

var primus = new Primus(url, { strategy: [ 'online', 'timeout', 'disconnect' ]});

We'll try to normalize everything as much as possible, we toLowerCase everything and join it back to a readable string so if you wrote dIsconNect it will get normalized to disconnect.

If you are using authentication you should disable the timeout strategy as there is no way of detecting the difference between a failed authorization and a failed connect. If you leave this enabled with authorization every unauthorized access will try to reconnect again.

We automatically disable this for you when you've set the authorization before you save the library.

But there are always use cases where reconnection is not advised for your application. In these cases we've provided a way to completely disable the reconnection, this is done by setting the strategy to false:

var primus = new Primus(url, { strategy: false });

If you want to manually control the reconnection you can call primus.end() to close the connection and primus.open() to establish a new one. Be sure to use primus.open() correctly, see below for details.

transport

The transport object allows you to add a transport specific configuration. We only recommend using this if you understand and accept the following consequences:

  • Primus will try to override configuration properties that are needed to ensure a correct functioning.
  • We might start using options without any announcement or major version bump.
  • Expect your client and its connection to malfunction once you switch between different transports, as these configurations are specific to the bundled transformer library/client.
  • Bugs and bug reports caused by using this functionality are closed immediately.

Having that said, this gives you total freedom while still getting the benefits of Primus.

primus.open()

This method opens a connection with the server. By default it is called automatically when the Primus instance is created, but there are cases where it's desirable to open the connection manually. To do this set the manual option to true and when you have the Primus instance call the method:

primus.open();

When you call primus.open() you should make sure that the connection is totally dead (e.g. after an end event) and primus isn't already trying or planning to reconnect.

primus.write(message)

Once you've created your Primus instance you're ready to go. When you want to write data to your server you can just call the .write method:

primus.write('message');

It automatically encodes your messages using the parser that you've specified on the server. So sending objects back and forth between the server is nothing different then just writing:

primus.write({ foo: 'bar' });

When you are sending messages to the server, you don't have to wait for the open event to happen, the client will automatically buffer all the data you've send and automatically write it to the server once it's connected. The client supports a couple of different events.

primus.on('data')

The data event is the most important event of the whole library. It's emitted when we receive data from the server. The data that is received is already decoded by the specified parser.

primus.on('data', function message(data) {
  console.log('Received a new message from the server', data);
});

primus.on('open')

The open event is emitted when we've successfully created a connection with the server. It will also be emitted when we've successfully reconnected after the connection goes down unintentionally.

primus.on('open', function open() {
  console.log('Connection is alive and kicking');
});

primus.on('error')

The error event is emitted when something breaks that is out of our control. Unlike Node.js, we do not throw an error if no error event listener is specified. In general, when there is an active connection, it is not directly closed when an error event is emitted. The cause of an error, in fact, could be that the parser failed to encode or decode a message. In this case we only emit the error, discard the message and keep the connection alive. An error event can also be emitted when a connection fails to establish. When this happens the client automatically tries to reconnect, unless the connection gets closed for some other reason. The only exception is when there is an authorization hook. If we get an error when connecting to a server where authorization is required, we simply close the connection, as we can't determinate if the error is the result of an unauthorized access or not.

primus.on('error', function error(err) {
  console.error('Something horrible has happened', err.stack);
});

primus.on('reconnect')

The reconnect event is emitted when we're attempting to reconnect to the server. This all happens transparently and it's just a way for you to know when these reconnects are actually happening.

primus.on('reconnect', function (opts) {
  console.log('Reconnection attempt started');
});

primus.on('reconnect scheduled')

Looks a lot like the reconnect event mentioned above, but it's emitted when we've detected that connection went/is down and we're going to start a reconnect operation. This event would be ideal to update your application's UI when the connection is down and you are trying to reconnect in x seconds.

primus.on('reconnect scheduled', function (opts) {
  console.log('Reconnecting in %d ms', opts.scheduled);
  console.log('This is attempt %d out of %d', opts.attempt, opts.retries);
});

primus.on('reconnected')

The client successfully reconnected with the server.

primus.on('reconnected', function (opts) {
  console.log('It took %d ms to reconnect', opts.duration);
});

primus.on('reconnect timeout')

The reconnect timeout event is emitted when a reconnection attempt takes too much time. This can happen for example when the server does not answer a request in a timely manner.

primus.on('reconnect timeout', function (err, opts) {
  console.log('Timeout expired: %s', err.message);
});

After this event a whole new reconnection procedure is automatically started, so you don't have to worry about it.

primus.on('reconnect failed')

This event is emitted when the reconnection failed, for example when all attempts to reconnect have been unsuccessful.

primus.on('reconnect failed', function (err, opts) {
  console.log('The reconnection failed: %s', err.message);
});

primus.on('end')

The end event is emitted when we've closed the connection. When this event is emitted you should consider your connection to be fully dead with no way of reconnecting. But it's also emitted when the server closes the connection.

primus.on('end', function () {
  console.log('Connection closed');
});

primus.end()

When you want to close the connection you can call the primus.end() method. After this the connection should be considered dead and a new connection needs to be made using Primus.connect(url) or primus = new Primus(url) if you want to talk with the server again.

primus.end();

primus.destroy()

This method literally destroys the primus instance. Internally it calls the primus.end() method but it also frees some potentially heavy objects like the underlying socket, the timers, the message transformers, etc. It also removes all the event listeners but before doing that it emits a final destroy event. Keep in mind that once this method is executed, you can no longer use primus.open() on the same primus instance.

primus.on('destroy', function () {
  console.log('Feel the power of my lasers!');
});

primus.destroy();

primus.emits(event, parser)

This method is analogous to the spark.emits method. It returns a function that emits the given event every time it's called. See emits for detailed usage instructions.

primus.emits('event', function parser(next, structure) {
  next(undefined, structure.data);
});

primus.id(callback)

There are cases where it is necessary to retrieve the spark.id from the client. To make this easier, we added a primus.id() method that takes a callback function to which the id will be passed.

primus.id(function (id) {
  console.log(id);
});

Connecting from the server

There are two ways of creating a server side client.

  1. When you've created your primus instance you can access the Socket property on it. This Socket is automatically configured to connect to the correct pathname, using the same transformer and parser that you've specified when you created your primus instance.

    var primus = new Primus(server, { transformer: transformer, parser: parser })
      , Socket = primus.Socket;
    
    var client = new Socket('http://localhost:8080');
    //
    // It has the same interface as the client, so you can just socket.write or
    // listen for the `open` events etc.
    //
  2. You might need to connect from a different node process where you don't have access to your primus instance and the compatible Socket instance. For these cases there a special createSocket method where you can specify the transformer, parser, plugin that you are using on your server to create another compatible socket.

    var Primus = require('primus') // Primus library from npm install primus
      , Socket = Primus.createSocket({ transformer: transformer, parser: parser })
      , client = new Socket('http://localhost:8080');

When you are using plugins with Primus make sure you add them before you reference the primus.Socket or it will compile a client without your plugins. If you're using the Primus.createSocket api you can directly supply the plugins as part of the options as it supports plugin object:

var Socket = Primus.createSocket({
  transformer: transformer,
  parser: parser,
  plugin: {
    'my-emitter': require('my-emitter'),
    'substream': require('substream')
  }
});

The constructor returned by primus.Socket or Primus.createSocket has the same signature of the constructor used to connect from the browser. This means that you can use all the options mentioned in the previous section:

var Socket = Primus.createSocket()
  , client = new Socket('http://localhost:8080', { options });

If you do not know which transformer and parser are used on the server, we expose a small JSON "spec" file that exposes this information. The specification can be reached on the /<pathname>/spec and will output the following JSON document:

{
  "version":"2.4.0",
  "pathname":"/primus",
  "parser":"json",
  "transformer":"websockets"
}

Authorization

Server

Primus has a built-in auth hook that allows you to leverage the basic auth header to validate the connection. To setup the optional auth hook, use the Primus#authorize method:

var authParser = require('basic-auth-parser');

//
// Add hook on server
//
primus.authorize(function (req, done) {
  var auth;

  try { auth = authParser(req.headers['authorization']) }
  catch (ex) { return done(ex) }

  //
  // Do some async auth check
  //
  authCheck(auth, done);
});

primus.on('connection', function (spark) {
  //
  // You only get here if you make it through the auth hook!
  //
});

In this particular case, if an error is passed to done by authCheck or the exception handler then the connection attempt will never make it to the primus.on('connection') handler.

The error you pass can either be a string or an object. If an object, it can have the following properties which affect the response sent to the client:

  • statusCode: The HTTP status code returned to the client. Defaults to 401.
  • authenticate: If set and statusCode is 401 then a WWW-Authenticate header is added to the response, with a value equal to the authenticate property's value.
  • message: The error message returned to the client. The response body will be {error: message}, JSON-encoded.

If the error you pass is a string then a 401 response is sent to the client with no WWW-Authenticate header and the string as the error message.

For example to send 500 when an exception is caught, 403 for forbidden users and details of the basic auth scheme being used when authentication fails:

primus.authorize(function (req, done) {
  var auth;

  if (req.headers.authorization) {
    try { auth = authParser(req.headers.authorization) }
    catch (ex) {
      ex.statusCode = 500;
      return done(ex);
    }

    if ((auth.scheme === 'myscheme') &&
        checkCredentials(auth.username, auth.password)) {
      if (userAllowed(auth.username)) {
        return done();
      } else {
        return done({ statusCode: 403, message: 'Go away!' });
      }
    }
  }

  done({
    message: 'Authentication required',
    authenticate: 'Basic realm="myscheme"'
  });
});

Please note that the auth hook is run each and every time a request is made to the server.

Client

Unfortunately, the amount of detail you get in your client when authorization fails depends on the transformer in use. Most real-time frameworks supported by Primus don't expose the status code, headers or response body.

The WebSocket transformer's underlying transport socket will fire an unexpected-response event with the HTTP request and response:

primus.on('outgoing::open', function () {
  primus.socket.on('unexpected-response', function (req, res) {
    console.error(res.statusCode);
    console.error(res.headers['www-authenticate']);

    //
    // It's up to us to close the request (although it will time out).
    //
    req.abort();

    //
    // It's also up to us to emit an error so primus can clean up.
    //
    primus.socket.emit('error', 'authorization failed: ' + res.statusCode);
  });
});

If you want to read the response body then you can do something like this:

primus.on('outgoing::open', function () {
  primus.socket.on('unexpected-response', function (req, res) {
    console.error(res.statusCode);
    console.error(res.headers['www-authenticate']);

    var data = '';

    res.on('data', function (v) {
      data += v;
    });

    res.on('end', function () {
      //
      // Remember error message is in the 'error' property.
      //
      primus.socket.emit('error', new Error(JSON.parse(data).error));
    });
  });
});

If unexpected-response isn't caught (because the WebSocket transformer isn't being used or you don't listen for it) then you'll get an error event:

primus.on('error', function error(err) {
  console.error('Something horrible has happened', err.stack);
});

As noted above, err won't contain any details about the authorization failure so you won't be able to distinguish it from other errors.

Broadcasting

Broadcasting allows you to write a message to every connected Spark on your server. There are 2 different ways of doing broadcasting in Primus. The easiest way is to use the Primus#write method which will write a message to every connected user:

primus.write('message');

There are cases where you only want to broadcast a message to a smaller group of users. To make it easier to do this, we've added a Primus#forEach method which allows you to iterate over all active connections.

primus.forEach(function (spark, id, connections) {
  if (spark.query.foo !== 'bar') return;

  spark.write('message');
});

The method can be also used asynchronously. To enable the asynchronous iteration you have to call Primus#forEach with two arguments. The first is the iterator function that is called on every step. The iterator is called with a connection from the list and a callback for when it has finished. The second argument is the main callback and is called when the iteration has finished.

primus.forEach(function (spark, next) {
  //
  // Do something and call next when done
  //
  next();
}, function (err) {
  console.log('We are done');
});

There are also cases where you want to select a single Spark. To do this you can use the Primus#spark method.

// Get a spark by its id
var spark = primus.spark(id);

spark.write('message');

This method returns a Spark or undefined if the given id doesn't match any of the active Spark ids on the server.

Destruction

In rare cases you might need to destroy the Primus instance you've created. You can use the primus.destroy() or primus.end() method for this. This method accepts an Object which allows you to configure the destruction process:

  • close Close the HTTP server that Primus received. Defaults to true.
  • reconnect Automatically reconnect the clients. Defaults to false.
  • timeout Close all active connections and clean up the Primus instance after the specified amount of timeout. Defaults to 0.

The timeout is especially useful if you want gracefully shutdown your server but really don't want to wait an infinite amount of time.

primus.destroy({ timeout: 10000 });

Events

Primus is built upon the Stream and EventEmitter interfaces. This is a summary of the events emitted by Primus.

Event Usage Location Description
outgoing::reconnect private client Transformer should reconnect.
reconnect scheduled public client We're scheduling a reconnect.
reconnect public client Reconnect attempt is about to be made.
reconnected public client Successfully reconnected.
reconnect timeout public client Reconnect attempt took too much time.
reconnect failed public client Failed to reconnect.
timeout public client Failed to connect to server.
outgoing::open private client/spark Transformer should connect.
incoming::open private client/spark Transformer has connected.
open public client Connection is open.
destroy public client The instance has been destroyed.
incoming::error private client Transformer received an error.
error public client/spark An error happened.
incoming::data private client/server Transformer received data.
outgoing::data private client/spark Transformer should write data.
data public client/spark We received data.
incoming::end private client/spark Transformer closed the connection.
outgoing::end private client/spark Transformer should close connection.
end public client/spark The connection has ended.
close public client/server The connection has closed, we might reconnect. / The server has been destroyed.
connection public server We received a new connection.
disconnection public server We received a disconnection.
initialised public server The server is initialised.
plugin public server A new plugin has been added.
plugout public server A plugin has been removed.
incoming::ping private client We received a ping message.
outgoing::ping private spark We're sending a ping message.
incoming::pong private spark We received a pong message.
outgoing::pong private client We're sending a pong message.
heartbeat public spark We've received a response to a heartbeat.
online public client We've regained a network connection.
offline public client We've lost our internet connection.
log public server Log messages.
readyStateChange public client/spark The readyState has changed.
outgoing::url private client The options used to construct the URL.

As a rule of thumb assume that every event that is prefixed with incoming:: or outgoing:: is reserved for internal use only and that emitting such events your self will most likely result in chaos and destruction.

To make it easier for developers to emit events on Primus itself, we've added a small helper function that checks if the event you want to emit is reserved for Primus only. This would be all incoming:: and outgoing:: prefixed events and the events listed above. This method is called <class>.reserved() and it's implemented on the Spark:

primus.on('connection', function connection(spark) {
  spark.on('data', function (data) {
    //
    // Just imagine that we receive an array of arguments from the client which
    // first argument is the name of the event that we need to emit and the
    // second argument are the arguments for function.
    //
    if (spark.reserved(data.args[0])) return;

    spark.emit.apply(spark, data.args[0]);
  });
});

But also the client:

var primus = new Primus('http://example.bar');

primus.on('data', function (data) {
  if (primus.reserved(data.args[0])) return;

  primus.emit.apply(primus, data.args);
});

And of course the Primus instance as well.

Heartbeats and latency

Heartbeats are used in Primus to figure out if we still have an active, working and reliable connection with the server. These heartbeats are sent from the server to the client as shown in the following diagram.

     client will disconnect
       if not recv within
          `pingTimeout`

     primus:pong:{timestamp}
    +----------------------+
    |                      |
+---v----+            +---------+
| server |            |  client |
+--------+            +----^----+
    |                      |
    +----------------------+
     primus:ping:{timestamp}

      sent at `pingInterval`
      server will disconnect
      if no response since
           last ping

The heartbeat message that we send over the connection is primus::ping::<timestamp>. Upon receipt of this message, the client will send back a primus::pong::<timestamp> message with the same <timestamp> it received from the server. This allows to calculate the latency between messages by simply getting the <timestamp> and comparing it with the local time.

Supported Real-time Frameworks

The following transformers/transports are supported in Primus:

BrowserChannel

BrowserChannel was the original technology that GMail used for their real-time communication. It's designed for same domain communication and does not use WebSockets. To use BrowserChannel you need to install the browserchannel module:

npm install browserchannel --save

And tell Primus that you want to use browserchannel as transformer:

var primus = new Primus(server, { transformer: 'browserchannel' });

The browserchannel transformer comes with built-in node client support and can be accessed using:

var Socket = primus.Socket
  , socket = new Socket('url');

Please note that you should use at least version 1.0.6 which contains support for query strings.

Engine.IO

Engine.IO is the low level transport functionality of Socket.IO 1.0. It supports multiple transports for creating a real-time connection. It uses transport upgrading instead of downgrading which makes it more resilient to blocking proxies and firewalls. To enable engine.io you need to install the engine.io module:

npm install engine.io --save

And tell Primus that you want to use engine.io as transformer:

var primus = new Primus(server, { transformer: 'engine.io' });

If you want to use the client interface inside of Node.js you also need to install the engine.io-client:

npm install engine.io-client --save

And then you can access it from your server instance:

var Socket = primus.Socket
  , socket = new Socket('url');

Faye

Faye is a WebSocket only transformer. It uses the faye-websocket module which is part of the Faye project and supports all protocol specifications. To use this you need to install the faye-websocket module:

npm install faye-websocket --save

And tell Primus that you want to use faye as transformer:

var primus = new Primus(server, { transformer: 'faye' });

The faye transformer comes with built-in node client support and can be accessed using:

var Socket = primus.Socket
  , socket = new Socket('url');

SockJS

SockJS is a real-time server that focuses on cross-domain connections and does this by using multiple transports. To use SockJS you need to install the sockjs module:

npm install sockjs --save

And tell Primus that you want to use sockjs as transformer:

var primus = new Primus(server, { transformer: 'sockjs' });

If you want to use the client interface inside of Node.js you also need to install the sockjs-client module:

npm install sockjs-client --save

And then you can access it from your server instance:

var Socket = primus.Socket
  , socket = new Socket('url');

uws

uws is a WebSocket only transformer. It uses the uws module which is probably the fastest WebSocket server available in Node.js. To use uws you have to install the uws module:

npm install uws --save

And tell Primus that you want to use uws as transformer:

var primus = new Primus(server, { transformer: 'uws' });

If you want to use the client interface inside of Node.js you also need to install the ws module:

npm install ws --save

And then you can access it from your server instance:

var Socket = primus.Socket
  , socket = new Socket('url');

WebSockets

If you are targeting a high end audience or maybe just need something for internal uses you can use a pure WebSocket server. This transformer uses the popular ws module which is battle tested and supports all protocol specifications. To use WebSockets you need to install the ws module:

npm install ws --save

And tell Primus that you want to use WebSockets as transformer:

var primus = new Primus(server, { transformer: 'websockets' });

The WebSockets transformer comes with built-in node client support and can be accessed using:

var Socket = primus.Socket
  , socket = new Socket('url');

As you can see from the examples above, it doesn't matter how you write the name of the transformer, we just toLowerCase() everything.

Transformer inconsistencies

  • BrowserChannel does not give you access to the remotePort of the incoming connection. So when you access spark.address the port property will be set to 1337 by default.
  • BrowserChannel is the only transformer that does not support cross domain connections.
  • BrowserChannel and SockJS are written in CoffeeScript and this can make debugging harder when their internals fail.
  • Engine.IO and SockJS do not ship their client-side library with their server side component. We're bundling a snapshot of these libraries inside of Primus. We will always be targeting the latest version of these transformers when we bundle the library.

Parsers

In addition to support different frameworks we've also made it possible to use custom encoding and decoding libraries. We're using JSON by default but you could also use binary or EJSON for example (but these parsers need to be supported by Primus, so check out the parser folder for examples). To specify the parser to use you can supply a parser configuration option:

var primus = new Primus(server, { parser: 'JSON' });

All parsers have an async interface for error handling.

Middleware

Primus has two ways of extending the functionality. We have plugins but also support middleware. And there is an important difference between these. The middleware layers allows you to modify the incoming requests before they are passed in to the transformers. Plugins allow you to modify and interact with the sparks. The middleware layer is only run for the requests that are handled by Primus.

We support 2 kind of middleware, async and sync middleware. The main difference between these kinds is that sync middleware doesn't require a callback, it is completely optional. In Primus, we eat our own dog food. Various of components in Primus are implemented through middleware layers:

  • cors: Adds the Access Control headers.
  • primus.js: It serves our primus.js client file.
  • spec: It outputs the server specification (version, transformer, path).
  • authorization: Our authorization handler, which is implemented as a middleware.
  • no-cache: Add no-cache headers to every HTTP request.
  • x-xss: Add X-XSS-Protection headers to every HTTP request.

Primus.use(name, fn, options, index)

The primus.use method is how you add middleware layers to your system. All middleware layers need to be named. This allows you to also enable, disable and remove middleware layers. The supplied function can either be a pre-configured function that is ready to answer request/response or an unconfigured middleware. An unconfigured middleware is a function with less then 2 arguments. We execute this function automatically with Primus as context of the function and optionally, the options that got provided:

primus.use('name', function () {
  var primus = this;

  return function (req, res) {
    res.end('foo');
  }
}, { foo: 'bar' });

As you can see in the example above, we assume that you return the actual middleware layer. If you don't need any pre-configuration you can just supply the function directly:

// sync middleware
primus.use('name', function (req, res) {

});

// async middleware
primus.use('name', function (req, res, next) {
  doStuff();
});

You need to be aware that these middleware layers are running for HTTP requests but also for upgrade requests. Certain middleware layers should only run for HTTP or Upgrade requests. To make it possible you can add a http or upgrade property to the middleware function and set it to false if you don't want it to be triggered.

primus.use('name', function () {
  function middleware(req, res, next) {

  }

  middleware.upgrade = false; // Don't run this middleware for upgrades

  return middleware;
});

By default a new middleware layer is added after the previous one, but there are cases where you need to add a middleware at a specified index in the stack. To accomplish this you can use the optional 0 based index argument.

// add a middleware after the first two in the stack
primus.use('name', function (req, res) {

}, 2);

Primus.remove(name)

This method allows you to remove configured middleware. This works for the middleware layers that you added but also the middleware layers that we add by default. If you want to use a different way to serve the primus.js file you can simply:

primus.remove('primus.js');

And add your own middleware instead.

Primus.disable(name)

In addition to removing middleware layers, it's also possible to disable them so they are skipped when we iterate over the middleware layers. It might be useful to just disable certain middleware layers in production.

primus.disable('name');

Primus.enable(name)

Of course, when you can disable middleware there also needs to be way to enable them again. This is exactly what this method does. Re-enable a disabled middleware layer.

primus.enable('name');

Plugins

Primus was built as a low level interface where you can build your applications upon. At it's core, it's nothing more than something that passes messages back and forth between the client and server. To make it easier for developers to switch to Primus we've developed a simple but effective plugin system that allows you to extend Primus's functionality.

Plugins are added on the server side in the form of an Object:

//
// Require a plugin directly.
//
primus.plugin('name', require('metroplex'));

//
// Or supply it manually with the required object structure
//
primus.plugin('name', {
  server: function (primus, options) {},
  client: function (primus, options) {},
  library: 'client side library'
});

Or you can pass the plugin Object directly into the constructor:

var primus = new Primus(server, { plugin: {
  name: {
    server: function (primus, options) {},
    client: function (primus, options) {},
    library: 'client side library'
  }
}})

And last but not least, you can also supply the constructor with a comma or space separated list of plugin names which will be required automatically:

var primus = new Primus(server, { plugin: 'metroplex, primus-emit' })

To remove added plugins you can use the plugout method:

primus.plugin('name', require('metroplex'));
primus.plugout('name'); // returns true/false indicating successful removal.

The server function is only executed on the server side and receives 2 arguments:

  1. A reference to the initialised Primus server.
  2. The options that were passed in the new Primus(server, { options }) constructor. So the plugin can be configured through the same interface.

The client receives the same arguments:

  1. A reference to the initialised Primus client.
  2. The options that were passed in the new Primus(url, { options }) constructor. So the plugin can be configured through the same interface.

The only thing you need to remember is that the client is stored in the library using toString() so it cannot have any references outside the client's closure. But luckily, there's a library property that will also be included on the client side when it's specified. The library property should be an absolute path to the library file.

Intercepting the connection events

The connection event is emitted using a async emitter. It checks if your supplied event emitter function has extra callback function. When it detects this it will wait with the execution of the other assigned listeners until the callback has been called. Please note that the order of assigning event listeners is still respected so if you've assigned a connection listener before an async connection listener it will still be executed first.

primus.on('connection', function (spark) {
  console.log('first call, i have no spark.newproperty', spark.newproperty);
});

primus.on('connection', function (spark, next) {
  longrunningasynmethod(spark.query, function (err, data) {
    spark.newproperty = data;

    console.log('second call, i added the new property');
    next(err);
  });
});

primus.on('connection', function (spark) {
  console.log('third call, i can read the ', spark.newproperty);
});

When an error argument is supplied it will automatically end the connection and emit an error event on the spark. If you are coming from Socket.IO 1.0 >=, this will basically work the same way as their middleware system.

Extending the Spark / Socket

The server has a .Spark property that can be extended. This allows you to easily add new functionality to the socket. For example adding join room function would be as easy as:

primus.plugin('rooms', {
  server: function (primus) {
    var Spark = primus.Spark;

    Spark.prototype.join = function () {
      // implement room functionality.
    };
  }
});

Transforming and intercepting messages

Intercepting and transforming messages is something that a lot of plugins require. When you're building an EventEmitter plugin or something else you probably don't want the default data event to be emitted but your custom event. There are 2 different types of messages that can be transformed:

  1. incoming These messages are being received by the server.
  2. outgoing These messages are being sent to the client.

The transformer is available on both the client and the server and share, like you would have expected the same identical API. Adding a new transformer is relatively straightforward:

primus.transform('incoming', function (packet) {
  //
  // The packet.data contains the actual message that either received or
  // transformed.
  //

  // This would transform all incoming messages to foo;
  packet.data = 'foo';

  // If you are handling the message and want to prevent the `data` event from
  // happening, simply `return false` at the end of your function. No new
  // transformers will be called, and the event won't be emitted.
});

These transformations can easily be done in the plugins:

primus.plugin('name', {
  server: function (primus) {
    primus.transform('outgoing', function (packet) {
      packet.data = 'foo';
    });

    primus.transform('incoming', function (packet) {
      if (packet.data === 'foo') packet.data = 'bar';
    });
  },

  client: function (primus) {
    primus.transform('outgoing', function (packet) {
      packet.data = 'foo';
    });

    primus.transform('incoming', function (packet) {
      if (packet.data === 'foo') packet.data = 'bar';
    });
  }
});

We also expose asynchronous interfaces for these transformers. If your function accepts 2 arguments we automatically assume it's async and that the last argument is the callback variable:

primus.transform('outgoing', function (packet, next) {
  asyncprocess(packet.data, function (err, data) {
    //
    // If you return an error here, it will be emitted as `error` on the
    // spark/client and no `data` event will be emitted.
    //
    if (err) return next(err);

    //
    // If you just wanted to ignore this message instead of emitting an error
    // you can do:
    //
    if (err) return next(undefined, false);

    //
    // To update the data, just re-assign the `data` property on the packet you
    // received and call the next callback.
    //
    packet.data = data;
    next();
  });
});

Primus project plugins

The following plugins are part of the Primus project.

fortess-maximus
Fortress Maximus validates every incoming message on your Primus server as all user input should be seen as a potential security risk.
NPM versionBuild Status
metroplex
Metroplex is a Redis based spark/connection registry for Primus. It stores the sparks and their server address. So you can cluster multiple primus's together with Metroplex and Omega Supreme
NPM versionBuild Status
mirage
Mirage generates and validates persistent session IDs.
NPM versionBuild Status
omega-supreme
Omega Supreme allows you to broadcast messages to Primus using a regular HTTP request. These messages can be broacasted to every spark, single spark or a collection of sparks.
NPM versionBuild Status
primus-analytics
Integrates Primus with Google Analytics.
NPM versionBuild Status
primus-emit
The emit module adds client -> server and server -> client event emitting to Primus.
NPM versionBuild Status
substream
Substream is an opinionated but stream compatible connection multiplexer on top of the Primus connections. These streams can be created without pre-defining them on the server or client.
NPM versionBuild Status

Community plugins

These are also plugins created by our amazing community. If you want your module to be listed here, please open a pull request.

backbone.primus
Bind primus.io events to backbone models and collections.
Build Status
hapi_primus_sessions
A hapi and primus plugin which extends primus' spark with a `getSession(cb)` method which returns the current hapi session object.
NPM version
primus-cluster
Scale Primus across multiple servers or with node cluster.
NPM versionBuild Status
primus-emitter
A module that adds emitter capabilities to Primus.
NPM versionBuild Status
primus-express-session
Share a user session between Express and Primus.
NPM versionBuild Status
primus-multiplex
A module that adds multiplexing capabilities to Primus.
NPM versionBuild Status
primus-redis
primus-redis is a Redis store for Primus. It takes care of distributing messages to other instances using Redis Pub/Sub.
NPM versionBuild Status
primus-redis-rooms
primus-redis-rooms is a Redis store for Primus and primus-rooms.
NPM versionBuild Status
primus-resource
Define resources with auto-bound methods that can be called remotely on top of Primus.
NPM versionBuild Status
primus-responder
Client and server plugin that adds a request/response cycle to Primus.
NPM versionBuild Status
primus-rooms
A module that adds rooms capabilities to Primus. It's based on the rooms implementation of Socket.IO.
NPM versionBuild Status
primus-rooms-redis-adapter
A redis adapter for primus-rooms module. Supports integration with metroplex and omega-supreme.
NPM versionBuild Status
primus-spark-latency
Adds a latency property to primus sparks server-side.
NPM versionBuild Status

Community

Deployed Primus to production or built an awesome demo using the technology? We've set up a special wiki page for it where you can show your awesome creations or learn from demo and example applications how to use Primus. Checkout the wiki page at:

https://github.com/primus/primus/wiki/Production

FAQ

What is the best way to scale Primus

Scaling Primus is as simple as sticking it behind a load balancer that supports sticky sessions and run multiple versions of your application. This is a vital feature that your load balancer needs to support. This ensures that the incoming requests always go back to the same server. If your load balancer does not support sticky sessions, get another one. I highly recommend HAProxy. According to my own testing it is the fastest and best proxy available that supports WebSockets. See https://github.com/observing/balancerbattle for more detailed information.

The reason for which sticky-sessions are so important is that a lot of frameworks that use polling transports require to save a state in the node process in order to work correctly. This state contains times, sessions ids, handshake data etc. If a request from the same client does not enter the same node process it will be treated as an unknown request and your real-time connection will be closed.

If you want more advanced scaling and messaging please take a look at the various plugins we've written for this scope. Plugins like metroplex, omega-supreme and primacron can be time savers.

Can I use cluster?

Note: The following only applies to websocket emulation transformers like sockjs or engine.io. If you are using ws, uws or faye-websocket, there is no need for sticky sessions, and thus no issue.

The cluster module that ships with Node.js does not implement sticky sessions.

There are projects like stick-session which attempt to implement sticky-sessions in cluster, but the problem with this specific approach is that it uses the remoteAddress of the connection. For some people this isn't a problem but when you add this behind a load balancer the remote address will be set to the address of the load balancer that forwards the requests. So all in all it only causes more scalability problems instead of solving them. This is why we've opted to warn people about the risks of cluster when we detect that the Primus library is run in a worker environment. USE IT AT YOUR OWN RISK.

To turn off the cluster warning in your Primus instance you can set the option iknowclusterwillbreakconnections to true.

How do I use Primus with Express

Express' express() instance isn't a valid HTTP server. In order to make it work with Primus and other real-time transformers you need to feed the instance to a real http server and supply this server. See example below:

'use strict';

var express = require('express')
  , Primus = require('primus')
  , app = express();

//
// Do your express magic.
//

var server = require('http').createServer(app)
  , primus = new Primus(server, { options });

server.listen(port);

Is require.js supported

Require.js is supported to a certain degree. The primus.js core file should be compatible with require.js but it could be that the transformer of your choosing isn't compatible with require.js. For example engine.io uses component which introduces it's own require function that causes issues. In addition to that, there are plugins which might use these modules that break require.js. The general advice for this is to drop require.js in favour of plain script loading or use of browserify where possible. If you feel strong about require.js we accept pull requests that improve this behaviour or helps us save guard against these issues.

Can I send custom headers to the server

It is not possible to send custom headers from the client to the server. This is because these headers need to be set by the actual transports that the transformers are using. The only transport that would support this would be AJAX polling. To send custom data to the server use a query string in your connection URL, as this is something that all transports support.

var primus = new Primus('http://localhost:8080/?token=1&name=foo');

Versioning

History

You can discover the version history and change logs on the Releases page

Convention

All 0.x.x releases should be considered unstable and not ready for production. The version number is laid out as: major.minor.patch and tries to follow semver as closely as possible but this is how we use our version numbering:

major

A major and possible breaking change has been made in the primus core. These changes are not backwards compatible with older versions.

minor

New features are added or a big change has happened with one of the real-time libraries that we're supporting.

patch

A bug has been fixed, without any major internal and breaking changes.

Release cycle

There isn't a steady or monthly release cycle. We usually release a new version when:

  1. A critical bug is discovered.
  2. There have been a lot of minor changes.
  3. A framework did an incompatible update.
  4. A new framework is added.
  5. People ask for it.

Other languages

These projects are maintained by our valuable community and allow you to use Primus in a different language than JavaScript:

primus-objc
A client written in Objective-C for the Primus real-time framework with initial support for web sockets (via SocketRocket) and socket.io (via socket.IO-objc). Easily switch between different real-time Objective-C frameworks without any code changes.
Build Status
primus-android
A Primus client written in Java for Android with initial support for web sockets via AndroidAsync.

Want to have your project listed here? Add it using a pull-request!

Protocol

Primus uses some internal protocol messages in order to keep the connection open and stable between a client and a server. If you are planning on implementing Primus in another language you must handle the following primus::* prefixed messages:

  • primus::ping::<ping> server -> client, The ping type contains the time in EPOCH. Ping messages are needed to keep the connection open as certain load balancers, proxies and browsers will close connections automatically when there is inactivity.
  • primus::pong::<ping> client -> server, The pong is the response to the ping packet. It echoes back the exact value that it received.
  • primus::server::close server -> client, Indication that the server intentionally closed the connection and that no reconnection/connection should be made.
  • primus::id:: client -> server, Request of the internal spark.id that's assigned to the connection.
  • primus::id::<spark.id> server -> client, The internal id that we used on the server to identify the connection as we do not sync this information by default and requires a primus.id() call on the client.

Any other message that is prefixed with primus:: should be ignored and not emitted to the user.

License

MIT

primus's People

Contributors

3rd-eden avatar balupton avatar brycekahle avatar cayasso avatar davedoesdev avatar derek-watson avatar fishrock123 avatar greenkeeper[bot] avatar japhar81 avatar jcrugzz avatar jimihford avatar jucrouzet avatar kinsi55 avatar lholznagel avatar lloydwatkin avatar lpinca avatar marcelklehr avatar mariocasciaro avatar mgalgs avatar mlebrun avatar mrdnk avatar readmecritic avatar strml avatar swaagie avatar swissmanu avatar thapar avatar timbur avatar trshafer avatar unetworkingab avatar zemirco avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

primus's Issues

new home, I see...

Hello Arnout,

Yep, Primus is looking better and better. I've been hacking around on a jcw/primus-live project which I'd be happy to migrate/donate to this new primus area if you want it. Young, but already in constant use here for development of an AngularJS + LevelDB real-time app. See npm info primus-live or the intro docs.

For me, this replaces an earlier design based on SocketStream and Nodemon, and does it all in just a hundred lines of code. Long live modularity.

-jcw

Basic Auth in primus

So I have been working on adding basic auth to primus and have ran into a really odd issue. After successfully implementing basic auth in ws I made the changes found here and began to test. I discovered that the authorization header was somehow stripped away from the request.

When implementing auth for ws i found that this function correctly received the authorization when used as a standalone module. This same function when used in primus does not include the header. I've tried adding logging to every possible place that handles the upgrade request and none of them have the authorization header when using some simple test code.

@3rd-Eden any ideas? I'm thinking it may have something to do with how the upgrade listeners are overwritten but that is just my shot in the dark.

Websocket.js 'not opened' stops Primus

Hard to know the exact course of events, but flaky server-client connections regularly end up stopping the server with Exception:

/.../node_modules/ws/lib/WebSocket.js:187
    else throw new Error('not opened');
               ^
Error: not opened
    at WebSocket.send (/.../node_modules/ws/lib/WebSocket.js:187:16)
    at Sparky.write (/.../node_modules/primus/transformers/websockets/server.js:36:53)
    at Sparky.EventEmitter.emit (events.js:95:17)
    at encoded (/.../node_modules/primus/spark.js:211:11)
    at Sparky._write (/.../node_modules/primus/spark.js:191:10)
    at Sparky.write (/.../node_modules/primus/spark.js:177:8)
    at forEach (/.../node_modules/primus/index.js:253:11)
    at Primus.forEach (/.../node_modules/primus/index.js:239:5)
    at Primus.write (/.../node_modules/primus/index.js:252:8)

Is this bug or a feature? IMHO default behavior on the server side should not be 'give everything up and die'.

Meanwhile: how can I guard against this error mechanism?

socket.io transport with a nodejs client issues

I am trying to connect to a server running primus using the transformer socket.io.
When I connect using the example browser based code all is fine, but when I connect from NodeJS (using the example code slightly altered) I get an error of:

events.js:74
        throw TypeError('Uncaught, unspecified "error" event.');
              ^
TypeError: Uncaught, unspecified "error" event.
    at TypeError (<anonymous>)
    at Primus.EventEmitter.emit (events.js:74:15)
    at Primus.client (primus.js:946:29)
    at Primus.initalise [as initialise] (primus.js:479:10)
    at new Primus (primus.js:227:10)
    at Object.<anonymous> (/Users/mattapperson/GitHub/tests/user-test.js:47:14)
    at Module._compile (module.js:456:26)
    at Object.Module._extensions..js (module.js:474:10)
    at Module.load (module.js:356:32)
    at Function.Module._load (module.js:312:12)
DEBUG: Program node user-test.js exited with code 8

Here is the code of user-test.js

'use strict';

//
// Require all dependencies.
//
var Primus = require('primus')
  , Socket;

//
// Create a socket that's compatible with the given parser and transformer.
//
Socket = Primus.createSocket({
      transformer: 'socket.io',
      parser: 'JSON'
});

//
// Open a connection to the server
//
var socket = new Socket('http://primus-example.nodejitsu.com');

socket.on('open', function open() {
  console.log('The connection has been opened.');
}).on('end', function end() {
  console.log('The connection has been closed.');
}).on('reconnecting', function reconnecting(opts) {
  console.log('We are scheduling a reconnect operation', opts);
}).on('data', function incoming(data) {
  console.log('Received some data', data);
});

Access spark from transformer?

hi arnout

i'm writing a plugin for primus which allows to await a response when sending a message to the server: https://github.com/swissmanu/primus-responder

an example on the client side of the wip-version:

primus.writeAndWait('my-super-important-data', function(response) {
    console.log('i got a response on my write. async baby.', response);
});

i'm now at the point where i want to access the spark which delivered the data for my transformer. at the moment, i have to broadcast to all sparks (see https://github.com/swissmanu/primus-responder/blob/master/lib/server/index.js#L11) instead to the one which initiated the transformer chain execution.

was it a a design decision with intent to not allow access to the spark from inside a transformer? or is there a way to access which i just don't see? (even looked in your source without success ;) )

thank you & cheers,
manuel

TypeError: object is not a function

Just installed Primus and tried with example server but gets this error:

server.js:7
var primus = new Primus(server, { parser: 'JSON' });
                   ^
TypeError: object is not a function
    at Object.<anonymous> (server.js:7:14)
    at Module._compile (module.js:456:26)
    at Object.Module._extensions..js (module.js:474:10)
    at Module.load (module.js:356:32)
    at Function.Module._load (module.js:312:12)
    at Function.Module.runMain (module.js:497:10)
    at startup (node.js:119:16)
    at node.js:901:3

Messages lost on mac os x

(Enter Mac OS X 10.8) When I do socket.write within interval (doesn't really matter how often, I tried 200ms and 1500ms โ€“ same result) other messages won't be delivered to clients, until interval is cleared. I've tried socket.io and engine.io transports with same results. Same interval with socket.write commented out doesn't prevent other messages to be sent to the client.

When I carbon copied the whole thing to Debian 6 everything worked perfectly.

PS. Tested with Google Chrome 30.0.

Socket reconnection

I have been experimenting with Primus using the websockets transformer and here is some feedback:

  1. I've noticed that upon a successful reconnection, the number of attempts is not reset. This means that the time the next time a reconnection is necessary, the delay can be potentially very high.
  2. Since the reconnect event is only dispatched after a reconnect attempt has run, this means that in terms of UI there is no way to tell the user immediately that the connection to the server has been lost. If the current delay is high, it might take a lot of time to receive a reconnect event. I suggest emitting another event to provide realtime feedback to the user.
  3. Using nginx as a reverse proxy results on a disconnect/connect event every minute or so. I'm not sure if this is related to Primus itself or the underlaying transformers.

Nodeschool workshop

http://nodeschool.io is an awesome project. It already contains some lessons on using node.js, streams, leveldb and basic brogramming. We should leverage this platform and write some lessons on how to use primus. These lessons could include:

  • Creating your first primus instance
  • Switching between transformers and encoders
  • Simple ping/pong application
  • Broadcasting
  • Building a chat room
  • Plugins
  • Scaling

If you got any idea's on the lessons please post them here.

Remove the `remotePort` property

We store the remote port property in Primus but this is useless as it's most likely a port number from a old request and not from the most recent request.

A better Primus.Socket for Node.js

The current limitation of the client is that it can only be created of an initialised Primus instance. This is done because it needs to know what kind of transformer and encoder it should use for a connection. This makes connecting from a different Node.js processes.

We can add a createClient method which accepts an object:

var primus = require('primus')
var Socket = primus.createClient({ transformer: 'foo', parser: 'bar' });

The only problem that this still requires knowledge about the used transport and parser. There isn't really a way around that without creating a separate client for the server which would be less than ideal but possible. We can introduce a /<path-name>/spec route which outputs a JSON documentation with the used transformer and parser.

People could then write a small wrapper around it if needed:

var request = require('request');

request('http://server.com/pathname/spec', function (err, res, body) {
  var Socket = primus.createClient(body);


});

cc @jcrugzz

autoClose & some engine.io notes

Small notes about the example that comes with Primus...

The "autoClose: false" option in the example should be "end: false" (at least on Node.js 0.10.17 - see docs).

Also, the example's index.html has nodejitsu hard-coded, which puzzled me until I changed it to localhost:8080 (I was getting errors due to domain when trying this out with engine.io). Might be worth a note in the example's README?

Lastly, I wonder whether the engine.io patch is really needed (closing a ws is not the same as closing the tcp connection which it shares). The main use case I see is the server restarting, and that seems to work & reconnect fine with the official engine.io package.

Missing close event with XHR

When you use the XHR-polling transport of socket.io with Primus and the Primus client lib, the connection never receives a close / end event whenever I kill the node. It does receive an open event whenever the node is back up.

Is this intentional / normal behavior?

Roadmap

Primus has grown into a framework we can all depend upon in our daily lives. It is and will always be an innovative way to work and build real-time applications.

Feature & API freeze

The base of Primus is feature complete. Additional logic and features can easily be added on top of Primus through the plugin interface. The only features that might be added in the future would be those that improve the connectivity and stability of Primus or an enhancement of the currently available methods. One of these features might be:

  • Port degrading or upgrading for WebSocket connections.
  • Upgrading and downgrading between transformers using 1 single JS file.

New transformers

Transformers are the most important part about Primus. They are the engines that Primus rides upon. We're currently supporting some of the most popular real-time frameworks:

  • Engine.IO
  • Socket.IO
  • SockJS
  • WebSockets
  • Browserchannel

But there a lot more frameworks and transports available that we're not yet supporting. In the future we should add support for:

With the addition of Flashsockets we should also start supporting them in:

  • Engine.IO
  • Socket.IO

As they've been disabled because of the engineering overhead it takes to get this running smoothly. Currently all real-time systems require you to use your own Node.js powered real-time server. The ultimate goal for this project is to prevent lock-in, this includes lock-in from Node.js. In order to support that we can add support for lightstreamer. They provide a node.js module to interact with their services so in addition to the 2 transformers listed above, we should start with integrating cloud solutions:

Better static support

Serving files from the /primus pathname is really convenient, we should optimize our file serving and make it easier to add files. Which is needed for serving the flash files and additional libraries for the FlashSocket transport.

Tests

There is nothing more important in this project then tests. While we have great coverage already, we can still improve this. I've setup a sauselabs account so we can have automated browser testing. We've tried using testling before, but it failed misserably and because it's build on browserify, the stacktraces and the error feedback that you get from it is completely useless. So this needs to addressed

  • Run client tests on saus labs
  • Write more tests for the clients
  • Write even more tests
  • Write integration tests

Website

We've purchased the primus.io domain name, we should add a proper website with documentation and API references for the client, spark and server interface as wel as a list of primus modules.

  • Run the application @nodejitsu
  • Design a website
  • Add API reference material
  • Add another working demo of Primus.

Iterating over arrays with `for ... in` causes unexpected behavior in some scenarios

Iterating over arrays with for ... in breaks if additional functionality is added to Array.prototype.

// some polyfill or extension lib adds additional functionality
Array.prototype.first = function(){ return this[0] }

// your code tries to iterate with the for loop, causing undesirable behavior 
for( x in [1,2] ){ console.log(x) }
// (keys)
//=> 0
//=> 1
//=> first   <-- problematic

// forEach (and map) still work as expected
[1,2].forEach(function(x) { console.log(x) })
// (values)
//=> 1
//=> 2

Allow passing a function as a plugin

Hey @3rd-Eden I faced this issue where I wanted to pass a Constructor Function as plugin.

But Primus only accepts objects for passing as argument.
https://github.com/3rd-Eden/primus/blob/master/index.js#L429

// rooms.js
function Rooms() {
  // some awesome code
}

Rooms.server = function () { /* Server stuff for primus only */}
Rooms.client = function () { /* Client stuff for primus only */ }

module.exports = Rooms;

Then:

// app.js
var Rooms = require('rooms');

// then use as plugin
primus.use('rooms', Rooms)

// but also use as standalone
var room = new Rooms(options);

Would be great to add this little convenient change :) for avoiding a bunch of 'exports.'

Best,

JB

Context aware asserts

For the methods that require access to this we should add an this instanceof Primus check to ensure that our methods are called with the correct context.

This can be a cause of memory leaks and other nasty bugs that go un noticed until.

cc @Swaagie

Atmosphere Framework support

At the moment, Primus supports mostly node.js-driven realtime web protocols (except SockJS which is supported by Java-framework, vert.x). It would be very cool if Primus supports communication protocol used in popular Atmosphere Framework. It will open new perspectives for Java developers.

P.S. Thank you for this very interesting project! )

Mixup in the README

README.md says this about using client side code for sockjs:

If yo want to use the client interface inside of Node.js you also need to
install the sockjs-client-node module:

npm install socket.io-client --save

That reference to socket.io-client seems like a cut-and-paste error?

Primus and requireJS don't work (no error)

With requireJS I don't know how to make Primus works...
Before to open this issue, I tried to import it directly with a <script> tag in my html file and it works fine.

But not when I use it with requireJS. I though that I was doing something wrong but the problem is there is no errors in the console, all looks greate, but nothings respond to my primus.emit('test', 'write this'), etc...

Please, look at my client code:

require.config({
    baseUrl: 'app/',
    paths: {
        'Primus' : 'js/libs/primus-sockjs_toRefresh'
    }
});

require([
    'Primus',
], function(Primus){

    var primus = new Primus('http://localhost:4000/?query=string');

    primus.emit('test', 'write this');

    primus.on('hello', function(msg){
      console.log(msg);
    });

    primus.on('test2', function(msg){
      console.log(msg);
    });
});

And in the console, no error:

GET http://localhost:4000/primus/info 200 OK 3ms

If you want my server side code:

var server = require('http').createServer(_handler);

var Primus = require('primus.io'),
    primus = new Primus(server, { transformer: 'sockjs', parser: 'JSON' });

primus.on('connection', function connection(spark) {
  console.log('new connection');

  spark.emit('hello', 'world');

  spark.emit('test2', 'text to write');

  spark.on('test', function (msg) {
      console.log(msg);
  });
});

server.listen(4000);
console.log('Server running at http://127.0.0.1:4000/');

Note that if i do a console.log(Primus) in the client I can see the good function Prime(), so I don't understand why it don't workd (it seems that the server is not bind with the js primus file...)

npm release cycle

whats the current primus npm release cycle? what are the conditions that must be met so the version is bumped and published to npm? I'm eager to work with Primus using the last bells and whistles that were added to the master branch, but npm only uses version 1.2.0 (and which is the last version tagged). Hoping to hear from (I'm clearly very excited about this framework lol)

Catch-all http server breaks SockJS (and vice versa)

When the http server reacts on all addresses (including the Primus.pathname path "/primus"), things don't work:

var Primus = require('primus')
  , http = require('http');

var server = http.createServer(function (req, res) {
  res.writeHead(200, {'Content-Type': 'text/plain'});
  res.end('Hello World\n');
});

var primus = new Primus(server, { transformer: 'sockjs' });

server.listen(3000);
  • http://localhost:3000/ correctly returns "Hello World"
  • http://localhost:3000/primus also returns "Hello World"
  • http://localhost:3000/primus/ (put anything behind "primus", e.g. "primus/info") breaks the server:
http.js:704
    throw new Error('Can\'t set headers after they are sent.');
          ^
Error: Can't set headers after they are sent.
    at ServerResponse.OutgoingMessage.setHeader (http.js:704:11)
    at App.handle_404 (/Users/nico/tmp/node_modules/sockjs/lib/sockjs.js:59:11)
    at Listener.webjs_handler (/Users/nico/tmp/node_modules/sockjs/lib/webjs.js:105:28)
    at Listener.handler (/Users/nico/tmp/node_modules/sockjs/lib/sockjs.js:141:12)
    at Listener.handler (/Users/nico/tmp/node_modules/sockjs/lib/sockjs.js:5:61)
    at null.<anonymous> (/Users/nico/tmp/node_modules/sockjs/lib/sockjs.js:148:22)
    at new_handler (/Users/nico/tmp/node_modules/sockjs/lib/utils.js:82:19)
    at EventEmitter.emit (events.js:106:17)
    at request (/Users/nico/tmp/node_modules/primus/transformer.js:83:8)
    at Server.EventEmitter.emit (events.js:117:20)

When removing the default request handler function, the server correctly forwards the "/primus" requests to SockJS, but hangs on any other request (as there is no handler for other URLs).

Primus should handle all requests that match pathname (and then remove other handlers for these requests), and leave all other requests alone, so we can add a default request handler (that returns error 400 or something like that).

Test for backoff function

I made a change here 975bb9f that prevented the options object from being modified (which holds the info for reconnect attempts) in the case where we want to ignore a subsequent backoff call. This needs a test.

Two-way SSL

I need to use two-way SSL to secure the communications between clients (Spark) and server, and also identity the clients (authentication & authorization). I would like to have the ability to verify incoming connections on the server side (thus, have access to the HTTP request).

Right now, I initialize my server this way:

var options = {
  key: fs.readFileSync('localhost.key'),
  cert: fs.readFileSync('localhost.crt'),
  ca: fs.readFileSync('cacert.crt'),
  requestCert: true,
  rejectUnauthorized: false
};

var app = express();
var server = https.createServer(options, app);
var primus = new Primus(server, { transformer: 'websockets' });

And this is how I initialize my client:

var Socket = Primus.createSocket({ transformer: 'websockets' });
var socket = new Socket('https://localhost:8443');

Of course, this doesn't work since I don't have a way to specify my client certificate and private key. Upon connection, the client gets denied by the server because no certificate was given. The reconnect process starts.

To add support for two-way SSL, it would require:

  • Ability to specify a "verifyClient" function on WebSocket server creation, returning true or false to accept/refuse the connection. Use the auth hook. But, can we set the identity/username in the Spark object to know who the user is?
  • Ability to specify the client cert and private key on the Socket; and set rejectUnauthorized to false for self-signed cert.

I guess that not all transformers supports that, but I'm not sure.

Any input?

References:

Force no websockets

Is there a way to pass the options to the transformer. I basically want to NOT use a websocket connection when using engine.io. I tried setting AVOID_WEBSOCKETS my self but that didn't seem to take.

What is the best way to extend the client in Primus

Primus use primus.library() function and or primus.Socket to get access to the client code, both get the code from primus.js + some magic.

What is the best way you recommend to extend this code for both the Node and Browser client?

Best,

JB

High CPU usage on engine.io client

Hey, I'm not sure if this is the right repo to open this issue against, but I've been using primus with engine.io, and seeing massive (100%) cpu usage listening on a few rooms and emitting events with primus-rooms and primus-emitter.

I haven't been able to figure out why with any more detail, but I've found that switching off engine.io appears to fix this problem.

I'm happy to provide more specifics if this is something you guys are interested in.

Requirejs and Client code don't play nice

Using requirejs to load the client code and it gets tripped up when trying to require('url').parse on line 184 of primus.js

screen shot 2013-08-07 at 5 29 09 pm

If I remove everything except the content within the catch statement, everything works fine. Can we make an option just to force the DOM implementation of parse? That way it essentially works the same, unless you specify that you'd prefer DOM implementation instead?

I can make the pull request if you think it's worth it.

parser-error vs user-error

I noticed that not just parser errors get handled nicely and emitted (if anyone is listening) but actually any exception that may occur within a "data" event handler. Whether this is an issue or not boils down to opinion, but might be worth to note it somewhere in the docs that error handling is not just limited to server/socket/parser errors.

spark.on('data', function (data) {
  throw new Error('oh noes!');
});

support a `binary` parser

So it would be awesome if I was able to do the following:

//
// Example is just in node but we would want to work in 
// either environment except from a different source in browser
//
var fs = require('fs');
var path = require('path');
var primus = require('primus');


var Socket = primus.createSocket({ parser: 'binary'});
var socket = new Socket('https://my-web-service.jit.su');

var file = path.join(__dirname + '/movie.mp4');
fs.createReadStream(file).pipe(socket);

I would then be able to easily pipe that stream to another destination on the server.

@3rd-Eden Thoughts? And also would this be possible over transports besides websockets?

plugins browser side problems.

Hi, i have a problem using Primus with plugins on browser side.

The server part works fine but the client library, generated with the following code

primus.use 'emitter', emitter
primus.use 'responder', responder
primus.use 'multiplex', multiplex
primus.use 'rooms', rooms
primus.save __dirname + '/primus.js'

and then included with require.js shows this error:

ReferenceError: Primus is not defined
parse = Primus.require('url').parse;

Primus is defined, but the function "require" returns undefined and so accessing to "parse" throws the error.
It seems it tries to load the 'url' node module on the browser side.
Is it possible?

I'm also using require.js 2.1.8, could it be a version problem?

Thanks.

plugins for client-side code Q

(Please let me know if this is not the right place to ask (newbie) questions)

I'm using the library: field to get client-side code into the browser, and it's working well, just as convenient as server: is for the server end.

Many client-side scripts use and set globals, such as AngularJS, which only needs a global "angular" variable - all the app code lives in modules, and they register themselves with "angular.module(...)". So far, so good. I just set client: to a dummy function in this case, as it's not needed (but Primus seems to insist on it to include the library part).

But I also have a few modules which are more CommonJS style, i.e. defined as "module.exports = function(...) { ... }".

What's the best way to integrate these into Primus? I'd rather keep the source code as is, so one thing which comes to mind is to add a bit of boilerplate to "register" them. Feels a bit like re-inventing wheels, though.

The other thing is that I need to do part of the init after the Primus object has been created on the client, i.e. after all of /primus/primus.js has been loaded.

Is there some clever way to pass the bulk of the client code via library: and then somehow get hold of it when the stringified client: entry gets called? From what I see, the client entries get compiled before the library entries right now.

-jcw

plugin .library should include the file automatically

the plugin code:

module.exports = {
  server: function(){},
  client: function(){},
  library: require('fs').readFileSync(require.resolve('./angular/primus.js'), 'utf-8')  // could be simplified
};

the library member should automatically require the files, for example, if you pass an array, like this:

module.exports = {
  server: function(){},
  client: function(){},
  library: ['./angular/primus.js','another/lib.js']
};

the path should resolved using require.resolve each file and concat them. This way the, Primus can distinguish between paths and a 'raw' library string. Could it be done?

Can I expect these code to not change in near future?

Hey @3rd-Eden I'm writing an AngularJS "magic" plugin for model synchronization, and I want to know, for the near future, is it safe to rely on accessing the ark parameter on the primus instance for accessing my own plugin (since it's private)? (in this case, primus.ark.angularPrimus). the existing primus plugins (that @cayasso made) create a new namespace on primus.$, like a shortcut to access the plugins, but I think defining a defacto standard or even an official way to do this is important for the health of the plugins that will come to Primus.

The thing is, I don't want to modify Primus.prototype directly, for obvious reasons, if any plugin come with same name method, they will conflict. for example, get and update are pretty common names that have a great chance for collision if injected into primus prototype. So I'd like a position, and thanks for this project, it's great, hope to see it growing.

Client port number issue

When running my code using grunt on port 4000 (for example), then requests from the Primus client library always add this port number on.

i.e. I see the primus client loaded from https://xmpp-ftw.jit.su but when doing this:

    var socket = new Primus('https://xmpp-ftw.jit.su')

I see requests to connect going to https://xmpp-ftw.jit.su:4000/primus/1/?t=xxxxx.

Additionally specifying 'https://xmpp-ftw.jit.su:443/' results in 443 being overwritten back to 4000.
Passing a second parameter with options host and port seem to get overwritten also (placing a breakpoint in the client code and editing config manually gives me a socket connection).

Using socket.io as the engine. Using the emitter plugin.

Piping and MQTT.js

From the README this library claims that is based on Streams, but I see no 'pipe' method.

I'm building a MQTT-over-WebSocket solution based on MQTT.js and Mosca , and I'd like to build a MQTT-over-Primus thing.
This will provide a full pub/sub protocol with offline and retained messages capabilities.

The MQTT.js library is now built on streams and piping, so that's why I'm asking if there is any plan to support piping.
There is also http://git.eclipse.org/c/paho/org.eclipse.paho.mqtt.javascript.git/tree/src/mqttws31.js, which is smaller and might be adapted.

What's the best route?

Make saving the library easier.

Manually saving the library can a bit of a pain. We should make it easier to developers to save the library to disk.

  1. We can add a /primus.js route and send the compiled library. This would be great for testing and makes it significantly easier to get started with primus.
  2. Another option would be adding Primus#save(path) method where we will just save the compiled library to disk.

SockJS Not Reconnecting

I hate to keep bugging you guys, but I'm having another problem.

As I understand it, SockJS does not handle reconnections. But Primus does, right?

I just moved to using SockJS (from socketio then engineio), but now my site doesn't reconnect automatically if the network fails for a moment. Is Primus supposed to handle this reconnection? Or is this something I need to do manually since SockJS doesn't support it?

Here's a link a live example:
https://app.draftnight.com/league/draft/board

Ability to do custom auth on primus connection

Based on our IRC discussion, it seems that having the ability to register a function on the primus instance that accepts the request object and a callback (or something similar) in order to asynchronously handle the auth would be awesome.

Changelog?

I'd like a document that details the changes so I know whether or not my code will be compatible with upgrades and possible gotchas and new goodies to look for.

forwarded-for

We're automatically transforming the x-forwarded-for headers for users when we detect the presence of these headers. But we don't check if these values are coming from a known and white listed proxy server and if it's an real-ip address.

  • Implement a whitelist for x-forwarded-for checking
  • Scan the value using the net.isIP method

I'm not aware of any exploits regarding this (but maybe @evilpacket knows this) but it should be fixed anyways.

How to destroy primus on the server-side?

We're adding more thorough destroying of DocPad instances, and as the livereload docpad plugin uses primus it makes sense to make sure primus destroys as well. How do I go about destroying primus? I couldn't find something like primus.destroy()

Auth not working?

Send GET request http://localhost:3000/primus/info response 401 (Unauthorized) with body {error:"Error auth"} then there is reconnected. There is no way to track down the error, as it occurs at the level xhr request (server response)

Example code:

    primus.authorize(function (data, accept) {
        accept(new Error('Error auth'));
    });

Problems serving engine.io client

For flashsocket fallback in engine.io, you need to serve a .swf file, too. The docs don't appear to capture this nuance in setting up the client for this transformer.

Can we make the docs better or have a cakefile that builds the complete client, etc?

Rapid Reconnections In Firefox

I recently moved to Primus/Engine.IO from Socket.IO and it's been excellent.

Today a user reported that he kept seeing a "Connecting" message flash on & off. We listen for connection changes and show an error if they're disconnected. He's on a WiFi connection running iPad OS 6.1.3.

I was able to reproduce it on Firefox 23.0.1 on Windows 7. Here's a live link to the page where it happens:
http://draft.bz/draft

Looking at the console logs, it's clearly trying to reconnect multiple times per second. I've linked a gist of what gets logged over and over, but here's a quick version:

"NetworkError: 400 Bad Request - https://mysite.com/primus/?EIO=2&transport=polling&sid=VV_2AW6uTCxS_ADqAAMt"

Firefox can't establish a connection to the server at wss://mysite.com/primus/?EIO=2&transport=websocket&sid=VV_2AW6uTCxS_ADqAAMt.

The connection to wss://mysite.com/primus/?EIO=2&transport=websocket&sid=VV_2AW6uTCxS_ADqAAMt was interrupted while the page was loading.

GET https://mysite.com/primus/?EIO=2&transport=polling 200 OK

GET https://mysite.com/primus/?EIO=2&transport=polling&sid=Z8GdxtIynXXnjdFOAAMu  400 Bad Request

https://gist.github.com/bendytree/80b0e257e9868c522957

Any ideas what could be up? Obviously this could be a problem with Engine.IO or my code, but it does seem to work on most browsers. And shouldn't Primus prevent quick reconnects? From what I can tell, it happens about 2 times per second.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.