Skip to content

Commit 4a5f879

Browse files
authored
fix(client): bring disableClientInfo option back (#2959)
* fix(client): bring disableClientInfo option back It disappeared in v5 fixes #2958
1 parent f3d1d33 commit 4a5f879

File tree

6 files changed

+196
-50
lines changed

6 files changed

+196
-50
lines changed

packages/client/lib/client/index.ts

Lines changed: 88 additions & 43 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,28 @@ 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();
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(): Promise<
546+
Array<{ cmd: CommandArguments } & { errorHandler?: (err: Error) => void }>
547+
> {
518548
const commands = [];
519549
const cp = this.#options?.credentialsProvider;
520550

@@ -532,8 +562,8 @@ export default class RedisClient<
532562
}
533563

534564
if (cp && cp.type === 'streaming-credentials-provider') {
535-
536-
const [credentials, disposable] = await this.#subscribeForStreamingCredentials(cp)
565+
const [credentials, disposable] =
566+
await this.#subscribeForStreamingCredentials(cp);
537567
this.#credentialsSubscription = disposable;
538568

539569
if (credentials.password) {
@@ -548,59 +578,88 @@ export default class RedisClient<
548578
hello.SETNAME = this.#options.name;
549579
}
550580

551-
commands.push(
552-
parseArgs(HELLO, this.#options.RESP, hello)
553-
);
581+
commands.push({ cmd: parseArgs(HELLO, this.#options.RESP, hello) });
554582
} else {
555-
556583
if (cp && cp.type === 'async-credentials-provider') {
557-
558584
const credentials = await cp.credentials();
559585

560586
if (credentials.username || credentials.password) {
561-
commands.push(
562-
parseArgs(COMMANDS.AUTH, {
587+
commands.push({
588+
cmd: parseArgs(COMMANDS.AUTH, {
563589
username: credentials.username,
564590
password: credentials.password ?? ''
565591
})
566-
);
592+
});
567593
}
568594
}
569595

570596
if (cp && cp.type === 'streaming-credentials-provider') {
571-
572-
const [credentials, disposable] = await this.#subscribeForStreamingCredentials(cp)
597+
const [credentials, disposable] =
598+
await this.#subscribeForStreamingCredentials(cp);
573599
this.#credentialsSubscription = disposable;
574600

575601
if (credentials.username || credentials.password) {
576-
commands.push(
577-
parseArgs(COMMANDS.AUTH, {
602+
commands.push({
603+
cmd: parseArgs(COMMANDS.AUTH, {
578604
username: credentials.username,
579605
password: credentials.password ?? ''
580606
})
581-
);
607+
});
582608
}
583609
}
584610

585611
if (this.#options?.name) {
586-
commands.push(
587-
parseArgs(COMMANDS.CLIENT_SETNAME, this.#options.name)
588-
);
612+
commands.push({
613+
cmd: parseArgs(COMMANDS.CLIENT_SETNAME, this.#options.name)
614+
});
589615
}
590616
}
591617

592-
if (selectedDB !== 0) {
593-
commands.push(['SELECT', this.#selectedDB.toString()]);
618+
if (this.#selectedDB !== 0) {
619+
commands.push({ cmd: ['SELECT', this.#selectedDB.toString()] });
594620
}
595621

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

602661
if (this.#clientSideCache) {
603-
commands.push(this.#clientSideCache.trackingOn());
662+
commands.push({cmd: this.#clientSideCache.trackingOn()});
604663
}
605664

606665
return commands;
@@ -629,15 +688,7 @@ export default class RedisClient<
629688
);
630689
}
631690

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-
}
691+
promises.push(...(await this.#handshake(chainId, true)));
641692

642693
if (promises.length) {
643694
this.#write();
@@ -1221,13 +1272,7 @@ export default class RedisClient<
12211272
selectedDB = this._self.#options?.database ?? 0;
12221273
this._self.#credentialsSubscription?.dispose();
12231274
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-
}
1275+
promises.push(...(await this._self.#handshake(chainId, false)));
12311276
this._self.#scheduleWrite();
12321277
await Promise.all(promises);
12331278
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
@@ -70,7 +70,11 @@ export class ErrorReply extends Error {
7070
}
7171
}
7272

73-
export class SimpleError extends ErrorReply {}
73+
export class SimpleError extends ErrorReply {
74+
isUnknownSubcommand(): boolean {
75+
return this.message.toLowerCase().indexOf('err unknown subcommand') !== -1;
76+
}
77+
}
7478

7579
export class BlobError extends ErrorReply {}
7680

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)