You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					296 lines
				
				6.9 KiB
			
		
		
			
		
	
	
					296 lines
				
				6.9 KiB
			| 
								 
											3 years ago
										 
									 | 
							
								'use strict';
							 | 
						||
| 
								 | 
							
								const os = require('os');
							 | 
						||
| 
								 | 
							
								const crypto = require('crypto');
							 | 
						||
| 
								 | 
							
								const requireOptional = require('optional-require')(require);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								/**
							 | 
						||
| 
								 | 
							
								 * Generate a UUIDv4
							 | 
						||
| 
								 | 
							
								 */
							 | 
						||
| 
								 | 
							
								const uuidV4 = () => {
							 | 
						||
| 
								 | 
							
								  const result = crypto.randomBytes(16);
							 | 
						||
| 
								 | 
							
								  result[6] = (result[6] & 0x0f) | 0x40;
							 | 
						||
| 
								 | 
							
								  result[8] = (result[8] & 0x3f) | 0x80;
							 | 
						||
| 
								 | 
							
								  return result;
							 | 
						||
| 
								 | 
							
								};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								/**
							 | 
						||
| 
								 | 
							
								 * Relays events for a given listener and emitter
							 | 
						||
| 
								 | 
							
								 *
							 | 
						||
| 
								 | 
							
								 * @param {EventEmitter} listener the EventEmitter to listen to the events from
							 | 
						||
| 
								 | 
							
								 * @param {EventEmitter} emitter the EventEmitter to relay the events to
							 | 
						||
| 
								 | 
							
								 */
							 | 
						||
