React
This commit is contained in:
353
server/node_modules/mongodb/src/cmap/handshake/client_metadata.ts
generated
vendored
Normal file
353
server/node_modules/mongodb/src/cmap/handshake/client_metadata.ts
generated
vendored
Normal file
@@ -0,0 +1,353 @@
|
||||
import * as os from 'os';
|
||||
import * as process from 'process';
|
||||
|
||||
import { BSON, type Document, Int32, NumberUtils } from '../../bson';
|
||||
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)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @see https://github.com/mongodb/specifications/blob/master/source/mongodb-handshake/handshake.md#hello-command
|
||||
*/
|
||||
export interface ClientMetadata {
|
||||
driver: {
|
||||
name: string;
|
||||
version: string;
|
||||
};
|
||||
os: {
|
||||
type: string;
|
||||
name?: NodeJS.Platform;
|
||||
architecture?: string;
|
||||
version?: string;
|
||||
};
|
||||
platform: string;
|
||||
application?: {
|
||||
name: string;
|
||||
};
|
||||
/** FaaS environment information */
|
||||
env?: {
|
||||
name?: 'aws.lambda' | 'gcp.func' | 'azure.func' | 'vercel';
|
||||
timeout_sec?: Int32;
|
||||
memory_mb?: Int32;
|
||||
region?: string;
|
||||
container?: {
|
||||
runtime?: string;
|
||||
orchestrator?: string;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export class LimitedSizeDocument {
|
||||
private document = new Map();
|
||||
/** BSON overhead: Int32 + Null byte */
|
||||
private documentSize = 5;
|
||||
private maxSize: number;
|
||||
|
||||
constructor(maxSize: number) {
|
||||
this.maxSize = maxSize;
|
||||
}
|
||||
|
||||
/** Only adds key/value if the bsonByteLength is less than MAX_SIZE */
|
||||
public ifItFitsItSits(key: string, value: Record<string, any> | string): boolean {
|
||||
// The BSON byteLength of the new element is the same as serializing it to its own document
|
||||
// subtracting the document size int32 and the null terminator.
|
||||
const newElementSize = BSON.serialize(new Map().set(key, value)).byteLength - 5;
|
||||
|
||||
if (newElementSize + this.documentSize > this.maxSize) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.documentSize += newElementSize;
|
||||
|
||||
this.document.set(key, value);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
toObject(): Document {
|
||||
return BSON.deserialize(BSON.serialize(this.document), {
|
||||
promoteLongs: false,
|
||||
promoteBuffers: false,
|
||||
promoteValues: false,
|
||||
useBigInt64: false
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
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:
|
||||
* 1. Omit fields from `env` except `env.name`.
|
||||
* 2. Omit fields from `os` except `os.type`.
|
||||
* 3. Omit the `env` document entirely.
|
||||
* 4. Truncate `platform`. -- special we do not truncate this field
|
||||
*/
|
||||
export async function makeClientMetadata(
|
||||
driverInfoList: DriverInfo[],
|
||||
{ appName = '' }: MakeClientMetadataOptions
|
||||
): Promise<ClientMetadata> {
|
||||
const metadataDocument = new LimitedSizeDocument(512);
|
||||
|
||||
// Add app name first, it must be sent
|
||||
if (appName.length > 0) {
|
||||
const name =
|
||||
Buffer.byteLength(appName, 'utf8') <= 128
|
||||
? appName
|
||||
: Buffer.from(appName, 'utf8').subarray(0, 128).toString('utf8');
|
||||
metadataDocument.ifItFitsItSits('application', { name });
|
||||
}
|
||||
|
||||
const driverInfo = {
|
||||
name: 'nodejs',
|
||||
version: NODE_DRIVER_VERSION
|
||||
};
|
||||
|
||||
// This is where we handle additional driver info added after client construction.
|
||||
for (const { name: n = '', version: v = '' } of driverInfoList) {
|
||||
if (n.length > 0) {
|
||||
driverInfo.name = `${driverInfo.name}|${n}`;
|
||||
}
|
||||
if (v.length > 0) {
|
||||
driverInfo.version = `${driverInfo.version}|${v}`;
|
||||
}
|
||||
}
|
||||
|
||||
if (!metadataDocument.ifItFitsItSits('driver', driverInfo)) {
|
||||
throw new MongoInvalidArgumentError(
|
||||
'Unable to include driverInfo name and version, metadata cannot exceed 512 bytes'
|
||||
);
|
||||
}
|
||||
|
||||
let runtimeInfo = getRuntimeInfo();
|
||||
// This is where we handle additional driver info added after client construction.
|
||||
for (const { platform = '' } of driverInfoList) {
|
||||
if (platform.length > 0) {
|
||||
runtimeInfo = `${runtimeInfo}|${platform}`;
|
||||
}
|
||||
}
|
||||
|
||||
if (!metadataDocument.ifItFitsItSits('platform', runtimeInfo)) {
|
||||
throw new MongoInvalidArgumentError(
|
||||
'Unable to include driverInfo platform, metadata cannot exceed 512 bytes'
|
||||
);
|
||||
}
|
||||
|
||||
// Note: order matters, os.type is last so it will be removed last if we're at maxSize
|
||||
const osInfo = new Map()
|
||||
.set('name', os.platform())
|
||||
.set('architecture', os.arch())
|
||||
.set('version', os.release())
|
||||
.set('type', os.type());
|
||||
|
||||
if (!metadataDocument.ifItFitsItSits('os', osInfo)) {
|
||||
for (const key of osInfo.keys()) {
|
||||
osInfo.delete(key);
|
||||
if (osInfo.size === 0) break;
|
||||
if (metadataDocument.ifItFitsItSits('os', osInfo)) break;
|
||||
}
|
||||
}
|
||||
|
||||
const faasEnv = getFAASEnv();
|
||||
if (faasEnv != null) {
|
||||
if (!metadataDocument.ifItFitsItSits('env', faasEnv)) {
|
||||
for (const key of faasEnv.keys()) {
|
||||
faasEnv.delete(key);
|
||||
if (faasEnv.size === 0) break;
|
||||
if (metadataDocument.ifItFitsItSits('env', faasEnv)) break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return await addContainerMetadata(metadataDocument.toObject() as ClientMetadata);
|
||||
}
|
||||
|
||||
let dockerPromise: Promise<boolean>;
|
||||
type ContainerMetadata = NonNullable<NonNullable<ClientMetadata['env']>['container']>;
|
||||
/** @internal */
|
||||
async function getContainerMetadata(): Promise<ContainerMetadata> {
|
||||
dockerPromise ??= fileIsAccessible('/.dockerenv');
|
||||
const isDocker = await dockerPromise;
|
||||
|
||||
const { KUBERNETES_SERVICE_HOST = '' } = process.env;
|
||||
const isKubernetes = KUBERNETES_SERVICE_HOST.length > 0 ? true : false;
|
||||
|
||||
const containerMetadata: ContainerMetadata = {};
|
||||
|
||||
if (isDocker) containerMetadata.runtime = 'docker';
|
||||
if (isKubernetes) containerMetadata.orchestrator = 'kubernetes';
|
||||
|
||||
return containerMetadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* Re-add each metadata value.
|
||||
* Attempt to add new env container metadata, but keep old data if it does not fit.
|
||||
*/
|
||||
async function addContainerMetadata(originalMetadata: ClientMetadata): Promise<ClientMetadata> {
|
||||
const containerMetadata = await getContainerMetadata();
|
||||
if (Object.keys(containerMetadata).length === 0) return originalMetadata;
|
||||
|
||||
const extendedMetadata = new LimitedSizeDocument(512);
|
||||
|
||||
const extendedEnvMetadata: NonNullable<ClientMetadata['env']> = {
|
||||
...originalMetadata?.env,
|
||||
container: containerMetadata
|
||||
};
|
||||
|
||||
for (const [key, val] of Object.entries(originalMetadata)) {
|
||||
if (key !== 'env') {
|
||||
extendedMetadata.ifItFitsItSits(key, val);
|
||||
} else {
|
||||
if (!extendedMetadata.ifItFitsItSits('env', extendedEnvMetadata)) {
|
||||
// add in old data if newer / extended metadata does not fit
|
||||
extendedMetadata.ifItFitsItSits('env', val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!('env' in originalMetadata)) {
|
||||
extendedMetadata.ifItFitsItSits('env', extendedEnvMetadata);
|
||||
}
|
||||
|
||||
return extendedMetadata.toObject() as ClientMetadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* Collects FaaS metadata.
|
||||
* - `name` MUST be the last key in the Map returned.
|
||||
*/
|
||||
export function getFAASEnv(): Map<string, string | Int32> | null {
|
||||
const {
|
||||
AWS_EXECUTION_ENV = '',
|
||||
AWS_LAMBDA_RUNTIME_API = '',
|
||||
FUNCTIONS_WORKER_RUNTIME = '',
|
||||
K_SERVICE = '',
|
||||
FUNCTION_NAME = '',
|
||||
VERCEL = '',
|
||||
AWS_LAMBDA_FUNCTION_MEMORY_SIZE = '',
|
||||
AWS_REGION = '',
|
||||
FUNCTION_MEMORY_MB = '',
|
||||
FUNCTION_REGION = '',
|
||||
FUNCTION_TIMEOUT_SEC = '',
|
||||
VERCEL_REGION = ''
|
||||
} = process.env;
|
||||
|
||||
const isAWSFaaS =
|
||||
AWS_EXECUTION_ENV.startsWith('AWS_Lambda_') || AWS_LAMBDA_RUNTIME_API.length > 0;
|
||||
const isAzureFaaS = FUNCTIONS_WORKER_RUNTIME.length > 0;
|
||||
const isGCPFaaS = K_SERVICE.length > 0 || FUNCTION_NAME.length > 0;
|
||||
const isVercelFaaS = VERCEL.length > 0;
|
||||
|
||||
// Note: order matters, name must always be the last key
|
||||
const faasEnv = new Map();
|
||||
|
||||
// When isVercelFaaS is true so is isAWSFaaS; Vercel inherits the AWS env
|
||||
if (isVercelFaaS && !(isAzureFaaS || isGCPFaaS)) {
|
||||
if (VERCEL_REGION.length > 0) {
|
||||
faasEnv.set('region', VERCEL_REGION);
|
||||
}
|
||||
|
||||
faasEnv.set('name', 'vercel');
|
||||
return faasEnv;
|
||||
}
|
||||
|
||||
if (isAWSFaaS && !(isAzureFaaS || isGCPFaaS || isVercelFaaS)) {
|
||||
if (AWS_REGION.length > 0) {
|
||||
faasEnv.set('region', AWS_REGION);
|
||||
}
|
||||
|
||||
if (
|
||||
AWS_LAMBDA_FUNCTION_MEMORY_SIZE.length > 0 &&
|
||||
Number.isInteger(+AWS_LAMBDA_FUNCTION_MEMORY_SIZE)
|
||||
) {
|
||||
faasEnv.set('memory_mb', new Int32(AWS_LAMBDA_FUNCTION_MEMORY_SIZE));
|
||||
}
|
||||
|
||||
faasEnv.set('name', 'aws.lambda');
|
||||
return faasEnv;
|
||||
}
|
||||
|
||||
if (isAzureFaaS && !(isGCPFaaS || isAWSFaaS || isVercelFaaS)) {
|
||||
faasEnv.set('name', 'azure.func');
|
||||
return faasEnv;
|
||||
}
|
||||
|
||||
if (isGCPFaaS && !(isAzureFaaS || isAWSFaaS || isVercelFaaS)) {
|
||||
if (FUNCTION_REGION.length > 0) {
|
||||
faasEnv.set('region', FUNCTION_REGION);
|
||||
}
|
||||
|
||||
if (FUNCTION_MEMORY_MB.length > 0 && Number.isInteger(+FUNCTION_MEMORY_MB)) {
|
||||
faasEnv.set('memory_mb', new Int32(FUNCTION_MEMORY_MB));
|
||||
}
|
||||
|
||||
if (FUNCTION_TIMEOUT_SEC.length > 0 && Number.isInteger(+FUNCTION_TIMEOUT_SEC)) {
|
||||
faasEnv.set('timeout_sec', new Int32(FUNCTION_TIMEOUT_SEC));
|
||||
}
|
||||
|
||||
faasEnv.set('name', 'gcp.func');
|
||||
return faasEnv;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* This type represents the global Deno object and the minimal type contract we expect it to satisfy.
|
||||
*/
|
||||
declare const Deno: { version?: { deno?: string } } | undefined;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* This type represents the global Bun object and the minimal type contract we expect it to satisfy.
|
||||
*/
|
||||
declare const Bun: { (): void; version?: string } | undefined;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* Get current JavaScript runtime platform
|
||||
*
|
||||
* NOTE: The version information fetching is intentionally written defensively
|
||||
* to avoid having a released driver version that becomes incompatible
|
||||
* with a future change to these global objects.
|
||||
*/
|
||||
function getRuntimeInfo(): string {
|
||||
const endianness = NumberUtils.isBigEndian ? 'BE' : 'LE';
|
||||
if ('Deno' in globalThis) {
|
||||
const version = typeof Deno?.version?.deno === 'string' ? Deno?.version?.deno : '0.0.0-unknown';
|
||||
|
||||
return `Deno v${version}, ${endianness}`;
|
||||
}
|
||||
|
||||
if ('Bun' in globalThis) {
|
||||
const version = typeof Bun?.version === 'string' ? Bun?.version : '0.0.0-unknown';
|
||||
|
||||
return `Bun v${version}, ${endianness}`;
|
||||
}
|
||||
|
||||
return `Node.js ${process.version}, ${endianness}`;
|
||||
}
|
||||
Reference in New Issue
Block a user