Compare commits

...

131 Commits
0.7.1 ... 0.7.3

Author SHA1 Message Date
Guillermo Rauch
9b40977616 Release 0.7.3 2011-06-30 10:38:03 -03:00
Guillermo Rauch
964f040b58 Merge pull request #318 from dvv/patch-1
normalize SocketNamespace local eventing
2011-06-30 06:22:26 -07:00
Vladimir Dronnikov
44919189ec normalize SocketNamespace local eventing 2011-06-30 03:12:55 -07:00
Guillermo Rauch
6c4cc5113f Merge pull request #315 from 3rd-Eden/intentional
disconnect
2011-06-29 13:23:45 -07:00
Arnout Kazemier
06e29fef67 Use packet.reason or default to 'packet' 2011-06-29 21:58:13 +02:00
Arnout Kazemier
16518c0715 Added a small indicator + test suite so you can see how a user is disconnected 2011-06-29 21:45:14 +02:00
Guillermo Rauch
09a713da18 Merge pull request #314 from nyxtom/master
Issue #313 TypeError: Object #<CleartextStream> has no method 'address'
2011-06-29 10:52:56 -07:00
Thomas Holloway
3e0239ed07 Quick fix to issues #313 to check if an address exists on a connection 2011-06-29 12:29:00 -05:00
Guillermo Rauch
d3d2a70944 Added MemoryStore expiration tests. 2011-06-29 11:38:28 -03:00
Guillermo Rauch
1bcf87eb14 Added RedisStore expiration tests. 2011-06-29 11:38:18 -03:00
Guillermo Rauch
f9c210c11c Added expiration support to RedisStore.Client#destroy through EXPIRE command. 2011-06-29 11:37:58 -03:00
Guillermo Rauch
06421dd008 Added expiration support to MemoryStore.Client#destroy. 2011-06-29 11:37:26 -03:00
Guillermo Rauch
af7c7141f0 Changed; for consistency across stores, undefined becomes null. 2011-06-29 11:36:14 -03:00
Guillermo Rauch
def5498f61 Added expiration parameters to Store#destroyClient and Store#destroy 2011-06-29 11:35:44 -03:00
Guillermo Rauch
494b2d4487 Added Socket#has
Added `Socket#del`
2011-06-29 11:35:13 -03:00
Guillermo Rauch
ba3b854190 Changed; make sure to cache a reference to the client object so that it can be
referenced after its destroying and before the data expires.
2011-06-29 11:33:54 -03:00
Guillermo Rauch
abc3723b54 Changed; make sure to pass client store expiration to Store#destroyClient. 2011-06-29 11:33:24 -03:00
Guillermo Rauch
ba10280071 Added a client store expiration option with a 15 seconds default. 2011-06-29 11:32:47 -03:00
Guillermo Rauch
fc95d340ef Merge pull request #306 from 3rd-Eden/namespace.auth
Namespace.auth
2011-06-29 04:14:29 -07:00
Arnout Kazemier
d47bcd51ba Merge branch 'master' of github.com:LearnBoost/socket.io into namespace.auth 2011-06-29 13:10:23 +02:00
Guillermo Rauch
be7f56b819 Merge branch 'master' of github.com:LearnBoost/socket.io 2011-06-29 07:55:21 -03:00
Guillermo Rauch
6a83d6879c Added redis store tests 2011-06-29 07:54:26 -03:00
Guillermo Rauch
56f5911b60 Added memory store tests 2011-06-29 07:54:13 -03:00
Guillermo Rauch
2fd38866d2 Finished redis store 2011-06-29 07:53:51 -03:00
Guillermo Rauch
4398271ff5 Added MemoryClient#has, MemoryClient#del
Refactored MemoryClient#destroy
2011-06-29 07:51:54 -03:00
Guillermo Rauch
6ce89b80ef Added Store#destroyClient
Refactored Store#destroy
2011-06-29 07:51:21 -03:00
Guillermo Rauch
1449b18681 Added docs 2011-06-29 07:50:23 -03:00
Guillermo Rauch
f921164bf8 Removed Store instrumentation 2011-06-29 07:50:04 -03:00
Guillermo Rauch
ea1f5822ae Removed Store#log since they're manager-independent. 2011-06-29 07:49:47 -03:00
Guillermo Rauch
2fd406f7b2 Fixed; make sure to clean up store upon client disconnect. 2011-06-29 07:49:22 -03:00
Guillermo Rauch
34d0cbdbf0 Merge pull request #310 from 3rd-Eden/websocket.write
Having a stab at issue #295
2011-06-29 03:20:49 -07:00
Arnout Kazemier
2110bac72d Having a stab at issue #295 2011-06-29 12:17:20 +02:00
Arnout Kazemier
299e097fa7 Added storage for socket.handshake & updated test suite to check for it 2011-06-28 23:50:33 +02:00
Arnout Kazemier
a1963fcf7d Added missing test suite 2011-06-28 21:49:45 +02:00
Arnout Kazemier
8abe712294 Passes test suite 2011-06-28 21:41:46 +02:00
Guillermo Rauch
df21bea4df Fixed store references 2011-06-28 16:32:40 -03:00
Arnout Kazemier
45f2718954 Cleaned up the websocket test 2011-06-28 21:05:53 +02:00
Guillermo Rauch
24aef9e40b Merge branch 'master' of github.com:LearnBoost/Socket.IO-node 2011-06-28 15:50:19 -03:00
Guillermo Rauch
a00ce7eee6 Fix for client side namespaces. 2011-06-28 15:49:48 -03:00
Guillermo Rauch
17dd94e3c9 Merge pull request #300 from 3rd-Eden/static.write
Create a new branch for fixing #287
2011-06-28 11:45:40 -07:00
Guillermo Rauch
81fe9e0117 Merge pull request #302 from 3rd-Eden/readme
Readme fixes
2011-06-28 11:45:05 -07:00
Arnout Kazemier
c9bf814580 Readme fixes 2011-06-28 20:43:24 +02:00
Arnout Kazemier
78b546e185 Incorporated feedback from @guille 2011-06-28 20:37:58 +02:00
Arnout Kazemier
e7deb32a6f Create a new branch for fixing #287 2011-06-28 20:23:13 +02:00
Guillermo Rauch
4e4199c15e Added test for SocketNamespace#clients 2011-06-28 13:33:09 -03:00
Guillermo Rauch
fb79657476 Added SocketNamespace#clients 2011-06-28 13:33:01 -03:00
Guillermo Rauch
f2212f6962 Make handshake data extensible 2011-06-28 09:27:07 -03:00
Guillermo Rauch
c9d21c5ff5 Fixed tests 2011-06-28 08:29:42 -03:00
Guillermo Rauch
6b6f17b098 Added handshakeData#address
Added handshakeData#secure
2011-06-28 08:29:00 -03:00
Guillermo Rauch
9740b42866 Added test for Socket#handshake. 2011-06-28 07:37:25 -03:00
Guillermo Rauch
2a1b2bd37e Added Socket#handshake data getter. 2011-06-28 07:37:15 -03:00
Guillermo Rauch
b58c2e4afe Added handshake data normalizer function.
Changed; Make sure we store the `handshake data` in the hash of connected clients.
2011-06-28 07:36:35 -03:00
Guillermo Rauch
a2f8fb4970 Implemented RedisStore.Client#get and RedisStore.Client#set 2011-06-27 16:21:32 -03:00
Guillermo Rauch
a4ef10d6a2 Added missing redis require. 2011-06-27 16:14:47 -03:00
Guillermo Rauch
91e43064f3 Added Redis store.
- Simply leverages pub/sub for nodes communication
  - Makes sure own node doesn't consume its own messages
  - Optionally uses msgpack to encode data for higher speed than JSON
  - Opens two connections per node (one in pub/sub mode, one for normal redis)
2011-06-27 15:55:33 -03:00
Guillermo Rauch
76e6b9ea6f Removed comments in xhr-polling test. 2011-06-27 15:53:15 -03:00
Guillermo Rauch
a517efcaca Fixed signature of Socket#join in tests
Fixed style
2011-06-27 15:52:35 -03:00
Guillermo Rauch
8f6860b155 Make sure to close websocket clients on session tests 2011-06-27 15:52:17 -03:00
Guillermo Rauch
72283a9078 Removed comments from tests 2011-06-27 15:51:59 -03:00
Guillermo Rauch
c9d87ebb98 Fixed xhr polling transport name 2011-06-27 15:51:44 -03:00
Guillermo Rauch
4a591f191f Fixed xhr polling constructor to take request as parameter 2011-06-27 15:51:26 -03:00
Guillermo Rauch
779816ded7 Added req to websocket transport constructor. 2011-06-27 15:50:46 -03:00
Guillermo Rauch
2b90edb3a2 Added req parameter to jsonp-polling constructor. 2011-06-27 15:50:24 -03:00
Guillermo Rauch
a99016df79 Make sure to take request as parameter in the http constructor 2011-06-27 15:50:08 -03:00
Guillermo Rauch
78c7a55ce4 Make sure to take request as parameter in the polling constructor 2011-06-27 15:49:37 -03:00
Guillermo Rauch
3fbce85315 Make sure to take request as parameter in the htmlfile constructor 2011-06-27 15:49:13 -03:00
Guillermo Rauch
9aa5320730 Fixed Flashsocket constructor 2011-06-27 15:48:20 -03:00
Guillermo Rauch
2fb28c8843 Removed Transport#subscribe and Transport#unsubscribe 2011-06-27 15:47:55 -03:00
Guillermo Rauch
767ca4f100 Fixed .end signature 2011-06-27 15:47:43 -03:00
Guillermo Rauch
733569ae68 Added Transport#discard. Mostly useful for when request/response cycles are load
balanced into different nodes.
2011-06-27 15:47:14 -03:00
Guillermo Rauch
f736302b97 Refactored Transport#end
Removed concept of transport pausing as we no longer have async buffers.
2011-06-27 15:46:43 -03:00
Guillermo Rauch
7bfcf54f90 Publish transport close event. 2011-06-27 15:45:58 -03:00
Guillermo Rauch
fa5ef9ff2d Removed forced parameter from .end call from disconnect 2011-06-27 15:45:37 -03:00
Guillermo Rauch
df457440fd Refactored onMessage to leverage subscriptions 2011-06-27 15:45:18 -03:00
Guillermo Rauch
375cbf49de Important fix for hearbeats 2011-06-27 15:45:09 -03:00
Guillermo Rauch
2c66814b82 Applied new .end signature 2011-06-27 15:44:50 -03:00
Guillermo Rauch
2a4e4e1300 Added handler for dispatch subscription 2011-06-27 15:44:38 -03:00
Guillermo Rauch
6f12de98fc Added handler for heartbeat clear subscription 2011-06-27 15:44:28 -03:00
Guillermo Rauch
73ea4dd13f Removed the .end method to disregard whether it was forced or not 2011-06-27 15:43:46 -03:00
Guillermo Rauch
772afe897d Refactored Transport to leverage subscriptions. 2011-06-27 15:43:24 -03:00
Guillermo Rauch
0b75d09090 Refactored Memory.Client 2011-06-27 15:42:49 -03:00
Guillermo Rauch
95e787dcc5 Removed all transport-specific methods from Memory store
Added publish/subscribe placeholders that basically do nothing.
2011-06-27 15:40:32 -03:00
Guillermo Rauch
5342dd6d76 Refactored Socket
- initialize options in constructor
  - inherit from EventEmitter always
  - added generic Socket#client
2011-06-27 15:37:30 -03:00
Guillermo Rauch
195e227393 Refactored Socket#disconnect to intelligently decide whether the disconnection has
to be forced or just destroy the storage, so that a re-opening of the transport is
not possible.
2011-06-27 15:36:20 -03:00
Guillermo Rauch
c28a85e520 Refacotred Socket#set and Socket#get 2011-06-27 15:36:08 -03:00
Guillermo Rauch
bf8b1e6879 Added Socket#dispatch, in charge of intelligently determining whether the packet
has to be relied to other nodes to handle, handle it internally, or discarding it
if it's volatile from the local buffer provided the transport is not open.
2011-06-27 15:35:09 -03:00
Guillermo Rauch
240cf3fde6 Leverage Socket#dispatch to dispatch a message. 2011-06-27 15:34:46 -03:00
Guillermo Rauch
d40cde8503 Refactored Socket#leave 2011-06-27 15:34:31 -03:00
Guillermo Rauch
db9d81971d Refactored Socket#join 2011-06-27 15:34:20 -03:00
Guillermo Rauch
54d2c0111d Changed; Socket#store now returns the client store. 2011-06-27 15:32:44 -03:00
Guillermo Rauch
cfd5315b1e Removed disconnect subscription per-socket (added unnecessary overhead with multiple
namespaces).
2011-06-27 15:32:19 -03:00
Guillermo Rauch
cbc12ecdcf Make sure to leave rooms upon disconnection of a namespace (fixes some bug). 2011-06-27 15:31:44 -03:00
Guillermo Rauch
9aa650e430 Removed the join callback, since it's not necessary (TCP based pub/sub guarantees
proper total ordering).
2011-06-27 15:30:47 -03:00
Guillermo Rauch
f06fefab14 Added handleDisconnect method to emit the disconnect event when the socket is
disconnected due to network reasons, making sure we don't emit it on non-connected
namespaces.
2011-06-27 15:30:02 -03:00
Guillermo Rauch
5b6efb784f Simplified global message dispatching by leveraging subscriptions. 2011-06-27 15:28:24 -03:00
Guillermo Rauch
c210241379 Refactored handshake 2011-06-27 15:27:58 -03:00
Guillermo Rauch
8594d684ef Fixed style 2011-06-27 15:27:44 -03:00
Guillermo Rauch
970621d5ee Refactored client request handling:
- removed the concept of async handshake, since we now keep track of open/closed
   clients and handshakes across all nodes (replication)
 - when a client closes temporarily, before the disconnection is triggered and before
   the transport is reopened in any node, we buffer messages in all nodes. We therefore
   remove the need for a complex message queue with a memory tradeoff (buffer redundancy).
