mirror of
https://github.com/socketio/socket.io.git
synced 2026-04-30 03:00:39 -04:00
Initial import
This commit is contained in:
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
node_modules
|
||||
.DS_Store
|
||||
1
.npmignore
Normal file
1
.npmignore
Normal file
@@ -0,0 +1 @@
|
||||
support/
|
||||
153
README.md
Normal file
153
README.md
Normal file
@@ -0,0 +1,153 @@
|
||||
|
||||
# Engine.IO clien
|
||||
|
||||
This is the client for [Engine](http://github.com/learnboost/engine.io), the
|
||||
implementation of transport-based cross-browser/cross-device bi-directional
|
||||
communication layer for [Socket.IO](http://github.com/learnboost/socket.io).
|
||||
|
||||
## Hello World
|
||||
|
||||
```html
|
||||
<script src="/path/to/engine.js"></script>
|
||||
<script>
|
||||
var socket = new io.Engine({ host: 'localhost', port: 80 });
|
||||
socket.onopen = function () {
|
||||
socket.onmessage = function (data) { });
|
||||
socket.onclose = function () { });
|
||||
};
|
||||
</script>
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
- Lightweight
|
||||
- Lazyloads Flash transport
|
||||
- Isomorphic with WebSocket API
|
||||
- Written for node, runs on browser thanks to
|
||||
[browserbuild](http://github.com/learnboost/browserbuild)
|
||||
- Maximizes code readability / maintenance.
|
||||
- Simplifies testing.
|
||||
- Transports are independent of `Engine`
|
||||
- Easy to debug
|
||||
- Easy to unit test
|
||||
- Runs inside HTML5 WebWorker
|
||||
|
||||
## API
|
||||
|
||||
<hr><br>
|
||||
|
||||
### Top-level
|
||||
|
||||
These are exposed in the `io` global namespace (in the browser), or by
|
||||
`require('engine-client')` (in Node.JS).
|
||||
|
||||
#### Properties
|
||||
|
||||
- `version` _(String)_: protocol revision number
|
||||
- `Engine` _(Function)_: client constructor
|
||||
|
||||
### Engine
|
||||
|
||||
The client class. _Inherits from EventEmitter_.
|
||||
|
||||
#### Properties
|
||||
|
||||
- `onopen` (_Function_)
|
||||
- `open` event handler
|
||||
- `onmessage` (_Function_)
|
||||
- `message` event handler
|
||||
- `onclose` (_Function_)
|
||||
- `message` event handler
|
||||
|
||||
#### Events
|
||||
|
||||
- `open`
|
||||
- Fired upon successful connection.
|
||||
- `message`
|
||||
- Fired when data is received from the server.
|
||||
- **Arguments**
|
||||
- `String`: utf-8 encoded data
|
||||
- `close`
|
||||
- Fired upon disconnection.
|
||||
- `error`
|
||||
- Fired when an error occurs.
|
||||
|
||||
#### Methods
|
||||
|
||||
- **constructor**
|
||||
- Initializes the client
|
||||
- **Parameters**
|
||||
- `Object`: optional, options object
|
||||
- **Options**
|
||||
- `host` (`String`): host name (`localhost`)
|
||||
- `port` (`Number`): port name (`80`)
|
||||
- `path` (`String`): path name
|
||||
- `query` (`String`): optional query string addition (eg:
|
||||
`a=b&c=hello+world`)
|
||||
- `secure` (`Boolean): whether the connection is secure
|
||||
- `upgrade` (`Boolean`): defaults to true, whether the client should try
|
||||
to upgrade the transport from long-polling to something better.
|
||||
- `forceJSONP` (`Boolean`): forces JSONP for polling transport.
|
||||
- `transports` (`Array`): a list of transports to try (in order).
|
||||
Defaults to `['polling', 'websocket', 'flashsocket']`. `Engine`
|
||||
always attempts to connect directly with the first one, provided the
|
||||
feature detection test for it passes.
|
||||
- `send`
|
||||
- Sends a message to the server
|
||||
- **Parameters**
|
||||
- `String`: data to send
|
||||
- `close`
|
||||
- Disconnects the client.
|
||||
|
||||
## Tests
|
||||
|
||||
`engine.io-client` is used to test
|
||||
[engine](http://github.com/learnboost/engine.io)
|
||||
|
||||
## Support
|
||||
|
||||
The support channels for `engine.io-client` are the same as `socket.io`:
|
||||
- irc.freenode.net **#socket.io**
|
||||
- [Google Groups](http://groups.google.com/group/socket_io)
|
||||
- [Website](http://socket.io)
|
||||
|
||||
## Development
|
||||
|
||||
To contribute patches, run tests or benchmarks, make sure to clone the
|
||||
repository:
|
||||
|
||||
```
|
||||
git clone git://github.com/LearnBoost/engine.io-client.git
|
||||
```
|
||||
|
||||
Then:
|
||||
|
||||
```
|
||||
cd engine.io-client
|
||||
npm install
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
(The MIT License)
|
||||
|
||||
Copyright (c) 2011 Guillermo Rauch <guillermo@learnboost.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
'Software'), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
24
lib/engine-client.js
Normal file
24
lib/engine-client.js
Normal file
@@ -0,0 +1,24 @@
|
||||
|
||||
/**
|
||||
* Client version.
|
||||
*
|
||||
* @api public.
|
||||
*/
|
||||
|
||||
exports.engineVersion = '0.1.0';
|
||||
|
||||
/**
|
||||
* Protocol version.
|
||||
*
|
||||
* @api public.
|
||||
*/
|
||||
|
||||
exports.engineProtocol = 1;
|
||||
|
||||
/**
|
||||
* Engine constructor.
|
||||
*
|
||||
* @api public.
|
||||
*/
|
||||
|
||||
exports.Engine = require('./engine');
|
||||
312
lib/engine.js
Normal file
312
lib/engine.js
Normal file
@@ -0,0 +1,312 @@
|
||||
|
||||
/**
|
||||
* Module exports.
|
||||
*/
|
||||
|
||||
module.exports = exports = Engine;
|
||||
|
||||
/**
|
||||
* Export Transport.
|
||||
*/
|
||||
|
||||
exports.Transport = require('./transport');
|
||||
|
||||
/**
|
||||
* Export transports
|
||||
*/
|
||||
|
||||
var transports = exports.transports = require('./transports');
|
||||
|
||||
/**
|
||||
* Export utils.
|
||||
*/
|
||||
|
||||
exports.util = require('./util')
|
||||
|
||||
/**
|
||||
* Engine constructor.
|
||||
*
|
||||
* @param {Object} options
|
||||
* @api public
|
||||
*/
|
||||
|
||||
function Engine (opts) {
|
||||
opts = opts || {};
|
||||
|
||||
this.host = opts.host || opts.hostname || 'localhost';
|
||||
this.port = opts.port || 80;
|
||||
this.upgrade = false !== opts.upgrade;
|
||||
this.path = opts.path || '/engine.io'
|
||||
this.forceJSONP = !!opts.forceJSONP;
|
||||
this.transports = opts.transports || ['polling', 'websocket', 'flashsocket'];
|
||||
this.readyState = '';
|
||||
|
||||
// handle inline function events (eg: `onopen`)
|
||||
var evs = ['open', 'message', 'close']
|
||||
, self = this
|
||||
|
||||
for (var i = 0, l = evs.length; i < l; i++) {
|
||||
(function (ev) {
|
||||
self.on(ev, function () {
|
||||
if (self['on' + ev]) {
|
||||
self['on' + ev].apply(this, arguments);
|
||||
}
|
||||
});
|
||||
})(evs[i]);
|
||||
}
|
||||
|
||||
this.init();
|
||||
};
|
||||
|
||||
/**
|
||||
* Initializes transport to use and starts probe.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Engine.prototype.init = function () {
|
||||
// use the first transport always for the first try
|
||||
this.setTransport(this.transports[0]);
|
||||
|
||||
var self = this;
|
||||
|
||||
// whether we should perform a probe
|
||||
if (this.upgrade && this.transports.length > 1) {
|
||||
var probeTransports = this.transports.slice(1)
|
||||
, probes = []
|
||||
|
||||
function abort () {
|
||||
for (var i = 0, l = probes.length; i++) {
|
||||
probes[i].close();
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0, l = probeTransports.length; i < l; i++) {
|
||||
(function (i) {
|
||||
var id = probeTransports[i]
|
||||
|
||||
probes.push(this.probe(id, function (err) {
|
||||
probes.splice(i, 1);
|
||||
|
||||
if (err) {
|
||||
self.emit('error', err);
|
||||
self.log.debug('probing transport "%s" failed', id);
|
||||
} else {
|
||||
self.setTransport(probeTransports[i]);
|
||||
abort();
|
||||
}
|
||||
}));
|
||||
})();
|
||||
}
|
||||
}
|
||||
|
||||
// flush write buffers
|
||||
function flush () {
|
||||
if (self.writeBuffer.length) {
|
||||
// make sure to transfer the buffer to the transport
|
||||
self.transport.buffer = true;
|
||||
for (var i = 0, l = self.writeBuffer.length; i < l; i++) {
|
||||
self.transport.send(self.writeBuffer[i]);
|
||||
}
|
||||
self.transport.flush();
|
||||
}
|
||||
}
|
||||
|
||||
this.on('open', flush);
|
||||
this.on('upgrade', flush);
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets the current transport. Disables the existing one (if any).
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Engine.prototype.setTransport = function (id) {
|
||||
var self = this;
|
||||
|
||||
function set () {
|
||||
// make sure to set upgrading state
|
||||
self.upgrading = false;
|
||||
|
||||
// set up transport
|
||||
self.transportId = id;
|
||||
self.transport = new transports[id]({
|
||||
host: self.host
|
||||
, port: self.port
|
||||
, secure: self.secure
|
||||
, path: self.path
|
||||
, query: 'transport=' + id + (self.query ? '&' + self.query : '')
|
||||
, forceJSONP: self.forceJSONP
|
||||
});
|
||||
|
||||
// emit upgrade event
|
||||
self.emit('upgrade', id);
|
||||
|
||||
// set up transport listeners
|
||||
self.transport.on('data', function (data) {
|
||||
self.onMessage(parser.decodePacket(data));
|
||||
});
|
||||
self.transport.on('close', function () {
|
||||
self.onClose();
|
||||
});
|
||||
self.transport.open();
|
||||
};
|
||||
|
||||
if (this.transport) {
|
||||
// upgrade transports
|
||||
if (!this.transport.pause) {
|
||||
this.emit('error', new Error('Transport "' + this.transportId
|
||||
+ '" can\'t be upgraded.'));
|
||||
return;
|
||||
}
|
||||
|
||||
this.upgrading = true;
|
||||
this.transport.pause(set);
|
||||
} else {
|
||||
// first open
|
||||
this.readyState = 'opening';
|
||||
set();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Probes a tranposrt
|
||||
*
|
||||
* @param {String} transport id
|
||||
* @param {Function} callback
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Engine.prototype.probe = function (id, fn) {
|
||||
this.log.debug('probing transport "%s"', id);
|
||||
|
||||
var transport = new transports[id]({
|
||||
host: this.host
|
||||
, port: this.port
|
||||
, secure: this.secure
|
||||
, path: this.path
|
||||
, query: 'transport=' id
|
||||
});
|
||||
|
||||
transport.once('open', function () {
|
||||
transport.write(parser.encodePacket('probe'));
|
||||
transport.once('data', function (data) {
|
||||
if ('probe' == parser.decodePacket(data).type) {
|
||||
fn();
|
||||
} else {
|
||||
var err = new Error('probe fail');
|
||||
err.transport = id;
|
||||
fn(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return transport;
|
||||
};
|
||||
|
||||
/**
|
||||
* Opens the connection
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Engine.prototype.open = function () {
|
||||
if ('' == this.readyState || 'closed' == this.readyState) {
|
||||
this.transport.open()
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Called when connection is deemed open.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Engine.prototype.onOpen = function () {
|
||||
this.readyState = 'open';
|
||||
this.emit('open');
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles a message.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Engine.prototype.onMessage = function (msg) {
|
||||
switch (msg.type) {
|
||||
case 'open':
|
||||
this.onOpen();
|
||||
break;
|
||||
|
||||
case 'heartbeat':
|
||||
this.writePacket('heartbeat');
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Sends a message.
|
||||
*
|
||||
* @param {String} message.
|
||||
* @return {Engine} for chaining.
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Engine.prototype.send = function (msg) {
|
||||
this.writePacket('message', msg);
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Encodes a packet and writes it out.
|
||||
*
|
||||
* @param {String} packet type.
|
||||
* @param {String} data.
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Engine.prototype.writePacket = function (type, data) {
|
||||
this.write(parser.encodePacket(type, data));
|
||||
};
|
||||
|
||||
/**
|
||||
* Writes data.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Engine.prototype.write = function (data) {
|
||||
if ('open' != this.readyState || this.upgrading) {
|
||||
this.writeBuffer.push(data);
|
||||
} else {
|
||||
this.transport.send(data);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Closes the connection.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Engine.prototype.close = function () {
|
||||
if ('opening' == this.readyState || 'open' == this.readyState) {
|
||||
this.transport.close();
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Called upon transport close.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Engine.prototype.onClose = function () {
|
||||
this.readyState = 'closed';
|
||||
this.emit('close');
|
||||
};
|
||||
170
lib/event-emitter.js
Normal file
170
lib/event-emitter.js
Normal file
@@ -0,0 +1,170 @@
|
||||
|
||||
/**
|
||||
* Module exports.
|
||||
*/
|
||||
|
||||
module.exports = EventEmitter;
|
||||
|
||||
/**
|
||||
* Event emitter constructor.
|
||||
*
|
||||
* @api public.
|
||||
*/
|
||||
|
||||
function EventEmitter () {};
|
||||
|
||||
/**
|
||||
* Adds a listener
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
EventEmitter.prototype.on = function (name, fn) {
|
||||
if (!this.$events) {
|
||||
this.$events = {};
|
||||
}
|
||||
|
||||
if (!this.$events[name]) {
|
||||
this.$events[name] = fn;
|
||||
} else if (io.util.isArray(this.$events[name])) {
|
||||
this.$events[name].push(fn);
|
||||
} else {
|
||||
this.$events[name] = [this.$events[name], fn];
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
EventEmitter.prototype.addListener = EventEmitter.prototype.on;
|
||||
|
||||
/**
|
||||
* Adds a volatile listener.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
EventEmitter.prototype.once = function (name, fn) {
|
||||
var self = this;
|
||||
|
||||
function on () {
|
||||
self.removeListener(name, on);
|
||||
fn.apply(this, arguments);
|
||||
};
|
||||
|
||||
on.listener = fn;
|
||||
this.on(name, on);
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Removes a listener.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
EventEmitter.prototype.removeListener = function (name, fn) {
|
||||
if (this.$events && this.$events[name]) {
|
||||
var list = this.$events[name];
|
||||
|
||||
if (io.util.isArray(list)) {
|
||||
var pos = -1;
|
||||
|
||||
for (var i = 0, l = list.length; i < l; i++) {
|
||||
if (list[i] === fn || (list[i].listener && list[i].listener === fn)) {
|
||||
pos = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (pos < 0) {
|
||||
return this;
|
||||
}
|
||||
|
||||
list.splice(pos, 1);
|
||||
|
||||
if (!list.length) {
|
||||
delete this.$events[name];
|
||||
}
|
||||
} else if (list === fn || (list.listener && list.listener === fn)) {
|
||||
delete this.$events[name];
|
||||
}
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Removes all listeners for an event.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
EventEmitter.prototype.removeAllListeners = function (name) {
|
||||
if (name === undefined) {
|
||||
this.$events = {};
|
||||
return this;
|
||||
}
|
||||
|
||||
if (this.$events && this.$events[name]) {
|
||||
this.$events[name] = null;
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets all listeners for a certain event.
|
||||
*
|
||||
* @api publci
|
||||
*/
|
||||
|
||||
EventEmitter.prototype.listeners = function (name) {
|
||||
if (!this.$events) {
|
||||
this.$events = {};
|
||||
}
|
||||
|
||||
if (!this.$events[name]) {
|
||||
this.$events[name] = [];
|
||||
}
|
||||
|
||||
if (!io.util.isArray(this.$events[name])) {
|
||||
this.$events[name] = [this.$events[name]];
|
||||
}
|
||||
|
||||
return this.$events[name];
|
||||
};
|
||||
|
||||
/**
|
||||
* Emits an event.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
EventEmitter.prototype.emit = function (name) {
|
||||
if (!this.$events) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var handler = this.$events[name];
|
||||
|
||||
if (!handler) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var args = Array.prototype.slice.call(arguments, 1);
|
||||
|
||||
if ('function' == typeof handler) {
|
||||
handler.apply(this, args);
|
||||
} else if (io.util.isArray(handler)) {
|
||||
var listeners = handler.slice();
|
||||
|
||||
for (var i = 0, l = listeners.length; i < l; i++) {
|
||||
listeners[i].apply(this, args);
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
102
lib/parser.js
Normal file
102
lib/parser.js
Normal file
@@ -0,0 +1,102 @@
|
||||
|
||||
/**
|
||||
* Packet types.
|
||||
*/
|
||||
|
||||
var packets = exports.packets = {
|
||||
'open': 0
|
||||
, 'close': 1
|
||||
, 'heartbeat': 2
|
||||
, 'message': 3
|
||||
, 'probe': 4
|
||||
, 'error': 5
|
||||
, 'noop': 6
|
||||
};
|
||||
|
||||
var packetslist = Object.keys(packets);
|
||||
|
||||
/**
|
||||
* Encodes a packet.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
exports.encodePacket = function (type, data) {
|
||||
var encoded = packets[type]
|
||||
|
||||
// data fragment is optional
|
||||
if ('string' == typeof data) {
|
||||
encoded += ':' + data;
|
||||
}
|
||||
|
||||
return encoded;
|
||||
};
|
||||
|
||||
/**
|
||||
* Decodes a packet.
|
||||
*
|
||||
* @return {Object} with `type` and `data` (if any)
|
||||
* @api private
|
||||
*/
|
||||
|
||||
exports.decodePacket = function (data) {
|
||||
if (~data.indexOf(':')) {
|
||||
var pieces = data.split(':');
|
||||
return { type: packetslist[pieces[0]], data: pieces[1] };
|
||||
} else {
|
||||
return { type: packetslist[data] };
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Encodes multiple messages (payload).
|
||||
*
|
||||
* @param {Array} messages
|
||||
* @api private
|
||||
*/
|
||||
|
||||
exports.encodePayload = function (packets) {
|
||||
var encoded = '';
|
||||
|
||||
if (packets.length == 1) {
|
||||
return packets[0];
|
||||
}
|
||||
|
||||
for (var i = 0, l = packets.length; i < l; i++) {
|
||||
encoded += '\ufffd' + packets[i].length + '\ufffd' + packets[i]
|
||||
}
|
||||
|
||||
return encoded;
|
||||
};
|
||||
|
||||
/*
|
||||
* Decodes data when a payload is maybe expected.
|
||||
*
|
||||
* @param {String} data
|
||||
* @return {Array} messages
|
||||
* @api public
|
||||
*/
|
||||
|
||||
exports.decodePayload = function (data) {
|
||||
if (undefined == data || null == data) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (data[0] == '\ufffd') {
|
||||
var ret = [];
|
||||
|
||||
for (var i = 1, length = ''; i < data.length; i++) {
|
||||
if (data[i] == '\ufffd') {
|
||||
ret.push(data.substr(i + 1).substr(0, length));
|
||||
i += Number(length) + 1;
|
||||
length = '';
|
||||
} else {
|
||||
length += data[i];
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
} else {
|
||||
return [data];
|
||||
}
|
||||
}
|
||||
163
lib/transport.js
Normal file
163
lib/transport.js
Normal file
@@ -0,0 +1,163 @@
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var util = require('./util')
|
||||
, EventEmitter = require('./event-emitter')
|
||||
|
||||
/**
|
||||
* Module exports.
|
||||
*/
|
||||
|
||||
module.exports = Transport;
|
||||
|
||||
/**
|
||||
* Transport abstract constructor.
|
||||
*
|
||||
* @param {Object} opts.
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function Transport (opts) {
|
||||
this.options = opts;
|
||||
this.readyState = '';
|
||||
};
|
||||
|
||||
/**
|
||||
* Inherits from EventEmitter.
|
||||
*/
|
||||
|
||||
util.inherits(Transport, EventEmitter);
|
||||
|
||||
/**
|
||||
* Whether to buffer outgoing data.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Transport.prototype.buffer = true;
|
||||
|
||||
/**
|
||||
* Emits an error.
|
||||
*
|
||||
* @param {String} str
|
||||
* @return {Transport} for chaining
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Transport.prototype.error = function (str) {
|
||||
this.emit('error', new Error(str));
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Opens the transport.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Transport.prototype.open = function () {
|
||||
if ('closed' == this.readyState || '' == this.readyState) {
|
||||
this.readyState = 'opening';
|
||||
this.doOpen();
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Closes the transport.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Transport.prototype.close = function () {
|
||||
if ('opening' == this.readyState || 'open' == this.readyState) {
|
||||
this.doClose();
|
||||
this.onClose();
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Sends a message.
|
||||
*
|
||||
* @param {String} data
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Transport.prototype.send = function (data) {
|
||||
if (this.readyState != 'open') {
|
||||
this.error('write error');
|
||||
} else {
|
||||
if (this.buffer) {
|
||||
if (!this.writeBuffer) {
|
||||
this.writeBuffer = [];
|
||||
}
|
||||
|
||||
this.writeBuffer.push(data);
|
||||
return this;
|
||||
}
|
||||
|
||||
var self = this;
|
||||
this.buffer = true;
|
||||
this.write(data, function () {
|
||||
self.flush();
|
||||
});
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Flushes the buffer.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Transport.prototype.flush = function () {
|
||||
this.buffer = false;
|
||||
this.emit('flush');
|
||||
|
||||
if (this.writeBuffer.length) {
|
||||
this.writeMany(self.writeBuffer);
|
||||
this.writeBuffer = [];
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Called upon open
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Transport.prototype.onOpen = function () {
|
||||
this.readyState = 'open';
|
||||
this.emit('open');
|
||||
};
|
||||
|
||||
/**
|
||||
* Called with data.
|
||||
*
|
||||
* @param {String} data
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Transport.prototype.onData = function (data) {
|
||||
this.emit('data', data);
|
||||
};
|
||||
|
||||
/**
|
||||
* Called upon close.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Transport.prototype.onClose = function () {
|
||||
this.readyState = 'closed';
|
||||
this.emit('close');
|
||||
};
|
||||
189
lib/transports/flashsocket.js
Normal file
189
lib/transports/flashsocket.js
Normal file
@@ -0,0 +1,189 @@
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var WebSocket = require('./websocket')
|
||||
, util = require('../util')
|
||||
|
||||
/**
|
||||
* Module exports.
|
||||
*/
|
||||
|
||||
module.exports = FlashWS;
|
||||
|
||||
/**
|
||||
* FlashWS constructor.
|
||||
*
|
||||
* @param {Engine} engine instance.
|
||||
* @api public
|
||||
*/
|
||||
|
||||
function FlashWS (options) {
|
||||
WebSocket.call(this, options);
|
||||
};
|
||||
|
||||
/**
|
||||
* Inherits from WebSocket.
|
||||
*/
|
||||
|
||||
util.inherits(FlashWS, WebSocket);
|
||||
|
||||
/**
|
||||
* Opens the transport.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
FlashWS.prototype.doOpen = function () {
|
||||
if (!check) {
|
||||
// let the probe timeout
|
||||
return;
|
||||
}
|
||||
|
||||
var base = io.enginePath + '/support/web-socket-js/'
|
||||
, self = this
|
||||
|
||||
// TODO: proxy logging to client logger
|
||||
WEB_SOCKET_LOGGER = function () { }
|
||||
WEB_SOCKET_SWF_LOCATION = base + '/WebSocketMainInsecure.swf';
|
||||
|
||||
$script.ready([base + 'swfobject.js', base + 'web_socket.js'], function () {
|
||||
FlashWs.prototype.doOpen.call(self);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Feature detection for FlashSocket.
|
||||
*
|
||||
* @return {Boolean} whether this transport is available.
|
||||
* @api public
|
||||
*/
|
||||
|
||||
function check () {
|
||||
// if node
|
||||
return false;
|
||||
// end
|
||||
|
||||
for (var i = 0, l = navigator.plugins.length; i < l; i++) {
|
||||
if (navigator.plugins[i].indexOf('Shockwave Flash')) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Dependency injection helper.
|
||||
* @license MIT - Copyright Dustin Diaz - Jacob Thornton - 2011
|
||||
*/
|
||||
|
||||
var $script = (function () {
|
||||
var win = this, doc = document
|
||||
, head = doc.getElementsByTagName('head')[0]
|
||||
, validBase = /^https?:\/\//
|
||||
, old = win.$script, list = {}, ids = {}, delay = {}, scriptpath
|
||||
, scripts = {}, s = 'string', f = false
|
||||
, push = 'push', domContentLoaded = 'DOMContentLoaded', readyState = 'readyState'
|
||||
, addEventListener = 'addEventListener', onreadystatechange = 'onreadystatechange'
|
||||
|
||||
function every(ar, fn, i) {
|
||||
for (i = 0, j = ar.length; i < j; ++i) if (!fn(ar[i])) return f
|
||||
return 1
|
||||
}
|
||||
function each(ar, fn) {
|
||||
every(ar, function(el) {
|
||||
return !fn(el)
|
||||
})
|
||||
}
|
||||
|
||||
if (!doc[readyState] && doc[addEventListener]) {
|
||||
doc[addEventListener](domContentLoaded, function fn() {
|
||||
doc.removeEventListener(domContentLoaded, fn, f)
|
||||
doc[readyState] = 'complete'
|
||||
}, f)
|
||||
doc[readyState] = 'loading'
|
||||
}
|
||||
|
||||
function $script(paths, idOrDone, optDone) {
|
||||
paths = paths[push] ? paths : [paths]
|
||||
var idOrDoneIsDone = idOrDone && idOrDone.call
|
||||
, done = idOrDoneIsDone ? idOrDone : optDone
|
||||
, id = idOrDoneIsDone ? paths.join('') : idOrDone
|
||||
, queue = paths.length
|
||||
function loopFn(item) {
|
||||
return item.call ? item() : list[item]
|
||||
}
|
||||
function callback() {
|
||||
if (!--queue) {
|
||||
list[id] = 1
|
||||
done && done()
|
||||
for (var dset in delay) {
|
||||
every(dset.split('|'), loopFn) && !each(delay[dset], loopFn) && (delay[dset] = [])
|
||||
}
|
||||
}
|
||||
}
|
||||
setTimeout(function () {
|
||||
each(paths, function(path) {
|
||||
if (scripts[path]) {
|
||||
id && (ids[id] = 1)
|
||||
return scripts[path] == 2 && callback()
|
||||
}
|
||||
scripts[path] = 1
|
||||
id && (ids[id] = 1)
|
||||
create(!validBase.test(path) && scriptpath ? scriptpath + path + '.js' : path, callback)
|
||||
})
|
||||
}, 0)
|
||||
return $script
|
||||
}
|
||||
|
||||
function create(path, fn) {
|
||||
var el = doc.createElement('script')
|
||||
, loaded = f
|
||||
el.onload = el.onerror = el[onreadystatechange] = function () {
|
||||
if ((el[readyState] && !(/^c|loade/.test(el[readyState]))) || loaded) return;
|
||||
el.onload = el[onreadystatechange] = null
|
||||
loaded = 1
|
||||
scripts[path] = 2
|
||||
fn()
|
||||
}
|
||||
el.async = 1
|
||||
el.src = path
|
||||
head.insertBefore(el, head.firstChild)
|
||||
}
|
||||
|
||||
$script.get = create
|
||||
|
||||
$script.order = function (scripts, id, done) {
|
||||
(function callback(s) {
|
||||
s = scripts.shift()
|
||||
if (!scripts.length) $script(s, id, done)
|
||||
else $script(s, callback)
|
||||
}())
|
||||
}
|
||||
|
||||
$script.path = function(p) {
|
||||
scriptpath = p
|
||||
}
|
||||
$script.ready = function(deps, ready, req) {
|
||||
deps = deps[push] ? deps : [deps]
|
||||
var missing = [];
|
||||
!each(deps, function(dep) {
|
||||
list[dep] || missing[push](dep);
|
||||
}) && every(deps, function(dep) {return list[dep]}) ?
|
||||
ready() : !function(key) {
|
||||
delay[key] = delay[key] || []
|
||||
delay[key][push](ready)
|
||||
req && req(missing)
|
||||
}(deps.join('|'))
|
||||
return $script
|
||||
}
|
||||
|
||||
$script.noConflict = function () {
|
||||
win.$script = old;
|
||||
return this
|
||||
}
|
||||
|
||||
return $script
|
||||
});
|
||||
8
lib/transports/index.js
Normal file
8
lib/transports/index.js
Normal file
@@ -0,0 +1,8 @@
|
||||
|
||||
/**
|
||||
* Export transports.
|
||||
*/
|
||||
|
||||
exports.polling = require('./polling');
|
||||
exports.websocket = require('./websocket');
|
||||
exports.flashsocket = require('./flashsocket');
|
||||
120
lib/transports/polling-jsonp.js
Normal file
120
lib/transports/polling-jsonp.js
Normal file
@@ -0,0 +1,120 @@
|
||||
|
||||
/**
|
||||
* Module requirements.
|
||||
*/
|
||||
|
||||
var Transport = require('../transport')
|
||||
, Polling = require('./polling')
|
||||
, util = require('../util')
|
||||
|
||||
/**
|
||||
* Noop.
|
||||
*/
|
||||
|
||||
function empty () { }
|
||||
|
||||
/**
|
||||
* Module exports.
|
||||
*/
|
||||
|
||||
module.exports = JSONPPolling;
|
||||
|
||||
/**
|
||||
* JSONP Polling constructor.
|
||||
*
|
||||
* @param {Object} opts.
|
||||
* @api public
|
||||
*/
|
||||
|
||||
function JSONPPolling (opts) {
|
||||
Transport.call(this, opts);
|
||||
this.setIndex();
|
||||
};
|
||||
|
||||
/**
|
||||
* Inherits from Polling.
|
||||
*/
|
||||
|
||||
util.inherits(JSONPPolling, Polling);
|
||||
|
||||
/**
|
||||
* Sets JSONP global callback.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
JSONPPolling.prototype.setIndex = function () {
|
||||
var self = this;
|
||||
|
||||
// if we have an index already, set it to empy
|
||||
if (undefined != this.index) {
|
||||
io.j[this.index] = empty;
|
||||
}
|
||||
|
||||
this.index = io.j.length;
|
||||
io.j.push(function (msg) {
|
||||
self.onData(msg);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Opens the socket.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
JSONPPolling.prototype.doOpen = function () {
|
||||
var self = this;
|
||||
util.defer(function () {
|
||||
Polling.prototype.doOpen.call(self);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Closes the socket
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
JSONPPolling.prototype.doClose = function () {
|
||||
this.setIndex();
|
||||
|
||||
if (this.script) {
|
||||
this.script.parentNode.removeChild(this.script);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Starts a poll cycle.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
JSONPPolling.prototype.doPoll = function () {
|
||||
var self = this
|
||||
, script = document.createElement('script')
|
||||
, query = io.util.query(
|
||||
this.socket.options.query
|
||||
, 't='+ (+new Date) + '&i=' + this.index
|
||||
);
|
||||
|
||||
if (this.script) {
|
||||
this.script.parentNode.removeChild(this.script);
|
||||
this.script = null;
|
||||
}
|
||||
|
||||
script.async = true;
|
||||
script.src = this.prepareUrl() + query;
|
||||
|
||||
var insertAt = document.getElementsByTagName('script')[0]
|
||||
insertAt.parentNode.insertBefore(script, insertAt);
|
||||
this.script = script;
|
||||
|
||||
if (util.ua.gecko) {
|
||||
setTimeout(function () {
|
||||
var iframe = document.createElement('iframe');
|
||||
document.body.appendChild(iframe);
|
||||
document.body.removeChild(iframe);
|
||||
}, 100);
|
||||
}
|
||||
};
|
||||
270
lib/transports/polling-xhr.js
Normal file
270
lib/transports/polling-xhr.js
Normal file
@@ -0,0 +1,270 @@
|
||||
|
||||
/**
|
||||
* Module requirements.
|
||||
*/
|
||||
|
||||
var Transport = require('../transport')
|
||||
, Polling = require('./polling')
|
||||
, EventEmitter = require('../event-emitter')
|
||||
, util = require('../util')
|
||||
, global = this
|
||||
|
||||
/**
|
||||
* Module exports.
|
||||
*/
|
||||
|
||||
module.exports = XHRPolling;
|
||||
module.exports.Request = Request;
|
||||
|
||||
/**
|
||||
* Empty function
|
||||
*/
|
||||
|
||||
function empty () { }
|
||||
|
||||
/**
|
||||
* XHR Polling constructor.
|
||||
*
|
||||
* @param {Object} opts.
|
||||
* @api public
|
||||
*/
|
||||
|
||||
function XHRPolling (opts) {
|
||||
Transport.call(this, opts);
|
||||
// if browser
|
||||
this.xd = opts.host != global.location.hostname
|
||||
|| global.location.port != opts.port;
|
||||
//end
|
||||
};
|
||||
|
||||
/**
|
||||
* Inherits from Polling.
|
||||
*/
|
||||
|
||||
util.inherits(XHRPolling, Polling);
|
||||
|
||||
/**
|
||||
* Opens the socket
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
XHRPolling.prototype.doOpen = function () {
|
||||
var self = this;
|
||||
util.defer(function () {
|
||||
Polling.prototype.open.call(self);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Closes the socket.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
XHRPolling.prototype.doClose = function () {
|
||||
if (this.pollXhr) {
|
||||
this.pollXhr.abort();
|
||||
}
|
||||
if (this.sendXhr) {
|
||||
this.sendXhr.abort();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a request.
|
||||
*
|
||||
* @param {String} method
|
||||
* @api private
|
||||
*/
|
||||
|
||||
XHR.prototype.request = function (opts) {
|
||||
opts.uri = this.uri();
|
||||
opts.xd = this.xd;
|
||||
var req = new Request(opts);
|
||||
req.on('error', function () {
|
||||
self.close();
|
||||
});
|
||||
return req;
|
||||
};
|
||||
|
||||
/**
|
||||
* Sends data.
|
||||
*
|
||||
* @param {String} data to send.
|
||||
* @param {Function} called upon flush.
|
||||
* @api private
|
||||
*/
|
||||
|
||||
XHR.prototype.write = function (data, fn) {
|
||||
var req = this.request({ method: 'POST', data: data })
|
||||
, self = this
|
||||
|
||||
req.on('success', fn);
|
||||
};
|
||||
|
||||
/**
|
||||
* Starts a poll cycle.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
XHRPolling.prototype.doPoll = function () {
|
||||
this.pollXhr = this.request();
|
||||
};
|
||||
|
||||
/**
|
||||
* Request constructor
|
||||
*
|
||||
* @param {Object} options
|
||||
* @api public
|
||||
*/
|
||||
|
||||
function Request (opts) {
|
||||
this.method = opts.method || 'GET';
|
||||
this.uri = opts.uri;
|
||||
this.xd = !!opts.xd;
|
||||
this.async = false !== opts.async;
|
||||
this.data = undefined != opts.data ? opts.data : null;
|
||||
this.create();
|
||||
}
|
||||
|
||||
/**
|
||||
* Inherits from Polling.
|
||||
*/
|
||||
|
||||
util.inherits(Request, EventEmitter);
|
||||
|
||||
/**
|
||||
* Creates the XHR object and sends the request.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Request.prototype.create = function () {
|
||||
var xhr = this.xhr = util.request(this.xd);
|
||||
this.xhr.open(this.method, this.uri, this.async);
|
||||
|
||||
if ('POST' == this.method) {
|
||||
try {
|
||||
if (xhr.setRequestHeader) {
|
||||
// xmlhttprequest
|
||||
xhr.setRequestHeader('Content-type', 'text/plain;charset=UTF-8');
|
||||
} else {
|
||||
// xdomainrequest
|
||||
xhr.contentType = 'text/plain';
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
if (this.xd && this.xhr instanceof XDomainRequest) {
|
||||
this.xhr.onerror = function () {
|
||||
self.onError();
|
||||
};
|
||||
this.xhr.onload = function () {
|
||||
self.onData(xhr.responseText);
|
||||
};
|
||||
this.xhr.onprogress = empty;
|
||||
} else {
|
||||
this.xhr.onreadystatechange = function () {
|
||||
try {
|
||||
if (xhr.readyState != 4) return;
|
||||
|
||||
if (200 == xhr.status) {
|
||||
self.onData(xhr.responseText);
|
||||
} else {
|
||||
self.onError();
|
||||
}
|
||||
} catch () {
|
||||
self.onError();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
this.xhr.send(this.data);
|
||||
|
||||
if (global.ActiveXObject) {
|
||||
this.index = Request.requestsCount++;
|
||||
Request.requests[this.index] = this;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Called upon successful response.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Request.prototype.onSuccess = function () {
|
||||
this.emit('success');
|
||||
this.cleanup();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called if we have data.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Request.prototype.onData = function (data) {
|
||||
this.emit('data', data);
|
||||
this.onSuccess();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called upon error.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Request.prototype.onError = function () {
|
||||
this.emit('error');
|
||||
this.cleanup();
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans up house.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Request.prototype.cleanup = function () {
|
||||
// xmlhttprequest
|
||||
this.xhr.onreadystatechange = empty;
|
||||
|
||||
// xdomainrequest
|
||||
this.xhr.onload = this.xhr.onerror = empty;
|
||||
|
||||
try {
|
||||
this.xhr.abort();
|
||||
} catch(e) {}
|
||||
|
||||
if (global.ActiveXObject) {
|
||||
delete Browser.requests[this.index];
|
||||
}
|
||||
|
||||
this.xhr = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Aborts the request.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Request.prototype.abort = function () {
|
||||
this.cleanup();
|
||||
};
|
||||
|
||||
if (global.ActiveXObject) {
|
||||
Request.requestsCount = 0;
|
||||
Request.requests = {};
|
||||
|
||||
global.attachEvent('onunload', function () {
|
||||
for (var i in Request.requests) {
|
||||
if (Request.requests.hasOwnProperty(i)) {
|
||||
Request.requests[i].abort();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
158
lib/transports/polling.js
Normal file
158
lib/transports/polling.js
Normal file
@@ -0,0 +1,158 @@
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var Transport = require('../transport')
|
||||
, XHR = require('./polling-xhr')
|
||||
, JSON = require('./polling-json')
|
||||
, util = require('../util')
|
||||
, parser = require('../parser')
|
||||
, global = this
|
||||
|
||||
/**
|
||||
* Module exports.
|
||||
*/
|
||||
|
||||
module.exports = Polling;
|
||||
|
||||
/**
|
||||
* Polling transport polymorphic constructor.
|
||||
* Decides on xhr vs jsonp based on feature detection.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
function Polling (opts) {
|
||||
var xd;
|
||||
|
||||
if (global.location) {
|
||||
xd = opts.host != global.location.hostname || global.location.port != opts.port;
|
||||
}
|
||||
|
||||
var xhr = request(xd);
|
||||
|
||||
if (xhr && !opts.forceJSONP) {
|
||||
// if we support xhr
|
||||
return new XHR;
|
||||
} else {
|
||||
return new JSONP;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Inherits from Transport.
|
||||
*/
|
||||
|
||||
util.inherits(Polling, Transport);
|
||||
|
||||
/**
|
||||
* Sets a callback for the next poll.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Polling.prototype.nextPoll = function (fn) {
|
||||
this.onNextPoll = fn;
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Opens the socket (triggers polling).
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Polling.prototype.doOpen = function () {
|
||||
this.poll();
|
||||
};
|
||||
|
||||
/**
|
||||
* Pauses polling.
|
||||
*
|
||||
* @param {Function} callback upon flush
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Polling.prototype.pause = function (onFlush) {
|
||||
this.paused = true;
|
||||
|
||||
var pending = 0;
|
||||
|
||||
if (this.polling) {
|
||||
pending++;
|
||||
this.once('data', function () {
|
||||
--pending || onFlush();
|
||||
}
|
||||
}
|
||||
|
||||
if (this.buffer) {
|
||||
pending++;
|
||||
this.once('flush', function () {
|
||||
--pending || onFlush();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Starts polling cycle.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Polling.prototype.poll = function () {
|
||||
if (!this.paused) {
|
||||
this.polling = true;
|
||||
this.doPoll();
|
||||
this.emit('poll');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Pauses polling.
|
||||
*
|
||||
* @param {Function} callback when buffers are flushed
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Polling.prototype.pause = function (fn) {
|
||||
this.paused = true;
|
||||
this.removeAllListeners();
|
||||
};
|
||||
|
||||
/**
|
||||
* Overloads onData to detect payloads.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Polling.prototype.onData = function (data) {
|
||||
if ('open' != this.readyState) {
|
||||
this.onOpen();
|
||||
}
|
||||
|
||||
var packets = parser.decodePayload(data);
|
||||
|
||||
for (var i = 0, l = packets.length; i < l; i++) {
|
||||
Transport.prototype.onData.call(this, packets[i]);
|
||||
}
|
||||
|
||||
// if we got data we're not polling
|
||||
this.polling = false;
|
||||
|
||||
// trigger next poll
|
||||
this.poll();
|
||||
|
||||
return ret;
|
||||
};
|
||||
|
||||
/**
|
||||
* Writes a packets payload.
|
||||
*
|
||||
* @param {Array} data packets
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Polling.prototype.writeMany = function (packets) {
|
||||
this.write(parser.encodePayload(packets));
|
||||
};
|
||||
133
lib/transports/websocket.js
Normal file
133
lib/transports/websocket.js
Normal file
@@ -0,0 +1,133 @@
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var Transport = require('../transport')
|
||||
, util = require('../util')
|
||||
, global = this
|
||||
|
||||
/**
|
||||
* Module exports.
|
||||
*/
|
||||
|
||||
module.exports = WS;
|
||||
|
||||
/**
|
||||
* WebSocket transport constructor.
|
||||
*
|
||||
* @api {Object} connection options
|
||||
* @api public
|
||||
*/
|
||||
|
||||
function WS (opts) {
|
||||
Transport.call(this, opts);
|
||||
};
|
||||
|
||||
/**
|
||||
* Inherits from Transport.
|
||||
*/
|
||||
|
||||
util.inherits(WS, Transport);
|
||||
|
||||
/**
|
||||
* Opens socket.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
WS.prototype.doOpen = function () {
|
||||
if (!check()) {
|
||||
// let probe timeout
|
||||
return;
|
||||
}
|
||||
|
||||
this.socket = new ws()(this.uri);
|
||||
this.socket.onopen = function () {
|
||||
self.onOpen();
|
||||
};
|
||||
this.socket.onclose = function () {
|
||||
self.onClose();
|
||||
};
|
||||
this.socket.ondata = function (ev) {
|
||||
self.onData(ev.data);
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Writes data to socket.
|
||||
*
|
||||
* @param {String} data.
|
||||
* @param {Function} flush callback.
|
||||
* @api private
|
||||
*/
|
||||
|
||||
WS.prototype.write = function (data, fn) {
|
||||
this.socket.send(data);
|
||||
fn();
|
||||
};
|
||||
|
||||
/**
|
||||
* Writes a packets payload.
|
||||
*
|
||||
* @param {Array} data packets
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Polling.prototype.writeMany = function (packets) {
|
||||
for (var i = 0, l = packets.length; i < l; i++) {
|
||||
this.write(packets[0]);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Closes socket.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
WS.prototype.doClose = function () {
|
||||
this.socket.close();
|
||||
};
|
||||
|
||||
/**
|
||||
* Generates uri for connection.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
WS.prototype.uri = function () {
|
||||
return [
|
||||
this.options.secure ? 'wss' : 'ws'
|
||||
, this.options.host
|
||||
, ':'
|
||||
, this.options.port
|
||||
, this.options.path
|
||||
, this.options.query
|
||||
].join('')
|
||||
};
|
||||
|
||||
/**
|
||||
* Getter for WS constructor.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function ws () {
|
||||
// if node
|
||||
return require('easy-websocket');
|
||||
// end
|
||||
|
||||
return global.WebSocket || global.MozWebSocket;
|
||||
}
|
||||
|
||||
/**
|
||||
* Feature detection for WebSocket.
|
||||
*
|
||||
* @return {Boolean} whether this transport is available.
|
||||
* @api public
|
||||
*/
|
||||
|
||||
function check () {
|
||||
return !!ws();
|
||||
}
|
||||
89
lib/util.js
Normal file
89
lib/util.js
Normal file
@@ -0,0 +1,89 @@
|
||||
|
||||
/**
|
||||
* Inheritance.
|
||||
*
|
||||
* @param {Function} ctor a
|
||||
* @param {Function} ctor b
|
||||
* @api public
|
||||
*/
|
||||
|
||||
exports.inherits = function inherits (a, b) {
|
||||
function c () { }
|
||||
c.prototype = b.prototype;
|
||||
a.prototype = new c;
|
||||
}
|
||||
|
||||
/**
|
||||
* UA / engines detection namespace.
|
||||
*
|
||||
* @namespace
|
||||
*/
|
||||
|
||||
util.ua = {};
|
||||
|
||||
/**
|
||||
* Whether the UA supports CORS for XHR.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
util.ua.hasCORS = 'undefined' != typeof XMLHttpRequest && (function () {
|
||||
try {
|
||||
var a = new XMLHttpRequest();
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return a.withCredentials != undefined;
|
||||
})();
|
||||
|
||||
/**
|
||||
* Detect webkit.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
util.ua.webkit = 'undefined' != typeof navigator &&
|
||||
/webkit/i.test(navigator.userAgent);
|
||||
|
||||
/**
|
||||
* Detect gecko.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
util.ua.gecko = 'undefined' != typeof navigator &&
|
||||
/gecko/i.test(navigator.userAgent);
|
||||
|
||||
// end
|
||||
|
||||
/**
|
||||
* XHR request helper.
|
||||
*
|
||||
* @param {Boolean} whether we need xdomain
|
||||
* @api private
|
||||
*/
|
||||
|
||||
util.request = function request (xdomain) {
|
||||
// if node
|
||||
var XMLHttpRequest = require('xmlhttprequest').XMLHttpRequest;
|
||||
return new XMLHttpRequest();
|
||||
// end
|
||||
|
||||
if (xdomain && 'undefined' != typeof XDomainRequest) {
|
||||
return new XDomainRequest();
|
||||
}
|
||||
|
||||
// XMLHttpRequest can be disabled on IE
|
||||
try {
|
||||
if ('undefined' != typeof XMLHttpRequest && (!xdomain || util.ua.hasCORS)) {
|
||||
return new XMLHttpRequest();
|
||||
}
|
||||
} catch (e) { }
|
||||
|
||||
if (!xdomain) {
|
||||
try {
|
||||
return new ActiveXObject('Microsoft.XMLHTTP');
|
||||
} catch(e) { }
|
||||
}
|
||||
};
|
||||
5
package.json
Normal file
5
package.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"name": "engine-client"
|
||||
, "description": "Client for the realtime Engine"
|
||||
, "version": "0.0.1"
|
||||
}
|
||||
1238
support/should.js
Normal file
1238
support/should.js
Normal file
File diff suppressed because it is too large
Load Diff
1
support/web-socket-js
Submodule
1
support/web-socket-js
Submodule
Submodule support/web-socket-js added at 14023075bf
12
test/engine-client.js
Normal file
12
test/engine-client.js
Normal file
@@ -0,0 +1,12 @@
|
||||
|
||||
describe('engine-client', function () {
|
||||
|
||||
it('should expose version number', function () {
|
||||
io.engineVersion.should().match(/);
|
||||
});
|
||||
|
||||
it('should expose protocol number', function () {
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
Reference in New Issue
Block a user