| 
								 | 
							
								function relayEvents(listener, emitter, events) {
							 | 
						||
| 
								 | 
							
								  events.forEach(eventName => listener.on(eventName, event => emitter.emit(eventName, event)));
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function retrieveKerberos() {
							 | 
						||
| 
								 | 
							
								  let kerberos;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  try {
							 | 
						||
| 
								 | 
							
								    // Ensure you always wrap an optional require in the try block NODE-3199
							 | 
						||
| 
								 | 
							
								    kerberos = requireOptional('kerberos');
							 | 
						||
| 
								 | 
							
								  } catch (err) {
							 | 
						||
| 
								 | 
							
								    if (err.code === 'MODULE_NOT_FOUND') {
							 | 
						||
| 
								 | 
							
								      throw new Error('The `kerberos` module was not found. Please install it and try again.');
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    throw err;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  return kerberos;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// Throw an error if an attempt to use EJSON is made when it is not installed
							 | 
						||
| 
								 | 
							
								const noEJSONError = function() {
							 | 
						||
| 
								 | 
							
								  throw new Error('The `mongodb-extjson` module was not found. Please install it and try again.');
							 | 
						||
| 
								 | 
							
								};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// Facilitate loading EJSON optionally
							 | 
						||
| 
								 | 
							
								function retrieveEJSON() {
							 | 
						||
| 
								 | 
							
								  let EJSON = requireOptional('mongodb-extjson');
							 | 
						||
| 
								 | 
							
								  if (!EJSON) {
							 | 
						||
| 
								 | 
							
								    EJSON = {
							 | 
						||
| 
								 | 
							
								      parse: noEJSONError,
							 | 
						||
| 
								 | 
							
								      deserialize: noEJSONError,
							 | 
						||
| 
								 | 
							
								      serialize: noEJSONError,
							 | 
						||
| 
								 | 
							
								      stringify: noEJSONError,
							 | 
						||
| 
								 | 
							
								      setBSONModule: noEJSONError,
							 | 
						||
| 
								 | 
							
								      BSON: noEJSONError
							 | 
						||
| 
								 | 
							
								    };
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  return EJSON;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								/**
							 | 
						||
| 
								 | 
							
								 * A helper function for determining `maxWireVersion` between legacy and new topology
							 | 
						||
| 
								 | 
							
								 * instances
							 | 
						||
| 
								 | 
							
								 *
							 | 
						||
| 
								 | 
							
								 * @private
							 | 
						||
| 
								 | 
							
								 * @param {(Topology|Server)} topologyOrServer
							 | 
						||
| 
								 | 
							
								 */
							 | 
						||
| 
								 | 
							
								function maxWireVersion(topologyOrServer) {
							 | 
						||
| 
								 | 
							
								  if (topologyOrServer) {
							 | 
						||
| 
								 | 
							
								    if (topologyOrServer.ismaster) {
							 | 
						||
| 
								 | 
							
								      return topologyOrServer.ismaster.maxWireVersion;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (typeof topologyOrServer.lastIsMaster === 'function') {
							 | 
						||
| 
								 | 
							
								      const lastIsMaster = topologyOrServer.lastIsMaster();
							 | 
						||
| 
								 | 
							
								      if (lastIsMaster) {
							 | 
						||
| 
								 | 
							
								        return lastIsMaster.maxWireVersion;
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (topologyOrServer.description) {
							 | 
						||
| 
								 | 
							
								      return topologyOrServer.description.maxWireVersion;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  return 0;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								/*
							 | 
						||
| 
								 | 
							
								 * Checks that collation is supported by server.
							 | 
						||
| 
								 | 
							
								 *
							 | 
						||
| 
								 | 
							
								 * @param {Server} [server] to check against
							 | 
						||
| 
								 | 
							
								 * @param {object} [cmd] object where collation may be specified
							 | 
						||
| 
								 | 
							
								 * @param {function} [callback] callback function
							 | 
						||
| 
								 | 
							
								 * @return true if server does not support collation
							 | 
						||
| 
								 | 
							
								 */
							 | 
						||
| 
								 | 
							
								function collationNotSupported(server, cmd) {
							 | 
						||
| 
								 | 
							
								  return cmd && cmd.collation && maxWireVersion(server) < 5;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								/**
							 | 
						||
| 
								 | 
							
								 * Checks if a given value is a Promise
							 | 
						||
| 
								 | 
							
								 *
							 | 
						||
| 
								 | 
							
								 * @param {*} maybePromise
							 | 
						||
| 
								 | 
							
								 * @return true if the provided value is a Promise
							 | 
						||
| 
								 | 
							
								 */
							 | 
						||
| 
								 | 
							
								function isPromiseLike(maybePromise) {
							 | 
						||
| 
								 | 
							
								  return maybePromise && typeof maybePromise.then === 'function';
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								/**
							 | 
						||
| 
								 | 
							
								 * Applies the function `eachFn` to each item in `arr`, in parallel.
							 | 
						||
| 
								 | 
							
								 *
							 | 
						||
| 
								 | 
							
								 * @param {array} arr an array of items to asynchronusly iterate over
							 | 
						||
| 
								 | 
							
								 * @param {function} eachFn A function to call on each item of the array. The callback signature is `(item, callback)`, where the callback indicates iteration is complete.
							 | 
						||
| 
								 | 
							
								 * @param {function} callback The callback called after every item has been iterated
							 | 
						||
| 
								 | 
							
								 */
							 | 
						||
| 
								 | 
							
								function eachAsync(arr, eachFn, callback) {
							 | 
						||
| 
								 | 
							
								  arr = arr || [];
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  let idx = 0;
							 | 
						||
| 
								 | 
							
								  let awaiting = 0;
							 | 
						||
| 
								 | 
							
								  for (idx = 0; idx < arr.length; ++idx) {
							 | 
						||
| 
								 | 
							
								    awaiting++;
							 | 
						||
| 
								 | 
							
								    eachFn(arr[idx], eachCallback);
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  if (awaiting === 0) {
							 | 
						||
| 
								 | 
							
								    callback();
							 | 
						||
| 
								 | 
							
								    return;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  function eachCallback(err) {
							 | 
						||
| 
								 | 
							
								    awaiting--;
							 | 
						||
| 
								 | 
							
								    if (err) {
							 | 
						||
| 
								 | 
							
								      callback(err);
							 | 
						||
| 
								 | 
							
								      return;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (idx === arr.length && awaiting <= 0) {
							 | 
						||
| 
								 | 
							
								      callback();
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function eachAsyncSeries(arr, eachFn, callback) {
							 | 
						||
| 
								 | 
							
								  arr = arr || [];
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  let idx = 0;
							 | 
						||
| 
								 | 
							
								  let awaiting = arr.length;
							 | 
						||
| 
								 | 
							
								  if (awaiting === 0) {
							 | 
						||
| 
								 | 
							
								    callback();
							 | 
						||
| 
								 | 
							
								    return;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  function eachCallback(err) {
							 | 
						||
| 
								 | 
							
								    idx++;
							 | 
						||
| 
								 | 
							
								    awaiting--;
							 | 
						||
| 
								 | 
							
								    if (err) {
							 | 
						||
| 
								 | 
							
								      callback(err);
							 | 
						||
| 
								 | 
							
								      return;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (idx === arr.length && awaiting <= 0) {
							 | 
						||
| 
								 | 
							
								      callback();
							 | 
						||
| 
								 | 
							
								      return;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    eachFn(arr[idx], eachCallback);
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  eachFn(arr[idx], eachCallback);
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function isUnifiedTopology(topology) {
							 | 
						||
| 
								 | 
							
								  return topology.description != null;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function arrayStrictEqual(arr, arr2) {
							 | 
						||
| 
								 | 
							
								  if (!Array.isArray(arr) || !Array.isArray(arr2)) {
							 | 
						||
| 
								 | 
							
								    return false;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  return arr.length === arr2.length && arr.every((elt, idx) => elt === arr2[idx]);
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function tagsStrictEqual(tags, tags2) {
							 | 
						||
| 
								 | 
							
								  const tagsKeys = Object.keys(tags);
							 | 
						||
| 
								 | 
							
								  const tags2Keys = Object.keys(tags2);
							 | 
						||
| 
								 | 
							
								  return tagsKeys.length === tags2Keys.length && tagsKeys.every(key => tags2[key] === tags[key]);
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function errorStrictEqual(lhs, rhs) {
							 | 
						||
| 
								 | 
							
								  if (lhs === rhs) {
							 | 
						||
| 
								 | 
							
								    return true;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  if ((lhs == null && rhs != null) || (lhs != null && rhs == null)) {
							 | 
						||
| 
								 | 
							
								    return false;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  if (lhs.constructor.name !== rhs.constructor.name) {
							 | 
						||
| 
								 | 
							
								    return false;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  if (lhs.message !== rhs.message) {
							 | 
						||
| 
								 | 
							
								    return false;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  return true;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function makeStateMachine(stateTable) {
							 | 
						||
| 
								 | 
							
								  return function stateTransition(target, newState) {
							 | 
						||
| 
								 | 
							
								    const legalStates = stateTable[target.s.state];
							 | 
						||
| 
								 | 
							
								    if (legalStates && legalStates.indexOf(newState) < 0) {
							 | 
						||
| 
								 | 
							
								      throw new TypeError(
							 | 
						||
| 
								 | 
							
								        `illegal state transition from [${target.s.state}] => [${newState}], allowed: [${legalStates}]`
							 | 
						||
| 
								 | 
							
								      );
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    target.emit('stateChanged', target.s.state, newState);
							 | 
						||
| 
								 | 
							
								    target.s.state = newState;
							 | 
						||
| 
								 | 
							
								  };
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function makeClientMetadata(options) {
							 | 
						||
| 
								 | 
							
								  options = options || {};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  const metadata = {
							 | 
						||
| 
								 | 
							
								    driver: {
							 | 
						||
| 
								 | 
							
								      name: 'nodejs',
							 | 
						||
| 
								 | 
							
								      version: require('../../package.json').version
							 | 
						||
| 
								 | 
							
								    },
							 | 
						||
| 
								 | 
							
								    os: {
							 | 
						||
| 
								 | 
							
								      type: os.type(),
							 | 
						||
| 
								 | 
							
								      name: process.platform,
							 | 
						||
| 
								 | 
							
								      architecture: process.arch,
							 | 
						||
| 
								 | 
							
								      version: os.release()
							 | 
						||
| 
								 | 
							
								    },
							 | 
						||
| 
								 | 
							
								    platform: `'Node.js ${process.version}, ${os.endianness} (${
							 | 
						||
| 
								 | 
							
								      options.useUnifiedTopology ? 'unified' : 'legacy'
							 | 
						||
| 
								 | 
							
								    })`
							 | 
						||
| 
								 | 
							
								  };
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  // support optionally provided wrapping driver info
							 | 
						||
| 
								 | 
							
								  if (options.driverInfo) {
							 | 
						||
| 
								 | 
							
								    if (options.driverInfo.name) {
							 | 
						||
| 
								 | 
							
								      metadata.driver.name = `${metadata.driver.name}|${options.driverInfo.name}`;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (options.driverInfo.version) {
							 | 
						||
| 
								 | 
							
								      metadata.version = `${metadata.driver.version}|${options.driverInfo.version}`;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (options.driverInfo.platform) {
							 | 
						||
| 
								 | 
							
								      metadata.platform = `${metadata.platform}|${options.driverInfo.platform}`;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  if (options.appname) {
							 | 
						||
| 
								 | 
							
								    // MongoDB requires the appname not exceed a byte length of 128
							 | 
						||
| 
								 | 
							
								    const buffer = Buffer.from(options.appname);
							 | 
						||
| 
								 | 
							
								    metadata.application = {
							 | 
						||
| 
								 | 
							
								      name: buffer.length > 128 ? buffer.slice(0, 128).toString('utf8') : options.appname
							 | 
						||
| 
								 | 
							
								    };
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  return metadata;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								const noop = () => {};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								module.exports = {
							 | 
						||
| 
								 | 
							
								  uuidV4,
							 | 
						||
| 
								 | 
							
								  relayEvents,
							 | 
						||
| 
								 | 
							
								  collationNotSupported,
							 | 
						||
| 
								 | 
							
								  retrieveEJSON,
							 | 
						||
| 
								 | 
							
								  retrieveKerberos,
							 | 
						||
| 
								 | 
							
								  maxWireVersion,
							 | 
						||
| 
								 | 
							
								  isPromiseLike,
							 | 
						||
| 
								 | 
							
								  eachAsync,
							 | 
						||
| 
								 | 
							
								  eachAsyncSeries,
							 | 
						||
| 
								 | 
							
								  isUnifiedTopology,
							 | 
						||
| 
								 | 
							
								  arrayStrictEqual,
							 | 
						||
| 
								 | 
							
								  tagsStrictEqual,
							 | 
						||
| 
								 | 
							
								  errorStrictEqual,
							 | 
						||
| 
								 | 
							
								  makeStateMachine,
							 | 
						||
| 
								 | 
							
								  makeClientMetadata,
							 | 
						||
| 
								 | 
							
								  noop
							 | 
						||
| 
								 | 
							
								};
							 |