2011-06-27 15:21:50 -03:00
Guillermo Rauch
c9d9c2e8b3 Added initStore and subscription handlers. 2011-06-27 15:21:01 -03:00
Guillermo Rauch
8f44f026ee Removed usage of Store#disconnect, replaced with a simple subscription. 2011-06-27 15:20:35 -03:00
Guillermo Rauch
c369073a72 Moved id generation method into the manager. 2011-06-27 15:18:37 -03:00
Guillermo Rauch
b700f0546d Added call to initStore to initialize subscriptions. 2011-06-27 15:16:54 -03:00
Guillermo Rauch
0701408061 Added redis client dependency.
Removed >= from policy.
2011-06-27 15:14:53 -03:00
Guillermo Rauch
bafb347e4a Changed console.error to console.log. 2011-06-22 23:56:14 -03:00
Guillermo Rauch
802da70bf7 Fixed; bind both servers at the same time do that the test never times out. 2011-06-22 22:18:51 -03:00
Guillermo Rauch
f2711daa37 Merge pull request #282 from 3rd-Eden/master
304
2011-06-22 15:49:56 -07:00
Arnout Kazemier
7200dfed84 Output the correct name 2011-06-23 00:48:06 +02:00
Arnout Kazemier
b348f4de99 Style 2011-06-23 00:23:18 +02:00
Arnout Kazemier
6452602603 Merge branch 'master' of https://github.com/LearnBoost/Socket.IO-node 2011-06-23 00:20:55 +02:00
Arnout Kazemier
6f93bb2ec7 Added 304 support 2011-06-23 00:20:28 +02:00
Guillermo Rauch
ffd3e8bc96 Removed Transport#name for abstract interface. 2011-06-22 18:40:26 -03:00
Guillermo Rauch
bdea4b11a7 Merge pull request #281 from 3rd-Eden/master
Lazy load http / https modules & fix for crash in memory store
2011-06-22 13:49:54 -07:00
Arnout Kazemier
041b5655f9 Delete they client instead of setting it to null. And make sure the client exists
before we do .disconnect
2011-06-22 22:43:39 +02:00
Arnout Kazemier
080676bf6e Lazy require http and https module only when needed 2011-06-22 21:40:02 +02:00
Guillermo Rauch
2e7076abcc Merge pull request #273 from 3rd-Eden/master
merge fail
2011-06-22 01:27:43 -07:00
Arnout Kazemier
b87e51bae3 Refixed merge fail 2011-06-22 10:26:39 +02:00
Guillermo Rauch
cc5bc44ef0 Merge pull request #265 from 3rd-Eden/master
Better naming in the logging
2011-06-22 01:25:30 -07:00
Arnout Kazemier
4672ab65d7 Merge branch 'master' of https://github.com/LearnBoost/Socket.IO-node
Conflicts:
	lib/transports/http-polling.js
2011-06-22 10:24:35 +02:00
Guillermo Rauch
331e6e85b9 Release 0.7.2 2011-06-22 04:15:01 -03:00
Guillermo Rauch
f2df40aa02 Bumped client version.
Bumped version
2011-06-22 04:12:26 -03:00
Guillermo Rauch
36ecd49ad2 Updated tests. 2011-06-22 04:09:19 -03:00
Guillermo Rauch
aef5027640 Make sure to write a packet (of type noop) when closing a poll. This solves a problem
with cross-domain requests being flagged as aborted and reconnection being triggered.
2011-06-22 04:08:28 -03:00
Guillermo Rauch
4933cf1a9e Added noop message type. 2011-06-22 04:08:11 -03:00
Arnout Kazemier
28b396c3fc Merge branch 'master' of https://github.com/LearnBoost/Socket.IO-node 2011-06-22 08:58:33 +02:00
Arnout Kazemier
808e794ec5 Merge branch 'master' of github.com:3rd-Eden/Socket.IO-node 2011-06-21 21:35:42 +02:00
Arnout Kazemier
cb7aa0a79c Merge branch 'master' of https://github.com/LearnBoost/Socket.IO-node 2011-06-21 21:35:26 +02:00
Arnout Kazemier
1bddfc45dd Added client handler to the options, just for the sake of consistency 2011-06-21 21:09:41 +02:00
Arnout Kazemier
836eb1d1c2 Merge branch 'master' of https://github.com/LearnBoost/Socket.IO-node 2011-06-21 11:55:24 +02:00
Arnout Kazemier
4c5dfd53f0 Merge branch 'master' of https://github.com/LearnBoost/Socket.IO-node 2011-06-21 10:03:20 +02:00
Arnout Kazemier
a62bced081 this.name > prototype.name 2011-06-21 10:00:13 +02:00
Arnout Kazemier
397dfbc51d Naming 2011-06-21 09:44:26 +02:00
28 changed files with 2108 additions and 714 deletions

View File

@@ -1,4 +1,27 @@
0.7.3 / 2011-06-30
==================
* Exposed handshake data to clients.
* Refactored dispatcher interface.
* Changed; Moved id generation method into the manager.
* Added sub-namespace authorization. [3rd-Eden]
* Changed; normalized SocketNamespace local eventing [dvv]
* Changed; Use packet.reason or default to 'packet' [3rd-Eden]
* Changed console.error to console.log.
* Fixed; bind both servers at the same time do that the test never times out.
* Added 304 support.
* Removed `Transport#name` for abstract interface.
* Changed; lazily require http and https module only when needed. [3rd-Eden]
0.7.2 / 2011-06-22
==================
* Make sure to write a packet (of type `noop`) when closing a poll.
This solves a problem with cross-domain requests being flagged as aborted and
reconnection being triggered.
* Added `noop` message type.
0.7.1 / 2011-06-21
==================

View File

