Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

eva-events

Package Overview
Dependencies
Maintainers
1
Versions
6
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

eva-events - npm Package Compare versions

Comparing version 0.2.1 to 0.3.1

4

index.js

@@ -14,5 +14,5 @@ 'use strict'

exports = module.exports = require('./lib/eva-application')
exports.Client = Eva
exports = module.exports = Eva
exports.Application = require('./lib/eva-application')
exports.Promise = require('bluebird')
exports.version = require('./lib/version').number
'use strict'
var urlParse = require('url').parse
var WebSocket = require('ws')
var EventEmitter = require('events')
var NetServer = require('net').Server
var http = require('http')
var WebSocketServer = require('ws').Server
var Eva = require('./eva')
var version = require('./version')
function EvaApplication(server, path, handler, verifyClient) {
if (!(this instanceof EvaApplication)) {return new EvaApplication(server, path, handler, verifyClient)}
if (typeof path === 'function') {
verifyClient = handler
handler = path
function EvaApplication(server, path, options) {
if (!(this instanceof EvaApplication)) {
return new EvaApplication(server, path, options)
}
if (typeof server === 'number' && isFinite(server)) {
this.server = server = http.createServer(function (req, res) {
var body = http.STATUS_CODES[426];
res.writeHead(426, {
'Content-Length': body.length,
'Content-Type': 'text/plain'
});
res.end(body);
}).listen(~~server)
}
if (!(server instanceof NetServer)) {
throw TypeError('Invalid argument. A server object must be provided.')
}
// Allow options as second argument.
if (typeof path === 'object' && path !== null) {
options = path
path = '/'
}
// Default arguments.
path = path ? String(path) : '/'
if (path[0] !== '/') {path = '/' + path}
options = options || {}
// Inherit from EventEmitter.
EventEmitter.call(this)
// Private variables.
var self = this
var useHeartbeat = options.useHeartbeat == null ? true : !!options.useHeartbeat
this._locked = false
this._originalHandleUpgrade = undefined
this._clients = []
this._handlers = typeof handler === 'function' ? handler : null
this._multipleHandlers = false
var onClose = function () {
var index = self._clients.indexOf(this)
index === -1 || self._clients.splice(index, 1)
}
var handle = function (handler) {handler(this)}
this._wss = new WebSocket.Server({
// WebSocket Server attachment.
this._wss = new WebSocketServer({
server: server,

@@ -35,3 +57,3 @@ path: path,

clientTracking: false,
verifyClient: verifyClient
verifyClient: options.verifyClient
})

@@ -41,12 +63,26 @@ this._wss.on('connection', function (ws) {

self._clients.push(client)
client.on('killed', onClose)
if (self._multipleHandlers) {
self._handlers.forEach(handle, client)
} else if (self._handlers) {
;(0, self._handlers)(client) // Dereference `self`.
client.on('killed', onClientKilled)
if (useHeartbeat) {
client._heartbeatTimer = setInterval(newHeartbeatTimer(client), 55000)
}
self.emit('client', client)
})
this._rejectFuture = function () {
var original = self._wss.handleUpgrade
self._wss.handleUpgrade = function (req, socket) {
// Client tracking.
function onClientKilled() {
var index = self._clients.indexOf(this)
if (index >= 0) {
self._clients.splice(index, 1)
if (self._locked && self._clients.length === 0) {
self.emit('vacant')
}
}
}
}
EvaApplication.prototype = {
lock: function () {
if (this._locked) {return}
this._locked = true
var original = this._originalHandleUpgrade = this._wss.handleUpgrade
this._wss.handleUpgrade = function (req, socket) {
var u = urlParse(req.url)

@@ -59,30 +95,12 @@ if (u && u.pathname === path) {

}
self._rejectFuture = function () {}
}
}
EvaApplication.prototype = {
addHandler: function (handler) {
if (this._multipleHandlers) {
this._handlers.push(handler)
} else if (this._handlers) {
this._multipleHandlers = true
this._handlers = [this._handlers, handler]
} else {
this._handlers = handler
if (this._clients.length === 0) {
this.emit('vacant')
}
return this
},
removeHandler: function (handler) {
if (this._multipleHandlers) {
var index = this._handlers.indexOf(handler)
if (index >= 0) {
this._handlers.splice(index, 1)
if (this._handlers.length < 2) {
this._multipleHandlers = false
this._handlers = this._handlers[0]
}
}
} else if (this._handlers === handler) {
this._handlers = null
}
unlock: function () {
if (!this._locked) {return}
this._locked = false
this._wss.handleUpgrade = this._originalHandleUpgrade
this._originalHandleUpgrade = undefined
return this

@@ -94,3 +112,3 @@ },

destroy: function () {
this._rejectFuture()
this.lock()
forEach(this._clients, Eva.prototype.destroy)

@@ -100,3 +118,3 @@ return this

kill: function () {
this._rejectFuture()
this.lock()
forEach(this._clients, Eva.prototype.kill)

@@ -106,3 +124,3 @@ return this

done: function () {
this._rejectFuture()
this.lock()
forEach(this._clients, Eva.prototype.done)

@@ -114,7 +132,11 @@ return this

for (var i=0, len=clients.length; i<len; i++) {
clients[i].isReady && clients[i].send(eventName, data)
clients[i].isReady && clients[i].emit(eventName, data)
}
return this
},
get isLocked () {
return this._locked
}
}
require('util').inherits(EvaApplication, EventEmitter)
module.exports = EvaApplication

@@ -128,2 +150,18 @@

function newHeartbeatTimer(client) {
client._ws.on('pong', function () {
client._pendingPings = 0
})
return function () {
if (client._pendingPings++ >= 10) {
if (!client._fatalError) {
client._fatalError = new Error('Remote endpoint failed to reciprocate a heartbeat after 10 tries.')
}
client.destroy()
} else {
client._ws.ping()
}
}
}
// Better performance than array.slice().

@@ -130,0 +168,0 @@ function copy(arr) {

@@ -9,3 +9,2 @@ 'use strict'

var isBrowser = require('./platform').isBrowser
var PASSTHROUGH = function (x) {return x}
var STATES = {

@@ -40,2 +39,4 @@ CONNECTING: 0,

this._finalBufferedAmount = 0
this._heartbeatTimer = null
this._pendingPings = 0

@@ -55,3 +56,9 @@ // Make sure Eva doesn't receive Blobs.

if (!isBrowser) {
catchCloseFrame(this._ws._receiver, onclose)
if (ws.readyState === ws.OPEN) {
catchCloseFrame(self._ws._receiver, onclose)
} else {
ws.on('open', function () {
catchCloseFrame(self._ws._receiver, onclose)
})
}
}

@@ -76,3 +83,3 @@

if (message.flags.isResponse) {
fulfill(self, message.data, message.UID)
fulfill(self, message.data, message.UID, false)
self._partnerIsConcerned && checkOurHappiness(self)

@@ -83,7 +90,12 @@ } else if (message.UID === 0xffffffff) {

checkOurHappiness(self)
} else if (message.UID > 0) {
emitFirst(self._external, message.eventName, message.data, message.UID)
} else {
self._external.emit(message.eventName, message.data, message.UID)
self._external.emit(message.eventName, message.data, 0)
}
})
// Protect against unsolicited `error` events.
this._external.on('error', function () {})
// Indicate when Eva is ready.

@@ -112,4 +124,8 @@ if (ws.readyState === ws.OPEN) {

this._state = STATES.DONE
if (this._heartbeatTimer) {
clearInterval(this._heartbeatTimer)
this._heartbeatTimer = null
}
if (this._fatalError) {
this.on('error', function () {})
this._internal.on('error', function () {})
this._internal.emit('error', this._fatalError)

@@ -157,3 +173,3 @@ }

},
emit: function (eventName, data) {
run: function (eventName, data) {
eventName = validateNewEvent(this, eventName)

@@ -174,3 +190,3 @@ var UID = this._nextUID

},
send: function (eventName, data) {
emit: function (eventName, data) {
eventName = validateNewEvent(this, eventName)

@@ -181,3 +197,3 @@ this._ws.send(BeepBoop.write(eventName, data, 0))

on: function (eventName, listener) {
return delegateEventListener(this, eventName, listener)
return delegateEventListener(this, eventName, listener, false)
},

@@ -188,10 +204,8 @@ once: function (eventName, listener) {

removeListener: function (eventName, listener) {
return delegateListenerRemoval(this, false, eventName, listener)
},
removeAllListeners: function (eventName) {
if (arguments.length > 0) {
return delegateListenerRemoval(this, true, eventName)
eventName = BeepBoop.getEventName(eventName)
if (isForbiddenEvent(eventName)) {
throw new SyntaxError('Eva does not support the "' + eventName + '" event.')
}
this._internal && this._internal.removeAllListeners()
this._external && this._external.removeAllListeners()
var emitter = isInternalEvent(eventName) ? this._internal : this._external
emitter && emitter.removeListener(eventName, listener)
return this

@@ -232,6 +246,6 @@ },

function isInternalEvent(eventName) {
return eventName === 'done'
return eventName === 'killed'
|| eventName === 'ready'
|| eventName === 'error'
|| eventName === 'killed'
|| eventName === 'done'
}

@@ -268,3 +282,3 @@ function isForbiddenEvent(eventName) {

isPromise(result)
? result.catch(PASSTHROUGH).then(function (data) {respondToEvent(eva, data, UID)})
? result.catch(whenErrorResponse).then(function (data) {respondToEvent(eva, data, UID)})
: respondToEvent(eva, result, UID)

@@ -274,13 +288,11 @@ }

}
function delegateListenerRemoval(eva, all, eventName, listener) {
eventName = BeepBoop.getEventName(eventName)
if (isForbiddenEvent) {
throw new SyntaxError('Eva does not support the "' + eventName + '" event.')
function emitFirst(emitter, eventName, a, b) {
var handler = emitter._events && emitter._events[eventName]
if (!handler) {return}
if (typeof handler === 'function') {
handler.call(emitter, a, b)
} else {
handler[0] && handler[0].call(emitter, a, b)
}
var emitter = isInternalEvent(eventName) ? eva._internal : eva._external
if (emitter) {
all ? emitter.removeAllListeners(eventName)
: emitter.removeListener(eventName, listener)
}
return eva
}

@@ -321,3 +333,5 @@

eva._pendings.delete(UID)
supress && fate.promise.suppressUnhandledRejections()
if (supress) {
fate.promise.suppressUnhandledRejections()
}
;(response instanceof Error ? fate.reject : fate.resolve)(response)

@@ -354,2 +368,10 @@ }

function whenErrorResponse(err) {
if (err instanceof Error) {
err = new Error(err.message)
err.fileName = null
}
return err
}
function fatalError(eva, err, statusCode) {

@@ -372,3 +394,6 @@ if (!eva._fatalError && !eva._statusCode) {

function releaseResources(eva) {
eva._killTimer && clearTimeout(eva._killTimer)
if (eva._killTimer) {
clearTimeout(eva._killTimer)
eva._killTimer = null
}
eva._internal.emit('killed', eva._selfIsHappy && eva._partnerIsHappy)

@@ -375,0 +400,0 @@ eva._finalBufferedAmount = eva._ws.bufferedAmount

{
"name": "eva-events",
"version": "0.2.1",
"version": "0.3.1",
"description": "Eva is like an EventEmitter, but over WebSockets.",

@@ -28,3 +28,3 @@ "main": "index.js",

"dependencies": {
"bluebird": "^3.1.1",
"bluebird": "^3.1.2",
"is-promise": "^2.1.0",

@@ -31,0 +31,0 @@ "msgpack-lite-lite": "^0.4.0",

@@ -17,10 +17,8 @@ ![Image of Eve](http://s11.postimg.org/h1cc0m88z/eveflying.jpg)

```javascript
var Eva = require('eva-events')
var Eva = require('eva-events');
var eva = new Eva('ws://myapp.com');
var eva = new Eva('ws://myapp.com')
eva.on('hello world', function (msg) {
console.log(msg) // "Hello world!"
return new Date
})
eva.on('greeting', function (msg) {
console.log(msg); // "Hello world!";
});
```

@@ -30,11 +28,8 @@

```javascript
var Eva = require('eva-events')
var EvaApp = require('eva-events').Application;
var app = EvaApp(server);
var app = Eva(server, function (eva) {
eva.emit('hello world', 'Hello world!')
.then(function (reply) {
reply instanceof Date // true
this === eva // true
})
})
app.on('client', function (eva) {
eva.emit('greeting', 'Hello world!');
});
```

@@ -45,3 +40,3 @@

The `.emit()` method returns a promise which is resolved with the return value of the remote event listener. If an `Error` object is returned, the promise is rejected with that error.
Aside, from the [`.emit()`](#emitstring-eventname-any-data---this) method, you can also use the [`.run()`](#runstring-eventname-any-data---promise) method, which returns a promise which is fulfilled with the return value of the remote event listener. If an `Error` object is returned, the promise is rejected with that error.

@@ -66,12 +61,6 @@ ## Special reserved events

## Bad practices
## Caution when using `.run()`
Avoid these things when using `eva-events`:
If you [`.run()`](#runstring-eventname-any-data---promise) an event before the other endpoint has registered a listener for that event, [`eva`](#class-eva) will wait for a response indefinitely. Other things can cause transactions to wait indefinitely too, such as poorly written application code. To prevent this, you can use [`.timeout()`](#timeoutnumber-sec---this) which causes all new [`.run()`](#runstring-eventname-any-data---promise) transactions to fail and kill the connection if they aren't settled after a certain amount of time. You should always listen on the `killed` event to react to these things accordingly.
#### Hanging transactions
If you `.emit()` an event before the other endpoint has registered a listener for that event, [`eva`](#class-eva) will wait for a response indefinitely. Other things can cause transactions to wait indefinitely too, such as poorly written application code. To prevent this, you can use [`.timeout()`](#timeoutnumber-sec---this) which causes all new transactions to fail and kill the connection if they aren't resolved after a certain amount of time. You should always listen on the `killed` event to react to these things accordingly.
#### Multiple listeners on the same event
If you have multiple listeners for the same event, the returned promise will only see the value returned by the first listener. In other words, each `.emit()` can only have one response. [`eva`](#class-eva) does not prevent you from having multiple listeners on the same event because that might sometimes be useful for applications making use of [`.send()`](#sendstring-eventname-any-data---this).
## Browser compatibility

@@ -107,3 +96,3 @@

On the server, these instances are exposed in the application handler (see [EvaApplication](#class-evaapplication)).
On the server, these instances are exposed by [EvaApplication](#class-evaapplication) in the `client` event.

@@ -114,16 +103,14 @@ #### .kill() -> this

#### .done() -> this
Starts to gracefully end the connection. The `done` event is immediately emitted. Both endpoints will then wait for all pending transactions to be resolved before finally closing the underlying connection.
Starts to gracefully end the connection. The `done` event is immediately emitted. Both endpoints will then wait for all pending transactions to be resolved before finally closing the underlying connection (at which point, the `killed` event is emitted).
#### .emit(string *eventName*, [any *data*]) -> Promise
Starts a transaction by emitting an event to the opposite endpoint. That endpoint's listeners will be invoked with *data* as the first argument. The promise returned by this method is resolved when it receives a response back from a listener. If the response is an `Error` object, the promise is rejected with it, otherwise the promise is fulfilled with whatever data was sent back. Promises returned by this method, and promises chained from that promise, have a `this` value of the [`eva`](#class-eva) instance.
#### .emit(string *eventName*, [any *data*]) -> this
This emits an event to the opposite endpoint. Remote event listeners are invoked with *data* as the first argument.
#### .send(string *eventName*, [any *data*]) -> this
This is the same as the [`.emit()`](#emitstring-eventname-any-data---promise) method, except that it does not expect back a response. Anything returned by the event listener is discarded.
#### .run(string *eventName*, [any *data*]) -> Promise
This is the same as [`.emit()`](#emitstring-eventname-any-data---this), except instead of just emitting an event, it starts a **transaction**. Only the first event listener for the event will fired, and that listener's return value is **sent back** and made available as the fulfillment value of the promise returned by this method. Or, if the return value is an `Error` object, the promise is rejected with it. Promises returned by this method, and promises chained from that promise, have a `this` value of the [`eva`](#class-eva) instance. Event listeners can return promises, whose fulfillment values or rejection reasons will be made available as the fulfillment value or rejection reason of the promise returned by this method.
#### .on(string *eventName*, function *listener*) -> this
#### .addListener(string *eventName*, function *listener*) -> this
Registers an event listener *listener* for event *eventName*.
#### .addListener(string *eventName*, function *listener*) -> this
Alias for [`.on()`](#onstring-eventname-function-listener---this).
#### .once(string *eventName*, function *listener*) -> this

@@ -135,5 +122,2 @@ Same as [`.on()`](#onstring-eventname-function-listener---this), but the *listener* will only be invoked once.

#### .removeAllListeners([string *eventName*]) -> this
Unregisters all event listeners on the instance, or all listeners of event *eventName*.
#### .listenerCount(string *eventName*) -> number

@@ -143,3 +127,3 @@ Returns the number of event listeners that are registered with event *eventName*.

#### .timeout(number *sec*) -> this
Causes all future transactions to timeout after *sec* seconds, at which point an `error` event will be emitted and the connection will be forcefully killed. A *sec* value of `0` or `Infinity` turns off timeouts.
Causes all future transactions (started by the [`.run()`](#runstring-eventname-any-data---promise) method) to timeout after *sec* seconds, at which point an `error` event will be emitted and the connection will be forcefully killed. A *sec* value of `0` or `Infinity` turns off timeouts.

@@ -161,15 +145,20 @@ Default: 0

On the server:
On the server only:
```javascript
var Eva = require('eva-events')
var app = Eva(server, '/path/to/app', function (eva) {
eva.emit('welcome', 'Welcome to my app.')
})
var EvaApp = require('eva-events').Application
var app = EvaApp(server, '/path/to/app', options)
```
#### constructor EvaApplication(http.Server *server*, [string *path*], [function *handler*, [function *verifyClients*]])
Also supports `https` (thus, `wss://`) servers.
#### constructor EvaApplication(http.Server *server*, [string *path*], [Object *options*])
You may supply a *verifyClients* function which must return `true` for each client wishing to connect to the WebSocket server. The function may have the following two signatures:
*path* defaults to `"/"`
[`EvaApplication`](#class-evaapplication) is an `EventEmitter`. When a new client connects, the `client` event is emitted with the [`Eva`](#class-eva) client as the first argument. If the [`EvaApplication`](#class-evaapplication) is [`locked`](#lock---this), and there are no more clients connected, the `vacant` event is fired.
#### Options
##### *options.verifyClients*
You may supply a *verifyClients* function which must return `true` for each client wishing to connect to the WebSocket endpoint. The function may have the following two signatures:
```javascript

@@ -188,24 +177,29 @@ function verifyClients(info) { // synchronous

*path* defaults to `"/"`
##### *options.useHeartbeat*
#### .addHandler(function *handler*) -> this
Registers *handler* to be invoked for each new client connection that is made. This is the same as passing a *handler* argument to the [`EvaApplication`](#class-evaapplication) constructor. You may have multiple handlers.
By default, all clients of an [`EvaApplication`](#class-evaapplication) will periodically be sent a heartbeat. If a client endpoint does not respond after 10 heartbeats, the connection will be destroyed. Setting `options.useHeartbeat = false` will cause this instance of [`EvaApplication`](#class-evaapplication) to **not** send and track heartbeats.
#### .removeHandler(function *handler*) -> this
Unregisters a function *handler*. This is the opposite of [`.addHandler()`](#addhandlerfunction-handler---this).
#### .lock() -> this
Prevents new clients from connecting to the websocket endpoint.
#### .currentClients() -> Array
Returns a snapshot array of the current [`eva`](#class-eva) clients that are connected.
#### .unlock() -> this
Allows new clients to connect to the websocket endpoint. When an [`EvaApplication`](#class-evaapplication) instance is created, it starts out unlocked.
#### .kill() -> this
Starts to close down this [`EvaApplication`](#class-evaapplication) by rejecting all new clients trying to connect, and by invoking the [`.kill()`](#kill---this) method on each connected client.
[`Locks`](#lock---this) the [`EvaApplication`](#class-evaapplication), and invokes the [`.kill()`](#kill---this) method on each connected client.
#### .done() -> this
Starts to close down this [`EvaApplication`](#class-evaapplication) by rejecting all new clients trying to connect, and by invoking the [`.done()`](#done---this) method on each connected client.
[`Locks`](#lock---this) the [`EvaApplication`](#class-evaapplication), and invokes the [`.done()`](#done---this) method on each connected client.
#### .broadcast(string *eventName*, [any *data*]) -> this
Invokes the [`.send()`](#sendstring-eventname-any-data---this) method on each client that [`isReady`](#get-isready---boolean).
Invokes the [`.emit()`](#emitstring-eventname-any-data---this) method on each client that [`isReady`](#get-isready---boolean).
#### .currentClients() -> Array
Returns a snapshot array of the current [clients](#class-eva) that are connected.
#### get .isLocked -> boolean
Returns whether the [`EvaApplication`](#class-evaapplication) is currently rejecting new clients.
# License
[MIT](https://github.com/JoshuaWise/eva-events/blob/master/LICENSE)

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc