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.
		
		
		
		
		
			
		
			
				
					
					
						
							785 lines
						
					
					
						
							22 KiB
						
					
					
				
			
		
		
	
	
							785 lines
						
					
					
						
							22 KiB
						
					
					
				'use strict';
 | 
						|
 | 
						|
const deprecate = require('util').deprecate;
 | 
						|
const Logger = require('../core').Logger;
 | 
						|
const MongoCredentials = require('../core').MongoCredentials;
 | 
						|
const MongoError = require('../core').MongoError;
 | 
						|
const Mongos = require('../topologies/mongos');
 | 
						|
const NativeTopology = require('../topologies/native_topology');
 | 
						|
const parse = require('../core').parseConnectionString;
 | 
						|
const ReadConcern = require('../read_concern');
 | 
						|
const ReadPreference = require('../core').ReadPreference;
 | 
						|
const ReplSet = require('../topologies/replset');
 | 
						|
const Server = require('../topologies/server');
 | 
						|
const ServerSessionPool = require('../core').Sessions.ServerSessionPool;
 | 
						|
const emitDeprecationWarning = require('../utils').emitDeprecationWarning;
 | 
						|
const emitWarningOnce = require('../utils').emitWarningOnce;
 | 
						|
const fs = require('fs');
 | 
						|
const WriteConcern = require('../write_concern');
 | 
						|
const CMAP_EVENT_NAMES = require('../cmap/events').CMAP_EVENT_NAMES;
 | 
						|
 | 
						|
let client;
 | 
						|
function loadClient() {
 | 
						|
  if (!client) {
 | 
						|
    client = require('../mongo_client');
 | 
						|
  }
 | 
						|
  return client;
 | 
						|
}
 | 
						|
 | 
						|
const legacyParse = deprecate(
 | 
						|
  require('../url_parser'),
 | 
						|
  'current URL string parser is deprecated, and will be removed in a future version. ' +
 | 
						|
    'To use the new parser, pass option { useNewUrlParser: true } to MongoClient.connect.'
 | 
						|
);
 | 
						|
 | 
						|
const AUTH_MECHANISM_INTERNAL_MAP = {
 | 
						|
  DEFAULT: 'default',
 | 
						|
  PLAIN: 'plain',
 | 
						|
  GSSAPI: 'gssapi',
 | 
						|
  'MONGODB-CR': 'mongocr',
 | 
						|
  'MONGODB-X509': 'x509',
 | 
						|
  'MONGODB-AWS': 'mongodb-aws',
 | 
						|
  'SCRAM-SHA-1': 'scram-sha-1',
 | 
						|
  'SCRAM-SHA-256': 'scram-sha-256'
 | 
						|
};
 | 
						|
 | 
						|
const monitoringEvents = [
 | 
						|
  'timeout',
 | 
						|
  'close',
 | 
						|
  'serverOpening',
 | 
						|
  'serverDescriptionChanged',
 | 
						|
  'serverHeartbeatStarted',
 | 
						|
  'serverHeartbeatSucceeded',
 | 
						|
  'serverHeartbeatFailed',
 | 
						|
  'serverClosed',
 | 
						|
  'topologyOpening',
 | 
						|
  'topologyClosed',
 | 
						|
  'topologyDescriptionChanged',
 | 
						|
  'commandStarted',
 | 
						|
  'commandSucceeded',
 | 
						|
  'commandFailed',
 | 
						|
  'joined',
 | 
						|
  'left',
 | 
						|
  'ping',
 | 
						|
  'ha',
 | 
						|
  'all',
 | 
						|
  'fullsetup',
 | 
						|
  'open'
 | 
						|
];
 | 
						|
 | 
						|
const VALID_AUTH_MECHANISMS = new Set([
 | 
						|
  'DEFAULT',
 | 
						|
  'PLAIN',
 | 
						|
  'GSSAPI',
 | 
						|
  'MONGODB-CR',
 | 
						|
  'MONGODB-X509',
 | 
						|
  'MONGODB-AWS',
 | 
						|
  'SCRAM-SHA-1',
 | 
						|
  'SCRAM-SHA-256'
 | 
						|
]);
 | 
						|
 | 
						|
