UNPKG

10.7 kBMarkdownView Raw
1# socket.io-redis
2
3[![Build Status](https://github.com/socketio/socket.io-redis/workflows/CI/badge.svg?branch=master)](https://github.com/socketio/socket.io-redis/actions)
4[![NPM version](https://badge.fury.io/js/socket.io-redis.svg)](http://badge.fury.io/js/socket.io-redis)
5
6## Table of contents
7
8- [How to use](#how-to-use)
9 - [CommonJS](#commonjs)
10 - [ES6 module](#es6-modules)
11 - [TypeScript](#typescript)
12- [Compatibility table](#compatibility-table)
13- [How does it work under the hood?](#how-does-it-work-under-the-hood)
14- [API](#api)
15 - [adapter(uri[, opts])](#adapteruri-opts)
16 - [adapter(opts)](#adapteropts)
17 - [RedisAdapter](#redisadapter)
18 - [RedisAdapter#sockets(rooms: Set<String>)](#redisadaptersocketsrooms-setstring)
19 - [RedisAdapter#allRooms()](#redisadapterallrooms)
20 - [RedisAdapter#remoteJoin(id:String, room:String)](#redisadapterremotejoinidstring-roomstring)
21 - [RedisAdapter#remoteLeave(id:String, room:String)](#redisadapterremoteleaveidstring-roomstring)
22 - [RedisAdapter#remoteDisconnect(id:String, close:Boolean)](#redisadapterremotedisconnectidstring-closeboolean)
23- [Client error handling](#client-error-handling)
24- [Custom client (eg: with authentication)](#custom-client-eg-with-authentication)
25- [With ioredis client](#with-ioredishttpsgithubcomluinioredis-client)
26 - [Cluster example](#cluster-example)
27 - [Sentinel Example](#sentinel-example)
28- [Protocol](#protocol)
29- [License](#license)
30
31## How to use
32
33### CommonJS
34
35```js
36const io = require('socket.io')(3000);
37const redisAdapter = require('socket.io-redis');
38io.adapter(redisAdapter({ host: 'localhost', port: 6379 }));
39```
40
41### ES6 modules
42
43```js
44import { Server } from 'socket.io';
45import redisAdapter from 'socket.io-redis';
46
47const io = new Server(3000);
48io.adapter(redisAdapter({ host: 'localhost', port: 6379 }));
49```
50
51### TypeScript
52
53```ts
54// npm i -D @types/redis
55import { Server } from 'socket.io';
56import { createAdapter } from 'socket.io-redis';
57import { RedisClient } from 'redis';
58
59const io = new Server(8080);
60const pubClient = new RedisClient({ host: 'localhost', port: 6379 });
61const subClient = pubClient.duplicate();
62
63io.adapter(createAdapter({ pubClient, subClient }));
64```
65
66By running Socket.IO with the `socket.io-redis` adapter you can run
67multiple Socket.IO instances in different processes or servers that can
68all broadcast and emit events to and from each other.
69
70So any of the following commands:
71
72```js
73io.emit('hello', 'to all clients');
74io.to('room42').emit('hello', "to all clients in 'room42' room");
75
76io.on('connection', (socket) => {
77 socket.broadcast.emit('hello', 'to all clients except sender');
78 socket.to('room42').emit('hello', "to all clients in 'room42' room except sender");
79});
80```
81
82will properly be broadcast to the clients through the Redis [Pub/Sub mechanism](https://redis.io/topics/pubsub).
83
84If you need to emit events to socket.io instances from a non-socket.io
85process, you should use [socket.io-emitter](https://github.com/socketio/socket.io-emitter).
86
87## Compatibility table
88
89| Redis Adapter version | Socket.IO server version |
90|-----------------------| ------------------------ |
91| 4.x | 1.x |
92| 5.x | 2.x |
93| 6.0.x | 3.x |
94| 6.1.x and above | 4.x |
95
96## How does it work under the hood?
97
98This adapter extends the [in-memory adapter](https://github.com/socketio/socket.io-adapter/) that is included by default with the Socket.IO server.
99
100The in-memory adapter stores the relationships between Sockets and Rooms in two Maps.
101
102When you run `socket.join("room21")`, here's what happens:
103
104```
105console.log(adapter.rooms); // Map { "room21" => Set { "mdpk4kxF5CmhwfCdAHD8" } }
106console.log(adapter.sids); // Map { "mdpk4kxF5CmhwfCdAHD8" => Set { "mdpk4kxF5CmhwfCdAHD8", room21" } }
107// "mdpk4kxF5CmhwfCdAHD8" being the ID of the given socket
108```
109
110Those two Maps are then used when broadcasting:
111
112- a broadcast to all sockets (`io.emit()`) loops through the `sids` Map, and send the packet to all sockets
113- a broadcast to a given room (`io.to("room21").emit()`) loops through the Set in the `rooms` Map, and sends the packet to all matching sockets
114
115The Redis adapter extends the broadcast function of the in-memory adapter: the packet is also [published](https://redis.io/topics/pubsub) to a Redis channel (see [below](#protocol) for the format of the channel name).
116
117Each Socket.IO server receives this packet and broadcasts it to its own list of connected sockets.
118
119To check what's happening on your Redis instance:
120
121```
122$ redis-cli
123127.0.0.1:6379> PSUBSCRIBE *
124Reading messages... (press Ctrl-C to quit)
1251) "psubscribe"
1262) "*"
1273) (integer) 1
128
1291) "pmessage"
1302) "*"
1313) "socket.io#/#" (a broadcast to all sockets or to a list of rooms)
1324) <the packet content>
133
1341) "pmessage"
1352) "*"
1363) "socket.io#/#room21#" (a broadcast to a single room)
1374) <the packet content>
138```
139
140Note: **no data** is stored in Redis itself
141
142There are 3 Redis subscriptions per namespace:
143
144- main channel: `<prefix>#<namespace>#*` (glob)
145- request channel: `<prefix>-request#<namespace>#`
146- response channel: `<prefix>-response#<namespace>#`
147
148The request and response channels are used in the additional methods exposed by the Redis adapter, like [RedisAdapter#allRooms()](#redisadapterallrooms).
149
150
151## API
152
153### adapter(uri[, opts])
154
155`uri` is a string like `localhost:6379` where your redis server
156is located. For a list of options see below.
157
158### adapter(opts)
159
160The following options are allowed:
161
162- `key`: the name of the key to pub/sub events on as prefix (`socket.io`)
163- `host`: host to connect to redis on (`localhost`)
164- `port`: port to connect to redis on (`6379`)
165- `pubClient`: optional, the redis client to publish events on
166- `subClient`: optional, the redis client to subscribe to events on
167- `requestsTimeout`: optional, after this timeout the adapter will stop waiting from responses to request (`5000ms`)
168
169If you decide to supply `pubClient` and `subClient`, make sure you use
170[node_redis](https://github.com/mranney/node_redis) as a client or one
171with an equivalent API.
172
173### RedisAdapter
174
175The redis adapter instances expose the following properties
176that a regular `Adapter` does not
177
178- `uid`
179- `prefix`
180- `pubClient`
181- `subClient`
182- `requestsTimeout`
183
184### RedisAdapter#sockets(rooms: Set<String>)
185
186Returns the list of socket IDs connected to `rooms` across all nodes. See [Namespace#allSockets()](https://socket.io/docs/v3/server-api/#namespace-allSockets)
187
188```js
189const sockets = await io.of('/').adapter.sockets();
190console.log(sockets); // a Set containing all the connected socket ids
191
192const sockets = await io.of('/').adapter.sockets(new Set(['room1', 'room2']));
193console.log(sockets); // a Set containing the socket ids in 'room1' or in 'room2'
194
195// this method is also exposed by the Server instance
196const sockets = await io.in('room3').allSockets();
197console.log(sockets); // a Set containing the socket ids in 'room3'
198```
199
200### RedisAdapter#allRooms()
201
202Returns the list of all rooms.
203
204```js
205const rooms = await io.of('/').adapter.allRooms();
206console.log(rooms); // a Set containing all rooms (across every node)
207```
208
209### RedisAdapter#remoteJoin(id:String, room:String)
210
211Makes the socket with the given id join the room.
212
213```js
214try {
215 await io.of('/').adapter.remoteJoin('<my-id>', 'room1');
216} catch (e) {
217 // the socket was not found
218}
219```
220
221### RedisAdapter#remoteLeave(id:String, room:String)
222
223Makes the socket with the given id leave the room.
224
225```js
226try {
227 await io.of('/').adapter.remoteLeave('<my-id>', 'room1');
228} catch (e) {
229 // the socket was not found
230}
231```
232
233### RedisAdapter#remoteDisconnect(id:String, close:Boolean)
234
235Makes the socket with the given id to get disconnected. If `close` is set to true, it also closes the underlying socket.
236
237```js
238try {
239 await io.of('/').adapter.remoteDisconnect('<my-id>', true);
240} catch (e) {
241 // the socket was not found
242}
243```
244
245## Client error handling
246
247Access the `pubClient` and `subClient` properties of the
248Redis Adapter instance to subscribe to its `error` event:
249
250```js
251const adapter = require('socket.io-redis')('localhost:6379');
252adapter.pubClient.on('error', function(){});
253adapter.subClient.on('error', function(){});
254```
255
256The errors emitted from `pubClient` and `subClient` will
257also be forwarded to the adapter instance:
258
259```js
260const io = require('socket.io')(3000);
261const redisAdapter = require('socket.io-redis');
262io.adapter(redisAdapter({ host: 'localhost', port: 6379 }));
263io.of('/').adapter.on('error', function(){});
264```
265
266## Custom client (eg: with authentication)
267
268If you need to create a redisAdapter to a redis instance
269that has a password, use pub/sub options instead of passing
270a connection string.
271
272```js
273const redis = require('redis');
274const redisAdapter = require('socket.io-redis');
275const pubClient = redis.createClient(port, host, { auth_pass: "pwd" });
276const subClient = pubClient.duplicate();
277io.adapter(redisAdapter({ pubClient, subClient }));
278```
279
280## With ioredis client
281
282### Cluster example
283
284```js
285const io = require('socket.io')(3000);
286const redisAdapter = require('socket.io-redis');
287const Redis = require('ioredis');
288
289const startupNodes = [
290 {
291 port: 6380,
292 host: '127.0.0.1'
293 },
294 {
295 port: 6381,
296 host: '127.0.0.1'
297 }
298];
299
300io.adapter(redisAdapter({
301 pubClient: new Redis.Cluster(startupNodes),
302 subClient: new Redis.Cluster(startupNodes)
303}));
304```
305
306### Sentinel Example
307
308```js
309const io = require('socket.io')(3000);
310const redisAdapter = require('socket.io-redis');
311const Redis = require('ioredis');
312
313const options = {
314 sentinels: [
315 { host: 'somehost1', port: 26379 },
316 { host: 'somehost2', port: 26379 }
317 ],
318 name: 'master01'
319};
320
321io.adapter(redisAdapter({
322 pubClient: new Redis(options),
323 subClient: new Redis(options)
324}));
325```
326
327## Protocol
328
329The `socket.io-redis` adapter broadcasts and receives messages on particularly named Redis channels. For global broadcasts the channel name is:
330```
331prefix + '#' + namespace + '#'
332```
333
334In broadcasting to a single room the channel name is:
335```
336prefix + '#' + namespace + '#' + room + '#'
337```
338
339
340- `prefix`: The base channel name. Default value is `socket.io`. Changed by setting `opts.key` in `adapter(opts)` constructor
341- `namespace`: See https://github.com/socketio/socket.io#namespace.
342- `room` : Used if targeting a specific room.
343
344A number of other libraries adopt this protocol including:
345
346- [socket.io-emitter](https://github.com/socketio/socket.io-emitter)
347- [socket.io-python-emitter](https://github.com/GameXG/socket.io-python-emitter)
348- [socket.io-emitter-go](https://github.com/stackcats/socket.io-emitter-go)
349
350## License
351
352MIT