diff --git a/CHANGELOG.md b/CHANGELOG.md index c145f38..5ddef8d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,21 @@ # History -- [0.2.0](#020-2024-02-21) (February 2024) -- [0.1.0](#010-2023-04-06) (April 2023) +- [0.2.1](#021-2024-03-11) (Mar 2024) +- [0.2.0](#020-2024-02-21) (Feb 2024) +- [0.1.0](#010-2023-04-06) (Apr 2023) # Release notes +## [0.2.1](https://github.com/socketio/socket.io-redis-streams-adapter/compare/0.2.0...0.2.1) (2024-03-11) + + +### Bug Fixes + +* ensure CSR works with a Redis cluster ([d2d635d](https://github.com/socketio/socket.io-redis-streams-adapter/commit/d2d635dbfe506f6af80025a2bfd9b157b5941ab3)) +* ensure CSR works with the ioredis package ([78075ec](https://github.com/socketio/socket.io-redis-streams-adapter/commit/78075ec6663f839d80972520d89eca994a09ae8f)) + + + ## [0.2.0](https://github.com/socketio/socket.io-redis-streams-adapter/compare/0.1.0...0.2.0) (2024-02-21) diff --git a/README.md b/README.md index 239e962..243451b 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,10 @@ Related packages: - [Installation](#installation) - [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) - [Options](#options) - [How it works](#how-it-works) - [License](#license) @@ -38,6 +42,8 @@ npm install @socket.io/redis-streams-adapter redis ## Usage +### With the `redis` package + ```js import { createClient } from "redis"; import { Server } from "socket.io"; @@ -54,6 +60,81 @@ const io = new Server({ io.listen(3000); ``` +### With the `redis` package and a Redis cluster + +```js +import { createCluster } from "redis"; +import { Server } from "socket.io"; +import { createAdapter } from "@socket.io/redis-streams-adapter"; + +const redisClient = createCluster({ + rootNodes: [ + { + url: "redis://localhost:7000", + }, + { + url: "redis://localhost:7001", + }, + { + url: "redis://localhost:7002", + }, + ], +}); + +await redisClient.connect(); + +const io = new Server({ + adapter: createAdapter(redisClient) +}); + +io.listen(3000); +``` + +### With the `ioredis` package + +```js +import { Redis } from "ioredis"; +import { Server } from "socket.io"; +import { createAdapter } from "@socket.io/redis-streams-adapter"; + +const redisClient = new Redis(); + +const io = new Server({ + adapter: createAdapter(redisClient) +}); + +io.listen(3000); +``` + +### With the `ioredis` package and a Redis cluster + +```js +import { Cluster } from "ioredis"; +import { Server } from "socket.io"; +import { createAdapter } from "@socket.io/redis-streams-adapter"; + +const redisClient = new Cluster([ + { + host: "localhost", + port: 7000, + }, + { + host: "localhost", + port: 7001, + }, + { + host: "localhost", + port: 7002, + }, +]); + +const io = new Server({ + adapter: createAdapter(redisClient) +}); + +io.listen(3000); +``` + ## Options | Name | Description | Default value | diff --git a/lib/adapter.ts b/lib/adapter.ts index e28cda0..17f082a 100644 --- a/lib/adapter.ts +++ b/lib/adapter.ts @@ -1,33 +1,20 @@ -import { - ClusterAdapterWithHeartbeat, - type ClusterMessage, - type PrivateSessionId, - type Session, - type ServerId, - type ClusterResponse, +import { ClusterAdapterWithHeartbeat, MessageType } from "socket.io-adapter"; +import type { + ClusterAdapterOptions, + ClusterMessage, + PrivateSessionId, + Session, + ServerId, + ClusterResponse, } from "socket.io-adapter"; import { decode, encode } from "@msgpack/msgpack"; import debugModule from "debug"; -import { hasBinary, XADD, XREAD } from "./util"; +import { hasBinary, GETDEL, SET, XADD, XRANGE, XREAD } from "./util"; const debug = debugModule("socket.io-redis-streams-adapter"); const RESTORE_SESSION_MAX_XRANGE_CALLS = 100; -// TODO ClusterAdapterOptions should be exported by the socket.io-adapter package -interface ClusterAdapterOptions { - /** - * The number of ms between two heartbeats. - * @default 5_000 - */ - heartbeatInterval?: number; - /** - * The number of ms without heartbeat before we consider a node down. - * @default 10_000 - */ - heartbeatTimeout?: number; -} - export interface RedisStreamsAdapterOptions { /** * The name of the Redis stream. @@ -188,13 +175,12 @@ class RedisStreamsAdapter extends ClusterAdapterWithHeartbeat { // @ts-ignore if (message.data) { - // TODO MessageType should be exported by the socket.io-adapter package const mayContainBinary = [ - 3, // MessageType.BROADCAST, - 8, // MessageType.FETCH_SOCKETS_RESPONSE, - 9, // MessageType.SERVER_SIDE_EMIT, - 10, // MessageType.SERVER_SIDE_EMIT_RESPONSE, - 12, // MessageType.BROADCAST_ACK, + MessageType.BROADCAST, + MessageType.FETCH_SOCKETS_RESPONSE, + MessageType.SERVER_SIDE_EMIT, + MessageType.SERVER_SIDE_EMIT_RESPONSE, + MessageType.BROADCAST_ACK, ].includes(message.type); // @ts-ignore @@ -249,9 +235,12 @@ class RedisStreamsAdapter extends ClusterAdapterWithHeartbeat { const sessionKey = this.#opts.sessionKeyPrefix + session.pid; const encodedSession = Buffer.from(encode(session)).toString("base64"); - this.#redisClient.set(sessionKey, encodedSession, { - PX: this.nsp.server.opts.connectionStateRecovery.maxDisconnectionDuration, - }); + SET( + this.#redisClient, + sessionKey, + encodedSession, + this.nsp.server.opts.connectionStateRecovery.maxDisconnectionDuration + ); } override async restoreSession( @@ -266,12 +255,13 @@ class RedisStreamsAdapter extends ClusterAdapterWithHeartbeat { const sessionKey = this.#opts.sessionKeyPrefix + pid; - const [rawSession, offsetExists] = await this.#redisClient - .multi() - .get(sessionKey) - .del(sessionKey) // GETDEL was added in Redis version 6.2 - .xRange(this.#opts.streamName, offset, offset) - .exec(); + const results = await Promise.all([ + GETDEL(this.#redisClient, sessionKey), + XRANGE(this.#redisClient, this.#opts.streamName, offset, offset), + ]); + + const rawSession = results[0][0]; + const offsetExists = results[1][0]; if (!rawSession || !offsetExists) { return Promise.reject("session or offset not found"); @@ -286,13 +276,11 @@ class RedisStreamsAdapter extends ClusterAdapterWithHeartbeat { // FIXME we need to add an arbitrary limit here, because if entries are added faster than what we can consume, then // we will loop endlessly. But if we stop before reaching the end of the stream, we might lose messages. for (let i = 0; i < RESTORE_SESSION_MAX_XRANGE_CALLS; i++) { - const entries = await this.#redisClient.xRange( + const entries = await XRANGE( + this.#redisClient, this.#opts.streamName, RedisStreamsAdapter.nextOffset(offset), - "+", - { - COUNT: this.#opts.readCount, - } + "+" ); if (entries.length === 0) { diff --git a/lib/util.ts b/lib/util.ts index a86fcbb..c311ce4 100644 --- a/lib/util.ts +++ b/lib/util.ts @@ -47,6 +47,23 @@ function isRedisV4Client(redisClient: any) { return typeof redisClient.sSubscribe === "function"; } +/** + * Map the output of the XREAD/XRANGE command with the ioredis package to the format of the redis package + * @param result + */ +function mapResult(result) { + const id = result[0]; + const inlineValues = result[1]; + const message = {}; + for (let i = 0; i < inlineValues.length; i += 2) { + message[inlineValues[i]] = inlineValues[i + 1]; + } + return { + id, + message, + }; +} + /** * @see https://redis.io/commands/xread/ */ @@ -81,18 +98,7 @@ export function XREAD( } return [ { - messages: results[0][1].map((result) => { - const id = result[0]; - const inlineValues = result[1]; - const message = {}; - for (let i = 0; i < inlineValues.length; i += 2) { - message[inlineValues[i]] = inlineValues[i + 1]; - } - return { - id, - message, - }; - }), + messages: results[0][1].map(mapResult), }, ]; }); @@ -125,3 +131,58 @@ export function XADD( return redisClient.xadd.call(redisClient, args); } } + +/** + * @see https://redis.io/commands/xrange/ + */ +export function XRANGE( + redisClient: any, + streamName: string, + start: string, + end: string +) { + if (isRedisV4Client(redisClient)) { + return redisClient.xRange(streamName, start, end); + } else { + return redisClient.xrange(streamName, start, end).then((res) => { + return res.map(mapResult); + }); + } +} + +/** + * @see https://redis.io/commands/set/ + */ +export function SET( + redisClient: any, + key: string, + value: string, + expiryInSeconds: number +) { + if (isRedisV4Client(redisClient)) { + return redisClient.set(key, value, { + PX: expiryInSeconds, + }); + } else { + return redisClient.set(key, value, "PX", expiryInSeconds); + } +} + +/** + * @see https://redis.io/commands/getdel/ + */ +export function GETDEL(redisClient: any, key: string) { + if (isRedisV4Client(redisClient)) { + // note: GETDEL was added in Redis version 6.2 + return redisClient.multi().get(key).del(key).exec(); + } else { + return redisClient + .multi() + .get(key) + .del(key) + .exec() + .then((res) => { + return [res[0][1]]; + }); + } +} diff --git a/package-lock.json b/package-lock.json index 4d5d2ab..c3d1eb1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@socket.io/redis-streams-adapter", - "version": "0.1.0", + "version": "0.2.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@socket.io/redis-streams-adapter", - "version": "0.1.0", + "version": "0.2.0", "license": "MIT", "dependencies": { "@msgpack/msgpack": "~2.8.0", @@ -23,6 +23,7 @@ "nyc": "^15.1.0", "prettier": "^2.8.7", "redis": "^4.6.5", + "rimraf": "^5.0.5", "socket.io": "^4.6.1", "socket.io-client": "^4.6.1", "ts-node": "^10.9.1", @@ -32,7 +33,7 @@ "node": ">=14.0.0" }, "peerDependencies": { - "socket.io-adapter": "^2.5.2" + "socket.io-adapter": "^2.5.4" } }, "node_modules/@babel/code-frame": { @@ -286,6 +287,102 @@ "integrity": "sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg==", "dev": true }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", @@ -344,6 +441,16 @@ "node": ">= 10" } }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "optional": true, + "engines": { + "node": ">=14" + } + }, "node_modules/@redis/bloom": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-1.2.0.tgz", @@ -907,6 +1014,12 @@ "node": ">=0.3.1" } }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true + }, "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -1479,6 +1592,21 @@ "node": ">=8" } }, + "node_modules/istanbul-lib-processinfo/node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/istanbul-lib-report": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", @@ -1541,6 +1669,24 @@ "node": ">=8" } }, + "node_modules/jackspeak": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz", + "integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==", + "dev": true, + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -1697,6 +1843,15 @@ "node": ">=8" } }, + "node_modules/lru-cache": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz", + "integrity": "sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==", + "dev": true, + "engines": { + "node": "14 || >=16.14" + } + }, "node_modules/make-dir": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", @@ -1751,6 +1906,15 @@ "node": "*" } }, + "node_modules/minipass": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz", + "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==", + "dev": true, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, "node_modules/mocha": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.1.0.tgz", @@ -2094,6 +2258,21 @@ "node": ">=8.9" } }, + "node_modules/nyc/node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -2208,6 +2387,22 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true }, + "node_modules/path-scurry": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.1.tgz", + "integrity": "sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==", + "dev": true, + "dependencies": { + "lru-cache": "^9.1.1 || ^10.0.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", @@ -2365,15 +2560,92 @@ } }, "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.5.tgz", + "integrity": "sha512-CqDakW+hMe/Bz202FPEymy68P+G50RfMQK+Qo5YUqc9SPipvbGjCGKd0RSKEelbsfQuw3g5NZDSrlZZAJurH1A==", "dev": true, "dependencies": { - "glob": "^7.1.3" + "glob": "^10.3.7" }, "bin": { - "rimraf": "bin.js" + "rimraf": "dist/esm/bin.mjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/rimraf/node_modules/foreground-child": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", + "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/glob": { + "version": "10.3.10", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz", + "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==", + "dev": true, + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^2.3.5", + "minimatch": "^9.0.1", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", + "path-scurry": "^1.10.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "engines": { + "node": ">=14" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -2455,9 +2727,9 @@ } }, "node_modules/socket.io-adapter": { - "version": "2.5.3", - "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.3.tgz", - "integrity": "sha512-OtkQtynXUM0JSEwmI6YlEJ5hU9kpDUVjda0hx8QVffKhqum53xhynH8eTCyjHSfI8FiJnyfK8I3Dlc88Jr81Dg==", + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.4.tgz", + "integrity": "sha512-wDNHGXGewWAjQPt3pyeYBtpWSq9cLE5UW1ZUPL/2eGK9jtse/FpXib7epSTsz0Q0m+6sg6Y4KtcFTlah1bdOVg==", "dependencies": { "debug": "~4.3.4", "ws": "~8.11.0" @@ -2517,6 +2789,21 @@ "node": ">=8" } }, + "node_modules/spawn-wrap/node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", @@ -2543,13 +2830,41 @@ "node": ">=8" } }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "dependencies": { - "ansi-regex": "^5.0.0" + "ansi-regex": "^5.0.1" }, "engines": { "node": ">=8" @@ -2775,6 +3090,57 @@ "node": ">=8" } }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, "node_modules/wrap-ansi/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -3198,6 +3564,71 @@ "integrity": "sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg==", "dev": true }, + "@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "requires": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true + }, + "ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true + }, + "emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true + }, + "string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "requires": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + } + }, + "strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "requires": { + "ansi-regex": "^6.0.1" + } + }, + "wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "requires": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + } + } + } + }, "@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", @@ -3244,6 +3675,13 @@ "resolved": "https://registry.npmjs.org/@msgpack/msgpack/-/msgpack-2.8.0.tgz", "integrity": "sha512-h9u4u/jiIRKbq25PM+zymTyW6bhTzELvOoUd+AvYriWOAKpLGnIamaET3pnHYoI5iYphAHBI4ayx0MehR+VVPQ==" }, + "@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "optional": true + }, "@redis/bloom": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-1.2.0.tgz", @@ -3687,6 +4125,12 @@ "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", "dev": true }, + "eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true + }, "emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -4101,6 +4545,17 @@ "p-map": "^3.0.0", "rimraf": "^3.0.0", "uuid": "^3.3.3" + }, + "dependencies": { + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + } } }, "istanbul-lib-report": { @@ -4154,6 +4609,16 @@ "istanbul-lib-report": "^3.0.0" } }, + "jackspeak": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz", + "integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==", + "dev": true, + "requires": { + "@isaacs/cliui": "^8.0.2", + "@pkgjs/parseargs": "^0.11.0" + } + }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -4270,6 +4735,12 @@ } } }, + "lru-cache": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz", + "integrity": "sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==", + "dev": true + }, "make-dir": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", @@ -4309,6 +4780,12 @@ "brace-expansion": "^1.1.7" } }, + "minipass": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz", + "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==", + "dev": true + }, "mocha": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.1.0.tgz", @@ -4560,6 +5037,17 @@ "spawn-wrap": "^2.0.0", "test-exclude": "^6.0.0", "yargs": "^15.0.2" + }, + "dependencies": { + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + } } }, "object-assign": { @@ -4646,6 +5134,16 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true }, + "path-scurry": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.1.tgz", + "integrity": "sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==", + "dev": true, + "requires": { + "lru-cache": "^9.1.1 || ^10.0.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + } + }, "picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", @@ -4761,12 +5259,61 @@ "dev": true }, "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.5.tgz", + "integrity": "sha512-CqDakW+hMe/Bz202FPEymy68P+G50RfMQK+Qo5YUqc9SPipvbGjCGKd0RSKEelbsfQuw3g5NZDSrlZZAJurH1A==", "dev": true, "requires": { - "glob": "^7.1.3" + "glob": "^10.3.7" + }, + "dependencies": { + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "foreground-child": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", + "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + } + }, + "glob": { + "version": "10.3.10", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz", + "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==", + "dev": true, + "requires": { + "foreground-child": "^3.1.0", + "jackspeak": "^2.3.5", + "minimatch": "^9.0.1", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", + "path-scurry": "^1.10.1" + } + }, + "minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + }, + "signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true + } } }, "safe-buffer": { @@ -4833,9 +5380,9 @@ } }, "socket.io-adapter": { - "version": "2.5.3", - "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.3.tgz", - "integrity": "sha512-OtkQtynXUM0JSEwmI6YlEJ5hU9kpDUVjda0hx8QVffKhqum53xhynH8eTCyjHSfI8FiJnyfK8I3Dlc88Jr81Dg==", + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.4.tgz", + "integrity": "sha512-wDNHGXGewWAjQPt3pyeYBtpWSq9cLE5UW1ZUPL/2eGK9jtse/FpXib7epSTsz0Q0m+6sg6Y4KtcFTlah1bdOVg==", "requires": { "debug": "~4.3.4", "ws": "~8.11.0" @@ -4881,6 +5428,17 @@ "rimraf": "^3.0.0", "signal-exit": "^3.0.2", "which": "^2.0.1" + }, + "dependencies": { + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + } } }, "sprintf-js": { @@ -4906,13 +5464,33 @@ "strip-ansi": "^6.0.0" } }, + "string-width-cjs": { + "version": "npm:string-width@4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "requires": { - "ansi-regex": "^5.0.0" + "ansi-regex": "^5.0.1" + } + }, + "strip-ansi-cjs": { + "version": "npm:strip-ansi@6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" } }, "strip-bom": { @@ -5088,6 +5666,43 @@ } } }, + "wrap-ansi-cjs": { + "version": "npm:wrap-ansi@7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + } + } + }, "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", diff --git a/package.json b/package.json index 083fb62..76d3be9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@socket.io/redis-streams-adapter", - "version": "0.2.0", + "version": "0.2.1", "description": "The Socket.IO adapter based on Redis Streams, allowing to broadcast events between several Socket.IO servers", "license": "MIT", "repository": { @@ -13,7 +13,7 @@ "main": "./dist/index.js", "types": "./dist/index.d.ts", "scripts": { - "compile": "tsc", + "compile": "rimraf ./dist && tsc", "format:check": "prettier --parser typescript --check \"lib/**/*.ts\" \"test/**/*.ts\"", "format:fix": "prettier --parser typescript --write \"lib/**/*.ts\" \"test/**/*.ts\"", "prepack": "npm run compile", @@ -31,7 +31,7 @@ "debug": "~4.3.1" }, "peerDependencies": { - "socket.io-adapter": "^2.5.3" + "socket.io-adapter": "^2.5.4" }, "devDependencies": { "@types/expect.js": "^0.3.29", @@ -44,6 +44,7 @@ "nyc": "^15.1.0", "prettier": "^2.8.7", "redis": "^4.6.5", + "rimraf": "^5.0.5", "socket.io": "^4.6.1", "socket.io-client": "^4.6.1", "ts-node": "^10.9.1", diff --git a/test/connection-state-recovery.ts b/test/connection-state-recovery.ts index 45f5eff..12beab6 100644 --- a/test/connection-state-recovery.ts +++ b/test/connection-state-recovery.ts @@ -1,61 +1,28 @@ -import { createServer } from "http"; import { Server } from "socket.io"; -import expect = require("expect.js"); import { io as ioc } from "socket.io-client"; -import { createAdapter } from "../lib"; -import { AddressInfo } from "net"; -import { createClient } from "redis"; -import { sleep } from "./util"; - -const NODES_COUNT = 3; +import { setup, sleep } from "./util"; +import expect = require("expect.js"); describe("connection state recovery", () => { - let servers: Server[], ports: number[], redisClients: any[]; + let servers: Server[], ports: number[], cleanup; beforeEach(async () => { - servers = []; - ports = []; - redisClients = []; - - return new Promise(async (resolve) => { - for (let i = 1; i <= NODES_COUNT; i++) { - const redisClient = createClient(); - - await redisClient.connect(); - - const httpServer = createServer(); - const io = new Server(httpServer, { - adapter: createAdapter(redisClient), - connectionStateRecovery: { - maxDisconnectionDuration: 5000, - }, - }); - httpServer.listen(async () => { - const port = (httpServer.address() as AddressInfo).port; - - ports.push(port); - servers.push(io); - redisClients.push(redisClient); - if (servers.length === NODES_COUNT) { - await sleep(200); - - resolve(); - } - }); - } + const testContext = await setup({ + nodeCount: 3, + serverOptions: { + connectionStateRecovery: { + maxDisconnectionDuration: 5000, + }, + }, }); + servers = testContext.servers; + cleanup = testContext.cleanup; + ports = testContext.ports; }); afterEach(() => { - servers.forEach((server) => { - // @ts-ignore - server.httpServer.close(); - server.of("/").adapter.close(); - servers[0].of("/foo").adapter.close(); - }); - redisClients.forEach((redisClient) => { - redisClient.quit(); - }); + servers[0].of("/foo").adapter.close(); + cleanup(); }); it("should restore the session", (done) => { diff --git a/test/util.ts b/test/util.ts index 7b4dd73..4cc7da6 100644 --- a/test/util.ts +++ b/test/util.ts @@ -1,4 +1,4 @@ -import { Server } from "socket.io"; +import { Server, ServerOptions } from "socket.io"; import { Socket as ServerSocket } from "socket.io/dist/socket"; import { io as ioc, Socket as ClientSocket } from "socket.io-client"; import { createClient, createCluster } from "redis"; @@ -30,6 +30,7 @@ interface TestContext { serverSockets: ServerSocket[]; clientSockets: ClientSocket[]; cleanup: () => void; + ports: number[]; } const mode = process.env.REDIS_CLUSTER === "1" ? "cluster" : "standalone"; @@ -99,7 +100,6 @@ async function initRedisClient() { return new Redis(); } else { const redisClient = createClient(); - await redisClient.connect(); return redisClient; @@ -107,19 +107,28 @@ async function initRedisClient() { } } -export function setup() { +type SetupOptions = { + nodeCount?: number; + serverOptions?: Partial; +}; +export function setup({ + nodeCount = NODES_COUNT, + serverOptions = {}, +}: SetupOptions = {}) { const servers = []; const serverSockets = []; const clientSockets = []; const redisClients = []; + const ports = []; return new Promise(async (resolve) => { - for (let i = 1; i <= NODES_COUNT; i++) { + for (let i = 1; i <= nodeCount; i++) { const redisClient = await initRedisClient(); const httpServer = createServer(); const io = new Server(httpServer, { adapter: createAdapter(redisClient), + ...serverOptions, }); httpServer.listen(() => { const port = (httpServer.address() as AddressInfo).port; @@ -130,13 +139,15 @@ export function setup() { serverSockets.push(socket); servers.push(io); redisClients.push(redisClient); - if (servers.length === NODES_COUNT) { + ports.push(port); + if (servers.length === nodeCount) { await sleep(200); resolve({ servers, serverSockets, clientSockets, + ports, cleanup: () => { servers.forEach((server) => { // @ts-ignore