const validOptionNames = [
 | 
						|
  'poolSize',
 | 
						|
  'ssl',
 | 
						|
  'sslValidate',
 | 
						|
  'sslCA',
 | 
						|
  'sslCert',
 | 
						|
  'sslKey',
 | 
						|
  'sslPass',
 | 
						|
  'sslCRL',
 | 
						|
  'autoReconnect',
 | 
						|
  'noDelay',
 | 
						|
  'keepAlive',
 | 
						|
  'keepAliveInitialDelay',
 | 
						|
  'connectTimeoutMS',
 | 
						|
  'family',
 | 
						|
  'socketTimeoutMS',
 | 
						|
  'reconnectTries',
 | 
						|
  'reconnectInterval',
 | 
						|
  'ha',
 | 
						|
  'haInterval',
 | 
						|
  'replicaSet',
 | 
						|
  'secondaryAcceptableLatencyMS',
 | 
						|
  'acceptableLatencyMS',
 | 
						|
  'connectWithNoPrimary',
 | 
						|
  'authSource',
 | 
						|
  'w',
 | 
						|
  'wtimeout',
 | 
						|
  'j',
 | 
						|
  'writeConcern',
 | 
						|
  'forceServerObjectId',
 | 
						|
  'serializeFunctions',
 | 
						|
  'ignoreUndefined',
 | 
						|
  'raw',
 | 
						|
  'bufferMaxEntries',
 | 
						|
  'readPreference',
 | 
						|
  'pkFactory',
 | 
						|
  'promiseLibrary',
 | 
						|
  'readConcern',
 | 
						|
  'maxStalenessSeconds',
 | 
						|
  'loggerLevel',
 | 
						|
  'logger',
 | 
						|
  'promoteValues',
 | 
						|
  'promoteBuffers',
 | 
						|
  'promoteLongs',
 | 
						|
  'bsonRegExp',
 | 
						|
  'domainsEnabled',
 | 
						|
  'checkServerIdentity',
 | 
						|
  'validateOptions',
 | 
						|
  'appname',
 | 
						|
  'auth',
 | 
						|
  'user',
 | 
						|
  'password',
 | 
						|
  'authMechanism',
 | 
						|
  'compression',
 | 
						|
  'fsync',
 | 
						|
  'readPreferenceTags',
 | 
						|
  'numberOfRetries',
 | 
						|
  'auto_reconnect',
 | 
						|
  'minSize',
 | 
						|
  'monitorCommands',
 | 
						|
  'serverApi',
 | 
						|
  'retryWrites',
 | 
						|
  'retryReads',
 | 
						|
  'useNewUrlParser',
 | 
						|
  'useUnifiedTopology',
 | 
						|
  'serverSelectionTimeoutMS',
 | 
						|
  'useRecoveryToken',
 | 
						|
  'autoEncryption',
 | 
						|
  'driverInfo',
 | 
						|
  'tls',
 | 
						|
  'tlsInsecure',
 | 
						|
  'tlsinsecure',
 | 
						|
  'tlsAllowInvalidCertificates',
 | 
						|
  'tlsAllowInvalidHostnames',
 | 
						|
  'tlsCAFile',
 | 
						|
  'tlsCertificateFile',
 | 
						|
  'tlsCertificateKeyFile',
 | 
						|
  'tlsCertificateKeyFilePassword',
 | 
						|
  'minHeartbeatFrequencyMS',
 | 
						|
  'heartbeatFrequencyMS',
 | 
						|
  'directConnection',
 | 
						|
  'appName',
 | 
						|
 | 
						|
  // CMAP options
 | 
						|
  'maxPoolSize',
 | 
						|
  'minPoolSize',
 | 
						|
  'maxIdleTimeMS',
 | 
						|
  'waitQueueTimeoutMS'
 | 
						|
];
 | 
						|
 | 
						|
const ignoreOptionNames = ['native_parser'];
 | 
						|
const legacyOptionNames = ['server', 'replset', 'replSet', 'mongos', 'db'];
 | 
						|
 | 
						|
// Validate options object
 | 
						|
