Skip to content

Commit d70448f

Browse files
committed
fix(client): bring disableClientInfo option back
It disappeared in v5 fixes #2958
1 parent f01f101 commit d70448f

File tree

6 files changed

+197
-49
lines changed

6 files changed

+197
-49
lines changed

packages/client/lib/client/index.ts

Lines changed: 89 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@ import { BasicAuth, CredentialsError, CredentialsProvider, StreamingCredentialsP
44
import RedisCommandsQueue, { CommandOptions } from './commands-queue';
55
import { EventEmitter } from 'node:events';
66
import { attachConfig, functionArgumentsPrefix, getTransformReply, scriptArgumentsPrefix } from '../commander';
7-
import { ClientClosedError, ClientOfflineError, DisconnectsClientError, WatchError } from '../errors';
7+
import { ClientClosedError, ClientOfflineError, DisconnectsClientError, SimpleError, WatchError } from '../errors';
88
import { URL } from 'node:url';
99
import { TcpSocketConnectOpts } from 'node:net';
1010
import { PUBSUB_TYPE, PubSubType, PubSubListener, PubSubTypeListeners, ChannelListeners } from './pub-sub';
11-
import { Command, CommandSignature, TypeMapping, CommanderConfig, RedisFunction, RedisFunctions, RedisModules, RedisScript, RedisScripts, ReplyUnion, RespVersions, RedisArgument, ReplyWithTypeMapping, SimpleStringReply, TransformReply } from '../RESP/types';
11+
import { Command, CommandSignature, TypeMapping, CommanderConfig, RedisFunction, RedisFunctions, RedisModules, RedisScript, RedisScripts, ReplyUnion, RespVersions, RedisArgument, ReplyWithTypeMapping, SimpleStringReply, TransformReply, CommandArguments } from '../RESP/types';
1212
import RedisClientMultiCommand, { RedisClientMultiCommandType } from './multi-command';
1313
import { RedisMultiQueuedCommand } from '../multi-command';
1414
import HELLO, { HelloOptions } from '../commands/HELLO';
@@ -19,6 +19,7 @@ import { RedisVariadicArgument, parseArgs, pushVariadicArguments } from '../comm
1919
import { BasicClientSideCache, ClientSideCacheConfig, ClientSideCacheProvider } from './cache';
2020
import { BasicCommandParser, CommandParser } from './parser';
2121
import SingleEntryCache from '../single-entry-cache';
22+
import { version } from '../../package.json'
2223

2324
export interface RedisClientOptions<
2425
M extends RedisModules = RedisModules,
@@ -135,6 +136,14 @@ export interface RedisClientOptions<
135136
* ```
136137
*/
137138
clientSideCache?: ClientSideCacheProvider | ClientSideCacheConfig;
139+
/**
140+
* If set to true, disables sending client identifier (user-agent like message) to the redis server
141+
*/
142+
disableClientInfo?: boolean;
143+
/**
144+
* Tag to append to library name that is sent to the Redis server
145+
*/
146+
clientInfoTag?: string;
138147
}
139148

140149
type WithCommands<
@@ -514,7 +523,30 @@ export default class RedisClient<
514523
});
515524
}
516525

517-
async #handshake(selectedDB: number) {
526+
async #handshake(chainId: symbol, asap: boolean) {
527+
const promises = [];
528+
const commandsWithErrorHandlers = await this.#getHandshakeCommands(this.#selectedDB ?? 0);
529+
530+
if (asap) commandsWithErrorHandlers.reverse()
531+
532+
for (const { cmd, errorHandler } of commandsWithErrorHandlers) {
533+
promises.push(
534+
this.#queue
535+
.addCommand(cmd, {
536+
chainId,
537+
asap
538+
})
539+
.catch(errorHandler)
540+
);
541+
}
542+
return promises;
543+
}
544+
545+
async #getHandshakeCommands(
546+
selectedDB: number
547+
): Promise<
548+
Array<{ cmd: CommandArguments } & { errorHandler?: (err: Error) => void }>
549+
> {
518550
const commands = [];
519551
const cp = this.#options?.credentialsProvider;
520552

@@ -532,8 +564,8 @@ export default class RedisClient<
532564
}
533565

534566
if (cp && cp.type === 'streaming-credentials-provider') {
535-
536-
const [credentials, disposable] = await this.#subscribeForStreamingCredentials(cp)
567+
const [credentials, disposable] =
568+
await this.#subscribeForStreamingCredentials(cp);
537569
this.#credentialsSubscription = disposable;
538570

539571
if (credentials.password) {
@@ -548,59 +580,88 @@ export default class RedisClient<
548580
hello.SETNAME = this.#options.name;
549581
}
550582

551-
commands.push(
552-
parseArgs(HELLO, this.#options.RESP, hello)
553-
);
583+
commands.push({ cmd: parseArgs(HELLO, this.#options.RESP, hello) });
554584
} else {
555-
556585
if (cp && cp.type === 'async-credentials-provider') {
557-
558586
const credentials = await cp.credentials();
559587

560588
if (credentials.username || credentials.password) {
561-
commands.push(
562-
parseArgs(COMMANDS.AUTH, {
589+
commands.push({
590+
cmd: parseArgs(COMMANDS.AUTH, {
563591
username: credentials.username,
564592
password: credentials.password ?? ''
565593
})
566-
);
594+
});
567595
}
568596
}
569597

570598
if (cp && cp.type === 'streaming-credentials-provider') {
571-
572-
const [credentials, disposable] = await this.#subscribeForStreamingCredentials(cp)
599+
const [credentials, disposable] =
600+
await this.#subscribeForStreamingCredentials(cp);
573601
this.#credentialsSubscription = disposable;
574602

575603
if (credentials.username || credentials.password) {
576-
commands.push(
577-
parseArgs(COMMANDS.AUTH, {
604+
commands.push({
605+
cmd: parseArgs(COMMANDS.AUTH, {
578606
username: credentials.username,
579607
password: credentials.password ?? ''
580608
})
581-
);
609+
});
582610
}
583611
}
584612

585613
if (this.#options?.name) {
586-
commands.push(
587-
parseArgs(COMMANDS.CLIENT_SETNAME, this.#options.name)
588-
);
614+
commands.push({
615+
cmd: parseArgs(COMMANDS.CLIENT_SETNAME, this.#options.name)
616+
});
589617
}
590618
}
591619

592620
if (selectedDB !== 0) {
593-
commands.push(['SELECT', this.#selectedDB.toString()]);
621+
commands.push({ cmd: ['SELECT', this.#selectedDB.toString()] });
594622
}
595623

596624
if (this.#options?.readonly) {
597-
commands.push(
598-
parseArgs(COMMANDS.READONLY)
599-
);
625+
commands.push({ cmd: parseArgs(COMMANDS.READONLY) });
626+
}
627+
628+
if (!this.#options?.disableClientInfo) {
629+
commands.push({
630+
cmd: ['CLIENT', 'SETINFO', 'LIB-VER', version],
631+
errorHandler: (err: Error) => {
632+
// Only throw if not a SimpleError - unknown subcommand
633+
// Client libraries are expected to ignore failures
634+
// of type SimpleError - unknown subcommand, which are
635+
// expected from older servers ( < v7 )
636+
if (!(err instanceof SimpleError) || !err.isUnknownSubcommand()) {
637+
throw err;
638+
}
639+
}
640+
});
641+
642+
commands.push({
643+
cmd: [
644+
'CLIENT',
645+
'SETINFO',
646+
'LIB-NAME',
647+
this.#options?.clientInfoTag
648+
? `node-redis(${this.#options.clientInfoTag})`
649+
: 'node-redis'
650+
],
651+
errorHandler: (err: Error) => {
652+
// Only throw if not a SimpleError - unknown subcommand
653+
// Client libraries are expected to ignore failures
654+
// of type SimpleError - unknown subcommand, which are
655+
// expected from older servers ( < v7 )
656+
if (!(err instanceof SimpleError) || !err.isUnknownSubcommand()) {
657+
throw err;
658+
}
659+
}
660+
});
600661
}
601662

