Skip to content
65 changes: 36 additions & 29 deletions src/cmap/handshake/client_metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,30 @@ import * as os from 'os';
import * as process from 'process';

import { BSON, type Document, Int32 } from '../../bson';
import { MongoInvalidArgumentError, MongoRuntimeError } from '../../error';
import type { MongoOptions } from '../../mongo_client';
import { MongoInvalidArgumentError } from '../../error';
import type { DriverInfo, MongoOptions } from '../../mongo_client';
import { fileIsAccessible } from '../../utils';

// eslint-disable-next-line @typescript-eslint/no-require-imports
const NODE_DRIVER_VERSION = require('../../../package.json').version;

/** @internal */
export function isDriverInfoEqual(info1: DriverInfo, info2: DriverInfo): boolean {
/** for equality comparison, we consider "" as unset */
const nonEmptyCmp = (s1: string | undefined, s2: string | undefined): boolean => {
s1 ||= undefined;
s2 ||= undefined;

return s1 === s2;
};

return (
nonEmptyCmp(info1.name, info2.name) &&
nonEmptyCmp(info1.platform, info2.platform) &&
nonEmptyCmp(info1.version, info2.version)
);
}

/**
* @public
* @deprecated This interface will be made internal in the next major release.
Expand Down Expand Up @@ -90,10 +107,7 @@ export class LimitedSizeDocument {
}
}

type MakeClientMetadataOptions = Pick<
MongoOptions,
'appName' | 'driverInfo' | 'additionalDriverInfo'
>;
type MakeClientMetadataOptions = Pick<MongoOptions, 'appName'>;
/**
* From the specs:
* Implementors SHOULD cumulatively update fields in the following order until the document is under the size limit:
Expand All @@ -102,34 +116,28 @@ type MakeClientMetadataOptions = Pick<
* 3. Omit the `env` document entirely.
* 4. Truncate `platform`. -- special we do not truncate this field
*/
export function makeClientMetadata(options: MakeClientMetadataOptions): ClientMetadata {
export function makeClientMetadata(
driverInfoList: DriverInfo[],
{ appName = '' }: MakeClientMetadataOptions
): ClientMetadata {
const metadataDocument = new LimitedSizeDocument(512);

const { appName = '' } = options;
// Add app name first, it must be sent
if (appName.length > 0) {
const name =
Buffer.byteLength(appName, 'utf8') <= 128
? options.appName
? appName
: Buffer.from(appName, 'utf8').subarray(0, 128).toString('utf8');
metadataDocument.ifItFitsItSits('application', { name });
}

const { name = '', version = '', platform = '' } = options.driverInfo;

const driverInfo = {
name: name.length > 0 ? `nodejs|${name}` : 'nodejs',
version: version.length > 0 ? `${NODE_DRIVER_VERSION}|${version}` : NODE_DRIVER_VERSION
name: 'nodejs',
version: NODE_DRIVER_VERSION
};

if (options.additionalDriverInfo == null) {
throw new MongoRuntimeError(
'Client options `additionalDriverInfo` must always default to an empty array'
);
}

// This is where we handle additional driver info added after client construction.
for (const { name: n = '', version: v = '' } of options.additionalDriverInfo) {
for (const { name: n = '', version: v = '' } of driverInfoList) {
if (n.length > 0) {
driverInfo.name = `${driverInfo.name}|${n}`;
}
Expand All @@ -145,13 +153,10 @@ export function makeClientMetadata(options: MakeClientMetadataOptions): ClientMe
}

let runtimeInfo = getRuntimeInfo();
if (platform.length > 0) {
runtimeInfo = `${runtimeInfo}|${platform}`;
}

for (const { platform: p = '' } of options.additionalDriverInfo) {
if (p.length > 0) {
runtimeInfo = `${runtimeInfo}|${p}`;
// This is where we handle additional driver info added after client construction.
for (const { platform = '' } of driverInfoList) {
if (platform.length > 0) {
runtimeInfo = `${runtimeInfo}|${platform}`;
}
}

Expand Down Expand Up @@ -210,7 +215,9 @@ async function getContainerMetadata() {
* Re-add each metadata value.
* Attempt to add new env container metadata, but keep old data if it does not fit.
*/
export async function addContainerMetadata(originalMetadata: ClientMetadata) {
export async function addContainerMetadata(
originalMetadata: ClientMetadata
): Promise<ClientMetadata> {
const containerMetadata = await getContainerMetadata();
if (Object.keys(containerMetadata).length === 0) return originalMetadata;

Expand All @@ -233,7 +240,7 @@ export async function addContainerMetadata(originalMetadata: ClientMetadata) {
extendedMetadata.ifItFitsItSits('env', extendedEnvMetadata);
}

return extendedMetadata.toObject();
return extendedMetadata.toObject() as ClientMetadata;
}

/**
Expand Down
11 changes: 0 additions & 11 deletions src/connection_string.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { URLSearchParams } from 'url';
import type { Document } from './bson';
import { MongoCredentials } from './cmap/auth/mongo_credentials';
import { AUTH_MECHS_AUTH_SRC_EXTERNAL, AuthMechanism } from './cmap/auth/providers';
import { addContainerMetadata, makeClientMetadata } from './cmap/handshake/client_metadata';
import { Compressor, type CompressorName } from './cmap/wire_protocol/compression';
import { Encrypter } from './encrypter';
import {
Expand Down Expand Up @@ -535,16 +534,6 @@ export function parseOptions(
}
);

// Set the default for the additional driver info.
mongoOptions.additionalDriverInfo = [];

mongoOptions.metadata = makeClientMetadata(mongoOptions);

mongoOptions.extendedMetadata = addContainerMetadata(mongoOptions.metadata).then(
undefined,
squashError
); // rejections will be handled later

return mongoOptions;
}

Expand Down
14 changes: 12 additions & 2 deletions src/mongo_client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import type { Connection } from './cmap/connection';
import {
addContainerMetadata,
type ClientMetadata,
isDriverInfoEqual,
makeClientMetadata
} from './cmap/handshake/client_metadata';
import type { CompressorName } from './cmap/wire_protocol/compression';
Expand Down Expand Up @@ -430,12 +431,16 @@ export class MongoClient extends TypedEventEmitter<MongoClientEvents> implements
| 'extendedMetadata'
>;

private driverInfoList: DriverInfo[] = [];

constructor(url: string, options?: MongoClientOptions) {
super();
this.on('error', noop);

this.options = parseOptions(url, this, options);

this.appendMetadata(this.options.driverInfo);

const shouldSetLogger = Object.values(this.options.mongoLoggerOptions.componentSeverities).some(
value => value !== SeverityLevel.OFF
);
Expand Down Expand Up @@ -492,8 +497,13 @@ export class MongoClient extends TypedEventEmitter<MongoClientEvents> implements
* @param driverInfo - Information about the application or library.
*/
appendMetadata(driverInfo: DriverInfo) {
this.options.additionalDriverInfo.push(driverInfo);
this.options.metadata = makeClientMetadata(this.options);
const isDuplicateDriverInfo = this.driverInfoList.some(info =>
isDriverInfoEqual(info, driverInfo)
);
if (isDuplicateDriverInfo) return;

this.driverInfoList.push(driverInfo);
this.options.metadata = makeClientMetadata(this.driverInfoList, this.options);
this.options.extendedMetadata = addContainerMetadata(this.options.metadata)
.then(undefined, squashError)
.then(result => result ?? {}); // ensure Promise<Document>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,8 @@ describe('Connection', function () {
...commonConnectOptions,
connectionType: Connection,
...this.configuration.options,
metadata: makeClientMetadata({ driverInfo: {}, additionalDriverInfo: [] }),
extendedMetadata: addContainerMetadata(
makeClientMetadata({ driverInfo: {}, additionalDriverInfo: [] })
)
metadata: makeClientMetadata([], {}),
extendedMetadata: addContainerMetadata(makeClientMetadata([], {}))
};

let conn;
Expand All @@ -74,10 +72,8 @@ describe('Connection', function () {
connectionType: Connection,
...this.configuration.options,
monitorCommands: true,
metadata: makeClientMetadata({ driverInfo: {}, additionalDriverInfo: [] }),
extendedMetadata: addContainerMetadata(
makeClientMetadata({ driverInfo: {}, additionalDriverInfo: [] })
)
metadata: makeClientMetadata([], {}),
extendedMetadata: addContainerMetadata(makeClientMetadata([], {}))
};

let conn;
Expand Down Expand Up @@ -108,10 +104,8 @@ describe('Connection', function () {
connectionType: Connection,
...this.configuration.options,
monitorCommands: true,
metadata: makeClientMetadata({ driverInfo: {}, additionalDriverInfo: [] }),
extendedMetadata: addContainerMetadata(
makeClientMetadata({ driverInfo: {}, additionalDriverInfo: [] })
)
metadata: makeClientMetadata([], {}),
extendedMetadata: addContainerMetadata(makeClientMetadata([], {}))
};

let conn;
Expand Down
Loading