client_metadata.js 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. exports.getFAASEnv = exports.makeClientMetadata = exports.LimitedSizeDocument = void 0;
  4. const os = require("os");
  5. const process = require("process");
  6. const bson_1 = require("../../bson");
  7. const error_1 = require("../../error");
  8. // eslint-disable-next-line @typescript-eslint/no-var-requires
  9. const NODE_DRIVER_VERSION = require('../../../package.json').version;
  10. /** @internal */
  11. class LimitedSizeDocument {
  12. constructor(maxSize) {
  13. this.maxSize = maxSize;
  14. this.document = new Map();
  15. /** BSON overhead: Int32 + Null byte */
  16. this.documentSize = 5;
  17. }
  18. /** Only adds key/value if the bsonByteLength is less than MAX_SIZE */
  19. ifItFitsItSits(key, value) {
  20. // The BSON byteLength of the new element is the same as serializing it to its own document
  21. // subtracting the document size int32 and the null terminator.
  22. const newElementSize = bson_1.BSON.serialize(new Map().set(key, value)).byteLength - 5;
  23. if (newElementSize + this.documentSize > this.maxSize) {
  24. return false;
  25. }
  26. this.documentSize += newElementSize;
  27. this.document.set(key, value);
  28. return true;
  29. }
  30. toObject() {
  31. return bson_1.BSON.deserialize(bson_1.BSON.serialize(this.document), {
  32. promoteLongs: false,
  33. promoteBuffers: false,
  34. promoteValues: false,
  35. useBigInt64: false
  36. });
  37. }
  38. }
  39. exports.LimitedSizeDocument = LimitedSizeDocument;
  40. /**
  41. * From the specs:
  42. * Implementors SHOULD cumulatively update fields in the following order until the document is under the size limit:
  43. * 1. Omit fields from `env` except `env.name`.
  44. * 2. Omit fields from `os` except `os.type`.
  45. * 3. Omit the `env` document entirely.
  46. * 4. Truncate `platform`. -- special we do not truncate this field
  47. */
  48. function makeClientMetadata(options) {
  49. const metadataDocument = new LimitedSizeDocument(512);
  50. const { appName = '' } = options;
  51. // Add app name first, it must be sent
  52. if (appName.length > 0) {
  53. const name = Buffer.byteLength(appName, 'utf8') <= 128
  54. ? options.appName
  55. : Buffer.from(appName, 'utf8').subarray(0, 128).toString('utf8');
  56. metadataDocument.ifItFitsItSits('application', { name });
  57. }
  58. const { name = '', version = '', platform = '' } = options.driverInfo;
  59. const driverInfo = {
  60. name: name.length > 0 ? `nodejs|${name}` : 'nodejs',
  61. version: version.length > 0 ? `${NODE_DRIVER_VERSION}|${version}` : NODE_DRIVER_VERSION
  62. };
  63. if (!metadataDocument.ifItFitsItSits('driver', driverInfo)) {
  64. throw new error_1.MongoInvalidArgumentError('Unable to include driverInfo name and version, metadata cannot exceed 512 bytes');
  65. }
  66. let runtimeInfo = getRuntimeInfo();
  67. if (platform.length > 0) {
  68. runtimeInfo = `${runtimeInfo}|${platform}`;
  69. }
  70. if (!metadataDocument.ifItFitsItSits('platform', runtimeInfo)) {
  71. throw new error_1.MongoInvalidArgumentError('Unable to include driverInfo platform, metadata cannot exceed 512 bytes');
  72. }
  73. // Note: order matters, os.type is last so it will be removed last if we're at maxSize
  74. const osInfo = new Map()
  75. .set('name', process.platform)
  76. .set('architecture', process.arch)
  77. .set('version', os.release())
  78. .set('type', os.type());
  79. if (!metadataDocument.ifItFitsItSits('os', osInfo)) {
  80. for (const key of osInfo.keys()) {
  81. osInfo.delete(key);
  82. if (osInfo.size === 0)
  83. break;
  84. if (metadataDocument.ifItFitsItSits('os', osInfo))
  85. break;
  86. }
  87. }
  88. const faasEnv = getFAASEnv();
  89. if (faasEnv != null) {
  90. if (!metadataDocument.ifItFitsItSits('env', faasEnv)) {
  91. for (const key of faasEnv.keys()) {
  92. faasEnv.delete(key);
  93. if (faasEnv.size === 0)
  94. break;
  95. if (metadataDocument.ifItFitsItSits('env', faasEnv))
  96. break;
  97. }
  98. }
  99. }
  100. return metadataDocument.toObject();
  101. }
  102. exports.makeClientMetadata = makeClientMetadata;
  103. /**
  104. * Collects FaaS metadata.
  105. * - `name` MUST be the last key in the Map returned.
  106. */
  107. function getFAASEnv() {
  108. 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;
  109. const isAWSFaaS = AWS_EXECUTION_ENV.startsWith('AWS_Lambda_') || AWS_LAMBDA_RUNTIME_API.length > 0;
  110. const isAzureFaaS = FUNCTIONS_WORKER_RUNTIME.length > 0;
  111. const isGCPFaaS = K_SERVICE.length > 0 || FUNCTION_NAME.length > 0;
  112. const isVercelFaaS = VERCEL.length > 0;
  113. // Note: order matters, name must always be the last key
  114. const faasEnv = new Map();
  115. // When isVercelFaaS is true so is isAWSFaaS; Vercel inherits the AWS env
  116. if (isVercelFaaS && !(isAzureFaaS || isGCPFaaS)) {
  117. if (VERCEL_REGION.length > 0) {
  118. faasEnv.set('region', VERCEL_REGION);
  119. }
  120. faasEnv.set('name', 'vercel');
  121. return faasEnv;
  122. }
  123. if (isAWSFaaS && !(isAzureFaaS || isGCPFaaS || isVercelFaaS)) {
  124. if (AWS_REGION.length > 0) {
  125. faasEnv.set('region', AWS_REGION);
  126. }
  127. if (AWS_LAMBDA_FUNCTION_MEMORY_SIZE.length > 0 &&
  128. Number.isInteger(+AWS_LAMBDA_FUNCTION_MEMORY_SIZE)) {
  129. faasEnv.set('memory_mb', new bson_1.Int32(AWS_LAMBDA_FUNCTION_MEMORY_SIZE));
  130. }
  131. faasEnv.set('name', 'aws.lambda');
  132. return faasEnv;
  133. }
  134. if (isAzureFaaS && !(isGCPFaaS || isAWSFaaS || isVercelFaaS)) {
  135. faasEnv.set('name', 'azure.func');
  136. return faasEnv;
  137. }
  138. if (isGCPFaaS && !(isAzureFaaS || isAWSFaaS || isVercelFaaS)) {
  139. if (FUNCTION_REGION.length > 0) {
  140. faasEnv.set('region', FUNCTION_REGION);
  141. }
  142. if (FUNCTION_MEMORY_MB.length > 0 && Number.isInteger(+FUNCTION_MEMORY_MB)) {
  143. faasEnv.set('memory_mb', new bson_1.Int32(FUNCTION_MEMORY_MB));
  144. }
  145. if (FUNCTION_TIMEOUT_SEC.length > 0 && Number.isInteger(+FUNCTION_TIMEOUT_SEC)) {
  146. faasEnv.set('timeout_sec', new bson_1.Int32(FUNCTION_TIMEOUT_SEC));
  147. }
  148. faasEnv.set('name', 'gcp.func');
  149. return faasEnv;
  150. }
  151. return null;
  152. }
  153. exports.getFAASEnv = getFAASEnv;
  154. /**
  155. * @internal
  156. * Get current JavaScript runtime platform
  157. *
  158. * NOTE: The version information fetching is intentionally written defensively
  159. * to avoid having a released driver version that becomes incompatible
  160. * with a future change to these global objects.
  161. */
  162. function getRuntimeInfo() {
  163. if ('Deno' in globalThis) {
  164. const version = typeof Deno?.version?.deno === 'string' ? Deno?.version?.deno : '0.0.0-unknown';
  165. return `Deno v${version}, ${os.endianness()}`;
  166. }
  167. if ('Bun' in globalThis) {
  168. const version = typeof Bun?.version === 'string' ? Bun?.version : '0.0.0-unknown';
  169. return `Bun v${version}, ${os.endianness()}`;
  170. }
  171. return `Node.js ${process.version}, ${os.endianness()}`;
  172. }
  173. //# sourceMappingURL=client_metadata.js.map