602663
if (this.#clientSideCache) {
603-
commands.push(this.#clientSideCache.trackingOn());
664+
commands.push({cmd: this.#clientSideCache.trackingOn()});
604665
}
605666

606667
return commands;
@@ -629,15 +690,7 @@ export default class RedisClient<
629690
);
630691
}
631692

632-
const commands = await this.#handshake(this.#selectedDB);
633-
for (let i = commands.length - 1; i >= 0; --i) {
634-
promises.push(
635-
this.#queue.addCommand(commands[i], {
636-
chainId,
637-
asap: true
638-
})
639-
);
640-
}
693+
promises.push(...(await this.#handshake(chainId, true)));
641694

642695
if (promises.length) {
643696
this.#write();
@@ -1221,13 +1274,7 @@ export default class RedisClient<
12211274
selectedDB = this._self.#options?.database ?? 0;
12221275
this._self.#credentialsSubscription?.dispose();
12231276
this._self.#credentialsSubscription = null;
1224-
for (const command of (await this._self.#handshake(selectedDB))) {
1225-
promises.push(
1226-
this._self.#queue.addCommand(command, {
1227-
chainId
1228-
})
1229-
);
1230-
}
1277+
promises.push(...(await this._self.#handshake(chainId, false)));
12311278
this._self.#scheduleWrite();
12321279
await Promise.all(promises);
12331280
this._self.#selectedDB = selectedDB;

packages/client/lib/commands/CLIENT_INFO.spec.ts

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { strict as assert } from 'node:assert';
22
import CLIENT_INFO from './CLIENT_INFO';
33
import testUtils, { GLOBAL } from '../test-utils';
44
import { parseArgs } from './generic-transformers';
5+
import { version } from '../../package.json';
56

67
describe('CLIENT INFO', () => {
78
testUtils.isVersionGreaterThanHook([6, 2]);
@@ -48,4 +49,89 @@ describe('CLIENT INFO', () => {
4849
}
4950
}
5051
}, GLOBAL.SERVERS.OPEN);
52+
53+
testUtils.testWithClient('client.clientInfo Redis < 7', async client => {
54+
const reply = await client.clientInfo();
55+
if (!testUtils.isVersionGreaterThan([7])) {
56+
assert.strictEqual(reply.libName, undefined, 'LibName should be undefined for Redis < 7');
57+
assert.strictEqual(reply.libVer, undefined, 'LibVer should be undefined for Redis < 7');
58+
}
59+
}, GLOBAL.SERVERS.OPEN);
60+
61+
testUtils.testWithClientIfVersionWithinRange([[7], 'LATEST'], 'client.clientInfo Redis>=7 info disabled', async client => {
62+
const reply = await client.clientInfo();
63+
assert.equal(reply.libName, '');
64+
assert.equal(reply.libVer, '');
65+
}, {
66+
...GLOBAL.SERVERS.OPEN,
67+
clientOptions: {
68+
disableClientInfo: true
69+
}
70+
});
71+
72+
testUtils.testWithClientIfVersionWithinRange([[7], 'LATEST'], 'client.clientInfo Redis>=7 resp unset, info enabled, tag set', async client => {
73+
const reply = await client.clientInfo();
74+
assert.equal(reply.libName, 'node-redis(client1)');
75+
assert.equal(reply.libVer, version);
76+
}, {
77+
...GLOBAL.SERVERS.OPEN,
78+
clientOptions: {
79+
clientInfoTag: 'client1'
80+
}
81+
});
82+
83+
testUtils.testWithClientIfVersionWithinRange([[7], 'LATEST'], 'client.clientInfo Redis>=7 resp unset, info enabled, tag unset', async client => {
84+
const reply = await client.clientInfo();
85+
assert.equal(reply.libName, 'node-redis');
86+
assert.equal(reply.libVer, version);
87+
}, GLOBAL.SERVERS.OPEN);
88+
89+
testUtils.testWithClientIfVersionWithinRange([[7], 'LATEST'], 'client.clientInfo Redis>=7 resp2 info enabled', async client => {
90+
const reply = await client.clientInfo();
91+
assert.equal(reply.libName, 'node-redis(client1)');
92+
assert.equal(reply.libVer, version);
93+
}, {
94+
...GLOBAL.SERVERS.OPEN,
95+
clientOptions: {
96+
RESP: 2,
97+
clientInfoTag: 'client1'
98+
}
99+
});
100+
101+
testUtils.testWithClientIfVersionWithinRange([[7], 'LATEST'], 'client.clientInfo Redis>=7 resp2 info disabled', async client => {
102+
const reply = await client.clientInfo();
103+
assert.equal(reply.libName, '');
104+
assert.equal(reply.libVer, '');
105+
}, {
106+
...GLOBAL.SERVERS.OPEN,
107+
clientOptions: {
108+
disableClientInfo: true,
109+
RESP: 2
110+
}
111+
});
112+
113+
testUtils.testWithClientIfVersionWithinRange([[7], 'LATEST'], 'client.clientInfo Redis>=7 resp3 info enabled', async client => {
114+
const reply = await client.clientInfo();
115+
assert.equal(reply.libName, 'node-redis(client1)');
116+
assert.equal(reply.libVer, version);
117+
}, {
118+
...GLOBAL.SERVERS.OPEN,
119+
clientOptions: {
120+
RESP: 3,
121+
clientInfoTag: 'client1'
122+
}
123+
});
124+
125+
testUtils.testWithClientIfVersionWithinRange([[7], 'LATEST'], 'client.clientInfo Redis>=7 resp3 info disabled', async client => {
126+
const reply = await client.clientInfo();
127+
assert.equal(reply.libName, '');
128+
assert.equal(reply.libVer, '');
129+
}, {
130+
...GLOBAL.SERVERS.OPEN,
131+
clientOptions: {
132+
disableClientInfo: true,
133+
RESP: 3
134+
}
135+
});
136+
51137
});

packages/client/lib/commands/CLIENT_INFO.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,14 @@ export interface ClientInfoReply {
5252
* available since 7.0
5353
*/
5454
resp?: number;
55+
/**
56+
* available since 7.0
57+
*/
58+
libName?: string;
59+
/**
60+
* available since 7.0
61+
*/
62+
libVer?: string;
5563
}
5664

5765
const CLIENT_INFO_REGEX = /([^\s=]+)=([^\s]*)/g;
@@ -67,7 +75,6 @@ export default {
6775
for (const item of rawReply.toString().matchAll(CLIENT_INFO_REGEX)) {
6876
map[item[1]] = item[2];
6977
}
70-
7178
const reply: ClientInfoReply = {
7279
id: Number(map.id),
7380
addr: map.addr,
@@ -89,7 +96,9 @@ export default {
8996
totMem: Number(map['tot-mem']),
9097
events: map.events,
9198
cmd: map.cmd,
92-
user: map.user
99+
user: map.user,
100+
libName: map['lib-name'],
101+
libVer: map['lib-ver']
93102
};
94103

95104
if (map.laddr !== undefined) {

packages/client/lib/errors.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,11 @@ export class ErrorReply extends Error {
6464
}
6565
}
6666

67-
export class SimpleError extends ErrorReply {}
67+
export class SimpleError extends ErrorReply {
68+
isUnknownSubcommand(): boolean {
69+
return this.message.toLowerCase().indexOf('err unknown subcommand') !== -1;
70+
}
71+
}
6872

6973
export class BlobError extends ErrorReply {}
7074

packages/client/tsconfig.json

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
{
22
"extends": "../../tsconfig.base.json",
33
"compilerOptions": {
4-
"outDir": "./dist"
4+
"outDir": "./dist",
55
},
66
"include": [
77
"./index.ts",
8-
"./lib/**/*.ts"
8+
"./lib/**/*.ts",
9+
"./package.json"
910
],
1011
"exclude": [
1112
"./lib/test-utils.ts",
@@ -18,6 +19,6 @@
1819
"./lib"
1920
],
2021
"entryPointStrategy": "expand",
21-
"out": "../../documentation/client"
22+
"out": "../../documentation/client",
2223
}
2324
}

tsconfig.base.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
"sourceMap": true,
1616
"declaration": true,
1717
"declarationMap": true,
18-
"allowJs": true
18+
"allowJs": true,
19+
"resolveJsonModule": true
1920
}
2021
}

0 commit comments

Comments
 (0)