├── .gitignore
├── .travis.yml
├── CHANGELOG.md
├── README.md
├── example
├── index.html
└── server.js
├── index.js
├── package.json
└── spec
├── attach.test.js
├── connection.test.js
├── handlers.test.js
├── helpers
├── connect.js
└── utils.js
├── index.js
└── stack.test.js
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | *.log
3 | coverage
4 | .idea
5 | package-lock.json
6 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: false
2 | language: node_js
3 | node_js:
4 | - "8"
5 | script:
6 | - npm test
7 | after_script:
8 | - npm run travis
9 | matrix:
10 | fast_finish: true
11 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 |
2 | ## 1.1.0 - 05.09.2019
3 |
4 | * _update_ promoted Socket.IO for better API exposure
5 | * _update_ improved async passthrough handling
6 |
7 | ## 1.0.17 - 05.12.2017
8 |
9 | * _update_ improved and re-added bug fix for promise chaining (credit: ihwbox)
10 |
11 | ## 1.0.16 - 03.12.2017
12 |
13 | * _update_ revert bug fix for promise chaining, tests failing
14 |
15 | ## 1.0.15 - 03.12.2017
16 |
17 | * _update_ bug fix for promise chaining
18 |
19 | ## 1.0.14 - 23.11.2017
20 |
21 | * _update_ bug fix for multi-node adapter support (credit: ihwbox)
22 |
23 | ## 1.0.13 - 23.06.2017
24 |
25 | * _update_ room broadcast functionality
26 |
27 | ## 1.0.12 - 14.06.2017
28 |
29 | * _add_ socket ack functionality
30 |
31 | ## 1.0.11 - 13.06.2017
32 |
33 | * _update_ documentation
34 |
35 | ## 1.0.10 - 08.06.2017
36 |
37 | * _add_ multi-node clustering adapter support
38 |
39 | ## 1.0.9 - 07.06.2017
40 |
41 | * _remove_ babel support
42 | * _remove_ co wrapper support
43 | * _update_ examples to NodeJS v7
44 | * _update_ documentation
45 | * _update_ tests
46 |
47 | ## 1.0.8 - 06.06.2017
48 |
49 | * _update_ minor bug fixes for room support
50 |
51 | ## 1.0.7 - 06.06.2017
52 |
53 | * _update_ documentation
54 | * _add_ room list
55 |
56 | ## 1.0.6 - 06.06.2017
57 |
58 | * _update_ improved room management
59 |
60 | ## 1.0.5 - 05.06.2017
61 |
62 | * _add_ room join/leave support
63 |
64 | ## 1.0.4 - 02.06.2017
65 |
66 | * _update_ documentation
67 |
68 | ## 1.0.3 - 18.05.2017
69 |
70 | * _update_ documentation
71 |
72 | ## 1.0.2 - 15.05.2017
73 |
74 | * _update_ breaking change - broadcast functionality now works as in the native method
75 | * _add_ volatile message support
76 | * _add_ compress message support
77 |
78 | ## 1.0.1 - 12.05.2017
79 |
80 | * _add_ built-in HTTPS support
81 |
82 | ## 1.0.0 - 11.05.2017
83 |
84 | * _update_ upgraded socket.io version to >= 2.0.1
85 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://travis-ci.org/ambelovsky/koa-socket-2)
2 | [](https://npmjs.com/packages/koa-socket-2)
3 |
4 | # Koa-socket-2
5 |
6 | > Sugar for connecting socket.io to a Koa instance
7 |
8 | **Koa-socket-2 uses socket.io v3. It is recommended that you connect to a koa-socket-2 server with a socket.io v3 client.**
9 |
10 | Koa-socket-2 is only compatible with Koa v2 style of middleware (where context is passed as a parameter).
11 |
12 | Koa-socket-2 requires Node v7.0.0 or higher.
13 |
14 | ## Interested in GoLang?
15 |
16 | This project helps you start a SocketIO server using Koa and NodeJS. However, Google's Go language -- or GoLang -- allows you to write code that compiles down to binary. It can be a very good way to take your SocketIO server to the next level by running faster and requiring less overhead than runtime environments like NodeJS.
17 |
18 | If you're interested in building a SocketIO server in GoLang, take a look at [gosf.io](http://gosf.io) or [GOSF on GitHub](https://github.com/ambelovsky/gosf), the GoLang SocketIO Framework for building SocketIO API servers.
19 |
20 | ## Installation
21 |
22 | ```sh
23 | npm i -S koa-socket-2
24 | ```
25 |
26 | ## HTTP Example
27 |
28 | Please make the world a better place and stop using unsecure channels. If you
29 | absolutely must, however, then the following will get you started.
30 |
31 | ```js
32 | const Koa = require('koa');
33 | const IO = require('koa-socket-2');
34 |
35 | const app = new Koa();
36 | const io = new IO();
37 |
38 | app.use( ... );
39 |
40 | io.attach(app);
41 |
42 | io.on('message', (ctx, data) => {
43 | console.log('client sent data to message endpoint', data);
44 | });
45 |
46 | app.listen( process.env.PORT || 3000 );
47 | ```
48 |
49 | ## HTTPS Example
50 |
51 | ```js
52 | const Koa = require('koa');
53 | const IO = require('koa-socket-2');
54 | const fs = require('fs');
55 |
56 | // If you want to access the HTTPS server from a local JS client for
57 | // development, then try this simple plugin:
58 | app.use(async (ctx, next) => {
59 | ctx.set('Access-Control-Allow-Origin', 'null');
60 | ctx.set('Access-Control-Allow-Credentials', 'true');
61 | await next();
62 | });
63 |
64 | const app = new Koa();
65 | const io = new IO();
66 |
67 | app.use( ... );
68 |
69 | // Replace the "..." placeholders below with your own SSL certificate files
70 | io.attach(app, true, {
71 | key: fs.readFileSync(...),
72 | cert: fs.readFileSync(...),
73 | ca: fs.readFileSync(...)
74 | });
75 |
76 | console.log('Server: HTTPS/TLS Enabled.');
77 |
78 | io.on('message', (ctx, data) => {
79 | console.log('client sent data to message endpoint', data);
80 | });
81 |
82 | app.listen(process.env.PORT || 3000);
83 | ```
84 |
85 | ## Features
86 |
87 | * Attach socket.io to existing koa projects
88 | * Attach koa-style middleware to socket.io events
89 | * Supports koa v2 style of passing context along the response chain
90 |
91 |
92 | ## Attaching to existing projects
93 |
94 | The `attach` function is used to attach the `IO` instance to the application, this adds `server`\* and `io` properties to the koa application and should happen before the app starts listening on a port.
95 |
96 | It also re-maps `app.listen` to `app.server.listen`, so you could simply do `app.listen()`. However if you already had an `app.server` attached, it uses it instead and expects you to do `app.server.listen()` yourself.
97 |
98 | ```js
99 | const Koa = require( 'koa' );
100 | const IO = require( 'koa-socket-2' );
101 |
102 | const app = new Koa();
103 | const io = new IO();
104 |
105 | // Attach the socket to the application
106 | io.attach( app );
107 |
108 | // Socket is now available as app.io if you prefer
109 | app.io.on( event, eventHandler );
110 |
111 | // The raw socket.io instance is attached as app._io if you need it
112 | app._io.on( 'connection', sock => {
113 | // ...
114 | });
115 |
116 | // *If* you had manually attached an `app.server` yourself, you should do:
117 | app.listen = function() {
118 | app.server.listen.apply(app.server, arguments);
119 | return app.server;
120 | }
121 |
122 | // app.listen is mapped to app.server.listen, so you can just do:
123 | app.listen( process.env.PORT || 3000 );
124 | ```
125 |
126 | ## Middleware and event handlers
127 |
128 | Middleware can be added in much the same way as it can be added to any regular koa instance.
129 |
130 | ### Example with *async* functions
131 |
132 | ```js
133 | io.use( async ( ctx, next ) => {
134 | let start = new Date();
135 | await next();
136 | console.log( `response time: ${ new Date() - start }ms` );
137 | })
138 | ```
139 |
140 |
141 | ### Example with generator functions
142 |
143 | Don't use generator functions. Get with the times, and upgrade to Node >= 7.X.X.
144 |
145 |
146 | ### Plain example
147 |
148 | Whilst slightly unwieldy, the standalone method also works
149 |
150 | ```js
151 | io.use( ( ctx, next ) => {
152 | let start = new Date()
153 | return next().then( () => {
154 | console.log( `response time: ${ new Date() - start }ms` )
155 | })
156 | })
157 | ```
158 |
159 |
160 | ## Passed Context
161 |
162 | ```js
163 | let ctx = {
164 | event: listener.event,
165 | data: data,
166 | socket: Socket,
167 | acknowledge: cb
168 | }
169 | ```
170 |
171 | The context passed to each socket middleware and handler begins the chain with the event that triggered the response, the data sent with that event and the socket instance that is handling the event. There is also a shorthand for firing an acknowledgement back to the client.
172 |
173 | As the context is passed to each function in the response chain it is fair game for mutation at any point along that chain, it is up to you to decide whether this is an anti-pattern or not. There was much discussion around this topic for koa v2.
174 |
175 |
176 | ```js
177 | io.use( async ( ctx, next ) => {
178 | ctx.process = process.pid
179 | await next()
180 | })
181 |
182 | io.use( async ( ctx, next ) => {
183 | // ctx is passed along so ctx.process is now available
184 | console.log( ctx.process )
185 | })
186 |
187 | io.on( 'event', ( ctx, data ) => {
188 | // ctx is passed all the way through to the end point
189 | console.log( ctx.process )
190 | })
191 | ```
192 |
193 |
194 | ## Namespaces
195 |
196 | Namespaces can be defined simply by instantiating a new instance of `koaSocket` and passing the namespace id in the constructor. All other functionality works the same, it’ll just be constrained to the single namespace.
197 |
198 | ```js
199 | const app = new Koa()
200 | const chat = new IO({
201 | namespace: 'chat'
202 | });
203 |
204 | chat.attach( app );
205 |
206 | chat.on( 'message', ctx => {
207 | console.log( ctx.data );
208 | chat.broadcast( 'response', ... );
209 | });
210 | ```
211 |
212 | Namespaces also attach themselves to the `app` instance, throwing an error if the property name already exists.
213 |
214 | ```js
215 | const app = new Koa();
216 | const chat = new IO({
217 | namespace: 'chat'
218 | });
219 |
220 | chat.attach( app );
221 |
222 | app.chat.use( ... );
223 | app.chat.on( ... );
224 | app.chat.broadcast( ... );
225 | ```
226 |
227 | The attachment is configurable if you don’t want to muddy the `app` object with all your namespaces.
228 |
229 | ```js
230 | const chat = new IO({
231 | namespace: 'chat',
232 | hidden: true
233 | });
234 |
235 | chat.use( ... );
236 | chat.on( ... );
237 | ```
238 |
239 | Namespaces are fairly ubiquitous so they get a dirty shorthand for creating them, note that if you want to add any additional options you’ll need to use the longhand object parameter to instantiate `koaSocket`.
240 |
241 | ```js
242 | const chat = new IO( 'chat' );
243 | ```
244 |
245 |
246 | ## IO API
247 |
248 | ### .attach( `Koa app` )
249 |
250 | Attaches to a koa application
251 |
252 | ```js
253 | io.attach( app );
254 | app.listen( process.env.PORT );
255 | ```
256 |
257 | ### .use( `Function callback` )
258 |
259 | Applies middleware to the stack.
260 |
261 | Middleware are executed each time an event is reacted to and before the callback is triggered for an event.
262 |
263 | Middleware with generators should use `co.wrap`.
264 |
265 | Middleware functions are called with `ctx` and `next`. The context is passed through each middleware and out to the event listener callback. `next` allows the middleware chain to be traversed. Under the hood `koa-compose` is used to follow functionality with `koa`.
266 |
267 |
268 | ```js
269 | io.use( async ( ctx, next ) {
270 | console.log( 'Upstream' );
271 | await next();
272 | console.log( 'Downstream' );
273 | })
274 | ```
275 |
276 | ### .on( `String event`, `Function callback` )
277 |
278 | Attaches a callback to an event.
279 |
280 | The callback is fired after any middleware that are attached to the instance and is called with the `ctx` object and the `data` that triggered the event. The `data` can also be found on the `ctx`, the only potential difference is that `data` is the raw `data` emitted with the event trigger whilst `ctx.data` could have been mutated within the middleware stack.
281 |
282 | ```js
283 | io.on( 'message', ( ctx, data ) => {
284 | console.log( data );
285 | console.log( ctx.data, data );
286 | });
287 | ```
288 |
289 | ### .off( `String event`, `Function callback` )
290 |
291 | Removes a callback from an event.
292 |
293 | If the `event` is omitted then it will remove all listeners from the instance.
294 |
295 | If the `callback` is omitted then all callbacks for the supplied event will be removed.
296 |
297 | ```js
298 | io.off( 'message', onChat );
299 | io.off( 'message' );
300 | io.off();
301 | ```
302 |
303 | ### .broadcast.emit( `String event`, `data` )
304 |
305 | Sends a message to all connections.
306 |
307 |
308 | ### .to( `String room` ).emit( `String event`, `data` )
309 |
310 | Sends data to all connections in a room.
311 |
312 | ```js
313 | io.to( 'some_room' ).emit( 'message', { hello: 'world' } );
314 | ```
315 |
316 |
317 | ### .adapter( `Object adapter` )
318 |
319 | ```js
320 | const redis = require('socket.io-redis');
321 | io.adapter(redis({ host: 'localhost', port: 6379 }));
322 | ```
323 |
324 |
325 | ## Socket Connection API
326 |
327 | ### .rooms
328 |
329 | A list of rooms that this connection is associated with.
330 |
331 | ```js
332 | io.on( 'message', ( ctx, data ) => {
333 | console.log(ctx.socket.rooms);
334 | });
335 | ```
336 |
337 |
338 | ### .join( `String room` )
339 |
340 | Associates the connection with a room.
341 |
342 | ```js
343 | io.on( 'message', ( ctx, data ) => {
344 | ctx.socket.join('some_room');
345 | });
346 | ```
347 |
348 |
349 | ### .leave( `String room` )
350 |
351 | Disassociates the connection with a room.
352 |
353 | ```js
354 | io.on( 'message', ( ctx, data ) => {
355 | ctx.socket.leave( 'some_room' );
356 | });
357 | ```
358 |
359 |
360 | ### .broadcast.emit( `String event`, `data` )
361 |
362 | Sends a message to all active connections except the current connection.
363 |
364 | ```js
365 | io.on( 'message', ( ctx, data ) => {
366 | ctx.socket.broadcast.emit( 'message', { hello: 'world' } );
367 | });
368 | ```
369 |
370 |
371 | ### .broadcast.to(`String room`).emit( `String event`, `data` )
372 |
373 | Sends a message to all active connections in a room except the current connection.
374 |
375 | ```js
376 | io.on( 'message', ( ctx, data ) => {
377 | ctx.socket.broadcast.to('some_room').emit( 'message', { hello: 'world' } );
378 | });
379 | ```
380 |
381 |
382 | ### .volatile.emit( `String event`, `data` )
383 |
384 | Sends a message without ensuring delivery.
385 |
386 | ```js
387 | io.on( 'message', ( ctx, data ) => {
388 | ctx.socket.volatile.emit( 'message', { hello: 'world' } );
389 | });
390 | ```
391 |
392 |
393 | ### .compress(true).emit( `String event`, `data` )
394 |
395 | Activates per-message compression.
396 |
397 | ```js
398 | io.on( 'message', ( ctx, data ) => {
399 | ctx.socket.compress(true).emit( 'message', { hello: 'world' } );
400 | });
401 | ```
402 |
403 |
404 | ## Running tests
405 |
406 | ```sh
407 | npm test
408 | ```
409 |
410 |
411 | ## Maintainer/Contributor
412 |
413 | - [Aaron Belovsky](https://github.com/ambelovsky)
414 | - [Jonas Lieb](https://github.com/jojonas)
415 |
416 |
417 | ## Original Author
418 |
419 | - [Matt Styles](https://github.com/mattstyles)
420 |
421 |
422 | ## License
423 |
424 | MIT
425 |
--------------------------------------------------------------------------------
/example/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Koa-Socket Example
6 |
7 |
8 |
58 |
59 |
60 |
61 |
62 |
63 | 1
64 |
65 |
103 |
104 |
105 |
--------------------------------------------------------------------------------
/example/server.js:
--------------------------------------------------------------------------------
1 |
2 | const fs = require( 'fs' );
3 | const path = require( 'path' );
4 |
5 | const Koa = require( 'koa' );
6 | const IO = require( '../' );
7 |
8 | const app = new Koa();
9 | const io = new IO();
10 | const chat = new IO( 'chat' );
11 |
12 | io.attach( app );
13 | chat.attach( app );
14 |
15 | /**
16 | * Koa Middlewares
17 | */
18 | app.use( async ( ctx, next ) => {
19 | const start = new Date;
20 | await next();
21 | const ms = new Date - start;
22 | console.log( `${ ctx.method } ${ ctx.url } - ${ ms }ms` );
23 | });
24 |
25 | /**
26 | * App handlers
27 | */
28 | app.use( ctx => {
29 | ctx.type = 'text/html'
30 | ctx.body = fs.createReadStream( path.join( __dirname, 'index.html' ) )
31 | });
32 |
33 | /**
34 | * Socket middlewares
35 | */
36 | io.use( async ( ctx, next ) => {
37 | console.log( 'Socket middleware' );
38 | const start = new Date;
39 | await next();
40 | const ms = new Date - start;
41 | console.log( `WS ${ ms }ms` );
42 | });
43 |
44 | io.use( async ( ctx, next ) => {
45 | ctx.teststring = 'test';
46 | await next();
47 | });
48 |
49 | /**
50 | * Socket handlers
51 | */
52 | io.on( 'connection', ctx => {
53 | console.log( 'Join event', ctx.id );
54 | io.broadcast( 'connections', {
55 | numConnections: io.connections.size
56 | });
57 |
58 | ctx.on( 'disconnect', () => {
59 | console.log( 'leave event', ctx.id );
60 | io.broadcast( 'connections', {
61 | numConnections: io.connections.size
62 | });
63 | });
64 | });
65 |
66 | io.on( 'data', ( ctx, data ) => {
67 | console.log( 'data event', data );
68 | console.log( 'ctx:', ctx.event, ctx.data, ctx.id );
69 | console.log( 'ctx.teststring:', ctx.teststring );
70 | ctx.socket.emit( 'response', {
71 | message: 'response from server'
72 | });
73 | });
74 |
75 | io.on( 'ack', ( ctx, data ) => {
76 | console.log( 'data event with acknowledgement', data );
77 | ctx.acknowledge( 'received' );
78 | });
79 |
80 | io.on( 'numConnections', packet => {
81 | console.log( `Number of connections: ${ io.connections.size }` );
82 | });
83 |
84 | /**
85 | * Chat handlers
86 | */
87 | chat.on( 'connection', ctx => {
88 | console.log( 'Joining chat namespace', ctx.id );
89 | });
90 |
91 | chat.on( 'message', ctx => {
92 | console.log( 'chat message received', ctx.data );
93 |
94 | // Broadcasts to everybody, including this connection
95 | app.chat.broadcast( 'message', 'yo connections, lets chat' );
96 |
97 | // Broadcasts to all other connections
98 | ctx.socket.broadcast.emit( 'message', 'ok connections:chat:broadcast' );
99 |
100 | // Emits to just this socket
101 | ctx.socket.emit( 'message', 'ok connections:chat:emit' );
102 | });
103 |
104 | chat.use( async ( ctx, next ) => {
105 | ctx.teststring = 'chattest';
106 | console.log(`ctx.socket =>`)
107 | console.dir(ctx.socket, {colors:true, depth:2})
108 | console.log(`ctx.socket.nsp =>`, ctx.socket.nsp)
109 | await next();
110 | });
111 |
112 | const PORT = 3000;
113 | app.listen( 3000, () => {
114 | console.log( `Listening on ${ PORT }` );
115 | } );
116 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 |
2 | "use strict";
3 |
4 | const socketIO = require( 'socket.io' );
5 | const compose = require( 'koa-compose' );
6 |
7 | /**
8 | * Main IO class that handles the socket.io connections
9 | * @class
10 | */
11 | module.exports = class IO {
12 | /**
13 | * @constructs
14 | * @param namespace namespace identifier
15 | */
16 | constructor( opts ) {
17 | if ( opts && !(typeof opts !== 'string' || opts && typeof opts !== 'object' ) ) {
18 | throw new Error( 'Incorrect argument passed to koaSocket constructor' );
19 | }
20 |
21 | // app._io reference
22 | this._io = null;
23 |
24 | /**
25 | * List of middlewares, these are composed into an execution chain and
26 | * evaluated with each event
27 | * @type
28 | */
29 | this.middleware = [];
30 |
31 | /**
32 | * Composed middleware stack
33 | * @type
34 | */
35 | this.composed = null;
36 |
37 | /**
38 | * All of the listeners currently added to the IO instance
39 | * event:callback
40 | * @type