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 };
 |