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
			| 
											3 years ago
										 | '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 }; |