function validOptions(options) {
 | 
						|
  const _validOptions = validOptionNames.concat(legacyOptionNames);
 | 
						|
 | 
						|
  for (const name in options) {
 | 
						|
    if (ignoreOptionNames.indexOf(name) !== -1) {
 | 
						|
      continue;
 | 
						|
    }
 | 
						|
 | 
						|
    if (_validOptions.indexOf(name) === -1) {
 | 
						|
      if (options.validateOptions) {
 | 
						|
        return new MongoError(`option ${name} is not supported`);
 | 
						|
      } else {
 | 
						|
        emitWarningOnce(`the options [${name}] is not supported`);
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    if (legacyOptionNames.indexOf(name) !== -1) {
 | 
						|
      emitWarningOnce(
 | 
						|
        `the server/replset/mongos/db options are deprecated, ` +
 | 
						|
          `all their options are supported at the top level of the options object [${validOptionNames}]`
 | 
						|
      );
 | 
						|
    }
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
const LEGACY_OPTIONS_MAP = validOptionNames.reduce((obj, name) => {
 | 
						|
  obj[name.toLowerCase()] = name;
 | 
						|
  return obj;
 | 
						|
}, {});
 | 
						|
 | 
						|
function addListeners(mongoClient, topology) {
 | 
						|
  topology.on('authenticated', createListener(mongoClient, 'authenticated'));
 | 
						|
  topology.on('error', createListener(mongoClient, 'error'));
 | 
						|
  topology.on('timeout', createListener(mongoClient, 'timeout'));
 | 
						|
  topology.on('close', createListener(mongoClient, 'close'));
 | 
						|
  topology.on('parseError', createListener(mongoClient, 'parseError'));
 | 
						|
  topology.once('open', createListener(mongoClient, 'open'));
 | 
						|
  topology.once('fullsetup', createListener(mongoClient, 'fullsetup'));
 | 
						|
  topology.once('all', createListener(mongoClient, 'all'));
 | 
						|
  topology.on('reconnect', createListener(mongoClient, 'reconnect'));
 | 
						|
}
 | 
						|
 | 
						|
function assignTopology(client, topology) {
 | 
						|
  client.topology = topology;
 | 
						|
 | 
						|
  if (!(topology instanceof NativeTopology)) {
 | 
						|
    topology.s.sessionPool = new ServerSessionPool(topology.s.coreTopology);
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
// Clear out all events
 | 
						|
function clearAllEvents(topology) {
 | 
						|
  monitoringEvents.forEach(event => topology.removeAllListeners(event));
 | 
						|
}
 | 
						|
 | 
						|
// Collect all events in order from SDAM
 | 
						|
function collectEvents(mongoClient, topology) {
 | 
						|
  let MongoClient = loadClient();
 | 
						|
  const collectedEvents = [];
 | 
						|
 | 
						|
  if (mongoClient instanceof MongoClient) {
 | 
						|
    monitoringEvents.forEach(event => {
 | 
						|
      topology.on(event, (object1, object2) => {
 | 
						|
        if (event === 'open') {
 | 
						|
          collectedEvents.push({ event: event, object1: mongoClient });
 | 
						|
        } else {
 | 
						|
          collectedEvents.push({ event: event, object1: object1, object2: object2 });
 | 
						|
        }
 | 
						|
      });
 | 
						|
    });
 | 
						|
  }
 | 
						|
 | 
						|
  return collectedEvents;
 | 
						|
}
 | 
						|
 | 
						|
function resolveTLSOptions(options) {
 | 
						|
  if (options.tls == null) {
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  ['sslCA', 'sslKey', 'sslCert'].forEach(optionName => {
 | 
						|
    if (options[optionName]) {
 | 
						|
      options[optionName] = fs.readFileSync(options[optionName]);
 | 
						|
    }
 | 
						|
  });
 | 
						|
}
 | 
						|
 | 
						|
function connect(mongoClient, url, options, callback) {
 | 
						|
  options = Object.assign({}, options);
 | 
						|
 | 
						|
  // If callback is null throw an exception
 | 
						|
  if (callback == null) {
 | 
						|
    throw new Error('no callback function provided');
 | 
						|
  }
 | 
						|
 | 
						|
  let didRequestAuthentication = false;
 | 
						|
  const logger = Logger('MongoClient', options);
 | 
						|
 | 
						|
  // Did we pass in a Server/ReplSet/Mongos
 | 
						|
  if (url instanceof Server || url instanceof ReplSet || url instanceof Mongos) {
 | 
						|
    return connectWithUrl(mongoClient, url, options, connectCallback);
 | 
						|
  }
 | 
						|
 | 
						|
  const useNewUrlParser = options.useNewUrlParser !== false;
 | 
						|
 | 
						|
  const parseFn = useNewUrlParser ? parse : legacyParse;
 | 
						|
  const transform = useNewUrlParser ? transformUrlOptions : legacyTransformUrlOptions;
 | 
						|
 | 
						|
  parseFn(url, options, (err, _object) => {
 | 
						|
    // Do not attempt to connect if parsing error
 | 
						|
    if (err) return callback(err);
 | 
						|
 | 
						|
    // Flatten
 | 
						|
    const object = transform(_object);
 | 
						|
 | 
						|
    // Parse the string
 | 
						|
    const _finalOptions = createUnifiedOptions(object, options);
 | 
						|
 | 
						|
    // Check if we have connection and socket timeout set
 | 
						|
    if (_finalOptions.socketTimeoutMS == null) _finalOptions.socketTimeoutMS = 0;
 | 
						|
    if (_finalOptions.connectTimeoutMS == null) _finalOptions.connectTimeoutMS = 10000;
 | 
						|
    if (_finalOptions.retryWrites == null) _finalOptions.retryWrites = true;
 | 
						|
    if (_finalOptions.useRecoveryToken == null) _finalOptions.useRecoveryToken = true;
 | 
						|
    if (_finalOptions.readPreference == null) _finalOptions.readPreference = 'primary';
 | 
						|
 | 
						|
    if (_finalOptions.db_options && _finalOptions.db_options.auth) {
 | 
						|
      delete _finalOptions.db_options.auth;
 | 
						|
    }
 | 
						|
 | 
						|
    // resolve tls options if needed
 | 
						|
    resolveTLSOptions(_finalOptions);
 | 
						|
 | 
						|
    // Store the merged options object
 | 
						|
    mongoClient.s.options = _finalOptions;
 | 
						|
 | 
						|
    // Apply read and write concern from parsed url
 | 
						|
    mongoClient.s.readPreference = ReadPreference.fromOptions(_finalOptions);
 | 
						|
    mongoClient.s.writeConcern = WriteConcern.fromOptions(_finalOptions);
 | 
						|
 | 
						|
    // Failure modes
 | 
						|
    if (object.servers.length === 0) {
 | 
						|
      return callback(new Error('connection string must contain at least one seed host'));
 | 
						|
    }
 | 
						|
 | 
						|
    if (_finalOptions.auth && !_finalOptions.credentials) {
 | 
						|
      try {
 | 
						|
        didRequestAuthentication = true;
 | 
						|
        _finalOptions.credentials = generateCredentials(
 | 
						|
          mongoClient,
 | 
						|
          _finalOptions.auth.user,
 | 
						|
          _finalOptions.auth.password,
 | 
						|
          _finalOptions
 | 
						|
        );
 | 
						|
      } catch (err) {
 | 
						|
        return callback(err);
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    if (_finalOptions.useUnifiedTopology) {
 | 
						|
      return createTopology(mongoClient, 'unified', _finalOptions, connectCallback);
 | 
						|
    }
 | 
						|
 | 
						|
    emitWarningOnce(
 | 
						|
      'Current Server Discovery and Monitoring engine is deprecated, and will be removed in a future version. To use the new Server Discover and Monitoring engine, pass option { useUnifiedTopology: true } to the MongoClient constructor.'
 | 
						|
    );
 | 
						|
 | 
						|
    // Do we have a replicaset then skip discovery and go straight to connectivity
 | 
						|
    if (_finalOptions.replicaSet || _finalOptions.rs_name) {
 | 
						|
      return createTopology(mongoClient, 'replicaset', _finalOptions, connectCallback);
 | 
						|
    } else if (object.servers.length > 1) {
 | 
						|
      return createTopology(mongoClient, 'mongos', _finalOptions, connectCallback);
 | 
						|
    } else {
 | 
						|
      return createServer(mongoClient, _finalOptions, connectCallback);
 | 
						|
    }
 | 
						|
  });
 | 
						|
 | 
						|
  function connectCallback(err, topology) {
 | 
						|
    const warningMessage = `seed list contains no mongos proxies, replicaset connections requires the parameter replicaSet to be supplied in the URI or options object, mongodb://server:port/db?replicaSet=name`;
 | 
						|
    if (err && err.message === 'no mongos proxies found in seed list') {
 | 
						|
      if (logger.isWarn()) {
 | 
						|
        logger.warn(warningMessage);
 | 
						|
      }
 | 
						|
 | 
						|
      // Return a more specific error message for MongoClient.connect
 | 
						|
      return callback(new MongoError(warningMessage));
 | 
						|
    }
 | 
						|
 | 
						|
    if (didRequestAuthentication) {
 | 
						|
      mongoClient.emit('authenticated', null, true);
 | 
						|
    }
 | 
						|
 | 
						|
    // Return the error and db instance
 | 
						|
    callback(err, topology);
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
function connectWithUrl(mongoClient, url, options, connectCallback) {
 | 
						|
  // Set the topology
 | 
						|
  assignTopology(mongoClient, url);
 | 
						|
 | 
						|
  // Add listeners
 | 
						|
  addListeners(mongoClient, url);
 | 
						|
 | 
						|
  // Propagate the events to the client
 | 
						|
  relayEvents(mongoClient, url);
 | 
						|
 | 
						|
  let finalOptions = Object.assign({}, options);
 | 
						|
 | 
						|
  // If we have a readPreference passed in by the db options, convert it from a string
 | 
						|
  if (typeof options.readPreference === 'string' || typeof options.read_preference === 'string') {
 | 
						|
    finalOptions.readPreference = new ReadPreference(
 | 
						|
      options.readPreference || options.read_preference
 | 
						|
    );
 | 
						|
  }
 | 
						|
 | 
						|
  const isDoingAuth = finalOptions.user || finalOptions.password || finalOptions.authMechanism;
 | 
						|
  if (isDoingAuth && !finalOptions.credentials) {
 | 
						|
    try {
 | 
						|
      finalOptions.credentials = generateCredentials(
 | 
						|
        mongoClient,
 | 
						|
        finalOptions.user,
 | 
						|
        finalOptions.password,
 | 
						|
        finalOptions
 | 
						|
      );
 | 
						|
    } catch (err) {
 | 
						|
      return connectCallback(err, url);
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  return url.connect(finalOptions, connectCallback);
 | 
						|
}
 | 
						|
 | 
						|
function createListener(mongoClient, event) {
 | 
						|
  const eventSet = new Set(['all', 'fullsetup', 'open', 'reconnect']);
 | 
						|
  return (v1, v2) => {
 | 
						|
    if (eventSet.has(event)) {
 | 
						|
      return mongoClient.emit(event, mongoClient);
 | 
						|
    }
 | 
						|
 | 
						|
    mongoClient.emit(event, v1, v2);
 | 
						|
  };
 | 
						|
}
 | 
						|
 | 
						|
function createServer(mongoClient, options, callback) {
 | 
						|
  // Pass in the promise library
 | 
						|
  options.promiseLibrary = mongoClient.s.promiseLibrary;
 | 
						|
 | 
						|
  // Set default options
 | 
						|
  const servers = translateOptions(options);
 | 
						|
 | 
						|
  const server = servers[0];
 | 
						|
 | 
						|
  // Propagate the events to the client
 | 
						|
  const collectedEvents = collectEvents(mongoClient, server);
 | 
						|
 | 
						|
  // Connect to topology
 | 
						|
  server.connect(options, (err, topology) => {
 | 
						|
    if (err) {
 | 
						|
      server.close(true);
 | 
						|
      return callback(err);
 | 
						|
    }
 | 
						|
    // Clear out all the collected event listeners
 | 
						|
    clearAllEvents(server);
 | 
						|
 | 
						|
    // Relay all the events
 | 
						|
    relayEvents(mongoClient, server);
 | 
						|
    // Add listeners
 | 
						|
    addListeners(mongoClient, server);
 | 
						|
    // Check if we are really speaking to a mongos
 | 
						|
    const ismaster = topology.lastIsMaster();
 | 
						|
 | 
						|
    // Set the topology
 | 
						|
    assignTopology(mongoClient, topology);
 | 
						|
 | 
						|
    // Do we actually have a mongos
 | 
						|
    if (ismaster && ismaster.msg === 'isdbgrid') {
 | 
						|
      // Destroy the current connection
 | 
						|
      topology.close();
 | 
						|
      // Create mongos connection instead
 | 
						|
      return createTopology(mongoClient, 'mongos', options, callback);
 | 
						|
    }
 | 
						|
 | 
						|
    // Fire all the events
 | 
						|
    replayEvents(mongoClient, collectedEvents);
 | 
						|
    // Otherwise callback
 | 
						|
    callback(err, topology);
 | 
						|
  });
 | 
						|
}
 | 
						|
 | 
						|
const DEPRECATED_UNIFIED_EVENTS = new Set([
 | 
						|
  'reconnect',
 | 
						|
  'reconnectFailed',
 | 
						|
  'attemptReconnect',
 | 
						|
  'joined',
 | 
						|
  'left',
 | 
						|
  'ping',
 | 
						|
  'ha',
 | 
						|
  'all',
 | 
						|
  'fullsetup',
 | 
						|
  'open'
 | 
						|
]);
 | 
						|
 | 
						|
function registerDeprecatedEventNotifiers(client) {
 | 
						|
  client.on('newListener', eventName => {
 | 
						|
    if (DEPRECATED_UNIFIED_EVENTS.has(eventName)) {
 | 
						|
      emitDeprecationWarning(
 | 
						|
        `The \`${eventName}\` event is no longer supported by the unified topology, please read more by visiting http://bit.ly/2D8WfT6`,
 | 
						|
        'DeprecationWarning'
 | 
						|
      );
 | 
						|
    }
 | 
						|
  });
 | 
						|
}
 | 
						|
 | 
						|
function createTopology(mongoClient, topologyType, options, callback) {
 | 
						|
  // Pass in the promise library
 | 
						|
  options.promiseLibrary = mongoClient.s.promiseLibrary;
 | 
						|
 | 
						|
  const translationOptions = {};
 | 
						|
  if (topologyType === 'unified') translationOptions.createServers = false;
 | 
						|
 | 
						|
  // Set default options
 | 
						|
  const servers = translateOptions(options, translationOptions);
 | 
						|
 | 
						|
  // determine CSFLE support
 | 
						|
  if (options.autoEncryption != null) {
 | 
						|
    const Encrypter = require('../encrypter').Encrypter;
 | 
						|
    options.encrypter = new Encrypter(mongoClient, options);
 | 
						|
    options.autoEncrypter = options.encrypter.autoEncrypter;
 | 
						|
  }
 | 
						|
 | 
						|
  // Create the topology
 | 
						|
  let topology;
 | 
						|
  if (topologyType === 'mongos') {
 | 
						|
    topology = new Mongos(servers, options);
 | 
						|
  } else if (topologyType === 'replicaset') {
 | 
						|
    topology = new ReplSet(servers, options);
 | 
						|
  } else if (topologyType === 'unified') {
 | 
						|
    topology = new NativeTopology(options.servers, options);
 | 
						|
    registerDeprecatedEventNotifiers(mongoClient);
 | 
						|
  }
 | 
						|
 | 
						|
  // Add listeners
 | 
						|
  addListeners(mongoClient, topology);
 | 
						|
 | 
						|
  // Propagate the events to the client
 | 
						|
  relayEvents(mongoClient, topology);
 | 
						|
 | 
						|
  // Open the connection
 | 
						|
  assignTopology(mongoClient, topology);
 | 
						|
 | 
						|
  // initialize CSFLE if requested
 | 
						|
  if (options.autoEncrypter) {
 | 
						|
    options.autoEncrypter.init(err => {
 | 
						|
      if (err) {
 | 
						|
        callback(err);
 | 
						|
        return;
 | 
						|
      }
 | 
						|
 | 
						|
      topology.connect(options, err => {
 | 
						|
        if (err) {
 | 
						|
          topology.close(true);
 | 
						|
          callback(err);
 | 
						|
          return;
 | 
						|
        }
 | 
						|
 | 
						|
        options.encrypter.connectInternalClient(error => {
 | 
						|
          if (error) return callback(error);
 | 
						|
          callback(undefined, topology);
 | 
						|
        });
 | 
						|
      });
 | 
						|
    });
 | 
						|
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  // otherwise connect normally
 | 
						|
  topology.connect(options, err => {
 | 
						|
    if (err) {
 | 
						|
      topology.close(true);
 | 
						|
      return callback(err);
 | 
						|
    }
 | 
						|
 | 
						|
    callback(undefined, topology);
 | 
						|
    return;
 | 
						|
  });
 | 
						|
}
 | 
						|
 | 
						|
function createUnifiedOptions(finalOptions, options) {
 | 
						|
  const childOptions = [
 | 
						|
    'mongos',
 | 
						|
    'server',
 | 
						|
    'db',
 | 
						|
    'replset',
 | 
						|
    'db_options',
 | 
						|
    'server_options',
 | 
						|
    'rs_options',
 | 
						|
    'mongos_options'
 | 
						|
  ];
 | 
						|
  const noMerge = ['readconcern', 'compression', 'autoencryption'];
 | 
						|
  const skip = ['w', 'wtimeout', 'j', 'journal', 'fsync', 'writeconcern'];
 | 
						|
 | 
						|
  for (const name in options) {
 | 
						|
    if (skip.indexOf(name.toLowerCase()) !== -1) {
 | 
						|
      continue;
 | 
						|
    } else if (noMerge.indexOf(name.toLowerCase()) !== -1) {
 | 
						|
      finalOptions[name] = options[name];
 | 
						|
    } else if (childOptions.indexOf(name.toLowerCase()) !== -1) {
 | 
						|
      finalOptions = mergeOptions(finalOptions, options[name], false);
 | 
						|
    } else {
 | 
						|
      if (
 | 
						|
        options[name] &&
 | 
						|
        typeof options[name] === 'object' &&
 | 
						|
        !Buffer.isBuffer(options[name]) &&
 | 
						|
        !Array.isArray(options[name])
 | 
						|
      ) {
 | 
						|
        finalOptions = mergeOptions(finalOptions, options[name], true);
 | 
						|
      } else {
 | 
						|
        finalOptions[name] = options[name];
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  // Handle write concern keys separately, since `options` may have the keys at the top level or
 | 
						|
  // under `options.writeConcern`. The final merged keys will be under `finalOptions.writeConcern`.
 | 
						|
  // This way, `fromOptions` will warn once if `options` is using deprecated write concern options
 | 
						|
  const optionsWriteConcern = WriteConcern.fromOptions(options);
 | 
						|
  if (optionsWriteConcern) {
 | 
						|
    finalOptions.writeConcern = Object.assign({}, finalOptions.writeConcern, optionsWriteConcern);
 | 
						|
  }
 | 
						|
 | 
						|
  return finalOptions;
 | 
						|
}
 | 
						|
 | 
						|
function generateCredentials(client, username, password, options) {
 | 
						|
  options = Object.assign({}, options);
 | 
						|
 | 
						|
  // the default db to authenticate against is 'self'
 | 
						|
  // if authententicate is called from a retry context, it may be another one, like admin
 | 
						|
  const source = options.authSource || options.authdb || options.dbName;
 | 
						|
 | 
						|
  // authMechanism
 | 
						|
  const authMechanismRaw = options.authMechanism || 'DEFAULT';
 | 
						|
  const authMechanism = authMechanismRaw.toUpperCase();
 | 
						|
  const mechanismProperties = options.authMechanismProperties;
 | 
						|
 | 
						|
  if (!VALID_AUTH_MECHANISMS.has(authMechanism)) {
 | 
						|
    throw MongoError.create({
 | 
						|
      message: `authentication mechanism ${authMechanismRaw} not supported', options.authMechanism`,
 | 
						|
      driver: true
 | 
						|
    });
 | 
						|
  }
 | 
						|
 | 
						|
  return new MongoCredentials({
 | 
						|
    mechanism: AUTH_MECHANISM_INTERNAL_MAP[authMechanism],
 | 
						|
    mechanismProperties,
 | 
						|
    source,
 | 
						|
    username,
 | 
						|
    password
 | 
						|
  });
 | 
						|
}
 | 
						|
 | 
						|
function legacyTransformUrlOptions(object) {
 | 
						|
  return mergeOptions(createUnifiedOptions({}, object), object, false);
 | 
						|
}
 | 
						|
 | 
						|
function mergeOptions(target, source, flatten) {
 | 
						|
  for (const name in source) {
 | 
						|
    if (source[name] && typeof source[name] === 'object' && flatten) {
 | 
						|
      target = mergeOptions(target, source[name], flatten);
 | 
						|
    } else {
 | 
						|
      target[name] = source[name];
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  return target;
 | 
						|
}
 | 
						|
 | 
						|
function relayEvents(mongoClient, topology) {
 | 
						|
  const serverOrCommandEvents = [
 | 
						|
    // APM
 | 
						|
    'commandStarted',
 | 
						|
    'commandSucceeded',
 | 
						|
    'commandFailed',
 | 
						|
 | 
						|
    // SDAM
 | 
						|
    'serverOpening',
 | 
						|
    'serverClosed',
 | 
						|
    'serverDescriptionChanged',
 | 
						|
    'serverHeartbeatStarted',
 | 
						|
    'serverHeartbeatSucceeded',
 | 
						|
    'serverHeartbeatFailed',
 | 
						|
    'topologyOpening',
 | 
						|
    'topologyClosed',
 | 
						|
    'topologyDescriptionChanged',
 | 
						|
 | 
						|
    // Legacy
 | 
						|
    'joined',
 | 
						|
    'left',
 | 
						|
    'ping',
 | 
						|
    'ha'
 | 
						|
  ].concat(CMAP_EVENT_NAMES);
 | 
						|
 | 
						|
  serverOrCommandEvents.forEach(event => {
 | 
						|
    topology.on(event, (object1, object2) => {
 | 
						|
      mongoClient.emit(event, object1, object2);
 | 
						|
    });
 | 
						|
  });
 | 
						|
}
 | 
						|
 | 
						|
//
 | 
						|
// Replay any events due to single server connection switching to Mongos
 | 
						|
//
 | 
						|
function replayEvents(mongoClient, events) {
 | 
						|
  for (let i = 0; i < events.length; i++) {
 | 
						|
    mongoClient.emit(events[i].event, events[i].object1, events[i].object2);
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
function transformUrlOptions(_object) {
 | 
						|
  let object = Object.assign({ servers: _object.hosts }, _object.options);
 | 
						|
  for (let name in object) {
 | 
						|
    const camelCaseName = LEGACY_OPTIONS_MAP[name];
 | 
						|
    if (camelCaseName) {
 | 
						|
      object[camelCaseName] = object[name];
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  const hasUsername = _object.auth && _object.auth.username;
 | 
						|
  const hasAuthMechanism = _object.options && _object.options.authMechanism;
 | 
						|
  if (hasUsername || hasAuthMechanism) {
 | 
						|
    object.auth = Object.assign({}, _object.auth);
 | 
						|
    if (object.auth.db) {
 | 
						|
      object.authSource = object.authSource || object.auth.db;
 | 
						|
    }
 | 
						|
 | 
						|
    if (object.auth.username) {
 | 
						|
      object.auth.user = object.auth.username;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  if (_object.defaultDatabase) {
 | 
						|
    object.dbName = _object.defaultDatabase;
 | 
						|
  }
 | 
						|
 | 
						|
  if (object.maxPoolSize) {
 | 
						|
    object.poolSize = object.maxPoolSize;
 | 
						|
  }
 | 
						|
 | 
						|
  if (object.readConcernLevel) {
 | 
						|
    object.readConcern = new ReadConcern(object.readConcernLevel);
 | 
						|
  }
 | 
						|
 | 
						|
  if (object.wTimeoutMS) {
 | 
						|
    object.wtimeout = object.wTimeoutMS;
 | 
						|
    object.wTimeoutMS = undefined;
 | 
						|
  }
 | 
						|
 | 
						|
  if (_object.srvHost) {
 | 
						|
    object.srvHost = _object.srvHost;
 | 
						|
  }
 | 
						|
 | 
						|
  // Any write concern options from the URL will be top-level, so we manually
 | 
						|
  // move them options under `object.writeConcern` to avoid warnings later
 | 
						|
  const wcKeys = ['w', 'wtimeout', 'j', 'journal', 'fsync'];
 | 
						|
  for (const key of wcKeys) {
 | 
						|
    if (object[key] !== undefined) {
 | 
						|
      if (object.writeConcern === undefined) object.writeConcern = {};
 | 
						|
      object.writeConcern[key] = object[key];
 | 
						|
      object[key] = undefined;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  return object;
 | 
						|
}
 | 
						|
 | 
						|
function translateOptions(options, translationOptions) {
 | 
						|
  translationOptions = Object.assign({}, { createServers: true }, translationOptions);
 | 
						|
 | 
						|
  // If we have a readPreference passed in by the db options
 | 
						|
  if (typeof options.readPreference === 'string' || typeof options.read_preference === 'string') {
 | 
						|
    options.readPreference = new ReadPreference(options.readPreference || options.read_preference);
 | 
						|
  }
 | 
						|
 | 
						|
  // Do we have readPreference tags, add them
 | 
						|
  if (options.readPreference && (options.readPreferenceTags || options.read_preference_tags)) {
 | 
						|
    options.readPreference.tags = options.readPreferenceTags || options.read_preference_tags;
 | 
						|
  }
 | 
						|
 | 
						|
  // Do we have maxStalenessSeconds
 | 
						|
  if (options.maxStalenessSeconds) {
 | 
						|
    options.readPreference.maxStalenessSeconds = options.maxStalenessSeconds;
 | 
						|
  }
 | 
						|
 | 
						|
  // Set the socket and connection timeouts
 | 
						|
  if (options.socketTimeoutMS == null) options.socketTimeoutMS = 0;
 | 
						|
  if (options.connectTimeoutMS == null) options.connectTimeoutMS = 10000;
 | 
						|
 | 
						|
  if (!translationOptions.createServers) {
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  // Create server instances
 | 
						|
  return options.servers.map(serverObj => {
 | 
						|
    return serverObj.domain_socket
 | 
						|
      ? new Server(serverObj.domain_socket, 27017, options)
 | 
						|
      : new Server(serverObj.host, serverObj.port, options);
 | 
						|
  });
 | 
						|
}
 | 
						|
 | 
						|
module.exports = { validOptions, connect };
 |