@socket.io/redis-adapter
Advanced tools
Comparing version 8.2.1 to 8.3.0
@@ -1,2 +0,2 @@ | ||
import { ClusterAdapter } from "./cluster-adapter"; | ||
import { ClusterAdapter, ClusterMessage, ClusterResponse, Offset } from "socket.io-adapter"; | ||
export interface ShardedRedisAdapterOptions { | ||
@@ -21,9 +21,13 @@ /** | ||
* Only public rooms (i.e. not related to a particular Socket ID) are taken in account, because: | ||
* | ||
* - a lot of connected clients would mean a lot of subscription/unsubscription | ||
* - the Socket ID attribute is ephemeral | ||
* | ||
* - "dynamic-private" | ||
* | ||
* Like "dynamic" but creates separate channels for private rooms as well. Useful when there is lots of 1:1 communication | ||
* via socket.emit() calls. | ||
* | ||
* @default "dynamic" | ||
*/ | ||
subscriptionMode?: "static" | "dynamic"; | ||
subscriptionMode?: "static" | "dynamic" | "dynamic-private"; | ||
} | ||
@@ -48,10 +52,11 @@ /** | ||
close(): Promise<void> | void; | ||
publishMessage(message: any): Promise<string>; | ||
doPublish(message: ClusterMessage): Promise<Offset>; | ||
private computeChannel; | ||
private dynamicChannel; | ||
publishResponse(requesterUid: any, response: any): void; | ||
doPublishResponse(requesterUid: string, response: ClusterResponse): Promise<void>; | ||
private encode; | ||
private onRawMessage; | ||
serverCount(): Promise<number>; | ||
private shouldUseASeparateNamespace; | ||
} | ||
export {}; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.createShardedAdapter = void 0; | ||
const cluster_adapter_1 = require("./cluster-adapter"); | ||
const socket_io_adapter_1 = require("socket.io-adapter"); | ||
const notepack_io_1 = require("notepack.io"); | ||
@@ -9,2 +9,5 @@ const util_1 = require("./util"); | ||
const debug = (0, debug_1.default)("socket.io-redis"); | ||
function looksLikeASocketId(room) { | ||
return typeof room === "string" && room.length === 20; | ||
} | ||
/** | ||
@@ -25,3 +28,3 @@ * Create a new Adapter based on Redis sharded Pub/Sub introduced in Redis 7.0. | ||
exports.createShardedAdapter = createShardedAdapter; | ||
class ShardedRedisAdapter extends cluster_adapter_1.ClusterAdapter { | ||
class ShardedRedisAdapter extends socket_io_adapter_1.ClusterAdapter { | ||
constructor(nsp, pubClient, subClient, opts) { | ||
@@ -40,6 +43,6 @@ super(nsp); | ||
(0, util_1.SSUBSCRIBE)(this.subClient, this.responseChannel, handler); | ||
if (this.opts.subscriptionMode === "dynamic") { | ||
if (this.opts.subscriptionMode === "dynamic" || | ||
this.opts.subscriptionMode === "dynamic-private") { | ||
this.on("create-room", (room) => { | ||
const isPublicRoom = !this.sids.has(room); | ||
if (isPublicRoom) { | ||
if (this.shouldUseASeparateNamespace(room)) { | ||
(0, util_1.SSUBSCRIBE)(this.subClient, this.dynamicChannel(room), handler); | ||
@@ -49,4 +52,3 @@ } | ||
this.on("delete-room", (room) => { | ||
const isPublicRoom = !this.sids.has(room); | ||
if (isPublicRoom) { | ||
if (this.shouldUseASeparateNamespace(room)) { | ||
(0, util_1.SUNSUBSCRIBE)(this.subClient, this.dynamicChannel(room)); | ||
@@ -59,6 +61,6 @@ } | ||
const channels = [this.channel, this.responseChannel]; | ||
if (this.opts.subscriptionMode === "dynamic") { | ||
if (this.opts.subscriptionMode === "dynamic" || | ||
this.opts.subscriptionMode === "dynamic-private") { | ||
this.rooms.forEach((_sids, room) => { | ||
const isPublicRoom = !this.sids.has(room); | ||
if (isPublicRoom) { | ||
if (this.shouldUseASeparateNamespace(room)) { | ||
channels.push(this.dynamicChannel(room)); | ||
@@ -70,7 +72,6 @@ } | ||
} | ||
publishMessage(message) { | ||
doPublish(message) { | ||
const channel = this.computeChannel(message); | ||
debug("publishing message of type %s to %s", message.type, channel); | ||
(0, util_1.SPUBLISH)(this.pubClient, channel, this.encode(message)); | ||
return Promise.resolve(""); | ||
return (0, util_1.SPUBLISH)(this.pubClient, channel, this.encode(message)).then(() => ""); | ||
} | ||
@@ -80,6 +81,8 @@ computeChannel(message) { | ||
// servers, not only the ones where the given room exists | ||
const useDynamicChannel = this.opts.subscriptionMode === "dynamic" && | ||
message.type === cluster_adapter_1.MessageType.BROADCAST && | ||
const useDynamicChannel = message.type === socket_io_adapter_1.MessageType.BROADCAST && | ||
message.data.requestId === undefined && | ||
message.data.opts.rooms.length === 1; | ||
message.data.opts.rooms.length === 1 && | ||
((this.opts.subscriptionMode === "dynamic" && | ||
!looksLikeASocketId(message.data.opts.rooms[0])) || | ||
this.opts.subscriptionMode === "dynamic-private"); | ||
if (useDynamicChannel) { | ||
@@ -95,14 +98,15 @@ return this.dynamicChannel(message.data.opts.rooms[0]); | ||
} | ||
publishResponse(requesterUid, response) { | ||
doPublishResponse(requesterUid, response) { | ||
debug("publishing response of type %s to %s", response.type, requesterUid); | ||
(0, util_1.SPUBLISH)(this.pubClient, `${this.channel}${requesterUid}#`, this.encode(response)); | ||
return (0, util_1.SPUBLISH)(this.pubClient, `${this.channel}${requesterUid}#`, this.encode(response)).then(); | ||
} | ||
encode(message) { | ||
const mayContainBinary = [ | ||
cluster_adapter_1.MessageType.BROADCAST, | ||
cluster_adapter_1.MessageType.BROADCAST_ACK, | ||
cluster_adapter_1.MessageType.FETCH_SOCKETS_RESPONSE, | ||
cluster_adapter_1.MessageType.SERVER_SIDE_EMIT, | ||
cluster_adapter_1.MessageType.SERVER_SIDE_EMIT_RESPONSE, | ||
socket_io_adapter_1.MessageType.BROADCAST, | ||
socket_io_adapter_1.MessageType.BROADCAST_ACK, | ||
socket_io_adapter_1.MessageType.FETCH_SOCKETS_RESPONSE, | ||
socket_io_adapter_1.MessageType.SERVER_SIDE_EMIT, | ||
socket_io_adapter_1.MessageType.SERVER_SIDE_EMIT_RESPONSE, | ||
].includes(message.type); | ||
// @ts-ignore | ||
if (mayContainBinary && (0, util_1.hasBinary)(message.data)) { | ||
@@ -138,2 +142,7 @@ return (0, notepack_io_1.encode)(message); | ||
} | ||
shouldUseASeparateNamespace(room) { | ||
const isPublicRoom = !this.sids.has(room); | ||
return ((this.opts.subscriptionMode === "dynamic" && isPublicRoom) || | ||
this.opts.subscriptionMode === "dynamic-private"); | ||
} | ||
} |
/// <reference types="node" /> | ||
export declare function hasBinary(obj: any, toJSON?: boolean): boolean; | ||
export declare function randomId(): string; | ||
export declare function parseNumSubResponse(res: any): number; | ||
@@ -8,3 +7,6 @@ export declare function sumValues(values: any): any; | ||
export declare function SUNSUBSCRIBE(redisClient: any, channel: string | string[]): void; | ||
export declare function SPUBLISH(redisClient: any, channel: string, payload: string | Uint8Array): void; | ||
/** | ||
* @see https://redis.io/commands/spublish/ | ||
*/ | ||
export declare function SPUBLISH(redisClient: any, channel: string, payload: string | Uint8Array): any; | ||
export declare function PUBSUB(redisClient: any, arg: string, channel: string): any; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.PUBSUB = exports.SPUBLISH = exports.SUNSUBSCRIBE = exports.SSUBSCRIBE = exports.sumValues = exports.parseNumSubResponse = exports.randomId = exports.hasBinary = void 0; | ||
const crypto_1 = require("crypto"); | ||
exports.PUBSUB = exports.SPUBLISH = exports.SUNSUBSCRIBE = exports.SSUBSCRIBE = exports.sumValues = exports.parseNumSubResponse = exports.hasBinary = void 0; | ||
function hasBinary(obj, toJSON) { | ||
@@ -31,6 +30,2 @@ if (!obj || typeof obj !== "object") { | ||
exports.hasBinary = hasBinary; | ||
function randomId() { | ||
return (0, crypto_1.randomBytes)(8).toString("hex"); | ||
} | ||
exports.randomId = randomId; | ||
function parseNumSubResponse(res) { | ||
@@ -57,2 +52,3 @@ return parseInt(res[1], 10); | ||
} | ||
const kHandlers = Symbol("handlers"); | ||
function SSUBSCRIBE(redisClient, channel, handler) { | ||
@@ -63,8 +59,11 @@ if (isRedisV4Client(redisClient)) { | ||
else { | ||
if (!redisClient[kHandlers]) { | ||
redisClient[kHandlers] = new Map(); | ||
redisClient.on("smessageBuffer", (rawChannel, message) => { | ||
var _a; | ||
(_a = redisClient[kHandlers].get(rawChannel.toString())) === null || _a === void 0 ? void 0 : _a(message, rawChannel); | ||
}); | ||
} | ||
redisClient[kHandlers].set(channel, handler); | ||
redisClient.ssubscribe(channel); | ||
redisClient.on("smessageBuffer", (rawChannel, message) => { | ||
if (rawChannel.toString() === channel) { | ||
handler(message, rawChannel); | ||
} | ||
}); | ||
} | ||
@@ -79,11 +78,20 @@ } | ||
redisClient.sunsubscribe(channel); | ||
if (Array.isArray(channel)) { | ||
channel.forEach((c) => redisClient[kHandlers].delete(c)); | ||
} | ||
else { | ||
redisClient[kHandlers].delete(channel); | ||
} | ||
} | ||
} | ||
exports.SUNSUBSCRIBE = SUNSUBSCRIBE; | ||
/** | ||
* @see https://redis.io/commands/spublish/ | ||
*/ | ||
function SPUBLISH(redisClient, channel, payload) { | ||
if (isRedisV4Client(redisClient)) { | ||
redisClient.sPublish(channel, payload); | ||
return redisClient.sPublish(channel, payload); | ||
} | ||
else { | ||
redisClient.spublish(channel, payload); | ||
return redisClient.spublish(channel, payload); | ||
} | ||
@@ -90,0 +98,0 @@ } |
{ | ||
"name": "@socket.io/redis-adapter", | ||
"version": "8.2.1", | ||
"version": "8.3.0", | ||
"description": "The Socket.IO Redis adapter, allowing to broadcast events between several Socket.IO servers", | ||
@@ -16,6 +16,7 @@ "license": "MIT", | ||
"scripts": { | ||
"compile": "rimraf ./dist && tsc", | ||
"test": "npm run format:check && tsc && nyc mocha --bail --require ts-node/register test/test-runner.ts", | ||
"format:check": "prettier --parser typescript --check 'lib/**/*.ts' 'test/**/*.ts'", | ||
"format:fix": "prettier --parser typescript --write 'lib/**/*.ts' 'test/**/*.ts'", | ||
"prepack": "tsc" | ||
"prepack": "npm run compile" | ||
}, | ||
@@ -28,3 +29,3 @@ "dependencies": { | ||
"peerDependencies": { | ||
"socket.io-adapter": "^2.4.0" | ||
"socket.io-adapter": "^2.5.4" | ||
}, | ||
@@ -42,2 +43,3 @@ "devDependencies": { | ||
"redis-v3": "npm:redis@^3.1.2", | ||
"rimraf": "^5.0.5", | ||
"socket.io": "^4.6.1", | ||
@@ -44,0 +46,0 @@ "socket.io-client": "^4.1.1", |
404
README.md
@@ -1,127 +0,161 @@ | ||
# socket.io-redis | ||
# Socket.IO Redis adapter | ||
[![Build Status](https://github.com/socketio/socket.io-redis/workflows/CI/badge.svg?branch=main)](https://github.com/socketio/socket.io-redis/actions) | ||
[![npm version](https://badge.fury.io/js/%40socket.io%2Fredis-adapter.svg)](https://badge.fury.io/js/%40socket.io%2Fredis-adapter) | ||
The `@socket.io/redis-adapter` package allows broadcasting packets between multiple Socket.IO servers. | ||
## Table of contents | ||
<picture> | ||
<source media="(prefers-color-scheme: dark)" srcset="./assets/adapter_dark.png"> | ||
<img alt="Diagram of Socket.IO packets forwarded through Redis" src="./assets/adapter.png"> | ||
</picture> | ||
- [How to use](#how-to-use) | ||
- [CommonJS](#commonjs) | ||
- [ES6 module](#es6-modules) | ||
- [TypeScript](#typescript) | ||
- [Sharded Redis Pub/Sub](#sharded-redis-pubsub) | ||
**Table of contents** | ||
- [Supported features](#supported-features) | ||
- [Installation](#installation) | ||
- [Compatibility table](#compatibility-table) | ||
- [How does it work under the hood?](#how-does-it-work-under-the-hood) | ||
- [API](#api) | ||
- [adapter(pubClient, subClient[, opts])](#adapterpubclient-subclient-opts) | ||
- [RedisAdapter](#redisadapter) | ||
- [RedisAdapter#allRooms()](#redisadapterallrooms) | ||
- [With ioredis client](#with-ioredis-client) | ||
- [Cluster example](#cluster-example) | ||
- [Sentinel Example](#sentinel-example) | ||
- [Protocol](#protocol) | ||
- [Migrating from `socket.io-redis`](#migrating-from-socketio-redis) | ||
- [Usage](#usage) | ||
- [With the `redis` package](#with-the-redis-package) | ||
- [With the `redis` package and a Redis cluster](#with-the-redis-package-and-a-redis-cluster) | ||
- [With the `ioredis` package](#with-the-ioredis-package) | ||
- [With the `ioredis` package and a Redis cluster](#with-the-ioredis-package-and-a-redis-cluster) | ||
- [With Redis sharded Pub/Sub](#with-redis-sharded-pubsub) | ||
- [Options](#options) | ||
- [Default adapter](#default-adapter) | ||
- [Sharded adapter](#sharded-adapter) | ||
- [License](#license) | ||
## How to use | ||
## Supported features | ||
Installation: | ||
| Feature | `socket.io` version | Support | | ||
|---------------------------------|---------------------|------------------------------------------------| | ||
| Socket management | `4.0.0` | :white_check_mark: YES (since version `6.1.0`) | | ||
| Inter-server communication | `4.1.0` | :white_check_mark: YES (since version `7.0.0`) | | ||
| Broadcast with acknowledgements | `4.5.0` | :white_check_mark: YES (since version `7.2.0`) | | ||
| Connection state recovery | `4.6.0` | :x: NO | | ||
## Installation | ||
``` | ||
$ npm install @socket.io/redis-adapter redis | ||
npm install @socket.io/redis-adapter | ||
``` | ||
### CommonJS | ||
## Compatibility table | ||
| Redis Adapter version | Socket.IO server version | | ||
|-----------------------|--------------------------| | ||
| 4.x | 1.x | | ||
| 5.x | 2.x | | ||
| 6.0.x | 3.x | | ||
| 6.1.x | 4.x | | ||
| 7.x and above | 4.3.1 and above | | ||
## Usage | ||
### With the `redis` package | ||
```js | ||
const { Server } = require('socket.io'); | ||
const { createClient } = require('redis'); | ||
const { createAdapter } = require('@socket.io/redis-adapter'); | ||
import { createClient } from "redis"; | ||
import { Server } from "socket.io"; | ||
import { createAdapter } from "@socket.io/redis-adapter"; | ||
const io = new Server(); | ||
const pubClient = createClient({ host: 'localhost', port: 6379 }); | ||
const pubClient = createClient({ url: "redis://localhost:6379" }); | ||
const subClient = pubClient.duplicate(); | ||
Promise.all([pubClient.connect(), subClient.connect()]).then(() => { | ||
io.adapter(createAdapter(pubClient, subClient)); | ||
io.listen(3000); | ||
await Promise.all([ | ||
pubClient.connect(), | ||
subClient.connect() | ||
]); | ||
const io = new Server({ | ||
adapter: createAdapter(pubClient, subClient) | ||
}); | ||
io.listen(3000); | ||
``` | ||
With `redis@3`, calling `connect()` on the Redis clients is not needed: | ||
### With the `redis` package and a Redis cluster | ||
```js | ||
const { Server } = require('socket.io'); | ||
const { createClient } = require('redis'); | ||
const { createAdapter } = require('@socket.io/redis-adapter'); | ||
import { createCluster } from "redis"; | ||
import { Server } from "socket.io"; | ||
import { createAdapter } from "@socket.io/redis-adapter"; | ||
const io = new Server(); | ||
const pubClient = createClient({ host: 'localhost', port: 6379 }); | ||
const pubClient = createCluster({ | ||
rootNodes: [ | ||
{ | ||
url: "redis://localhost:7000", | ||
}, | ||
{ | ||
url: "redis://localhost:7001", | ||
}, | ||
{ | ||
url: "redis://localhost:7002", | ||
}, | ||
], | ||
}); | ||
const subClient = pubClient.duplicate(); | ||
io.adapter(createAdapter(pubClient, subClient)); | ||
await Promise.all([ | ||
pubClient.connect(), | ||
subClient.connect() | ||
]); | ||
const io = new Server({ | ||
adapter: createAdapter(pubClient, subClient) | ||
}); | ||
io.listen(3000); | ||
``` | ||
### ES6 modules | ||
### With the `ioredis` package | ||
```js | ||
import { Server } from 'socket.io'; | ||
import { createClient } from 'redis'; | ||
import { createAdapter } from '@socket.io/redis-adapter'; | ||
import { Redis } from "ioredis"; | ||
import { Server } from "socket.io"; | ||
import { createAdapter } from "@socket.io/redis-adapter"; | ||
const io = new Server(); | ||
const pubClient = createClient({ host: 'localhost', port: 6379 }); | ||
const pubClient = new Redis(); | ||
const subClient = pubClient.duplicate(); | ||
Promise.all([pubClient.connect(), subClient.connect()]).then(() => { | ||
io.adapter(createAdapter(pubClient, subClient)); | ||
io.listen(3000); | ||
const io = new Server({ | ||
adapter: createAdapter(pubClient, subClient) | ||
}); | ||
io.listen(3000); | ||
``` | ||
### TypeScript | ||
### With the `ioredis` package and a Redis cluster | ||
```ts | ||
import { Server } from 'socket.io'; | ||
import { createClient } from 'redis'; | ||
import { createAdapter } from '@socket.io/redis-adapter'; | ||
```js | ||
import { Cluster } from "ioredis"; | ||
import { Server } from "socket.io"; | ||
import { createAdapter } from "@socket.io/redis-adapter"; | ||
const io = new Server(); | ||
const pubClient = createClient({ host: 'localhost', port: 6379 }); | ||
const pubClient = new Cluster([ | ||
{ | ||
host: "localhost", | ||
port: 7000, | ||
}, | ||
{ | ||
host: "localhost", | ||
port: 7001, | ||
}, | ||
{ | ||
host: "localhost", | ||
port: 7002, | ||
}, | ||
]); | ||
const subClient = pubClient.duplicate(); | ||
Promise.all([pubClient.connect(), subClient.connect()]).then(() => { | ||
io.adapter(createAdapter(pubClient, subClient)); | ||
io.listen(3000); | ||
const io = new Server({ | ||
adapter: createAdapter(pubClient, subClient) | ||
}); | ||
``` | ||
By running Socket.IO with the `@socket.io/redis-adapter` adapter you can run | ||
multiple Socket.IO instances in different processes or servers that can | ||
all broadcast and emit events to and from each other. | ||
So any of the following commands: | ||
```js | ||
io.emit('hello', 'to all clients'); | ||
io.to('room42').emit('hello', "to all clients in 'room42' room"); | ||
io.on('connection', (socket) => { | ||
socket.broadcast.emit('hello', 'to all clients except sender'); | ||
socket.to('room42').emit('hello', "to all clients in 'room42' room except sender"); | ||
}); | ||
io.listen(3000); | ||
``` | ||
will properly be broadcast to the clients through the Redis [Pub/Sub mechanism](https://redis.io/topics/pubsub). | ||
### With Redis sharded Pub/Sub | ||
If you need to emit events to socket.io instances from a non-socket.io | ||
process, you should use [socket.io-emitter](https://github.com/socketio/socket.io-emitter). | ||
### Sharded Redis Pub/Sub | ||
Sharded Pub/Sub was introduced in Redis 7.0 in order to help scaling the usage of Pub/Sub in cluster mode. | ||
Reference: https://redis.io/docs/manual/pubsub/#sharded-pubsub | ||
Reference: https://redis.io/docs/interact/pubsub/#sharded-pubsub | ||
@@ -131,7 +165,7 @@ A dedicated adapter can be created with the `createShardedAdapter()` method: | ||
```js | ||
import { Server } from 'socket.io'; | ||
import { createClient } from 'redis'; | ||
import { createShardedAdapter } from '@socket.io/redis-adapter'; | ||
import { Server } from "socket.io"; | ||
import { createClient } from "redis"; | ||
import { createShardedAdapter } from "@socket.io/redis-adapter"; | ||
const pubClient = createClient({ host: 'localhost', port: 6379 }); | ||
const pubClient = createClient({ host: "localhost", port: 6379 }); | ||
const subClient = pubClient.duplicate(); | ||
@@ -156,198 +190,24 @@ | ||
Note: it is not currently possible to use the sharded adapter with the `ioredis` package and a Redis cluster ([reference](https://github.com/luin/ioredis/issues/1759)). | ||
## Compatibility table | ||
## Options | ||
| Redis Adapter version | Socket.IO server version | | ||
|-----------------------| ------------------------ | | ||
| 4.x | 1.x | | ||
| 5.x | 2.x | | ||
| 6.0.x | 3.x | | ||
| 6.1.x and above | 4.x | | ||
### Default adapter | ||
## How does it work under the hood? | ||
| Name | Description | Default value | | ||
|------------------------------------|-------------------------------------------------------------------------------|---------------| | ||
| `key` | The prefix for the Redis Pub/Sub channels. | `socket.io` | | ||
| `requestsTimeout` | After this timeout the adapter will stop waiting from responses to request. | `5_000` | | ||
| `publishOnSpecificResponseChannel` | Whether to publish a response to the channel specific to the requesting node. | `false` | | ||
| `parser` | The parser to use for encoding and decoding messages sent to Redis. | `-` | | ||
This adapter extends the [in-memory adapter](https://github.com/socketio/socket.io-adapter/) that is included by default with the Socket.IO server. | ||
### Sharded adapter | ||
The in-memory adapter stores the relationships between Sockets and Rooms in two Maps. | ||
| Name | Description | Default value | | ||
|--------------------|-----------------------------------------------------------------------------------------|---------------| | ||
| `channelPrefix` | The prefix for the Redis Pub/Sub channels. | `socket.io` | | ||
| `subscriptionMode` | The subscription mode impacts the number of Redis Pub/Sub channels used by the adapter. | `dynamic` | | ||
When you run `socket.join("room21")`, here's what happens: | ||
``` | ||
console.log(adapter.rooms); // Map { "room21" => Set { "mdpk4kxF5CmhwfCdAHD8" } } | ||
console.log(adapter.sids); // Map { "mdpk4kxF5CmhwfCdAHD8" => Set { "mdpk4kxF5CmhwfCdAHD8", "room21" } } | ||
// "mdpk4kxF5CmhwfCdAHD8" being the ID of the given socket | ||
``` | ||
Those two Maps are then used when broadcasting: | ||
- a broadcast to all sockets (`io.emit()`) loops through the `sids` Map, and send the packet to all sockets | ||
- 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 | ||
The 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). | ||
Each Socket.IO server receives this packet and broadcasts it to its own list of connected sockets. | ||
To check what's happening on your Redis instance: | ||
``` | ||
$ redis-cli | ||
127.0.0.1:6379> PSUBSCRIBE * | ||
Reading messages... (press Ctrl-C to quit) | ||
1) "psubscribe" | ||
2) "*" | ||
3) (integer) 1 | ||
1) "pmessage" | ||
2) "*" | ||
3) "socket.io#/#" (a broadcast to all sockets or to a list of rooms) | ||
4) <the packet content> | ||
1) "pmessage" | ||
2) "*" | ||
3) "socket.io#/#room21#" (a broadcast to a single room) | ||
4) <the packet content> | ||
``` | ||
Note: **no data** is stored in Redis itself | ||
There are 3 Redis subscriptions per namespace: | ||
- main channel: `<prefix>#<namespace>#*` (glob) | ||
- request channel: `<prefix>-request#<namespace>#` | ||
- response channel: `<prefix>-response#<namespace>#` | ||
The request and response channels are used in the additional methods exposed by the Redis adapter, like [RedisAdapter#allRooms()](#redisadapterallrooms). | ||
## API | ||
### adapter(pubClient, subClient[, opts]) | ||
The following options are allowed: | ||
- `key`: the name of the key to pub/sub events on as prefix (`socket.io`) | ||
- `requestsTimeout`: optional, after this timeout the adapter will stop waiting from responses to request (`5000ms`) | ||
- `parser`: optional, parser to use for encoding and decoding messages passed through Redis ([`notepack.io`](https://www.npmjs.com/package/notepack.io)) | ||
### RedisAdapter | ||
The redis adapter instances expose the following properties | ||
that a regular `Adapter` does not | ||
- `uid` | ||
- `prefix` | ||
- `pubClient` | ||
- `subClient` | ||
- `requestsTimeout` | ||
- `parser` | ||
### RedisAdapter#allRooms() | ||
Returns the list of all rooms. | ||
```js | ||
const rooms = await io.of('/').adapter.allRooms(); | ||
console.log(rooms); // a Set containing all rooms (across every node) | ||
``` | ||
## With ioredis client | ||
### Cluster example | ||
```js | ||
const io = require('socket.io')(3000); | ||
const redisAdapter = require('@socket.io/redis-adapter'); | ||
const Redis = require('ioredis'); | ||
const startupNodes = [ | ||
{ | ||
port: 6380, | ||
host: '127.0.0.1' | ||
}, | ||
{ | ||
port: 6381, | ||
host: '127.0.0.1' | ||
} | ||
]; | ||
const pubClient = new Redis.Cluster(startupNodes); | ||
const subClient = pubClient.duplicate(); | ||
io.adapter(redisAdapter(pubClient, subClient)); | ||
``` | ||
### Sentinel Example | ||
```js | ||
const io = require('socket.io')(3000); | ||
const redisAdapter = require('@socket.io/redis-adapter'); | ||
const Redis = require('ioredis'); | ||
const options = { | ||
sentinels: [ | ||
{ host: 'somehost1', port: 26379 }, | ||
{ host: 'somehost2', port: 26379 } | ||
], | ||
name: 'master01' | ||
}; | ||
const pubClient = new Redis(options); | ||
const subClient = pubClient.duplicate(); | ||
io.adapter(redisAdapter(pubClient, subClient)); | ||
``` | ||
## Protocol | ||
The `@socket.io/redis-adapter` adapter broadcasts and receives messages on particularly named Redis channels. For global broadcasts the channel name is: | ||
``` | ||
prefix + '#' + namespace + '#' | ||
``` | ||
In broadcasting to a single room the channel name is: | ||
``` | ||
prefix + '#' + namespace + '#' + room + '#' | ||
``` | ||
- `prefix`: The base channel name. Default value is `socket.io`. Changed by setting `opts.key` in `adapter(opts)` constructor | ||
- `namespace`: See https://github.com/socketio/socket.io#namespace. | ||
- `room` : Used if targeting a specific room. | ||
A number of other libraries adopt this protocol including: | ||
- [socket.io-redis-emitter](https://github.com/socketio/socket.io-redis-emitter) | ||
- [socket.io-python-emitter](https://github.com/GameXG/socket.io-python-emitter) | ||
- [socket.io-emitter-go](https://github.com/stackcats/socket.io-emitter-go) | ||
## Migrating from `socket.io-redis` | ||
The package was renamed from `socket.io-redis` to `@socket.io/redis-adapter` in [v7](https://github.com/socketio/socket.io-redis-adapter/releases/tag/7.0.0), in order to match the name of the Redis emitter (`@socket.io/redis-emitter`). | ||
To migrate to the new package, you'll need to make sure to provide your own Redis clients, as the package will no longer create Redis clients on behalf of the user. | ||
Before: | ||
```js | ||
const redisAdapter = require("socket.io-redis"); | ||
io.adapter(redisAdapter({ host: "localhost", port: 6379 })); | ||
``` | ||
After: | ||
```js | ||
const { createClient } = require("redis"); | ||
const { createAdapter } = require("@socket.io/redis-adapter"); | ||
const pubClient = createClient({ host: "localhost", port: 6379 }); | ||
const subClient = pubClient.duplicate(); | ||
io.adapter(createAdapter(pubClient, subClient)); | ||
``` | ||
Please note that the communication protocol between the Socket.IO servers has not been updated, so you can have some servers with `socket.io-redis` and some others with `@socket.io/redis-adapter` at the same time. | ||
## License | ||
MIT | ||
[MIT](LICENSE) |
54091
15
9
1171
212