mirror of
https://github.com/socketio/socket.io.git
synced 2026-01-11 16:08:24 -05:00
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7926d96739 | ||
|
|
54871bcc53 | ||
|
|
bf114ddf2e | ||
|
|
300ee104bf | ||
|
|
d742345480 | ||
|
|
d9e60e334d | ||
|
|
2cd0dd7080 | ||
|
|
9e467381f3 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -8,4 +8,3 @@ lib-cov
|
||||
*.pid
|
||||
benchmarks/*.png
|
||||
node_modules
|
||||
coverage
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
support
|
||||
test
|
||||
examples
|
||||
.gitignore
|
||||
|
||||
13
.travis.yml
13
.travis.yml
@@ -1,16 +1,7 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
- "0.8"
|
||||
- "0.10"
|
||||
- "0.11"
|
||||
|
||||
git:
|
||||
depth: 1
|
||||
|
||||
matrix:
|
||||
fast_finish: true
|
||||
allow_failures:
|
||||
- node_js: "0.11"
|
||||
- 0.4
|
||||
- 0.6
|
||||
|
||||
notifications:
|
||||
irc: "irc.freenode.org#socket.io"
|
||||
|
||||
294
History.md
294
History.md
@@ -1,298 +1,4 @@
|
||||
|
||||
1.3.3 / 2015-02-03
|
||||
==================
|
||||
|
||||
* socket: warn node_redis-style about missing `error`
|
||||
* package: bump parser to better handle bad binary packets
|
||||
|
||||
1.3.2 / 2015-01-19
|
||||
==================
|
||||
|
||||
* no change on this release
|
||||
|
||||
1.3.1 / 2015-01-19
|
||||
==================
|
||||
|
||||
* no change on this release
|
||||
* package: bump `engine.io`
|
||||
|
||||
1.3.0 / 2015-01-19
|
||||
==================
|
||||
|
||||
* package: bump `engine.io`
|
||||
* add test for reconnection after server restarts [rase-]
|
||||
* update license with up-to-date year range [fay-jai]
|
||||
* fix leaving unknown rooms [defunctzombie]
|
||||
* allow null origins when allowed origins is a function [drewblaisdell]
|
||||
* fix tests on node 0.11
|
||||
* package: fix `npm test` to run on windows
|
||||
* package: bump `debug` v2.1.0 [coderaiser]
|
||||
* added tests for volatile [rase-]
|
||||
|
||||
1.2.1 / 2014-11-21
|
||||
==================
|
||||
|
||||
* fix protocol violations and improve error handling (GH-1880)
|
||||
* package: bump `engine.io` for websocket leak fix [3rd-Eden]
|
||||
* style tweaks
|
||||
|
||||
1.2.0 / 2014-10-27
|
||||
==================
|
||||
|
||||
* package: bump `engine.io`
|
||||
* downloads badge
|
||||
* add test to check that empty rooms are autopruned
|
||||
* added Server#origins(v:Function) description for dynamic CORS
|
||||
* added test coverage for Server#origins(function) for dynamic CORS
|
||||
* added optional Server#origins(function) for dynamic CORS
|
||||
* fix usage example for Server#close
|
||||
* package: fix main file for example application 'chat'
|
||||
* package: bump `socket.io-parser`
|
||||
* update README http ctor to createServer()
|
||||
* bump adapter with a lot of fixes for room bookkeeping
|
||||
|
||||
1.1.0 / 2014-09-04
|
||||
==================
|
||||
|
||||
* examples: minor fix of escaping
|
||||
* testing for equivalence of namespaces starting with / or without
|
||||
* update index.js
|
||||
* added relevant tests
|
||||
* take "" and "/" as equivalent namespaces on server
|
||||
* use svg instead of png to get better image quality in readme
|
||||
* make CI build faster
|
||||
* fix splice arguments and `socket.rooms` value update in `socket.leaveAll`.
|
||||
* client cannot connect to non-existing namespaces
|
||||
* bump engine.io version to get the cached IP address
|
||||
* fixed handshake object address property and made the test case more strict.
|
||||
* package: bump `engine.io`
|
||||
* fixed the failing test where server crashes on disconnect involving connectBuffer
|
||||
* npmignore: ignore `.gitignore` (fixes #1607)
|
||||
* test: added failing case for `socket.disconnect` and nsps
|
||||
* fix repo in package.json
|
||||
* improve Close documentation
|
||||
* use ephemeral ports
|
||||
* fix: We should use the standard http protocol to handler the etag header.
|
||||
* override default browser font-family for inputs
|
||||
* update has-binary-data to 1.0.3
|
||||
* add close specs
|
||||
* add ability to stop the http server even if not created inside socket.io
|
||||
* make sure server gets close
|
||||
* Add test case for checking that reconnect_failed is fired only once upon failure
|
||||
* package: bump `socket.io-parser` for `component-emitter` dep fix
|
||||
|
||||
1.0.6 / 2014-06-19
|
||||
==================
|
||||
|
||||
* package: bump `socket.io-client`
|
||||
|
||||
1.0.5 / 2014-06-16
|
||||
==================
|
||||
|
||||
* package: bump `engine.io` to fix jsonp `\n` bug and CORS warnings
|
||||
* index: fix typo [yanatan16]
|
||||
* add `removeListener` to blacklisted events
|
||||
* examples: clearer instructions to install chat example
|
||||
* index: fix namespace `connectBuffer` issue
|
||||
|
||||
1.0.4 / 2014-06-02
|
||||
==================
|
||||
|
||||
* package: bump socket.io-client
|
||||
|
||||
1.0.3 / 2014-05-31
|
||||
==================
|
||||
|
||||
* package: bump `socket.io-client`
|
||||
* package: bump `socket.io-parser` for binary ACK fix
|
||||
* package: bump `engine.io` for binary UTF8 fix
|
||||
* example: fix XSS in chat example
|
||||
|
||||
1.0.2 / 2014-05-28
|
||||
==================
|
||||
|
||||
* package: bump `socket.io-parser` for windows fix
|
||||
|
||||
1.0.1 / 2014-05-28
|
||||
==================
|
||||
|
||||
* bump due to bad npm tag
|
||||
|
||||
1.0.0 / 2014-05-28
|
||||
==================
|
||||
|
||||
* stable release
|
||||
|
||||
1.0.0-pre5 / 2014-05-22
|
||||
=======================
|
||||
|
||||
* package: bump `socket.io-client` for parser fixes
|
||||
* package: bump `engine.io`
|
||||
|
||||
1.0.0-pre4 / 2014-05-19
|
||||
=======================
|
||||
|
||||
* package: bump client
|
||||
|
||||
1.0.0-pre3 / 2014-05-17
|
||||
=======================
|
||||
|
||||
* package: bump parser
|
||||
* package: bump engine.io
|
||||
|
||||
1.0.0-pre2 / 2014-04-27
|
||||
=======================
|
||||
|
||||
* package: bump `engine.io`
|
||||
* added backwards compatible of engine.io maxHttpBufferSize
|
||||
* added test that server and client using same protocol
|
||||
* added support for setting allowed origins
|
||||
* added information about logging
|
||||
* the set function in server can be used to set some attributes for BC
|
||||
* fix error in callback call 'done' instead of 'next' in docs
|
||||
* package: bump `socket.io-parser`
|
||||
* package: bump `expect.js`
|
||||
* added some new tests, including binary with acks
|
||||
|
||||
1.0.0-pre / 2014-03-14
|
||||
======================
|
||||
|
||||
* implemented `engine.io`
|
||||
* implemented `socket.io-adapter`
|
||||
* implemented `socket.io-protocol`
|
||||
* implemented `debug` and improved instrumentation
|
||||
* added binary support
|
||||
* added new `require('io')(srv)` signature
|
||||
* simplified `socket.io-client` serving
|
||||
|
||||
0.9.14 / 2013-03-29
|
||||
===================
|
||||
|
||||
* manager: fix memory leak with SSL [jpallen]
|
||||
|
||||
0.9.13 / 2012-12-13
|
||||
===================
|
||||
|
||||
* package: fixed `base64id` requirement
|
||||
|
||||
0.9.12 / 2012-12-13
|
||||
===================
|
||||
|
||||
* manager: fix for latest node which is returning a clone with `listeners` [viirya]
|
||||
|
||||
0.9.11 / 2012-11-02
|
||||
===================
|
||||
|
||||
* package: move redis to optionalDependenices [3rd-Eden]
|
||||
* bumped client
|
||||
|
||||
0.9.10 / 2012-08-10
|
||||
===================
|
||||
|
||||
* Don't lowercase log messages
|
||||
* Always set the HTTP response in case an error should be returned to the client
|
||||
* Create or destroy the flash policy server on configuration change
|
||||
* Honour configuration to disable flash policy server
|
||||
* Add express 3.0 instructions on Readme.md
|
||||
* Bump client
|
||||
|
||||
0.9.9 / 2012-08-01
|
||||
==================
|
||||
|
||||
* Fixed sync disconnect xhrs handling
|
||||
* Put license text in its own file (#965)
|
||||
* Add warning to .listen() to ease the migration to Express 3.x
|
||||
* Restored compatibility with node 0.4.x
|
||||
|
||||
0.9.8 / 2012-07-24
|
||||
==================
|
||||
|
||||
* Bumped client.
|
||||
|
||||
0.9.7 / 2012-07-24
|
||||
==================
|
||||
|
||||
* Prevent crash when socket leaves a room twice.
|
||||
* Corrects unsafe usage of for..in
|
||||
* Fix for node 0.8 with `gzip compression` [vadimi]
|
||||
* Update redis to support Node 0.8.x
|
||||
* Made ID generation securely random
|
||||
* Fix Redis Store race condition in manager onOpen unsubscribe callback
|
||||
* Fix for EventEmitters always reusing the same Array instance for listeners
|
||||
|
||||
0.9.6 / 2012-04-17
|
||||
==================
|
||||
|
||||
* Fixed XSS in jsonp-polling.
|
||||
|
||||
0.9.5 / 2012-04-05
|
||||
==================
|
||||
|
||||
* Added test for polling and socket close.
|
||||
* Ensure close upon request close.
|
||||
* Fix disconnection reason being lost for polling transports.
|
||||
* Ensure that polling transports work with Connection: close.
|
||||
* Log disconnection reason.
|
||||
|
||||
0.9.4 / 2012-04-01
|
||||
==================
|
||||
|
||||
* Disconnecting from namespace improvement (#795) [DanielBaulig]
|
||||
* Bumped client with polling reconnection loop (#438)
|
||||
|
||||
0.9.3 / 2012-03-28
|
||||
==================
|
||||
|
||||
* Fix "Syntax error" on FF Web Console with XHR Polling [mikito]
|
||||
|
||||
0.9.2 / 2012-03-13
|
||||
==================
|
||||
|
||||
* More sensible close `timeout default` (fixes disconnect issue)
|
||||
|
||||
0.9.1-1 / 2012-03-02
|
||||
====================
|
||||
|
||||
* Bumped client with NPM dependency fix.
|
||||
|
||||
0.9.1 / 2012-03-02
|
||||
==================
|
||||
|
||||
* Changed heartbeat timeout and interval defaults (60 and 25 seconds)
|
||||
* Make tests work both on 0.4 and 0.6
|
||||
* Updated client (improvements + bug fixes).
|
||||
|
||||
0.9.0 / 2012-02-26
|
||||
==================
|
||||
|
||||
* Make it possible to use a regexp to match the socket.io resource URL.
|
||||
We need this because we have to prefix the socket.io URL with a variable ID.
|
||||
* Supplemental fix to gavinuhma/authfix, it looks like the same Access-Control-Origin logic is needed in the http and xhr-polling transports
|
||||
* Updated express dep for windows compatibility.
|
||||
* Combine two substr calls into one in decodePayload to improve performance
|
||||
* Minor documentation fix
|
||||
* Minor. Conform to style of other files.
|
||||
* Switching setting to 'match origin protocol'
|
||||
* Revert "Fixes leaking Redis subscriptions for #663. The local flag was not getting passed through onClientDisconnect()."
|
||||
* Revert "Handle leaked dispatch:[id] subscription."
|
||||
* Merge pull request #667 from dshaw/patch/redis-disconnect
|
||||
* Handle leaked dispatch:[id] subscription.
|
||||
* Fixes leaking Redis subscriptions for #663. The local flag was not getting passed through onClientDisconnect().
|
||||
* Prevent memory leaking on uncompleted requests & add max post size limitation
|
||||
* Fix for testcase
|
||||
* Set Access-Control-Allow-Credentials true, regardless of cookie
|
||||
* Remove assertvarnish from package as it breaks on 0.6
|
||||
* Correct irc channel
|
||||
* Added proper return after reserved field error
|
||||
* Fixes manager.js failure to close connection after transport error has happened
|
||||
* Added implicit port 80 for origin checks. fixes #638
|
||||
* Fixed bug #432 in 0.8.7
|
||||
* Set Access-Control-Allow-Origin header to origin to enable withCredentials
|
||||
* Adding configuration variable matchOriginProtocol
|
||||
* Fixes location mismatch error in Safari.
|
||||
* Use tty to detect if we should add colors or not by default.
|
||||
* Updated the package location.
|
||||
|
||||
0.8.7 / 2011-11-05
|
||||
==================
|
||||
|
||||
|
||||
22
LICENSE
22
LICENSE
@@ -1,22 +0,0 @@
|
||||
(The MIT License)
|
||||
|
||||
Copyright (c) 2014-2015 Automattic <dev@cloudup.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.
|
||||
17
Makefile
17
Makefile
@@ -1,15 +1,18 @@
|
||||
|
||||
TESTS = test/*.js
|
||||
BENCHMARKS = $(shell find bench -type f ! -name 'runner.js')
|
||||
REPORTER = dot
|
||||
|
||||
test:
|
||||
@./node_modules/.bin/mocha \
|
||||
--require test/common \
|
||||
--reporter $(REPORTER) \
|
||||
--slow 200ms \
|
||||
--bail
|
||||
--slow 500ms \
|
||||
--bail \
|
||||
--growl \
|
||||
$(TESTS)
|
||||
|
||||
test-cov:
|
||||
@./node_modules/.bin/istanbul cover ./node_modules/.bin/_mocha -- \
|
||||
--reporter $(REPORTER) \
|
||||
test/
|
||||
bench:
|
||||
@node $(PROFILEFLAGS) bench/runner.js $(BENCHMARKS)
|
||||
|
||||
.PHONY: test
|
||||
.PHONY: test bench
|
||||
|
||||
595
Readme.md
595
Readme.md
@@ -1,374 +1,277 @@
|
||||
# Socket.IO
|
||||
|
||||
# socket.io
|
||||
Socket.IO is a Node.JS framework for making realtime applications.
|
||||
|
||||
[](http://travis-ci.org/Automattic/socket.io)
|
||||

|
||||

|
||||
It brings the best of WebSockets and other data transport mechanisms for
|
||||
blazing fast realtime data exchange in web browsers, mobile devices and
|
||||
servers.
|
||||
|
||||
## How to Install
|
||||
|
||||
```
|
||||
npm install socket.io
|
||||
```
|
||||
|
||||
## How to use
|
||||
|
||||
The following example attaches socket.io to a plain Node.JS
|
||||
HTTP server listening on port `3000`.
|
||||
First, require `socket.io` and attach it to a HTTP/HTTPS server:
|
||||
|
||||
```js
|
||||
var server = require('http').createServer();
|
||||
var io = require('socket.io')(server);
|
||||
io.on('connection', function(socket){
|
||||
socket.on('event', function(data){});
|
||||
socket.on('disconnect', function(){});
|
||||
var app = express.createServer()
|
||||
, io = require('socket.io')(app);
|
||||
|
||||
app.listen(80);
|
||||
|
||||
io.on('connection', function (socket) {
|
||||
socket.emit('news', { hello: 'world' });
|
||||
socket.on('my other event', function (data) {
|
||||
console.log(data);
|
||||
});
|
||||
});
|
||||
server.listen(3000);
|
||||
```
|
||||
|
||||
### Standalone
|
||||
Finally, load it from the client side code:
|
||||
|
||||
```html
|
||||
<script src="/socket.io/socket.io.js"></script>
|
||||
<script>
|
||||
var socket = io.connect('http://localhost');
|
||||
socket.on('news', function (data) {
|
||||
console.log(data);
|
||||
socket.emit('my other event', { my: 'data' });
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
For more thorough examples, look at the `examples/` directory.
|
||||
|
||||
## Short recipes
|
||||
|
||||
### Sending and receiving events.
|
||||
|
||||
Socket.IO allows you to emit and receive custom events.
|
||||
Besides `connect`, `message` and `disconnect`, you can emit custom events:
|
||||
|
||||
```js
|
||||
var io = require('socket.io')();
|
||||
io.on('connection', function(socket){});
|
||||
io.listen(3000);
|
||||
// note, io(<port>) will create a http server for you
|
||||
var io = require('socket.io')(80);
|
||||
|
||||
io.on('connection', function (socket) {
|
||||
io.emit('this', { will: 'be received by everyone' });
|
||||
|
||||
socket.on('private message', function (from, msg) {
|
||||
console.log('I received a private message by ', from, ' saying ', msg);
|
||||
});
|
||||
|
||||
socket.on('disconnect', function () {
|
||||
io.emit('user disconnected');
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### In conjunction with Express
|
||||
### Storing data associated to a client
|
||||
|
||||
Starting with **3.0**, express applications have become request handler
|
||||
functions that you pass to `http` or `http` `Server` instances. You need
|
||||
to pass the `Server` to `socket.io`, and not the express application
|
||||
function.
|
||||
Sometimes it's necessary to store data associated with a client that's
|
||||
necessary for the duration of the session.
|
||||
|
||||
#### Server side
|
||||
|
||||
```js
|
||||
var app = require('express')();
|
||||
var server = require('http').createServer(app);
|
||||
var io = require('socket.io')(server);
|
||||
io.on('connection', function(){ /* … */ });
|
||||
server.listen(3000);
|
||||
var io = require('socket.io')(80);
|
||||
|
||||
io.on('connection', function (socket) {
|
||||
socket.on('set nickname', function (name) {
|
||||
socket.set('nickname', name, function () { socket.emit('ready'); });
|
||||
});
|
||||
|
||||
socket.on('msg', function () {
|
||||
socket.get('nickname', function (err, name) {
|
||||
console.log('Chat message by ', name);
|
||||
});
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### In conjunction with Koa
|
||||
#### Client side
|
||||
|
||||
Like Express.JS, Koa works by exposing an application as a request
|
||||
handler function, but only by calling the `callback` method.
|
||||
```html
|
||||
<script>
|
||||
var socket = io.connect('http://localhost');
|
||||
|
||||
socket.on('connect', function () {
|
||||
socket.emit('set nickname', confirm('What is your nickname?'));
|
||||
socket.on('ready', function () {
|
||||
console.log('Connected !');
|
||||
socket.emit('msg', confirm('What is your message?'));
|
||||
});
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
#### Client side
|
||||
|
||||
In the client side, messages are received the same way whether they're volatile
|
||||
or not.
|
||||
|
||||
### Getting acknowledgements
|
||||
|
||||
Sometimes, you might want to get a callback when the client confirmed the message
|
||||
reception.
|
||||
|
||||
To do this, simply pass a function as the last parameter of `.emit`.
|
||||
What's more, when you use `.emit`, the acknowledgement is done by you, which
|
||||
means you can also pass data along:
|
||||
|
||||
#### Server side
|
||||
|
||||
```js
|
||||
var app = require('koa')();
|
||||
var server = require('http').createServer(app.callback());
|
||||
var io = require('socket.io')(server);
|
||||
io.on('connection', function(){ /* … */ });
|
||||
server.listen(3000);
|
||||
var io = require('socket.io')(80);
|
||||
|
||||
io.on('connection', function (socket) {
|
||||
socket.on('ferret', function (name, fn) {
|
||||
fn('woot');
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
### Server
|
||||
|
||||
Exposed by `require('socket.io')`.
|
||||
|
||||
### Server()
|
||||
|
||||
Creates a new `Server`. Works with and without `new`:
|
||||
|
||||
```js
|
||||
var io = require('socket.io')();
|
||||
// or
|
||||
var Server = require('socket.io');
|
||||
var io = new Server();
|
||||
```
|
||||
|
||||
### Server(opts:Object)
|
||||
|
||||
Optionally, the first or second argument (see below) of the `Server`
|
||||
constructor can be an options object.
|
||||
|
||||
The following options are supported:
|
||||
|
||||
- `serveClient` sets the value for Server#serveClient()
|
||||
- `path` sets the value for Server#path()
|
||||
|
||||
The same options passed to socket.io are always passed to
|
||||
the `engine.io` `Server` that gets created. See engine.io
|
||||
[options](https://github.com/learnboost/engine.io#methods-1)
|
||||
as reference.
|
||||
|
||||
### Server(srv:http#Server, opts:Object)
|
||||
|
||||
Creates a new `Server` and attaches it to the given `srv`. Optionally
|
||||
`opts` can be passed.
|
||||
|
||||
### Server(port:Number, opts:Object)
|
||||
|
||||
Binds socket.io to a new `http.Server` that listens on `port`.
|
||||
|
||||
### Server#serveClient(v:Boolean):Server
|
||||
|
||||
If `v` is `true` the attached server (see `Server#attach`) will serve
|
||||
the client files. Defaults to `true`.
|
||||
|
||||
This method has no effect after `attach` is called.
|
||||
|
||||
```js
|
||||
// pass a server and the `serveClient` option
|
||||
var io = require('socket.io')(http, { serveClient: false });
|
||||
|
||||
// or pass no server and then you can call the method
|
||||
var io = require('socket.io')();
|
||||
io.serveClient(false);
|
||||
io.attach(http);
|
||||
```
|
||||
|
||||
If no arguments are supplied this method returns the current value.
|
||||
|
||||
### Server#path(v:String):Server
|
||||
|
||||
Sets the path `v` under which `engine.io` and the static files will be
|
||||
served. Defaults to `/socket.io`.
|
||||
|
||||
If no arguments are supplied this method returns the current value.
|
||||
|
||||
### Server#adapter(v:Adapter):Server
|
||||
|
||||
Sets the adapter `v`. Defaults to an instance of the `Adapter` that
|
||||
ships with socket.io which is memory based. See
|
||||
[socket.io-adapter](https://github.com/Automattic/socket.io-adapter).
|
||||
|
||||
If no arguments are supplied this method returns the current value.
|
||||
|
||||
### Server#origins(v:String):Server
|
||||
|
||||
Sets the allowed origins `v`. Defaults to any origins being allowed.
|
||||
|
||||
If no arguments are supplied this method returns the current value.
|
||||
|
||||
### Server#origins(v:Function):Server
|
||||
|
||||
Sets the allowed origins as dynamic function. Function takes two arguments `origin:String` and `callback(error, success)`, where `success` is a boolean value indicating whether origin is allowed or not.
|
||||
|
||||
__Potential drawbacks__:
|
||||
* in some situations, when it is not possible to determine `origin` it may have value of `*`
|
||||
* As this function will be executed for every request, it is advised to make this function work as fast as possible
|
||||
* If `socket.io` is used together with `Express`, the CORS headers will be affected only for `socket.io` requests. For Express can use [cors](https://github.com/troygoode/node-cors/)
|
||||
|
||||
|
||||
### Server#sockets:Namespace
|
||||
|
||||
The default (`/`) namespace.
|
||||
|
||||
### Server#attach(srv:http#Server, opts:Object):Server
|
||||
|
||||
Attaches the `Server` to an engine.io instance on `srv` with the
|
||||
supplied `opts` (optionally).
|
||||
|
||||
### Server#attach(port:Number, opts:Object):Server
|
||||
|
||||
Attaches the `Server` to an engine.io instance that is bound to `port`
|
||||
with the given `opts` (optionally).
|
||||
|
||||
### Server#listen
|
||||
|
||||
Synonym of `Server#attach`.
|
||||
|
||||
### Server#bind(srv:engine#Server):Server
|
||||
|
||||
Advanced use only. Binds the server to a specific engine.io `Server`
|
||||
(or compatible API) instance.
|
||||
|
||||
### Server#onconnection(socket:engine#Socket):Server
|
||||
|
||||
Advanced use only. Creates a new `socket.io` client from the incoming
|
||||
engine.io (or compatible API) `socket`.
|
||||
|
||||
### Server#of(nsp:String):Namespace
|
||||
|
||||
Initializes and retrieves the given `Namespace` by its pathname
|
||||
identifier `nsp`.
|
||||
|
||||
If the namespace was already initialized it returns it right away.
|
||||
|
||||
### Server#emit
|
||||
|
||||
Emits an event to all connected clients. The following two are
|
||||
equivalent:
|
||||
|
||||
```js
|
||||
var io = require('socket.io')();
|
||||
io.sockets.emit('an event sent to all connected clients');
|
||||
io.emit('an event sent to all connected clients');
|
||||
```
|
||||
|
||||
For other available methods, see `Namespace` below.
|
||||
|
||||
### Server#close
|
||||
|
||||
Closes socket server
|
||||
|
||||
```js
|
||||
var Server = require('socket.io');
|
||||
var PORT = 3030;
|
||||
var server = require('http').Server();
|
||||
|
||||
var io = Server(PORT);
|
||||
|
||||
io.close(); // Close current server
|
||||
|
||||
server.listen(PORT); // PORT is free to use
|
||||
|
||||
io = Server(server);
|
||||
```
|
||||
|
||||
### Server#use
|
||||
|
||||
See `Namespace#use` below.
|
||||
|
||||
### Namespace
|
||||
|
||||
Represents a pool of sockets connected under a given scope identified
|
||||
by a pathname (eg: `/chat`).
|
||||
|
||||
By default the client always connects to `/`.
|
||||
|
||||
#### Events
|
||||
|
||||
- `connection` / `connect`. Fired upon a connection.
|
||||
|
||||
Parameters:
|
||||
- `Socket` the incoming socket.
|
||||
|
||||
### Namespace#name:String
|
||||
|
||||
The namespace identifier property.
|
||||
|
||||
### Namespace#connected:Object<Socket>
|
||||
|
||||
Hash of `Socket` objects that are connected to this namespace indexed
|
||||
by `id`.
|
||||
|
||||
### Namespace#use(fn:Function):Namespace
|
||||
|
||||
Registers a middleware, which is a function that gets executed for
|
||||
every incoming `Socket` and receives as parameter the socket and a
|
||||
function to optionally defer execution to the next registered
|
||||
middleware.
|
||||
|
||||
```js
|
||||
var io = require('socket.io')();
|
||||
io.use(function(socket, next){
|
||||
if (socket.request.headers.cookie) return next();
|
||||
next(new Error('Authentication error'));
|
||||
#### Client side
|
||||
|
||||
```html
|
||||
<script>
|
||||
var socket = io.connect(); // TIP: .connect with no args does auto-discovery
|
||||
socket.on('connect', function () { // TIP: you can avoid listening on `connect` and listen on events directly too!
|
||||
socket.emit('ferret', 'tobi', function (data) {
|
||||
console.log(data); // data will be 'woot'
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
Errors passed to middleware callbacks are sent as special `error`
|
||||
packets to clients.
|
||||
|
||||
### Socket
|
||||
|
||||
A `Socket` is the fundamental class for interacting with browser
|
||||
clients. A `Socket` belongs to a certain `Namespace` (by default `/`)
|
||||
and uses an underlying `Client` to communicate.
|
||||
|
||||
### Socket#rooms:Array
|
||||
|
||||
A list of strings identifying the rooms this socket is in.
|
||||
|
||||
### Socket#client:Client
|
||||
|
||||
A reference to the underlying `Client` object.
|
||||
|
||||
### Socket#conn:Socket
|
||||
|
||||
A reference to the underlying `Client` transport connection (engine.io
|
||||
`Socket` object).
|
||||
|
||||
### Socket#request:Request
|
||||
|
||||
A getter proxy that returns the reference to the `request` that
|
||||
originated the underlying engine.io `Client`. Useful for accessing
|
||||
request headers such as `Cookie` or `User-Agent`.
|
||||
|
||||
### Socket#id:String
|
||||
|
||||
A unique identifier for the socket session, that comes from the
|
||||
underlying `Client`.
|
||||
|
||||
### Socket#emit(name:String[, …]):Socket
|
||||
|
||||
Emits an event to the socket identified by the string `name`. Any
|
||||
other parameters can be included.
|
||||
|
||||
All datastructures are supported, including `Buffer`. JavaScript
|
||||
functions can't be serialized/deserialized.
|
||||
|
||||
```js
|
||||
var io = require('socket.io')();
|
||||
io.on('connection', function(socket){
|
||||
socket.emit('an event', { some: 'data' });
|
||||
});
|
||||
```
|
||||
|
||||
### Socket#join(name:String[, fn:Function]):Socket
|
||||
|
||||
Adds the socket to the `room`, and fires optionally a callback `fn`
|
||||
with `err` signature (if any).
|
||||
|
||||
The socket is automatically a member of a room identified with its
|
||||
session id (see `Socket#id`).
|
||||
|
||||
The mechanics of joining rooms are handled by the `Adapter`
|
||||
that has been configured (see `Server#adapter` above), defaulting to
|
||||
[socket.io-adapter](https://github.com/Automattic/socket.io-adapter).
|
||||
|
||||
### Socket#leave(name:String[, fn:Function]):Socket
|
||||
|
||||
Removes the socket from `room`, and fires optionally a callback `fn`
|
||||
with `err` signature (if any).
|
||||
|
||||
**Rooms are left automatically upon disconnection**.
|
||||
|
||||
The mechanics of leaving rooms are handled by the `Adapter`
|
||||
that has been configured (see `Server#adapter` above), defaulting to
|
||||
[socket.io-adapter](https://github.com/Automattic/socket.io-adapter).
|
||||
|
||||
### Socket#to(room:String):Socket
|
||||
### Socket#in(room:String):Socket
|
||||
|
||||
Sets a modifier for a subsequent event emission that the event will
|
||||
only be _broadcasted_ to sockets that have joined the given `room`.
|
||||
|
||||
To emit to multiple rooms, you can call `to` several times.
|
||||
|
||||
```js
|
||||
var io = require('socket.io')();
|
||||
io.on('connection', function(socket){
|
||||
socket.to('others').emit('an event', { some: 'data' });
|
||||
});
|
||||
```
|
||||
|
||||
### Client
|
||||
|
||||
The `Client` class represents an incoming transport (engine.io)
|
||||
connection. A `Client` can be associated with many multiplexed `Socket`
|
||||
that belong to different `Namespace`s.
|
||||
|
||||
### Client#conn
|
||||
|
||||
A reference to the underlying `engine.io` `Socket` connection.
|
||||
|
||||
### Client#request
|
||||
|
||||
A getter proxy that returns the reference to the `request` that
|
||||
originated the engine.io connection. Useful for accessing
|
||||
request headers such as `Cookie` or `User-Agent`.
|
||||
|
||||
## Debug / logging
|
||||
|
||||
Socket.IO is powered by [debug](http://github.com/visionmedia/debug).
|
||||
In order to see all the debug output, run your app with the environment variable
|
||||
`DEBUG` including the desired scope.
|
||||
|
||||
To see the output from all of Socket.IO's debugging scopes you can use:
|
||||
|
||||
```
|
||||
DEBUG=socket.io* node myapp
|
||||
</script>
|
||||
```
|
||||
|
||||
## License
|
||||
### Broadcasting messages
|
||||
|
||||
MIT
|
||||
To broadcast, simply add a `broadcast` flag to `emit` and `send` method calls.
|
||||
Broadcasting means sending a message to everyone else except for the socket
|
||||
that starts it.
|
||||
|
||||
#### Server side
|
||||
|
||||
```js
|
||||
var io = require('socket.io')(80);
|
||||
|
||||
io.on('connection', function (socket) {
|
||||
socket.broadcast.emit('user connected');
|
||||
});
|
||||
```
|
||||
|
||||
### Rooms
|
||||
|
||||
Sometimes you want to put certain sockets in the same room, so that it's easy
|
||||
to broadcast to all of them together.
|
||||
|
||||
Think of this as built-in channels for sockets. Sockets `join` and `leave`
|
||||
rooms in each socket.
|
||||
|
||||
#### Server side
|
||||
|
||||
```js
|
||||
var io = require('socket.io')(80);
|
||||
|
||||
io.on('connection', function (socket) {
|
||||
socket.join('justin bieber fans');
|
||||
socket.broadcast.to('justin bieber fans').emit('new fan');
|
||||
io.to('rammstein fans').emit('new non-fan');
|
||||
});
|
||||
```
|
||||
|
||||
### Using it just as a cross-browser WebSocket
|
||||
|
||||
If you just want the WebSocket semantics, you can do that too.
|
||||
Simply leverage `send` and listen on the `message` event:
|
||||
|
||||
#### Server side
|
||||
|
||||
```js
|
||||
var io = require('socket.io')(80);
|
||||
|
||||
io.on('connection', function (socket) {
|
||||
socket.on('message', function () { });
|
||||
socket.on('disconnect', function () { });
|
||||
});
|
||||
```
|
||||
|
||||
#### Client side
|
||||
|
||||
```html
|
||||
<script>
|
||||
var socket = io.connect('http://localhost/');
|
||||
socket.on('connect', function () {
|
||||
socket.send('hi');
|
||||
|
||||
socket.on('message', function (msg) {
|
||||
// my msg
|
||||
});
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
### Messaging between clients
|
||||
|
||||
Each client joins its own group, identified by the client id, which means you
|
||||
can leverage `to` to message a particular client:
|
||||
|
||||
```js
|
||||
var io = require('socket.io')(80);
|
||||
io.on('connection', function (socket) {
|
||||
socket.on('salutate', function (id) {
|
||||
io.to(id)
|
||||
.emit('hi!')
|
||||
.on('disconnect', function () {
|
||||
socket.emit('friend disconnected');
|
||||
});
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### Changing configuration
|
||||
|
||||
Configuration in socket.io is TJ-style:
|
||||
|
||||
#### Server side
|
||||
|
||||
```js
|
||||
var io = require('socket.io')(80);
|
||||
|
||||
io.configure(function () {
|
||||
io.set('transports', ['websocket', 'flashsocket', 'xhr-polling']);
|
||||
});
|
||||
|
||||
io.configure('development', function () {
|
||||
io.set('transports', ['websocket', 'xhr-polling']);
|
||||
io.enable('log');
|
||||
});
|
||||
```
|
||||
|
||||
## 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.
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
|
||||
# Socket.IO Chat
|
||||
|
||||
A simple chat demo for socket.io
|
||||
|
||||
## How to use
|
||||
|
||||
```
|
||||
$ cd socket.io
|
||||
$ npm install
|
||||
$ cd examples/chat
|
||||
$ npm install
|
||||
$ node .
|
||||
```
|
||||
|
||||
And point your browser to `http://localhost:3000`. Optionally, specify
|
||||
a port by supplying the `PORT` env variable.
|
||||
|
||||
## Features
|
||||
|
||||
- Multiple users can join a chat room by each entering a unique username
|
||||
on website load.
|
||||
- Users can type chat messages to the chat room.
|
||||
- A notification is sent to all users when a user joins or leaves
|
||||
the chatroom.
|
||||
80
examples/chat/app.js
Normal file
80
examples/chat/app.js
Normal file
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var express = require('express')
|
||||
, stylus = require('stylus')
|
||||
, nib = require('nib')
|
||||
, sio = require('../../lib/socket.io');
|
||||
|
||||
/**
|
||||
* App.
|
||||
*/
|
||||
|
||||
var app = express.createServer();
|
||||
|
||||
/**
|
||||
* App configuration.
|
||||
*/
|
||||
|
||||
app.configure(function () {
|
||||
app.use(stylus.middleware({ src: __dirname + '/public', compile: compile }));
|
||||
app.use(express.static(__dirname + '/public'));
|
||||
app.set('views', __dirname);
|
||||
app.set('view engine', 'jade');
|
||||
|
||||
function compile (str, path) {
|
||||
return stylus(str)
|
||||
.set('filename', path)
|
||||
.use(nib());
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* App routes.
|
||||
*/
|
||||
|
||||
app.get('/', function (req, res) {
|
||||
res.render('index', { layout: false });
|
||||
});
|
||||
|
||||
/**
|
||||
* App listen.
|
||||
*/
|
||||
|
||||
app.listen(3000, function () {
|
||||
var addr = app.address();
|
||||
console.log(' app listening on http://' + addr.address + ':' + addr.port);
|
||||
});
|
||||
|
||||
/**
|
||||
* Socket.IO server (single process only)
|
||||
*/
|
||||
|
||||
var io = sio(app)
|
||||
, nicknames = {};
|
||||
|
||||
io.on('connection', function (socket) {
|
||||
socket.on('user message', function (msg) {
|
||||
socket.broadcast.emit('user message', socket.nickname, msg);
|
||||
});
|
||||
|
||||
socket.on('nickname', function (nick, fn) {
|
||||
if (nicknames[nick]) {
|
||||
fn(true);
|
||||
} else {
|
||||
fn(false);
|
||||
nicknames[nick] = socket.nickname = nick;
|
||||
socket.broadcast.emit('announcement', nick + ' connected');
|
||||
io.emit('nicknames', nicknames);
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('disconnect', function () {
|
||||
if (!socket.nickname) return;
|
||||
|
||||
delete nicknames[socket.nickname];
|
||||
socket.broadcast.emit('announcement', socket.nickname + ' disconnected');
|
||||
socket.broadcast.emit('nicknames', nicknames);
|
||||
});
|
||||
});
|
||||
83
examples/chat/index.jade
Normal file
83
examples/chat/index.jade
Normal file
@@ -0,0 +1,83 @@
|
||||
doctype 5
|
||||
html
|
||||
head
|
||||
link(href='/stylesheets/style.css', rel='stylesheet')
|
||||
script(src='http://code.jquery.com/jquery-1.6.1.min.js')
|
||||
script(src='/socket.io/socket.io.js')
|
||||
script
|
||||
// socket.io specific code
|
||||
var socket = io.connect();
|
||||
|
||||
socket.on('connect', function () {
|
||||
$('#chat').addClass('connected');
|
||||
});
|
||||
|
||||
socket.on('announcement', function (msg) {
|
||||
$('#lines').append($('<p>').append($('<em>').text(msg)));
|
||||
});
|
||||
|
||||
socket.on('nicknames', function (nicknames) {
|
||||
$('#nicknames').empty().append($('<span>Online: </span>'));
|
||||
for (var i in nicknames) {
|
||||
$('#nicknames').append($('<b>').text(nicknames[i]));
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('user message', message);
|
||||
socket.on('reconnect', function () {
|
||||
$('#lines').remove();
|
||||
message('System', 'Reconnected to the server');
|
||||
});
|
||||
|
||||
socket.on('reconnecting', function () {
|
||||
message('System', 'Attempting to re-connect to the server');
|
||||
});
|
||||
|
||||
socket.on('error', function (e) {
|
||||
message('System', e ? e : 'A unknown error occurred');
|
||||
});
|
||||
|
||||
function message (from, msg) {
|
||||
$('#lines').append($('<p>').append($('<b>').text(from), msg));
|
||||
}
|
||||
|
||||
// dom manipulation
|
||||
$(function () {
|
||||
$('#set-nickname').submit(function (ev) {
|
||||
socket.emit('nickname', $('#nick').val(), function (set) {
|
||||
if (!set) {
|
||||
clear();
|
||||
return $('#chat').addClass('nickname-set');
|
||||
}
|
||||
$('#nickname-err').css('visibility', 'visible');
|
||||
});
|
||||
return false;
|
||||
});
|
||||
|
||||
$('#send-message').submit(function () {
|
||||
message('me', $('#message').val());
|
||||
socket.emit('user message', $('#message').val());
|
||||
clear();
|
||||
$('#lines').get(0).scrollTop = 10000000;
|
||||
return false;
|
||||
});
|
||||
|
||||
function clear () {
|
||||
$('#message').val('').focus();
|
||||
};
|
||||
});
|
||||
body
|
||||
#chat
|
||||
#nickname
|
||||
form.wrap#set-nickname
|
||||
p Please type in your nickname and press enter.
|
||||
input#nick
|
||||
p#nickname-err Nickname already in use
|
||||
#connecting
|
||||
.wrap Connecting to socket.io server
|
||||
#messages
|
||||
#nicknames
|
||||
#lines
|
||||
form#send-message
|
||||
input#message
|
||||
button Send
|
||||
@@ -1,79 +0,0 @@
|
||||
// Setup basic express server
|
||||
var express = require('express');
|
||||
var app = express();
|
||||
var server = require('http').createServer(app);
|
||||
var io = require('../..')(server);
|
||||
var port = process.env.PORT || 3000;
|
||||
|
||||
server.listen(port, function () {
|
||||
console.log('Server listening at port %d', port);
|
||||
});
|
||||
|
||||
// Routing
|
||||
app.use(express.static(__dirname + '/public'));
|
||||
|
||||
// Chatroom
|
||||
|
||||
// usernames which are currently connected to the chat
|
||||
var usernames = {};
|
||||
var numUsers = 0;
|
||||
|
||||
io.on('connection', function (socket) {
|
||||
var addedUser = false;
|
||||
|
||||
// when the client emits 'new message', this listens and executes
|
||||
socket.on('new message', function (data) {
|
||||
// we tell the client to execute 'new message'
|
||||
socket.broadcast.emit('new message', {
|
||||
username: socket.username,
|
||||
message: data
|
||||
});
|
||||
});
|
||||
|
||||
// when the client emits 'add user', this listens and executes
|
||||
socket.on('add user', function (username) {
|
||||
// we store the username in the socket session for this client
|
||||
socket.username = username;
|
||||
// add the client's username to the global list
|
||||
usernames[username] = username;
|
||||
++numUsers;
|
||||
addedUser = true;
|
||||
socket.emit('login', {
|
||||
numUsers: numUsers
|
||||
});
|
||||
// echo globally (all clients) that a person has connected
|
||||
socket.broadcast.emit('user joined', {
|
||||
username: socket.username,
|
||||
numUsers: numUsers
|
||||
});
|
||||
});
|
||||
|
||||
// when the client emits 'typing', we broadcast it to others
|
||||
socket.on('typing', function () {
|
||||
socket.broadcast.emit('typing', {
|
||||
username: socket.username
|
||||
});
|
||||
});
|
||||
|
||||
// when the client emits 'stop typing', we broadcast it to others
|
||||
socket.on('stop typing', function () {
|
||||
socket.broadcast.emit('stop typing', {
|
||||
username: socket.username
|
||||
});
|
||||
});
|
||||
|
||||
// when the user disconnects.. perform this
|
||||
socket.on('disconnect', function () {
|
||||
// remove the username from global usernames list
|
||||
if (addedUser) {
|
||||
delete usernames[socket.username];
|
||||
--numUsers;
|
||||
|
||||
// echo globally that this client has left
|
||||
socket.broadcast.emit('user left', {
|
||||
username: socket.username,
|
||||
numUsers: numUsers
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -1,12 +1,11 @@
|
||||
{
|
||||
"name": "socket.io-chat",
|
||||
"version": "0.0.0",
|
||||
"description": "A simple chat client using socket.io",
|
||||
"main": "index.js",
|
||||
"author": "Grant Timmerman",
|
||||
"private": true,
|
||||
"license": "BSD",
|
||||
"dependencies": {
|
||||
"express": "3.4.8"
|
||||
}
|
||||
}
|
||||
"name": "chat.io"
|
||||
, "description": "example chat application with socket.io"
|
||||
, "version": "0.0.1"
|
||||
, "dependencies": {
|
||||
"express": "2.5.5"
|
||||
, "jade": "0.16.4"
|
||||
, "stylus": "0.19.0"
|
||||
, "nib": "0.2.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Socket.IO Chat Example</title>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
</head>
|
||||
<body>
|
||||
<ul class="pages">
|
||||
<li class="chat page">
|
||||
<div class="chatArea">
|
||||
<ul class="messages"></ul>
|
||||
</div>
|
||||
<input class="inputMessage" placeholder="Type here..."/>
|
||||
</li>
|
||||
<li class="login page">
|
||||
<div class="form">
|
||||
<h3 class="title">What's your nickname?</h3>
|
||||
<input class="usernameInput" type="text" maxlength="14" />
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<script src="https://code.jquery.com/jquery-1.10.2.min.js"></script>
|
||||
<script src="/socket.io/socket.io.js"></script>
|
||||
<script src="/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,266 +0,0 @@
|
||||
$(function() {
|
||||
var FADE_TIME = 150; // ms
|
||||
var TYPING_TIMER_LENGTH = 400; // ms
|
||||
var COLORS = [
|
||||
'#e21400', '#91580f', '#f8a700', '#f78b00',
|
||||
'#58dc00', '#287b00', '#a8f07a', '#4ae8c4',
|
||||
'#3b88eb', '#3824aa', '#a700ff', '#d300e7'
|
||||
];
|
||||
|
||||
// Initialize varibles
|
||||
var $window = $(window);
|
||||
var $usernameInput = $('.usernameInput'); // Input for username
|
||||
var $messages = $('.messages'); // Messages area
|
||||
var $inputMessage = $('.inputMessage'); // Input message input box
|
||||
|
||||
var $loginPage = $('.login.page'); // The login page
|
||||
var $chatPage = $('.chat.page'); // The chatroom page
|
||||
|
||||
// Prompt for setting a username
|
||||
var username;
|
||||
var connected = false;
|
||||
var typing = false;
|
||||
var lastTypingTime;
|
||||
var $currentInput = $usernameInput.focus();
|
||||
|
||||
var socket = io();
|
||||
|
||||
function addParticipantsMessage (data) {
|
||||
var message = '';
|
||||
if (data.numUsers === 1) {
|
||||
message += "there's 1 participant";
|
||||
} else {
|
||||
message += "there are " + data.numUsers + " participants";
|
||||
}
|
||||
log(message);
|
||||
}
|
||||
|
||||
// Sets the client's username
|
||||
function setUsername () {
|
||||
username = cleanInput($usernameInput.val().trim());
|
||||
|
||||
// If the username is valid
|
||||
if (username) {
|
||||
$loginPage.fadeOut();
|
||||
$chatPage.show();
|
||||
$loginPage.off('click');
|
||||
$currentInput = $inputMessage.focus();
|
||||
|
||||
// Tell the server your username
|
||||
socket.emit('add user', username);
|
||||
}
|
||||
}
|
||||
|
||||
// Sends a chat message
|
||||
function sendMessage () {
|
||||
var message = $inputMessage.val();
|
||||
// Prevent markup from being injected into the message
|
||||
message = cleanInput(message);
|
||||
// if there is a non-empty message and a socket connection
|
||||
if (message && connected) {
|
||||
$inputMessage.val('');
|
||||
addChatMessage({
|
||||
username: username,
|
||||
message: message
|
||||
});
|
||||
// tell server to execute 'new message' and send along one parameter
|
||||
socket.emit('new message', message);
|
||||
}
|
||||
}
|
||||
|
||||
// Log a message
|
||||
function log (message, options) {
|
||||
var $el = $('<li>').addClass('log').text(message);
|
||||
addMessageElement($el, options);
|
||||
}
|
||||
|
||||
// Adds the visual chat message to the message list
|
||||
function addChatMessage (data, options) {
|
||||
// Don't fade the message in if there is an 'X was typing'
|
||||
var $typingMessages = getTypingMessages(data);
|
||||
options = options || {};
|
||||
if ($typingMessages.length !== 0) {
|
||||
options.fade = false;
|
||||
$typingMessages.remove();
|
||||
}
|
||||
|
||||
var $usernameDiv = $('<span class="username"/>')
|
||||
.text(data.username)
|
||||
.css('color', getUsernameColor(data.username));
|
||||
var $messageBodyDiv = $('<span class="messageBody">')
|
||||
.text(data.message);
|
||||
|
||||
var typingClass = data.typing ? 'typing' : '';
|
||||
var $messageDiv = $('<li class="message"/>')
|
||||
.data('username', data.username)
|
||||
.addClass(typingClass)
|
||||
.append($usernameDiv, $messageBodyDiv);
|
||||
|
||||
addMessageElement($messageDiv, options);
|
||||
}
|
||||
|
||||
// Adds the visual chat typing message
|
||||
function addChatTyping (data) {
|
||||
data.typing = true;
|
||||
data.message = 'is typing';
|
||||
addChatMessage(data);
|
||||
}
|
||||
|
||||
// Removes the visual chat typing message
|
||||
function removeChatTyping (data) {
|
||||
getTypingMessages(data).fadeOut(function () {
|
||||
$(this).remove();
|
||||
});
|
||||
}
|
||||
|
||||
// Adds a message element to the messages and scrolls to the bottom
|
||||
// el - The element to add as a message
|
||||
// options.fade - If the element should fade-in (default = true)
|
||||
// options.prepend - If the element should prepend
|
||||
// all other messages (default = false)
|
||||
function addMessageElement (el, options) {
|
||||
var $el = $(el);
|
||||
|
||||
// Setup default options
|
||||
if (!options) {
|
||||
options = {};
|
||||
}
|
||||
if (typeof options.fade === 'undefined') {
|
||||
options.fade = true;
|
||||
}
|
||||
if (typeof options.prepend === 'undefined') {
|
||||
options.prepend = false;
|
||||
}
|
||||
|
||||
// Apply options
|
||||
if (options.fade) {
|
||||
$el.hide().fadeIn(FADE_TIME);
|
||||
}
|
||||
if (options.prepend) {
|
||||
$messages.prepend($el);
|
||||
} else {
|
||||
$messages.append($el);
|
||||
}
|
||||
$messages[0].scrollTop = $messages[0].scrollHeight;
|
||||
}
|
||||
|
||||
// Prevents input from having injected markup
|
||||
function cleanInput (input) {
|
||||
return $('<div/>').text(input).text();
|
||||
}
|
||||
|
||||
// Updates the typing event
|
||||
function updateTyping () {
|
||||
if (connected) {
|
||||
if (!typing) {
|
||||
typing = true;
|
||||
socket.emit('typing');
|
||||
}
|
||||
lastTypingTime = (new Date()).getTime();
|
||||
|
||||
setTimeout(function () {
|
||||
var typingTimer = (new Date()).getTime();
|
||||
var timeDiff = typingTimer - lastTypingTime;
|
||||
if (timeDiff >= TYPING_TIMER_LENGTH && typing) {
|
||||
socket.emit('stop typing');
|
||||
typing = false;
|
||||
}
|
||||
}, TYPING_TIMER_LENGTH);
|
||||
}
|
||||
}
|
||||
|
||||
// Gets the 'X is typing' messages of a user
|
||||
function getTypingMessages (data) {
|
||||
return $('.typing.message').filter(function (i) {
|
||||
return $(this).data('username') === data.username;
|
||||
});
|
||||
}
|
||||
|
||||
// Gets the color of a username through our hash function
|
||||
function getUsernameColor (username) {
|
||||
// Compute hash code
|
||||
var hash = 7;
|
||||
for (var i = 0; i < username.length; i++) {
|
||||
hash = username.charCodeAt(i) + (hash << 5) - hash;
|
||||
}
|
||||
// Calculate color
|
||||
var index = Math.abs(hash % COLORS.length);
|
||||
return COLORS[index];
|
||||
}
|
||||
|
||||
// Keyboard events
|
||||
|
||||
$window.keydown(function (event) {
|
||||
// Auto-focus the current input when a key is typed
|
||||
if (!(event.ctrlKey || event.metaKey || event.altKey)) {
|
||||
$currentInput.focus();
|
||||
}
|
||||
// When the client hits ENTER on their keyboard
|
||||
if (event.which === 13) {
|
||||
if (username) {
|
||||
sendMessage();
|
||||
socket.emit('stop typing');
|
||||
typing = false;
|
||||
} else {
|
||||
setUsername();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$inputMessage.on('input', function() {
|
||||
updateTyping();
|
||||
});
|
||||
|
||||
// Click events
|
||||
|
||||
// Focus input when clicking anywhere on login page
|
||||
$loginPage.click(function () {
|
||||
$currentInput.focus();
|
||||
});
|
||||
|
||||
// Focus input when clicking on the message input's border
|
||||
$inputMessage.click(function () {
|
||||
$inputMessage.focus();
|
||||
});
|
||||
|
||||
// Socket events
|
||||
|
||||
// Whenever the server emits 'login', log the login message
|
||||
socket.on('login', function (data) {
|
||||
connected = true;
|
||||
// Display the welcome message
|
||||
var message = "Welcome to Socket.IO Chat – ";
|
||||
log(message, {
|
||||
prepend: true
|
||||
});
|
||||
addParticipantsMessage(data);
|
||||
});
|
||||
|
||||
// Whenever the server emits 'new message', update the chat body
|
||||
socket.on('new message', function (data) {
|
||||
addChatMessage(data);
|
||||
});
|
||||
|
||||
// Whenever the server emits 'user joined', log it in the chat body
|
||||
socket.on('user joined', function (data) {
|
||||
log(data.username + ' joined');
|
||||
addParticipantsMessage(data);
|
||||
});
|
||||
|
||||
// Whenever the server emits 'user left', log it in the chat body
|
||||
socket.on('user left', function (data) {
|
||||
log(data.username + ' left');
|
||||
addParticipantsMessage(data);
|
||||
removeChatTyping(data);
|
||||
});
|
||||
|
||||
// Whenever the server emits 'typing', show the typing message
|
||||
socket.on('typing', function (data) {
|
||||
addChatTyping(data);
|
||||
});
|
||||
|
||||
// Whenever the server emits 'stop typing', kill the typing message
|
||||
socket.on('stop typing', function (data) {
|
||||
removeChatTyping(data);
|
||||
});
|
||||
});
|
||||
@@ -1,150 +0,0 @@
|
||||
/* Fix user-agent */
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html {
|
||||
font-weight: 300;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
html, input {
|
||||
font-family:
|
||||
"HelveticaNeue-Light",
|
||||
"Helvetica Neue Light",
|
||||
"Helvetica Neue",
|
||||
Helvetica,
|
||||
Arial,
|
||||
"Lucida Grande",
|
||||
sans-serif;
|
||||
}
|
||||
|
||||
html, body {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
/* Pages */
|
||||
|
||||
.pages {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.page {
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* Login Page */
|
||||
|
||||
.login.page {
|
||||
background-color: #000;
|
||||
}
|
||||
|
||||
.login.page .form {
|
||||
height: 100px;
|
||||
margin-top: -100px;
|
||||
position: absolute;
|
||||
|
||||
text-align: center;
|
||||
top: 50%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.login.page .form .usernameInput {
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
border-bottom: 2px solid #fff;
|
||||
outline: none;
|
||||
padding-bottom: 15px;
|
||||
text-align: center;
|
||||
width: 400px;
|
||||
}
|
||||
|
||||
.login.page .title {
|
||||
font-size: 200%;
|
||||
}
|
||||
|
||||
.login.page .usernameInput {
|
||||
font-size: 200%;
|
||||
letter-spacing: 3px;
|
||||
}
|
||||
|
||||
.login.page .title, .login.page .usernameInput {
|
||||
color: #fff;
|
||||
font-weight: 100;
|
||||
}
|
||||
|
||||
/* Chat page */
|
||||
|
||||
.chat.page {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Font */
|
||||
|
||||
.messages {
|
||||
font-size: 150%;
|
||||
}
|
||||
|
||||
.inputMessage {
|
||||
font-size: 100%;
|
||||
}
|
||||
|
||||
.log {
|
||||
color: gray;
|
||||
font-size: 70%;
|
||||
margin: 5px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* Messages */
|
||||
|
||||
.chatArea {
|
||||
height: 100%;
|
||||
padding-bottom: 60px;
|
||||
}
|
||||
|
||||
.messages {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
overflow-y: scroll;
|
||||
padding: 10px 20px 10px 20px;
|
||||
}
|
||||
|
||||
.message.typing .messageBody {
|
||||
color: gray;
|
||||
}
|
||||
|
||||
.username {
|
||||
float: left;
|
||||
font-weight: 700;
|
||||
overflow: hidden;
|
||||
padding-right: 15px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
/* Input */
|
||||
|
||||
.inputMessage {
|
||||
border: 10px solid #000;
|
||||
bottom: 0;
|
||||
height: 60px;
|
||||
left: 0;
|
||||
outline: none;
|
||||
padding-left: 10px;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
width: 100%;
|
||||
}
|
||||
96
examples/chat/public/stylesheets/mixins.styl
Normal file
96
examples/chat/public/stylesheets/mixins.styl
Normal file
@@ -0,0 +1,96 @@
|
||||
border-radius(n)
|
||||
-webkit-border-radius n
|
||||
-moz-border-radius n
|
||||
border-radius n
|
||||
|
||||
// replace str with val
|
||||
|
||||
replace(expr, str, val)
|
||||
expr = clone(expr)
|
||||
for e, i in expr
|
||||
if str == e
|
||||
expr[i] = val
|
||||
expr
|
||||
|
||||
// normalize gradient point (webkit)
|
||||
|
||||
grad-point(pos)
|
||||
if length(pos) == 1
|
||||
return left pos if pos in (top bottom)
|
||||
return pos top if pos in (left right)
|
||||
else if pos[0] in (top bottom)
|
||||
pos[1] pos[0]
|
||||
else
|
||||
pos
|
||||
|
||||
// implicit color stop position
|
||||
|
||||
pos-in-stops(i, stops)
|
||||
len = length(stops)
|
||||
if len - 1 == i
|
||||
100%
|
||||
else if i
|
||||
unit(i / len * 100, '%')
|
||||
else
|
||||
0%
|
||||
|
||||
// normalize color stops
|
||||
// - (color pos) -> (pos color)
|
||||
// - (color) -> (implied-pos color)
|
||||
|
||||
normalize-stops(stops)
|
||||
stops = clone(stops)
|
||||
for stop, i in stops
|
||||
if length(stop) == 1
|
||||
color = stop[0]
|
||||
stop[0] = pos-in-stops(i, stops)
|
||||
stop[1] = color
|
||||
else if typeof(stop[1]) == 'unit'
|
||||
pos = stop[1]
|
||||
stop[1] = stop[0]
|
||||
stop[0] = pos
|
||||
stops
|
||||
|
||||
// join color stops with the given translation function
|
||||
|
||||
join-stops(stops, translate)
|
||||
str = ''
|
||||
len = length(stops)
|
||||
for stop, i in stops
|
||||
str += ', ' if i
|
||||
pos = stop[0]
|
||||
color = stop[1]
|
||||
str += translate(color, pos)
|
||||
unquote(str)
|
||||
|
||||
// webkit translation function
|
||||
|
||||
webkit-stop(color, pos)
|
||||
s('color-stop(%d, %s)', pos / 100, color)
|
||||
|
||||
// mozilla translation function
|
||||
|
||||
moz-stop(color, pos)
|
||||
s('%s %s', color, pos)
|
||||
|
||||
// create a linear gradient with the given start
|
||||
// position, followed by color stops
|
||||
|
||||
linear-gradient(start, stops...)
|
||||
error('color stops required') unless length(stops)
|
||||
prop = current-property[0]
|
||||
val = current-property[1]
|
||||
stops = normalize-stops(stops)
|
||||
|
||||
// webkit
|
||||
end = grad-point(opposite-position(start))
|
||||
webkit = s('-webkit-gradient(linear, %s, %s, %s)', grad-point(start), end, join-stops(stops, webkit-stop))
|
||||
add-property(prop, replace(val, '__CALL__', webkit))
|
||||
|
||||
// moz
|
||||
stops = join-stops(stops, moz-stop)
|
||||
moz = s('-moz-linear-gradient(%s, %s)', start, stops)
|
||||
add-property(prop, replace(val, '__CALL__', moz))
|
||||
|
||||
// literal
|
||||
s('linear-gradient(%s, %s)', start, stops)
|
||||
188
examples/chat/public/stylesheets/style.css
Normal file
188
examples/chat/public/stylesheets/style.css
Normal file
@@ -0,0 +1,188 @@
|
||||
#chat,
|
||||
#nickname,
|
||||
#messages {
|
||||
width: 600px;
|
||||
}
|
||||
#chat {
|
||||
position: relative;
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
#nickname,
|
||||
#connecting {
|
||||
position: absolute;
|
||||
height: 410px;
|
||||
z-index: 100;
|
||||
left: 0;
|
||||
top: 0;
|
||||
background: #fff;
|
||||
text-align: center;
|
||||
width: 600px;
|
||||
font: 15px Georgia;
|
||||
color: #666;
|
||||
display: block;
|
||||
}
|
||||
#nickname .wrap,
|
||||
#connecting .wrap {
|
||||
padding-top: 150px;
|
||||
}
|
||||
#nickname input {
|
||||
border: 1px solid #ccc;
|
||||
padding: 10px;
|
||||
}
|
||||
#nickname input:focus {
|
||||
border-color: #999;
|
||||
outline: 0;
|
||||
}
|
||||
#nickname #nickname-err {
|
||||
color: #8b0000;
|
||||
font-size: 12px;
|
||||
visibility: hidden;
|
||||
}
|
||||
.connected #connecting {
|
||||
display: none;
|
||||
}
|
||||
.nickname-set #nickname {
|
||||
display: none;
|
||||
}
|
||||
#messages {
|
||||
height: 380px;
|
||||
background: #eee;
|
||||
}
|
||||
#messages em {
|
||||
text-shadow: 0 1px 0 #fff;
|
||||
color: #999;
|
||||
}
|
||||
#messages p {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
font: 12px Helvetica, Arial;
|
||||
padding: 5px 10px;
|
||||
}
|
||||
#messages p b {
|
||||
display: inline-block;
|
||||
padding-right: 10px;
|
||||
}
|
||||
#messages p:nth-child(even) {
|
||||
background: #fafafa;
|
||||
}
|
||||
#messages #nicknames {
|
||||
background: #ccc;
|
||||
padding: 2px 4px 4px;
|
||||
font: 11px Helvetica;
|
||||
}
|
||||
#messages #nicknames span {
|
||||
color: #000;
|
||||
}
|
||||
#messages #nicknames b {
|
||||
display: inline-block;
|
||||
color: #fff;
|
||||
background: #999;
|
||||
padding: 3px 6px;
|
||||
margin-right: 5px;
|
||||
-webkit-border-radius: 10px;
|
||||
-moz-border-radius: 10px;
|
||||
border-radius: 10px;
|
||||
text-shadow: 0 1px 0 #666;
|
||||
}
|
||||
#messages #lines {
|
||||
height: 355px;
|
||||
overflow: auto;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
}
|
||||
#messages #lines::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
}
|
||||
#messages #lines::-webkit-scrollbar-button:start:decrement,
|
||||
#messages #lines ::-webkit-scrollbar-button:end:increment {
|
||||
display: block;
|
||||
height: 10px;
|
||||
}
|
||||
#messages #lines::-webkit-scrollbar-button:vertical:increment {
|
||||
background-color: #fff;
|
||||
}
|
||||
#messages #lines::-webkit-scrollbar-track-piece {
|
||||
background-color: #fff;
|
||||
-webkit-border-radius: 3px;
|
||||
}
|
||||
#messages #lines::-webkit-scrollbar-thumb:vertical {
|
||||
height: 50px;
|
||||
background-color: #ccc;
|
||||
-webkit-border-radius: 3px;
|
||||
}
|
||||
#messages #lines::-webkit-scrollbar-thumb:horizontal {
|
||||
width: 50px;
|
||||
background-color: #fff;
|
||||
-webkit-border-radius: 3px;
|
||||
}
|
||||
#send-message {
|
||||
background: #fff;
|
||||
position: relative;
|
||||
}
|
||||
#send-message input {
|
||||
border: none;
|
||||
height: 30px;
|
||||
padding: 0 10px;
|
||||
line-height: 30px;
|
||||
vertical-align: middle;
|
||||
width: 580px;
|
||||
}
|
||||
#send-message input:focus {
|
||||
outline: 0;
|
||||
}
|
||||
#send-message button {
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
right: 5px;
|
||||
}
|
||||
button {
|
||||
margin: 0;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
user-select: none;
|
||||
display: inline-block;
|
||||
text-decoration: none;
|
||||
background: #43a1f7;
|
||||
background: -webkit-gradient(linear, left top, left bottom, color-stop(0, #43a1f7), color-stop(1, #377ad0));
|
||||
background: -webkit-linear-gradient(top, #43a1f7 0%, #377ad0 100%);
|
||||
background: -moz-linear-gradient(top, #43a1f7 0%, #377ad0 100%);
|
||||
background: linear-gradient(top, #43a1f7 0%, #377ad0 100%);
|
||||
border: 1px solid #2e70c4;
|
||||
-webkit-border-radius: 16px;
|
||||
-moz-border-radius: 16px;
|
||||
border-radius: 16px;
|
||||
color: #fff;
|
||||
font-family: "lucida grande", sans-serif;
|
||||
font-size: 11px;
|
||||
font-weight: normal;
|
||||
line-height: 1;
|
||||
padding: 3px 10px 5px 10px;
|
||||
text-align: center;
|
||||
text-shadow: 0 -1px 1px #2d6dc0;
|
||||
}
|
||||
button:hover,
|
||||
button.hover {
|
||||
background: darker;
|
||||
background: -webkit-gradient(linear, left top, left bottom, color-stop(0, #43a1f7), color-stop(1, #2e70c4));
|
||||
background: -webkit-linear-gradient(top, #43a1f7 0%, #2e70c4 100%);
|
||||
background: -moz-linear-gradient(top, #43a1f7 0%, #2e70c4 100%);
|
||||
background: linear-gradient(top, #43a1f7 0%, #2e70c4 100%);
|
||||
border: 1px solid #2e70c4;
|
||||
cursor: pointer;
|
||||
text-shadow: 0 -1px 1px #2c6bbb;
|
||||
}
|
||||
button:active,
|
||||
button.active {
|
||||
background: #2e70c4;
|
||||
border: 1px solid #2e70c4;
|
||||
border-bottom: 1px solid #2861aa;
|
||||
text-shadow: 0 -1px 1px #2b67b5;
|
||||
}
|
||||
button:focus,
|
||||
button.focus {
|
||||
outline: none;
|
||||
-webkit-box-shadow: 0 1px 0 0 rgba(255,255,255,0.4), 0 0 4px 0 #377ad0;
|
||||
-moz-box-shadow: 0 1px 0 0 rgba(255,255,255,0.4), 0 0 4px 0 #377ad0;
|
||||
box-shadow: 0 1px 0 0 rgba(255,255,255,0.4), 0 0 4px 0 #377ad0;
|
||||
}
|
||||
118
examples/chat/public/stylesheets/style.styl
Normal file
118
examples/chat/public/stylesheets/style.styl
Normal file
@@ -0,0 +1,118 @@
|
||||
@import 'nib'
|
||||
|
||||
#chat, #nickname, #messages
|
||||
width 600px
|
||||
|
||||
#chat
|
||||
position relative
|
||||
border 1px solid #ccc
|
||||
|
||||
#nickname, #connecting
|
||||
position absolute
|
||||
height 410px
|
||||
z-index 100
|
||||
left 0
|
||||
top 0
|
||||
background #fff
|
||||
text-align center
|
||||
width 600px
|
||||
font 15px Georgia
|
||||
color #666
|
||||
display block
|
||||
.wrap
|
||||
padding-top 150px
|
||||
|
||||
#nickname
|
||||
input
|
||||
border 1px solid #ccc
|
||||
padding 10px
|
||||
&:focus
|
||||
border-color #999
|
||||
outline 0
|
||||
#nickname-err
|
||||
color darkred
|
||||
font-size 12px
|
||||
visibility hidden
|
||||
|
||||
.connected
|
||||
#connecting
|
||||
display none
|
||||
|
||||
.nickname-set
|
||||
#nickname
|
||||
display none
|
||||
|
||||
#messages
|
||||
height 380px
|
||||
background #eee
|
||||
em
|
||||
text-shadow 0 1px 0 #fff
|
||||
color #999
|
||||
p
|
||||
padding 0
|
||||
margin 0
|
||||
font 12px Helvetica, Arial
|
||||
padding 5px 10px
|
||||
b
|
||||
display inline-block
|
||||
padding-right 10px
|
||||
p:nth-child(even)
|
||||
background #fafafa
|
||||
#nicknames
|
||||
background #ccc
|
||||
padding 2px 4px 4px
|
||||
font 11px Helvetica
|
||||
span
|
||||
color black
|
||||
b
|
||||
display inline-block
|
||||
color #fff
|
||||
background #999
|
||||
padding 3px 6px
|
||||
margin-right 5px
|
||||
border-radius 10px
|
||||
text-shadow 0 1px 0 #666
|
||||
#lines
|
||||
height 355px
|
||||
overflow auto
|
||||
overflow-x hidden
|
||||
overflow-y auto
|
||||
&::-webkit-scrollbar
|
||||
width 6px
|
||||
height 6px
|
||||
&::-webkit-scrollbar-button:start:decrement, ::-webkit-scrollbar-button:end:increment
|
||||
display block
|
||||
height 10px
|
||||
&::-webkit-scrollbar-button:vertical:increment
|
||||
background-color #fff
|
||||
&::-webkit-scrollbar-track-piece
|
||||
background-color #fff
|
||||
-webkit-border-radius 3px
|
||||
&::-webkit-scrollbar-thumb:vertical
|
||||
height 50px
|
||||
background-color #ccc
|
||||
-webkit-border-radius 3px
|
||||
&::-webkit-scrollbar-thumb:horizontal
|
||||
width 50px
|
||||
background-color #fff
|
||||
-webkit-border-radius 3px
|
||||
|
||||
#send-message
|
||||
background #fff
|
||||
position relative
|
||||
input
|
||||
border none
|
||||
height 30px
|
||||
padding 0 10px
|
||||
line-height 30px
|
||||
vertical-align middle
|
||||
width 580px
|
||||
&:focus
|
||||
outline 0
|
||||
button
|
||||
position absolute
|
||||
top 5px
|
||||
right 5px
|
||||
|
||||
button
|
||||
download-button()
|
||||
74
examples/irc-output/app.js
Normal file
74
examples/irc-output/app.js
Normal file
@@ -0,0 +1,74 @@
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var express = require('express')
|
||||
, stylus = require('stylus')
|
||||
, nib = require('nib')
|
||||
, sio = require('../../lib/socket.io')
|
||||
, irc = require('./irc');
|
||||
|
||||
/**
|
||||
* App.
|
||||
*/
|
||||
|
||||
var app = express.createServer();
|
||||
|
||||
/**
|
||||
* App configuration.
|
||||
*/
|
||||
|
||||
app.configure(function () {
|
||||
app.use(stylus.middleware({ src: __dirname + '/public', compile: compile }))
|
||||
app.use(express.static(__dirname + '/public'));
|
||||
app.set('views', __dirname);
|
||||
app.set('view engine', 'jade');
|
||||
|
||||
function compile (str, path) {
|
||||
return stylus(str)
|
||||
.set('filename', path)
|
||||
.use(nib());
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* App routes.
|
||||
*/
|
||||
|
||||
app.get('/', function (req, res) {
|
||||
res.render('index', { layout: false });
|
||||
});
|
||||
|
||||
/**
|
||||
* App listen.
|
||||
*/
|
||||
|
||||
app.listen(3000, function () {
|
||||
var addr = app.address();
|
||||
console.log(' app listening on http://' + addr.address + ':' + addr.port);
|
||||
});
|
||||
|
||||
/**
|
||||
* Socket.IO server
|
||||
*/
|
||||
|
||||
var io = sio(app)
|
||||
|
||||
/**
|
||||
* Connect to IRC.
|
||||
*/
|
||||
|
||||
var client = new irc.Client('irc.freenode.net', 6667);
|
||||
client.connect('socketio\\test\\' + String(Math.random()).substr(-3));
|
||||
client.on('001', function () {
|
||||
this.send('JOIN', '#node.js');
|
||||
});
|
||||
client.on('PART', function (prefix) {
|
||||
io.emit('announcement', irc.user(prefix) + ' left the channel');
|
||||
});
|
||||
client.on('JOIN', function (prefix) {
|
||||
io.emit('announcement', irc.user(prefix) + ' joined the channel');
|
||||
});
|
||||
client.on('PRIVMSG', function (prefix, channel, text) {
|
||||
io.emit('irc message', irc.user(prefix), text);
|
||||
});
|
||||
28
examples/irc-output/index.jade
Normal file
28
examples/irc-output/index.jade
Normal file
@@ -0,0 +1,28 @@
|
||||
doctype 5
|
||||
html
|
||||
head
|
||||
link(href='/stylesheets/style.css', rel='stylesheet')
|
||||
script(src='http://code.jquery.com/jquery-1.6.1.min.js')
|
||||
script(src='/socket.io/socket.io.js')
|
||||
script
|
||||
var socket = io.connect();
|
||||
|
||||
socket.on('connect', function () {
|
||||
$('#irc').addClass('connected');
|
||||
});
|
||||
|
||||
socket.on('announcement', function (msg) {
|
||||
$('#messages').append($('<p>').append($('<em>').text(msg)));
|
||||
$('#messages').get(0).scrollTop = 10000000;
|
||||
});
|
||||
|
||||
socket.on('irc message', function (user, msg) {
|
||||
$('#messages').append($('<p>').append($('<b>').text(user), msg));
|
||||
$('#messages').get(0).scrollTop = 10000000;
|
||||
});
|
||||
body
|
||||
h2 Node.JS IRC
|
||||
#irc
|
||||
#connecting
|
||||
.wrap Connecting to socket.io server
|
||||
#messages
|
||||
164
examples/irc-output/irc.js
Normal file
164
examples/irc-output/irc.js
Normal file
@@ -0,0 +1,164 @@
|
||||
/**
|
||||
* From https://github.com/felixge/nodelog/
|
||||
*/
|
||||
|
||||
var sys = require('util');
|
||||
var tcp = require('net');
|
||||
var irc = exports;
|
||||
|
||||
function bind(fn, scope) {
|
||||
var bindArgs = Array.prototype.slice.call(arguments);
|
||||
bindArgs.shift();
|
||||
bindArgs.shift();
|
||||
|
||||
return function() {
|
||||
var args = Array.prototype.slice.call(arguments);
|
||||
fn.apply(scope, bindArgs.concat(args));
|
||||
};
|
||||
}
|
||||
|
||||
function each(set, iterator) {
|
||||
for (var i = 0; i < set.length; i++) {
|
||||
var r = iterator(set[i], i);
|
||||
if (r === false) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var Client = irc.Client = function(host, port) {
|
||||
this.host = host || 'localhost';
|
||||
this.port = port || 6667;
|
||||
|
||||
this.connection = null;
|
||||
this.buffer = '';
|
||||
this.encoding = 'utf8';
|
||||
this.timeout = 10 * 60 * 60 * 1000;
|
||||
|
||||
this.nick = null;
|
||||
this.user = null;
|
||||
this.real = null;
|
||||
}
|
||||
sys.inherits(Client, process.EventEmitter);
|
||||
|
||||
Client.prototype.connect = function(nick, user, real) {
|
||||
var connection = tcp.createConnection(this.port, this.host);
|
||||
connection.setEncoding(this.encoding);
|
||||
connection.setTimeout(this.timeout);
|
||||
connection.addListener('connect', bind(this.onConnect, this));
|
||||
connection.addListener('data', bind(this.onReceive, this));
|
||||
connection.addListener('end', bind(this.onEof, this));
|
||||
connection.addListener('timeout', bind(this.onTimeout, this));
|
||||
connection.addListener('close', bind(this.onClose, this));
|
||||
|
||||
this.nick = nick;
|
||||
this.user = user || 'guest';
|
||||
this.real = real || 'Guest';
|
||||
|
||||
this.connection = connection;
|
||||
};
|
||||
|
||||
Client.prototype.disconnect = function(why) {
|
||||
if (this.connection.readyState !== 'closed') {
|
||||
this.connection.close();
|
||||
sys.puts('disconnected (reason: '+why+')');
|
||||
this.emit('DISCONNECT', why);
|
||||
}
|
||||
};
|
||||
|
||||
Client.prototype.send = function(arg1) {
|
||||
if (this.connection.readyState !== 'open') {
|
||||
return this.disconnect('cannot send with readyState: '+this.connection.readyState);
|
||||
}
|
||||
|
||||
var message = [];
|
||||
for (var i = 0; i< arguments.length; i++) {
|
||||
if (arguments[i]) {
|
||||
message.push(arguments[i]);
|
||||
}
|
||||
}
|
||||
message = message.join(' ');
|
||||
|
||||
sys.puts('> '+message);
|
||||
message = message + "\r\n";
|
||||
this.connection.write(message, this.encoding);
|
||||
};
|
||||
|
||||
Client.prototype.parse = function(message) {
|
||||
var match = message.match(/(?:(:[^\s]+) )?([^\s]+) (.+)/);
|
||||
var parsed = {
|
||||
prefix: match[1],
|
||||
command: match[2]
|
||||
};
|
||||
|
||||
var params = match[3].match(/(.*?) ?:(.*)/);
|
||||
if (params) {
|
||||
// Params before :
|
||||
params[1] = (params[1])
|
||||
? params[1].split(' ')
|
||||
: [];
|
||||
// Rest after :
|
||||
params[2] = params[2]
|
||||
? [params[2]]
|
||||
: [];
|
||||
|
||||
params = params[1].concat(params[2]);
|
||||
} else {
|
||||
params = match[3].split(' ');
|
||||
}
|
||||
|
||||
parsed.params = params;
|
||||
return parsed;
|
||||
};
|
||||
|
||||
Client.prototype.onConnect = function() {
|
||||
this.send('NICK', this.nick);
|
||||
this.send('USER', this.user, '0', '*', ':'+this.real);
|
||||
};
|
||||
|
||||
Client.prototype.onReceive = function(chunk) {
|
||||
this.buffer = this.buffer + chunk;
|
||||
|
||||
while (this.buffer) {
|
||||
var offset = this.buffer.indexOf("\r\n");
|
||||
if (offset < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
var message = this.buffer.substr(0, offset);
|
||||
this.buffer = this.buffer.substr(offset + 2);
|
||||
sys.puts('< '+message);
|
||||
|
||||
message = this.parse(message);
|
||||
|
||||
this.emit.apply(this, [message.command, message.prefix].concat(message.params));
|
||||
|
||||
if (message !== false) {
|
||||
this.onMessage(message);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Client.prototype.onMessage = function(message) {
|
||||
switch (message.command) {
|
||||
case 'PING':
|
||||
this.send('PONG', ':'+message.params[0]);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
Client.prototype.onEof = function() {
|
||||
this.disconnect('eof');
|
||||
};
|
||||
|
||||
Client.prototype.onTimeout = function() {
|
||||
this.disconnect('timeout');
|
||||
};
|
||||
|
||||
Client.prototype.onClose = function() {
|
||||
this.disconnect('close');
|
||||
};
|
||||
|
||||
exports.user = function(prefix) {
|
||||
return prefix.match(/:([^!]+)!/)[1]
|
||||
};
|
||||
10
examples/irc-output/package.json
Normal file
10
examples/irc-output/package.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"name": "socket.io-irc"
|
||||
, "version": "0.0.1"
|
||||
, "dependencies": {
|
||||
"express": "2.5.5"
|
||||
, "jade": "0.16.4"
|
||||
, "stylus": "0.19.0"
|
||||
, "nib": "0.2.0"
|
||||
}
|
||||
}
|
||||
69
examples/irc-output/public/stylesheets/style.styl
Normal file
69
examples/irc-output/public/stylesheets/style.styl
Normal file
@@ -0,0 +1,69 @@
|
||||
@import 'nib'
|
||||
|
||||
h2
|
||||
font bold 18px Helvetica Neue, Arial
|
||||
|
||||
#irc, #messages
|
||||
width 600px
|
||||
|
||||
#irc
|
||||
position relative
|
||||
border 1px solid #ccc
|
||||
|
||||
#connecting
|
||||
position absolute
|
||||
height 410px
|
||||
z-index 100
|
||||
left 0
|
||||
top 0
|
||||
background #fff
|
||||
text-align center
|
||||
width 600px
|
||||
font 15px Georgia
|
||||
color #666
|
||||
display block
|
||||
.wrap
|
||||
padding-top 150px
|
||||
|
||||
.connected
|
||||
#connecting
|
||||
display none
|
||||
|
||||
#messages
|
||||
height 380px
|
||||
background #eee
|
||||
overflow auto
|
||||
overflow-x hidden
|
||||
overflow-y auto
|
||||
&::-webkit-scrollbar
|
||||
width 6px
|
||||
height 6px
|
||||
&::-webkit-scrollbar-button:start:decrement, ::-webkit-scrollbar-button:end:increment
|
||||
display block
|
||||
height 10px
|
||||
&::-webkit-scrollbar-button:vertical:increment
|
||||
background-color #fff
|
||||
&::-webkit-scrollbar-track-piece
|
||||
background-color #fff
|
||||
-webkit-border-radius 3px
|
||||
&::-webkit-scrollbar-thumb:vertical
|
||||
height 50px
|
||||
background-color #ccc
|
||||
-webkit-border-radius 3px
|
||||
&::-webkit-scrollbar-thumb:horizontal
|
||||
width 50px
|
||||
background-color #fff
|
||||
-webkit-border-radius 3px
|
||||
em
|
||||
text-shadow 0 1px 0 #fff
|
||||
color #999
|
||||
p
|
||||
padding 0
|
||||
margin 0
|
||||
font 12px Helvetica, Arial
|
||||
padding 5px 10px
|
||||
b
|
||||
display inline-block
|
||||
padding-right 10px
|
||||
p:nth-child(even)
|
||||
background #fafafa
|
||||
247
lib/client.js
247
lib/client.js
@@ -1,247 +0,0 @@
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var parser = require('socket.io-parser');
|
||||
var debug = require('debug')('socket.io:client');
|
||||
|
||||
/**
|
||||
* Module exports.
|
||||
*/
|
||||
|
||||
module.exports = Client;
|
||||
|
||||
/**
|
||||
* Client constructor.
|
||||
*
|
||||
* @param {Server} server instance
|
||||
* @param {Socket} connection
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function Client(server, conn){
|
||||
this.server = server;
|
||||
this.conn = conn;
|
||||
this.encoder = new parser.Encoder();
|
||||
this.decoder = new parser.Decoder();
|
||||
this.id = conn.id;
|
||||
this.request = conn.request;
|
||||
this.setup();
|
||||
this.sockets = [];
|
||||
this.nsps = {};
|
||||
this.connectBuffer = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up event listeners.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Client.prototype.setup = function(){
|
||||
this.onclose = this.onclose.bind(this);
|
||||
this.ondata = this.ondata.bind(this);
|
||||
this.onerror = this.onerror.bind(this);
|
||||
this.ondecoded = this.ondecoded.bind(this);
|
||||
|
||||
this.decoder.on('decoded', this.ondecoded);
|
||||
this.conn.on('data', this.ondata);
|
||||
this.conn.on('error', this.onerror);
|
||||
this.conn.on('close', this.onclose);
|
||||
};
|
||||
|
||||
/**
|
||||
* Connects a client to a namespace.
|
||||
*
|
||||
* @param {String} namespace name
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Client.prototype.connect = function(name){
|
||||
debug('connecting to namespace %s', name);
|
||||
if (!this.server.nsps[name]) {
|
||||
this.packet({ type: parser.ERROR, nsp: name, data : 'Invalid namespace'});
|
||||
return;
|
||||
}
|
||||
var nsp = this.server.of(name);
|
||||
if ('/' != name && !this.nsps['/']) {
|
||||
this.connectBuffer.push(name);
|
||||
return;
|
||||
}
|
||||
|
||||
var self = this;
|
||||
var socket = nsp.add(this, function(){
|
||||
self.sockets.push(socket);
|
||||
self.nsps[nsp.name] = socket;
|
||||
|
||||
if ('/' == nsp.name && self.connectBuffer.length > 0) {
|
||||
self.connectBuffer.forEach(self.connect, self);
|
||||
self.connectBuffer = [];
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Disconnects from all namespaces and closes transport.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Client.prototype.disconnect = function(){
|
||||
var socket;
|
||||
// we don't use a for loop because the length of
|
||||
// `sockets` changes upon each iteration
|
||||
while (socket = this.sockets.shift()) {
|
||||
socket.disconnect();
|
||||
}
|
||||
this.close();
|
||||
};
|
||||
|
||||
/**
|
||||
* Removes a socket. Called by each `Socket`.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Client.prototype.remove = function(socket){
|
||||
var i = this.sockets.indexOf(socket);
|
||||
if (~i) {
|
||||
var nsp = this.sockets[i].nsp.name;
|
||||
this.sockets.splice(i, 1);
|
||||
delete this.nsps[nsp];
|
||||
} else {
|
||||
debug('ignoring remove for %s', socket.id);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Closes the underlying connection.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Client.prototype.close = function(){
|
||||
if ('open' == this.conn.readyState) {
|
||||
debug('forcing transport close');
|
||||
this.conn.close();
|
||||
this.onclose('forced server close');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Writes a packet to the transport.
|
||||
*
|
||||
* @param {Object} packet object
|
||||
* @param {Boolean} whether packet is already encoded
|
||||
* @param {Boolean} whether packet is volatile
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Client.prototype.packet = function(packet, preEncoded, volatile){
|
||||
var self = this;
|
||||
|
||||
// this writes to the actual connection
|
||||
function writeToEngine(encodedPackets) {
|
||||
if (volatile && !self.conn.transport.writable) return;
|
||||
for (var i = 0; i < encodedPackets.length; i++) {
|
||||
self.conn.write(encodedPackets[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if ('open' == this.conn.readyState) {
|
||||
debug('writing packet %j', packet);
|
||||
if(!preEncoded) { // not broadcasting, need to encode
|
||||
this.encoder.encode(packet, function (encodedPackets) { // encode, then write results to engine
|
||||
writeToEngine(encodedPackets);
|
||||
});
|
||||
} else { // a broadcast pre-encodes a packet
|
||||
writeToEngine(packet);
|
||||
}
|
||||
} else {
|
||||
debug('ignoring packet write %j', packet);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Called with incoming transport data.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Client.prototype.ondata = function(data){
|
||||
// try/catch is needed for protocol violations (GH-1880)
|
||||
try {
|
||||
this.decoder.add(data);
|
||||
} catch(e) {
|
||||
this.onerror(e);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Called when parser fully decodes a packet.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Client.prototype.ondecoded = function(packet) {
|
||||
if (parser.CONNECT == packet.type) {
|
||||
this.connect(packet.nsp);
|
||||
} else {
|
||||
var socket = this.nsps[packet.nsp];
|
||||
if (socket) {
|
||||
socket.onpacket(packet);
|
||||
} else {
|
||||
debug('no socket for namespace %s', packet.nsp);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles an error.
|
||||
*
|
||||
* @param {Objcet} error object
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Client.prototype.onerror = function(err){
|
||||
this.sockets.forEach(function(socket){
|
||||
socket.onerror(err);
|
||||
});
|
||||
this.onclose('client error');
|
||||
};
|
||||
|
||||
/**
|
||||
* Called upon transport close.
|
||||
*
|
||||
* @param {String} reason
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Client.prototype.onclose = function(reason){
|
||||
debug('client close with reason %s', reason);
|
||||
|
||||
// ignore a potential subsequent `close` event
|
||||
this.destroy();
|
||||
|
||||
// `nsps` and `sockets` are cleaned up seamlessly
|
||||
var socket;
|
||||
while (socket = this.sockets.shift()) {
|
||||
socket.onclose(reason);
|
||||
}
|
||||
|
||||
this.decoder.destroy(); // clean up decoder
|
||||
};
|
||||
|
||||
/**
|
||||
* Cleans up event listeners.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Client.prototype.destroy = function(){
|
||||
this.conn.removeListener('data', this.ondata);
|
||||
this.conn.removeListener('error', this.onerror);
|
||||
this.conn.removeListener('close', this.onclose);
|
||||
this.decoder.removeListener('decoded', this.ondecoded);
|
||||
};
|
||||
379
lib/index.js
379
lib/index.js
@@ -1,379 +0,0 @@
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var http = require('http');
|
||||
var read = require('fs').readFileSync;
|
||||
var parse = require('url').parse;
|
||||
var engine = require('engine.io');
|
||||
var client = require('socket.io-client');
|
||||
var clientVersion = require('socket.io-client/package').version;
|
||||
var Client = require('./client');
|
||||
var Namespace = require('./namespace');
|
||||
var Adapter = require('socket.io-adapter');
|
||||
var debug = require('debug')('socket.io:server');
|
||||
var url = require('url');
|
||||
|
||||
/**
|
||||
* Module exports.
|
||||
*/
|
||||
|
||||
module.exports = Server;
|
||||
|
||||
/**
|
||||
* Socket.IO client source.
|
||||
*/
|
||||
|
||||
var clientSource = read(require.resolve('socket.io-client/socket.io.js'), 'utf-8');
|
||||
|
||||
/**
|
||||
* Server constructor.
|
||||
*
|
||||
* @param {http.Server|Number|Object} http server, port or options
|
||||
* @param {Object} options
|
||||
* @api public
|
||||
*/
|
||||
|
||||
function Server(srv, opts){
|
||||
if (!(this instanceof Server)) return new Server(srv, opts);
|
||||
if ('object' == typeof srv && !srv.listen) {
|
||||
opts = srv;
|
||||
srv = null;
|
||||
}
|
||||
opts = opts || {};
|
||||
this.nsps = {};
|
||||
this.path(opts.path || '/socket.io');
|
||||
this.serveClient(false !== opts.serveClient);
|
||||
this.adapter(opts.adapter || Adapter);
|
||||
this.origins(opts.origins || '*:*');
|
||||
this.sockets = this.of('/');
|
||||
if (srv) this.attach(srv, opts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Server request verification function, that checks for allowed origins
|
||||
*
|
||||
* @param {http.IncomingMessage} request
|
||||
* @param {Function} callback to be called with the result: `fn(err, success)`
|
||||
*/
|
||||
|
||||
Server.prototype.checkRequest = function(req, fn) {
|
||||
var origin = req.headers.origin || req.headers.referer;
|
||||
|
||||
// file:// URLs produce a null Origin which can't be authorized via echo-back
|
||||
if ('null' == origin || null == origin) origin = '*';
|
||||
|
||||
if (!!origin && typeof(this._origins) == 'function') return this._origins(origin, fn);
|
||||
if (this._origins.indexOf('*:*') !== -1) return fn(null, true);
|
||||
if (origin) {
|
||||
try {
|
||||
var parts = url.parse(origin);
|
||||
parts.port = parts.port || 80;
|
||||
var ok =
|
||||
~this._origins.indexOf(parts.hostname + ':' + parts.port) ||
|
||||
~this._origins.indexOf(parts.hostname + ':*') ||
|
||||
~this._origins.indexOf('*:' + parts.port);
|
||||
return fn(null, !!ok);
|
||||
} catch (ex) {
|
||||
}
|
||||
}
|
||||
fn(null, false);
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets/gets whether client code is being served.
|
||||
*
|
||||
* @param {Boolean} whether to serve client code
|
||||
* @return {Server|Boolean} self when setting or value when getting
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Server.prototype.serveClient = function(v){
|
||||
if (!arguments.length) return this._serveClient;
|
||||
this._serveClient = v;
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Old settings for backwards compatibility
|
||||
*/
|
||||
|
||||
var oldSettings = {
|
||||
"transports": "transports",
|
||||
"heartbeat timeout": "pingTimeout",
|
||||
"heartbeat interval": "pingInterval",
|
||||
"destroy buffer size": "maxHttpBufferSize"
|
||||
};
|
||||
|
||||
/**
|
||||
* Backwards compatiblity.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Server.prototype.set = function(key, val){
|
||||
if ('authorization' == key && val) {
|
||||
this.use(function(socket, next) {
|
||||
val(socket.request, function(err, authorized) {
|
||||
if (err) return next(new Error(err));
|
||||
if (!authorized) return next(new Error('Not authorized'));
|
||||
next();
|
||||
});
|
||||
});
|
||||
} else if ('origins' == key && val) {
|
||||
this.origins(val);
|
||||
} else if ('resource' == key) {
|
||||
this.path(val);
|
||||
} else if (oldSettings[key] && this.eio[oldSettings[key]]) {
|
||||
this.eio[oldSettings[key]] = val;
|
||||
} else {
|
||||
console.error('Option %s is not valid. Please refer to the README.', key);
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets the client serving path.
|
||||
*
|
||||
* @param {String} pathname
|
||||
* @return {Server|String} self when setting or value when getting
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Server.prototype.path = function(v){
|
||||
if (!arguments.length) return this._path;
|
||||
this._path = v.replace(/\/$/, '');
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets the adapter for rooms.
|
||||
*
|
||||
* @param {Adapter} pathname
|
||||
* @return {Server|Adapter} self when setting or value when getting
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Server.prototype.adapter = function(v){
|
||||
if (!arguments.length) return this._adapter;
|
||||
this._adapter = v;
|
||||
for (var i in this.nsps) {
|
||||
if (this.nsps.hasOwnProperty(i)) {
|
||||
this.nsps[i].initAdapter();
|
||||
}
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets the allowed origins for requests.
|
||||
*
|
||||
* @param {String} origins
|
||||
* @return {Server|Adapter} self when setting or value when getting
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Server.prototype.origins = function(v){
|
||||
if (!arguments.length) return this._origins;
|
||||
|
||||
this._origins = v;
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Attaches socket.io to a server or port.
|
||||
*
|
||||
* @param {http.Server|Number} server or port
|
||||
* @param {Object} options passed to engine.io
|
||||
* @return {Server} self
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Server.prototype.listen =
|
||||
Server.prototype.attach = function(srv, opts){
|
||||
if ('function' == typeof srv) {
|
||||
var msg = 'You are trying to attach socket.io to an express ' +
|
||||
'request handler function. Please pass a http.Server instance.';
|
||||
throw new Error(msg);
|
||||
}
|
||||
|
||||
// handle a port as a string
|
||||
if (Number(srv) == srv) {
|
||||
srv = Number(srv);
|
||||
}
|
||||
|
||||
if ('number' == typeof srv) {
|
||||
debug('creating http server and binding to %d', srv);
|
||||
var port = srv;
|
||||
srv = http.Server(function(req, res){
|
||||
res.writeHead(404);
|
||||
res.end();
|
||||
});
|
||||
srv.listen(port);
|
||||
|
||||
}
|
||||
|
||||
// set engine.io path to `/socket.io`
|
||||
opts = opts || {};
|
||||
opts.path = opts.path || this.path();
|
||||
// set origins verification
|
||||
opts.allowRequest = this.checkRequest.bind(this);
|
||||
|
||||
// initialize engine
|
||||
debug('creating engine.io instance with opts %j', opts);
|
||||
this.eio = engine.attach(srv, opts);
|
||||
|
||||
// attach static file serving
|
||||
if (this._serveClient) this.attachServe(srv);
|
||||
|
||||
// Export http server
|
||||
this.httpServer = srv;
|
||||
|
||||
// bind to engine events
|
||||
this.bind(this.eio);
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Attaches the static file serving.
|
||||
*
|
||||
* @param {Function|http.Server} http server
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Server.prototype.attachServe = function(srv){
|
||||
debug('attaching client serving req handler');
|
||||
var url = this._path + '/socket.io.js';
|
||||
var evs = srv.listeners('request').slice(0);
|
||||
var self = this;
|
||||
srv.removeAllListeners('request');
|
||||
srv.on('request', function(req, res) {
|
||||
if (0 == req.url.indexOf(url)) {
|
||||
self.serve(req, res);
|
||||
} else {
|
||||
for (var i = 0; i < evs.length; i++) {
|
||||
evs[i].call(srv, req, res);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles a request serving `/socket.io.js`
|
||||
*
|
||||
* @param {http.Request} req
|
||||
* @param {http.Response} res
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Server.prototype.serve = function(req, res){
|
||||
var etag = req.headers['if-none-match'];
|
||||
if (etag) {
|
||||
if (clientVersion == etag) {
|
||||
debug('serve client 304');
|
||||
res.writeHead(304);
|
||||
res.end();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
debug('serve client source');
|
||||
res.setHeader('Content-Type', 'application/javascript');
|
||||
res.setHeader('ETag', clientVersion);
|
||||
res.writeHead(200);
|
||||
res.end(clientSource);
|
||||
};
|
||||
|
||||
/**
|
||||
* Binds socket.io to an engine.io instance.
|
||||
*
|
||||
* @param {engine.Server} engine.io (or compatible) server
|
||||
* @return {Server} self
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Server.prototype.bind = function(engine){
|
||||
this.engine = engine;
|
||||
this.engine.on('connection', this.onconnection.bind(this));
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Called with each incoming transport connection.
|
||||
*
|
||||
* @param {engine.Socket} socket
|
||||
* @return {Server} self
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Server.prototype.onconnection = function(conn){
|
||||
debug('incoming connection with id %s', conn.id);
|
||||
var client = new Client(this, conn);
|
||||
client.connect('/');
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Looks up a namespace.
|
||||
*
|
||||
* @param {String} nsp name
|
||||
* @param {Function} optional, nsp `connection` ev handler
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Server.prototype.of = function(name, fn){
|
||||
if (String(name)[0] !== '/') name = '/' + name;
|
||||
|
||||
if (!this.nsps[name]) {
|
||||
debug('initializing namespace %s', name);
|
||||
var nsp = new Namespace(this, name);
|
||||
this.nsps[name] = nsp;
|
||||
}
|
||||
if (fn) this.nsps[name].on('connect', fn);
|
||||
return this.nsps[name];
|
||||
};
|
||||
|
||||
/**
|
||||
* Closes server connection
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Server.prototype.close = function(){
|
||||
this.nsps['/'].sockets.forEach(function(socket){
|
||||
socket.onclose();
|
||||
});
|
||||
|
||||
this.engine.close();
|
||||
|
||||
if(this.httpServer){
|
||||
this.httpServer.close();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Expose main namespace (/).
|
||||
*/
|
||||
|
||||
['on', 'to', 'in', 'use', 'emit', 'send', 'write'].forEach(function(fn){
|
||||
Server.prototype[fn] = function(){
|
||||
var nsp = this.sockets[fn];
|
||||
return nsp.apply(this.sockets, arguments);
|
||||
};
|
||||
});
|
||||
|
||||
Namespace.flags.forEach(function(flag){
|
||||
Server.prototype.__defineGetter__(flag, function(name){
|
||||
this.flags.push(name);
|
||||
return this;
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* BC with `io.listen`
|
||||
*/
|
||||
|
||||
Server.listen = Server;
|
||||
242
lib/namespace.js
242
lib/namespace.js
@@ -1,242 +0,0 @@
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var Socket = require('./socket');
|
||||
var Emitter = require('events').EventEmitter;
|
||||
var parser = require('socket.io-parser');
|
||||
var debug = require('debug')('socket.io:namespace');
|
||||
var hasBin = require('has-binary-data');
|
||||
|
||||
/**
|
||||
* Module exports.
|
||||
*/
|
||||
|
||||
module.exports = exports = Namespace;
|
||||
|
||||
/**
|
||||
* Blacklisted events.
|
||||
*/
|
||||
|
||||
exports.events = [
|
||||
'connect', // for symmetry with client
|
||||
'connection',
|
||||
'newListener'
|
||||
];
|
||||
|
||||
/**
|
||||
* Flags.
|
||||
*/
|
||||
|
||||
exports.flags = ['json'];
|
||||
|
||||
/**
|
||||
* `EventEmitter#emit` reference.
|
||||
*/
|
||||
|
||||
var emit = Emitter.prototype.emit;
|
||||
|
||||
/**
|
||||
* Namespace constructor.
|
||||
*
|
||||
* @param {Server} server instance
|
||||
* @param {Socket} name
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function Namespace(server, name){
|
||||
this.name = name;
|
||||
this.server = server;
|
||||
this.sockets = [];
|
||||
this.connected = {};
|
||||
this.fns = [];
|
||||
this.ids = 0;
|
||||
this.acks = {};
|
||||
this.initAdapter();
|
||||
}
|
||||
|
||||
/**
|
||||
* Inherits from `EventEmitter`.
|
||||
*/
|
||||
|
||||
Namespace.prototype.__proto__ = Emitter.prototype;
|
||||
|
||||
/**
|
||||
* Apply flags from `Socket`.
|
||||
*/
|
||||
|
||||
exports.flags.forEach(function(flag){
|
||||
Namespace.prototype.__defineGetter__(flag, function(){
|
||||
this.flags = this.flags || {};
|
||||
this.flags[flag] = true;
|
||||
return this;
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Initializes the `Adapter` for this nsp.
|
||||
* Run upon changing adapter by `Server#adapter`
|
||||
* in addition to the constructor.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Namespace.prototype.initAdapter = function(){
|
||||
this.adapter = new (this.server.adapter())(this);
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets up namespace middleware.
|
||||
*
|
||||
* @return {Namespace} self
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Namespace.prototype.use = function(fn){
|
||||
this.fns.push(fn);
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Executes the middleware for an incoming client.
|
||||
*
|
||||
* @param {Socket} socket that will get added
|
||||
* @param {Function} last fn call in the middleware
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Namespace.prototype.run = function(socket, fn){
|
||||
var fns = this.fns.slice(0);
|
||||
if (!fns.length) return fn(null);
|
||||
|
||||
function run(i){
|
||||
fns[i](socket, function(err){
|
||||
// upon error, short-circuit
|
||||
if (err) return fn(err);
|
||||
|
||||
// if no middleware left, summon callback
|
||||
if (!fns[i + 1]) return fn(null);
|
||||
|
||||
// go on to next
|
||||
run(i + 1);
|
||||
});
|
||||
}
|
||||
|
||||
run(0);
|
||||
};
|
||||
|
||||
/**
|
||||
* Targets a room when emitting.
|
||||
*
|
||||
* @param {String} name
|
||||
* @return {Namespace} self
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Namespace.prototype.to =
|
||||
Namespace.prototype['in'] = function(name){
|
||||
this.rooms = this.rooms || [];
|
||||
if (!~this.rooms.indexOf(name)) this.rooms.push(name);
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds a new client.
|
||||
*
|
||||
* @return {Socket}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Namespace.prototype.add = function(client, fn){
|
||||
debug('adding socket to nsp %s', this.name);
|
||||
var socket = new Socket(this, client);
|
||||
var self = this;
|
||||
this.run(socket, function(err){
|
||||
process.nextTick(function(){
|
||||
if ('open' == client.conn.readyState) {
|
||||
if (err) return socket.error(err.data || err.message);
|
||||
|
||||
// track socket
|
||||
self.sockets.push(socket);
|
||||
|
||||
// it's paramount that the internal `onconnect` logic
|
||||
// fires before user-set events to prevent state order
|
||||
// violations (such as a disconnection before the connection
|
||||
// logic is complete)
|
||||
socket.onconnect();
|
||||
if (fn) fn();
|
||||
|
||||
// fire user-set events
|
||||
self.emit('connect', socket);
|
||||
self.emit('connection', socket);
|
||||
} else {
|
||||
debug('next called after client was closed - ignoring socket');
|
||||
}
|
||||
});
|
||||
});
|
||||
return socket;
|
||||
};
|
||||
|
||||
/**
|
||||
* Removes a client. Called by each `Socket`.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Namespace.prototype.remove = function(socket){
|
||||
var i = this.sockets.indexOf(socket);
|
||||
if (~i) {
|
||||
this.sockets.splice(i, 1);
|
||||
} else {
|
||||
debug('ignoring remove for %s', socket.id);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Emits to all clients.
|
||||
*
|
||||
* @return {Namespace} self
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Namespace.prototype.emit = function(ev){
|
||||
if (~exports.events.indexOf(ev)) {
|
||||
emit.apply(this, arguments);
|
||||
} else {
|
||||
// set up packet object
|
||||
var args = Array.prototype.slice.call(arguments);
|
||||
var parserType = parser.EVENT; // default
|
||||
if (hasBin(args)) { parserType = parser.BINARY_EVENT; } // binary
|
||||
|
||||
var packet = { type: parserType, data: args };
|
||||
|
||||
if ('function' == typeof args[args.length - 1]) {
|
||||
throw new Error('Callbacks are not supported when broadcasting');
|
||||
}
|
||||
|
||||
this.adapter.broadcast(packet, {
|
||||
rooms: this.rooms,
|
||||
flags: this.flags
|
||||
});
|
||||
|
||||
delete this.rooms;
|
||||
delete this.flags;
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Sends a `message` event to all clients.
|
||||
*
|
||||
* @return {Namespace} self
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Namespace.prototype.send =
|
||||
Namespace.prototype.write = function(){
|
||||
var args = Array.prototype.slice.call(arguments);
|
||||
args.unshift('message');
|
||||
this.emit.apply(this, args);
|
||||
return this;
|
||||
};
|
||||
76
lib/server.js
Normal file
76
lib/server.js
Normal file
@@ -0,0 +1,76 @@
|
||||
|
||||
/**
|
||||
* Socket.IO server.
|
||||
*
|
||||
* @param {Object} options
|
||||
* @api public
|
||||
*/
|
||||
|
||||
function Server (opts) {
|
||||
this.clients = {};
|
||||
this.clientsCount = 0;
|
||||
this.flags = {};
|
||||
|
||||
// legacy
|
||||
this.sockets = this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcast flag.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Server.prototype.__defineGetter__('broadcast', function () {
|
||||
this.flags.broadcast = true;
|
||||
});
|
||||
|
||||
/**
|
||||
* Called with a websocket.io-compatible connection.
|
||||
*
|
||||
* @param {engine.Socket|wsio.Socket} connection
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Server.prototype.onConnection = function (conn) {
|
||||
var socket = new Socket(conn, this)
|
||||
, self = this
|
||||
|
||||
socket.once('ready', function () {
|
||||
self.clients[socket.id] = socket;
|
||||
self.emit('connection', socket):
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets a client.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Server.prototype.socket =
|
||||
Server.prototype.client = function (id) {
|
||||
return this.clients[id];
|
||||
};
|
||||
|
||||
/**
|
||||
* Emits to all clients.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Server.prototype.emit = function () {
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets the room to broadcast to.
|
||||
*
|
||||
* @param {String} room name
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Server.prototype.to =
|
||||
Server.prototype.in = function () {
|
||||
|
||||
};
|
||||
116
lib/socket.io.js
Normal file
116
lib/socket.io.js
Normal file
@@ -0,0 +1,116 @@
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var engine = require('engine.io')
|
||||
, Server = require('http').Server
|
||||
|
||||
/**
|
||||
* Module exports.
|
||||
*/
|
||||
|
||||
module.exports = exports = create;
|
||||
|
||||
/**
|
||||
* Creates a Socket.IO server.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
function create (arg, options, fn) {
|
||||
if ('number' == typeof arg) {
|
||||
return exports.listen(arg, options, fn);
|
||||
} else {
|
||||
return exports.attach(arg, options, fn);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Version
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
exports.version = '1.0.0-alpha1';
|
||||
|
||||
/**
|
||||
* Server constructor.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
exports.Server = Server;
|
||||
|
||||
/**
|
||||
* Listen shortcut.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
exports.create = create;
|
||||
|
||||
/**
|
||||
* Makes socket.io listen on a port.
|
||||
*
|
||||
* @param {Number} port
|
||||
* @param {Object|Function} (optional) options or callback
|
||||
* @param {Function} (optional) callback
|
||||
* @return {Server} io
|
||||
* @api public
|
||||
*/
|
||||
|
||||
exports.listen = function (port, fn, options) {
|
||||
// legacy
|
||||
if (port instanceof Server) return attach(port, fn, opts);
|
||||
|
||||
if ('object' == typeof fn) {
|
||||
options = fn;
|
||||
fn = null;
|
||||
}
|
||||
|
||||
var server = http.createServer(function (req, res) {
|
||||
res.writeHead(501);
|
||||
res.end('Not Implemented');
|
||||
});
|
||||
|
||||
server.listen(port, fn);
|
||||
|
||||
// create socket.io server
|
||||
var io = exports.attach(server, options);
|
||||
|
||||
// keep ref to http server
|
||||
io.httpServer = server;
|
||||
|
||||
return io;
|
||||
};
|
||||
|
||||
/**
|
||||
* Attaches socket.io to a http server.
|
||||
*
|
||||
* @param {http.Server} server
|
||||
* @param {Object} (optional) options
|
||||
* @return {Server} io server
|
||||
* @api public
|
||||
*/
|
||||
|
||||
exports.attach = function (server, options) {
|
||||
var opts = options || {}
|
||||
, engineOpts = opts.engine || {}
|
||||
|
||||
// use default socket.io base path
|
||||
engineOpts.path = engineOpts.path || '/socket.io';
|
||||
|
||||
// spawn engine server
|
||||
var server = engine.attach(server, engineOpts);
|
||||
|
||||
// spawn socket.io
|
||||
var io = new exports.Server(options);
|
||||
|
||||
// capture connections
|
||||
server.on('connection', function (conn) {
|
||||
io.onConnection(conn);
|
||||
});
|
||||
|
||||
return io;
|
||||
};
|
||||
454
lib/socket.js
454
lib/socket.js
@@ -3,447 +3,89 @@
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var Emitter = require('events').EventEmitter;
|
||||
var parser = require('socket.io-parser');
|
||||
var url = require('url');
|
||||
var debug = require('debug')('socket.io:socket');
|
||||
var hasBin = require('has-binary-data');
|
||||
var EventEmitter = require('events').EventEmitter;
|
||||
|
||||
/**
|
||||
* Module exports.
|
||||
*/
|
||||
|
||||
module.exports = exports = Socket;
|
||||
|
||||
/**
|
||||
* Blacklisted events.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
exports.events = [
|
||||
'error',
|
||||
'connect',
|
||||
'disconnect',
|
||||
'newListener',
|
||||
'removeListener'
|
||||
];
|
||||
|
||||
/**
|
||||
* Flags.
|
||||
* Socket.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
var flags = [
|
||||
'json',
|
||||
'volatile',
|
||||
'broadcast'
|
||||
];
|
||||
function Socket (connection, server) {
|
||||
this.connection = connection;
|
||||
this.id = this.sid = connection.id;
|
||||
this.server = server;
|
||||
this.store = this.server.store;
|
||||
|
||||
/**
|
||||
* `EventEmitter#emit` reference.
|
||||
*/
|
||||
// group subscriptions
|
||||
this.subscriptions = [];
|
||||
|
||||
var emit = Emitter.prototype.emit;
|
||||
|
||||
/**
|
||||
* Interface to a `Client` for a given `Namespace`.
|
||||
*
|
||||
* @param {Namespace} nsp
|
||||
* @param {Client} client
|
||||
* @api public
|
||||
*/
|
||||
|
||||
function Socket(nsp, client){
|
||||
this.nsp = nsp;
|
||||
this.server = nsp.server;
|
||||
this.adapter = this.nsp.adapter;
|
||||
this.id = client.id;
|
||||
this.request = client.request;
|
||||
this.client = client;
|
||||
this.conn = client.conn;
|
||||
this.rooms = [];
|
||||
this.acks = {};
|
||||
this.connected = true;
|
||||
this.disconnected = false;
|
||||
this.handshake = this.buildHandshake();
|
||||
// join to group for itself
|
||||
var self = this;
|
||||
this.join(sid, function () {
|
||||
self.emit('ready');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Inherits from `EventEmitter`.
|
||||
* Inherits from EventEmitter.
|
||||
*/
|
||||
|
||||
Socket.prototype.__proto__ = Emitter.prototype;
|
||||
Socket.prototype.__proto__ = EventEmitter.prototype;
|
||||
|
||||
/**
|
||||
* Apply flags from `Socket`.
|
||||
*/
|
||||
|
||||
flags.forEach(function(flag){
|
||||
Socket.prototype.__defineGetter__(flag, function(){
|
||||
this.flags = this.flags || {};
|
||||
this.flags[flag] = true;
|
||||
return this;
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* `request` engine.io shorcut.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Socket.prototype.__defineGetter__('request', function(){
|
||||
return this.conn.request;
|
||||
});
|
||||
|
||||
/**
|
||||
* Builds the `handshake` BC object
|
||||
* Save reference to original `emit`.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Socket.prototype.buildHandshake = function(){
|
||||
return {
|
||||
headers: this.request.headers,
|
||||
time: (new Date) + '',
|
||||
address: this.conn.remoteAddress,
|
||||
xdomain: !!this.request.headers.origin,
|
||||
secure: !!this.request.connection.encrypted,
|
||||
issued: +(new Date),
|
||||
url: this.request.url,
|
||||
query: url.parse(this.request.url, true).query || {}
|
||||
};
|
||||
};
|
||||
Socket.prototype._emit = Socket.prototype.emit;
|
||||
|
||||
/**
|
||||
* Emits to this client.
|
||||
* Joins a group.
|
||||
*
|
||||
* @return {Socket} self
|
||||
* @param {String} group
|
||||
* @return {Socket} for chaining
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Socket.prototype.emit = function(ev){
|
||||
if (~exports.events.indexOf(ev)) {
|
||||
emit.apply(this, arguments);
|
||||
Socket.prototype.join = function (group, fn) {
|
||||
if (!~this.subscriptions.indexOf(group)) {
|
||||
var self = this;
|
||||
this.subscriptions.push(group);
|
||||
this.store.addToGroup(group, this.sid, function (ev, args) {
|
||||
self.onGroupEvent(ev, args);
|
||||
}, fn);
|
||||
} else {
|
||||
var args = Array.prototype.slice.call(arguments);
|
||||
var packet = {};
|
||||
packet.type = hasBin(args) ? parser.BINARY_EVENT : parser.EVENT;
|
||||
packet.data = args;
|
||||
|
||||
// access last argument to see if it's an ACK callback
|
||||
if ('function' == typeof args[args.length - 1]) {
|
||||
if (this._rooms || (this.flags && this.flags.broadcast)) {
|
||||
throw new Error('Callbacks are not supported when broadcasting');
|
||||
}
|
||||
|
||||
debug('emitting packet with ack id %d', this.nsp.ids);
|
||||
this.acks[this.nsp.ids] = args.pop();
|
||||
packet.id = this.nsp.ids++;
|
||||
}
|
||||
|
||||
if (this._rooms || (this.flags && this.flags.broadcast)) {
|
||||
this.adapter.broadcast(packet, {
|
||||
except: [this.id],
|
||||
rooms: this._rooms,
|
||||
flags: this.flags
|
||||
});
|
||||
} else {
|
||||
// dispatch packet
|
||||
this.packet(packet);
|
||||
}
|
||||
|
||||
// reset flags
|
||||
delete this._rooms;
|
||||
delete this.flags;
|
||||
fn && fn();
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Targets a room when broadcasting.
|
||||
* Leaves a group.
|
||||
*
|
||||
* @param {String} name
|
||||
* @return {Socket} self
|
||||
* @return {Socket} for chaining
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Socket.prototype.to =
|
||||
Socket.prototype.in = function(name){
|
||||
this._rooms = this._rooms || [];
|
||||
if (!~this._rooms.indexOf(name)) this._rooms.push(name);
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Sends a `message` event.
|
||||
*
|
||||
* @return {Socket} self
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Socket.prototype.send =
|
||||
Socket.prototype.write = function(){
|
||||
var args = Array.prototype.slice.call(arguments);
|
||||
args.unshift('message');
|
||||
this.emit.apply(this, args);
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Writes a packet.
|
||||
*
|
||||
* @param {Object} packet object
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Socket.prototype.packet = function(packet, preEncoded){
|
||||
packet.nsp = this.nsp.name;
|
||||
var volatile = this.flags && this.flags.volatile;
|
||||
this.client.packet(packet, preEncoded, volatile);
|
||||
};
|
||||
|
||||
/**
|
||||
* Joins a room.
|
||||
*
|
||||
* @param {String} room
|
||||
* @param {Function} optional, callback
|
||||
* @return {Socket} self
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Socket.prototype.join = function(room, fn){
|
||||
debug('joining room %s', room);
|
||||
var self = this;
|
||||
if (~this.rooms.indexOf(room)) return this;
|
||||
this.adapter.add(this.id, room, function(err){
|
||||
if (err) return fn && fn(err);
|
||||
debug('joined room %s', room);
|
||||
self.rooms.push(room);
|
||||
fn && fn(null);
|
||||
});
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Leaves a room.
|
||||
*
|
||||
* @param {String} room
|
||||
* @param {Function} optional, callback
|
||||
* @return {Socket} self
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Socket.prototype.leave = function(room, fn){
|
||||
debug('leave room %s', room);
|
||||
var self = this;
|
||||
this.adapter.del(this.id, room, function(err){
|
||||
if (err) return fn && fn(err);
|
||||
debug('left room %s', room);
|
||||
var idx = self.rooms.indexOf(room);
|
||||
if (idx >= 0) {
|
||||
self.rooms.splice(idx, 1);
|
||||
}
|
||||
fn && fn(null);
|
||||
});
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Leave all rooms.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Socket.prototype.leaveAll = function(){
|
||||
this.adapter.delAll(this.id);
|
||||
this.rooms = [];
|
||||
};
|
||||
|
||||
/**
|
||||
* Called by `Namespace` upon succesful
|
||||
* middleware execution (ie: authorization).
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Socket.prototype.onconnect = function(){
|
||||
debug('socket connected - writing packet');
|
||||
this.join(this.id);
|
||||
this.packet({ type: parser.CONNECT });
|
||||
this.nsp.connected[this.id] = this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Called with each packet. Called by `Client`.
|
||||
*
|
||||
* @param {Object} packet
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Socket.prototype.onpacket = function(packet){
|
||||
debug('got packet %j', packet);
|
||||
switch (packet.type) {
|
||||
case parser.EVENT:
|
||||
this.onevent(packet);
|
||||
break;
|
||||
|
||||
case parser.BINARY_EVENT:
|
||||
this.onevent(packet);
|
||||
break;
|
||||
|
||||
case parser.ACK:
|
||||
this.onack(packet);
|
||||
break;
|
||||
|
||||
case parser.BINARY_ACK:
|
||||
this.onack(packet);
|
||||
break;
|
||||
|
||||
case parser.DISCONNECT:
|
||||
this.ondisconnect();
|
||||
break;
|
||||
|
||||
case parser.ERROR:
|
||||
this.emit('error', packet.data);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Called upon event packet.
|
||||
*
|
||||
* @param {Object} packet object
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Socket.prototype.onevent = function(packet){
|
||||
var args = packet.data || [];
|
||||
debug('emitting event %j', args);
|
||||
|
||||
if (null != packet.id) {
|
||||
debug('attaching ack callback to event');
|
||||
args.push(this.ack(packet.id));
|
||||
}
|
||||
|
||||
emit.apply(this, args);
|
||||
};
|
||||
|
||||
/**
|
||||
* Produces an ack callback to emit with an event.
|
||||
*
|
||||
* @param {Number} packet id
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Socket.prototype.ack = function(id){
|
||||
var self = this;
|
||||
var sent = false;
|
||||
return function(){
|
||||
// prevent double callbacks
|
||||
if (sent) return;
|
||||
var args = Array.prototype.slice.call(arguments);
|
||||
debug('sending ack %j', args);
|
||||
|
||||
var type = hasBin(args) ? parser.BINARY_ACK : parser.ACK;
|
||||
self.packet({
|
||||
id: id,
|
||||
type: type,
|
||||
data: args
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Called upon ack packet.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Socket.prototype.onack = function(packet){
|
||||
var ack = this.acks[packet.id];
|
||||
if ('function' == typeof ack) {
|
||||
debug('calling ack %s with %j', packet.id, packet.data);
|
||||
ack.apply(this, packet.data);
|
||||
delete this.acks[packet.id];
|
||||
} else {
|
||||
debug('bad ack %s', packet.id);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Called upon client disconnect packet.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Socket.prototype.ondisconnect = function(){
|
||||
debug('got disconnect packet');
|
||||
this.onclose('client namespace disconnect');
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles a client error.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Socket.prototype.onerror = function(err){
|
||||
if (this.listeners('error').length) {
|
||||
this.emit('error', err);
|
||||
} else {
|
||||
console.error('Missing error handler on `socket`.');
|
||||
console.error(err.stack);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Called upon closing. Called by `Client`.
|
||||
*
|
||||
* @param {String} reason
|
||||
* @param {Error} optional error object
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Socket.prototype.onclose = function(reason){
|
||||
if (!this.connected) return this;
|
||||
debug('closing socket - reason %s', reason);
|
||||
this.leaveAll();
|
||||
this.nsp.remove(this);
|
||||
this.client.remove(this);
|
||||
this.connected = false;
|
||||
this.disconnected = true;
|
||||
delete this.nsp.connected[this.id];
|
||||
this.emit('disconnect', reason);
|
||||
};
|
||||
|
||||
/**
|
||||
* Produces an `error` packet.
|
||||
*
|
||||
* @param {Object} error object
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Socket.prototype.error = function(err){
|
||||
this.packet({ type: parser.ERROR, data: err });
|
||||
};
|
||||
|
||||
/**
|
||||
* Disconnects this client.
|
||||
*
|
||||
* @param {Boolean} if `true`, closes the underlying connection
|
||||
* @return {Socket} self
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Socket.prototype.disconnect = function(close){
|
||||
if (!this.connected) return this;
|
||||
if (close) {
|
||||
this.client.disconnect();
|
||||
} else {
|
||||
this.packet({ type: parser.DISCONNECT });
|
||||
this.onclose('server namespace disconnect');
|
||||
Socket.prototype.leave = function (group) {
|
||||
var index = this.subscriptions.indexOf(group);
|
||||
if (~index) {
|
||||
this.subscriptions.splice(index, 1);
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Called upon disconnect.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Socket.prototype.onDisconnect = function () {
|
||||
for (var i = 0, l = this.subscriptions; i < l; i++) {
|
||||
this.store.removeFromGroup(id, group, fn);
|
||||
}
|
||||
};
|
||||
|
||||
53
lib/store.js
Normal file
53
lib/store.js
Normal file
@@ -0,0 +1,53 @@
|
||||
|
||||
/**
|
||||
* Store. In memory by default.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
function Store () {
|
||||
this.groups = {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds id to group.
|
||||
*
|
||||
* @param {String} client id
|
||||
* @param {String} group name
|
||||
* @param {Function} event listener
|
||||
* @param {Function} callback
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Store.prototype.addToGroup = function (id, group, listener, fn) {
|
||||
if (!this.groups[group]) {
|
||||
this.groups[group] = [];
|
||||
this.listeners[group] = {};
|
||||
}
|
||||
|
||||
if (!this.listeners[group][id]) {
|
||||
this.on('group:' + group, listener);
|
||||
this.listeners[group][id] = listener;
|
||||
this.groups[group].push(id);
|
||||
}
|
||||
|
||||
fn && fn();
|
||||
};
|
||||
|
||||
/**
|
||||
* Removes id from group.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Store.prototype.removeFromGroup = function (id, group, fn) {
|
||||
if (this.groups[group]) {
|
||||
var i = this.groups[group].indexOf(id);
|
||||
if (~i) {
|
||||
this.groups[group].splice(i, 1);
|
||||
this.removeListener('group:' + group, this.listeners[group][id]);
|
||||
}
|
||||
}
|
||||
|
||||
fn && fn();
|
||||
};
|
||||
79
package.json
79
package.json
@@ -1,54 +1,31 @@
|
||||
{
|
||||
"name": "socket.io",
|
||||
"version": "1.3.3",
|
||||
"description": "node.js realtime framework server",
|
||||
"keywords": [
|
||||
"realtime",
|
||||
"framework",
|
||||
"websocket",
|
||||
"tcp",
|
||||
"events",
|
||||
"socket",
|
||||
"io"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/Automattic/socket.io"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "mocha --reporter dot --slow 200ms --bail"
|
||||
},
|
||||
"dependencies": {
|
||||
"engine.io": "1.5.1",
|
||||
"socket.io-parser": "2.2.3",
|
||||
"socket.io-client": "1.3.3",
|
||||
"socket.io-adapter": "0.3.1",
|
||||
"has-binary-data": "0.1.3",
|
||||
"debug": "2.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"mocha": "1.16.2",
|
||||
"expect.js": "0.3.1",
|
||||
"supertest": "0.8.2",
|
||||
"superagent": "0.17.0",
|
||||
"istanbul": "0.2.3"
|
||||
},
|
||||
"contributors": [
|
||||
{
|
||||
"name": "Guillermo Rauch",
|
||||
"email": "rauchg@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Arnout Kazemier",
|
||||
"email": "info@3rd-eden.com"
|
||||
},
|
||||
{
|
||||
"name": "Vladimir Dronnikov",
|
||||
"email": "dronnikov@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Einar Otto Stangvik",
|
||||
"email": "einaros@gmail.com"
|
||||
"name": "socket.io"
|
||||
, "version": "1.0.0-alpha1"
|
||||
, "description": "Real-time apps made cross-browser & easy with a WebSocket-like API"
|
||||
, "homepage": "http://socket.io"
|
||||
, "keywords": ["websocket", "socket", "realtime", "socket.io", "comet", "ajax"]
|
||||
, "author": "Guillermo Rauch <guillermo@learnboost.com>"
|
||||
, "contributors": [
|
||||
{ "name": "Guillermo Rauch", "email": "rauchg@gmail.com" }
|
||||
, { "name": "Arnout Kazemier", "email": "info@3rd-eden.com" }
|
||||
, { "name": "Vladimir Dronnikov", "email": "dronnikov@gmail.com" }
|
||||
, { "name": "Einar Otto Stangvik", "email": "einaros@gmail.com" }
|
||||
]
|
||||
, "repository": {
|
||||
"type": "git"
|
||||
, "url": "https://github.com/learnboost/socket.io.git"
|
||||
}
|
||||
, "main": "./lib/socket.io"
|
||||
, "dependencies": {
|
||||
"engine.io": "0.1.0"
|
||||
, "socket.io-client": "1.0.0-alpha1"
|
||||
}
|
||||
, "devDependencies": {
|
||||
"mocha": "*"
|
||||
, "expect.js": "*"
|
||||
, "superagent": "*"
|
||||
}
|
||||
, "scripts": {
|
||||
"test": "make test"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
BIN
test/fixtures/big.jpg
vendored
BIN
test/fixtures/big.jpg
vendored
Binary file not shown.
|
Before Width: | Height: | Size: 4.5 MiB |
174066
test/fixtures/big.json
vendored
174066
test/fixtures/big.json
vendored
File diff suppressed because it is too large
Load Diff
1830
test/socket.io.js
1830
test/socket.io.js
File diff suppressed because it is too large
Load Diff
Binary file not shown.
|
Before Width: | Height: | Size: 32 KiB |
Reference in New Issue
Block a user