@@ -1,4 +1,3 @@
# Socket.IO
Socket.IO is a Node.JS project that makes WebSockets and realtime possible in
@@ -89,7 +88,7 @@ io.sockets.on('connection', function (socket) {
});
socket.on('msg', function () {
socket.get('nickname', function (name) {
socket.get('nickname', function (err, name) {
console.log('Chat message by ', name);
});
});
@@ -149,9 +148,8 @@ var news = io
```html
<script>
var socket = io.connect('http://localhost/')
, chat = socket.of('/chat')
, news = socket.of('/news');
var chat = io.connect('http://localhost/chat')
, news = io.connect('http://localhost/news');
chat.on('connect', function () {
chat.emit('hi!');
@@ -277,7 +275,7 @@ Simply leverage `send` and listen on the `message` event:
#### Server side
```js
var io = require('socket.io-node').listen(80);
var io = require('socket.io').listen(80);
io.sockets.on('connection', function (socket) {
socket.on('message', function () { });
@@ -307,7 +305,7 @@ Configuration in socket.io is TJ-style:
#### Server side
```js
var io = require('socket.io-node').listen(80);
var io = require('socket.io').listen(80);
io.configure(function () {
io.set('transports', ['websocket', 'flashsocket', 'xhr-polling']);

View File

@@ -74,7 +74,7 @@ Logger.prototype.log = function (type) {
if (index > this.level)
return this;
console.error.apply(
console.log.apply(
console
, [this.colors
? ' \033[' + colors[index] + 'm' + pad(type) + ' -\033[39m'

View File

@@ -79,8 +79,12 @@ function Manager (server) {
, 'browser client': true
, 'browser client minification': false
, 'browser client etag': false
, 'browser client handler': false
, 'client store expiration': 15
};
this.initStore();
// reset listeners
this.oldListeners = server.listeners('request');
server.removeAllListeners('request');
@@ -216,6 +220,265 @@ Manager.prototype.configure = function (env, fn) {
return this;
};
/**
* Initializes everything related to the message dispatcher.
*
* @api private
*/
Manager.prototype.initStore = function () {
this.handshaken = {};
this.connected = {};
this.open = {};
this.closed = {};
this.closedA = [];
this.rooms = {};
this.roomClients = {};
var self = this;
this.store.subscribe('handshake', function (id, data) {
self.onHandshake(id, data);
});
this.store.subscribe('connect', function (id) {
self.onConnect(id);
});
this.store.subscribe('open', function (id) {
self.onOpen(id);
});
this.store.subscribe('join', function (id, room) {
self.onJoin(id, room);
});
this.store.subscribe('leave', function (id, room) {
self.onLeave(id, room);
});
this.store.subscribe('close', function (id) {
self.onClose(id);
});
this.store.subscribe('dispatch', function (room, packet, volatile, exceptions) {
self.onDispatch(room, packet, volatile, exceptions);
});
this.store.subscribe('disconnect', function (id) {
self.onDisconnect(id);
});
};
/**
* Called when a client handshakes.
*
* @param text
*/
Manager.prototype.onHandshake = function (id, data) {
this.handshaken[id] = data;
};
/**
* Called when a client connects (ie: transport first opens)
*
* @api private
*/
Manager.prototype.onConnect = function (id) {
this.connected[id] = true;
};
/**
* Called when a client opens a request in a different node.
*
* @api private
*/
Manager.prototype.onOpen = function (id) {
this.open[id] = true;
// if we were buffering messages for the client, clear them
if (this.closed[id]) {
var self = this;
this.closedA.splice(this.closedA.indexOf(id), 1);
this.store.unsubscribe('dispatch:' + id, function () {
delete self.closed[id];
});
}
// clear the current transport
if (this.transports[id]) {
this.transports[id].discard();
this.transports[id] = null;
}
};
/**
* Called when a message is sent to a namespace and/or room.
*
* @api private
*/
Manager.prototype.onDispatch = function (room, packet, volatile, exceptions) {
// go through the users who have pending buffers
for (var i = 0, l = this.closedA.length; i < l; i++) {
if (this.roomClients[this.closedA[i]][room]) {
if (!~exceptions.indexOf(this.closedA[i])) {
this.closed[this.closedA[i]].push(packet);
}
}
}
// go through room clients
if (this.rooms[room]) {
for (var i = 0, l = this.rooms[room].length; i < l; i++) {
var id = this.rooms[room][i];
if (!~exceptions.indexOf(id)) {
if (this.transports[id] && this.transports[id].open) {
this.transports[id].onDispatch(packet, volatile);
} else if (!volatile) {
this.onClientDispatch(id, packet);
}
}
}
}
};
/**
* Called when a client joins a nsp / room.
*
* @api private
*/
Manager.prototype.onJoin = function (id, name) {
if (!this.roomClients[id]) {
this.roomClients[id] = [];
}
if (!this.rooms[name]) {
this.rooms[name] = [];
}
this.rooms[name].push(id);
this.roomClients[id][name] = true;
};
/**
* Called when a client leaves a nsp / room.
*
* @param private
*/
Manager.prototype.onLeave = function (id, room) {
if (this.rooms[room]) {
this.rooms[room].splice(this.rooms[room].indexOf(id), 1);
delete this.roomClients[id][room];
}
};
/**
* Called when a client closes a request in different node.
*
* @api private
*/
Manager.prototype.onClose = function (id) {
this.closed[id] = [];
this.closedA.push(id);
var self = this;
this.store.subscribe('dispatch:' + id, function (packet, volatile) {
if (!volatile) {
self.onClientDispatch(id, packet);
}
});
};
/**
* Dispatches a message for a closed client.
*
* @api private
*/
Manager.prototype.onClientDispatch = function (id, packet) {
this.closed[id].push(packet);
};
/**
* Receives a message for a client.
*
* @api private
*/
Manager.prototype.onClientMessage = function (id, packet) {
if (this.namespaces[packet.endpoint]) {
this.namespaces[packet.endpoint].handlePacket(id, packet);
}
};
/**
* Fired when a client disconnects (not triggered).
*
* @api private
*/
Manager.prototype.onClientDisconnect = function (id, reason) {
for (var name in this.namespaces) {
if (this.roomClients[id][name]) {
this.namespaces[name].handleDisconnect(id, reason);
}
}
};
/**
* Called when a client disconnects.
*
* @param text
*/
Manager.prototype.onDisconnect = function (id, local) {
delete this.handshaken[id];
if (this.open[id]) {
delete this.open[id];
}
if (this.connected[id]) {
delete this.connected[id];
}
if (this.transports[id]) {
this.transports[id].discard();
delete this.transports[id];
}
if (this.closed[id]) {
delete this.closed[id];
this.closedA.splice(this.closedA.indexOf(id), 1);
}
if (this.roomClients[id]) {
for (var room in this.roomClients[id]) {
this.rooms[room].splice(this.rooms.indexOf(id), 1);
}
}
this.store.destroyClient(id, this.get('client store expiration'));
this.store.unsubscribe('dispatch:' + id);
if (local) {
this.store.unsubscribe('message:' + id);
this.store.unsubscribe('disconnect:' + id);
}
};
/**
* Handles an HTTP request.
*
@@ -302,12 +565,15 @@ Manager.prototype.handleHTTPRequest = function (data, req, res) {
Manager.prototype.handleClient = function (data, req) {
var socket = req.socket
, newTransport = false
, store = this.store
, self = this;
if (undefined != data.query.disconnect) {
self.log.debug('handling disconnection url');
self.store.disconnect(data.id, true);
if (this.transports[data.id] && this.transports[data.id].open) {
this.transports[data.id].onForcedDisconnect();
} else {
this.store.publish('disconnect-force:' + data.id);
}
return;
}
@@ -317,43 +583,49 @@ Manager.prototype.handleClient = function (data, req) {
return;
}
var transport = new transports[data.transport](this, data);
transport.pause();
transport.request = req;
var transport = new transports[data.transport](this, data, req);
if (!transport.open) {
this.log.debug('transport not writeable, not subscribing');
return;
}
if (this.handshaken[data.id]) {
if (transport.open) {
if (this.closed[data.id] && this.closed[data.id].length) {
transport.payload(this.closed[data.id]);
this.closed[data.id] = [];
}
this.store.isHandshaken(data.id, function (err, handshaken) {
if (err || !handshaken) {
if (err) console.error(err);
transport.error('client not handshaken', 'reconnect');
return;
this.onOpen(data.id);
this.store.publish('open', data.id);
this.transports[data.id] = transport;
}
self.store.client(data.id).count(function (err, count) {
transport.resume();
if (!this.connected[data.id]) {
this.onConnect(data.id);
this.store.publish('connect', data.id);
if (count == 1) {
// initialize the socket for all namespaces
for (var i in self.namespaces) {
var socket = self.namespaces[i].socket(data.id, true);
// initialize the socket for all namespaces
for (var i in this.namespaces) {
var socket = this.namespaces[i].socket(data.id, true);
// echo back connect packet and fire connection event
if (i === '') {
self.namespaces[i].handlePacket(data.id, { type: 'connect' });
}
// echo back connect packet and fire connection event
if (i === '') {
this.namespaces[i].handlePacket(data.id, { type: 'connect' });
}
// handle packets for the client (all namespaces)
self.store.on('message:' + data.id, function (packet) {
self.handlePacket(data.id, packet);
});
}
});
});
this.store.subscribe('message:' + data.id, function (packet) {
self.onClientMessage(data.id, packet);
});
this.store.subscribe('disconnect:' + data.id, function (reason) {
self.onClientDisconnect(data.id, reason);
});
}
} else {
if (transport.open) {
transport.error('client not handshaken', 'reconnect');
}
transport.discard();
}
};
/**
@@ -363,21 +635,24 @@ Manager.prototype.handleClient = function (data, req) {
*/
Manager.static = {
cache:{}
, paths: {
'/static/flashsocket/WebSocketMain.swf': client.dist + '/WebSocketMain.swf'
, '/static/flashsocket/WebSocketMainInsecure.swf': client.dist + '/WebSocketMainInsecure.swf'
, '/socket.io.js': client.dist + '/socket.io.js'
, '/socket.io.js.min': client.dist + '/socket.io.min.js'
}
, contentType: {
'js': 'application/javascript'
, 'swf': 'application/x-shockwave-flash'
}
, encoding:{
'js': 'utf8'
, 'swf': 'binary'
}
cache: {}
, paths: {
'/static/flashsocket/WebSocketMain.swf': client.dist + '/WebSocketMain.swf'
, '/static/flashsocket/WebSocketMainInsecure.swf':
client.dist + '/WebSocketMainInsecure.swf'
, '/socket.io.js': client.dist + '/socket.io.js'
, '/socket.io.js.min': client.dist + '/socket.io.min.js'
}
, mime: {
'js': {
contentType: 'application/javascript'
, encoding: 'utf8'
}
, 'swf': {
contentType: 'application/x-shockwave-flash'
, encoding: 'binary'
}
}
};
/**
@@ -389,15 +664,34 @@ Manager.static = {
Manager.prototype.handleClientRequest = function (req, res, data) {
var static = Manager.static
, extension = data.path.split('.').pop()
, file = data.path + (this.enabled('browser client minification') && extension == 'js' ? '.min' : '')
, file = data.path + (this.enabled('browser client minification')
&& extension == 'js' ? '.min' : '')
, location = static.paths[file]
, cache = static.cache[file];
var self = this;
/**
* Writes a response, safely
*
* @api private
*/
function write (status, headers, content, encoding) {
try {
res.writeHead(status, headers || null);
res.end(content || '', encoding || null);
} catch (e) {}
}
function serve () {
var headers = {
'Content-Type': static.contentType[extension]
if (req.headers['if-none-match'] === cache.Etag) {
return write(304);
}
var mime = static.mime[extension]
, headers = {
'Content-Type': mime.contentType
, 'Content-Length': cache.length
};
@@ -405,9 +699,7 @@ Manager.prototype.handleClientRequest = function (req, res, data) {
headers.Etag = cache.Etag;
}
res.writeHead(200, headers);
res.end(cache.content, cache.encoding);
write(200, headers, cache.content, mime.encoding);
self.log.debug('served static ' + data.path);
}
@@ -416,10 +708,8 @@ Manager.prototype.handleClientRequest = function (req, res, data) {
} else if (!cache) {
fs.readFile(location, function (err, data) {
if (err) {
res.writeHead(500);
res.end('Error serving socket.io client.');
self.log.warn('Can\'t cache socket.io client, ' + err.message);
write(500, null, 'Error serving static ' + data.path);
self.log.warn('Can\'t cache '+ data.path +', ' + err.message);
return;
}
@@ -427,7 +717,6 @@ Manager.prototype.handleClientRequest = function (req, res, data) {
content: data
, length: data.length
, Etag: client.version
, encoding: static.encoding[extension]
};
serve();
@@ -437,6 +726,17 @@ Manager.prototype.handleClientRequest = function (req, res, data) {
}
};
/**
* Generates a session id.
*
* @api private
*/
Manager.prototype.generateId = function () {
return Math.abs(Math.random() * Math.random() * Date.now() | 0).toString()
+ Math.abs(Math.random() * Math.random() * Date.now() | 0).toString();
};
/**
* Handles a handshake request.
*
@@ -466,38 +766,60 @@ Manager.prototype.handleHandshake = function (data, req, res) {
return;
}
this.authorize(data, function (err, authorized) {
var handshakeData = this.handshakeData(data);
this.authorize(handshakeData, function (err, authorized, newData) {
if (err) return error(err);
self.log.info('handshake ' + (authorized ? 'authorized' : 'unauthorized'));
if (authorized) {
self.store.handshake(data, function (err, id) {
if (err) return error(err);
var id = self.generateId()
, hs = [
id
, self.get('heartbeat timeout') || ''
, self.get('close timeout') || ''
, self.transports(data).join(',')
].join(':');
var hs = [
id
, self.get('heartbeat timeout') || ''
, self.get('close timeout') || ''
, self.transports(data).join(',')
].join(':');
if (data.query.jsonp) {
hs = 'io.j[' + data.query.jsonp + '](' + JSON.stringify(hs) + ');';
res.writeHead(200, { 'Content-Type': 'application/javascript' });
} else {
res.writeHead(200);
}
if (data.query.jsonp) {
hs = 'io.j[' + data.query.jsonp + '](' + JSON.stringify(hs) + ');';
res.writeHead(200, { 'Content-Type': 'application/javascript' });
} else {
res.writeHead(200);
}
res.end(hs);
res.end(hs);
self.log.info('handshaken', id);
});
self.onHandshake(id, newData || handshakeData);
self.store.publish('handshake', id, newData || handshakeData);
self.log.info('handshake authorized', id);
} else {
writeErr(403, 'handshake unauthorized');
self.log.info('handshake unauthorized');
}
})
};
/**
* Gets normalized handshake data
*
* @api private
*/
Manager.prototype.handshakeData = function (data) {
var connectionAddress = null;
if (data.request.connection.address) {
connectionAddress = data.request.connection.address();
}
return {
headers: data.headers
, address: connectionAddress
, time: (new Date).toString()
, xdomain: !!data.request.headers.origin
, secure: data.request.connection.secure
};
};
/**
* Verifies the origin of a request.
*

View File

@@ -1,4 +1,3 @@
/**
* Module dependencies.
*/
@@ -24,6 +23,7 @@ function SocketNamespace (mgr, name) {
this.manager = mgr;
this.name = name || '';
this.sockets = {};
this.auth = false;
this.setFlags();
};
@@ -33,6 +33,33 @@ function SocketNamespace (mgr, name) {
SocketNamespace.prototype.__proto__ = EventEmitter.prototype;
/**
* Copies emit since we override it
*
* @api private
*/
SocketNamespace.prototype.$emit = EventEmitter.prototype.emit;
/**
* Retrieves all clients as Socket instances as an array.
*
* @api public
*/
SocketNamespace.prototype.clients = function (room) {
var room = this.name + (room !== undefined ?
(this.name !== '' ? '/' : '') + room : '');
if (!this.manager.rooms[room]) {
return [];
}
return this.manager.rooms[room].map(function (id) {
return this.socket(id);
}, this);
};
/**
* Access logger interface.
*
@@ -126,20 +153,8 @@ SocketNamespace.prototype.packet = function (packet) {
, exceptions = this.flags.exceptions
, packet = parser.encodePacket(packet);
store.clients(this.flags.endpoint, function (clients) {
clients.forEach(function (id) {
if (~exceptions.indexOf(id)) {
log.debug('ignoring packet to ', id);
return;
}
if (volatile) {
store.publish('volatile:' + id, packet);
} else {
store.client(id).publish(packet);
}
});
});
this.manager.onDispatch(this.flags.endpoint, packet, volatile, exceptions);
this.store.publish('dispatch', this.flags.endpoint, packet, volatile, exceptions);
this.setFlags();
@@ -167,7 +182,7 @@ SocketNamespace.prototype.send = function (data) {
SocketNamespace.prototype.emit = function (name) {
if (name == 'connection' || name == 'newListener') {
return EventEmitter.prototype.emit.apply(this, arguments);
return this.$emit.apply(this, arguments);
}
return this.packet({
@@ -192,6 +207,53 @@ SocketNamespace.prototype.socket = function (sid, readable) {
return this.sockets[sid];
};
/**
* Sets authorization for this namespace
*
* @api public
*/
SocketNamespace.prototype.authorization = function (fn) {
this.auth = fn;
return this;
};
/**
* Called when a socket disconnects entirely.
*
* @api private
*/
SocketNamespace.prototype.handleDisconnect = function (sid, reason) {
if (this.sockets[sid] && this.sockets[sid].readable) {
this.sockets[sid].onDisconnect(reason);
}
};
/**
* Performs authentication.
*
* @param Object client request data
* @api private
*/
SocketNamespace.prototype.authorize = function (data, fn) {
if (this.auth) {
var self = this;
this.auth.call(this, data, function (err, authorized) {
self.log.debug('client ' +
(authorized ? '' : 'un') + 'authorized for ' + self.name);
fn(err, authorized);
});
} else {
this.log.debug('client authorized for ' + this.name);
fn(null, true);
}
return this;
};
/**
* Handles a packet.
*
@@ -212,13 +274,42 @@ SocketNamespace.prototype.handlePacket = function (sessid, packet) {
});
};
function error (err) {
self.log.warn('handshake error ' + err + ' for ' + self.name);
socket.packet({ type: 'error', reason: err });
};
function connect () {
self.manager.onJoin(sessid, self.name);
self.store.publish('join', sessid, self.name);
// packet echo
socket.packet({ type: 'connect' });
// emit connection event
self.emit('connection', socket);
};
switch (packet.type) {
case 'connect':
this.store.join(sessid, this.name, function () {
// packet echo
socket.packet({ type: 'connect' });
self.emit('connection', socket);
});
if (packet.endpoint == '') {
connect();
} else {
var manager = this.manager
, handshakeData = manager.handshaken[sessid];
this.authorize(handshakeData, function (err, authorized, newData) {
if (err) return error(err);
if (authorized) {
manager.onHandshake(sessid, newData || handshakeData);
self.store.publish('handshake', sessid, newData || handshakeData);
connect();
} else {
error('unauthorized');
}
});
}
break;
case 'ack':
@@ -239,7 +330,10 @@ SocketNamespace.prototype.handlePacket = function (sessid, packet) {
break;
case 'disconnect':
socket.emit('disconnect');
this.manager.onLeave(sessid, this.name);
this.store.publish('leave', sessid, this.name);
socket.emit('disconnect', packet.reason || 'packet');
break;
case 'json':

View File

@@ -22,6 +22,7 @@ var packets = exports.packets = [
, 'event'
, 'ack'
, 'error'
, 'noop'
];
/**

View File

@@ -9,15 +9,13 @@
* Module dependencies.
*/
var http = require('http')
, https = require('https')
, client = require('socket.io-client');
var client = require('socket.io-client');
/**
* Version.
*/
exports.version = '0.7.0';
exports.version = '0.7.3';
/**
* Supported protocol version.
@@ -53,9 +51,9 @@ exports.listen = function (server, options, fn) {
var port = server;
if (options && options.key)
server = https.createServer(options, server);
server = require('https').createServer(options);
else
server = http.createServer();
server = require('http').createServer();
// default response
server.on('request', function (req, res) {
@@ -102,6 +100,22 @@ exports.Socket = require('./socket');
exports.Store = require('./store');
/**
* Memory Store constructor.
*
* @api public
*/
exports.MemoryStore = require('./stores/memory');
/**
* Redis Store constructor.
*
* @api public
*/
exports.RedisStore = require('./stores/redis');
/**
* Parser.
*

View File

@@ -53,14 +53,8 @@ function Socket (manager, id, nsp, readable) {
this.ackPackets = 0;
this.acks = {};
this.setFlags();
if (readable) {
var self = this;
this.store.once('disconnect:' + id, function (reason) {
self.onDisconnect(reason);
});
}
this.readable = readable;
this.store = this.manager.store.client(this.id);
};
/**
@@ -70,13 +64,13 @@ function Socket (manager, id, nsp, readable) {
Socket.prototype.__proto__ = EventEmitter.prototype;
/**
* Accessor shortcut for the store.
* Accessor shortcut for the handshake data
*
* @api private
*/
Socket.prototype.__defineGetter__('store', function () {
return this.manager.store;
Socket.prototype.__defineGetter__('handshake', function () {
return this.manager.handshaken[this.id];
});
/**
@@ -167,8 +161,17 @@ Socket.prototype.onDisconnect = function (reason) {
*/
Socket.prototype.join = function (name, fn) {
var nsp = this.namespace.name;
this.store.join(this.id, (nsp === '' ? '' : (nsp + '/')) + name, fn);
var nsp = this.namespace.name
, name = (nsp === '' ? '' : (nsp + '/')) + name;
this.manager.onJoin(this.id, name);
this.manager.store.publish('join', this.id, name);
if (fn) {
this.log.warn('Client#join callback is deprecated');
fn();
}
return this;
};
@@ -179,8 +182,17 @@ Socket.prototype.join = function (name, fn) {
*/
Socket.prototype.leave = function (name, fn) {
var nsp = this.namespace.name;
this.store.leave(this.id, (nsp === '' ? '' : (nsp + '/')) + name, fn);
var nsp = this.namespace.name
, name = (nsp === '' ? '' : (nsp + '/')) + name;
this.manager.onLeave(this.id, name);
this.manager.store.publish('leave', this.id, name);
if (fn) {
this.log.warn('Client#leave callback is deprecated');
fn();
}
return this;
};
@@ -198,11 +210,7 @@ Socket.prototype.packet = function (packet) {
packet.endpoint = this.flags.endpoint;
packet = parser.encodePacket(packet);
if (this.flags.volatile) {
this.store.publish('volatile:' + this.id, packet);
} else {
this.store.client(this.id).publish(packet);
}
this.dispatch(packet, this.flags.volatile);
}
this.setFlags();
@@ -210,6 +218,24 @@ Socket.prototype.packet = function (packet) {
return this;
};
/**
* Dispatches a packet
*
* @api private
*/
Socket.prototype.dispatch = function (packet, volatile) {
if (this.manager.transports[this.id] && this.manager.transports[this.id].open) {
this.manager.transports[this.id].onDispatch(packet, volatile);
} else {
if (!volatile) {
this.manager.onClientDispatch(this.id, packet, volatile);
}
this.manager.store.publish('dispatch:' + this.id, packet, volatile);
}
};
/**
* Stores data for the client.
*
@@ -217,7 +243,7 @@ Socket.prototype.packet = function (packet) {
*/
Socket.prototype.set = function (key, value, fn) {
this.store.client(this.id).set(key, value, fn);
this.store.set(key, value, fn);
return this;
};
@@ -228,7 +254,29 @@ Socket.prototype.set = function (key, value, fn) {
*/
Socket.prototype.get = function (key, fn) {
this.store.client(this.id).get(key, fn);
this.store.get(key, fn);
return this;
};
/**
* Checks data for the client
*
* @api public
*/
Socket.prototype.has = function (key, fn) {
this.store.has(key, fn);
return this;
};
/**
* Deletes data for the client
*
* @api public
*/
Socket.prototype.del = function (key, fn) {
this.store.del(key, fn);
return this;
};
@@ -241,7 +289,18 @@ Socket.prototype.get = function (key, fn) {
Socket.prototype.disconnect = function () {
if (!this.disconnected) {
this.log.info('booting client');
this.store.disconnect(this.id, true);
if (this.manager.transports[this.id] && this.manager.transports[this.id].open) {
this.manager.transports[this.id].onForcedDisconnect();
} else {
if (this.manager.open[this.id]) {
// if the connection is open in a different node
this.manager.store.publish('disconnect-force:' + this.id);
} else {
this.manager.onClientDisconnect(this.id);
this.manager.store.publish('disconnect:' + this.id);
}
}
}
return this;

View File

@@ -5,41 +5,86 @@
* MIT Licensed
*/
/**
* Module dependencies.
*/
var EventEmitter = process.EventEmitter;
/**
* Expose the constructor.
*/
exports = module.exports = Store;
/**
* Module dependencies.
*/
var EventEmitter = process.EventEmitter;
/**
* Store interface
*
* @api public
*/
function Store () {};
function Store (options) {
this.options = options;
this.clients = {};
};
/**
* Inherits from EventEmitter
* Inherit from EventEmitter.
*/
Store.prototype.__proto__ = EventEmitter.prototype;
/**
* Log accessor.
* Initializes a client store
*
* @param {String} id
* @api public
*/
Store.prototype.__defineGetter__('log', function () {
return this.manager.log;
});
Store.prototype.client = function (id) {
if (!this.clients[id]) {
this.clients[id] = new (this.constructor.Client)(this, id);
}
return this.clients[id];
};
/**
* Destroys a client
*
* @api {String} sid
* @param {Number} number of seconds to expire client data
* @api private
*/
Store.prototype.destroyClient = function (id, expiration) {
if (this.clients[id]) {
this.clients[id].destroy(expiration);
delete this.clients[id];
}
return this;
};
/**
* Destroys the store
*
* @param {Number} number of seconds to expire client data
* @api private
*/
Store.prototype.destroy = function (clientExpiration) {
var keys = Object.keys(this.clients)
, count = keys.length;
for (var i = 0, l = count; i < l; i++) {
this.destroyClient(keys[i], clientExpiration);
}
this.clients = {};
return this;
};
/**
* Client.
@@ -50,6 +95,4 @@ Store.prototype.__defineGetter__('log', function () {
Store.Client = function (store, id) {
this.store = store;
this.id = id;
this.buffer = [];
this.dict = {};
};

View File

@@ -10,7 +10,7 @@
*/
var crypto = require('crypto')
, Store = require('../store')
, Store = require('../store');
/**
* Exports the constructor.
@@ -26,9 +26,7 @@ Memory.Client = Client;
*/
function Memory (opts) {
this.handshaken = [];
this.clientsMap = {};
this.rooms = {};
Store.call(this, opts);
};
/**
@@ -38,192 +36,28 @@ function Memory (opts) {
Memory.prototype.__proto__ = Store.prototype;
/**
* Handshake a client.
*
* @param {Object} client request object
* @param {Function} callback
* @api public
*/
Memory.prototype.handshake = function (data, fn) {
var id = this.generateId();
this.handshaken.push(id);
fn(null, id);
return this;
};
/**
* Checks if a client is handshaken.
*
* @api public
*/
Memory.prototype.isHandshaken = function (id, fn) {
fn(null, ~this.handshaken.indexOf(id));
return this;
};
/**
* Generates a random id.
* Publishes a message.
*
* @api private
*/
Memory.prototype.generateId = function () {
var rand = String(Math.random() * Math.random() * Date.now());
return crypto.createHash('md5').update(rand).digest('hex');
};
Memory.prototype.publish = function () { };
/**
* Retrieves a client store instance.
*
* @api public
*/
Memory.prototype.client = function (id) {
if (!this.clientsMap[id]) {
this.clientsMap[id] = new Memory.Client(this, id);
this.log.debug('initializing client store for', id);
}
return this.clientsMap[id];
};
/**
* Called when a client disconnects.
*
* @api public
*/
Memory.prototype.disconnect = function (id, force, reason) {
if (~this.handshaken.indexOf(id)) {
this.log.debug('destroying dispatcher for', id);
this.handshaken.splice(this.handshaken.indexOf(id), 1);
this.clientsMap[id].destroy();
this.clientsMap[id] = null;
if (force)
this.publish('disconnect-force:' + id, reason);
this.publish('disconnect:' + id, reason);
}
return this;
};
/**
* Relays a heartbeat message.
* Subscribes to a channel
*
* @api private
*/
Memory.prototype.heartbeat = function (id) {
return this.publish('heartbeat-clear:' + id);
};
Memory.prototype.subscribe = function () { };
/**
* Relays a packet
* Unsubscribes
*
* @api private
*/
Memory.prototype.message = function (id, packet) {
return this.publish('message:' + id, packet);
};
/**
* Returns client ids in a particular room
*
* @api public
*/
Memory.prototype.clients = function (room, fn) {
if ('function' == typeof room) {
fn = room;
room = '';
}
fn && fn(this.rooms[room] || []);
};
/**
* Joins a user to a room
*
* @api private
*/
Memory.prototype.join = function (sid, room, fn) {
if (!this.rooms[room]) {
this.rooms[room] = [];
}
this.client(sid).rooms[room] = this.rooms[room].length;
this.rooms[room].push(sid);
fn && fn();
return this;
};
/**
* Removes a user from a room
*
* @api private
*/
Memory.prototype.leave = function (sid, room, fn) {
if (!this.rooms[room] || this.client(sid).rooms[room] === undefined) {
return this;
}
var i = this.client(sid).rooms[room];
this.rooms[room][i] = null;
delete this.client(sid).rooms[room];
fn && fn();
return this;
};
/**
* Simple publish
*
* @api public
*/
Memory.prototype.publish = function (ev, data, fn) {
if ('function' == typeof data) {
fn = data;
data = undefined;
}
this.emit(ev, data);
if (fn) fn();
return this;
};
/**
* Simple subscribe
*
* @api public
*/
Memory.prototype.subscribe = function (chn, fn) {
this.on(chn, fn);
return this;
};
/**
* Simple unsubscribe
*
* @api public
*/
Memory.prototype.unsubscribe = function (chn) {
this.removeAllListeners(chn);
};
Memory.prototype.unsubscribe = function () { };
/**
* Client constructor
@@ -233,9 +67,7 @@ Memory.prototype.unsubscribe = function (chn) {
function Client () {
Store.Client.apply(this, arguments);
this.reqs = 0;
this.paused = true;
this.rooms = {};
this.data = {};
};
/**
@@ -244,73 +76,6 @@ function Client () {
Client.prototype.__proto__ = Store.Client;
/**
* Counts transport requests.
*
* @api public
*/
Client.prototype.count = function (fn) {
fn(null, ++this.reqs);
return this;
};
/**
* Sets up queue consumption
*
* @api public
*/
Client.prototype.consume = function (fn) {
this.consumer = fn;
this.paused = false;
if (this.buffer.length) {
fn(this.buffer, null);
this.buffer = [];
}
return this;
};
/**
* Publishes a message to be sent to the client.
*
* @String encoded message
* @api public
*/
Client.prototype.publish = function (msg) {
if (this.paused) {
this.buffer.push(msg);
} else {
this.consumer(null, msg);
}
return this;
};
/**
* Pauses the stream.
*
* @api public
*/
Client.prototype.pause = function () {
this.paused = true;
return this;
};
/**
* Destroys the client.
*
* @api public
*/
Client.prototype.destroy = function () {
this.buffer = null;
};
/**
* Gets a key
*
@@ -318,7 +83,7 @@ Client.prototype.destroy = function () {
*/
Client.prototype.get = function (key, fn) {
fn(null, this.dict[key]);
fn(null, this.data[key] === undefined ? null : this.data[key]);
return this;
};
@@ -329,18 +94,50 @@ Client.prototype.get = function (key, fn) {
*/
Client.prototype.set = function (key, value, fn) {
this.dict[key] = value;
this.data[key] = value;
fn && fn(null);
return this;
};
/**
* Emits a message incoming from client.
* Has a key
*
* @api public
*/
Client.prototype.has = function (key, fn) {
fn(null, key in this.data);
};
/**
* Deletes a key
*
* @api public
*/
Client.prototype.del = function (key, fn) {
delete this.data[key];
fn && fn(null);
return this;
};
/**
* Destroys the client.
*
* @param {Number} number of seconds to expire data
* @api private
*/
Client.prototype.onMessage = function (msg) {
this.store.emit('message:' + id, msg);
};
Client.prototype.destroy = function (expiration) {
if ('number' != typeof expiration) {
this.data = {};
} else {
var self = this;
setTimeout(function () {
self.data = {};
}, expiration * 1000);
}
return this;
};

248
lib/stores/redis.js Normal file
View File

@@ -0,0 +1,248 @@
/*!
* socket.io-node
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
* MIT Licensed
*/
/**
* Module dependencies.
*/
var crypto = require('crypto')
, Store = require('../store')
, assert = require('assert')
, redis = require('redis');
/**
* Exports the constructor.
*/
exports = module.exports = Redis;
Redis.Client = Client;
/**
* Redis store.
* Options:
* - nodeId (fn) gets an id that uniquely identifies this node
* - redisPub (object) options to pass to the pub redis client
* - redisSub (object) options to pass to the sub redis client
* - redisClient (object) options to pass to the general redis client
* - pack (fn) custom packing, defaults to JSON or msgpack if installed
* - unpack (fn) custom packing, defaults to JSON or msgpack if installed
*
* @api public
*/
function Redis (opts) {
opts = opts || {};
// node id to uniquely identify this node
var nodeId = opts.nodeId || function () {
// by default, we generate a random id
return Math.abs(Math.random() * Math.random() * Date.now() | 0);
};
this.nodeId = nodeId();
// packing / unpacking mechanism
if (opts.pack) {
this.pack = opts.pack;
this.unpack = opts.unpack;
} else {
try {
var msgpack = require('msgpack');
this.pack = msgpack.pack;
this.unpack = msgpack.unpack;
} catch (e) {
this.pack = JSON.stringify;
this.unpack = JSON.parse;
}
}
// initialize a pubsub client and a regular client
this.pub = redis.createClient(opts.redisPub);
this.sub = redis.createClient(opts.redisSub);
this.cmd = redis.createClient(opts.redisClient);
Store.call(this, opts);
};
/**
* Inherits from Store.
*/
Redis.prototype.__proto__ = Store.prototype;
/**
* Publishes a message.
*
* @api private
*/
Redis.prototype.publish = function (name) {
var args = Array.prototype.slice.call(arguments, 1);
this.pub.publish(name, this.pack({ nodeId: this.nodeId, args: args }));
this.emit.apply(this, ['publish', name].concat(args));
};
/**
* Subscribes to a channel
*
* @api private
*/
Redis.prototype.subscribe = function (name, consumer, fn) {
this.sub.subscribe(name);
if (consumer || fn) {
var self = this;
self.sub.on('subscribe', function subscribe (ch) {
if (name == ch) {
function message (ch, msg) {
if (name == ch) {
msg = self.unpack(msg);
// we check that the message consumed wasnt emitted by this node
if (self.nodeId != msg.nodeId) {
consumer.apply(null, msg.args);
}
}
};
self.sub.on('message', message);
self.on('unsubscribe', function unsubscribe (ch) {
if (name == ch) {
self.sub.removeListener('message', message);
self.removeEvent('unsubscribe', unsubscribe);
}
});
self.sub.removeListener('subscribe', subscribe);
fn && fn();
}
});
}
this.emit('subscribe', name, consumer, fn);
};
/**
* Unsubscribes
*
* @api private
*/
Redis.prototype.unsubscribe = function (name, fn) {
this.sub.unsubscribe(name);
if (fn) {
var client = this.sub;
client.on('unsubscribe', function unsubscribe (ch) {
if (name == ch) {
fn();
client.removeListener('unsubscribe', unsubscribe);
}
});
}
this.emit('unsubscribe', name, fn);
};
/**
* Destroys the store
*
* @api public
*/
Redis.prototype.destroy = function () {
Store.prototype.destroy.call(this);
this.pub.end();
this.sub.end();
this.cmd.end();
};
/**
* Client constructor
*
* @api private
*/
function Client (store, id) {
Store.Client.call(this, store, id);
};
/**
* Inherits from Store.Client
*/
Client.prototype.__proto__ = Store.Client;
/**
* Redis hash get
*
* @api private
*/
Client.prototype.get = function (key, fn) {
this.store.cmd.hget(this.id, key, fn);
return this;
};
/**
* Redis hash set
*
* @api private
*/
Client.prototype.set = function (key, value, fn) {
this.store.cmd.hset(this.id, key, value, fn);
return this;
};
/**
* Redis hash del
*
* @api private
*/
Client.prototype.del = function (key, fn) {
this.store.cmd.hdel(this.id, key, fn);
return this;
};
/**
* Redis hash has
*
* @api private
*/
Client.prototype.has = function (key, fn) {
this.store.cmd.hexists(this.id, key, function (err, has) {
if (err) return fn(err);
fn(null, !!has);
});
return this;
};
/**
* Destroys client
*
* @param {Number} number of seconds to expire data
* @api private
*/
Client.prototype.destroy = function (expiration) {
if ('number' != typeof expiration) {
this.store.cmd.del(this.id);
} else {
this.store.cmd.expire(this.id, expiration);
}
return this;
};

View File

@@ -23,22 +23,14 @@ exports = module.exports = Transport;
* @api public
*/
function Transport (mng, data) {
function Transport (mng, data, req) {
this.manager = mng;
this.id = data.id;
this.paused = true;
this.disconnected = false;
this.drained = true;
};
/**
* Sets the corresponding request object.
*/
Transport.prototype.__defineSetter__('request', function (req) {
this.log.debug('setting request', req.method, req.url);
this.buffer = [];
this.handleRequest(req);
});
};
/**
* Access the logger.
@@ -67,29 +59,14 @@ Transport.prototype.__defineGetter__('store', function () {
*/
Transport.prototype.handleRequest = function (req) {
this.log.debug('setting request', req.method, req.url);
this.req = req;
if (req.method == 'GET') {
this.socket = req.socket;
this.open = true;
this.drained = true;
this.log.debug('publishing that', this.id, 'connected');
var self = this;
this.store.publish('open:' + this.id, function () {
self.store.once('open:' + self.id, function () {
self.log.info('request for existing session connection change');
self.close();
self.clearTimeouts();
self.clearHandlers();
});
if (!self.paused) {
self.subscribe();
}
});
this.setHeartbeatInterval();
this.setHandlers();
this.onSocketConnect();
@@ -113,17 +90,18 @@ Transport.prototype.onSocketConnect = function () { };
Transport.prototype.setHandlers = function () {
var self = this;
this.store.once('disconnect-force:' + this.id, function () {
// we need to do this in a pub/sub way since the client can POST the message
// over a different socket (ie: different Transport instance)
this.store.subscribe('heartbeat-clear:' + this.id, function () {
self.onHeartbeatClear();
});
this.store.subscribe('disconnect-force:' + this.id, function () {
self.onForcedDisconnect();
});
this.store.on('heartbeat-clear:' + this.id, function () {
self.clearHeartbeatTimeout();
self.setHeartbeatInterval();
});
this.store.on('volatile:' + this.id, function (packet) {
self.writeVolatile(packet);
this.store.subscribe('dispatch:' + this.id, function (packet, volatile) {
self.onDispatch(packet, volatile);
});
this.bound = {
@@ -148,7 +126,7 @@ Transport.prototype.setHandlers = function () {
Transport.prototype.clearHandlers = function () {
this.store.unsubscribe('disconnect-force:' + this.id);
this.store.unsubscribe('heartbeat-clear:' + this.id);
this.store.unsubscribe('volatile:' + this.id);
this.store.unsubscribe('dispatch:' + this.id);
this.socket.removeListener('end', this.bound.end);
this.socket.removeListener('close', this.bound.close);
@@ -163,10 +141,7 @@ Transport.prototype.clearHandlers = function () {
*/
Transport.prototype.onSocketEnd = function () {
// we check that the socket wasn't swapped
// we don't want to sever a connection that's not active, since we don't kill
// inactive sockets that the browser might reuse for other purposes
this.end(false, 'socket end');
this.end('socket end');
};
/**
@@ -176,7 +151,7 @@ Transport.prototype.onSocketEnd = function () {
*/
Transport.prototype.onSocketClose = function (error) {
this.end(false, error ? 'socket error' : 'socket close');
this.end(error ? 'socket error' : 'socket close');
};
/**
@@ -204,6 +179,17 @@ Transport.prototype.onSocketDrain = function () {
this.drained = true;
};
/**
* Called upon receiving a heartbeat packet.
*
* @api private
*/
Transport.prototype.onHeartbeatClear = function () {
this.clearHeartbeatTimeout();
this.setHeartbeatInterval();
};
/**
* Called upon a forced disconnection.
*
@@ -216,7 +202,21 @@ Transport.prototype.onForcedDisconnect = function () {
if (this.open) {
this.packet({ type: 'disconnect' });
}
this.end(true);
this.end('booted');
}
};
/**
* Dispatches a packet.
*
* @api private
*/
Transport.prototype.onDispatch = function (packet, volatile) {
if (volatile) {
this.writeVolatile(packet);
} else {
this.write(packet);
}
};
@@ -231,7 +231,7 @@ Transport.prototype.setCloseTimeout = function () {
this.closeTimeout = setTimeout(function () {
self.log.debug('fired close timeout for client', self.id);
self.closeTimeout = null;
self.end(false, 'close timeout');
self.end('close timeout');
}, this.manager.get('close timeout') * 1000);
this.log.debug('set close timeout for client', this.id);
@@ -262,7 +262,7 @@ Transport.prototype.setHeartbeatTimeout = function () {
this.heartbeatTimeout = setTimeout(function () {
self.log.debug('fired heartbeat timeout for client', self.id);
self.heartbeatTimeout = null;
self.end(false, 'heartbeat timeout');
self.end('heartbeat timeout');
}, this.manager.get('heartbeat timeout') * 1000);
this.log.debug('set heartbeat timeout for client', this.id);
@@ -291,11 +291,12 @@ Transport.prototype.clearHeartbeatTimeout = function () {
*/
Transport.prototype.setHeartbeatInterval = function () {
if (!this.heartbeatTimeout) {
if (!this.heartbeatInterval) {
var self = this;
this.heartbeatInterval = setTimeout(function () {
self.heartbeat();
self.heartbeatInterval = null;
}, this.manager.get('heartbeat interval') * 1000);
this.log.debug('set heartbeat interval for client', this.id);
@@ -338,25 +339,50 @@ Transport.prototype.heartbeat = function () {
*/
Transport.prototype.onMessage = function (packet) {
var current = this.manager.transports[this.id];
if ('heartbeat' == packet.type) {
this.log.debug('got heartbeat packet');
this.store.heartbeat(this.id);
} else if ('disconnect' == packet.type && packet.endpoint == '') {
this.log.debug('got disconnection packet');
this.store.disconnect(this.id, true);
if (current && current.open) {
current.onHeartbeatClear();
} else {
this.store.publish('heartbeat-clear:' + this.id);
}
} else {
this.log.debug('got packet');
if ('disconnect' == packet.type && packet.endpoint == '') {
this.log.debug('got disconnection packet');
if (current && current.open) {
current.onForcedDisconnect();
} else {
this.store.publish('disconnect-force:' + this.id);
}
}
if (packet.id && packet.ack != 'data') {
this.log.debug('acknowledging packet automatically');
this.store.client(this.id).publish(parser.encodePacket({
var ack = parser.encodePacket({
type: 'ack'
, ackId: packet.id
, endpoint: packet.endpoint || ''
}));
});
if (current && current.open) {
current.onDispatch(ack);
} else {
this.manager.onClientDispatch(this.id, ack);
this.store.publish('dispatch:' + this.id, ack);
}
}
this.store.message(this.id, packet);
// handle packet locally or publish it
if (current) {
this.manager.onClientMessage(this.id, packet);
} else {
this.store.publish('message:' + this.id, packet);
}
}
};
@@ -382,7 +408,7 @@ Transport.prototype.clearHeartbeatInterval = function () {
Transport.prototype.disconnect = function (reason) {
this.packet({ type: 'disconnect' });
this.end(false, reason);
this.end(reason);
return this;
};
@@ -409,8 +435,9 @@ Transport.prototype.close = function () {
Transport.prototype.onClose = function () {
if (this.open) {
this.setCloseTimeout();
this.unsubscribe();
this.open = false;
this.manager.onClose(this.id);
this.store.publish('close', this.id);
}
};
@@ -420,43 +447,37 @@ Transport.prototype.onClose = function () {
* @api private
*/
Transport.prototype.end = function (forced, reason) {
Transport.prototype.end = function (reason) {
if (!this.disconnected) {
this.log.info('ending socket');
var local = this.manager.transports[this.id];
this.close();
this.clearTimeouts();
if (!forced)
this.store.disconnect(this.id, false, reason);
this.disconnected = true;
if (local) {
this.manager.onClientDisconnect(this.id, reason, true);
} else {
this.store.publish('disconnect:' + this.id, reason);
}
}
};
/**
* Signals that the transport can start flushing buffers.
*
* @api public
*/
Transport.prototype.resume = function () {
if (!this.disconnected) {
this.paused = false;
this.setHeartbeatInterval();
this.subscribe();
}
return this;
};
/**
* Signals that the transport should pause and buffer data.
*
* @api public
*/
Transport.prototype.pause = function () {
this.paused = true;
Transport.prototype.discard = function () {
this.log.debug('discarding transport');
this.discarded = true;
this.clearTimeouts();
this.clearHandlers();
this.buffer = [];
return this;
};
@@ -476,7 +497,7 @@ Transport.prototype.error = function (reason, advice) {
});
this.log.warn(reason, advice ? ('client should ' + advice) : '');
this.end(false, 'error');
this.end('error');
};
/**
@@ -489,44 +510,6 @@ Transport.prototype.packet = function (obj) {
return this.write(parser.encodePacket(obj));
};
/**
* Subscribe client.
*
* @api private
*/
Transport.prototype.subscribe = function () {
if (!this.subscribed) {
this.log.debug('subscribing', this.id);
var self = this;
// subscribe to buffered + normal messages
this.store.client(this.id).consume(function (payload, packet) {
if (payload) {
self.payload(payload);
} else {
self.write(packet);
}
});
this.subscribed = true;
}
};
/**
* Unsubscribe client.
*
* @api private
*/
Transport.prototype.unsubscribe = function () {
this.log.info('unsubscribing', this.id);
this.store.client(this.id).pause();
this.subscribed = false;
};
/**
* Writes a volatile message.
*

View File

@@ -23,8 +23,8 @@ exports = module.exports = FlashSocket;
* @api public
*/
function FlashSocket () {
WebSocket.apply(this, arguments);
function FlashSocket (mng, data, req) {
WebSocket.call(this, mng, data, req);
}
/**
@@ -33,6 +33,14 @@ function FlashSocket () {
FlashSocket.prototype.__proto__ = WebSocket.prototype;
/**
* Transport name
*
* @api public
*/
FlashSocket.prototype.name = 'flashsocket';
/**
* Listens for new configuration changes of the Manager
* this way we can enable and disable the flash server.

View File

@@ -23,8 +23,8 @@ exports = module.exports = HTMLFile;
* @api public
*/
function HTMLFile (mng, data) {
HTTPTransport.call(this, mng, data);
function HTMLFile (mng, data, req) {
HTTPTransport.call(this, mng, data, req);
};
/**
@@ -33,6 +33,14 @@ function HTMLFile (mng, data) {
HTMLFile.prototype.__proto__ = HTTPTransport.prototype;
/**
* Transport name
*
* @api public
*/
HTMLFile.prototype.name = 'htmlfile';
/**
* Handles the request.
*
@@ -70,5 +78,5 @@ HTMLFile.prototype.write = function (data) {
this.drained = true;
}
this.log.debug('htmlfile writing', data);
this.log.debug(this.name + ' writing', data);
};

View File

@@ -23,8 +23,8 @@ exports = module.exports = HTTPPolling;
* @api public.
*/
function HTTPPolling (mng, data) {
HTTPTransport.call(this, mng, data);
function HTTPPolling (mng, data, req) {
HTTPTransport.call(this, mng, data, req);
};
/**
@@ -35,6 +35,14 @@ function HTTPPolling (mng, data) {
HTTPPolling.prototype.__proto__ = HTTPTransport.prototype;
/**
* Transport name
*
* @api public
*/
HTTPPolling.prototype.name = 'httppolling';
/**
* Removes heartbeat timeouts for polling.
*/
@@ -56,8 +64,8 @@ HTTPPolling.prototype.handleRequest = function (req) {
var self = this;
this.pollTimeout = setTimeout(function () {
self.close();
self.log.debug('polling closed due to exceeded duration');
self.packet({ type: 'noop' });
self.log.debug(self.name + ' closed due to exceeded duration');
}, this.manager.get('polling duration') * 1000);
this.log.debug('setting poll timeout');

View File

@@ -25,8 +25,8 @@ exports = module.exports = HTTPTransport;
* @api public
*/
function HTTPTransport (mng, data) {
Transport.call(this, mng, data);
function HTTPTransport (mng, data, req) {
Transport.call(this, mng, data, req);
};
/**
@@ -85,7 +85,7 @@ HTTPTransport.prototype.onData = function (data) {
var messages = parser.decodePayload(data);
for (var i = 0, l = messages.length; i < l; i++) {
this.log.debug('xhr received data packet', data);
this.log.debug(this.name + ' received data packet', data);
this.onMessage(messages[i]);
}
};

View File

@@ -23,8 +23,8 @@ exports = module.exports = JSONPPolling;
* @api public
*/
function JSONPPolling (mng, data) {
HTTPPolling.call(this, mng, data);
function JSONPPolling (mng, data, req) {
HTTPPolling.call(this, mng, data, req);
this.head = 'io.j[0](';
this.foot = ');';
@@ -40,6 +40,14 @@ function JSONPPolling (mng, data) {
JSONPPolling.prototype.__proto__ = HTTPPolling.prototype;
/**
* Transport name
*
* @api public
*/
JSONPPolling.prototype.name = 'jsonppolling';
/**
* Make sure POST are decoded.
*/
@@ -65,5 +73,5 @@ JSONPPolling.prototype.doWrite = function (data) {
});
this.response.write(data);
this.log.debug('json-p writing', data);
this.log.debug(this.name + ' writing', data);
};

View File

@@ -27,13 +27,13 @@ exports = module.exports = WebSocket;
* @api public
*/
function WebSocket (mng, data) {
function WebSocket (mng, data, req) {
// parser
var self = this;
this.parser = new Parser();
this.parser.on('data', function (packet) {
self.log.debug('websocket received data packet', packet);
self.log.debug(self.name + ' received data packet', packet);
self.onMessage(parser.decodePacket(packet));
});
this.parser.on('close', function () {
@@ -43,7 +43,7 @@ function WebSocket (mng, data) {
self.end();
});
Transport.call(this, mng, data);
Transport.call(this, mng, data, req);
};
/**
@@ -52,6 +52,14 @@ function WebSocket (mng, data) {
WebSocket.prototype.__proto__ = Transport.prototype;
/**
* Transport name
*
* @api public
*/
WebSocket.prototype.name = 'websocket';
/**
* Called when the socket connects.
*
@@ -67,7 +75,7 @@ WebSocket.prototype.onSocketConnect = function () {
this.buffered = [];
if (this.req.headers.upgrade !== 'WebSocket') {
this.log.warn('WebSocket connection invalid');
this.log.warn(this.name + ' connection invalid');
this.end();
return;
}
@@ -180,7 +188,7 @@ WebSocket.prototype.write = function (data) {
this.end();
}
this.log.debug('websocket writing', data);
this.log.debug(this.name + ' writing', data);
}
};
@@ -194,7 +202,7 @@ WebSocket.prototype.flush = function () {
this.buffer = false;
for (var i = 0, l = this.buffered.length; i < l; i++) {
this.write(this.buffered.splice(0, 1));
this.write(this.buffered.splice(0, 1)[0]);
}
};
@@ -217,7 +225,7 @@ WebSocket.prototype.proveReception = function (headers) {
, spaces = k.replace(/[^ ]/g, '').length;
if (spaces === 0 || n % spaces !== 0){
self.log.warn('Invalid WebSocket key: "' + k + '".');
self.log.warn('Invalid ' + self.name + ' key: "' + k + '".');
self.end();
return false;
}

View File

@@ -23,8 +23,8 @@ exports = module.exports = XHRPolling;
* @api public
*/
function XHRPolling (mng, data) {
HTTPPolling.call(this, mng, data);
function XHRPolling (mng, data, req) {
HTTPPolling.call(this, mng, data, req);
};
/**
@@ -33,6 +33,14 @@ function XHRPolling (mng, data) {
XHRPolling.prototype.__proto__ = HTTPPolling.prototype;
/**
* Transport name
*
* @api public
*/
XHRPolling.prototype.name = 'xhr-polling';
/**
* Frames data prior to write.
*
@@ -60,5 +68,5 @@ XHRPolling.prototype.doWrite = function (data) {
this.response.writeHead(200, headers);
this.response.write(data);
this.log.debug('xhr-polling writing', data);
this.log.debug(this.name + ' writing', data);
};

View File

@@ -1,6 +1,6 @@
{
"name": "socket.io"
, "version": "0.7.1"
, "version": "0.7.3"
, "description": "Realtime apps made cross-browser & easy with a WebSocket-like API"
, "homepage": "http://socket.io"
, "keywords": ["websocket", "socket", "realtime", "socket.io", "comet", "ajax"]
@@ -15,8 +15,9 @@
, "url": "https://github.com/LearnBoost/Socket.IO-node.git"
}
, "dependencies": {
"socket.io-client": "0.7.0"
, "policyfile": ">= 0.0.3"
"socket.io-client": "0.7.3"
, "policyfile": "0.0.3"
, "redis": "0.6.0"
}
, "devDependencies": {
"expresso": "0.7.7"

View File

@@ -12,7 +12,8 @@
var io = require('socket.io')
, parser = io.parser
, http = require('http')
, https = require('https');
, https = require('https')
, WebSocket = require('../support/node-websocket-client/lib/websocket').WebSocket;
/**
* Exports.
@@ -178,6 +179,64 @@ client = function (port) {
*/
create = function (cl) {
console.error('');
console.log('');
return io.listen(cl.port);
};
/**
* WebSocket socket.io client.
*
* @api private
*/
function WSClient (port, sid) {
this.sid = sid;
this.port = port;
WebSocket.call(
this
, 'ws://localhost:' + port + '/socket.io/'
+ io.protocol + '/websocket/' + sid
);
};
/**
* Inherits from WebSocket.
*/
WSClient.prototype.__proto__ = WebSocket.prototype;
/**
* Overrides message event emission.
*
* @api private
*/
WSClient.prototype.emit = function (name) {
var args = arguments;
if (name == 'message' || name == 'data') {
args[1] = parser.decodePacket(args[1].toString());
}
return WebSocket.prototype.emit.apply(this, arguments);
};
/**
* Writes a packet
*/
WSClient.prototype.packet = function (pack) {
this.write(parser.encodePacket(pack));
return this;
};
/**
* Creates a websocket client.
*
* @api public
*/
websocket = function (cl, sid) {
return new WSClient(cl.port, sid);
};

View File

@@ -237,11 +237,37 @@ module.exports = {
});
},
'test that client minification works': function (done) {
'test that the cached client sends a 304 header': function (done) {
var port = ++ports
, io = sio.listen(port)
, cl = client(port);
io.configure(function () {
io.enable('browser client etag');
});
cl.get('/socket.io/socket.io.js', function (res, data) {
cl.get('/socket.io/socket.io.js', {headers:{'if-none-match':res.headers.etag}}, function (res, data) {
res.statusCode.should.eql(304);
cl.end();
io.server.close();
done();
});
});
},
'test that client minification works': function (done) {
// server 1
var port = ++ports
, io = sio.listen(port)
, cl = client(port);
// server 2
var port = ++ports
, io2 = sio.listen(port)
, cl2 = client(port);
io.configure(function () {
io.enable('browser client minification');
});
@@ -252,11 +278,6 @@ module.exports = {
cl.end();
io.server.close();
// start a new server with minification enabled and compare lengths
var port = ++ports
, io2 = sio.listen(port)
, cl2 = client(port);
cl2.get('/socket.io/socket.io.js', function (res, data) {
res.headers['content-type'].should.eql('application/javascript');
res.headers['content-length'].should.match(/([0-9]+)/);

143
test/namespace.test.js Normal file
View File

@@ -0,0 +1,143 @@
/*!
* socket.io-node
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
* MIT Licensed
*/
/**
* Test dependencies.
*/
var sio = require('socket.io')
, should = require('./common')
, ports = 15700;
/**
* Test.
*/
module.exports = {
'namespace pass no authentication': function (done) {
var cl = client(++ports)
, io = create(cl)
, ws;
io.of('/a')
.on('connection', function (socket) {
cl.end();
ws.finishClose();
io.server.close()
done();
});
cl.handshake(function (sid) {
ws = websocket(cl, sid);
ws.on('open', function () {
ws.packet({
type: 'connect'
, endpoint: '/a'
});
})
});
},
'namespace pass authentication': function (done) {
var cl = client(++ports)
, io = create(cl)
, ws;
io.of('/a')
.authorization(function (data, fn) {
fn(null, true);
})
.on('connection', function (socket) {
cl.end();
ws.finishClose();
io.server.close()
done();
});
cl.handshake(function (sid) {
ws = websocket(cl, sid);
ws.on('open', function () {
ws.packet({
type: 'connect'
, endpoint: '/a'
});
})
});
},
'namespace authentication handshake data': function (done) {
var cl = client(++ports)
, io = create(cl)
, ws;
io.of('/a')
.authorization(function (data, fn) {
data.foo = 'bar';
fn(null, true);
})
.on('connection', function (socket) {
socket.handshake.address.address.should.equal('127.0.0.1');
socket.handshake.address.port.should.equal(ports);
socket.handshake.headers.host.should.equal('localhost');
socket.handshake.headers.connection.should.equal('keep-alive');
socket.handshake.time.should.match(/GMT/);
socket.handshake.foo.should.equal('bar');
cl.end();
ws.finishClose();
io.server.close()
done();
});
cl.handshake(function (sid) {
ws = websocket(cl, sid);
ws.on('open', function () {
ws.packet({
type: 'connect'
, endpoint: '/a'
});
})
});
},
'namespace fail authentication': function (done) {
var cl = client(++ports)
, io = create(cl)
, calls = 0
, ws;
io.of('/a')
.authorization(function (data, fn) {
fn(null, false);
})
.on('connection', function (socket) {
throw new Error('Should not be called');
});
cl.handshake(function (sid) {
ws = websocket(cl, sid);
ws.on('open', function () {
ws.packet({
type: 'connect'
, endpoint: '/a'
});
});
ws.on('message', function (data) {
if (data.endpoint == '/a') {
data.type.should.eql('error');
data.reason.should.eql('unauthorized')
cl.end();
ws.finishClose();
io.server.close()
done();
}
})
});
}
};

190
test/stores.memory.test.js Normal file
View File

@@ -0,0 +1,190 @@
/**
* Test dependencies
*
* @api private
*/
var sio = require('socket.io')
, should = require('should')
, MemoryStore = sio.MemoryStore;
/**
* Test.
*/
module.exports = {
'test storing data for a client': function (done) {
var store = new MemoryStore
, client = store.client('test');
client.id.should.equal('test');
client.set('a', 'b', function (err) {
should.strictEqual(err, null);
client.get('a', function (err, val) {
should.strictEqual(err, null);
val.should.eql('b');
client.has('a', function (err, has) {
should.strictEqual(err, null);
has.should.be.true;
client.has('b', function (err, has) {
should.strictEqual(err, null);
has.should.be.false;
client.del('a', function (err) {
should.strictEqual(err, null);
client.has('a', function (err, has) {
should.strictEqual(err, null);
has.should.be.false;
client.set('b', 'c', function (err) {
should.strictEqual(err, null);
client.set('c', 'd', function (err) {
should.strictEqual(err, null);
client.get('b', function (err, val) {
should.strictEqual(err, null);
val.should.equal('c');
client.get('c', function (err, val) {
should.strictEqual(err, null);
val.should.equal('d');
store.destroy();
done();
});
});
});
});
});
});
});
});
});
});
},
'test cleaning up clients data': function (done) {
var rand1 = Math.abs(Math.random() * Date.now() | 0)
, rand2 = Math.abs(Math.random() * Date.now() | 0);
var store = new MemoryStore()
, client1 = store.client(rand1)
, client2 = store.client(rand2);
client1.set('a', 'b', function (err) {
should.strictEqual(err, null);
client2.set('c', 'd', function (err) {
should.strictEqual(err, null);
client1.has('a', function (err, val) {
should.strictEqual(err, null);
val.should.be.true;
client2.has('c', function (err, val) {
should.strictEqual(err, null);
val.should.be.true;
store.destroy();
var newstore = new MemoryStore()
, newclient1 = newstore.client(rand1)
, newclient2 = newstore.client(rand2);
newclient1.has('a', function (err, val) {
should.strictEqual(err, null);
val.should.be.false;
newclient2.has('c', function (err, val) {
should.strictEqual(err, null);
val.should.be.false;
newstore.destroy();
done();
});
});
});
});
});
});
},
'test cleaning up a particular client': function (done) {
var rand1 = Math.abs(Math.random() * Date.now() | 0)
, rand2 = Math.abs(Math.random() * Date.now() | 0);
var store = new MemoryStore()
, client1 = store.client(rand1)
, client2 = store.client(rand2);
client1.set('a', 'b', function (err) {
should.strictEqual(err, null);
client2.set('c', 'd', function (err) {
should.strictEqual(err, null);
client1.has('a', function (err, val) {
should.strictEqual(err, null);
val.should.be.true;
client2.has('c', function (err, val) {
should.strictEqual(err, null);
val.should.be.true;
store.clients.should.have.property(rand1);
store.clients.should.have.property(rand2);
store.destroyClient(rand1);
store.clients.should.not.have.property(rand1);
store.clients.should.have.property(rand2);
client1.has('a', function (err, val) {
should.strictEqual(err, null);
val.should.equal(false);
store.destroy();
done();
});
});
});
});
});
},
'test destroy expiration': function (done) {
var store = new MemoryStore()
, id = Math.abs(Math.random() * Date.now() | 0)
, client = store.client(id);
client.set('a', 'b', function (err) {
should.strictEqual(err, null);
store.destroyClient(id, 1);
setTimeout(function () {
client.get('a', function (err, val) {
should.strictEqual(err, null);
val.should.equal('b');
});
}, 500);
setTimeout(function () {
client.get('a', function (err, val) {
should.strictEqual(err, null);
should.strictEqual(val, null);
store.destroy();
done();
});
}, 1900);
});
}
};

240
test/stores.redis.test.js Normal file
View File

@@ -0,0 +1,240 @@
/**
* Test dependencies
*
* @api private
*/
var sio = require('socket.io')
, redis = require('redis')
, should = require('should')
, RedisStore = sio.RedisStore;
/**
* Test.
*/
module.exports = {
'test publishing doesnt get caught by the own store subscriber': function (done) {
var a = new RedisStore
, b = new RedisStore;
a.subscribe('woot', function (arg) {
arg.should.equal('bb');
a.destroy();
b.destroy();
done();
}, function () {
a.publish('woot', 'aa');
b.publish('woot', 'bb');
});
},
'test publishing to multiple subscribers': function (done) {
var a = new RedisStore
, b = new RedisStore
, c = new RedisStore
, subscriptions = 3
, messages = 2;
a.subscribe('tobi', function () {
throw new Error('Shouldnt publish to itself');
}, publish);
function subscription (arg1, arg2, arg3) {
arg1.should.equal(1);
arg2.should.equal(2);
arg3.should.equal(3);
--messages || finish();
}
b.subscribe('tobi', subscription, publish);
c.subscribe('tobi', subscription, publish);
function publish () {
--subscriptions || a.publish('tobi', 1, 2, 3);
}
function finish () {
a.destroy();
b.destroy();
c.destroy();
done();
}
},
'test storing data for a client': function (done) {
var store = new RedisStore
, rand = 'test-' + Date.now()
, client = store.client(rand);
client.id.should.equal(rand);
client.set('a', 'b', function (err) {
should.strictEqual(err, null);
client.get('a', function (err, val) {
should.strictEqual(err, null);
val.should.equal('b');
client.has('a', function (err, has) {
should.strictEqual(err, null);
has.should.be.true;
client.has('b', function (err, has) {
should.strictEqual(err, null);
has.should.be.false;
client.del('a', function (err) {
should.strictEqual(err, null);
client.has('a', function (err, has) {
should.strictEqual(err, null);
has.should.be.false;
client.set('b', 'c', function (err) {
should.strictEqual(err, null);
client.set('c', 'd', function (err) {
should.strictEqual(err, null);
client.get('b', function (err, val) {
should.strictEqual(err, null);
val.should.equal('c');
client.get('c', function (err, val) {
should.strictEqual(err, null);
val.should.equal('d');
store.destroy();
done();
});
});
});
});
});
});
});
});
});
});
},
'test cleaning up clients data': function (done) {
var rand1 = Math.abs(Math.random() * Date.now() | 0)
, rand2 = Math.abs(Math.random() * Date.now() | 0);
var store = new RedisStore()
, client1 = store.client(rand1)
, client2 = store.client(rand2);
client1.set('a', 'b', function (err) {
should.strictEqual(err, null);
client2.set('c', 'd', function (err) {
should.strictEqual(err, null);
client1.has('a', function (err, val) {
should.strictEqual(err, null);
val.should.be.true;
client2.has('c', function (err, val) {
should.strictEqual(err, null);
val.should.be.true;
store.destroy();
var newstore = new RedisStore()
, newclient1 = newstore.client(rand1)
, newclient2 = newstore.client(rand2);
newclient1.has('a', function (err, val) {
should.strictEqual(err, null);
val.should.be.false;
newclient2.has('c', function (err, val) {
should.strictEqual(err, null);
val.should.be.false;
newstore.destroy();
done();
});
});
});
});
});
});
},
'test cleaning up a particular client': function (done) {
var rand1 = Math.abs(Math.random() * Date.now() | 0)
, rand2 = Math.abs(Math.random() * Date.now() | 0);
var store = new RedisStore()
, client1 = store.client(rand1)
, client2 = store.client(rand2);
client1.set('a', 'b', function (err) {
should.strictEqual(err, null);
client2.set('c', 'd', function (err) {
should.strictEqual(err, null);
client1.has('a', function (err, val) {
should.strictEqual(err, null);
val.should.be.true;
client2.has('c', function (err, val) {
should.strictEqual(err, null);
val.should.be.true;
store.clients.should.have.property(rand1);
store.clients.should.have.property(rand2);
store.destroyClient(rand1);
store.clients.should.not.have.property(rand1);
store.clients.should.have.property(rand2);
client1.has('a', function (err, val) {
should.strictEqual(err, null);
val.should.equal(false);
store.destroy();
done();
});
});
});
});
});
},
'test destroy expiration': function (done) {
var store = new RedisStore()
, id = Math.abs(Math.random() * Date.now() | 0)
, client = store.client(id);
client.set('a', 'b', function (err) {
should.strictEqual(err, null);
store.destroyClient(id, 1);
setTimeout(function () {
client.get('a', function (err, val) {
should.strictEqual(err, null);
val.should.equal('b');
});
}, 500);
setTimeout(function () {
client.get('a', function (err, val) {
should.strictEqual(err, null);
should.strictEqual(val, null);
store.destroy();
done();
});
}, 1900);
});
}
};

View File

@@ -177,10 +177,10 @@ module.exports = {
--total || finish();
});
// we rely on a small poll duration to close this request quickly
cl.get('/socket.io/{protocol}/jsonp-polling/' + sid, function (res, data) {
cl.get('/socket.io/{protocol}/jsonp-polling/' + sid, function (res, msgs) {
res.statusCode.should.eql(200);
data.should.eql('');
msgs.should.have.length(1);
msgs[0].should.eql({ type: 'noop', endpoint: '' });
--total || finish();
});
});
@@ -243,7 +243,6 @@ module.exports = {
msgs.should.have.length(1);
msgs[0].type.should.eql('connect');
// here we close the request instead of relying on a small poll timeout
setTimeout(function () {
cl.end();
}, 10);
@@ -459,8 +458,9 @@ module.exports = {
});
cl.handshake(function (sid) {
cl.get('/socket.io/{protocol}/jsonp-polling/' + sid, function (res, data) {
data.should.eql('');
cl.get('/socket.io/{protocol}/jsonp-polling/' + sid, function (res, msgs) {
msgs.should.have.length(1);
msgs[0].should.eql({ type: 'noop', endpoint: '' });
tobi();
@@ -649,9 +649,10 @@ module.exports = {
});
cl.handshake(function (sid) {
cl.get('/socket.io/{protocol}/jsonp-polling/' + sid, function (res, data) {
cl.get('/socket.io/{protocol}/jsonp-polling/' + sid, function (res, msgs) {
res.statusCode.should.eql(200);
data.should.eql('');
msgs.should.have.length(1);
msgs[0].should.eql({ type: 'noop', endpoint: '' });
cl.post(
'/socket.io/{protocol}/jsonp-polling/' + sid

View File

@@ -11,75 +11,9 @@
var sio = require('socket.io')
, should = require('./common')
, HTTPClient = should.HTTPClient
, WebSocket = require('../support/node-websocket-client/lib/websocket').WebSocket
, parser = sio.parser
, ports = 15400;
/**
* Exports WSClient.
*/
module.exports = exports = WSClient;
/**
* WebSocket socket.io client.
*
* @api private
*/
function WSClient (port, sid) {
this.sid = sid;
this.port = port;
WebSocket.call(
this
, 'ws://localhost:' + port + '/socket.io/'
+ sio.protocol + '/websocket/' + sid
);
};
/**
* Inherits from WebSocket.
*/
WSClient.prototype.__proto__ = WebSocket.prototype;
/**
* Overrides message event emission.
*
* @api private
*/
WSClient.prototype.emit = function (name) {
var args = arguments;
if (name == 'message' || name == 'data') {
args[1] = parser.decodePacket(args[1].toString());
}
return WebSocket.prototype.emit.apply(this, arguments);
};
/**
* Writes a packet
*/
WSClient.prototype.packet = function (pack) {
this.write(parser.encodePacket(pack));
return this;
};
/**
* Creates a websocket client.
*
* @api public
*/
function websocket (cl, sid) {
return new WSClient(cl.port, sid);
};
/**
* Tests.
*/
@@ -89,7 +23,8 @@ module.exports = {
'test that not responding to a heartbeat drops client': function (done) {
var cl = client(++ports)
, io = create(cl)
, messages = 0;
, messages = 0
, ws;
io.configure(function () {
io.set('heartbeat interval', .05);
@@ -103,13 +38,14 @@ module.exports = {
reason.should.eql('heartbeat timeout');
cl.end();
ws.finishClose();
io.server.close();
done();
});
});
cl.handshake(function (sid) {
var ws = websocket(cl, sid);
ws = websocket(cl, sid);
ws.on('message', function (packet) {
if (++messages == 1) {
packet.type.should.eql('connect');
@@ -125,7 +61,8 @@ module.exports = {
var cl = client(++ports)
, io = create(cl)
, messages = 0
, heartbeats = 0;
, heartbeats = 0
, ws;
io.configure(function () {
io.set('heartbeat interval', .05);
@@ -139,13 +76,14 @@ module.exports = {
reason.should.eql('heartbeat timeout');
cl.end();
ws.finishClose();
io.server.close();
done();
});
});
cl.handshake(function (sid) {
var ws = websocket(cl, sid);
ws = websocket(cl, sid);
ws.on('message', function (packet) {
if (++messages == 1) {
packet.type.should.eql('connect');
@@ -644,16 +582,15 @@ module.exports = {
connections++;
if (connections != 3) {
socket.join('woot', function () {
joins++;
socket.join('woot');
joins++;
if (joins == 2) {
setTimeout(function () {
connections.should.eql(3);
io.sockets.in('woot').send('hahaha');
}, 20);
}
});
if (joins == 2) {
setTimeout(function () {
connections.should.eql(3);
io.sockets.in('woot').send('hahaha');
}, 20);
}
}
socket.on('disconnect', function () {
@@ -756,16 +693,15 @@ module.exports = {
connections++;
if (connections != 3) {
socket.join('woot', function () {
joins++;
socket.join('woot');
joins++;
if (joins == 2) {
setTimeout(function () {
connections.should.eql(3);
io.sockets.in('woot').json.send(123);
}, 20);
}
});
if (joins == 2) {
setTimeout(function () {
connections.should.eql(3);
io.sockets.in('woot').json.send(123);
}, 20);
}
}
socket.on('disconnect', function () {
@@ -868,16 +804,15 @@ module.exports = {
connections++;
if (connections != 3) {
socket.join('woot', function () {
joins++;
socket.join('woot');
joins++;
if (joins == 2) {
setTimeout(function () {
connections.should.eql(3);
io.sockets.in('woot').emit('locki');
}, 20);
}
});
if (joins == 2) {
setTimeout(function () {
connections.should.eql(3);
io.sockets.in('woot').emit('locki');
}, 20);
}
}
socket.on('disconnect', function () {
@@ -1268,8 +1203,9 @@ module.exports = {
io.sockets.on('connection', function (socket) {
connections++;
if (connections == 1)
if (connections == 1) {
socket.join('losers');
}
socket.on('trigger broadcast', function () {
socket.broadcast.to('losers').send('boom');
@@ -1367,8 +1303,9 @@ module.exports = {
io.sockets.on('connection', function (socket) {
connections++;
if (connections == 1)
if (connections == 1) {
socket.join('losers');
}
socket.on('trigger broadcast', function () {
socket.broadcast.json.to('losers').send({ hello: 'world' });
@@ -1548,6 +1485,162 @@ module.exports = {
});
});
});
}
},
'test accessing handshake data from sockets': function (done) {
var cl = client(++ports)
, io = create(cl)
, ws;
io.sockets.on('connection', function (socket) {
socket.handshake.address.address.should.equal('127.0.0.1');
socket.handshake.address.port.should.equal(ports);
socket.handshake.headers.host.should.equal('localhost');
socket.handshake.headers.connection.should.equal('keep-alive');
socket.handshake.time.should.match(/GMT/);
socket.on('disconnect', function () {
setTimeout(function () {
ws.finishClose();
cl.end();
io.server.close();
done();
}, 10);
});
socket.disconnect();
});
cl.handshake(function (sid) {
ws = websocket(cl, sid);
ws.on('message', function (msg) {
if (!ws.connected) {
msg.type.should.eql('connect');
ws.connected = true;
}
});
});
},
'test accessing the array of clients': function (done) {
var port = ++ports
, cl1 = client(port)
, cl2 = client(port)
, io = create(cl1)
, total = 2
, ws1, ws2;
io.sockets.on('connection', function (socket) {
socket.on('join ferrets', function () {
socket.join('ferrets');
socket.send('done');
});
});
function check() {
io.sockets.clients('ferrets').should.have.length(1);
io.sockets.clients('ferrets')[0].should.be.an.instanceof(sio.Socket);
io.sockets.clients('ferrets')[0].id.should.equal(ws1.sid);
io.sockets.clients().should.have.length(2);
io.sockets.clients()[0].should.be.an.instanceof(sio.Socket);
io.sockets.clients()[0].id.should.equal(ws1.sid);
io.sockets.clients()[1].should.be.an.instanceof(sio.Socket);
io.sockets.clients()[1].id.should.equal(ws2.sid);
ws1.finishClose();
ws2.finishClose();
cl1.end();
cl2.end();
io.server.close();
done();
};
cl1.handshake(function (sid) {
ws1 = websocket(cl1, sid);
ws1.sid = sid;
ws1.on('message', function (msg) {
if (!ws1.connected) {
msg.type.should.eql('connect');
ws1.connected = true;
ws1.packet({
type: 'event'
, name: 'join ferrets'
, endpoint: ''
});
} else {
cl2.handshake(function (sid) {
ws2 = websocket(cl2, sid);
ws2.sid = sid;
ws2.on('message', function (msg) {
if (!ws2.connected) {
msg.type.should.eql('connect');
ws2.connected = true;
check();
}
});
});
}
});
});
},
'test for intentional and unintentional disconnects': function (done) {
var cl = client(++ports)
, io = create(cl)
, calls = 0
, ws;
function close () {
cl.end();
io.server.close();
ws.finishClose();
done();
}
io.configure(function () {
io.set('heartbeat interval', .05);
io.set('heartbeat timeout', .05);
io.set('close timeout', 0);
});
io.of('/foo').on('connection', function (socket) {
socket.on('disconnect', function (reason) {
reason.should.equal('packet');
if (++calls == 2) close();
});
});
io.of('/bar').on('connection', function (socket) {
socket.on('disconnect', function (reason) {
reason.should.equal('socket end');
if (++calls == 2) close();
});
});
cl.handshake(function (sid) {
var messages = 0;
ws = websocket(cl, sid);
ws.on('open', function () {
ws.packet({
type: 'connect'
, endpoint: '/foo'
});
ws.packet({
type: 'connect'
, endpoint: '/bar'
});
});
ws.on('message', function (packet) {
if (packet.type == 'connect') {
if (++messages === 3) {
ws.packet({ type: 'disconnect', endpoint:'/foo' });
ws.finishClose();
}
}
});
});
}
};

View File

@@ -113,10 +113,10 @@ module.exports = {
--total || finish();
});
// we rely on a small poll duration to close this request quickly
cl.get('/socket.io/{protocol}/xhr-polling/' + sid, function (res, data) {
cl.get('/socket.io/{protocol}/xhr-polling/' + sid, function (res, msgs) {
res.statusCode.should.eql(200);
data.should.eql('');
msgs.should.have.length(1);
msgs[0].should.eql({ type: 'noop', endpoint: '' });
--total || finish();
});
});
@@ -179,7 +179,6 @@ module.exports = {
msgs.should.have.length(1);
msgs[0].type.should.eql('connect');
// here we close the request instead of relying on a small poll timeout
setTimeout(function () {
cl.end();
}, 10);
@@ -395,8 +394,10 @@ module.exports = {
});
cl.handshake(function (sid) {
cl.get('/socket.io/{protocol}/xhr-polling/' + sid, function (res, data) {
data.should.eql('');
cl.get('/socket.io/{protocol}/xhr-polling/' + sid, function (res, msgs) {
res.statusCode.should.equal(200);
msgs.should.have.length(1);
msgs[0].should.eql({ type: 'noop', endpoint: '' });
tobi();
@@ -585,9 +586,10 @@ module.exports = {
});
cl.handshake(function (sid) {
cl.get('/socket.io/{protocol}/xhr-polling/' + sid, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('');
cl.get('/socket.io/{protocol}/xhr-polling/' + sid, function (res, msgs) {
res.statusCode.should.equal(200);
msgs.should.have.length(1);
msgs[0].should.eql({ type: 'noop', endpoint: '' });
cl.post(
'/socket.io/{protocol}/xhr-polling/' + sid
@@ -724,9 +726,10 @@ module.exports = {
});
cl.handshake(function (sid) {
cl.get('/socket.io/{protocol}/xhr-polling/' + sid, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('');
cl.get('/socket.io/{protocol}/xhr-polling/' + sid, function (res, msgs) {
res.statusCode.should.equal(200);
msgs.should.have.length(1);
msgs[0].should.eql({ type: 'noop', endpoint: '' });
s.json.send(['a', 'b', 'c']);
s.json.send({
@@ -785,9 +788,10 @@ module.exports = {
});
cl.handshake(function (sid) {
cl.get('/socket.io/{protocol}/xhr-polling/' + sid, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('');
cl.get('/socket.io/{protocol}/xhr-polling/' + sid, function (res, msgs) {
res.statusCode.should.equal(200);
msgs.should.have.length(1);
msgs[0].should.eql({ type: 'noop', endpoint: '' });
cl.post(
'/socket.io/{protocol}/xhr-polling/' + sid
@@ -837,9 +841,10 @@ module.exports = {
});
cl.handshake(function (sid) {
cl.get('/socket.io/{protocol}/xhr-polling/' + sid, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('');
cl.get('/socket.io/{protocol}/xhr-polling/' + sid, function (res, msgs) {
res.statusCode.should.equal(200);
msgs.should.have.length(1);
msgs[0].should.eql({ type: 'noop', endpoint: '' });
s.emit('tobi is playing');
@@ -879,9 +884,10 @@ module.exports = {
});
cl.handshake(function (sid) {
cl.get('/socket.io/{protocol}/xhr-polling/' + sid, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('');
cl.get('/socket.io/{protocol}/xhr-polling/' + sid, function (res, msgs) {
res.statusCode.should.equal(200);
msgs.should.have.length(1);
msgs[0].should.eql({ type: 'noop', endpoint: '' });
s.emit('edwald', { woot: 'woot' }, [1, 2, 3]);
@@ -924,9 +930,10 @@ module.exports = {
});
cl.handshake(function (sid) {
cl.get('/socket.io/{protocol}/xhr-polling/' + sid, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('');
cl.get('/socket.io/{protocol}/xhr-polling/' + sid, function (res, msgs) {
res.statusCode.should.equal(200);
msgs.should.have.length(1);
msgs[0].should.eql({ type: 'noop', endpoint: '' });
cl.post(
'/socket.io/{protocol}/xhr-polling/' + sid
@@ -971,9 +978,10 @@ module.exports = {
});
cl.handshake(function (sid) {
cl.get('/socket.io/{protocol}/xhr-polling/' + sid, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('');
cl.get('/socket.io/{protocol}/xhr-polling/' + sid, function (res, msgs) {
res.statusCode.should.equal(200);
msgs.should.have.length(1);
msgs[0].should.eql({ type: 'noop', endpoint: '' });
cl.post(
'/socket.io/{protocol}/xhr-polling/' + sid
@@ -1012,15 +1020,17 @@ module.exports = {
});
cl.handshake(function (sid) {
cl.get('/socket.io/{protocol}/xhr-polling/' + sid, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('');
cl.get('/socket.io/{protocol}/xhr-polling/' + sid, function (res, msgs) {
res.statusCode.should.equal(200);
msgs.should.have.length(1);
msgs[0].should.eql({ type: 'noop', endpoint: '' });
s.volatile.send('woooot');
cl.get('/socket.io/{protocol}/xhr-polling/' + sid, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('');
cl.get('/socket.io/{protocol}/xhr-polling/' + sid, function (res, msgs) {
res.statusCode.should.equal(200);
msgs.should.have.length(1);
msgs[0].should.eql({ type: 'noop', endpoint: '' });
});
});
});
@@ -1047,15 +1057,17 @@ module.exports = {
});
cl.handshake(function (sid) {
cl.get('/socket.io/{protocol}/xhr-polling/' + sid, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('');
cl.get('/socket.io/{protocol}/xhr-polling/' + sid, function (res, msgs) {
res.statusCode.should.equal(200);
msgs.should.have.length(1);
msgs[0].should.eql({ type: 'noop', endpoint: '' });
s.volatile.json.send('woooot');
cl.get('/socket.io/{protocol}/xhr-polling/' + sid, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('');
cl.get('/socket.io/{protocol}/xhr-polling/' + sid, function (res, msgs) {
res.statusCode.should.equal(200);
msgs.should.have.length(1);
msgs[0].should.eql({ type: 'noop', endpoint: '' });
});
});
});
@@ -1082,15 +1094,17 @@ module.exports = {
});
cl.handshake(function (sid) {
cl.get('/socket.io/{protocol}/xhr-polling/' + sid, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('');
cl.get('/socket.io/{protocol}/xhr-polling/' + sid, function (res, msgs) {
res.statusCode.should.equal(200);
msgs.should.have.length(1);
msgs[0].should.eql({ type: 'noop', endpoint: '' });
s.volatile.emit('woooot');
cl.get('/socket.io/{protocol}/xhr-polling/' + sid, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('');
cl.get('/socket.io/{protocol}/xhr-polling/' + sid, function (res, msgs) {
res.statusCode.should.equal(200);
msgs.should.have.length(1);
msgs[0].should.eql({ type: 'noop', endpoint: '' });
});
});
});
@@ -1236,9 +1250,10 @@ module.exports = {
});
cl.handshake(function (sid) {
cl.get('/socket.io/{protocol}/xhr-polling/' + sid, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('');
cl.get('/socket.io/{protocol}/xhr-polling/' + sid, function (res, msgs) {
res.statusCode.should.equal(200);
msgs.should.have.length(1);
msgs[0].should.eql({ type: 'noop', endpoint: '' });
cl.post(
'/socket.io/{protocol}/xhr-polling/' + sid
@@ -2509,9 +2524,10 @@ module.exports = {
});
cl.handshake(function (sid) {
cl.get('/socket.io/{protocol}/xhr-polling/' + sid, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('');
cl.get('/socket.io/{protocol}/xhr-polling/' + sid, function (res, msgs) {
res.statusCode.should.equal(200);
msgs.should.have.length(1);
msgs[0].should.eql({ type: 'noop', endpoint: '' });
cl.post(
'/socket.io/{protocol}/xhr-polling/' + sid