├── test ├── fixtures │ ├── big.jpg │ └── server-close.js └── support │ └── doge.jpg ├── examples ├── cluster-nginx │ ├── nginx │ │ ├── Dockerfile │ │ └── nginx.conf │ ├── server │ │ ├── Dockerfile │ │ ├── package.json │ │ ├── public │ │ │ ├── index.html │ │ │ ├── style.css │ │ │ └── main.js │ │ └── index.js │ ├── docker-compose.yml │ └── README.md ├── webpack-build │ ├── support │ │ ├── noop.js │ │ ├── webpack.config.js │ │ └── webpack.config.slim.js │ ├── lib │ │ └── index.js │ ├── index.html │ ├── package.json │ └── README.md ├── cluster-httpd │ ├── httpd │ │ ├── Dockerfile │ │ └── httpd.conf │ ├── server │ │ ├── Dockerfile │ │ ├── package.json │ │ ├── public │ │ │ ├── index.html │ │ │ ├── style.css │ │ │ └── main.js │ │ └── index.js │ ├── docker-compose.yml │ └── README.md ├── cluster-haproxy │ ├── haproxy │ │ ├── Dockerfile │ │ └── haproxy.cfg │ ├── server │ │ ├── Dockerfile │ │ ├── package.json │ │ ├── public │ │ │ ├── index.html │ │ │ ├── style.css │ │ │ └── main.js │ │ └── index.js │ ├── docker-compose.yml │ └── README.md ├── chat │ ├── package.json │ ├── README.md │ ├── public │ │ ├── index.html │ │ ├── style.css │ │ └── main.js │ └── index.js ├── whiteboard │ ├── README.md │ ├── package.json │ ├── index.js │ └── public │ │ ├── index.html │ │ ├── style.css │ │ └── main.js └── webpack-build-server │ ├── support │ └── webpack.config.js │ ├── lib │ └── index.js │ ├── README.md │ └── package.json ├── Makefile ├── .gitignore ├── .github ├── PULL_REQUEST_TEMPLATE.md └── ISSUE_TEMPLATE.md ├── docs ├── README.md ├── emit.md └── API.md ├── .travis.yml ├── LICENSE ├── package.json ├── gulpfile.js ├── lib ├── client.js ├── namespace.js ├── index.js └── socket.js ├── Readme.md └── History.md /test/fixtures/big.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/functions/socket.io/master/test/fixtures/big.jpg -------------------------------------------------------------------------------- /test/support/doge.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/functions/socket.io/master/test/support/doge.jpg -------------------------------------------------------------------------------- /examples/cluster-nginx/nginx/Dockerfile: -------------------------------------------------------------------------------- 1 | 2 | FROM nginx:alpine 3 | COPY nginx.conf /etc/nginx/nginx.conf 4 | -------------------------------------------------------------------------------- /examples/webpack-build/support/noop.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = function () { return function () {}; }; 3 | -------------------------------------------------------------------------------- /examples/cluster-httpd/httpd/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM httpd:2.4-alpine 2 | COPY ./httpd.conf /usr/local/apache2/conf/httpd.conf 3 | -------------------------------------------------------------------------------- /examples/cluster-haproxy/haproxy/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM haproxy:1.7-alpine 2 | COPY haproxy.cfg /usr/local/etc/haproxy/haproxy.cfg 3 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | test: 3 | @./node_modules/.bin/gulp test 4 | 5 | test-cov: 6 | @./node_modules/.bin/gulp test-cov 7 | 8 | .PHONY: test 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | lib-cov 3 | *.seed 4 | *.log 5 | *.csv 6 | *.dat 7 | *.out 8 | *.pid 9 | benchmarks/*.png 10 | node_modules 11 | coverage 12 | .idea 13 | dist 14 | -------------------------------------------------------------------------------- /examples/webpack-build/support/webpack.config.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = { 3 | entry: './lib/index.js', 4 | output: { 5 | path: './dist', 6 | filename: 'app.js' 7 | }, 8 | }; 9 | -------------------------------------------------------------------------------- /examples/webpack-build/lib/index.js: -------------------------------------------------------------------------------- 1 | 2 | var socket = require('socket.io-client')('http://localhost:3000'); 3 | 4 | console.log('init'); 5 | 6 | socket.on('connect', onConnect); 7 | 8 | function onConnect(){ 9 | console.log('connect ' + socket.id); 10 | } 11 | -------------------------------------------------------------------------------- /examples/webpack-build/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Socket.IO WebPack Example 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /examples/cluster-haproxy/server/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM mhart/alpine-node:6 2 | 3 | # Create app directory 4 | RUN mkdir -p /usr/src/app 5 | WORKDIR /usr/src/app 6 | 7 | # Install app dependencies 8 | COPY package.json /usr/src/app/ 9 | RUN npm install 10 | 11 | # Bundle app source 12 | COPY . /usr/src/app 13 | 14 | EXPOSE 3000 15 | CMD [ "npm", "start" ] 16 | -------------------------------------------------------------------------------- /examples/cluster-httpd/server/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM mhart/alpine-node:6 2 | 3 | # Create app directory 4 | RUN mkdir -p /usr/src/app 5 | WORKDIR /usr/src/app 6 | 7 | # Install app dependencies 8 | COPY package.json /usr/src/app/ 9 | RUN npm install 10 | 11 | # Bundle app source 12 | COPY . /usr/src/app 13 | 14 | EXPOSE 3000 15 | CMD [ "npm", "start" ] 16 | -------------------------------------------------------------------------------- /examples/cluster-nginx/server/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM mhart/alpine-node:6 2 | 3 | # Create app directory 4 | RUN mkdir -p /usr/src/app 5 | WORKDIR /usr/src/app 6 | 7 | # Install app dependencies 8 | COPY package.json /usr/src/app/ 9 | RUN npm install 10 | 11 | # Bundle app source 12 | COPY . /usr/src/app 13 | 14 | EXPOSE 3000 15 | CMD [ "npm", "start" ] 16 | -------------------------------------------------------------------------------- /test/fixtures/server-close.js: -------------------------------------------------------------------------------- 1 | var server = require('http').createServer(); 2 | var ioc = require('socket.io-client'); 3 | var io = require('../..')(server); 4 | 5 | var srv = server.listen(function() { 6 | var socket = ioc('ws://localhost:' + server.address().port); 7 | socket.on('connect', function() { 8 | io.close(); 9 | socket.close(); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | ### The kind of change this PR does introduce 3 | 4 | * [x] a bug fix 5 | * [ ] a new feature 6 | * [ ] an update to the documentation 7 | * [ ] a code change that improves performance 8 | * [ ] other 9 | 10 | ### Current behaviour 11 | 12 | 13 | ### New behaviour 14 | 15 | 16 | ### Other information (e.g. related issues) 17 | 18 | 19 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | 2 | ## Table of Contents 3 | 4 | #### Getting started 5 | 6 | - [Write a chat application](http://socket.io/get-started/chat/) 7 | 8 | #### API Reference 9 | 10 | - [Server API](API.md) 11 | - [Client API](https://github.com/socketio/socket.io-client/blob/master/docs/API.md) 12 | 13 | #### Advanced topics 14 | 15 | - [Emit cheatsheet](emit.md) 16 | -------------------------------------------------------------------------------- /examples/chat/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "socket.io-chat", 3 | "version": "0.0.0", 4 | "description": "A simple chat client using socket.io", 5 | "main": "index.js", 6 | "author": "Grant Timmerman", 7 | "private": true, 8 | "license": "BSD", 9 | "dependencies": { 10 | "express": "4.13.4" 11 | }, 12 | "scripts": { 13 | "start": "node index.js" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /examples/whiteboard/README.md: -------------------------------------------------------------------------------- 1 | 2 | # Socket.IO Collaborative Whiteboard 3 | 4 | A simple collaborative whiteboard for socket.io 5 | 6 | ## How to use 7 | 8 | ``` 9 | $ npm i && npm start 10 | ``` 11 | 12 | And point your browser to `http://localhost:3000`. Optionally, specify 13 | a port by supplying the `PORT` env variable. 14 | 15 | ## Features 16 | 17 | - draw on the whiteboard and all other users will see you drawings live 18 | -------------------------------------------------------------------------------- /examples/cluster-httpd/server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "socket.io-chat", 3 | "version": "0.0.0", 4 | "description": "A simple chat client using socket.io", 5 | "main": "index.js", 6 | "author": "Grant Timmerman", 7 | "private": true, 8 | "license": "BSD", 9 | "dependencies": { 10 | "express": "4.13.4", 11 | "socket.io": "^1.7.2", 12 | "socket.io-redis": "^3.0.0" 13 | }, 14 | "scripts": { 15 | "start": "node index.js" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /examples/cluster-nginx/server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "socket.io-chat", 3 | "version": "0.0.0", 4 | "description": "A simple chat client using socket.io", 5 | "main": "index.js", 6 | "author": "Grant Timmerman", 7 | "private": true, 8 | "license": "BSD", 9 | "dependencies": { 10 | "express": "4.13.4", 11 | "socket.io": "^1.7.2", 12 | "socket.io-redis": "^3.0.0" 13 | }, 14 | "scripts": { 15 | "start": "node index.js" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /examples/cluster-haproxy/server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "socket.io-chat", 3 | "version": "0.0.0", 4 | "description": "A simple chat client using socket.io", 5 | "main": "index.js", 6 | "author": "Grant Timmerman", 7 | "private": true, 8 | "license": "BSD", 9 | "dependencies": { 10 | "express": "4.13.4", 11 | "socket.io": "^1.7.2", 12 | "socket.io-redis": "^3.0.0" 13 | }, 14 | "scripts": { 15 | "start": "node index.js" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /examples/whiteboard/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "whiteboard", 3 | "version": "1.0.0", 4 | "description": "A simple collaborative whiteboard using socket.io", 5 | "main": "index.js", 6 | "keywords": [ 7 | "socket.io", 8 | "whiteboard" 9 | ], 10 | "dependencies": { 11 | "express": "4.9.x", 12 | "socket.io": "latest" 13 | }, 14 | "scripts": { 15 | "start": "node index" 16 | }, 17 | "author": "Damien Arrachequesne", 18 | "license": "MIT" 19 | } 20 | -------------------------------------------------------------------------------- /examples/whiteboard/index.js: -------------------------------------------------------------------------------- 1 | 2 | const express = require('express'); 3 | const app = express(); 4 | const http = require('http').Server(app); 5 | const io = require('socket.io')(http); 6 | const port = process.env.PORT || 3000; 7 | 8 | app.use(express.static(__dirname + '/public')); 9 | 10 | function onConnection(socket){ 11 | socket.on('drawing', (data) => socket.broadcast.emit('drawing', data)); 12 | } 13 | 14 | io.on('connection', onConnection); 15 | 16 | http.listen(port, () => console.log('listening on port ' + port)); 17 | -------------------------------------------------------------------------------- /examples/webpack-build-server/support/webpack.config.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = { 3 | entry: './lib/index.js', 4 | target: 'node', 5 | output: { 6 | path: './dist', 7 | filename: 'server.js' 8 | }, 9 | module: { 10 | loaders: [ 11 | { 12 | test: /(\.md|\.map)$/, 13 | loader: 'null' 14 | }, 15 | { 16 | test: /\.json$/, 17 | loader: 'json' 18 | }, 19 | { 20 | test: /\.js$/, 21 | loader: "transform-loader?brfs" 22 | } 23 | ] 24 | } 25 | }; 26 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | before_install: 3 | - npm install -g npm@'>=1.4.3' 4 | language: node_js 5 | node_js: 6 | - "0.10" 7 | - "0.12" 8 | - "4" 9 | - "node" 10 | 11 | git: 12 | depth: 1 13 | 14 | matrix: 15 | include: 16 | - node_js: '0.10' 17 | env: TEST_VERSION=compat 18 | - node_js: '0.12' 19 | env: TEST_VERSION=compat 20 | - node_js: '4' 21 | env: TEST_VERSION=compat 22 | #matrix: 23 | #fast_finish: true 24 | #allow_failures: 25 | #- node_js: "0.11" 26 | 27 | notifications: 28 | irc: "irc.freenode.org#socket.io" 29 | -------------------------------------------------------------------------------- /examples/webpack-build-server/lib/index.js: -------------------------------------------------------------------------------- 1 | 2 | const server = require('http').createServer(); 3 | const io = require('socket.io')(server, { 4 | // serveClient: false // do not serve the client file, in that case the brfs loader is not needed 5 | }); 6 | const port = process.env.PORT || 3000; 7 | 8 | io.on('connect', onConnect); 9 | server.listen(port, () => console.log('server listening on port ' + port)); 10 | 11 | function onConnect(socket){ 12 | console.log('connect ' + socket.id); 13 | 14 | socket.on('disconnect', () => console.log('disconnect ' + socket.id)); 15 | } 16 | -------------------------------------------------------------------------------- /examples/webpack-build/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "webpack-build", 3 | "version": "1.0.0", 4 | "description": "A sample Webpack build", 5 | "scripts": { 6 | "build": "webpack --config ./support/webpack.config.js", 7 | "build-slim": "webpack --config ./support/webpack.config.slim.js", 8 | "build-all": "npm run build && npm run build-slim" 9 | }, 10 | "author": "Damien Arrachequesne", 11 | "license": "MIT", 12 | "dependencies": { 13 | "socket.io-client": "^1.7.2" 14 | }, 15 | "devDependencies": { 16 | "strip-loader": "^0.1.2", 17 | "webpack": "^1.14.0" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /examples/chat/README.md: -------------------------------------------------------------------------------- 1 | 2 | # Socket.IO Chat 3 | 4 | A simple chat demo for socket.io 5 | 6 | ## How to use 7 | 8 | ``` 9 | $ cd socket.io 10 | $ npm install 11 | $ cd examples/chat 12 | $ npm install 13 | $ npm start 14 | ``` 15 | 16 | And point your browser to `http://localhost:3000`. Optionally, specify 17 | a port by supplying the `PORT` env variable. 18 | 19 | ## Features 20 | 21 | - Multiple users can join a chat room by each entering a unique username 22 | on website load. 23 | - Users can type chat messages to the chat room. 24 | - A notification is sent to all users when a user joins or leaves 25 | the chatroom. 26 | -------------------------------------------------------------------------------- /examples/whiteboard/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Socket.IO whiteboard 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /examples/webpack-build-server/README.md: -------------------------------------------------------------------------------- 1 | 2 | # Socket.IO WebPack build 3 | 4 | A sample Webpack build for the server. 5 | 6 | ## How to use 7 | 8 | ``` 9 | $ npm i 10 | $ npm run build 11 | $ npm start 12 | ``` 13 | 14 | **Note:** 15 | 16 | - the `bufferutil` and `utf-8-validate` are optional dependencies from `ws`, compiled from native code, which are meant to improve performance ([ref](https://github.com/websockets/ws#opt-in-for-performance)). You can also omit them, as they have their JS fallback, and ignore the WebPack warning. 17 | 18 | - the server is initiated with `serveClient` set to `false`, so it will not serve the client file. 19 | -------------------------------------------------------------------------------- /examples/webpack-build-server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "webpack-build-server", 3 | "version": "1.0.0", 4 | "description": "A sample Webpack build (for the server)", 5 | "scripts": { 6 | "start": "node dist/server.js", 7 | "build": "webpack --config ./support/webpack.config.js" 8 | }, 9 | "author": "Damien Arrachequesne", 10 | "license": "MIT", 11 | "dependencies": { 12 | "brfs": "^1.4.3", 13 | "bufferutil": "^1.3.0", 14 | "socket.io": "^1.7.2", 15 | "transform-loader": "^0.2.3", 16 | "utf-8-validate": "^2.0.0" 17 | }, 18 | "devDependencies": { 19 | "json-loader": "^0.5.4", 20 | "null-loader": "^0.1.1", 21 | "webpack": "^1.14.0" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /examples/webpack-build/README.md: -------------------------------------------------------------------------------- 1 | 2 | # Socket.IO WebPack build 3 | 4 | A sample Webpack build for the browser. 5 | 6 | ## How to use 7 | 8 | ``` 9 | $ npm i 10 | $ npm run build-all 11 | ``` 12 | 13 | There are two WebPack configuration: 14 | 15 | - the minimal configuration, just bundling the application and its dependencies. The `app.js` file in the `dist` folder is the result of that build. 16 | 17 | - a slimmer one, where: 18 | - the JSON polyfill needed for IE6/IE7 support has been removed. 19 | - the `debug` calls and import have been removed (the [debug](https://github.com/visionmedia/debug) library is included in the build by default). 20 | - the source has been uglified (dropping IE8 support), and an associated SourceMap has been generated. 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | *Note*: for support questions, please use one of these channels: [stackoverflow](http://stackoverflow.com/questions/tagged/socket.io) or [slack](https://socketio.slack.com) 3 | 4 | ### You want to: 5 | 6 | * [x] report a *bug* 7 | * [ ] request a *feature* 8 | 9 | ### Current behaviour 10 | 11 | 12 | ### Steps to reproduce (if the current behaviour is a bug) 13 | 14 | **Note**: the best way to get a quick answer is to provide a failing test case, by forking the following [fiddle](https://github.com/darrachequesne/socket.io-fiddle) for example. 15 | 16 | ### Expected behaviour 17 | 18 | 19 | ### Setup 20 | - OS: 21 | - browser: 22 | - socket.io version: 23 | 24 | ### Other information (e.g. stacktraces, related issues, suggestions how to fix) 25 | 26 | 27 | -------------------------------------------------------------------------------- /examples/whiteboard/public/style.css: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Fix user-agent 4 | */ 5 | 6 | * { 7 | box-sizing: border-box; 8 | } 9 | 10 | html, body { 11 | height: 100%; 12 | margin: 0; 13 | padding: 0; 14 | } 15 | 16 | /** 17 | * Canvas 18 | */ 19 | 20 | .whiteboard { 21 | height: 100%; 22 | width: 100%; 23 | position: absolute; 24 | left: 0; 25 | right: 0; 26 | bottom: 0; 27 | top: 0; 28 | } 29 | 30 | .colors { 31 | position: fixed; 32 | } 33 | 34 | .color { 35 | display: inline-block; 36 | height: 48px; 37 | width: 48px; 38 | } 39 | 40 | .color.black { background-color: black; } 41 | .color.red { background-color: red; } 42 | .color.green { background-color: green; } 43 | .color.blue { background-color: blue; } 44 | .color.yellow { background-color: yellow; } 45 | -------------------------------------------------------------------------------- /examples/cluster-nginx/nginx/nginx.conf: -------------------------------------------------------------------------------- 1 | # Reference: https://www.nginx.com/resources/wiki/start/topics/examples/full/ 2 | 3 | worker_processes 4; 4 | 5 | events { 6 | worker_connections 1024; 7 | } 8 | 9 | http { 10 | server { 11 | listen 80; 12 | 13 | location / { 14 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 15 | proxy_set_header Host $host; 16 | 17 | proxy_pass http://nodes; 18 | 19 | # enable WebSockets 20 | proxy_http_version 1.1; 21 | proxy_set_header Upgrade $http_upgrade; 22 | proxy_set_header Connection "upgrade"; 23 | } 24 | } 25 | 26 | upstream nodes { 27 | # enable sticky session 28 | ip_hash; 29 | 30 | server server-john:3000; 31 | server server-paul:3000; 32 | server server-george:3000; 33 | server server-ringo:3000; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /examples/chat/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Socket.IO Chat Example 6 | 7 | 8 | 9 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /examples/cluster-httpd/docker-compose.yml: -------------------------------------------------------------------------------- 1 | 2 | httpd: 3 | build: ./httpd 4 | links: 5 | - server-john 6 | - server-paul 7 | - server-george 8 | - server-ringo 9 | ports: 10 | - "3000:80" 11 | 12 | server-john: 13 | build: ./server 14 | links: 15 | - redis 16 | expose: 17 | - "3000" 18 | environment: 19 | - NAME=John 20 | 21 | server-paul: 22 | build: ./server 23 | links: 24 | - redis 25 | expose: 26 | - "3000" 27 | environment: 28 | - NAME=Paul 29 | 30 | server-george: 31 | build: ./server 32 | links: 33 | - redis 34 | expose: 35 | - "3000" 36 | environment: 37 | - NAME=George 38 | 39 | server-ringo: 40 | build: ./server 41 | links: 42 | - redis 43 | expose: 44 | - "3000" 45 | environment: 46 | - NAME=Ringo 47 | 48 | redis: 49 | image: redis:alpine 50 | expose: 51 | - "6379" 52 | -------------------------------------------------------------------------------- /examples/cluster-nginx/docker-compose.yml: -------------------------------------------------------------------------------- 1 | 2 | nginx: 3 | build: ./nginx 4 | links: 5 | - server-john 6 | - server-paul 7 | - server-george 8 | - server-ringo 9 | ports: 10 | - "3000:80" 11 | 12 | server-john: 13 | build: ./server 14 | links: 15 | - redis 16 | expose: 17 | - "3000" 18 | environment: 19 | - NAME=John 20 | 21 | server-paul: 22 | build: ./server 23 | links: 24 | - redis 25 | expose: 26 | - "3000" 27 | environment: 28 | - NAME=Paul 29 | 30 | server-george: 31 | build: ./server 32 | links: 33 | - redis 34 | expose: 35 | - "3000" 36 | environment: 37 | - NAME=George 38 | 39 | server-ringo: 40 | build: ./server 41 | links: 42 | - redis 43 | expose: 44 | - "3000" 45 | environment: 46 | - NAME=Ringo 47 | 48 | redis: 49 | image: redis:alpine 50 | expose: 51 | - "6379" 52 | -------------------------------------------------------------------------------- /examples/cluster-haproxy/docker-compose.yml: -------------------------------------------------------------------------------- 1 | 2 | haproxy: 3 | build: ./haproxy 4 | links: 5 | - server-john 6 | - server-paul 7 | - server-george 8 | - server-ringo 9 | ports: 10 | - "3000:80" 11 | 12 | server-john: 13 | build: ./server 14 | links: 15 | - redis 16 | expose: 17 | - "3000" 18 | environment: 19 | - NAME=John 20 | 21 | server-paul: 22 | build: ./server 23 | links: 24 | - redis 25 | expose: 26 | - "3000" 27 | environment: 28 | - NAME=Paul 29 | 30 | server-george: 31 | build: ./server 32 | links: 33 | - redis 34 | expose: 35 | - "3000" 36 | environment: 37 | - NAME=George 38 | 39 | server-ringo: 40 | build: ./server 41 | links: 42 | - redis 43 | expose: 44 | - "3000" 45 | environment: 46 | - NAME=Ringo 47 | 48 | redis: 49 | image: redis:alpine 50 | expose: 51 | - "6379" 52 | -------------------------------------------------------------------------------- /examples/cluster-haproxy/server/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Socket.IO Chat Example 6 | 7 | 8 | 9 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /examples/cluster-httpd/server/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Socket.IO Chat Example 6 | 7 | 8 | 9 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /examples/cluster-nginx/server/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Socket.IO Chat Example 6 | 7 | 8 | 9 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /examples/webpack-build/support/webpack.config.slim.js: -------------------------------------------------------------------------------- 1 | 2 | var webpack = require('webpack'); 3 | 4 | module.exports = { 5 | entry: './lib/index.js', 6 | output: { 7 | path: './dist', 8 | filename: 'app.slim.js' 9 | }, 10 | externals: { 11 | // replace JSON polyfill (IE6/IE7) with global JSON object 12 | json3: 'JSON' 13 | }, 14 | // generate sourcemap 15 | devtool: 'source-map', 16 | plugins: [ 17 | // replace require('debug')() with an noop function 18 | new webpack.NormalModuleReplacementPlugin(/debug/, process.cwd() + '/support/noop.js'), 19 | // use uglifyJS (IE9+ support) 20 | new webpack.optimize.UglifyJsPlugin({ 21 | compress: { 22 | warnings: false 23 | } 24 | }) 25 | ], 26 | module: { 27 | loaders: [ 28 | { 29 | // strip `debug()` calls 30 | test: /\.js$/, 31 | loader: 'strip-loader?strip[]=debug' 32 | } 33 | ] 34 | } 35 | }; 36 | -------------------------------------------------------------------------------- /examples/cluster-haproxy/haproxy/haproxy.cfg: -------------------------------------------------------------------------------- 1 | # Reference: http://blog.haproxy.com/2012/11/07/websockets-load-balancing-with-haproxy/ 2 | 3 | global 4 | daemon 5 | maxconn 4096 6 | nbproc 2 7 | 8 | defaults 9 | mode http 10 | balance roundrobin 11 | option http-server-close 12 | timeout connect 5s 13 | timeout client 30s 14 | timeout client-fin 30s 15 | timeout server 30s 16 | timeout tunnel 1h 17 | default-server inter 1s rise 2 fall 1 on-marked-down shutdown-sessions 18 | option forwardfor 19 | 20 | listen chat 21 | bind *:80 22 | default_backend nodes 23 | 24 | backend nodes 25 | option httpchk HEAD /health 26 | http-check expect status 200 27 | cookie serverid insert 28 | server john server-john:3000 cookie john check 29 | server paul server-paul:3000 cookie paul check 30 | server george server-george:3000 cookie george check 31 | server ringo server-ringo:3000 cookie ringo check 32 | -------------------------------------------------------------------------------- /examples/cluster-httpd/README.md: -------------------------------------------------------------------------------- 1 | 2 | # Socket.IO Chat with httpd & redis 3 | 4 | A simple chat demo for socket.io 5 | 6 | ## How to use 7 | 8 | Install [Docker Compose](https://docs.docker.com/compose/install/), then: 9 | 10 | ``` 11 | $ docker-compose up -d 12 | ``` 13 | 14 | And then point your browser to `http://localhost:3000`. 15 | 16 | This will start four Socket.IO nodes, behind a httpd proxy which will loadbalance the requests (using a cookie for sticky sessions, see [cookie](http://httpd.apache.org/docs/2.4/fr/mod/mod_proxy_balancer.html)). 17 | 18 | Each node connects to the redis backend, which will enable to broadcast to every client, no matter which node it is currently connected to. 19 | 20 | ``` 21 | # you can kill a given node, the client should reconnect to another node 22 | $ docker-compose stop server-george 23 | ``` 24 | 25 | ## Features 26 | 27 | - Multiple users can join a chat room by each entering a unique username 28 | on website load. 29 | - Users can type chat messages to the chat room. 30 | - A notification is sent to all users when a user joins or leaves 31 | the chatroom. 32 | -------------------------------------------------------------------------------- /examples/cluster-nginx/README.md: -------------------------------------------------------------------------------- 1 | 2 | # Socket.IO Chat with nginx & redis 3 | 4 | A simple chat demo for socket.io 5 | 6 | ## How to use 7 | 8 | Install [Docker Compose](https://docs.docker.com/compose/install/), then: 9 | 10 | ``` 11 | $ docker-compose up -d 12 | ``` 13 | 14 | And then point your browser to `http://localhost:3000`. 15 | 16 | This will start four Socket.IO nodes, behind a nginx proxy which will loadbalance the requests (using the IP of the client, see [ip_hash](http://nginx.org/en/docs/http/ngx_http_upstream_module.html#ip_hash)). 17 | 18 | Each node connects to the redis backend, which will enable to broadcast to every client, no matter which node it is currently connected to. 19 | 20 | ``` 21 | # you can kill a given node, the client should reconnect to another node 22 | $ docker-compose stop server-george 23 | ``` 24 | 25 | ## Features 26 | 27 | - Multiple users can join a chat room by each entering a unique username 28 | on website load. 29 | - Users can type chat messages to the chat room. 30 | - A notification is sent to all users when a user joins or leaves 31 | the chatroom. 32 | -------------------------------------------------------------------------------- /examples/cluster-haproxy/README.md: -------------------------------------------------------------------------------- 1 | 2 | # Socket.IO Chat with haproxy & redis 3 | 4 | A simple chat demo for socket.io 5 | 6 | ## How to use 7 | 8 | Install [Docker Compose](https://docs.docker.com/compose/install/), then: 9 | 10 | ``` 11 | $ docker-compose up -d 12 | ``` 13 | 14 | And then point your browser to `http://localhost:3000`. 15 | 16 | This will start four Socket.IO nodes, behind a haproxy instance which will loadbalance the requests (using a cookie for sticky sessions, see [cookie](https://cbonte.github.io/haproxy-dconv/1.7/configuration.html#4.2-cookie)). 17 | 18 | Each node connects to the redis backend, which will enable to broadcast to every client, no matter which node it is currently connected to. 19 | 20 | ``` 21 | # you can kill a given node, the client should reconnect to another node 22 | $ docker-compose stop server-george 23 | ``` 24 | 25 | ## Features 26 | 27 | - Multiple users can join a chat room by each entering a unique username 28 | on website load. 29 | - Users can type chat messages to the chat room. 30 | - A notification is sent to all users when a user joins or leaves 31 | the chatroom. 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | (The MIT License) 2 | 3 | Copyright (c) 2014-2017 Automattic 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | 'Software'), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "socket.io", 3 | "version": "1.7.2", 4 | "description": "node.js realtime framework server", 5 | "keywords": [ 6 | "realtime", 7 | "framework", 8 | "websocket", 9 | "tcp", 10 | "events", 11 | "socket", 12 | "io" 13 | ], 14 | "main": "./lib/index", 15 | "files": [ 16 | "lib/" 17 | ], 18 | "license": "MIT", 19 | "repository": { 20 | "type": "git", 21 | "url": "git://github.com/socketio/socket.io" 22 | }, 23 | "scripts": { 24 | "test": "gulp test" 25 | }, 26 | "dependencies": { 27 | "debug": "2.3.3", 28 | "engine.io": "2.0.2", 29 | "has-binary": "0.1.7", 30 | "object-assign": "4.1.0", 31 | "socket.io-adapter": "~1.1.0", 32 | "socket.io-client": "socketio/socket.io-client", 33 | "socket.io-parser": "2.3.1" 34 | }, 35 | "devDependencies": { 36 | "babel-preset-es2015": "6.3.13", 37 | "del": "2.2.0", 38 | "expect.js": "0.3.1", 39 | "gulp": "3.9.0", 40 | "gulp-babel": "6.1.1", 41 | "gulp-istanbul": "0.10.3", 42 | "gulp-mocha": "2.2.0", 43 | "gulp-task-listing": "1.0.1", 44 | "istanbul": "0.4.1", 45 | "mocha": "2.3.4", 46 | "superagent": "1.6.1", 47 | "supertest": "1.1.0" 48 | }, 49 | "contributors": [ 50 | { 51 | "name": "Guillermo Rauch", 52 | "email": "rauchg@gmail.com" 53 | }, 54 | { 55 | "name": "Arnout Kazemier", 56 | "email": "info@3rd-eden.com" 57 | }, 58 | { 59 | "name": "Vladimir Dronnikov", 60 | "email": "dronnikov@gmail.com" 61 | }, 62 | { 63 | "name": "Einar Otto Stangvik", 64 | "email": "einaros@gmail.com" 65 | } 66 | ] 67 | } 68 | -------------------------------------------------------------------------------- /docs/emit.md: -------------------------------------------------------------------------------- 1 | 2 | ## Emit cheatsheet 3 | 4 | ```js 5 | 6 | io.on('connect', onConnect); 7 | 8 | function onConnect(socket){ 9 | 10 | // sending to the client 11 | socket.emit('hello', 'can you hear me?', 1, 2, 'abc'); 12 | 13 | // sending to all clients except sender 14 | socket.broadcast.emit('broadcast', 'hello friends!'); 15 | 16 | // sending to all clients in 'game' room except sender 17 | socket.to('game').emit('nice game', "let's play a game"); 18 | 19 | // sending to all clients in 'game1' and/or in 'game2' room, except sender 20 | socket.to('game1').to('game2').emit('nice game', "let's play a game (too)"); 21 | 22 | // sending to all clients in 'game' room, including sender 23 | io.in('game').emit('big-announcement', 'the game will start soon'); 24 | 25 | // sending to all clients in namespace 'myNamespace', including sender 26 | io.of('myNamespace').emit('bigger-announcement', 'the tournament will start soon'); 27 | 28 | // sending to a specific room in a specific namespace, including sender 29 | io.of('myNamespace').to('room').emit('event', 'message'); 30 | 31 | // sending to individual socketid (private message) 32 | socket.to().emit('hey', 'I just met you'); 33 | 34 | // sending with acknowledgement 35 | socket.emit('question', 'do you think so?', function (answer) {}); 36 | 37 | // sending without compression 38 | socket.compress(false).emit('uncompressed', "that's rough"); 39 | 40 | // sending a message that might be dropped if the client is not ready to receive messages 41 | socket.volatile.emit('maybe', 'do you really need it?'); 42 | 43 | // sending to all clients on this node (when using multiple nodes) 44 | io.local.emit('hi', 'my lovely babies'); 45 | 46 | }; 47 | 48 | ``` 49 | 50 | **Note:** The following events are reserved and should not be used as event names by your application: 51 | - `error` 52 | - `connect` 53 | - `disconnect` 54 | - `disconnecting` 55 | - `newListener` 56 | - `removeListener` 57 | - `ping` 58 | - `pong` 59 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | const gulp = require('gulp'); 2 | const mocha = require('gulp-mocha'); 3 | const babel = require("gulp-babel"); 4 | const istanbul = require('gulp-istanbul'); 5 | const help = require('gulp-task-listing'); 6 | const del = require('del'); 7 | 8 | gulp.task('help', help); 9 | 10 | gulp.task('default', ['transpile']); 11 | 12 | const TRANSPILE_DEST_DIR = './dist'; 13 | 14 | // By default, individual js files are transformed by babel and exported to /dist 15 | gulp.task('transpile', function () { 16 | return gulp.src("lib/*.js") 17 | .pipe(babel({ "presets": ["es2015"] })) 18 | .pipe(gulp.dest(TRANSPILE_DEST_DIR)); 19 | }); 20 | 21 | gulp.task('clean', function () { 22 | return del([TRANSPILE_DEST_DIR]); 23 | }) 24 | 25 | gulp.task('test', ['transpile'], function(){ 26 | return gulp.src('test/socket.io.js', {read: false}) 27 | .pipe(mocha({ 28 | slow: 200, 29 | reporter: 'spec', 30 | bail: true, 31 | timeout: 10000 32 | })) 33 | .once('error', function (err) { 34 | console.error(err.stack); 35 | process.exit(1); 36 | }) 37 | .once('end', function () { 38 | process.exit(); 39 | }); 40 | }); 41 | 42 | gulp.task('set-compat-node-env', function() { 43 | process.env.TEST_VERSION = 'compat'; 44 | }); 45 | 46 | gulp.task('test-compat', ['set-compat-node-env', 'test']); 47 | 48 | gulp.task('istanbul-pre-test', function () { 49 | return gulp.src(['lib/**/*.js']) 50 | // Covering files 51 | .pipe(istanbul()) 52 | // Force `require` to return covered files 53 | .pipe(istanbul.hookRequire()); 54 | }); 55 | 56 | gulp.task('test-cov', ['istanbul-pre-test'], function(){ 57 | return gulp.src('test/socket.io.js', {read: false}) 58 | .pipe(mocha({ 59 | reporter: 'dot' 60 | })) 61 | .pipe(istanbul.writeReports()) 62 | .once('error', function (err){ 63 | console.error(err.stack); 64 | process.exit(1); 65 | }) 66 | .once('end', function (){ 67 | process.exit(); 68 | }); 69 | }); 70 | -------------------------------------------------------------------------------- /examples/chat/index.js: -------------------------------------------------------------------------------- 1 | // Setup basic express server 2 | var express = require('express'); 3 | var app = express(); 4 | var server = require('http').createServer(app); 5 | var io = require('../..')(server); 6 | var port = process.env.PORT || 3000; 7 | 8 | server.listen(port, function () { 9 | console.log('Server listening at port %d', port); 10 | }); 11 | 12 | // Routing 13 | app.use(express.static(__dirname + '/public')); 14 | 15 | // Chatroom 16 | 17 | var numUsers = 0; 18 | 19 | io.on('connection', function (socket) { 20 | var addedUser = false; 21 | 22 | // when the client emits 'new message', this listens and executes 23 | socket.on('new message', function (data) { 24 | // we tell the client to execute 'new message' 25 | socket.broadcast.emit('new message', { 26 | username: socket.username, 27 | message: data 28 | }); 29 | }); 30 | 31 | // when the client emits 'add user', this listens and executes 32 | socket.on('add user', function (username) { 33 | if (addedUser) return; 34 | 35 | // we store the username in the socket session for this client 36 | socket.username = username; 37 | ++numUsers; 38 | addedUser = true; 39 | socket.emit('login', { 40 | numUsers: numUsers 41 | }); 42 | // echo globally (all clients) that a person has connected 43 | socket.broadcast.emit('user joined', { 44 | username: socket.username, 45 | numUsers: numUsers 46 | }); 47 | }); 48 | 49 | // when the client emits 'typing', we broadcast it to others 50 | socket.on('typing', function () { 51 | socket.broadcast.emit('typing', { 52 | username: socket.username 53 | }); 54 | }); 55 | 56 | // when the client emits 'stop typing', we broadcast it to others 57 | socket.on('stop typing', function () { 58 | socket.broadcast.emit('stop typing', { 59 | username: socket.username 60 | }); 61 | }); 62 | 63 | // when the user disconnects.. perform this 64 | socket.on('disconnect', function () { 65 | if (addedUser) { 66 | --numUsers; 67 | 68 | // echo globally that this client has left 69 | socket.broadcast.emit('user left', { 70 | username: socket.username, 71 | numUsers: numUsers 72 | }); 73 | } 74 | }); 75 | }); 76 | -------------------------------------------------------------------------------- /examples/cluster-httpd/httpd/httpd.conf: -------------------------------------------------------------------------------- 1 | 2 | Listen 80 3 | 4 | ServerName localhost 5 | 6 | LoadModule authn_file_module modules/mod_authn_file.so 7 | LoadModule authn_core_module modules/mod_authn_core.so 8 | LoadModule authz_host_module modules/mod_authz_host.so 9 | LoadModule authz_groupfile_module modules/mod_authz_groupfile.so 10 | LoadModule authz_user_module modules/mod_authz_user.so 11 | LoadModule authz_core_module modules/mod_authz_core.so 12 | 13 | LoadModule headers_module modules/mod_headers.so 14 | LoadModule lbmethod_byrequests_module modules/mod_lbmethod_byrequests.so 15 | LoadModule proxy_module modules/mod_proxy.so 16 | LoadModule proxy_balancer_module modules/mod_proxy_balancer.so 17 | LoadModule proxy_http_module modules/mod_proxy_http.so 18 | LoadModule proxy_wstunnel_module modules/mod_proxy_wstunnel.so 19 | LoadModule rewrite_module modules/mod_rewrite.so 20 | LoadModule slotmem_shm_module modules/mod_slotmem_shm.so 21 | LoadModule unixd_module modules/mod_unixd.so 22 | 23 | User daemon 24 | Group daemon 25 | 26 | ErrorLog /proc/self/fd/2 27 | 28 | Header add Set-Cookie "SERVERID=sticky.%{BALANCER_WORKER_ROUTE}e; path=/" env=BALANCER_ROUTE_CHANGED 29 | 30 | 31 | BalancerMember "http://server-john:3000" route=john 32 | BalancerMember "http://server-paul:3000" route=paul 33 | BalancerMember "http://server-george:3000" route=george 34 | BalancerMember "http://server-ringo:3000" route=ringo 35 | ProxySet stickysession=SERVERID 36 | 37 | 38 | 39 | BalancerMember "ws://server-john:3000" route=john 40 | BalancerMember "ws://server-paul:3000" route=paul 41 | BalancerMember "ws://server-george:3000" route=george 42 | BalancerMember "ws://server-ringo:3000" route=ringo 43 | ProxySet stickysession=SERVERID 44 | 45 | 46 | RewriteEngine On 47 | RewriteCond %{HTTP:Upgrade} =websocket [NC] 48 | RewriteRule /(.*) balancer://nodes_ws/$1 [P,L] 49 | RewriteCond %{HTTP:Upgrade} !=websocket [NC] 50 | RewriteRule /(.*) balancer://nodes_polling/$1 [P,L] 51 | 52 | ProxyTimeout 3 53 | -------------------------------------------------------------------------------- /examples/cluster-httpd/server/index.js: -------------------------------------------------------------------------------- 1 | // Setup basic express server 2 | var express = require('express'); 3 | var app = express(); 4 | var server = require('http').createServer(app); 5 | var io = require('socket.io')(server); 6 | var redis = require('socket.io-redis'); 7 | var port = process.env.PORT || 3000; 8 | var serverName = process.env.NAME || 'Unknown'; 9 | 10 | io.adapter(redis({ host: 'redis', port: 6379 })); 11 | 12 | server.listen(port, function () { 13 | console.log('Server listening at port %d', port); 14 | console.log('Hello, I\'m %s, how can I help?', serverName); 15 | }); 16 | 17 | // Routing 18 | app.use(express.static(__dirname + '/public')); 19 | 20 | // Chatroom 21 | 22 | var numUsers = 0; 23 | 24 | io.on('connection', function (socket) { 25 | socket.emit('my-name-is', serverName); 26 | 27 | var addedUser = false; 28 | 29 | // when the client emits 'new message', this listens and executes 30 | socket.on('new message', function (data) { 31 | // we tell the client to execute 'new message' 32 | socket.broadcast.emit('new message', { 33 | username: socket.username, 34 | message: data 35 | }); 36 | }); 37 | 38 | // when the client emits 'add user', this listens and executes 39 | socket.on('add user', function (username) { 40 | if (addedUser) return; 41 | 42 | // we store the username in the socket session for this client 43 | socket.username = username; 44 | ++numUsers; 45 | addedUser = true; 46 | socket.emit('login', { 47 | numUsers: numUsers 48 | }); 49 | // echo globally (all clients) that a person has connected 50 | socket.broadcast.emit('user joined', { 51 | username: socket.username, 52 | numUsers: numUsers 53 | }); 54 | }); 55 | 56 | // when the client emits 'typing', we broadcast it to others 57 | socket.on('typing', function () { 58 | socket.broadcast.emit('typing', { 59 | username: socket.username 60 | }); 61 | }); 62 | 63 | // when the client emits 'stop typing', we broadcast it to others 64 | socket.on('stop typing', function () { 65 | socket.broadcast.emit('stop typing', { 66 | username: socket.username 67 | }); 68 | }); 69 | 70 | // when the user disconnects.. perform this 71 | socket.on('disconnect', function () { 72 | if (addedUser) { 73 | --numUsers; 74 | 75 | // echo globally that this client has left 76 | socket.broadcast.emit('user left', { 77 | username: socket.username, 78 | numUsers: numUsers 79 | }); 80 | } 81 | }); 82 | }); 83 | -------------------------------------------------------------------------------- /examples/cluster-nginx/server/index.js: -------------------------------------------------------------------------------- 1 | // Setup basic express server 2 | var express = require('express'); 3 | var app = express(); 4 | var server = require('http').createServer(app); 5 | var io = require('socket.io')(server); 6 | var redis = require('socket.io-redis'); 7 | var port = process.env.PORT || 3000; 8 | var serverName = process.env.NAME || 'Unknown'; 9 | 10 | io.adapter(redis({ host: 'redis', port: 6379 })); 11 | 12 | server.listen(port, function () { 13 | console.log('Server listening at port %d', port); 14 | console.log('Hello, I\'m %s, how can I help?', serverName); 15 | }); 16 | 17 | // Routing 18 | app.use(express.static(__dirname + '/public')); 19 | 20 | // Chatroom 21 | 22 | var numUsers = 0; 23 | 24 | io.on('connection', function (socket) { 25 | socket.emit('my-name-is', serverName); 26 | 27 | var addedUser = false; 28 | 29 | // when the client emits 'new message', this listens and executes 30 | socket.on('new message', function (data) { 31 | // we tell the client to execute 'new message' 32 | socket.broadcast.emit('new message', { 33 | username: socket.username, 34 | message: data 35 | }); 36 | }); 37 | 38 | // when the client emits 'add user', this listens and executes 39 | socket.on('add user', function (username) { 40 | if (addedUser) return; 41 | 42 | // we store the username in the socket session for this client 43 | socket.username = username; 44 | ++numUsers; 45 | addedUser = true; 46 | socket.emit('login', { 47 | numUsers: numUsers 48 | }); 49 | // echo globally (all clients) that a person has connected 50 | socket.broadcast.emit('user joined', { 51 | username: socket.username, 52 | numUsers: numUsers 53 | }); 54 | }); 55 | 56 | // when the client emits 'typing', we broadcast it to others 57 | socket.on('typing', function () { 58 | socket.broadcast.emit('typing', { 59 | username: socket.username 60 | }); 61 | }); 62 | 63 | // when the client emits 'stop typing', we broadcast it to others 64 | socket.on('stop typing', function () { 65 | socket.broadcast.emit('stop typing', { 66 | username: socket.username 67 | }); 68 | }); 69 | 70 | // when the user disconnects.. perform this 71 | socket.on('disconnect', function () { 72 | if (addedUser) { 73 | --numUsers; 74 | 75 | // echo globally that this client has left 76 | socket.broadcast.emit('user left', { 77 | username: socket.username, 78 | numUsers: numUsers 79 | }); 80 | } 81 | }); 82 | }); 83 | -------------------------------------------------------------------------------- /examples/cluster-haproxy/server/index.js: -------------------------------------------------------------------------------- 1 | // Setup basic express server 2 | var express = require('express'); 3 | var app = express(); 4 | var server = require('http').createServer(app); 5 | var io = require('socket.io')(server); 6 | var redis = require('socket.io-redis'); 7 | var port = process.env.PORT || 3000; 8 | var serverName = process.env.NAME || 'Unknown'; 9 | 10 | io.adapter(redis({ host: 'redis', port: 6379 })); 11 | 12 | server.listen(port, function () { 13 | console.log('Server listening at port %d', port); 14 | console.log('Hello, I\'m %s, how can I help?', serverName); 15 | }); 16 | 17 | // Routing 18 | app.use(express.static(__dirname + '/public')); 19 | 20 | // Health check 21 | app.head('/health', function (req, res) { 22 | res.sendStatus(200); 23 | }); 24 | 25 | // Chatroom 26 | 27 | var numUsers = 0; 28 | 29 | io.on('connection', function (socket) { 30 | socket.emit('my-name-is', serverName); 31 | 32 | var addedUser = false; 33 | 34 | // when the client emits 'new message', this listens and executes 35 | socket.on('new message', function (data) { 36 | // we tell the client to execute 'new message' 37 | socket.broadcast.emit('new message', { 38 | username: socket.username, 39 | message: data 40 | }); 41 | }); 42 | 43 | // when the client emits 'add user', this listens and executes 44 | socket.on('add user', function (username) { 45 | if (addedUser) return; 46 | 47 | // we store the username in the socket session for this client 48 | socket.username = username; 49 | ++numUsers; 50 | addedUser = true; 51 | socket.emit('login', { 52 | numUsers: numUsers 53 | }); 54 | // echo globally (all clients) that a person has connected 55 | socket.broadcast.emit('user joined', { 56 | username: socket.username, 57 | numUsers: numUsers 58 | }); 59 | }); 60 | 61 | // when the client emits 'typing', we broadcast it to others 62 | socket.on('typing', function () { 63 | socket.broadcast.emit('typing', { 64 | username: socket.username 65 | }); 66 | }); 67 | 68 | // when the client emits 'stop typing', we broadcast it to others 69 | socket.on('stop typing', function () { 70 | socket.broadcast.emit('stop typing', { 71 | username: socket.username 72 | }); 73 | }); 74 | 75 | // when the user disconnects.. perform this 76 | socket.on('disconnect', function () { 77 | if (addedUser) { 78 | --numUsers; 79 | 80 | // echo globally that this client has left 81 | socket.broadcast.emit('user left', { 82 | username: socket.username, 83 | numUsers: numUsers 84 | }); 85 | } 86 | }); 87 | }); 88 | -------------------------------------------------------------------------------- /examples/chat/public/style.css: -------------------------------------------------------------------------------- 1 | /* Fix user-agent */ 2 | 3 | * { 4 | box-sizing: border-box; 5 | } 6 | 7 | html { 8 | font-weight: 300; 9 | -webkit-font-smoothing: antialiased; 10 | } 11 | 12 | html, input { 13 | font-family: 14 | "HelveticaNeue-Light", 15 | "Helvetica Neue Light", 16 | "Helvetica Neue", 17 | Helvetica, 18 | Arial, 19 | "Lucida Grande", 20 | sans-serif; 21 | } 22 | 23 | html, body { 24 | height: 100%; 25 | margin: 0; 26 | padding: 0; 27 | } 28 | 29 | ul { 30 | list-style: none; 31 | word-wrap: break-word; 32 | } 33 | 34 | /* Pages */ 35 | 36 | .pages { 37 | height: 100%; 38 | margin: 0; 39 | padding: 0; 40 | width: 100%; 41 | } 42 | 43 | .page { 44 | height: 100%; 45 | position: absolute; 46 | width: 100%; 47 | } 48 | 49 | /* Login Page */ 50 | 51 | .login.page { 52 | background-color: #000; 53 | } 54 | 55 | .login.page .form { 56 | height: 100px; 57 | margin-top: -100px; 58 | position: absolute; 59 | 60 | text-align: center; 61 | top: 50%; 62 | width: 100%; 63 | } 64 | 65 | .login.page .form .usernameInput { 66 | background-color: transparent; 67 | border: none; 68 | border-bottom: 2px solid #fff; 69 | outline: none; 70 | padding-bottom: 15px; 71 | text-align: center; 72 | width: 400px; 73 | } 74 | 75 | .login.page .title { 76 | font-size: 200%; 77 | } 78 | 79 | .login.page .usernameInput { 80 | font-size: 200%; 81 | letter-spacing: 3px; 82 | } 83 | 84 | .login.page .title, .login.page .usernameInput { 85 | color: #fff; 86 | font-weight: 100; 87 | } 88 | 89 | /* Chat page */ 90 | 91 | .chat.page { 92 | display: none; 93 | } 94 | 95 | /* Font */ 96 | 97 | .messages { 98 | font-size: 150%; 99 | } 100 | 101 | .inputMessage { 102 | font-size: 100%; 103 | } 104 | 105 | .log { 106 | color: gray; 107 | font-size: 70%; 108 | margin: 5px; 109 | text-align: center; 110 | } 111 | 112 | /* Messages */ 113 | 114 | .chatArea { 115 | height: 100%; 116 | padding-bottom: 60px; 117 | } 118 | 119 | .messages { 120 | height: 100%; 121 | margin: 0; 122 | overflow-y: scroll; 123 | padding: 10px 20px 10px 20px; 124 | } 125 | 126 | .message.typing .messageBody { 127 | color: gray; 128 | } 129 | 130 | .username { 131 | font-weight: 700; 132 | overflow: hidden; 133 | padding-right: 15px; 134 | text-align: right; 135 | } 136 | 137 | /* Input */ 138 | 139 | .inputMessage { 140 | border: 10px solid #000; 141 | bottom: 0; 142 | height: 60px; 143 | left: 0; 144 | outline: none; 145 | padding-left: 10px; 146 | position: absolute; 147 | right: 0; 148 | width: 100%; 149 | } 150 | -------------------------------------------------------------------------------- /examples/cluster-httpd/server/public/style.css: -------------------------------------------------------------------------------- 1 | /* Fix user-agent */ 2 | 3 | * { 4 | box-sizing: border-box; 5 | } 6 | 7 | html { 8 | font-weight: 300; 9 | -webkit-font-smoothing: antialiased; 10 | } 11 | 12 | html, input { 13 | font-family: 14 | "HelveticaNeue-Light", 15 | "Helvetica Neue Light", 16 | "Helvetica Neue", 17 | Helvetica, 18 | Arial, 19 | "Lucida Grande", 20 | sans-serif; 21 | } 22 | 23 | html, body { 24 | height: 100%; 25 | margin: 0; 26 | padding: 0; 27 | } 28 | 29 | ul { 30 | list-style: none; 31 | word-wrap: break-word; 32 | } 33 | 34 | /* Pages */ 35 | 36 | .pages { 37 | height: 100%; 38 | margin: 0; 39 | padding: 0; 40 | width: 100%; 41 | } 42 | 43 | .page { 44 | height: 100%; 45 | position: absolute; 46 | width: 100%; 47 | } 48 | 49 | /* Login Page */ 50 | 51 | .login.page { 52 | background-color: #000; 53 | } 54 | 55 | .login.page .form { 56 | height: 100px; 57 | margin-top: -100px; 58 | position: absolute; 59 | 60 | text-align: center; 61 | top: 50%; 62 | width: 100%; 63 | } 64 | 65 | .login.page .form .usernameInput { 66 | background-color: transparent; 67 | border: none; 68 | border-bottom: 2px solid #fff; 69 | outline: none; 70 | padding-bottom: 15px; 71 | text-align: center; 72 | width: 400px; 73 | } 74 | 75 | .login.page .title { 76 | font-size: 200%; 77 | } 78 | 79 | .login.page .usernameInput { 80 | font-size: 200%; 81 | letter-spacing: 3px; 82 | } 83 | 84 | .login.page .title, .login.page .usernameInput { 85 | color: #fff; 86 | font-weight: 100; 87 | } 88 | 89 | /* Chat page */ 90 | 91 | .chat.page { 92 | display: none; 93 | } 94 | 95 | /* Font */ 96 | 97 | .messages { 98 | font-size: 150%; 99 | } 100 | 101 | .inputMessage { 102 | font-size: 100%; 103 | } 104 | 105 | .log { 106 | color: gray; 107 | font-size: 70%; 108 | margin: 5px; 109 | text-align: center; 110 | } 111 | 112 | /* Messages */ 113 | 114 | .chatArea { 115 | height: 100%; 116 | padding-bottom: 60px; 117 | } 118 | 119 | .messages { 120 | height: 100%; 121 | margin: 0; 122 | overflow-y: scroll; 123 | padding: 10px 20px 10px 20px; 124 | } 125 | 126 | .message.typing .messageBody { 127 | color: gray; 128 | } 129 | 130 | .username { 131 | font-weight: 700; 132 | overflow: hidden; 133 | padding-right: 15px; 134 | text-align: right; 135 | } 136 | 137 | /* Input */ 138 | 139 | .inputMessage { 140 | border: 10px solid #000; 141 | bottom: 0; 142 | height: 60px; 143 | left: 0; 144 | outline: none; 145 | padding-left: 10px; 146 | position: absolute; 147 | right: 0; 148 | width: 100%; 149 | } 150 | -------------------------------------------------------------------------------- /examples/cluster-nginx/server/public/style.css: -------------------------------------------------------------------------------- 1 | /* Fix user-agent */ 2 | 3 | * { 4 | box-sizing: border-box; 5 | } 6 | 7 | html { 8 | font-weight: 300; 9 | -webkit-font-smoothing: antialiased; 10 | } 11 | 12 | html, input { 13 | font-family: 14 | "HelveticaNeue-Light", 15 | "Helvetica Neue Light", 16 | "Helvetica Neue", 17 | Helvetica, 18 | Arial, 19 | "Lucida Grande", 20 | sans-serif; 21 | } 22 | 23 | html, body { 24 | height: 100%; 25 | margin: 0; 26 | padding: 0; 27 | } 28 | 29 | ul { 30 | list-style: none; 31 | word-wrap: break-word; 32 | } 33 | 34 | /* Pages */ 35 | 36 | .pages { 37 | height: 100%; 38 | margin: 0; 39 | padding: 0; 40 | width: 100%; 41 | } 42 | 43 | .page { 44 | height: 100%; 45 | position: absolute; 46 | width: 100%; 47 | } 48 | 49 | /* Login Page */ 50 | 51 | .login.page { 52 | background-color: #000; 53 | } 54 | 55 | .login.page .form { 56 | height: 100px; 57 | margin-top: -100px; 58 | position: absolute; 59 | 60 | text-align: center; 61 | top: 50%; 62 | width: 100%; 63 | } 64 | 65 | .login.page .form .usernameInput { 66 | background-color: transparent; 67 | border: none; 68 | border-bottom: 2px solid #fff; 69 | outline: none; 70 | padding-bottom: 15px; 71 | text-align: center; 72 | width: 400px; 73 | } 74 | 75 | .login.page .title { 76 | font-size: 200%; 77 | } 78 | 79 | .login.page .usernameInput { 80 | font-size: 200%; 81 | letter-spacing: 3px; 82 | } 83 | 84 | .login.page .title, .login.page .usernameInput { 85 | color: #fff; 86 | font-weight: 100; 87 | } 88 | 89 | /* Chat page */ 90 | 91 | .chat.page { 92 | display: none; 93 | } 94 | 95 | /* Font */ 96 | 97 | .messages { 98 | font-size: 150%; 99 | } 100 | 101 | .inputMessage { 102 | font-size: 100%; 103 | } 104 | 105 | .log { 106 | color: gray; 107 | font-size: 70%; 108 | margin: 5px; 109 | text-align: center; 110 | } 111 | 112 | /* Messages */ 113 | 114 | .chatArea { 115 | height: 100%; 116 | padding-bottom: 60px; 117 | } 118 | 119 | .messages { 120 | height: 100%; 121 | margin: 0; 122 | overflow-y: scroll; 123 | padding: 10px 20px 10px 20px; 124 | } 125 | 126 | .message.typing .messageBody { 127 | color: gray; 128 | } 129 | 130 | .username { 131 | font-weight: 700; 132 | overflow: hidden; 133 | padding-right: 15px; 134 | text-align: right; 135 | } 136 | 137 | /* Input */ 138 | 139 | .inputMessage { 140 | border: 10px solid #000; 141 | bottom: 0; 142 | height: 60px; 143 | left: 0; 144 | outline: none; 145 | padding-left: 10px; 146 | position: absolute; 147 | right: 0; 148 | width: 100%; 149 | } 150 | -------------------------------------------------------------------------------- /examples/cluster-haproxy/server/public/style.css: -------------------------------------------------------------------------------- 1 | /* Fix user-agent */ 2 | 3 | * { 4 | box-sizing: border-box; 5 | } 6 | 7 | html { 8 | font-weight: 300; 9 | -webkit-font-smoothing: antialiased; 10 | } 11 | 12 | html, input { 13 | font-family: 14 | "HelveticaNeue-Light", 15 | "Helvetica Neue Light", 16 | "Helvetica Neue", 17 | Helvetica, 18 | Arial, 19 | "Lucida Grande", 20 | sans-serif; 21 | } 22 | 23 | html, body { 24 | height: 100%; 25 | margin: 0; 26 | padding: 0; 27 | } 28 | 29 | ul { 30 | list-style: none; 31 | word-wrap: break-word; 32 | } 33 | 34 | /* Pages */ 35 | 36 | .pages { 37 | height: 100%; 38 | margin: 0; 39 | padding: 0; 40 | width: 100%; 41 | } 42 | 43 | .page { 44 | height: 100%; 45 | position: absolute; 46 | width: 100%; 47 | } 48 | 49 | /* Login Page */ 50 | 51 | .login.page { 52 | background-color: #000; 53 | } 54 | 55 | .login.page .form { 56 | height: 100px; 57 | margin-top: -100px; 58 | position: absolute; 59 | 60 | text-align: center; 61 | top: 50%; 62 | width: 100%; 63 | } 64 | 65 | .login.page .form .usernameInput { 66 | background-color: transparent; 67 | border: none; 68 | border-bottom: 2px solid #fff; 69 | outline: none; 70 | padding-bottom: 15px; 71 | text-align: center; 72 | width: 400px; 73 | } 74 | 75 | .login.page .title { 76 | font-size: 200%; 77 | } 78 | 79 | .login.page .usernameInput { 80 | font-size: 200%; 81 | letter-spacing: 3px; 82 | } 83 | 84 | .login.page .title, .login.page .usernameInput { 85 | color: #fff; 86 | font-weight: 100; 87 | } 88 | 89 | /* Chat page */ 90 | 91 | .chat.page { 92 | display: none; 93 | } 94 | 95 | /* Font */ 96 | 97 | .messages { 98 | font-size: 150%; 99 | } 100 | 101 | .inputMessage { 102 | font-size: 100%; 103 | } 104 | 105 | .log { 106 | color: gray; 107 | font-size: 70%; 108 | margin: 5px; 109 | text-align: center; 110 | } 111 | 112 | /* Messages */ 113 | 114 | .chatArea { 115 | height: 100%; 116 | padding-bottom: 60px; 117 | } 118 | 119 | .messages { 120 | height: 100%; 121 | margin: 0; 122 | overflow-y: scroll; 123 | padding: 10px 20px 10px 20px; 124 | } 125 | 126 | .message.typing .messageBody { 127 | color: gray; 128 | } 129 | 130 | .username { 131 | font-weight: 700; 132 | overflow: hidden; 133 | padding-right: 15px; 134 | text-align: right; 135 | } 136 | 137 | /* Input */ 138 | 139 | .inputMessage { 140 | border: 10px solid #000; 141 | bottom: 0; 142 | height: 60px; 143 | left: 0; 144 | outline: none; 145 | padding-left: 10px; 146 | position: absolute; 147 | right: 0; 148 | width: 100%; 149 | } 150 | -------------------------------------------------------------------------------- /examples/whiteboard/public/main.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | (function() { 4 | 5 | var socket = io(); 6 | var canvas = document.getElementsByClassName('whiteboard')[0]; 7 | var colors = document.getElementsByClassName('color'); 8 | var context = canvas.getContext('2d'); 9 | 10 | var current = { 11 | color: 'black' 12 | }; 13 | var drawing = false; 14 | 15 | canvas.addEventListener('mousedown', onMouseDown, false); 16 | canvas.addEventListener('mouseup', onMouseUp, false); 17 | canvas.addEventListener('mouseout', onMouseUp, false); 18 | canvas.addEventListener('mousemove', throttle(onMouseMove, 10), false); 19 | 20 | for (var i = 0; i < colors.length; i++){ 21 | colors[i].addEventListener('click', onColorUpdate, false); 22 | } 23 | 24 | socket.on('drawing', onDrawingEvent); 25 | 26 | window.addEventListener('resize', onResize, false); 27 | onResize(); 28 | 29 | 30 | function drawLine(x0, y0, x1, y1, color, emit){ 31 | context.beginPath(); 32 | context.moveTo(x0, y0); 33 | context.lineTo(x1, y1); 34 | context.strokeStyle = color; 35 | context.lineWidth = 2; 36 | context.stroke(); 37 | context.closePath(); 38 | 39 | if (!emit) { return; } 40 | var w = canvas.width; 41 | var h = canvas.height; 42 | 43 | socket.emit('drawing', { 44 | x0: x0 / w, 45 | y0: y0 / h, 46 | x1: x1 / w, 47 | y1: y1 / h, 48 | color: color 49 | }); 50 | } 51 | 52 | function onMouseDown(e){ 53 | drawing = true; 54 | current.x = e.clientX; 55 | current.y = e.clientY; 56 | } 57 | 58 | function onMouseUp(e){ 59 | if (!drawing) { return; } 60 | drawing = false; 61 | drawLine(current.x, current.y, e.clientX, e.clientY, current.color, true); 62 | } 63 | 64 | function onMouseMove(e){ 65 | if (!drawing) { return; } 66 | drawLine(current.x, current.y, e.clientX, e.clientY, current.color, true); 67 | current.x = e.clientX; 68 | current.y = e.clientY; 69 | } 70 | 71 | function onColorUpdate(e){ 72 | current.color = e.target.className.split(' ')[1]; 73 | } 74 | 75 | // limit the number of events per second 76 | function throttle(callback, delay) { 77 | var previousCall = new Date().getTime(); 78 | return function() { 79 | var time = new Date().getTime(); 80 | 81 | if ((time - previousCall) >= delay) { 82 | previousCall = time; 83 | callback.apply(null, arguments); 84 | } 85 | }; 86 | } 87 | 88 | function onDrawingEvent(data){ 89 | var w = canvas.width; 90 | var h = canvas.height; 91 | drawLine(data.x0 * w, data.y0 * h, data.x1 * w, data.y1 * h, data.color); 92 | } 93 | 94 | // make the canvas fill its parent 95 | function onResize() { 96 | canvas.width = window.innerWidth; 97 | canvas.height = window.innerHeight; 98 | } 99 | 100 | })(); 101 | -------------------------------------------------------------------------------- /lib/client.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Module dependencies. 4 | */ 5 | 6 | var parser = require('socket.io-parser'); 7 | var debug = require('debug')('socket.io:client'); 8 | var url = require('url'); 9 | 10 | /** 11 | * Module exports. 12 | */ 13 | 14 | module.exports = Client; 15 | 16 | /** 17 | * Client constructor. 18 | * 19 | * @param {Server} server instance 20 | * @param {Socket} conn 21 | * @api private 22 | */ 23 | 24 | function Client(server, conn){ 25 | this.server = server; 26 | this.conn = conn; 27 | this.encoder = server.encoder; 28 | this.decoder = new server.parser.Decoder(); 29 | this.id = conn.id; 30 | this.request = conn.request; 31 | this.setup(); 32 | this.sockets = {}; 33 | this.nsps = {}; 34 | this.connectBuffer = []; 35 | } 36 | 37 | /** 38 | * Sets up event listeners. 39 | * 40 | * @api private 41 | */ 42 | 43 | Client.prototype.setup = function(){ 44 | this.onclose = this.onclose.bind(this); 45 | this.ondata = this.ondata.bind(this); 46 | this.onerror = this.onerror.bind(this); 47 | this.ondecoded = this.ondecoded.bind(this); 48 | 49 | this.decoder.on('decoded', this.ondecoded); 50 | this.conn.on('data', this.ondata); 51 | this.conn.on('error', this.onerror); 52 | this.conn.on('close', this.onclose); 53 | }; 54 | 55 | /** 56 | * Connects a client to a namespace. 57 | * 58 | * @param {String} name namespace 59 | * @api private 60 | */ 61 | 62 | Client.prototype.connect = function(name, query){ 63 | debug('connecting to namespace %s', name); 64 | var nsp = this.server.nsps[name]; 65 | if (!nsp) { 66 | this.packet({ type: parser.ERROR, nsp: name, data : 'Invalid namespace'}); 67 | return; 68 | } 69 | 70 | if ('/' != name && !this.nsps['/']) { 71 | this.connectBuffer.push(name); 72 | return; 73 | } 74 | 75 | var self = this; 76 | var socket = nsp.add(this, query, function(){ 77 | self.sockets[socket.id] = socket; 78 | self.nsps[nsp.name] = socket; 79 | 80 | if ('/' == nsp.name && self.connectBuffer.length > 0) { 81 | self.connectBuffer.forEach(self.connect, self); 82 | self.connectBuffer = []; 83 | } 84 | }); 85 | }; 86 | 87 | /** 88 | * Disconnects from all namespaces and closes transport. 89 | * 90 | * @api private 91 | */ 92 | 93 | Client.prototype.disconnect = function(){ 94 | for (var id in this.sockets) { 95 | if (this.sockets.hasOwnProperty(id)) { 96 | this.sockets[id].disconnect(); 97 | } 98 | } 99 | this.sockets = {}; 100 | this.close(); 101 | }; 102 | 103 | /** 104 | * Removes a socket. Called by each `Socket`. 105 | * 106 | * @api private 107 | */ 108 | 109 | Client.prototype.remove = function(socket){ 110 | if (this.sockets.hasOwnProperty(socket.id)) { 111 | var nsp = this.sockets[socket.id].nsp.name; 112 | delete this.sockets[socket.id]; 113 | delete this.nsps[nsp]; 114 | } else { 115 | debug('ignoring remove for %s', socket.id); 116 | } 117 | }; 118 | 119 | /** 120 | * Closes the underlying connection. 121 | * 122 | * @api private 123 | */ 124 | 125 | Client.prototype.close = function(){ 126 | if ('open' == this.conn.readyState) { 127 | debug('forcing transport close'); 128 | this.conn.close(); 129 | this.onclose('forced server close'); 130 | } 131 | }; 132 | 133 | /** 134 | * Writes a packet to the transport. 135 | * 136 | * @param {Object} packet object 137 | * @param {Object} opts 138 | * @api private 139 | */ 140 | 141 | Client.prototype.packet = function(packet, opts){ 142 | opts = opts || {}; 143 | var self = this; 144 | 145 | // this writes to the actual connection 146 | function writeToEngine(encodedPackets) { 147 | if (opts.volatile && !self.conn.transport.writable) return; 148 | for (var i = 0; i < encodedPackets.length; i++) { 149 | self.conn.write(encodedPackets[i], { compress: opts.compress }); 150 | } 151 | } 152 | 153 | if ('open' == this.conn.readyState) { 154 | debug('writing packet %j', packet); 155 | if (!opts.preEncoded) { // not broadcasting, need to encode 156 | this.encoder.encode(packet, writeToEngine); // encode, then write results to engine 157 | } else { // a broadcast pre-encodes a packet 158 | writeToEngine(packet); 159 | } 160 | } else { 161 | debug('ignoring packet write %j', packet); 162 | } 163 | }; 164 | 165 | /** 166 | * Called with incoming transport data. 167 | * 168 | * @api private 169 | */ 170 | 171 | Client.prototype.ondata = function(data){ 172 | // try/catch is needed for protocol violations (GH-1880) 173 | try { 174 | this.decoder.add(data); 175 | } catch(e) { 176 | this.onerror(e); 177 | } 178 | }; 179 | 180 | /** 181 | * Called when parser fully decodes a packet. 182 | * 183 | * @api private 184 | */ 185 | 186 | Client.prototype.ondecoded = function(packet) { 187 | if (parser.CONNECT == packet.type) { 188 | this.connect(url.parse(packet.nsp).pathname, url.parse(packet.nsp, true).query); 189 | } else { 190 | var socket = this.nsps[packet.nsp]; 191 | if (socket) { 192 | process.nextTick(function() { 193 | socket.onpacket(packet); 194 | }); 195 | } else { 196 | debug('no socket for namespace %s', packet.nsp); 197 | } 198 | } 199 | }; 200 | 201 | /** 202 | * Handles an error. 203 | * 204 | * @param {Object} err object 205 | * @api private 206 | */ 207 | 208 | Client.prototype.onerror = function(err){ 209 | for (var id in this.sockets) { 210 | if (this.sockets.hasOwnProperty(id)) { 211 | this.sockets[id].onerror(err); 212 | } 213 | } 214 | this.conn.close(); 215 | }; 216 | 217 | /** 218 | * Called upon transport close. 219 | * 220 | * @param {String} reason 221 | * @api private 222 | */ 223 | 224 | Client.prototype.onclose = function(reason){ 225 | debug('client close with reason %s', reason); 226 | 227 | // ignore a potential subsequent `close` event 228 | this.destroy(); 229 | 230 | // `nsps` and `sockets` are cleaned up seamlessly 231 | for (var id in this.sockets) { 232 | if (this.sockets.hasOwnProperty(id)) { 233 | this.sockets[id].onclose(reason); 234 | } 235 | } 236 | this.sockets = {}; 237 | 238 | this.decoder.destroy(); // clean up decoder 239 | }; 240 | 241 | /** 242 | * Cleans up event listeners. 243 | * 244 | * @api private 245 | */ 246 | 247 | Client.prototype.destroy = function(){ 248 | this.conn.removeListener('data', this.ondata); 249 | this.conn.removeListener('error', this.onerror); 250 | this.conn.removeListener('close', this.onclose); 251 | this.decoder.removeListener('decoded', this.ondecoded); 252 | }; 253 | -------------------------------------------------------------------------------- /lib/namespace.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Module dependencies. 4 | */ 5 | 6 | var Socket = require('./socket'); 7 | var Emitter = require('events').EventEmitter; 8 | var parser = require('socket.io-parser'); 9 | var debug = require('debug')('socket.io:namespace'); 10 | var hasBin = require('has-binary'); 11 | 12 | /** 13 | * Module exports. 14 | */ 15 | 16 | module.exports = exports = Namespace; 17 | 18 | /** 19 | * Blacklisted events. 20 | */ 21 | 22 | exports.events = [ 23 | 'connect', // for symmetry with client 24 | 'connection', 25 | 'newListener' 26 | ]; 27 | 28 | /** 29 | * Flags. 30 | */ 31 | 32 | exports.flags = [ 33 | 'json', 34 | 'volatile', 35 | 'local' 36 | ]; 37 | 38 | /** 39 | * `EventEmitter#emit` reference. 40 | */ 41 | 42 | var emit = Emitter.prototype.emit; 43 | 44 | /** 45 | * Namespace constructor. 46 | * 47 | * @param {Server} server instance 48 | * @param {Socket} name 49 | * @api private 50 | */ 51 | 52 | function Namespace(server, name){ 53 | this.name = name; 54 | this.server = server; 55 | this.sockets = {}; 56 | this.connected = {}; 57 | this.fns = []; 58 | this.ids = 0; 59 | this.rooms = []; 60 | this.flags = {}; 61 | this.initAdapter(); 62 | } 63 | 64 | /** 65 | * Inherits from `EventEmitter`. 66 | */ 67 | 68 | Namespace.prototype.__proto__ = Emitter.prototype; 69 | 70 | /** 71 | * Apply flags from `Socket`. 72 | */ 73 | 74 | exports.flags.forEach(function(flag){ 75 | Object.defineProperty(Namespace.prototype, flag, { 76 | get: function() { 77 | this.flags[flag] = true; 78 | return this; 79 | } 80 | }); 81 | }); 82 | 83 | /** 84 | * Initializes the `Adapter` for this nsp. 85 | * Run upon changing adapter by `Server#adapter` 86 | * in addition to the constructor. 87 | * 88 | * @api private 89 | */ 90 | 91 | Namespace.prototype.initAdapter = function(){ 92 | this.adapter = new (this.server.adapter())(this); 93 | }; 94 | 95 | /** 96 | * Sets up namespace middleware. 97 | * 98 | * @return {Namespace} self 99 | * @api public 100 | */ 101 | 102 | Namespace.prototype.use = function(fn){ 103 | this.fns.push(fn); 104 | return this; 105 | }; 106 | 107 | /** 108 | * Executes the middleware for an incoming client. 109 | * 110 | * @param {Socket} socket that will get added 111 | * @param {Function} fn last fn call in the middleware 112 | * @api private 113 | */ 114 | 115 | Namespace.prototype.run = function(socket, fn){ 116 | var fns = this.fns.slice(0); 117 | if (!fns.length) return fn(null); 118 | 119 | function run(i){ 120 | fns[i](socket, function(err){ 121 | // upon error, short-circuit 122 | if (err) return fn(err); 123 | 124 | // if no middleware left, summon callback 125 | if (!fns[i + 1]) return fn(null); 126 | 127 | // go on to next 128 | run(i + 1); 129 | }); 130 | } 131 | 132 | run(0); 133 | }; 134 | 135 | /** 136 | * Targets a room when emitting. 137 | * 138 | * @param {String} name 139 | * @return {Namespace} self 140 | * @api public 141 | */ 142 | 143 | Namespace.prototype.to = 144 | Namespace.prototype.in = function(name){ 145 | if (!~this.rooms.indexOf(name)) this.rooms.push(name); 146 | return this; 147 | }; 148 | 149 | /** 150 | * Adds a new client. 151 | * 152 | * @return {Socket} 153 | * @api private 154 | */ 155 | 156 | Namespace.prototype.add = function(client, query, fn){ 157 | debug('adding socket to nsp %s', this.name); 158 | var socket = new Socket(this, client, query); 159 | var self = this; 160 | this.run(socket, function(err){ 161 | process.nextTick(function(){ 162 | if ('open' == client.conn.readyState) { 163 | if (err) return socket.error(err.data || err.message); 164 | 165 | // track socket 166 | self.sockets[socket.id] = socket; 167 | 168 | // it's paramount that the internal `onconnect` logic 169 | // fires before user-set events to prevent state order 170 | // violations (such as a disconnection before the connection 171 | // logic is complete) 172 | socket.onconnect(); 173 | if (fn) fn(); 174 | 175 | // fire user-set events 176 | self.emit('connect', socket); 177 | self.emit('connection', socket); 178 | } else { 179 | debug('next called after client was closed - ignoring socket'); 180 | } 181 | }); 182 | }); 183 | return socket; 184 | }; 185 | 186 | /** 187 | * Removes a client. Called by each `Socket`. 188 | * 189 | * @api private 190 | */ 191 | 192 | Namespace.prototype.remove = function(socket){ 193 | if (this.sockets.hasOwnProperty(socket.id)) { 194 | delete this.sockets[socket.id]; 195 | } else { 196 | debug('ignoring remove for %s', socket.id); 197 | } 198 | }; 199 | 200 | /** 201 | * Emits to all clients. 202 | * 203 | * @return {Namespace} self 204 | * @api public 205 | */ 206 | 207 | Namespace.prototype.emit = function(ev){ 208 | if (~exports.events.indexOf(ev)) { 209 | emit.apply(this, arguments); 210 | } else { 211 | // set up packet object 212 | var args = Array.prototype.slice.call(arguments); 213 | var parserType = parser.EVENT; // default 214 | if (hasBin(args)) { parserType = parser.BINARY_EVENT; } // binary 215 | 216 | var packet = { type: parserType, data: args }; 217 | 218 | if ('function' == typeof args[args.length - 1]) { 219 | throw new Error('Callbacks are not supported when broadcasting'); 220 | } 221 | 222 | this.adapter.broadcast(packet, { 223 | rooms: this.rooms, 224 | flags: this.flags 225 | }); 226 | 227 | this.rooms = []; 228 | this.flags = {}; 229 | } 230 | return this; 231 | }; 232 | 233 | /** 234 | * Sends a `message` event to all clients. 235 | * 236 | * @return {Namespace} self 237 | * @api public 238 | */ 239 | 240 | Namespace.prototype.send = 241 | Namespace.prototype.write = function(){ 242 | var args = Array.prototype.slice.call(arguments); 243 | args.unshift('message'); 244 | this.emit.apply(this, args); 245 | return this; 246 | }; 247 | 248 | /** 249 | * Gets a list of clients. 250 | * 251 | * @return {Namespace} self 252 | * @api public 253 | */ 254 | 255 | Namespace.prototype.clients = function(fn){ 256 | this.adapter.clients(this.rooms, fn); 257 | // reset rooms for scenario: 258 | // .in('room').clients() (GH-1978) 259 | this.rooms = []; 260 | return this; 261 | }; 262 | 263 | /** 264 | * Sets the compress flag. 265 | * 266 | * @param {Boolean} compress if `true`, compresses the sending data 267 | * @return {Socket} self 268 | * @api public 269 | */ 270 | 271 | Namespace.prototype.compress = function(compress){ 272 | this.flags.compress = compress; 273 | return this; 274 | }; 275 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | 2 | # socket.io 3 | 4 | [![Build Status](https://secure.travis-ci.org/socketio/socket.io.svg?branch=master)](https://travis-ci.org/socketio/socket.io) 5 | [![Dependency Status](https://david-dm.org/socketio/socket.io.svg)](https://david-dm.org/socketio/socket.io) 6 | [![devDependency Status](https://david-dm.org/socketio/socket.io/dev-status.svg)](https://david-dm.org/socketio/socket.io#info=devDependencies) 7 | [![NPM version](https://badge.fury.io/js/socket.io.svg)](https://www.npmjs.com/package/socket.io) 8 | ![Downloads](https://img.shields.io/npm/dm/socket.io.svg?style=flat) 9 | [![](http://slack.socket.io/badge.svg?)](http://slack.socket.io) 10 | 11 | ## Features 12 | 13 | Socket.IO enables real-time bidirectional event-based communication. It consists in: 14 | 15 | - a Node.js server (this repository) 16 | - a [Javascript client library](https://github.com/socketio/socket.io-client) for the browser (or a Node.js client) 17 | 18 | Some implementations in other languages are also available: 19 | 20 | - [Java](https://github.com/socketio/socket.io-client-java) 21 | - [C++](https://github.com/socketio/socket.io-client-cpp) 22 | - [Swift](https://github.com/socketio/socket.io-client-swift) 23 | 24 | Its main features are: 25 | 26 | #### Reliability 27 | 28 | Connections are established even in the presence of: 29 | - proxies and load balancers. 30 | - personal firewall and antivirus software. 31 | 32 | For this purpose, it relies on [Engine.IO](https://github.com/socketio/engine.io), which first establishes a long-polling connection, then tries to upgrade to better transports that are "tested" on the side, like WebSocket. Please see the [Goals](https://github.com/socketio/engine.io#goals) section for more information. 33 | 34 | #### Auto-reconnection support 35 | 36 | Unless instructed otherwise a disconnected client will try to reconnect forever, until the server is available again. Please see the available reconnection options [here](https://github.com/socketio/socket.io-client/blob/master/docs/API.md#new-managerurl-options). 37 | 38 | #### Disconnection detection 39 | 40 | An heartbeat mechanism is implemented at the Engine.IO level, allowing both the server and the client to know when the other one is not responding anymore. 41 | 42 | That functionality is achieved with timers set on both the server and the client, with timeout values (the `pingInterval` and `pingTimeout` parameters) shared during the connection handshake. Those timers require any subsequent client calls to be directed to the same server, hence the `sticky-session` requirement when using multiples nodes. 43 | 44 | #### Binary support 45 | 46 | Any serializable data structures can be emitted, including: 47 | 48 | - [ArrayBuffer](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer) and [Blob](https://developer.mozilla.org/en-US/docs/Web/API/Blob) in the browser 49 | - [ArrayBuffer](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer) and [Buffer](https://nodejs.org/api/buffer.html) in Node.js 50 | 51 | #### Simple and convenient API 52 | 53 | Sample code: 54 | 55 | ```js 56 | io.on('connection', function(socket){ 57 | socket.emit('request', /* */); // emit an event to the socket 58 | io.emit('broadcast', /* */); // emit an event to all connected sockets 59 | socket.on('reply', function(){ /* */ }); // listen to the event 60 | }); 61 | ``` 62 | 63 | #### Cross-browser 64 | 65 | Browser support is tested in Saucelabs: 66 | 67 | [![Sauce Test Status](https://saucelabs.com/browser-matrix/socket.svg)](https://saucelabs.com/u/socket) 68 | 69 | #### Multiplexing support 70 | 71 | In order to create separation of concerns within your application (for example per module, or based on permissions), Socket.IO allows you to create several `Namespaces`, which will act as separate communication channels but will share the same underlying connection. 72 | 73 | #### Room support 74 | 75 | Within each `Namespace`, you can define arbitrary channels, called `Rooms`, that sockets can join and leave. You can then broadcast to any given room, reaching every socket that has joined it. 76 | 77 | This is a useful feature to send notifications to a group of users, or to a given user connected on several devices for example. 78 | 79 | 80 | **Note:** Socket.IO is not a WebSocket implementation. Although Socket.IO indeed uses WebSocket as a transport when possible, it adds some metadata to each packet: the packet type, the namespace and the ack id when a message acknowledgement is needed. That is why a WebSocket client will not be able to successfully connect to a Socket.IO server, and a Socket.IO client will not be able to connect to a WebSocket server (like `ws://echo.websocket.org`) either. Please see the protocol specification [here](https://github.com/socketio/socket.io-protocol). 81 | 82 | ## Installation 83 | 84 | ```bash 85 | npm install socket.io --save 86 | ``` 87 | 88 | ## How to use 89 | 90 | The following example attaches socket.io to a plain Node.JS 91 | HTTP server listening on port `3000`. 92 | 93 | ```js 94 | var server = require('http').createServer(); 95 | var io = require('socket.io')(server); 96 | io.on('connection', function(client){ 97 | client.on('event', function(data){}); 98 | client.on('disconnect', function(){}); 99 | }); 100 | server.listen(3000); 101 | ``` 102 | 103 | ### Standalone 104 | 105 | ```js 106 | var io = require('socket.io')(); 107 | io.on('connection', function(client){}); 108 | io.listen(3000); 109 | ``` 110 | 111 | ### In conjunction with Express 112 | 113 | Starting with **3.0**, express applications have become request handler 114 | functions that you pass to `http` or `http` `Server` instances. You need 115 | to pass the `Server` to `socket.io`, and not the express application 116 | function. Also make sure to call `.listen` on the `server`, not the `app`. 117 | 118 | ```js 119 | var app = require('express')(); 120 | var server = require('http').createServer(app); 121 | var io = require('socket.io')(server); 122 | io.on('connection', function(){ /* … */ }); 123 | server.listen(3000); 124 | ``` 125 | 126 | ### In conjunction with Koa 127 | 128 | Like Express.JS, Koa works by exposing an application as a request 129 | handler function, but only by calling the `callback` method. 130 | 131 | ```js 132 | var app = require('koa')(); 133 | var server = require('http').createServer(app.callback()); 134 | var io = require('socket.io')(server); 135 | io.on('connection', function(){ /* … */ }); 136 | server.listen(3000); 137 | ``` 138 | 139 | ## Documentation 140 | 141 | Please see the documentation [here](/docs/README.md). Contributions are welcome! 142 | 143 | ## Debug / logging 144 | 145 | Socket.IO is powered by [debug](https://github.com/visionmedia/debug). 146 | In order to see all the debug output, run your app with the environment variable 147 | `DEBUG` including the desired scope. 148 | 149 | To see the output from all of Socket.IO's debugging scopes you can use: 150 | 151 | ``` 152 | DEBUG=socket.io* node myapp 153 | ``` 154 | 155 | ## Testing 156 | 157 | ``` 158 | npm test 159 | ``` 160 | This runs the `gulp` task `test`. By default the test will be run with the source code in `lib` directory. 161 | 162 | Set the environmental variable `TEST_VERSION` to `compat` to test the transpiled es5-compat version of the code. 163 | 164 | The `gulp` task `test` will always transpile the source code into es5 and export to `dist` first before running the test. 165 | 166 | ## License 167 | 168 | [MIT](LICENSE) 169 | -------------------------------------------------------------------------------- /examples/chat/public/main.js: -------------------------------------------------------------------------------- 1 | $(function() { 2 | var FADE_TIME = 150; // ms 3 | var TYPING_TIMER_LENGTH = 400; // ms 4 | var COLORS = [ 5 | '#e21400', '#91580f', '#f8a700', '#f78b00', 6 | '#58dc00', '#287b00', '#a8f07a', '#4ae8c4', 7 | '#3b88eb', '#3824aa', '#a700ff', '#d300e7' 8 | ]; 9 | 10 | // Initialize variables 11 | var $window = $(window); 12 | var $usernameInput = $('.usernameInput'); // Input for username 13 | var $messages = $('.messages'); // Messages area 14 | var $inputMessage = $('.inputMessage'); // Input message input box 15 | 16 | var $loginPage = $('.login.page'); // The login page 17 | var $chatPage = $('.chat.page'); // The chatroom page 18 | 19 | // Prompt for setting a username 20 | var username; 21 | var connected = false; 22 | var typing = false; 23 | var lastTypingTime; 24 | var $currentInput = $usernameInput.focus(); 25 | 26 | var socket = io(); 27 | 28 | function addParticipantsMessage (data) { 29 | var message = ''; 30 | if (data.numUsers === 1) { 31 | message += "there's 1 participant"; 32 | } else { 33 | message += "there are " + data.numUsers + " participants"; 34 | } 35 | log(message); 36 | } 37 | 38 | // Sets the client's username 39 | function setUsername () { 40 | username = cleanInput($usernameInput.val().trim()); 41 | 42 | // If the username is valid 43 | if (username) { 44 | $loginPage.fadeOut(); 45 | $chatPage.show(); 46 | $loginPage.off('click'); 47 | $currentInput = $inputMessage.focus(); 48 | 49 | // Tell the server your username 50 | socket.emit('add user', username); 51 | } 52 | } 53 | 54 | // Sends a chat message 55 | function sendMessage () { 56 | var message = $inputMessage.val(); 57 | // Prevent markup from being injected into the message 58 | message = cleanInput(message); 59 | // if there is a non-empty message and a socket connection 60 | if (message && connected) { 61 | $inputMessage.val(''); 62 | addChatMessage({ 63 | username: username, 64 | message: message 65 | }); 66 | // tell server to execute 'new message' and send along one parameter 67 | socket.emit('new message', message); 68 | } 69 | } 70 | 71 | // Log a message 72 | function log (message, options) { 73 | var $el = $('
  • ').addClass('log').text(message); 74 | addMessageElement($el, options); 75 | } 76 | 77 | // Adds the visual chat message to the message list 78 | function addChatMessage (data, options) { 79 | // Don't fade the message in if there is an 'X was typing' 80 | var $typingMessages = getTypingMessages(data); 81 | options = options || {}; 82 | if ($typingMessages.length !== 0) { 83 | options.fade = false; 84 | $typingMessages.remove(); 85 | } 86 | 87 | var $usernameDiv = $('') 88 | .text(data.username) 89 | .css('color', getUsernameColor(data.username)); 90 | var $messageBodyDiv = $('') 91 | .text(data.message); 92 | 93 | var typingClass = data.typing ? 'typing' : ''; 94 | var $messageDiv = $('
  • ') 95 | .data('username', data.username) 96 | .addClass(typingClass) 97 | .append($usernameDiv, $messageBodyDiv); 98 | 99 | addMessageElement($messageDiv, options); 100 | } 101 | 102 | // Adds the visual chat typing message 103 | function addChatTyping (data) { 104 | data.typing = true; 105 | data.message = 'is typing'; 106 | addChatMessage(data); 107 | } 108 | 109 | // Removes the visual chat typing message 110 | function removeChatTyping (data) { 111 | getTypingMessages(data).fadeOut(function () { 112 | $(this).remove(); 113 | }); 114 | } 115 | 116 | // Adds a message element to the messages and scrolls to the bottom 117 | // el - The element to add as a message 118 | // options.fade - If the element should fade-in (default = true) 119 | // options.prepend - If the element should prepend 120 | // all other messages (default = false) 121 | function addMessageElement (el, options) { 122 | var $el = $(el); 123 | 124 | // Setup default options 125 | if (!options) { 126 | options = {}; 127 | } 128 | if (typeof options.fade === 'undefined') { 129 | options.fade = true; 130 | } 131 | if (typeof options.prepend === 'undefined') { 132 | options.prepend = false; 133 | } 134 | 135 | // Apply options 136 | if (options.fade) { 137 | $el.hide().fadeIn(FADE_TIME); 138 | } 139 | if (options.prepend) { 140 | $messages.prepend($el); 141 | } else { 142 | $messages.append($el); 143 | } 144 | $messages[0].scrollTop = $messages[0].scrollHeight; 145 | } 146 | 147 | // Prevents input from having injected markup 148 | function cleanInput (input) { 149 | return $('
    ').text(input).text(); 150 | } 151 | 152 | // Updates the typing event 153 | function updateTyping () { 154 | if (connected) { 155 | if (!typing) { 156 | typing = true; 157 | socket.emit('typing'); 158 | } 159 | lastTypingTime = (new Date()).getTime(); 160 | 161 | setTimeout(function () { 162 | var typingTimer = (new Date()).getTime(); 163 | var timeDiff = typingTimer - lastTypingTime; 164 | if (timeDiff >= TYPING_TIMER_LENGTH && typing) { 165 | socket.emit('stop typing'); 166 | typing = false; 167 | } 168 | }, TYPING_TIMER_LENGTH); 169 | } 170 | } 171 | 172 | // Gets the 'X is typing' messages of a user 173 | function getTypingMessages (data) { 174 | return $('.typing.message').filter(function (i) { 175 | return $(this).data('username') === data.username; 176 | }); 177 | } 178 | 179 | // Gets the color of a username through our hash function 180 | function getUsernameColor (username) { 181 | // Compute hash code 182 | var hash = 7; 183 | for (var i = 0; i < username.length; i++) { 184 | hash = username.charCodeAt(i) + (hash << 5) - hash; 185 | } 186 | // Calculate color 187 | var index = Math.abs(hash % COLORS.length); 188 | return COLORS[index]; 189 | } 190 | 191 | // Keyboard events 192 | 193 | $window.keydown(function (event) { 194 | // Auto-focus the current input when a key is typed 195 | if (!(event.ctrlKey || event.metaKey || event.altKey)) { 196 | $currentInput.focus(); 197 | } 198 | // When the client hits ENTER on their keyboard 199 | if (event.which === 13) { 200 | if (username) { 201 | sendMessage(); 202 | socket.emit('stop typing'); 203 | typing = false; 204 | } else { 205 | setUsername(); 206 | } 207 | } 208 | }); 209 | 210 | $inputMessage.on('input', function() { 211 | updateTyping(); 212 | }); 213 | 214 | // Click events 215 | 216 | // Focus input when clicking anywhere on login page 217 | $loginPage.click(function () { 218 | $currentInput.focus(); 219 | }); 220 | 221 | // Focus input when clicking on the message input's border 222 | $inputMessage.click(function () { 223 | $inputMessage.focus(); 224 | }); 225 | 226 | // Socket events 227 | 228 | // Whenever the server emits 'login', log the login message 229 | socket.on('login', function (data) { 230 | connected = true; 231 | // Display the welcome message 232 | var message = "Welcome to Socket.IO Chat – "; 233 | log(message, { 234 | prepend: true 235 | }); 236 | addParticipantsMessage(data); 237 | }); 238 | 239 | // Whenever the server emits 'new message', update the chat body 240 | socket.on('new message', function (data) { 241 | addChatMessage(data); 242 | }); 243 | 244 | // Whenever the server emits 'user joined', log it in the chat body 245 | socket.on('user joined', function (data) { 246 | log(data.username + ' joined'); 247 | addParticipantsMessage(data); 248 | }); 249 | 250 | // Whenever the server emits 'user left', log it in the chat body 251 | socket.on('user left', function (data) { 252 | log(data.username + ' left'); 253 | addParticipantsMessage(data); 254 | removeChatTyping(data); 255 | }); 256 | 257 | // Whenever the server emits 'typing', show the typing message 258 | socket.on('typing', function (data) { 259 | addChatTyping(data); 260 | }); 261 | 262 | // Whenever the server emits 'stop typing', kill the typing message 263 | socket.on('stop typing', function (data) { 264 | removeChatTyping(data); 265 | }); 266 | 267 | socket.on('disconnect', function () { 268 | log('you have been disconnected'); 269 | }); 270 | 271 | socket.on('reconnect', function () { 272 | log('you have been reconnected'); 273 | if (username) { 274 | socket.emit('add user', username); 275 | } 276 | }); 277 | 278 | socket.on('reconnect_error', function () { 279 | log('attempt to reconnect has failed'); 280 | }); 281 | 282 | }); 283 | -------------------------------------------------------------------------------- /examples/cluster-haproxy/server/public/main.js: -------------------------------------------------------------------------------- 1 | $(function() { 2 | var FADE_TIME = 150; // ms 3 | var TYPING_TIMER_LENGTH = 400; // ms 4 | var COLORS = [ 5 | '#e21400', '#91580f', '#f8a700', '#f78b00', 6 | '#58dc00', '#287b00', '#a8f07a', '#4ae8c4', 7 | '#3b88eb', '#3824aa', '#a700ff', '#d300e7' 8 | ]; 9 | 10 | // Initialize variables 11 | var $window = $(window); 12 | var $usernameInput = $('.usernameInput'); // Input for username 13 | var $messages = $('.messages'); // Messages area 14 | var $inputMessage = $('.inputMessage'); // Input message input box 15 | 16 | var $loginPage = $('.login.page'); // The login page 17 | var $chatPage = $('.chat.page'); // The chatroom page 18 | 19 | // Prompt for setting a username 20 | var username; 21 | var connected = false; 22 | var typing = false; 23 | var lastTypingTime; 24 | var $currentInput = $usernameInput.focus(); 25 | 26 | var socket = io(); 27 | 28 | function addParticipantsMessage (data) { 29 | var message = ''; 30 | if (data.numUsers === 1) { 31 | message += "there's 1 participant"; 32 | } else { 33 | message += "there are " + data.numUsers + " participants"; 34 | } 35 | log(message); 36 | } 37 | 38 | // Sets the client's username 39 | function setUsername () { 40 | username = cleanInput($usernameInput.val().trim()); 41 | 42 | // If the username is valid 43 | if (username) { 44 | $loginPage.fadeOut(); 45 | $chatPage.show(); 46 | $loginPage.off('click'); 47 | $currentInput = $inputMessage.focus(); 48 | 49 | // Tell the server your username 50 | socket.emit('add user', username); 51 | } 52 | } 53 | 54 | // Sends a chat message 55 | function sendMessage () { 56 | var message = $inputMessage.val(); 57 | // Prevent markup from being injected into the message 58 | message = cleanInput(message); 59 | // if there is a non-empty message and a socket connection 60 | if (message && connected) { 61 | $inputMessage.val(''); 62 | addChatMessage({ 63 | username: username, 64 | message: message 65 | }); 66 | // tell server to execute 'new message' and send along one parameter 67 | socket.emit('new message', message); 68 | } 69 | } 70 | 71 | // Log a message 72 | function log (message, options) { 73 | var $el = $('
  • ').addClass('log').text(message); 74 | addMessageElement($el, options); 75 | } 76 | 77 | // Adds the visual chat message to the message list 78 | function addChatMessage (data, options) { 79 | // Don't fade the message in if there is an 'X was typing' 80 | var $typingMessages = getTypingMessages(data); 81 | options = options || {}; 82 | if ($typingMessages.length !== 0) { 83 | options.fade = false; 84 | $typingMessages.remove(); 85 | } 86 | 87 | var $usernameDiv = $('') 88 | .text(data.username) 89 | .css('color', getUsernameColor(data.username)); 90 | var $messageBodyDiv = $('') 91 | .text(data.message); 92 | 93 | var typingClass = data.typing ? 'typing' : ''; 94 | var $messageDiv = $('
  • ') 95 | .data('username', data.username) 96 | .addClass(typingClass) 97 | .append($usernameDiv, $messageBodyDiv); 98 | 99 | addMessageElement($messageDiv, options); 100 | } 101 | 102 | // Adds the visual chat typing message 103 | function addChatTyping (data) { 104 | data.typing = true; 105 | data.message = 'is typing'; 106 | addChatMessage(data); 107 | } 108 | 109 | // Removes the visual chat typing message 110 | function removeChatTyping (data) { 111 | getTypingMessages(data).fadeOut(function () { 112 | $(this).remove(); 113 | }); 114 | } 115 | 116 | // Adds a message element to the messages and scrolls to the bottom 117 | // el - The element to add as a message 118 | // options.fade - If the element should fade-in (default = true) 119 | // options.prepend - If the element should prepend 120 | // all other messages (default = false) 121 | function addMessageElement (el, options) { 122 | var $el = $(el); 123 | 124 | // Setup default options 125 | if (!options) { 126 | options = {}; 127 | } 128 | if (typeof options.fade === 'undefined') { 129 | options.fade = true; 130 | } 131 | if (typeof options.prepend === 'undefined') { 132 | options.prepend = false; 133 | } 134 | 135 | // Apply options 136 | if (options.fade) { 137 | $el.hide().fadeIn(FADE_TIME); 138 | } 139 | if (options.prepend) { 140 | $messages.prepend($el); 141 | } else { 142 | $messages.append($el); 143 | } 144 | $messages[0].scrollTop = $messages[0].scrollHeight; 145 | } 146 | 147 | // Prevents input from having injected markup 148 | function cleanInput (input) { 149 | return $('
    ').text(input).text(); 150 | } 151 | 152 | // Updates the typing event 153 | function updateTyping () { 154 | if (connected) { 155 | if (!typing) { 156 | typing = true; 157 | socket.emit('typing'); 158 | } 159 | lastTypingTime = (new Date()).getTime(); 160 | 161 | setTimeout(function () { 162 | var typingTimer = (new Date()).getTime(); 163 | var timeDiff = typingTimer - lastTypingTime; 164 | if (timeDiff >= TYPING_TIMER_LENGTH && typing) { 165 | socket.emit('stop typing'); 166 | typing = false; 167 | } 168 | }, TYPING_TIMER_LENGTH); 169 | } 170 | } 171 | 172 | // Gets the 'X is typing' messages of a user 173 | function getTypingMessages (data) { 174 | return $('.typing.message').filter(function (i) { 175 | return $(this).data('username') === data.username; 176 | }); 177 | } 178 | 179 | // Gets the color of a username through our hash function 180 | function getUsernameColor (username) { 181 | // Compute hash code 182 | var hash = 7; 183 | for (var i = 0; i < username.length; i++) { 184 | hash = username.charCodeAt(i) + (hash << 5) - hash; 185 | } 186 | // Calculate color 187 | var index = Math.abs(hash % COLORS.length); 188 | return COLORS[index]; 189 | } 190 | 191 | // Keyboard events 192 | 193 | $window.keydown(function (event) { 194 | // Auto-focus the current input when a key is typed 195 | if (!(event.ctrlKey || event.metaKey || event.altKey)) { 196 | $currentInput.focus(); 197 | } 198 | // When the client hits ENTER on their keyboard 199 | if (event.which === 13) { 200 | if (username) { 201 | sendMessage(); 202 | socket.emit('stop typing'); 203 | typing = false; 204 | } else { 205 | setUsername(); 206 | } 207 | } 208 | }); 209 | 210 | $inputMessage.on('input', function() { 211 | updateTyping(); 212 | }); 213 | 214 | // Click events 215 | 216 | // Focus input when clicking anywhere on login page 217 | $loginPage.click(function () { 218 | $currentInput.focus(); 219 | }); 220 | 221 | // Focus input when clicking on the message input's border 222 | $inputMessage.click(function () { 223 | $inputMessage.focus(); 224 | }); 225 | 226 | // Socket events 227 | 228 | // Whenever the server emits 'login', log the login message 229 | socket.on('login', function (data) { 230 | connected = true; 231 | // Display the welcome message 232 | var message = "Welcome to Socket.IO Chat – "; 233 | log(message, { 234 | prepend: true 235 | }); 236 | addParticipantsMessage(data); 237 | }); 238 | 239 | // Whenever the server emits 'new message', update the chat body 240 | socket.on('new message', function (data) { 241 | addChatMessage(data); 242 | }); 243 | 244 | // Whenever the server emits 'user joined', log it in the chat body 245 | socket.on('user joined', function (data) { 246 | log(data.username + ' joined'); 247 | addParticipantsMessage(data); 248 | }); 249 | 250 | // Whenever the server emits 'user left', log it in the chat body 251 | socket.on('user left', function (data) { 252 | log(data.username + ' left'); 253 | addParticipantsMessage(data); 254 | removeChatTyping(data); 255 | }); 256 | 257 | // Whenever the server emits 'typing', show the typing message 258 | socket.on('typing', function (data) { 259 | addChatTyping(data); 260 | }); 261 | 262 | // Whenever the server emits 'stop typing', kill the typing message 263 | socket.on('stop typing', function (data) { 264 | removeChatTyping(data); 265 | }); 266 | 267 | socket.on('disconnect', function () { 268 | log('you have been disconnected'); 269 | }); 270 | 271 | socket.on('reconnect', function () { 272 | log('you have been reconnected'); 273 | if (username) { 274 | socket.emit('add user', username); 275 | } 276 | }); 277 | 278 | socket.on('reconnect_error', function () { 279 | log('attempt to reconnect has failed'); 280 | }); 281 | 282 | socket.on('my-name-is', function (serverName) { 283 | log('host is now ' + serverName); 284 | }) 285 | 286 | }); 287 | -------------------------------------------------------------------------------- /examples/cluster-httpd/server/public/main.js: -------------------------------------------------------------------------------- 1 | $(function() { 2 | var FADE_TIME = 150; // ms 3 | var TYPING_TIMER_LENGTH = 400; // ms 4 | var COLORS = [ 5 | '#e21400', '#91580f', '#f8a700', '#f78b00', 6 | '#58dc00', '#287b00', '#a8f07a', '#4ae8c4', 7 | '#3b88eb', '#3824aa', '#a700ff', '#d300e7' 8 | ]; 9 | 10 | // Initialize variables 11 | var $window = $(window); 12 | var $usernameInput = $('.usernameInput'); // Input for username 13 | var $messages = $('.messages'); // Messages area 14 | var $inputMessage = $('.inputMessage'); // Input message input box 15 | 16 | var $loginPage = $('.login.page'); // The login page 17 | var $chatPage = $('.chat.page'); // The chatroom page 18 | 19 | // Prompt for setting a username 20 | var username; 21 | var connected = false; 22 | var typing = false; 23 | var lastTypingTime; 24 | var $currentInput = $usernameInput.focus(); 25 | 26 | var socket = io(); 27 | 28 | function addParticipantsMessage (data) { 29 | var message = ''; 30 | if (data.numUsers === 1) { 31 | message += "there's 1 participant"; 32 | } else { 33 | message += "there are " + data.numUsers + " participants"; 34 | } 35 | log(message); 36 | } 37 | 38 | // Sets the client's username 39 | function setUsername () { 40 | username = cleanInput($usernameInput.val().trim()); 41 | 42 | // If the username is valid 43 | if (username) { 44 | $loginPage.fadeOut(); 45 | $chatPage.show(); 46 | $loginPage.off('click'); 47 | $currentInput = $inputMessage.focus(); 48 | 49 | // Tell the server your username 50 | socket.emit('add user', username); 51 | } 52 | } 53 | 54 | // Sends a chat message 55 | function sendMessage () { 56 | var message = $inputMessage.val(); 57 | // Prevent markup from being injected into the message 58 | message = cleanInput(message); 59 | // if there is a non-empty message and a socket connection 60 | if (message && connected) { 61 | $inputMessage.val(''); 62 | addChatMessage({ 63 | username: username, 64 | message: message 65 | }); 66 | // tell server to execute 'new message' and send along one parameter 67 | socket.emit('new message', message); 68 | } 69 | } 70 | 71 | // Log a message 72 | function log (message, options) { 73 | var $el = $('
  • ').addClass('log').text(message); 74 | addMessageElement($el, options); 75 | } 76 | 77 | // Adds the visual chat message to the message list 78 | function addChatMessage (data, options) { 79 | // Don't fade the message in if there is an 'X was typing' 80 | var $typingMessages = getTypingMessages(data); 81 | options = options || {}; 82 | if ($typingMessages.length !== 0) { 83 | options.fade = false; 84 | $typingMessages.remove(); 85 | } 86 | 87 | var $usernameDiv = $('') 88 | .text(data.username) 89 | .css('color', getUsernameColor(data.username)); 90 | var $messageBodyDiv = $('') 91 | .text(data.message); 92 | 93 | var typingClass = data.typing ? 'typing' : ''; 94 | var $messageDiv = $('
  • ') 95 | .data('username', data.username) 96 | .addClass(typingClass) 97 | .append($usernameDiv, $messageBodyDiv); 98 | 99 | addMessageElement($messageDiv, options); 100 | } 101 | 102 | // Adds the visual chat typing message 103 | function addChatTyping (data) { 104 | data.typing = true; 105 | data.message = 'is typing'; 106 | addChatMessage(data); 107 | } 108 | 109 | // Removes the visual chat typing message 110 | function removeChatTyping (data) { 111 | getTypingMessages(data).fadeOut(function () { 112 | $(this).remove(); 113 | }); 114 | } 115 | 116 | // Adds a message element to the messages and scrolls to the bottom 117 | // el - The element to add as a message 118 | // options.fade - If the element should fade-in (default = true) 119 | // options.prepend - If the element should prepend 120 | // all other messages (default = false) 121 | function addMessageElement (el, options) { 122 | var $el = $(el); 123 | 124 | // Setup default options 125 | if (!options) { 126 | options = {}; 127 | } 128 | if (typeof options.fade === 'undefined') { 129 | options.fade = true; 130 | } 131 | if (typeof options.prepend === 'undefined') { 132 | options.prepend = false; 133 | } 134 | 135 | // Apply options 136 | if (options.fade) { 137 | $el.hide().fadeIn(FADE_TIME); 138 | } 139 | if (options.prepend) { 140 | $messages.prepend($el); 141 | } else { 142 | $messages.append($el); 143 | } 144 | $messages[0].scrollTop = $messages[0].scrollHeight; 145 | } 146 | 147 | // Prevents input from having injected markup 148 | function cleanInput (input) { 149 | return $('
    ').text(input).text(); 150 | } 151 | 152 | // Updates the typing event 153 | function updateTyping () { 154 | if (connected) { 155 | if (!typing) { 156 | typing = true; 157 | socket.emit('typing'); 158 | } 159 | lastTypingTime = (new Date()).getTime(); 160 | 161 | setTimeout(function () { 162 | var typingTimer = (new Date()).getTime(); 163 | var timeDiff = typingTimer - lastTypingTime; 164 | if (timeDiff >= TYPING_TIMER_LENGTH && typing) { 165 | socket.emit('stop typing'); 166 | typing = false; 167 | } 168 | }, TYPING_TIMER_LENGTH); 169 | } 170 | } 171 | 172 | // Gets the 'X is typing' messages of a user 173 | function getTypingMessages (data) { 174 | return $('.typing.message').filter(function (i) { 175 | return $(this).data('username') === data.username; 176 | }); 177 | } 178 | 179 | // Gets the color of a username through our hash function 180 | function getUsernameColor (username) { 181 | // Compute hash code 182 | var hash = 7; 183 | for (var i = 0; i < username.length; i++) { 184 | hash = username.charCodeAt(i) + (hash << 5) - hash; 185 | } 186 | // Calculate color 187 | var index = Math.abs(hash % COLORS.length); 188 | return COLORS[index]; 189 | } 190 | 191 | // Keyboard events 192 | 193 | $window.keydown(function (event) { 194 | // Auto-focus the current input when a key is typed 195 | if (!(event.ctrlKey || event.metaKey || event.altKey)) { 196 | $currentInput.focus(); 197 | } 198 | // When the client hits ENTER on their keyboard 199 | if (event.which === 13) { 200 | if (username) { 201 | sendMessage(); 202 | socket.emit('stop typing'); 203 | typing = false; 204 | } else { 205 | setUsername(); 206 | } 207 | } 208 | }); 209 | 210 | $inputMessage.on('input', function() { 211 | updateTyping(); 212 | }); 213 | 214 | // Click events 215 | 216 | // Focus input when clicking anywhere on login page 217 | $loginPage.click(function () { 218 | $currentInput.focus(); 219 | }); 220 | 221 | // Focus input when clicking on the message input's border 222 | $inputMessage.click(function () { 223 | $inputMessage.focus(); 224 | }); 225 | 226 | // Socket events 227 | 228 | // Whenever the server emits 'login', log the login message 229 | socket.on('login', function (data) { 230 | connected = true; 231 | // Display the welcome message 232 | var message = "Welcome to Socket.IO Chat – "; 233 | log(message, { 234 | prepend: true 235 | }); 236 | addParticipantsMessage(data); 237 | }); 238 | 239 | // Whenever the server emits 'new message', update the chat body 240 | socket.on('new message', function (data) { 241 | addChatMessage(data); 242 | }); 243 | 244 | // Whenever the server emits 'user joined', log it in the chat body 245 | socket.on('user joined', function (data) { 246 | log(data.username + ' joined'); 247 | addParticipantsMessage(data); 248 | }); 249 | 250 | // Whenever the server emits 'user left', log it in the chat body 251 | socket.on('user left', function (data) { 252 | log(data.username + ' left'); 253 | addParticipantsMessage(data); 254 | removeChatTyping(data); 255 | }); 256 | 257 | // Whenever the server emits 'typing', show the typing message 258 | socket.on('typing', function (data) { 259 | addChatTyping(data); 260 | }); 261 | 262 | // Whenever the server emits 'stop typing', kill the typing message 263 | socket.on('stop typing', function (data) { 264 | removeChatTyping(data); 265 | }); 266 | 267 | socket.on('disconnect', function () { 268 | log('you have been disconnected'); 269 | }); 270 | 271 | socket.on('reconnect', function () { 272 | log('you have been reconnected'); 273 | if (username) { 274 | socket.emit('add user', username); 275 | } 276 | }); 277 | 278 | socket.on('reconnect_error', function () { 279 | log('attempt to reconnect has failed'); 280 | }); 281 | 282 | socket.on('my-name-is', function (serverName) { 283 | log('host is now ' + serverName); 284 | }) 285 | 286 | }); 287 | -------------------------------------------------------------------------------- /examples/cluster-nginx/server/public/main.js: -------------------------------------------------------------------------------- 1 | $(function() { 2 | var FADE_TIME = 150; // ms 3 | var TYPING_TIMER_LENGTH = 400; // ms 4 | var COLORS = [ 5 | '#e21400', '#91580f', '#f8a700', '#f78b00', 6 | '#58dc00', '#287b00', '#a8f07a', '#4ae8c4', 7 | '#3b88eb', '#3824aa', '#a700ff', '#d300e7' 8 | ]; 9 | 10 | // Initialize variables 11 | var $window = $(window); 12 | var $usernameInput = $('.usernameInput'); // Input for username 13 | var $messages = $('.messages'); // Messages area 14 | var $inputMessage = $('.inputMessage'); // Input message input box 15 | 16 | var $loginPage = $('.login.page'); // The login page 17 | var $chatPage = $('.chat.page'); // The chatroom page 18 | 19 | // Prompt for setting a username 20 | var username; 21 | var connected = false; 22 | var typing = false; 23 | var lastTypingTime; 24 | var $currentInput = $usernameInput.focus(); 25 | 26 | var socket = io(); 27 | 28 | function addParticipantsMessage (data) { 29 | var message = ''; 30 | if (data.numUsers === 1) { 31 | message += "there's 1 participant"; 32 | } else { 33 | message += "there are " + data.numUsers + " participants"; 34 | } 35 | log(message); 36 | } 37 | 38 | // Sets the client's username 39 | function setUsername () { 40 | username = cleanInput($usernameInput.val().trim()); 41 | 42 | // If the username is valid 43 | if (username) { 44 | $loginPage.fadeOut(); 45 | $chatPage.show(); 46 | $loginPage.off('click'); 47 | $currentInput = $inputMessage.focus(); 48 | 49 | // Tell the server your username 50 | socket.emit('add user', username); 51 | } 52 | } 53 | 54 | // Sends a chat message 55 | function sendMessage () { 56 | var message = $inputMessage.val(); 57 | // Prevent markup from being injected into the message 58 | message = cleanInput(message); 59 | // if there is a non-empty message and a socket connection 60 | if (message && connected) { 61 | $inputMessage.val(''); 62 | addChatMessage({ 63 | username: username, 64 | message: message 65 | }); 66 | // tell server to execute 'new message' and send along one parameter 67 | socket.emit('new message', message); 68 | } 69 | } 70 | 71 | // Log a message 72 | function log (message, options) { 73 | var $el = $('
  • ').addClass('log').text(message); 74 | addMessageElement($el, options); 75 | } 76 | 77 | // Adds the visual chat message to the message list 78 | function addChatMessage (data, options) { 79 | // Don't fade the message in if there is an 'X was typing' 80 | var $typingMessages = getTypingMessages(data); 81 | options = options || {}; 82 | if ($typingMessages.length !== 0) { 83 | options.fade = false; 84 | $typingMessages.remove(); 85 | } 86 | 87 | var $usernameDiv = $('') 88 | .text(data.username) 89 | .css('color', getUsernameColor(data.username)); 90 | var $messageBodyDiv = $('') 91 | .text(data.message); 92 | 93 | var typingClass = data.typing ? 'typing' : ''; 94 | var $messageDiv = $('
  • ') 95 | .data('username', data.username) 96 | .addClass(typingClass) 97 | .append($usernameDiv, $messageBodyDiv); 98 | 99 | addMessageElement($messageDiv, options); 100 | } 101 | 102 | // Adds the visual chat typing message 103 | function addChatTyping (data) { 104 | data.typing = true; 105 | data.message = 'is typing'; 106 | addChatMessage(data); 107 | } 108 | 109 | // Removes the visual chat typing message 110 | function removeChatTyping (data) { 111 | getTypingMessages(data).fadeOut(function () { 112 | $(this).remove(); 113 | }); 114 | } 115 | 116 | // Adds a message element to the messages and scrolls to the bottom 117 | // el - The element to add as a message 118 | // options.fade - If the element should fade-in (default = true) 119 | // options.prepend - If the element should prepend 120 | // all other messages (default = false) 121 | function addMessageElement (el, options) { 122 | var $el = $(el); 123 | 124 | // Setup default options 125 | if (!options) { 126 | options = {}; 127 | } 128 | if (typeof options.fade === 'undefined') { 129 | options.fade = true; 130 | } 131 | if (typeof options.prepend === 'undefined') { 132 | options.prepend = false; 133 | } 134 | 135 | // Apply options 136 | if (options.fade) { 137 | $el.hide().fadeIn(FADE_TIME); 138 | } 139 | if (options.prepend) { 140 | $messages.prepend($el); 141 | } else { 142 | $messages.append($el); 143 | } 144 | $messages[0].scrollTop = $messages[0].scrollHeight; 145 | } 146 | 147 | // Prevents input from having injected markup 148 | function cleanInput (input) { 149 | return $('
    ').text(input).text(); 150 | } 151 | 152 | // Updates the typing event 153 | function updateTyping () { 154 | if (connected) { 155 | if (!typing) { 156 | typing = true; 157 | socket.emit('typing'); 158 | } 159 | lastTypingTime = (new Date()).getTime(); 160 | 161 | setTimeout(function () { 162 | var typingTimer = (new Date()).getTime(); 163 | var timeDiff = typingTimer - lastTypingTime; 164 | if (timeDiff >= TYPING_TIMER_LENGTH && typing) { 165 | socket.emit('stop typing'); 166 | typing = false; 167 | } 168 | }, TYPING_TIMER_LENGTH); 169 | } 170 | } 171 | 172 | // Gets the 'X is typing' messages of a user 173 | function getTypingMessages (data) { 174 | return $('.typing.message').filter(function (i) { 175 | return $(this).data('username') === data.username; 176 | }); 177 | } 178 | 179 | // Gets the color of a username through our hash function 180 | function getUsernameColor (username) { 181 | // Compute hash code 182 | var hash = 7; 183 | for (var i = 0; i < username.length; i++) { 184 | hash = username.charCodeAt(i) + (hash << 5) - hash; 185 | } 186 | // Calculate color 187 | var index = Math.abs(hash % COLORS.length); 188 | return COLORS[index]; 189 | } 190 | 191 | // Keyboard events 192 | 193 | $window.keydown(function (event) { 194 | // Auto-focus the current input when a key is typed 195 | if (!(event.ctrlKey || event.metaKey || event.altKey)) { 196 | $currentInput.focus(); 197 | } 198 | // When the client hits ENTER on their keyboard 199 | if (event.which === 13) { 200 | if (username) { 201 | sendMessage(); 202 | socket.emit('stop typing'); 203 | typing = false; 204 | } else { 205 | setUsername(); 206 | } 207 | } 208 | }); 209 | 210 | $inputMessage.on('input', function() { 211 | updateTyping(); 212 | }); 213 | 214 | // Click events 215 | 216 | // Focus input when clicking anywhere on login page 217 | $loginPage.click(function () { 218 | $currentInput.focus(); 219 | }); 220 | 221 | // Focus input when clicking on the message input's border 222 | $inputMessage.click(function () { 223 | $inputMessage.focus(); 224 | }); 225 | 226 | // Socket events 227 | 228 | // Whenever the server emits 'login', log the login message 229 | socket.on('login', function (data) { 230 | connected = true; 231 | // Display the welcome message 232 | var message = "Welcome to Socket.IO Chat – "; 233 | log(message, { 234 | prepend: true 235 | }); 236 | addParticipantsMessage(data); 237 | }); 238 | 239 | // Whenever the server emits 'new message', update the chat body 240 | socket.on('new message', function (data) { 241 | addChatMessage(data); 242 | }); 243 | 244 | // Whenever the server emits 'user joined', log it in the chat body 245 | socket.on('user joined', function (data) { 246 | log(data.username + ' joined'); 247 | addParticipantsMessage(data); 248 | }); 249 | 250 | // Whenever the server emits 'user left', log it in the chat body 251 | socket.on('user left', function (data) { 252 | log(data.username + ' left'); 253 | addParticipantsMessage(data); 254 | removeChatTyping(data); 255 | }); 256 | 257 | // Whenever the server emits 'typing', show the typing message 258 | socket.on('typing', function (data) { 259 | addChatTyping(data); 260 | }); 261 | 262 | // Whenever the server emits 'stop typing', kill the typing message 263 | socket.on('stop typing', function (data) { 264 | removeChatTyping(data); 265 | }); 266 | 267 | socket.on('disconnect', function () { 268 | log('you have been disconnected'); 269 | }); 270 | 271 | socket.on('reconnect', function () { 272 | log('you have been reconnected'); 273 | if (username) { 274 | socket.emit('add user', username); 275 | } 276 | }); 277 | 278 | socket.on('reconnect_error', function () { 279 | log('attempt to reconnect has failed'); 280 | }); 281 | 282 | socket.on('my-name-is', function (serverName) { 283 | log('host is now ' + serverName); 284 | }) 285 | 286 | }); 287 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Module dependencies. 4 | */ 5 | 6 | var http = require('http'); 7 | var read = require('fs').readFileSync; 8 | var path = require('path'); 9 | var exists = require('fs').existsSync; 10 | var engine = require('engine.io'); 11 | var client = require('socket.io-client'); 12 | var clientVersion = require('socket.io-client/package').version; 13 | var Client = require('./client'); 14 | var Emitter = require('events').EventEmitter; 15 | var Namespace = require('./namespace'); 16 | var Adapter = require('socket.io-adapter'); 17 | var parser = require('socket.io-parser'); 18 | var debug = require('debug')('socket.io:server'); 19 | var url = require('url'); 20 | 21 | /** 22 | * Module exports. 23 | */ 24 | 25 | module.exports = Server; 26 | 27 | /** 28 | * Socket.IO client source. 29 | */ 30 | 31 | var clientSource = undefined; 32 | var clientSourceMap = undefined; 33 | 34 | /** 35 | * Server constructor. 36 | * 37 | * @param {http.Server|Number|Object} srv http server, port or options 38 | * @param {Object} [opts] 39 | * @api public 40 | */ 41 | 42 | function Server(srv, opts){ 43 | if (!(this instanceof Server)) return new Server(srv, opts); 44 | if ('object' == typeof srv && srv instanceof Object && !srv.listen) { 45 | opts = srv; 46 | srv = null; 47 | } 48 | opts = opts || {}; 49 | this.nsps = {}; 50 | this.path(opts.path || '/socket.io'); 51 | this.serveClient(false !== opts.serveClient); 52 | this.parser = opts.parser || parser; 53 | this.encoder = new this.parser.Encoder(); 54 | this.adapter(opts.adapter || Adapter); 55 | this.origins(opts.origins || '*:*'); 56 | this.sockets = this.of('/'); 57 | if (srv) this.attach(srv, opts); 58 | } 59 | 60 | /** 61 | * Server request verification function, that checks for allowed origins 62 | * 63 | * @param {http.IncomingMessage} req request 64 | * @param {Function} fn callback to be called with the result: `fn(err, success)` 65 | */ 66 | 67 | Server.prototype.checkRequest = function(req, fn) { 68 | var origin = req.headers.origin || req.headers.referer; 69 | 70 | // file:// URLs produce a null Origin which can't be authorized via echo-back 71 | if ('null' == origin || null == origin) origin = '*'; 72 | 73 | if (!!origin && typeof(this._origins) == 'function') return this._origins(origin, fn); 74 | if (this._origins.indexOf('*:*') !== -1) return fn(null, true); 75 | if (origin) { 76 | try { 77 | var parts = url.parse(origin); 78 | var defaultPort = 'https:' == parts.protocol ? 443 : 80; 79 | parts.port = parts.port != null 80 | ? parts.port 81 | : defaultPort; 82 | var ok = 83 | ~this._origins.indexOf(parts.hostname + ':' + parts.port) || 84 | ~this._origins.indexOf(parts.hostname + ':*') || 85 | ~this._origins.indexOf('*:' + parts.port); 86 | return fn(null, !!ok); 87 | } catch (ex) { 88 | } 89 | } 90 | fn(null, false); 91 | }; 92 | 93 | /** 94 | * Sets/gets whether client code is being served. 95 | * 96 | * @param {Boolean} v whether to serve client code 97 | * @return {Server|Boolean} self when setting or value when getting 98 | * @api public 99 | */ 100 | 101 | Server.prototype.serveClient = function(v){ 102 | if (!arguments.length) return this._serveClient; 103 | this._serveClient = v; 104 | var resolvePath = function(file){ 105 | var filepath = path.resolve(__dirname, './../../', file); 106 | if (exists(filepath)) { 107 | return filepath; 108 | } 109 | return require.resolve(file); 110 | }; 111 | if (v && !clientSource) { 112 | clientSource = read(resolvePath( 'socket.io-client/dist/socket.io.min.js'), 'utf-8'); 113 | try { 114 | clientSourceMap = read(resolvePath( 'socket.io-client/dist/socket.io.js.map'), 'utf-8'); 115 | } catch(err) { 116 | debug('could not load sourcemap file'); 117 | } 118 | } 119 | return this; 120 | }; 121 | 122 | /** 123 | * Old settings for backwards compatibility 124 | */ 125 | 126 | var oldSettings = { 127 | "transports": "transports", 128 | "heartbeat timeout": "pingTimeout", 129 | "heartbeat interval": "pingInterval", 130 | "destroy buffer size": "maxHttpBufferSize" 131 | }; 132 | 133 | /** 134 | * Backwards compatibility. 135 | * 136 | * @api public 137 | */ 138 | 139 | Server.prototype.set = function(key, val){ 140 | if ('authorization' == key && val) { 141 | this.use(function(socket, next) { 142 | val(socket.request, function(err, authorized) { 143 | if (err) return next(new Error(err)); 144 | if (!authorized) return next(new Error('Not authorized')); 145 | next(); 146 | }); 147 | }); 148 | } else if ('origins' == key && val) { 149 | this.origins(val); 150 | } else if ('resource' == key) { 151 | this.path(val); 152 | } else if (oldSettings[key] && this.eio[oldSettings[key]]) { 153 | this.eio[oldSettings[key]] = val; 154 | } else { 155 | console.error('Option %s is not valid. Please refer to the README.', key); 156 | } 157 | 158 | return this; 159 | }; 160 | 161 | /** 162 | * Sets the client serving path. 163 | * 164 | * @param {String} v pathname 165 | * @return {Server|String} self when setting or value when getting 166 | * @api public 167 | */ 168 | 169 | Server.prototype.path = function(v){ 170 | if (!arguments.length) return this._path; 171 | this._path = v.replace(/\/$/, ''); 172 | return this; 173 | }; 174 | 175 | /** 176 | * Sets the adapter for rooms. 177 | * 178 | * @param {Adapter} v pathname 179 | * @return {Server|Adapter} self when setting or value when getting 180 | * @api public 181 | */ 182 | 183 | Server.prototype.adapter = function(v){ 184 | if (!arguments.length) return this._adapter; 185 | this._adapter = v; 186 | for (var i in this.nsps) { 187 | if (this.nsps.hasOwnProperty(i)) { 188 | this.nsps[i].initAdapter(); 189 | } 190 | } 191 | return this; 192 | }; 193 | 194 | /** 195 | * Sets the allowed origins for requests. 196 | * 197 | * @param {String} v origins 198 | * @return {Server|Adapter} self when setting or value when getting 199 | * @api public 200 | */ 201 | 202 | Server.prototype.origins = function(v){ 203 | if (!arguments.length) return this._origins; 204 | 205 | this._origins = v; 206 | return this; 207 | }; 208 | 209 | /** 210 | * Attaches socket.io to a server or port. 211 | * 212 | * @param {http.Server|Number} server or port 213 | * @param {Object} options passed to engine.io 214 | * @return {Server} self 215 | * @api public 216 | */ 217 | 218 | Server.prototype.listen = 219 | Server.prototype.attach = function(srv, opts){ 220 | if ('function' == typeof srv) { 221 | var msg = 'You are trying to attach socket.io to an express ' + 222 | 'request handler function. Please pass a http.Server instance.'; 223 | throw new Error(msg); 224 | } 225 | 226 | // handle a port as a string 227 | if (Number(srv) == srv) { 228 | srv = Number(srv); 229 | } 230 | 231 | if ('number' == typeof srv) { 232 | debug('creating http server and binding to %d', srv); 233 | var port = srv; 234 | srv = http.Server(function(req, res){ 235 | res.writeHead(404); 236 | res.end(); 237 | }); 238 | srv.listen(port); 239 | 240 | } 241 | 242 | // set engine.io path to `/socket.io` 243 | opts = opts || {}; 244 | opts.path = opts.path || this.path(); 245 | // set origins verification 246 | opts.allowRequest = opts.allowRequest || this.checkRequest.bind(this); 247 | 248 | var self = this; 249 | 250 | var connectPacket = { type: parser.CONNECT, nsp: '/' }; 251 | this.encoder.encode(connectPacket, function (encodedPacket){ 252 | // the CONNECT packet will be merged with Engine.IO handshake, 253 | // to reduce the number of round trips 254 | opts.initialPacket = encodedPacket; 255 | 256 | // initialize engine 257 | debug('creating engine.io instance with opts %j', opts); 258 | self.eio = engine.attach(srv, opts); 259 | 260 | // attach static file serving 261 | if (self._serveClient) self.attachServe(srv); 262 | 263 | // Export http server 264 | self.httpServer = srv; 265 | 266 | // bind to engine events 267 | self.bind(self.eio); 268 | }); 269 | 270 | return this; 271 | }; 272 | 273 | /** 274 | * Attaches the static file serving. 275 | * 276 | * @param {Function|http.Server} srv http server 277 | * @api private 278 | */ 279 | 280 | Server.prototype.attachServe = function(srv){ 281 | debug('attaching client serving req handler'); 282 | var url = this._path + '/socket.io.js'; 283 | var urlMap = this._path + '/socket.io.js.map'; 284 | var evs = srv.listeners('request').slice(0); 285 | var self = this; 286 | srv.removeAllListeners('request'); 287 | srv.on('request', function(req, res) { 288 | if (0 === req.url.indexOf(urlMap)) { 289 | self.serveMap(req, res); 290 | } else if (0 === req.url.indexOf(url)) { 291 | self.serve(req, res); 292 | } else { 293 | for (var i = 0; i < evs.length; i++) { 294 | evs[i].call(srv, req, res); 295 | } 296 | } 297 | }); 298 | }; 299 | 300 | /** 301 | * Handles a request serving `/socket.io.js` 302 | * 303 | * @param {http.Request} req 304 | * @param {http.Response} res 305 | * @api private 306 | */ 307 | 308 | Server.prototype.serve = function(req, res){ 309 | // Per the standard, ETags must be quoted: 310 | // https://tools.ietf.org/html/rfc7232#section-2.3 311 | var expectedEtag = '"' + clientVersion + '"'; 312 | 313 | var etag = req.headers['if-none-match']; 314 | if (etag) { 315 | if (expectedEtag == etag) { 316 | debug('serve client 304'); 317 | res.writeHead(304); 318 | res.end(); 319 | return; 320 | } 321 | } 322 | 323 | debug('serve client source'); 324 | res.setHeader('Content-Type', 'application/javascript'); 325 | res.setHeader('ETag', expectedEtag); 326 | res.setHeader('X-SourceMap', 'socket.io.js.map'); 327 | res.writeHead(200); 328 | res.end(clientSource); 329 | }; 330 | 331 | /** 332 | * Handles a request serving `/socket.io.js.map` 333 | * 334 | * @param {http.Request} req 335 | * @param {http.Response} res 336 | * @api private 337 | */ 338 | 339 | Server.prototype.serveMap = function(req, res){ 340 | // Per the standard, ETags must be quoted: 341 | // https://tools.ietf.org/html/rfc7232#section-2.3 342 | var expectedEtag = '"' + clientVersion + '"'; 343 | 344 | var etag = req.headers['if-none-match']; 345 | if (etag) { 346 | if (expectedEtag == etag) { 347 | debug('serve client 304'); 348 | res.writeHead(304); 349 | res.end(); 350 | return; 351 | } 352 | } 353 | 354 | debug('serve client sourcemap'); 355 | res.setHeader('Content-Type', 'application/json'); 356 | res.setHeader('ETag', expectedEtag); 357 | res.writeHead(200); 358 | res.end(clientSourceMap); 359 | }; 360 | 361 | /** 362 | * Binds socket.io to an engine.io instance. 363 | * 364 | * @param {engine.Server} engine engine.io (or compatible) server 365 | * @return {Server} self 366 | * @api public 367 | */ 368 | 369 | Server.prototype.bind = function(engine){ 370 | this.engine = engine; 371 | this.engine.on('connection', this.onconnection.bind(this)); 372 | return this; 373 | }; 374 | 375 | /** 376 | * Called with each incoming transport connection. 377 | * 378 | * @param {engine.Socket} conn 379 | * @return {Server} self 380 | * @api public 381 | */ 382 | 383 | Server.prototype.onconnection = function(conn){ 384 | debug('incoming connection with id %s', conn.id); 385 | var client = new Client(this, conn); 386 | client.connect('/'); 387 | return this; 388 | }; 389 | 390 | /** 391 | * Looks up a namespace. 392 | * 393 | * @param {String} name nsp name 394 | * @param {Function} [fn] optional, nsp `connection` ev handler 395 | * @api public 396 | */ 397 | 398 | Server.prototype.of = function(name, fn){ 399 | if (String(name)[0] !== '/') name = '/' + name; 400 | 401 | var nsp = this.nsps[name]; 402 | if (!nsp) { 403 | debug('initializing namespace %s', name); 404 | nsp = new Namespace(this, name); 405 | this.nsps[name] = nsp; 406 | } 407 | if (fn) nsp.on('connect', fn); 408 | return nsp; 409 | }; 410 | 411 | /** 412 | * Closes server connection 413 | * 414 | * @param {Function} [fn] optional, called as `fn([err])` on error OR all conns closed 415 | * @api public 416 | */ 417 | 418 | Server.prototype.close = function(fn){ 419 | for (var id in this.nsps['/'].sockets) { 420 | if (this.nsps['/'].sockets.hasOwnProperty(id)) { 421 | this.nsps['/'].sockets[id].onclose(); 422 | } 423 | } 424 | 425 | this.engine.close(); 426 | 427 | if (this.httpServer) { 428 | this.httpServer.close(fn); 429 | } else { 430 | fn && fn(); 431 | } 432 | }; 433 | 434 | /** 435 | * Expose main namespace (/). 436 | */ 437 | 438 | var emitterMethods = Object.keys(Emitter.prototype).filter(function(key){ 439 | return typeof Emitter.prototype[key] === 'function'; 440 | }); 441 | 442 | emitterMethods.concat(['to', 'in', 'use', 'send', 'write', 'clients', 'compress']).forEach(function(fn){ 443 | Server.prototype[fn] = function(){ 444 | return this.sockets[fn].apply(this.sockets, arguments); 445 | }; 446 | }); 447 | 448 | Namespace.flags.forEach(function(flag){ 449 | Object.defineProperty(Server.prototype, flag, { 450 | get: function() { 451 | this.sockets.flags = this.sockets.flags || {}; 452 | this.sockets.flags[flag] = true; 453 | return this; 454 | } 455 | }); 456 | }); 457 | 458 | /** 459 | * BC with `io.listen` 460 | */ 461 | 462 | Server.listen = Server; 463 | -------------------------------------------------------------------------------- /lib/socket.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Module dependencies. 4 | */ 5 | 6 | var Emitter = require('events').EventEmitter; 7 | var parser = require('socket.io-parser'); 8 | var url = require('url'); 9 | var debug = require('debug')('socket.io:socket'); 10 | var hasBin = require('has-binary'); 11 | var assign = require('object-assign'); 12 | 13 | /** 14 | * Module exports. 15 | */ 16 | 17 | module.exports = exports = Socket; 18 | 19 | /** 20 | * Blacklisted events. 21 | * 22 | * @api public 23 | */ 24 | 25 | exports.events = [ 26 | 'error', 27 | 'connect', 28 | 'disconnect', 29 | 'disconnecting', 30 | 'newListener', 31 | 'removeListener' 32 | ]; 33 | 34 | /** 35 | * Flags. 36 | * 37 | * @api private 38 | */ 39 | 40 | var flags = [ 41 | 'json', 42 | 'volatile', 43 | 'broadcast' 44 | ]; 45 | 46 | /** 47 | * `EventEmitter#emit` reference. 48 | */ 49 | 50 | var emit = Emitter.prototype.emit; 51 | 52 | /** 53 | * Interface to a `Client` for a given `Namespace`. 54 | * 55 | * @param {Namespace} nsp 56 | * @param {Client} client 57 | * @api public 58 | */ 59 | 60 | function Socket(nsp, client, query){ 61 | this.nsp = nsp; 62 | this.server = nsp.server; 63 | this.adapter = this.nsp.adapter; 64 | this.id = nsp.name !== '/' ? nsp.name + '#' + client.id : client.id; 65 | this.client = client; 66 | this.conn = client.conn; 67 | this.rooms = {}; 68 | this.acks = {}; 69 | this.connected = true; 70 | this.disconnected = false; 71 | this.handshake = this.buildHandshake(query); 72 | this.fns = []; 73 | this.flags = {}; 74 | this._rooms = []; 75 | } 76 | 77 | /** 78 | * Inherits from `EventEmitter`. 79 | */ 80 | 81 | Socket.prototype.__proto__ = Emitter.prototype; 82 | 83 | /** 84 | * Apply flags from `Socket`. 85 | */ 86 | 87 | flags.forEach(function(flag){ 88 | Object.defineProperty(Socket.prototype, flag, { 89 | get: function() { 90 | this.flags[flag] = true; 91 | return this; 92 | } 93 | }); 94 | }); 95 | 96 | /** 97 | * `request` engine.io shortcut. 98 | * 99 | * @api public 100 | */ 101 | 102 | Object.defineProperty(Socket.prototype, 'request', { 103 | get: function() { 104 | return this.conn.request; 105 | } 106 | }); 107 | 108 | /** 109 | * Builds the `handshake` BC object 110 | * 111 | * @api private 112 | */ 113 | 114 | Socket.prototype.buildHandshake = function(query){ 115 | var self = this; 116 | function buildQuery(){ 117 | var requestQuery = url.parse(self.request.url, true).query; 118 | //if socket-specific query exist, replace query strings in requestQuery 119 | return assign({}, query, requestQuery); 120 | } 121 | return { 122 | headers: this.request.headers, 123 | time: (new Date) + '', 124 | address: this.conn.remoteAddress, 125 | xdomain: !!this.request.headers.origin, 126 | secure: !!this.request.connection.encrypted, 127 | issued: +(new Date), 128 | url: this.request.url, 129 | query: buildQuery() 130 | }; 131 | }; 132 | 133 | /** 134 | * Emits to this client. 135 | * 136 | * @return {Socket} self 137 | * @api public 138 | */ 139 | 140 | Socket.prototype.emit = function(ev){ 141 | if (~exports.events.indexOf(ev)) { 142 | emit.apply(this, arguments); 143 | } else { 144 | var args = Array.prototype.slice.call(arguments); 145 | var packet = { 146 | type: hasBin(args) ? parser.BINARY_EVENT : parser.EVENT, 147 | data: args 148 | }; 149 | 150 | // access last argument to see if it's an ACK callback 151 | if (typeof args[args.length - 1] === 'function') { 152 | if (this._rooms.length || this.flags.broadcast) { 153 | throw new Error('Callbacks are not supported when broadcasting'); 154 | } 155 | 156 | debug('emitting packet with ack id %d', this.nsp.ids); 157 | this.acks[this.nsp.ids] = args.pop(); 158 | packet.id = this.nsp.ids++; 159 | } 160 | 161 | if (this._rooms.length || this.flags.broadcast) { 162 | this.adapter.broadcast(packet, { 163 | except: [this.id], 164 | rooms: this._rooms, 165 | flags: this.flags 166 | }); 167 | } else { 168 | // dispatch packet 169 | this.packet(packet, this.flags); 170 | } 171 | 172 | // reset flags 173 | this._rooms = []; 174 | this.flags = {}; 175 | } 176 | return this; 177 | }; 178 | 179 | /** 180 | * Targets a room when broadcasting. 181 | * 182 | * @param {String} name 183 | * @return {Socket} self 184 | * @api public 185 | */ 186 | 187 | Socket.prototype.to = 188 | Socket.prototype.in = function(name){ 189 | if (!~this._rooms.indexOf(name)) this._rooms.push(name); 190 | return this; 191 | }; 192 | 193 | /** 194 | * Sends a `message` event. 195 | * 196 | * @return {Socket} self 197 | * @api public 198 | */ 199 | 200 | Socket.prototype.send = 201 | Socket.prototype.write = function(){ 202 | var args = Array.prototype.slice.call(arguments); 203 | args.unshift('message'); 204 | this.emit.apply(this, args); 205 | return this; 206 | }; 207 | 208 | /** 209 | * Writes a packet. 210 | * 211 | * @param {Object} packet object 212 | * @param {Object} opts options 213 | * @api private 214 | */ 215 | 216 | Socket.prototype.packet = function(packet, opts){ 217 | packet.nsp = this.nsp.name; 218 | opts = opts || {}; 219 | opts.compress = false !== opts.compress; 220 | this.client.packet(packet, opts); 221 | }; 222 | 223 | /** 224 | * Joins a room. 225 | * 226 | * @param {String|Array} room or array of rooms 227 | * @param {Function} fn optional, callback 228 | * @return {Socket} self 229 | * @api private 230 | */ 231 | 232 | Socket.prototype.join = function(rooms, fn){ 233 | debug('joining room %s', rooms); 234 | var self = this; 235 | if (!Array.isArray(rooms)) { 236 | rooms = [rooms]; 237 | } 238 | rooms = rooms.filter(function (room) { 239 | return !self.rooms.hasOwnProperty(room); 240 | }); 241 | if (!rooms.length) { 242 | fn && fn(null); 243 | return this; 244 | } 245 | this.adapter.addAll(this.id, rooms, function(err){ 246 | if (err) return fn && fn(err); 247 | debug('joined room %s', rooms); 248 | rooms.forEach(function (room) { 249 | self.rooms[room] = room; 250 | }); 251 | fn && fn(null); 252 | }); 253 | return this; 254 | }; 255 | 256 | /** 257 | * Leaves a room. 258 | * 259 | * @param {String} room 260 | * @param {Function} fn optional, callback 261 | * @return {Socket} self 262 | * @api private 263 | */ 264 | 265 | Socket.prototype.leave = function(room, fn){ 266 | debug('leave room %s', room); 267 | var self = this; 268 | this.adapter.del(this.id, room, function(err){ 269 | if (err) return fn && fn(err); 270 | debug('left room %s', room); 271 | delete self.rooms[room]; 272 | fn && fn(null); 273 | }); 274 | return this; 275 | }; 276 | 277 | /** 278 | * Leave all rooms. 279 | * 280 | * @api private 281 | */ 282 | 283 | Socket.prototype.leaveAll = function(){ 284 | this.adapter.delAll(this.id); 285 | this.rooms = {}; 286 | }; 287 | 288 | /** 289 | * Called by `Namespace` upon successful 290 | * middleware execution (ie: authorization). 291 | * Socket is added to namespace array before 292 | * call to join, so adapters can access it. 293 | * 294 | * @api private 295 | */ 296 | 297 | Socket.prototype.onconnect = function(){ 298 | debug('socket connected - writing packet'); 299 | this.nsp.connected[this.id] = this; 300 | this.join(this.id); 301 | // the CONNECT packet for the default namespace 302 | // has already been sent as an `initialPacket` 303 | if (this.nsp.name !== '/') { 304 | this.packet({ type: parser.CONNECT }); 305 | } 306 | }; 307 | 308 | /** 309 | * Called with each packet. Called by `Client`. 310 | * 311 | * @param {Object} packet 312 | * @api private 313 | */ 314 | 315 | Socket.prototype.onpacket = function(packet){ 316 | debug('got packet %j', packet); 317 | switch (packet.type) { 318 | case parser.EVENT: 319 | this.onevent(packet); 320 | break; 321 | 322 | case parser.BINARY_EVENT: 323 | this.onevent(packet); 324 | break; 325 | 326 | case parser.ACK: 327 | this.onack(packet); 328 | break; 329 | 330 | case parser.BINARY_ACK: 331 | this.onack(packet); 332 | break; 333 | 334 | case parser.DISCONNECT: 335 | this.ondisconnect(); 336 | break; 337 | 338 | case parser.ERROR: 339 | this.emit('error', packet.data); 340 | } 341 | }; 342 | 343 | /** 344 | * Called upon event packet. 345 | * 346 | * @param {Object} packet object 347 | * @api private 348 | */ 349 | 350 | Socket.prototype.onevent = function(packet){ 351 | var args = packet.data || []; 352 | debug('emitting event %j', args); 353 | 354 | if (null != packet.id) { 355 | debug('attaching ack callback to event'); 356 | args.push(this.ack(packet.id)); 357 | } 358 | 359 | this.dispatch(args); 360 | }; 361 | 362 | /** 363 | * Produces an ack callback to emit with an event. 364 | * 365 | * @param {Number} id packet id 366 | * @api private 367 | */ 368 | 369 | Socket.prototype.ack = function(id){ 370 | var self = this; 371 | var sent = false; 372 | return function(){ 373 | // prevent double callbacks 374 | if (sent) return; 375 | var args = Array.prototype.slice.call(arguments); 376 | debug('sending ack %j', args); 377 | 378 | var type = hasBin(args) ? parser.BINARY_ACK : parser.ACK; 379 | self.packet({ 380 | id: id, 381 | type: type, 382 | data: args 383 | }); 384 | 385 | sent = true; 386 | }; 387 | }; 388 | 389 | /** 390 | * Called upon ack packet. 391 | * 392 | * @api private 393 | */ 394 | 395 | Socket.prototype.onack = function(packet){ 396 | var ack = this.acks[packet.id]; 397 | if ('function' == typeof ack) { 398 | debug('calling ack %s with %j', packet.id, packet.data); 399 | ack.apply(this, packet.data); 400 | delete this.acks[packet.id]; 401 | } else { 402 | debug('bad ack %s', packet.id); 403 | } 404 | }; 405 | 406 | /** 407 | * Called upon client disconnect packet. 408 | * 409 | * @api private 410 | */ 411 | 412 | Socket.prototype.ondisconnect = function(){ 413 | debug('got disconnect packet'); 414 | this.onclose('client namespace disconnect'); 415 | }; 416 | 417 | /** 418 | * Handles a client error. 419 | * 420 | * @api private 421 | */ 422 | 423 | Socket.prototype.onerror = function(err){ 424 | if (this.listeners('error').length) { 425 | this.emit('error', err); 426 | } else { 427 | console.error('Missing error handler on `socket`.'); 428 | console.error(err.stack); 429 | } 430 | }; 431 | 432 | /** 433 | * Called upon closing. Called by `Client`. 434 | * 435 | * @param {String} reason 436 | * @throw {Error} optional error object 437 | * @api private 438 | */ 439 | 440 | Socket.prototype.onclose = function(reason){ 441 | if (!this.connected) return this; 442 | debug('closing socket - reason %s', reason); 443 | this.emit('disconnecting', reason); 444 | this.leaveAll(); 445 | this.nsp.remove(this); 446 | this.client.remove(this); 447 | this.connected = false; 448 | this.disconnected = true; 449 | delete this.nsp.connected[this.id]; 450 | this.emit('disconnect', reason); 451 | }; 452 | 453 | /** 454 | * Produces an `error` packet. 455 | * 456 | * @param {Object} err error object 457 | * @api private 458 | */ 459 | 460 | Socket.prototype.error = function(err){ 461 | this.packet({ type: parser.ERROR, data: err }); 462 | }; 463 | 464 | /** 465 | * Disconnects this client. 466 | * 467 | * @param {Boolean} close if `true`, closes the underlying connection 468 | * @return {Socket} self 469 | * @api public 470 | */ 471 | 472 | Socket.prototype.disconnect = function(close){ 473 | if (!this.connected) return this; 474 | if (close) { 475 | this.client.disconnect(); 476 | } else { 477 | this.packet({ type: parser.DISCONNECT }); 478 | this.onclose('server namespace disconnect'); 479 | } 480 | return this; 481 | }; 482 | 483 | /** 484 | * Sets the compress flag. 485 | * 486 | * @param {Boolean} compress if `true`, compresses the sending data 487 | * @return {Socket} self 488 | * @api public 489 | */ 490 | 491 | Socket.prototype.compress = function(compress){ 492 | this.flags.compress = compress; 493 | return this; 494 | }; 495 | 496 | /** 497 | * Dispatch incoming event to socket listeners. 498 | * 499 | * @param {Array} event that will get emitted 500 | * @api private 501 | */ 502 | 503 | Socket.prototype.dispatch = function(event){ 504 | debug('dispatching an event %j', event); 505 | var self = this; 506 | function dispatchSocket(err) { 507 | process.nextTick(function(){ 508 | if (err) { 509 | return self.error(err.data || err.message); 510 | } 511 | emit.apply(self, event); 512 | }); 513 | } 514 | this.run(event, dispatchSocket); 515 | }; 516 | 517 | /** 518 | * Sets up socket middleware. 519 | * 520 | * @param {Function} middleware function (event, next) 521 | * @return {Socket} self 522 | * @api public 523 | */ 524 | 525 | Socket.prototype.use = function(fn){ 526 | this.fns.push(fn); 527 | return this; 528 | }; 529 | 530 | /** 531 | * Executes the middleware for an incoming event. 532 | * 533 | * @param {Array} event that will get emitted 534 | * @param {Function} last fn call in the middleware 535 | * @api private 536 | */ 537 | Socket.prototype.run = function(event, fn){ 538 | var fns = this.fns.slice(0); 539 | if (!fns.length) return fn(null); 540 | 541 | function run(i){ 542 | fns[i](event, function(err){ 543 | // upon error, short-circuit 544 | if (err) return fn(err); 545 | 546 | // if no middleware left, summon callback 547 | if (!fns[i + 1]) return fn(null); 548 | 549 | // go on to next 550 | run(i + 1); 551 | }); 552 | } 553 | 554 | run(0); 555 | }; 556 | -------------------------------------------------------------------------------- /History.md: -------------------------------------------------------------------------------- 1 | 2 | 1.7.2 / 2016-12-11 3 | =================== 4 | 5 | * [chore] Bump engine.io to version 1.8.2 (#2782) 6 | * [fix] Fixes socket.use error packet (#2772) 7 | 8 | 1.7.1 / 2016-11-28 9 | =================== 10 | 11 | 1.7.0 / 2016-11-27 12 | =================== 13 | 14 | * [docs] Comment connected socket availability for adapters (#2081) 15 | * [docs] Fixed grammar issues in the README.md (#2159) 16 | * [feature] serve sourcemap for socket.io-client (#2482) 17 | * [feature] Add a `local` flag (#2628) 18 | * [chore] Bump engine.io to version 1.8.1 (#2765) 19 | * [chore] Update client location and serve minified file (#2766) 20 | 21 | 1.6.0 / 2016-11-20 22 | ================== 23 | 24 | * [fix] Make ETag header comply with standard. (#2603) 25 | * [feature] Loading client script on demand. (#2567) 26 | * [test] Fix leaking clientSocket (#2721) 27 | * [feature] Add support for all event emitter methods (#2601) 28 | * [chore] Update year to 2016 (#2456) 29 | * [feature] Add support for socket middleware (#2306) 30 | * [feature] add support for Server#close(callback) (#2748) 31 | * [fix] Don't drop query variables on handshake (#2745) 32 | * [example] Add disconnection/reconnection logs to the chat example (#2675) 33 | * [perf] Minor code optimizations (#2219) 34 | * [chore] Bump debug to version 2.3.3 (#2754) 35 | * [chore] Bump engine.io to version 1.8.0 (#2755) 36 | * [chore] Bump socket.io-adapter to version 0.5.0 (#2756) 37 | 38 | 1.5.1 / 2016-10-24 39 | ================== 40 | 41 | * [fix] Avoid swallowing exceptions thrown by user event handlers (#2682) 42 | * [test] Use client function to unify `client` in test script (#2731) 43 | * [docs] Add link to LICENSE (#2221) 44 | * [docs] Fix JSDoc of optional parameters (#2465) 45 | * [docs] Fix typo (#2724) 46 | * [docs] Link readme npm package badge to npm registry page (#2612) 47 | * [docs] Minor fixes (#2526) 48 | * [chore] Bump socket.io-parser to 2.3.0 (#2730) 49 | * [chore] Add Github issue and PR templates (#2733) 50 | * [chore] Bump engine.io to 1.7.2 (#2729) 51 | * [chore] Bump socket.io-parser to 2.3.1 (#2734) 52 | 53 | 1.5.0 / 2016-10-06 54 | ================== 55 | 56 | * [feature] stop append /# before id when no namespace (#2508) 57 | * [feature] Add a 'disconnecting' event to access to socket.rooms upon disconnection (#2332) 58 | * [fix] Fix query string management (#2422) 59 | * [fix] add quote to exec paths, prevent error when spaces in path (#2508) 60 | * [docs] Prevent mixup for new programmers (#2599) 61 | * [example] Fix chat display in Firefox (#2477) 62 | * [chore] Add gulp & babel in the build process (#2471) 63 | * [chore] Bump engine.io to 1.7.0 (#2707) 64 | * [chore] Remove unused zuul-ngrok dependency (#2708) 65 | * [chore] Point towards current master of socket.io-client (#2710) 66 | * [chore] Restrict files included in npm package (#2709) 67 | * [chore] Link build badge to master branch (#2549) 68 | 69 | 1.4.8 / 2016-06-23 70 | ================== 71 | 72 | * package: bump `engine.io` 73 | 74 | 1.4.7 / 2016-06-23 75 | ================== 76 | 77 | * package: bump `engine.io` 78 | 79 | 1.4.6 / 2016-05-02 80 | ================== 81 | 82 | * package: bump engine.io 83 | 84 | 1.4.5 / 2016-01-26 85 | ================== 86 | 87 | * fix closing the underlying `http.Server` 88 | 89 | 1.4.4 / 2016-01-10 90 | ================== 91 | 92 | * package: bump `engine.io` 93 | 94 | 1.4.3 / 2016-01-08 95 | ================== 96 | 97 | * bump `socket.io-client` 98 | 99 | 1.4.2 / 2016-01-07 100 | ================== 101 | 102 | * bump `engine.io` 103 | 104 | 1.4.1 / 2016-01-07 105 | ================== 106 | 107 | * version bump 108 | 109 | 1.4.0 / 2015-11-28 110 | ================== 111 | 112 | * socket.io: increase large binary data test timeout 113 | * package: bump `engine.io` for release 114 | * trigger callback even when joining an already joined room 115 | * package: bump parser 116 | * namespace: clear rooms flag after a clients call (fixes #1978) 117 | * package: bump `socket.io-parser` 118 | * fixed tests with large data 119 | * fixed a typo in the example code 120 | * package: bump mocha 121 | * package: bump `has-binary` and `zuul-ngrok` 122 | * package: bump `engine.io` and `socket.io-client` 123 | * README: clarified documentation of Socket.in 124 | * README: fixed up legacy repo links 125 | * test: better timeout for stress test 126 | * socket: don't set request property which has a getter 127 | * removed proxy index file 128 | * support flags on namespace 129 | * improve Socket#packet and Client#packet 130 | * socket: warn node_redis-style about missing `error` 131 | * test: added failing test 132 | * test: increase timeout for large binary data test 133 | * package: bump `has-binary` to work with all objects (fixes #1955) 134 | * fix origin verification default https port [evanlucas] 135 | * support compression [nkzawa] 136 | * changed type of `Client#sockets`, `Namespace#sockets` and `Socket#rooms` to maps (instead of arrays) 137 | 138 | 1.3.7 / 2015-09-21 139 | ================== 140 | 141 | * package: bump `socket.io-client` for node4 compatibility 142 | * package: bump `engine.io` for node4 compatibility 143 | 144 | 1.3.6 / 2015-07-14 145 | ================== 146 | 147 | * package: bump `engine.io` to fix build on windows 148 | 149 | 1.3.5 / 2015-03-03 150 | ================== 151 | 152 | * package: bump `socket.io-parser` 153 | 154 | 1.3.4 / 2015-02-14 155 | ================== 156 | 157 | * package: bump `socket.io-client` 158 | 159 | 1.3.3 / 2015-02-03 160 | ================== 161 | 162 | * socket: warn node_redis-style about missing `error` 163 | * package: bump parser to better handle bad binary packets 164 | 165 | 1.3.2 / 2015-01-19 166 | ================== 167 | 168 | * no change on this release 169 | 170 | 1.3.1 / 2015-01-19 171 | ================== 172 | 173 | * no change on this release 174 | * package: bump `engine.io` 175 | 176 | 1.3.0 / 2015-01-19 177 | ================== 178 | 179 | * package: bump `engine.io` 180 | * add test for reconnection after server restarts [rase-] 181 | * update license with up-to-date year range [fay-jai] 182 | * fix leaving unknown rooms [defunctzombie] 183 | * allow null origins when allowed origins is a function [drewblaisdell] 184 | * fix tests on node 0.11 185 | * package: fix `npm test` to run on windows 186 | * package: bump `debug` v2.1.0 [coderaiser] 187 | * added tests for volatile [rase-] 188 | 189 | 1.2.1 / 2014-11-21 190 | ================== 191 | 192 | * fix protocol violations and improve error handling (GH-1880) 193 | * package: bump `engine.io` for websocket leak fix [3rd-Eden] 194 | * style tweaks 195 | 196 | 1.2.0 / 2014-10-27 197 | ================== 198 | 199 | * package: bump `engine.io` 200 | * downloads badge 201 | * add test to check that empty rooms are autopruned 202 | * added Server#origins(v:Function) description for dynamic CORS 203 | * added test coverage for Server#origins(function) for dynamic CORS 204 | * added optional Server#origins(function) for dynamic CORS 205 | * fix usage example for Server#close 206 | * package: fix main file for example application 'chat' 207 | * package: bump `socket.io-parser` 208 | * update README http ctor to createServer() 209 | * bump adapter with a lot of fixes for room bookkeeping 210 | 211 | 1.1.0 / 2014-09-04 212 | ================== 213 | 214 | * examples: minor fix of escaping 215 | * testing for equivalence of namespaces starting with / or without 216 | * update index.js 217 | * added relevant tests 218 | * take "" and "/" as equivalent namespaces on server 219 | * use svg instead of png to get better image quality in readme 220 | * make CI build faster 221 | * fix splice arguments and `socket.rooms` value update in `socket.leaveAll`. 222 | * client cannot connect to non-existing namespaces 223 | * bump engine.io version to get the cached IP address 224 | * fixed handshake object address property and made the test case more strict. 225 | * package: bump `engine.io` 226 | * fixed the failing test where server crashes on disconnect involving connectBuffer 227 | * npmignore: ignore `.gitignore` (fixes #1607) 228 | * test: added failing case for `socket.disconnect` and nsps 229 | * fix repo in package.json 230 | * improve Close documentation 231 | * use ephemeral ports 232 | * fix: We should use the standard http protocol to handler the etag header. 233 | * override default browser font-family for inputs 234 | * update has-binary-data to 1.0.3 235 | * add close specs 236 | * add ability to stop the http server even if not created inside socket.io 237 | * make sure server gets close 238 | * Add test case for checking that reconnect_failed is fired only once upon failure 239 | * package: bump `socket.io-parser` for `component-emitter` dep fix 240 | 241 | 1.0.6 / 2014-06-19 242 | ================== 243 | 244 | * package: bump `socket.io-client` 245 | 246 | 1.0.5 / 2014-06-16 247 | ================== 248 | 249 | * package: bump `engine.io` to fix jsonp `\n` bug and CORS warnings 250 | * index: fix typo [yanatan16] 251 | * add `removeListener` to blacklisted events 252 | * examples: clearer instructions to install chat example 253 | * index: fix namespace `connectBuffer` issue 254 | 255 | 1.0.4 / 2014-06-02 256 | ================== 257 | 258 | * package: bump socket.io-client 259 | 260 | 1.0.3 / 2014-05-31 261 | ================== 262 | 263 | * package: bump `socket.io-client` 264 | * package: bump `socket.io-parser` for binary ACK fix 265 | * package: bump `engine.io` for binary UTF8 fix 266 | * example: fix XSS in chat example 267 | 268 | 1.0.2 / 2014-05-28 269 | ================== 270 | 271 | * package: bump `socket.io-parser` for windows fix 272 | 273 | 1.0.1 / 2014-05-28 274 | ================== 275 | 276 | * bump due to bad npm tag 277 | 278 | 1.0.0 / 2014-05-28 279 | ================== 280 | 281 | * stable release 282 | 283 | 1.0.0-pre5 / 2014-05-22 284 | ======================= 285 | 286 | * package: bump `socket.io-client` for parser fixes 287 | * package: bump `engine.io` 288 | 289 | 1.0.0-pre4 / 2014-05-19 290 | ======================= 291 | 292 | * package: bump client 293 | 294 | 1.0.0-pre3 / 2014-05-17 295 | ======================= 296 | 297 | * package: bump parser 298 | * package: bump engine.io 299 | 300 | 1.0.0-pre2 / 2014-04-27 301 | ======================= 302 | 303 | * package: bump `engine.io` 304 | * added backwards compatible of engine.io maxHttpBufferSize 305 | * added test that server and client using same protocol 306 | * added support for setting allowed origins 307 | * added information about logging 308 | * the set function in server can be used to set some attributes for BC 309 | * fix error in callback call 'done' instead of 'next' in docs 310 | * package: bump `socket.io-parser` 311 | * package: bump `expect.js` 312 | * added some new tests, including binary with acks 313 | 314 | 1.0.0-pre / 2014-03-14 315 | ====================== 316 | 317 | * implemented `engine.io` 318 | * implemented `socket.io-adapter` 319 | * implemented `socket.io-protocol` 320 | * implemented `debug` and improved instrumentation 321 | * added binary support 322 | * added new `require('io')(srv)` signature 323 | * simplified `socket.io-client` serving 324 | 325 | 0.9.14 / 2013-03-29 326 | =================== 327 | 328 | * manager: fix memory leak with SSL [jpallen] 329 | 330 | 0.9.13 / 2012-12-13 331 | =================== 332 | 333 | * package: fixed `base64id` requirement 334 | 335 | 0.9.12 / 2012-12-13 336 | =================== 337 | 338 | * manager: fix for latest node which is returning a clone with `listeners` [viirya] 339 | 340 | 0.9.11 / 2012-11-02 341 | =================== 342 | 343 | * package: move redis to optionalDependenices [3rd-Eden] 344 | * bumped client 345 | 346 | 0.9.10 / 2012-08-10 347 | =================== 348 | 349 | * Don't lowercase log messages 350 | * Always set the HTTP response in case an error should be returned to the client 351 | * Create or destroy the flash policy server on configuration change 352 | * Honour configuration to disable flash policy server 353 | * Add express 3.0 instructions on Readme.md 354 | * Bump client 355 | 356 | 0.9.9 / 2012-08-01 357 | ================== 358 | 359 | * Fixed sync disconnect xhrs handling 360 | * Put license text in its own file (#965) 361 | * Add warning to .listen() to ease the migration to Express 3.x 362 | * Restored compatibility with node 0.4.x 363 | 364 | 0.9.8 / 2012-07-24 365 | ================== 366 | 367 | * Bumped client. 368 | 369 | 0.9.7 / 2012-07-24 370 | ================== 371 | 372 | * Prevent crash when socket leaves a room twice. 373 | * Corrects unsafe usage of for..in 374 | * Fix for node 0.8 with `gzip compression` [vadimi] 375 | * Update redis to support Node 0.8.x 376 | * Made ID generation securely random 377 | * Fix Redis Store race condition in manager onOpen unsubscribe callback 378 | * Fix for EventEmitters always reusing the same Array instance for listeners 379 | 380 | 0.9.6 / 2012-04-17 381 | ================== 382 | 383 | * Fixed XSS in jsonp-polling. 384 | 385 | 0.9.5 / 2012-04-05 386 | ================== 387 | 388 | * Added test for polling and socket close. 389 | * Ensure close upon request close. 390 | * Fix disconnection reason being lost for polling transports. 391 | * Ensure that polling transports work with Connection: close. 392 | * Log disconnection reason. 393 | 394 | 0.9.4 / 2012-04-01 395 | ================== 396 | 397 | * Disconnecting from namespace improvement (#795) [DanielBaulig] 398 | * Bumped client with polling reconnection loop (#438) 399 | 400 | 0.9.3 / 2012-03-28 401 | ================== 402 | 403 | * Fix "Syntax error" on FF Web Console with XHR Polling [mikito] 404 | 405 | 0.9.2 / 2012-03-13 406 | ================== 407 | 408 | * More sensible close `timeout default` (fixes disconnect issue) 409 | 410 | 0.9.1-1 / 2012-03-02 411 | ==================== 412 | 413 | * Bumped client with NPM dependency fix. 414 | 415 | 0.9.1 / 2012-03-02 416 | ================== 417 | 418 | * Changed heartbeat timeout and interval defaults (60 and 25 seconds) 419 | * Make tests work both on 0.4 and 0.6 420 | * Updated client (improvements + bug fixes). 421 | 422 | 0.9.0 / 2012-02-26 423 | ================== 424 | 425 | * Make it possible to use a regexp to match the socket.io resource URL. 426 | We need this because we have to prefix the socket.io URL with a variable ID. 427 | * Supplemental fix to gavinuhma/authfix, it looks like the same Access-Control-Origin logic is needed in the http and xhr-polling transports 428 | * Updated express dep for windows compatibility. 429 | * Combine two substr calls into one in decodePayload to improve performance 430 | * Minor documentation fix 431 | * Minor. Conform to style of other files. 432 | * Switching setting to 'match origin protocol' 433 | * Revert "Fixes leaking Redis subscriptions for #663. The local flag was not getting passed through onClientDisconnect()." 434 | * Revert "Handle leaked dispatch:[id] subscription." 435 | * Merge pull request #667 from dshaw/patch/redis-disconnect 436 | * Handle leaked dispatch:[id] subscription. 437 | * Fixes leaking Redis subscriptions for #663. The local flag was not getting passed through onClientDisconnect(). 438 | * Prevent memory leaking on uncompleted requests & add max post size limitation 439 | * Fix for testcase 440 | * Set Access-Control-Allow-Credentials true, regardless of cookie 441 | * Remove assertvarnish from package as it breaks on 0.6 442 | * Correct irc channel 443 | * Added proper return after reserved field error 444 | * Fixes manager.js failure to close connection after transport error has happened 445 | * Added implicit port 80 for origin checks. fixes #638 446 | * Fixed bug #432 in 0.8.7 447 | * Set Access-Control-Allow-Origin header to origin to enable withCredentials 448 | * Adding configuration variable matchOriginProtocol 449 | * Fixes location mismatch error in Safari. 450 | * Use tty to detect if we should add colors or not by default. 451 | * Updated the package location. 452 | 453 | 0.8.7 / 2011-11-05 454 | ================== 455 | 456 | * Fixed memory leaks in closed clients. 457 | * Fixed memory leaks in namespaces. 458 | * Fixed websocket handling for malformed requests from proxies. [einaros] 459 | * Node 0.6 compatibility. [einaros] [3rd-Eden] 460 | * Adapted tests and examples. 461 | 462 | 0.8.6 / 2011-10-27 463 | ================== 464 | 465 | * Added JSON decoding on jsonp-polling transport. 466 | * Fixed README example. 467 | * Major speed optimizations [3rd-Eden] [einaros] [visionmedia] 468 | * Added decode/encode benchmarks [visionmedia] 469 | * Added support for black-listing client sent events. 470 | * Fixed logging options, closes #540 [3rd-Eden] 471 | * Added vary header for gzip [3rd-Eden] 472 | * Properly cleaned up async websocket / flashsocket tests, after patching node-websocket-client 473 | * Patched to properly shut down when a finishClose call is made during connection establishment 474 | * Added support for socket.io version on url and far-future Expires [3rd-Eden] [getify] 475 | * Began IE10 compatibility [einaros] [tbranyen] 476 | * Misc WebSocket fixes [einaros] 477 | * Added UTF8 to respone headers for htmlfile [3rd-Eden] 478 | 479 | 0.8.5 / 2011-10-07 480 | ================== 481 | 482 | * Added websocket draft HyBi-16 support. [einaros] 483 | * Fixed websocket continuation bugs. [einaros] 484 | * Fixed flashsocket transport name. 485 | * Fixed websocket tests. 486 | * Ensured `parser#decodePayload` doesn't choke. 487 | * Added http referrer verification to manager verifyOrigin. 488 | * Added access control for cross domain xhr handshakes [3rd-Eden] 489 | * Added support for automatic generation of socket.io files [3rd-Eden] 490 | * Added websocket binary support [einaros] 491 | * Added gzip support for socket.io.js [3rd-Eden] 492 | * Expose socket.transport [3rd-Eden] 493 | * Updated client. 494 | 495 | 0.8.4 / 2011-09-06 496 | ================== 497 | 498 | * Client build 499 | 500 | 0.8.3 / 2011-09-03 501 | ================== 502 | 503 | * Fixed `\n` parsing for non-JSON packets (fixes #479). 504 | * Fixed parsing of certain unicode characters (fixes #451). 505 | * Fixed transport message packet logging. 506 | * Fixed emission of `error` event resulting in an uncaught exception if unhandled (fixes #476). 507 | * Fixed; allow for falsy values as the configuration value of `log level` (fixes #491). 508 | * Fixed repository URI in `package.json`. Fixes #504. 509 | * Added text/plain content-type to handshake responses [einaros] 510 | * Improved single byte writes [einaros] 511 | * Updated socket.io-flashsocket default port from 843 to 10843 [3rd-Eden] 512 | * Updated client. 513 | 514 | 0.8.2 / 2011-08-29 515 | ================== 516 | 517 | * Updated client. 518 | 519 | 0.8.1 / 2011-08-29 520 | ================== 521 | 522 | * Fixed utf8 bug in send framing in websocket [einaros] 523 | * Fixed typo in docs [Znarkus] 524 | * Fixed bug in send framing for over 64kB of data in websocket [einaros] 525 | * Corrected ping handling in websocket transport [einaros] 526 | 527 | 0.8.0 / 2011-08-28 528 | ================== 529 | 530 | * Updated to work with two-level websocket versioning. [einaros] 531 | * Added hybi07 support. [einaros] 532 | * Added hybi10 support. [einaros] 533 | * Added http referrer verification to manager.js verifyOrigin. [einaors] 534 | 535 | 0.7.11 / 2011-08-27 536 | =================== 537 | 538 | * Updated socket.io-client. 539 | 540 | 0.7.10 / 2011-08-27 541 | =================== 542 | 543 | * Updated socket.io-client. 544 | 545 | 0.7.9 / 2011-08-12 546 | ================== 547 | 548 | * Updated socket.io-client. 549 | * Make sure we only do garbage collection when the server we receive is actually run. 550 | 551 | 0.7.8 / 2011-08-08 552 | ================== 553 | 554 | * Changed; make sure sio#listen passes options to both HTTP server and socket.io manager. 555 | * Added docs for sio#listen. 556 | * Added options parameter support for Manager constructor. 557 | * Added memory leaks tests and test-leaks Makefile task. 558 | * Removed auto npm-linking from make test. 559 | * Make sure that you can disable heartbeats. [3rd-Eden] 560 | * Fixed rooms memory leak [3rd-Eden] 561 | * Send response once we got all POST data, not immediately [Pita] 562 | * Fixed onLeave behavior with missing clientsk [3rd-Eden] 563 | * Prevent duplicate references in rooms. 564 | * Added alias for `to` to `in` and `in` to `to`. 565 | * Fixed roomClients definition. 566 | * Removed dependency on redis for installation without npm [3rd-Eden] 567 | * Expose path and querystring in handshakeData [3rd-Eden] 568 | 569 | 0.7.7 / 2011-07-12 570 | ================== 571 | 572 | * Fixed double dispatch handling with emit to closed clients. 573 | * Added test for emitting to closed clients to prevent regression. 574 | * Fixed race condition in redis test. 575 | * Changed Transport#end instrumentation. 576 | * Leveraged $emit instead of emit internally. 577 | * Made tests faster. 578 | * Fixed double disconnect events. 579 | * Fixed disconnect logic 580 | * Simplified remote events handling in Socket. 581 | * Increased testcase timeout. 582 | * Fixed unknown room emitting (GH-291). [3rd-Eden] 583 | * Fixed `address` in handshakeData. [3rd-Eden] 584 | * Removed transports definition in chat example. 585 | * Fixed room cleanup 586 | * Fixed; make sure the client is cleaned up after booting. 587 | * Make sure to mark the client as non-open if the connection is closed. 588 | * Removed unneeded `buffer` declarations. 589 | * Fixed; make sure to clear socket handlers and subscriptions upon transport close. 590 | 591 | 0.7.6 / 2011-06-30 592 | ================== 593 | 594 | * Fixed general dispatching when a client has closed. 595 | 596 | 0.7.5 / 2011-06-30 597 | ================== 598 | 599 | * Fixed dispatching to clients that are disconnected. 600 | 601 | 0.7.4 / 2011-06-30 602 | ================== 603 | 604 | * Fixed; only clear handlers if they were set. [level09] 605 | 606 | 0.7.3 / 2011-06-30 607 | ================== 608 | 609 | * Exposed handshake data to clients. 610 | * Refactored dispatcher interface. 611 | * Changed; Moved id generation method into the manager. 612 | * Added sub-namespace authorization. [3rd-Eden] 613 | * Changed; normalized SocketNamespace local eventing [dvv] 614 | * Changed; Use packet.reason or default to 'packet' [3rd-Eden] 615 | * Changed console.error to console.log. 616 | * Fixed; bind both servers at the same time do that the test never times out. 617 | * Added 304 support. 618 | * Removed `Transport#name` for abstract interface. 619 | * Changed; lazily require http and https module only when needed. [3rd-Eden] 620 | 621 | 0.7.2 / 2011-06-22 622 | ================== 623 | 624 | * Make sure to write a packet (of type `noop`) when closing a poll. 625 | This solves a problem with cross-domain requests being flagged as aborted and 626 | reconnection being triggered. 627 | * Added `noop` message type. 628 | 629 | 0.7.1 / 2011-06-21 630 | ================== 631 | 632 | * Fixed cross-domain XHR. 633 | * Added CORS test to xhr-polling suite. 634 | 635 | 0.7.0 / 2010-06-21 636 | ================== 637 | 638 | * http://socket.io/announcement.html 639 | -------------------------------------------------------------------------------- /docs/API.md: -------------------------------------------------------------------------------- 1 | 2 | ## Table of Contents 3 | 4 | - [Class: Server](#server) 5 | - [new Server(httpServer[, options])](#new-serverhttpserver-options) 6 | - [new Server(port[, options])](#new-serverport-options) 7 | - [new Server(options)](#new-serveroptions) 8 | - [server.sockets](#serversockets) 9 | - [server.engine.generateId](#serverenginegenerateid) 10 | - [server.serveClient([value])](#serverserveclientvalue) 11 | - [server.path([value])](#serverpathvalue) 12 | - [server.adapter([value])](#serveradaptervalue) 13 | - [server.origins([value])](#serveroriginsvalue) 14 | - [server.origins(fn)](#serveroriginsfn) 15 | - [server.attach(httpServer[, options])](#serverattachhttpserver-options) 16 | - [server.attach(port[, options])](#serverattachport-options) 17 | - [server.listen(httpServer[, options])](#serverlistenhttpserver-options) 18 | - [server.listen(port[, options])](#serverlistenport-options) 19 | - [server.bind(engine)](#serverbindengine) 20 | - [server.onconnection(socket)](#serveronconnectionsocket) 21 | - [server.of(nsp)](#serverofnsp) 22 | - [server.close([callback])](#serverclosecallback) 23 | - [Class: Namespace](#namespace) 24 | - [namespace.name](#namespacename) 25 | - [namespace.connected](#namespaceconnected) 26 | - [namespace.emit(eventName[, ...args])](#namespaceemiteventname-args) 27 | - [namespace.clients(callback)](#namespaceclientscallback) 28 | - [namespace.use(fn)](#namespaceusefn) 29 | - [Event: 'connect'](#event-connect) 30 | - [Event: 'connection'](#event-connect) 31 | - [Flag: 'volatile'](#flag-volatile) 32 | - [Flag: 'local'](#flag-local) 33 | - [Class: Socket](#socket) 34 | - [socket.id](#socketid) 35 | - [socket.rooms](#socketrooms) 36 | - [socket.client](#socketclient) 37 | - [socket.conn](#socketconn) 38 | - [socket.request](#socketrequest) 39 | - [socket.use(fn)](#socketusefn) 40 | - [socket.send([...args][, ack])](#socketsendargs-ack) 41 | - [socket.emit(eventName[, ...args][, ack])](#socketemiteventname-args-ack) 42 | - [socket.on(eventName, callback)](#socketoneventname-callback) 43 | - [socket.once(eventName, listener)](#socketonceeventname-listener) 44 | - [socket.removeListener(eventName, listener)](#socketremovelistenereventname-listener) 45 | - [socket.removeAllListeners([eventName])](#socketremovealllistenerseventname) 46 | - [socket.eventNames()](#socketeventnames) 47 | - [socket.join(room[, callback])](#socketjoinroom-callback) 48 | - [socket.join(rooms[, callback])](#socketjoinrooms-callback) 49 | - [socket.leave(room[, callback])](#socketleaveroom-callback) 50 | - [socket.to(room)](#sockettoroom) 51 | - [socket.in(room)](#socketinroom) 52 | - [socket.compress(value)](#socketcompressvalue) 53 | - [socket.disconnect(close)](#socketdisconnectclose) 54 | - [Flag: 'broadcast'](#flag-broadcast) 55 | - [Flag: 'volatile'](#flag-volatile-1) 56 | - [Event: 'disconnect'](#event-disconnect) 57 | - [Event: 'error'](#event-error) 58 | - [Event: 'disconnecting'](#event-disconnecting) 59 | - [Class: Client](#client) 60 | - [client.conn](#clientconn) 61 | - [client.request](#clientrequest) 62 | 63 | 64 | ### Server 65 | 66 | Exposed by `require('socket.io')`. 67 | 68 | #### new Server(httpServer[, options]) 69 | 70 | - `httpServer` _(http.Server)_ the server to bind to. 71 | - `options` _(Object)_ 72 | - `path` _(String)_: name of the path to capture (`/socket.io`) 73 | - `serveClient` _(Boolean)_: whether to serve the client files (`true`) 74 | - `adapter` _(Adapter)_: the adapter to use. Defaults to an instance of the `Adapter` that ships with socket.io which is memory based. See [socket.io-adapter](https://github.com/socketio/socket.io-adapter) 75 | - `origins` _(String)_: the allowed origins (`*`) 76 | - `allowRequest` _(Function)_: A function that receives a given handshake or upgrade request as its first parameter, and can decide whether to continue or not. The second argument is a function that needs to be called with the decided information: `fn(err, success)`, where `success` is a boolean value where false means that the request is rejected, and err is an error code. 77 | - `parser` _(Parser)_: the parser to use. Defaults to an instance of the `Parser` that ships with socket.io. See [socket.io-parser](https://github.com/socketio/socket.io-parser). 78 | 79 | Works with and without `new`: 80 | 81 | ```js 82 | var io = require('socket.io')(); 83 | // or 84 | var Server = require('socket.io'); 85 | var io = new Server(); 86 | ``` 87 | 88 | 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/socketio/engine.io#methods-1) as reference. 89 | 90 | Among those options: 91 | 92 | - `pingTimeout` _(Number)_: how many ms without a pong packet to consider the connection closed (`60000`) 93 | - `pingInterval` _(Number)_: how many ms before sending a new ping packet (`25000`). 94 | 95 | Those two parameters will impact the delay before a client knows the server is not available anymore. For example, if the underlying TCP connection is not closed properly due to a network issue, a client may have to wait up to `pingTimeout + pingInterval` ms before getting a `disconnect` event. 96 | 97 | - `transports` _(Array)_: transports to allow connections to (`['polling', 'websocket']`). 98 | 99 | **Note:** The order is important. By default, a long-polling connection is established first, and then upgraded to WebSocket if possible. Using `['websocket']` means there will be no fallback if a WebSocket connection cannot be opened. 100 | 101 | #### new Server(port[, options]) 102 | 103 | - `port` _(Number)_ a port to listen to (a new `http.Server` will be created) 104 | - `options` _(Object)_ 105 | 106 | See [above](#new-serverhttpserver-options) for available options. 107 | 108 | #### new Server(options) 109 | 110 | - `options` _(Object)_ 111 | 112 | See [above](#new-serverhttpserver-options) for available options. 113 | 114 | #### server.sockets 115 | 116 | * _(Namespace)_ 117 | 118 | The default (`/`) namespace. 119 | 120 | #### server.serveClient([value]) 121 | 122 | - `value` _(Boolean)_ 123 | - **Returns** `Server|Boolean` 124 | 125 | If `value` 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. If no arguments are supplied this method returns the current value. 126 | 127 | ```js 128 | // pass a server and the `serveClient` option 129 | var io = require('socket.io')(http, { serveClient: false }); 130 | 131 | // or pass no server and then you can call the method 132 | var io = require('socket.io')(); 133 | io.serveClient(false); 134 | io.attach(http); 135 | ``` 136 | 137 | #### server.path([value]) 138 | 139 | - `value` _(String)_ 140 | - **Returns** `Server|String` 141 | 142 | Sets the path `value` 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. 143 | 144 | #### server.adapter([value]) 145 | 146 | - `value` _(Adapter)_ 147 | - **Returns** `Server|Adapter` 148 | 149 | Sets the adapter `value`. Defaults to an instance of the `Adapter` that ships with socket.io which is memory based. See [socket.io-adapter](https://github.com/socketio/socket.io-adapter). If no arguments are supplied this method returns the current value. 150 | 151 | #### server.origins([value]) 152 | 153 | - `value` _(String)_ 154 | - **Returns** `Server|String` 155 | 156 | Sets the allowed origins `value`. Defaults to any origins being allowed. If no arguments are supplied this method returns the current value. 157 | 158 | #### server.origins(fn) 159 | 160 | - `fn` _(Function)_ 161 | - **Returns** `Server` 162 | 163 | Provides a function taking two arguments `origin:String` and `callback(error, success)`, where `success` is a boolean value indicating whether origin is allowed or not. 164 | 165 | __Potential drawbacks__: 166 | * in some situations, when it is not possible to determine `origin` it may have value of `*` 167 | * As this function will be executed for every request, it is advised to make this function work as fast as possible 168 | * 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/expressjs/cors). 169 | 170 | #### server.attach(httpServer[, options]) 171 | 172 | - `httpServer` _(http.Server)_ the server to attach to 173 | - `options` _(Object)_ 174 | 175 | Attaches the `Server` to an engine.io instance on `httpServer` with the supplied `options` (optionally). 176 | 177 | ### server.attach(port[, options]) 178 | 179 | - `port` _(Number)_ the port to listen on 180 | - `options` _(Object)_ 181 | 182 | Attaches the `Server` to an engine.io instance on a new http.Server with the supplied `options` (optionally). 183 | 184 | #### server.listen(httpServer[, options]) 185 | 186 | Synonym of [server.attach(httpServer[, options])](#serverattachhttpserver-options). 187 | 188 | #### server.listen(port[, options]) 189 | 190 | Synonym of [server.attach(port[, options])](#serverattachport-options). 191 | 192 | #### server.bind(engine) 193 | 194 | - `engine` _(engine.Server)_ 195 | - **Returns** `Server` 196 | 197 | Advanced use only. Binds the server to a specific engine.io `Server` (or compatible API) instance. 198 | 199 | #### server.onconnection(socket) 200 | 201 | - `socket` _(engine.Socket)_ 202 | - **Returns** `Server` 203 | 204 | Advanced use only. Creates a new `socket.io` client from the incoming engine.io (or compatible API) `Socket`. 205 | 206 | #### server.of(nsp) 207 | 208 | - `nsp` _(String)_ 209 | - **Returns** `Namespace` 210 | 211 | Initializes and retrieves the given `Namespace` by its pathname identifier `nsp`. If the namespace was already initialized it returns it immediately. 212 | 213 | #### server.close([callback]) 214 | 215 | - `callback` _(Function)_ 216 | 217 | Closes the socket.io server. The `callback` argument is optional and will be called when all connections are closed. 218 | 219 | ```js 220 | var Server = require('socket.io'); 221 | var PORT = 3030; 222 | var server = require('http').Server(); 223 | 224 | var io = Server(PORT); 225 | 226 | io.close(); // Close current server 227 | 228 | server.listen(PORT); // PORT is free to use 229 | 230 | io = Server(server); 231 | ``` 232 | 233 | #### server.engine.generateId 234 | 235 | Overwrites the default method to generate your custom socket id. 236 | 237 | The function is called with a node request object (`http.IncomingMessage`) as first parameter. 238 | 239 | ```js 240 | io.engine.generateId = function (req) { 241 | return "custom:id:" + custom_id++; // custom id must be unique 242 | } 243 | ``` 244 | 245 | ### Namespace 246 | 247 | Represents a pool of sockets connected under a given scope identified 248 | by a pathname (eg: `/chat`). 249 | 250 | By default the client always connects to `/`. 251 | 252 | #### namespace.name 253 | 254 | * _(String)_ 255 | 256 | The namespace identifier property. 257 | 258 | #### namespace.connected 259 | 260 | * _(Object)_ 261 | 262 | The hash of `Socket` objects that are connected to this namespace, indexed by `id`. 263 | 264 | #### namespace.emit(eventName[, ...args]) 265 | 266 | - `eventName` _(String)_ 267 | - `args` 268 | 269 | Emits an event to all connected clients. The following two are equivalent: 270 | 271 | ```js 272 | var io = require('socket.io')(); 273 | 274 | io.emit('an event sent to all connected clients'); // main namespace 275 | 276 | var chat = io.of('/chat'); 277 | chat.emit('an event sent to all connected clients in chat namespace'); 278 | ``` 279 | 280 | #### namespace.clients(callback) 281 | 282 | - `callback` _(Function)_ 283 | 284 | Gets a list of client IDs connected to this namespace (across all nodes if applicable). 285 | 286 | ```js 287 | var io = require('socket.io')(); 288 | io.of('/chat').clients(function(error, clients){ 289 | if (error) throw error; 290 | console.log(clients); // => [PZDoMHjiu8PYfRiKAAAF, Anw2LatarvGVVXEIAAAD] 291 | }); 292 | ``` 293 | 294 | An example to get all clients in namespace's room: 295 | 296 | ```js 297 | var io = require('socket.io')(); 298 | io.of('/chat').in('general').clients(function(error, clients){ 299 | if (error) throw error; 300 | console.log(clients); // => [Anw2LatarvGVVXEIAAAD] 301 | }); 302 | ``` 303 | 304 | As with broadcasting, the default is all clients from the default namespace ('/'): 305 | 306 | ```js 307 | var io = require('socket.io')(); 308 | io.clients(function(error, clients){ 309 | if (error) throw error; 310 | console.log(clients); // => [6em3d4TJP8Et9EMNAAAA, G5p55dHhGgUnLUctAAAB] 311 | }); 312 | ``` 313 | 314 | #### namespace.use(fn) 315 | 316 | - `fn` _(Function)_ 317 | 318 | Registers a middleware, which is a function that gets executed for every incoming `Socket`, and receives as parameters the socket and a function to optionally defer execution to the next registered middleware. 319 | 320 | Errors passed to middleware callbacks are sent as special `error` packets to clients. 321 | 322 | ```js 323 | var io = require('socket.io')(); 324 | io.use(function(socket, next){ 325 | if (socket.request.headers.cookie) return next(); 326 | next(new Error('Authentication error')); 327 | }); 328 | ``` 329 | 330 | #### Event: 'connect' 331 | 332 | - `socket` _(Socket)_ socket connection with client 333 | 334 | Fired upon a connection from client. 335 | 336 | #### Event: 'connection' 337 | 338 | Synonym of [Event: 'connect'](#event-connect). 339 | 340 | #### Flag: 'volatile' 341 | 342 | Sets a modifier for a subsequent event emission that the event data may be lost if the clients are not ready to receive messages (because of network slowness or other issues, or because they’re connected through long polling and is in the middle of a request-response cycle). 343 | 344 | ```js 345 | io.volatile.emit('an event', { some: 'data' }); // the clients may or may not receive it 346 | ``` 347 | 348 | #### Flag: 'local' 349 | 350 | Sets a modifier for a subsequent event emission that the event data will only be _broadcast_ to the current node (when the [Redis adapter](https://github.com/socketio/socket.io-redis) is used). 351 | 352 | ```js 353 | io.local.emit('an event', { some: 'data' }); 354 | ``` 355 | 356 | ### Socket 357 | 358 | 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. 359 | 360 | It should be noted the `Socket` doesn't relate directly to the actual underlying TCP/IP `socket` and it is only the name of the class. 361 | 362 | Within each `Namespace`, you can also define arbitrary channels (called `room`) that the `Socket` can join and leave. That provides a convenient way to broadcast to a group of `Socket`s (see `Socket#to` below). 363 | 364 | The `Socket` class inherits from [EventEmitter](https://nodejs.org/api/events.html#events_class_eventemitter). The `Socket` class overrides the `emit` method, and does not modify any other `EventEmitter` method. All methods documented here which also appear as `EventEmitter` methods (apart from `emit`) are implemented by `EventEmitter`, and documentation for `EventEmitter` applies. 365 | 366 | #### socket.id 367 | 368 | * _(String)_ 369 | 370 | A unique identifier for the session, that comes from the underlying `Client`. 371 | 372 | #### socket.rooms 373 | 374 | * _(Object)_ 375 | 376 | A hash of strings identifying the rooms this client is in, indexed by room name. 377 | 378 | #### socket.client 379 | 380 | * _(Client)_ 381 | 382 | A reference to the underlying `Client` object. 383 | 384 | #### socket.conn 385 | 386 | * _(engine.Socket)_ 387 | 388 | A reference to the underlying `Client` transport connection (engine.io `Socket` object). This allows access to the IO transport layer, which still (mostly) abstracts the actual TCP/IP socket. 389 | 390 | #### socket.request 391 | 392 | * _(Request)_ 393 | 394 | 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`. 395 | 396 | #### socket.use(fn) 397 | 398 | - `fn` _(Function)_ 399 | 400 | Registers a middleware, which is a function that gets executed for every incoming `Packet` and receives as parameter the packet and a function to optionally defer execution to the next registered middleware. 401 | 402 | Errors passed to middleware callbacks are sent as special `error` packets to clients. 403 | 404 | ```js 405 | var io = require('socket.io')(); 406 | io.on('connection', function(socket){ 407 | socket.use(function(packet, next){ 408 | if (packet.doge === true) return next(); 409 | next(new Error('Not a doge error')); 410 | }); 411 | }); 412 | ``` 413 | 414 | #### socket.send([...args][, ack]) 415 | 416 | - `args` 417 | - `ack` _(Function)_ 418 | - **Returns** `Socket` 419 | 420 | Sends a `message` event. See [socket.emit(eventName[, ...args][, ack])](#socketemiteventname-args-ack). 421 | 422 | #### socket.emit(eventName[, ...args][, ack]) 423 | 424 | *(overrides `EventEmitter.emit`)* 425 | - `eventName` _(String)_ 426 | - `args` 427 | - `ack` _(Function)_ 428 | - **Returns** `Socket` 429 | 430 | Emits an event to the socket identified by the string name. Any other parameters can be included. All serializable datastructures are supported, including `Buffer`. 431 | 432 | ```js 433 | socket.emit('hello', 'world'); 434 | socket.emit('with-binary', 1, '2', { 3: '4', 5: new Buffer(6) }); 435 | ``` 436 | 437 | The `ack` argument is optional and will be called with the client's answer. 438 | 439 | ```js 440 | var io = require('socket.io')(); 441 | io.on('connection', function(client){ 442 | client.emit('an event', { some: 'data' }); 443 | 444 | client.emit('ferret', 'tobi', function (data) { 445 | console.log(data); // data will be 'woot' 446 | }); 447 | 448 | // the client code 449 | // client.on('ferret', function (name, fn) { 450 | // fn('woot'); 451 | // }); 452 | 453 | }); 454 | ``` 455 | 456 | #### socket.on(eventName, callback) 457 | 458 | *(inherited from `EventEmitter`)* 459 | - `eventName` _(String)_ 460 | - `callback` _(Function)_ 461 | - **Returns** `Socket` 462 | 463 | Register a new handler for the given event. 464 | 465 | ```js 466 | socket.on('news', function (data) { 467 | console.log(data); 468 | }); 469 | ``` 470 | 471 | #### socket.once(eventName, listener) 472 | #### socket.removeListener(eventName, listener) 473 | #### socket.removeAllListeners([eventName]) 474 | #### socket.eventNames() 475 | 476 | Inherited from `EventEmitter` (along with other methods not mentioned here). See Node.js documentation for the `events` module. 477 | 478 | #### socket.join(room[, callback]) 479 | 480 | - `room` _(String)_ 481 | - `callback` _(Function)_ 482 | - **Returns** `Socket` for chaining 483 | 484 | Adds the client to the `room`, and fires optionally a callback with `err` signature (if any). 485 | 486 | ```js 487 | io.on('connection', function(socket){ 488 | socket.join('room 237', function(){ 489 | console.log(socket.rooms); // [ , 'room 237' ] 490 | io.to('room 237', 'a new user has joined the room'); // broadcast to everyone in the room 491 | }); 492 | }); 493 | ``` 494 | 495 | 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/socketio/socket.io-adapter). 496 | 497 | For your convenience, each socket automatically joins a room identified by this id (see `Socket#id`). This makes it easy to broadcast messages to other sockets: 498 | 499 | ```js 500 | io.on('connection', function(client){ 501 | client.on('say to someone', function(id, msg){ 502 | // send a private message to the socket with the given id 503 | client.broadcast.to(id).emit('my message', msg); 504 | }); 505 | }); 506 | ``` 507 | 508 | #### socket.join(rooms[, callback]) 509 | 510 | - `rooms` _(Array)_ 511 | - `callback` _(Function)_ 512 | - **Returns** `Socket` for chaining 513 | 514 | Adds the client to the list of room, and fires optionally a callback with `err` signature (if any). 515 | 516 | #### socket.leave(room[, callback]) 517 | 518 | - `room` _(String)_ 519 | - `callback` _(Function)_ 520 | - **Returns** `Socket` for chaining 521 | 522 | Removes the client from `room`, and fires optionally a callback with `err` signature (if any). 523 | 524 | **Rooms are left automatically upon disconnection**. 525 | 526 | #### socket.to(room) 527 | 528 | - `room` _(String)_ 529 | - **Returns** `Socket` for chaining 530 | 531 | Sets a modifier for a subsequent event emission that the event will only be _broadcasted_ to clients that have joined the given `room`. 532 | 533 | To emit to multiple rooms, you can call `to` several times. 534 | 535 | ```js 536 | var io = require('socket.io')(); 537 | io.on('connection', function(client){ 538 | // to one room 539 | client.to('others').emit('an event', { some: 'data' }); 540 | // to multiple rooms 541 | client.to('room1').to('room2').emit('hello'); 542 | }); 543 | ``` 544 | 545 | #### socket.in(room) 546 | 547 | Synonym of [socket.to(room)](#sockettoroom). 548 | 549 | #### socket.compress(value) 550 | 551 | - `value` _(Boolean)_ whether to following packet will be compressed 552 | - **Returns** `Socket` for chaining 553 | 554 | Sets a modifier for a subsequent event emission that the event data will only be _compressed_ if the value is `true`. Defaults to `true` when you don't call the method. 555 | 556 | #### socket.disconnect(close) 557 | 558 | - `close` _(Boolean)_ whether to close the underlying connection 559 | - **Returns** `Socket` 560 | 561 | Disconnects this client. If value of close is `true`, closes the underlying connection. Otherwise, it just disconnects the namespace. 562 | 563 | #### Flag: 'broadcast' 564 | 565 | Sets a modifier for a subsequent event emission that the event data will only be _broadcast_ to every sockets but the sender. 566 | 567 | ```js 568 | var io = require('socket.io')(); 569 | io.on('connection', function(socket){ 570 | socket.broadcast.emit('an event', { some: 'data' }); // everyone gets it but the sender 571 | }); 572 | ``` 573 | 574 | #### Flag: 'volatile' 575 | 576 | Sets a modifier for a subsequent event emission that the event data may be lost if the client is not ready to receive messages (because of network slowness or other issues, or because they’re connected through long polling and is in the middle of a request-response cycle). 577 | 578 | ```js 579 | var io = require('socket.io')(); 580 | io.on('connection', function(socket){ 581 | socket.volatile.emit('an event', { some: 'data' }); // the client may or may not receive it 582 | }); 583 | ``` 584 | 585 | #### Event: 'disconnect' 586 | 587 | - `reason` _(String)_ the reason of the disconnection (either client or server-side) 588 | 589 | Fired upon disconnection. 590 | 591 | #### Event: 'error' 592 | 593 | - `error` _(Object)_ error object 594 | 595 | Fired when an error occurs. 596 | 597 | #### Event: 'disconnecting' 598 | 599 | - `reason` _(String)_ the reason of the disconnection (either client or server-side) 600 | 601 | Fired when the client is going to be disconnected (but hasn't left its `rooms` yet). 602 | 603 | These are reserved events (along with `connect`, `newListener` and `removeListener`) which cannot be used as event names. 604 | 605 | ### Client 606 | 607 | The `Client` class represents an incoming transport (engine.io) connection. A `Client` can be associated with many multiplexed `Socket`s that belong to different `Namespace`s. 608 | 609 | #### client.conn 610 | 611 | * _(engine.Socket)_ 612 | 613 | A reference to the underlying `engine.io` `Socket` connection. 614 | 615 | #### client.request 616 | 617 | * _(Request)_ 618 | 619 | 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`. 620 | --------------------------------------------------------------------------------