Cleanup
This commit is contained in:
167
node_modules/mongodb/lib/core/auth/auth_provider.js
generated
vendored
167
node_modules/mongodb/lib/core/auth/auth_provider.js
generated
vendored
@@ -1,158 +1,55 @@
|
||||
'use strict';
|
||||
|
||||
const MongoError = require('../error').MongoError;
|
||||
|
||||
/**
|
||||
* Creates a new AuthProvider, which dictates how to authenticate for a given
|
||||
* mechanism.
|
||||
* @class
|
||||
* Context used during authentication
|
||||
*
|
||||
* @property {Connection} connection The connection to authenticate
|
||||
* @property {MongoCredentials} credentials The credentials to use for authentication
|
||||
* @property {object} options The options passed to the `connect` method
|
||||
* @property {object?} response The response of the initial handshake
|
||||
* @property {Buffer?} nonce A random nonce generated for use in an authentication conversation
|
||||
*/
|
||||
class AuthContext {
|
||||
constructor(connection, credentials, options) {
|
||||
this.connection = connection;
|
||||
this.credentials = credentials;
|
||||
this.options = options;
|
||||
}
|
||||
}
|
||||
|
||||
class AuthProvider {
|
||||
constructor(bson) {
|
||||
this.bson = bson;
|
||||
this.authStore = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the handshake document before the initial handshake.
|
||||
*
|
||||
* @param {object} handshakeDoc The document used for the initial handshake on a connection
|
||||
* @param {AuthContext} authContext Context for authentication flow
|
||||
* @param {function} callback
|
||||
*/
|
||||
prepare(handshakeDoc, context, callback) {
|
||||
callback(undefined, handshakeDoc);
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate
|
||||
* @method
|
||||
* @param {SendAuthCommand} sendAuthCommand Writes an auth command directly to a specific connection
|
||||
* @param {Connection[]} connections Connections to authenticate using this authenticator
|
||||
* @param {MongoCredentials} credentials Authentication credentials
|
||||
*
|
||||
* @param {AuthContext} context A shared context for authentication flow
|
||||
* @param {authResultCallback} callback The callback to return the result from the authentication
|
||||
*/
|
||||
auth(sendAuthCommand, connections, credentials, callback) {
|
||||
// Total connections
|
||||
let count = connections.length;
|
||||
|
||||
if (count === 0) {
|
||||
callback(null, null);
|
||||
return;
|
||||
}
|
||||
|
||||
// Valid connections
|
||||
let numberOfValidConnections = 0;
|
||||
let errorObject = null;
|
||||
|
||||
const execute = connection => {
|
||||
this._authenticateSingleConnection(sendAuthCommand, connection, credentials, (err, r) => {
|
||||
// Adjust count
|
||||
count = count - 1;
|
||||
|
||||
// If we have an error
|
||||
if (err) {
|
||||
errorObject = new MongoError(err);
|
||||
} else if (r && (r.$err || r.errmsg)) {
|
||||
errorObject = new MongoError(r);
|
||||
} else {
|
||||
numberOfValidConnections = numberOfValidConnections + 1;
|
||||
}
|
||||
|
||||
// Still authenticating against other connections.
|
||||
if (count !== 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// We have authenticated all connections
|
||||
if (numberOfValidConnections > 0) {
|
||||
// Store the auth details
|
||||
this.addCredentials(credentials);
|
||||
// Return correct authentication
|
||||
callback(null, true);
|
||||
} else {
|
||||
if (errorObject == null) {
|
||||
errorObject = new MongoError(`failed to authenticate using ${credentials.mechanism}`);
|
||||
}
|
||||
callback(errorObject, false);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const executeInNextTick = _connection => process.nextTick(() => execute(_connection));
|
||||
|
||||
// For each connection we need to authenticate
|
||||
while (connections.length > 0) {
|
||||
executeInNextTick(connections.shift());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of a single connection authenticating. Is meant to be overridden.
|
||||
* Will error if called directly
|
||||
* @ignore
|
||||
*/
|
||||
_authenticateSingleConnection(/*sendAuthCommand, connection, credentials, callback*/) {
|
||||
throw new Error('_authenticateSingleConnection must be overridden');
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds credentials to store only if it does not exist
|
||||
* @param {MongoCredentials} credentials credentials to add to store
|
||||
*/
|
||||
addCredentials(credentials) {
|
||||
const found = this.authStore.some(cred => cred.equals(credentials));
|
||||
|
||||
if (!found) {
|
||||
this.authStore.push(credentials);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Re authenticate pool
|
||||
* @method
|
||||
* @param {SendAuthCommand} sendAuthCommand Writes an auth command directly to a specific connection
|
||||
* @param {Connection[]} connections Connections to authenticate using this authenticator
|
||||
* @param {authResultCallback} callback The callback to return the result from the authentication
|
||||
*/
|
||||
reauthenticate(sendAuthCommand, connections, callback) {
|
||||
const authStore = this.authStore.slice(0);
|
||||
let count = authStore.length;
|
||||
if (count === 0) {
|
||||
return callback(null, null);
|
||||
}
|
||||
|
||||
for (let i = 0; i < authStore.length; i++) {
|
||||
this.auth(sendAuthCommand, connections, authStore[i], function(err) {
|
||||
count = count - 1;
|
||||
if (count === 0) {
|
||||
callback(err, null);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove credentials that have been previously stored in the auth provider
|
||||
* @method
|
||||
* @param {string} source Name of database we are removing authStore details about
|
||||
* @return {object}
|
||||
*/
|
||||
logout(source) {
|
||||
this.authStore = this.authStore.filter(credentials => credentials.source !== source);
|
||||
auth(context, callback) {
|
||||
callback(new TypeError('`auth` method must be overridden by subclass'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A function that writes authentication commands to a specific connection
|
||||
* @callback SendAuthCommand
|
||||
* @param {Connection} connection The connection to write to
|
||||
* @param {Command} command A command with a toBin method that can be written to a connection
|
||||
* @param {AuthWriteCallback} callback Callback called when command response is received
|
||||
*/
|
||||
|
||||
/**
|
||||
* A callback for a specific auth command
|
||||
* @callback AuthWriteCallback
|
||||
* @param {Error} err If command failed, an error from the server
|
||||
* @param {object} r The response from the server
|
||||
*/
|
||||
|
||||
/**
|
||||
* This is a result from an authentication strategy
|
||||
* This is a result from an authentication provider
|
||||
*
|
||||
* @callback authResultCallback
|
||||
* @param {error} error An error object. Set to null if no error present
|
||||
* @param {boolean} result The result of the authentication process
|
||||
*/
|
||||
|
||||
module.exports = { AuthProvider };
|
||||
module.exports = { AuthContext, AuthProvider };
|
||||
|
||||
4
node_modules/mongodb/lib/core/auth/defaultAuthProviders.js
generated
vendored
4
node_modules/mongodb/lib/core/auth/defaultAuthProviders.js
generated
vendored
@@ -4,9 +4,9 @@ const MongoCR = require('./mongocr');
|
||||
const X509 = require('./x509');
|
||||
const Plain = require('./plain');
|
||||
const GSSAPI = require('./gssapi');
|
||||
const SSPI = require('./sspi');
|
||||
const ScramSHA1 = require('./scram').ScramSHA1;
|
||||
const ScramSHA256 = require('./scram').ScramSHA256;
|
||||
const MongoDBAWS = require('./mongodb_aws');
|
||||
|
||||
/**
|
||||
* Returns the default authentication providers.
|
||||
@@ -16,11 +16,11 @@ const ScramSHA256 = require('./scram').ScramSHA256;
|
||||
*/
|
||||
function defaultAuthProviders(bson) {
|
||||
return {
|
||||
'mongodb-aws': new MongoDBAWS(bson),
|
||||
mongocr: new MongoCR(bson),
|
||||
x509: new X509(bson),
|
||||
plain: new Plain(bson),
|
||||
gssapi: new GSSAPI(bson),
|
||||
sspi: new SSPI(bson),
|
||||
'scram-sha-1': new ScramSHA1(bson),
|
||||
'scram-sha-256': new ScramSHA256(bson)
|
||||
};
|
||||
|
||||
346
node_modules/mongodb/lib/core/auth/gssapi.js
generated
vendored
346
node_modules/mongodb/lib/core/auth/gssapi.js
generated
vendored
@@ -1,241 +1,151 @@
|
||||
'use strict';
|
||||
const dns = require('dns');
|
||||
|
||||
const AuthProvider = require('./auth_provider').AuthProvider;
|
||||
const retrieveKerberos = require('../utils').retrieveKerberos;
|
||||
const MongoError = require('../error').MongoError;
|
||||
|
||||
let kerberos;
|
||||
|
||||
/**
|
||||
* Creates a new GSSAPI authentication mechanism
|
||||
* @class
|
||||
* @extends AuthProvider
|
||||
*/
|
||||
class GSSAPI extends AuthProvider {
|
||||
/**
|
||||
* Implementation of authentication for a single connection
|
||||
* @override
|
||||
*/
|
||||
_authenticateSingleConnection(sendAuthCommand, connection, credentials, callback) {
|
||||
const source = credentials.source;
|
||||
auth(authContext, callback) {
|
||||
const connection = authContext.connection;
|
||||
const credentials = authContext.credentials;
|
||||
if (credentials == null) return callback(new MongoError('credentials required'));
|
||||
const username = credentials.username;
|
||||
const password = credentials.password;
|
||||
const mechanismProperties = credentials.mechanismProperties;
|
||||
const gssapiServiceName =
|
||||
mechanismProperties['gssapiservicename'] ||
|
||||
mechanismProperties['gssapiServiceName'] ||
|
||||
'mongodb';
|
||||
|
||||
GSSAPIInitialize(
|
||||
this,
|
||||
kerberos.processes.MongoAuthProcess,
|
||||
source,
|
||||
username,
|
||||
password,
|
||||
source,
|
||||
gssapiServiceName,
|
||||
sendAuthCommand,
|
||||
connection,
|
||||
mechanismProperties,
|
||||
callback
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate
|
||||
* @override
|
||||
* @method
|
||||
*/
|
||||
auth(sendAuthCommand, connections, credentials, callback) {
|
||||
if (kerberos == null) {
|
||||
try {
|
||||
kerberos = retrieveKerberos();
|
||||
} catch (e) {
|
||||
return callback(e, null);
|
||||
}
|
||||
function externalCommand(command, cb) {
|
||||
return connection.command('$external.$cmd', command, cb);
|
||||
}
|
||||
|
||||
super.auth(sendAuthCommand, connections, credentials, callback);
|
||||
makeKerberosClient(authContext, (err, client) => {
|
||||
if (err) return callback(err);
|
||||
if (client == null) return callback(new MongoError('gssapi client missing'));
|
||||
client.step('', (err, payload) => {
|
||||
if (err) return callback(err);
|
||||
externalCommand(saslStart(payload), (err, response) => {
|
||||
if (err) return callback(err);
|
||||
const result = response.result;
|
||||
negotiate(client, 10, result.payload, (err, payload) => {
|
||||
if (err) return callback(err);
|
||||
externalCommand(saslContinue(payload, result.conversationId), (err, response) => {
|
||||
if (err) return callback(err);
|
||||
const result = response.result;
|
||||
finalize(client, username, result.payload, (err, payload) => {
|
||||
if (err) return callback(err);
|
||||
externalCommand(
|
||||
{
|
||||
saslContinue: 1,
|
||||
conversationId: result.conversationId,
|
||||
payload
|
||||
},
|
||||
(err, result) => {
|
||||
if (err) return callback(err);
|
||||
callback(undefined, result);
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
module.exports = GSSAPI;
|
||||
|
||||
//
|
||||
// Initialize step
|
||||
var GSSAPIInitialize = function(
|
||||
self,
|
||||
MongoAuthProcess,
|
||||
db,
|
||||
username,
|
||||
password,
|
||||
authdb,
|
||||
gssapiServiceName,
|
||||
sendAuthCommand,
|
||||
connection,
|
||||
options,
|
||||
callback
|
||||
) {
|
||||
// Create authenticator
|
||||
var mongo_auth_process = new MongoAuthProcess(
|
||||
connection.host,
|
||||
connection.port,
|
||||
gssapiServiceName,
|
||||
options
|
||||
);
|
||||
|
||||
// Perform initialization
|
||||
mongo_auth_process.init(username, password, function(err) {
|
||||
if (err) return callback(err, false);
|
||||
|
||||
// Perform the first step
|
||||
mongo_auth_process.transition('', function(err, payload) {
|
||||
if (err) return callback(err, false);
|
||||
|
||||
// Call the next db step
|
||||
MongoDBGSSAPIFirstStep(
|
||||
self,
|
||||
mongo_auth_process,
|
||||
payload,
|
||||
db,
|
||||
username,
|
||||
password,
|
||||
authdb,
|
||||
sendAuthCommand,
|
||||
connection,
|
||||
callback
|
||||
);
|
||||
});
|
||||
function makeKerberosClient(authContext, callback) {
|
||||
const host = authContext.options.host;
|
||||
const port = authContext.options.port;
|
||||
const credentials = authContext.credentials;
|
||||
if (!host || !port || !credentials) {
|
||||
return callback(
|
||||
new MongoError(
|
||||
`Connection must specify: ${host ? 'host' : ''}, ${port ? 'port' : ''}, ${
|
||||
credentials ? 'host' : 'credentials'
|
||||
}.`
|
||||
)
|
||||
);
|
||||
}
|
||||
if (kerberos == null) {
|
||||
try {
|
||||
kerberos = retrieveKerberos();
|
||||
} catch (e) {
|
||||
return callback(e);
|
||||
}
|
||||
}
|
||||
const username = credentials.username;
|
||||
const password = credentials.password;
|
||||
const mechanismProperties = credentials.mechanismProperties;
|
||||
const serviceName =
|
||||
mechanismProperties['gssapiservicename'] ||
|
||||
mechanismProperties['gssapiServiceName'] ||
|
||||
'mongodb';
|
||||
performGssapiCanonicalizeHostName(host, mechanismProperties, (err, host) => {
|
||||
if (err) return callback(err);
|
||||
const initOptions = {};
|
||||
if (password != null) {
|
||||
Object.assign(initOptions, { user: username, password: password });
|
||||
}
|
||||
kerberos.initializeClient(
|
||||
`${serviceName}${process.platform === 'win32' ? '/' : '@'}${host}`,
|
||||
initOptions,
|
||||
(err, client) => {
|
||||
if (err) return callback(new MongoError(err));
|
||||
callback(null, client);
|
||||
}
|
||||
);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
//
|
||||
// Perform first step against mongodb
|
||||
var MongoDBGSSAPIFirstStep = function(
|
||||
self,
|
||||
mongo_auth_process,
|
||||
payload,
|
||||
db,
|
||||
username,
|
||||
password,
|
||||
authdb,
|
||||
sendAuthCommand,
|
||||
connection,
|
||||
callback
|
||||
) {
|
||||
// Build the sasl start command
|
||||
var command = {
|
||||
function saslStart(payload) {
|
||||
return {
|
||||
saslStart: 1,
|
||||
mechanism: 'GSSAPI',
|
||||
payload: payload,
|
||||
payload,
|
||||
autoAuthorize: 1
|
||||
};
|
||||
|
||||
// Write the commmand on the connection
|
||||
sendAuthCommand(connection, '$external.$cmd', command, (err, doc) => {
|
||||
if (err) return callback(err, false);
|
||||
// Execute mongodb transition
|
||||
mongo_auth_process.transition(doc.payload, function(err, payload) {
|
||||
if (err) return callback(err, false);
|
||||
|
||||
// MongoDB API Second Step
|
||||
MongoDBGSSAPISecondStep(
|
||||
self,
|
||||
mongo_auth_process,
|
||||
payload,
|
||||
doc,
|
||||
db,
|
||||
username,
|
||||
password,
|
||||
authdb,
|
||||
sendAuthCommand,
|
||||
connection,
|
||||
callback
|
||||
);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
//
|
||||
// Perform first step against mongodb
|
||||
var MongoDBGSSAPISecondStep = function(
|
||||
self,
|
||||
mongo_auth_process,
|
||||
payload,
|
||||
doc,
|
||||
db,
|
||||
username,
|
||||
password,
|
||||
authdb,
|
||||
sendAuthCommand,
|
||||
connection,
|
||||
callback
|
||||
) {
|
||||
// Build Authentication command to send to MongoDB
|
||||
var command = {
|
||||
}
|
||||
function saslContinue(payload, conversationId) {
|
||||
return {
|
||||
saslContinue: 1,
|
||||
conversationId: doc.conversationId,
|
||||
payload: payload
|
||||
conversationId,
|
||||
payload
|
||||
};
|
||||
|
||||
// Execute the command
|
||||
// Write the commmand on the connection
|
||||
sendAuthCommand(connection, '$external.$cmd', command, (err, doc) => {
|
||||
if (err) return callback(err, false);
|
||||
// Call next transition for kerberos
|
||||
mongo_auth_process.transition(doc.payload, function(err, payload) {
|
||||
if (err) return callback(err, false);
|
||||
|
||||
// Call the last and third step
|
||||
MongoDBGSSAPIThirdStep(
|
||||
self,
|
||||
mongo_auth_process,
|
||||
payload,
|
||||
doc,
|
||||
db,
|
||||
username,
|
||||
password,
|
||||
authdb,
|
||||
sendAuthCommand,
|
||||
connection,
|
||||
callback
|
||||
);
|
||||
}
|
||||
function negotiate(client, retries, payload, callback) {
|
||||
client.step(payload, (err, response) => {
|
||||
// Retries exhausted, raise error
|
||||
if (err && retries === 0) return callback(err);
|
||||
// Adjust number of retries and call step again
|
||||
if (err) return negotiate(client, retries - 1, payload, callback);
|
||||
// Return the payload
|
||||
callback(undefined, response || '');
|
||||
});
|
||||
}
|
||||
function finalize(client, user, payload, callback) {
|
||||
// GSS Client Unwrap
|
||||
client.unwrap(payload, (err, response) => {
|
||||
if (err) return callback(err);
|
||||
// Wrap the response
|
||||
client.wrap(response || '', { user }, (err, wrapped) => {
|
||||
if (err) return callback(err);
|
||||
// Return the payload
|
||||
callback(undefined, wrapped);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
var MongoDBGSSAPIThirdStep = function(
|
||||
self,
|
||||
mongo_auth_process,
|
||||
payload,
|
||||
doc,
|
||||
db,
|
||||
username,
|
||||
password,
|
||||
authdb,
|
||||
sendAuthCommand,
|
||||
connection,
|
||||
callback
|
||||
) {
|
||||
// Build final command
|
||||
var command = {
|
||||
saslContinue: 1,
|
||||
conversationId: doc.conversationId,
|
||||
payload: payload
|
||||
};
|
||||
|
||||
// Execute the command
|
||||
sendAuthCommand(connection, '$external.$cmd', command, (err, r) => {
|
||||
if (err) return callback(err, false);
|
||||
mongo_auth_process.transition(null, function(err) {
|
||||
if (err) return callback(err, null);
|
||||
callback(null, r);
|
||||
});
|
||||
}
|
||||
function performGssapiCanonicalizeHostName(host, mechanismProperties, callback) {
|
||||
const canonicalizeHostName =
|
||||
typeof mechanismProperties.gssapiCanonicalizeHostName === 'boolean'
|
||||
? mechanismProperties.gssapiCanonicalizeHostName
|
||||
: false;
|
||||
if (!canonicalizeHostName) return callback(undefined, host);
|
||||
// Attempt to resolve the host name
|
||||
dns.resolveCname(host, (err, r) => {
|
||||
if (err) return callback(err);
|
||||
// Get the first resolve host id
|
||||
if (Array.isArray(r) && r.length > 0) {
|
||||
return callback(undefined, r[0]);
|
||||
}
|
||||
callback(undefined, host);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* This is a result from a authentication strategy
|
||||
*
|
||||
* @callback authResultCallback
|
||||
* @param {error} error An error object. Set to null if no error present
|
||||
* @param {boolean} result The result of the authentication process
|
||||
*/
|
||||
|
||||
module.exports = GSSAPI;
|
||||
}
|
||||
|
||||
35
node_modules/mongodb/lib/core/auth/mongo_credentials.js
generated
vendored
35
node_modules/mongodb/lib/core/auth/mongo_credentials.js
generated
vendored
@@ -47,7 +47,27 @@ class MongoCredentials {
|
||||
this.password = options.password;
|
||||
this.source = options.source || options.db;
|
||||
this.mechanism = options.mechanism || 'default';
|
||||
this.mechanismProperties = options.mechanismProperties;
|
||||
this.mechanismProperties = options.mechanismProperties || {};
|
||||
|
||||
if (/MONGODB-AWS/i.test(this.mechanism)) {
|
||||
if (!this.username && process.env.AWS_ACCESS_KEY_ID) {
|
||||
this.username = process.env.AWS_ACCESS_KEY_ID;
|
||||
}
|
||||
|
||||
if (!this.password && process.env.AWS_SECRET_ACCESS_KEY) {
|
||||
this.password = process.env.AWS_SECRET_ACCESS_KEY;
|
||||
}
|
||||
|
||||
if (
|
||||
this.mechanismProperties.AWS_SESSION_TOKEN == null &&
|
||||
process.env.AWS_SESSION_TOKEN != null
|
||||
) {
|
||||
this.mechanismProperties.AWS_SESSION_TOKEN = process.env.AWS_SESSION_TOKEN;
|
||||
}
|
||||
}
|
||||
|
||||
Object.freeze(this.mechanismProperties);
|
||||
Object.freeze(this);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -69,12 +89,21 @@ class MongoCredentials {
|
||||
* based on the server version and server supported sasl mechanisms.
|
||||
*
|
||||
* @param {Object} [ismaster] An ismaster response from the server
|
||||
* @returns {MongoCredentials}
|
||||
*/
|
||||
resolveAuthMechanism(ismaster) {
|
||||
// If the mechanism is not "default", then it does not need to be resolved
|
||||
if (this.mechanism.toLowerCase() === 'default') {
|
||||
this.mechanism = getDefaultAuthMechanism(ismaster);
|
||||
if (/DEFAULT/i.test(this.mechanism)) {
|
||||
return new MongoCredentials({
|
||||
username: this.username,
|
||||
password: this.password,
|
||||
source: this.source,
|
||||
mechanism: getDefaultAuthMechanism(ismaster),
|
||||
mechanismProperties: this.mechanismProperties
|
||||
});
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
18
node_modules/mongodb/lib/core/auth/mongocr.js
generated
vendored
18
node_modules/mongodb/lib/core/auth/mongocr.js
generated
vendored
@@ -3,27 +3,21 @@
|
||||
const crypto = require('crypto');
|
||||
const AuthProvider = require('./auth_provider').AuthProvider;
|
||||
|
||||
/**
|
||||
* Creates a new MongoCR authentication mechanism
|
||||
*
|
||||
* @extends AuthProvider
|
||||
*/
|
||||
class MongoCR extends AuthProvider {
|
||||
/**
|
||||
* Implementation of authentication for a single connection
|
||||
* @override
|
||||
*/
|
||||
_authenticateSingleConnection(sendAuthCommand, connection, credentials, callback) {
|
||||
auth(authContext, callback) {
|
||||
const connection = authContext.connection;
|
||||
const credentials = authContext.credentials;
|
||||
const username = credentials.username;
|
||||
const password = credentials.password;
|
||||
const source = credentials.source;
|
||||
|
||||
sendAuthCommand(connection, `${source}.$cmd`, { getnonce: 1 }, (err, r) => {
|
||||
connection.command(`${source}.$cmd`, { getnonce: 1 }, (err, result) => {
|
||||
let nonce = null;
|
||||
let key = null;
|
||||
|
||||
// Get nonce
|
||||
if (err == null) {
|
||||
const r = result.result;
|
||||
nonce = r.nonce;
|
||||
// Use node md5 generator
|
||||
let md5 = crypto.createHash('md5');
|
||||
@@ -43,7 +37,7 @@ class MongoCR extends AuthProvider {
|
||||
key
|
||||
};
|
||||
|
||||
sendAuthCommand(connection, `${source}.$cmd`, authenticateCommand, callback);
|
||||
connection.command(`${source}.$cmd`, authenticateCommand, callback);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
17
node_modules/mongodb/lib/core/auth/plain.js
generated
vendored
17
node_modules/mongodb/lib/core/auth/plain.js
generated
vendored
@@ -1,5 +1,4 @@
|
||||
'use strict';
|
||||
|
||||
const retrieveBSON = require('../connection/utils').retrieveBSON;
|
||||
const AuthProvider = require('./auth_provider').AuthProvider;
|
||||
|
||||
@@ -7,19 +6,13 @@ const AuthProvider = require('./auth_provider').AuthProvider;
|
||||
const BSON = retrieveBSON();
|
||||
const Binary = BSON.Binary;
|
||||
|
||||
/**
|
||||
* Creates a new Plain authentication mechanism
|
||||
*
|
||||
* @extends AuthProvider
|
||||
*/
|
||||
class Plain extends AuthProvider {
|
||||
/**
|
||||
* Implementation of authentication for a single connection
|
||||
* @override
|
||||
*/
|
||||
_authenticateSingleConnection(sendAuthCommand, connection, credentials, callback) {
|
||||
auth(authContext, callback) {
|
||||
const connection = authContext.connection;
|
||||
const credentials = authContext.credentials;
|
||||
const username = credentials.username;
|
||||
const password = credentials.password;
|
||||
|
||||
const payload = new Binary(`\x00${username}\x00${password}`);
|
||||
const command = {
|
||||
saslStart: 1,
|
||||
@@ -28,7 +21,7 @@ class Plain extends AuthProvider {
|
||||
autoAuthorize: 1
|
||||
};
|
||||
|
||||
sendAuthCommand(connection, '$external.$cmd', command, callback);
|
||||
connection.command('$external.$cmd', command, callback);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
443
node_modules/mongodb/lib/core/auth/scram.js
generated
vendored
443
node_modules/mongodb/lib/core/auth/scram.js
generated
vendored
@@ -1,47 +1,252 @@
|
||||
'use strict';
|
||||
|
||||
const crypto = require('crypto');
|
||||
const Buffer = require('safe-buffer').Buffer;
|
||||
const retrieveBSON = require('../connection/utils').retrieveBSON;
|
||||
const MongoError = require('../error').MongoError;
|
||||
const AuthProvider = require('./auth_provider').AuthProvider;
|
||||
const emitWarningOnce = require('../../utils').emitWarning;
|
||||
|
||||
const BSON = retrieveBSON();
|
||||
const Binary = BSON.Binary;
|
||||
|
||||
let saslprep;
|
||||
try {
|
||||
// Ensure you always wrap an optional require in the try block NODE-3199
|
||||
saslprep = require('saslprep');
|
||||
} catch (e) {
|
||||
// don't do anything;
|
||||
}
|
||||
|
||||
var parsePayload = function(payload) {
|
||||
var dict = {};
|
||||
var parts = payload.split(',');
|
||||
for (var i = 0; i < parts.length; i++) {
|
||||
var valueParts = parts[i].split('=');
|
||||
class ScramSHA extends AuthProvider {
|
||||
constructor(bson, cryptoMethod) {
|
||||
super(bson);
|
||||
this.cryptoMethod = cryptoMethod || 'sha1';
|
||||
}
|
||||
|
||||
prepare(handshakeDoc, authContext, callback) {
|
||||
const cryptoMethod = this.cryptoMethod;
|
||||
if (cryptoMethod === 'sha256' && saslprep == null) {
|
||||
emitWarningOnce('Warning: no saslprep library specified. Passwords will not be sanitized');
|
||||
}
|
||||
|
||||
crypto.randomBytes(24, (err, nonce) => {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
// store the nonce for later use
|
||||
Object.assign(authContext, { nonce });
|
||||
|
||||
const credentials = authContext.credentials;
|
||||
const request = Object.assign({}, handshakeDoc, {
|
||||
speculativeAuthenticate: Object.assign(makeFirstMessage(cryptoMethod, credentials, nonce), {
|
||||
db: credentials.source
|
||||
})
|
||||
});
|
||||
|
||||
callback(undefined, request);
|
||||
});
|
||||
}
|
||||
|
||||
auth(authContext, callback) {
|
||||
const response = authContext.response;
|
||||
if (response && response.speculativeAuthenticate) {
|
||||
continueScramConversation(
|
||||
this.cryptoMethod,
|
||||
response.speculativeAuthenticate,
|
||||
authContext,
|
||||
callback
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
executeScram(this.cryptoMethod, authContext, callback);
|
||||
}
|
||||
}
|
||||
|
||||
function cleanUsername(username) {
|
||||
return username.replace('=', '=3D').replace(',', '=2C');
|
||||
}
|
||||
|
||||
function clientFirstMessageBare(username, nonce) {
|
||||
// NOTE: This is done b/c Javascript uses UTF-16, but the server is hashing in UTF-8.
|
||||
// Since the username is not sasl-prep-d, we need to do this here.
|
||||
return Buffer.concat([
|
||||
Buffer.from('n=', 'utf8'),
|
||||
Buffer.from(username, 'utf8'),
|
||||
Buffer.from(',r=', 'utf8'),
|
||||
Buffer.from(nonce.toString('base64'), 'utf8')
|
||||
]);
|
||||
}
|
||||
|
||||
function makeFirstMessage(cryptoMethod, credentials, nonce) {
|
||||
const username = cleanUsername(credentials.username);
|
||||
const mechanism = cryptoMethod === 'sha1' ? 'SCRAM-SHA-1' : 'SCRAM-SHA-256';
|
||||
|
||||
// NOTE: This is done b/c Javascript uses UTF-16, but the server is hashing in UTF-8.
|
||||
// Since the username is not sasl-prep-d, we need to do this here.
|
||||
return {
|
||||
saslStart: 1,
|
||||
mechanism,
|
||||
payload: new Binary(
|
||||
Buffer.concat([Buffer.from('n,,', 'utf8'), clientFirstMessageBare(username, nonce)])
|
||||
),
|
||||
autoAuthorize: 1,
|
||||
options: { skipEmptyExchange: true }
|
||||
};
|
||||
}
|
||||
|
||||
function executeScram(cryptoMethod, authContext, callback) {
|
||||
const connection = authContext.connection;
|
||||
const credentials = authContext.credentials;
|
||||
const nonce = authContext.nonce;
|
||||
const db = credentials.source;
|
||||
|
||||
const saslStartCmd = makeFirstMessage(cryptoMethod, credentials, nonce);
|
||||
connection.command(`${db}.$cmd`, saslStartCmd, (_err, result) => {
|
||||
const err = resolveError(_err, result);
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
continueScramConversation(cryptoMethod, result.result, authContext, callback);
|
||||
});
|
||||
}
|
||||
|
||||
function continueScramConversation(cryptoMethod, response, authContext, callback) {
|
||||
const connection = authContext.connection;
|
||||
const credentials = authContext.credentials;
|
||||
const nonce = authContext.nonce;
|
||||
|
||||
const db = credentials.source;
|
||||
const username = cleanUsername(credentials.username);
|
||||
const password = credentials.password;
|
||||
|
||||
let processedPassword;
|
||||
if (cryptoMethod === 'sha256') {
|
||||
processedPassword = saslprep ? saslprep(password) : password;
|
||||
} else {
|
||||
try {
|
||||
processedPassword = passwordDigest(username, password);
|
||||
} catch (e) {
|
||||
return callback(e);
|
||||
}
|
||||
}
|
||||
|
||||
const payload = Buffer.isBuffer(response.payload)
|
||||
? new Binary(response.payload)
|
||||
: response.payload;
|
||||
const dict = parsePayload(payload.value());
|
||||
|
||||
const iterations = parseInt(dict.i, 10);
|
||||
if (iterations && iterations < 4096) {
|
||||
callback(new MongoError(`Server returned an invalid iteration count ${iterations}`), false);
|
||||
return;
|
||||
}
|
||||
|
||||
const salt = dict.s;
|
||||
const rnonce = dict.r;
|
||||
if (rnonce.startsWith('nonce')) {
|
||||
callback(new MongoError(`Server returned an invalid nonce: ${rnonce}`), false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Set up start of proof
|
||||
const withoutProof = `c=biws,r=${rnonce}`;
|
||||
const saltedPassword = HI(
|
||||
processedPassword,
|
||||
Buffer.from(salt, 'base64'),
|
||||
iterations,
|
||||
cryptoMethod
|
||||
);
|
||||
|
||||
const clientKey = HMAC(cryptoMethod, saltedPassword, 'Client Key');
|
||||
const serverKey = HMAC(cryptoMethod, saltedPassword, 'Server Key');
|
||||
const storedKey = H(cryptoMethod, clientKey);
|
||||
const authMessage = [
|
||||
clientFirstMessageBare(username, nonce),
|
||||
payload.value().toString('base64'),
|
||||
withoutProof
|
||||
].join(',');
|
||||
|
||||
const clientSignature = HMAC(cryptoMethod, storedKey, authMessage);
|
||||
const clientProof = `p=${xor(clientKey, clientSignature)}`;
|
||||
const clientFinal = [withoutProof, clientProof].join(',');
|
||||
|
||||
const serverSignature = HMAC(cryptoMethod, serverKey, authMessage);
|
||||
const saslContinueCmd = {
|
||||
saslContinue: 1,
|
||||
conversationId: response.conversationId,
|
||||
payload: new Binary(Buffer.from(clientFinal))
|
||||
};
|
||||
|
||||
connection.command(`${db}.$cmd`, saslContinueCmd, (_err, result) => {
|
||||
const err = resolveError(_err, result);
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
const r = result.result;
|
||||
const parsedResponse = parsePayload(r.payload.value());
|
||||
if (!compareDigest(Buffer.from(parsedResponse.v, 'base64'), serverSignature)) {
|
||||
callback(new MongoError('Server returned an invalid signature'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!r || r.done !== false) {
|
||||
return callback(err, r);
|
||||
}
|
||||
|
||||
const retrySaslContinueCmd = {
|
||||
saslContinue: 1,
|
||||
conversationId: r.conversationId,
|
||||
payload: Buffer.alloc(0)
|
||||
};
|
||||
|
||||
connection.command(`${db}.$cmd`, retrySaslContinueCmd, callback);
|
||||
});
|
||||
}
|
||||
|
||||
function parsePayload(payload) {
|
||||
const dict = {};
|
||||
const parts = payload.split(',');
|
||||
for (let i = 0; i < parts.length; i++) {
|
||||
const valueParts = parts[i].split('=');
|
||||
dict[valueParts[0]] = valueParts[1];
|
||||
}
|
||||
|
||||
return dict;
|
||||
};
|
||||
}
|
||||
|
||||
var passwordDigest = function(username, password) {
|
||||
if (typeof username !== 'string') throw new MongoError('username must be a string');
|
||||
if (typeof password !== 'string') throw new MongoError('password must be a string');
|
||||
if (password.length === 0) throw new MongoError('password cannot be empty');
|
||||
// Use node md5 generator
|
||||
var md5 = crypto.createHash('md5');
|
||||
// Generate keys used for authentication
|
||||
md5.update(username + ':mongo:' + password, 'utf8');
|
||||
function passwordDigest(username, password) {
|
||||
if (typeof username !== 'string') {
|
||||
throw new MongoError('username must be a string');
|
||||
}
|
||||
|
||||
if (typeof password !== 'string') {
|
||||
throw new MongoError('password must be a string');
|
||||
}
|
||||
|
||||
if (password.length === 0) {
|
||||
throw new MongoError('password cannot be empty');
|
||||
}
|
||||
|
||||
const md5 = crypto.createHash('md5');
|
||||
md5.update(`${username}:mongo:${password}`, 'utf8');
|
||||
return md5.digest('hex');
|
||||
};
|
||||
}
|
||||
|
||||
// XOR two buffers
|
||||
function xor(a, b) {
|
||||
if (!Buffer.isBuffer(a)) a = Buffer.from(a);
|
||||
if (!Buffer.isBuffer(b)) b = Buffer.from(b);
|
||||
if (!Buffer.isBuffer(a)) {
|
||||
a = Buffer.from(a);
|
||||
}
|
||||
|
||||
if (!Buffer.isBuffer(b)) {
|
||||
b = Buffer.from(b);
|
||||
}
|
||||
|
||||
const length = Math.max(a.length, b.length);
|
||||
const res = [];
|
||||
|
||||
@@ -66,12 +271,12 @@ function HMAC(method, key, text) {
|
||||
.digest();
|
||||
}
|
||||
|
||||
var _hiCache = {};
|
||||
var _hiCacheCount = 0;
|
||||
var _hiCachePurge = function() {
|
||||
let _hiCache = {};
|
||||
let _hiCacheCount = 0;
|
||||
function _hiCachePurge() {
|
||||
_hiCache = {};
|
||||
_hiCacheCount = 0;
|
||||
};
|
||||
}
|
||||
|
||||
const hiLengthMap = {
|
||||
sha256: 32,
|
||||
@@ -121,205 +326,19 @@ function compareDigest(lhs, rhs) {
|
||||
return result === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new ScramSHA authentication mechanism
|
||||
* @class
|
||||
* @extends AuthProvider
|
||||
*/
|
||||
class ScramSHA extends AuthProvider {
|
||||
constructor(bson, cryptoMethod) {
|
||||
super(bson);
|
||||
this.cryptoMethod = cryptoMethod || 'sha1';
|
||||
}
|
||||
function resolveError(err, result) {
|
||||
if (err) return err;
|
||||
|
||||
static _getError(err, r) {
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
if (r.$err || r.errmsg) {
|
||||
return new MongoError(r);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
_executeScram(sendAuthCommand, connection, credentials, nonce, callback) {
|
||||
let username = credentials.username;
|
||||
const password = credentials.password;
|
||||
const db = credentials.source;
|
||||
|
||||
const cryptoMethod = this.cryptoMethod;
|
||||
let mechanism = 'SCRAM-SHA-1';
|
||||
let processedPassword;
|
||||
|
||||
if (cryptoMethod === 'sha256') {
|
||||
mechanism = 'SCRAM-SHA-256';
|
||||
|
||||
processedPassword = saslprep ? saslprep(password) : password;
|
||||
} else {
|
||||
try {
|
||||
processedPassword = passwordDigest(username, password);
|
||||
} catch (e) {
|
||||
return callback(e);
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up the user
|
||||
username = username.replace('=', '=3D').replace(',', '=2C');
|
||||
|
||||
// NOTE: This is done b/c Javascript uses UTF-16, but the server is hashing in UTF-8.
|
||||
// Since the username is not sasl-prep-d, we need to do this here.
|
||||
const firstBare = Buffer.concat([
|
||||
Buffer.from('n=', 'utf8'),
|
||||
Buffer.from(username, 'utf8'),
|
||||
Buffer.from(',r=', 'utf8'),
|
||||
Buffer.from(nonce, 'utf8')
|
||||
]);
|
||||
|
||||
// Build command structure
|
||||
const saslStartCmd = {
|
||||
saslStart: 1,
|
||||
mechanism,
|
||||
payload: new Binary(Buffer.concat([Buffer.from('n,,', 'utf8'), firstBare])),
|
||||
autoAuthorize: 1
|
||||
};
|
||||
|
||||
// Write the commmand on the connection
|
||||
sendAuthCommand(connection, `${db}.$cmd`, saslStartCmd, (err, r) => {
|
||||
let tmpError = ScramSHA._getError(err, r);
|
||||
if (tmpError) {
|
||||
return callback(tmpError, null);
|
||||
}
|
||||
|
||||
const payload = Buffer.isBuffer(r.payload) ? new Binary(r.payload) : r.payload;
|
||||
const dict = parsePayload(payload.value());
|
||||
|
||||
const iterations = parseInt(dict.i, 10);
|
||||
if (iterations && iterations < 4096) {
|
||||
callback(new MongoError(`Server returned an invalid iteration count ${iterations}`), false);
|
||||
return;
|
||||
}
|
||||
|
||||
const salt = dict.s;
|
||||
const rnonce = dict.r;
|
||||
if (rnonce.startsWith('nonce')) {
|
||||
callback(new MongoError(`Server returned an invalid nonce: ${rnonce}`), false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Set up start of proof
|
||||
const withoutProof = `c=biws,r=${rnonce}`;
|
||||
const saltedPassword = HI(
|
||||
processedPassword,
|
||||
Buffer.from(salt, 'base64'),
|
||||
iterations,
|
||||
cryptoMethod
|
||||
);
|
||||
|
||||
const clientKey = HMAC(cryptoMethod, saltedPassword, 'Client Key');
|
||||
const serverKey = HMAC(cryptoMethod, saltedPassword, 'Server Key');
|
||||
const storedKey = H(cryptoMethod, clientKey);
|
||||
const authMessage = [firstBare, payload.value().toString('base64'), withoutProof].join(',');
|
||||
|
||||
const clientSignature = HMAC(cryptoMethod, storedKey, authMessage);
|
||||
const clientProof = `p=${xor(clientKey, clientSignature)}`;
|
||||
const clientFinal = [withoutProof, clientProof].join(',');
|
||||
|
||||
const serverSignature = HMAC(cryptoMethod, serverKey, authMessage);
|
||||
|
||||
const saslContinueCmd = {
|
||||
saslContinue: 1,
|
||||
conversationId: r.conversationId,
|
||||
payload: new Binary(Buffer.from(clientFinal))
|
||||
};
|
||||
|
||||
sendAuthCommand(connection, `${db}.$cmd`, saslContinueCmd, (err, r) => {
|
||||
if (err || (r && typeof r.ok === 'number' && r.ok === 0)) {
|
||||
callback(err, r);
|
||||
return;
|
||||
}
|
||||
|
||||
const parsedResponse = parsePayload(r.payload.value());
|
||||
if (!compareDigest(Buffer.from(parsedResponse.v, 'base64'), serverSignature)) {
|
||||
callback(new MongoError('Server returned an invalid signature'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!r || r.done !== false) {
|
||||
return callback(err, r);
|
||||
}
|
||||
|
||||
const retrySaslContinueCmd = {
|
||||
saslContinue: 1,
|
||||
conversationId: r.conversationId,
|
||||
payload: Buffer.alloc(0)
|
||||
};
|
||||
|
||||
sendAuthCommand(connection, `${db}.$cmd`, retrySaslContinueCmd, callback);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of authentication for a single connection
|
||||
* @override
|
||||
*/
|
||||
_authenticateSingleConnection(sendAuthCommand, connection, credentials, callback) {
|
||||
// Create a random nonce
|
||||
crypto.randomBytes(24, (err, buff) => {
|
||||
if (err) {
|
||||
return callback(err, null);
|
||||
}
|
||||
|
||||
return this._executeScram(
|
||||
sendAuthCommand,
|
||||
connection,
|
||||
credentials,
|
||||
buff.toString('base64'),
|
||||
callback
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate
|
||||
* @override
|
||||
* @method
|
||||
*/
|
||||
auth(sendAuthCommand, connections, credentials, callback) {
|
||||
this._checkSaslprep();
|
||||
super.auth(sendAuthCommand, connections, credentials, callback);
|
||||
}
|
||||
|
||||
_checkSaslprep() {
|
||||
const cryptoMethod = this.cryptoMethod;
|
||||
|
||||
if (cryptoMethod === 'sha256') {
|
||||
if (!saslprep) {
|
||||
console.warn('Warning: no saslprep library specified. Passwords will not be sanitized');
|
||||
}
|
||||
}
|
||||
}
|
||||
const r = result.result;
|
||||
if (r.$err || r.errmsg) return new MongoError(r);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new ScramSHA1 authentication mechanism
|
||||
* @class
|
||||
* @extends ScramSHA
|
||||
*/
|
||||
class ScramSHA1 extends ScramSHA {
|
||||
constructor(bson) {
|
||||
super(bson, 'sha1');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new ScramSHA256 authentication mechanism
|
||||
* @class
|
||||
* @extends ScramSHA
|
||||
*/
|
||||
class ScramSHA256 extends ScramSHA {
|
||||
constructor(bson) {
|
||||
super(bson, 'sha256');
|
||||
|
||||
131
node_modules/mongodb/lib/core/auth/sspi.js
generated
vendored
131
node_modules/mongodb/lib/core/auth/sspi.js
generated
vendored
@@ -1,131 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const AuthProvider = require('./auth_provider').AuthProvider;
|
||||
const retrieveKerberos = require('../utils').retrieveKerberos;
|
||||
let kerberos;
|
||||
|
||||
/**
|
||||
* Creates a new SSPI authentication mechanism
|
||||
* @class
|
||||
* @extends AuthProvider
|
||||
*/
|
||||
class SSPI extends AuthProvider {
|
||||
/**
|
||||
* Implementation of authentication for a single connection
|
||||
* @override
|
||||
*/
|
||||
_authenticateSingleConnection(sendAuthCommand, connection, credentials, callback) {
|
||||
// TODO: Destructure this
|
||||
const username = credentials.username;
|
||||
const password = credentials.password;
|
||||
const mechanismProperties = credentials.mechanismProperties;
|
||||
const gssapiServiceName =
|
||||
mechanismProperties['gssapiservicename'] ||
|
||||
mechanismProperties['gssapiServiceName'] ||
|
||||
'mongodb';
|
||||
|
||||
SSIPAuthenticate(
|
||||
this,
|
||||
kerberos.processes.MongoAuthProcess,
|
||||
username,
|
||||
password,
|
||||
gssapiServiceName,
|
||||
sendAuthCommand,
|
||||
connection,
|
||||
mechanismProperties,
|
||||
callback
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate
|
||||
* @override
|
||||
* @method
|
||||
*/
|
||||
auth(sendAuthCommand, connections, credentials, callback) {
|
||||
if (kerberos == null) {
|
||||
try {
|
||||
kerberos = retrieveKerberos();
|
||||
} catch (e) {
|
||||
return callback(e, null);
|
||||
}
|
||||
}
|
||||
|
||||
super.auth(sendAuthCommand, connections, credentials, callback);
|
||||
}
|
||||
}
|
||||
|
||||
function SSIPAuthenticate(
|
||||
self,
|
||||
MongoAuthProcess,
|
||||
username,
|
||||
password,
|
||||
gssapiServiceName,
|
||||
sendAuthCommand,
|
||||
connection,
|
||||
options,
|
||||
callback
|
||||
) {
|
||||
const authProcess = new MongoAuthProcess(
|
||||
connection.host,
|
||||
connection.port,
|
||||
gssapiServiceName,
|
||||
options
|
||||
);
|
||||
|
||||
function authCommand(command, authCb) {
|
||||
sendAuthCommand(connection, '$external.$cmd', command, authCb);
|
||||
}
|
||||
|
||||
authProcess.init(username, password, err => {
|
||||
if (err) return callback(err, false);
|
||||
|
||||
authProcess.transition('', (err, payload) => {
|
||||
if (err) return callback(err, false);
|
||||
|
||||
const command = {
|
||||
saslStart: 1,
|
||||
mechanism: 'GSSAPI',
|
||||
payload,
|
||||
autoAuthorize: 1
|
||||
};
|
||||
|
||||
authCommand(command, (err, doc) => {
|
||||
if (err) return callback(err, false);
|
||||
|
||||
authProcess.transition(doc.payload, (err, payload) => {
|
||||
if (err) return callback(err, false);
|
||||
const command = {
|
||||
saslContinue: 1,
|
||||
conversationId: doc.conversationId,
|
||||
payload
|
||||
};
|
||||
|
||||
authCommand(command, (err, doc) => {
|
||||
if (err) return callback(err, false);
|
||||
|
||||
authProcess.transition(doc.payload, (err, payload) => {
|
||||
if (err) return callback(err, false);
|
||||
const command = {
|
||||
saslContinue: 1,
|
||||
conversationId: doc.conversationId,
|
||||
payload
|
||||
};
|
||||
|
||||
authCommand(command, (err, response) => {
|
||||
if (err) return callback(err, false);
|
||||
|
||||
authProcess.transition(null, err => {
|
||||
if (err) return callback(err, null);
|
||||
callback(null, response);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = SSPI;
|
||||
41
node_modules/mongodb/lib/core/auth/x509.js
generated
vendored
41
node_modules/mongodb/lib/core/auth/x509.js
generated
vendored
@@ -1,26 +1,35 @@
|
||||
'use strict';
|
||||
|
||||
const AuthProvider = require('./auth_provider').AuthProvider;
|
||||
|
||||
/**
|
||||
* Creates a new X509 authentication mechanism
|
||||
* @class
|
||||
* @extends AuthProvider
|
||||
*/
|
||||
class X509 extends AuthProvider {
|
||||
/**
|
||||
* Implementation of authentication for a single connection
|
||||
* @override
|
||||
*/
|
||||
_authenticateSingleConnection(sendAuthCommand, connection, credentials, callback) {
|
||||
const username = credentials.username;
|
||||
const command = { authenticate: 1, mechanism: 'MONGODB-X509' };
|
||||
if (username) {
|
||||
command.user = username;
|
||||
prepare(handshakeDoc, authContext, callback) {
|
||||
const credentials = authContext.credentials;
|
||||
Object.assign(handshakeDoc, {
|
||||
speculativeAuthenticate: x509AuthenticateCommand(credentials)
|
||||
});
|
||||
|
||||
callback(undefined, handshakeDoc);
|
||||
}
|
||||
|
||||
auth(authContext, callback) {
|
||||
const connection = authContext.connection;
|
||||
const credentials = authContext.credentials;
|
||||
const response = authContext.response;
|
||||
if (response.speculativeAuthenticate) {
|
||||
return callback();
|
||||
}
|
||||
|
||||
sendAuthCommand(connection, '$external.$cmd', command, callback);
|
||||
connection.command('$external.$cmd', x509AuthenticateCommand(credentials), callback);
|
||||
}
|
||||
}
|
||||
|
||||
function x509AuthenticateCommand(credentials) {
|
||||
const command = { authenticate: 1, mechanism: 'MONGODB-X509' };
|
||||
if (credentials.username) {
|
||||
Object.assign(command, { user: credentials.username });
|
||||
}
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
module.exports = X509;
|
||||
|
||||
133
node_modules/mongodb/lib/core/connection/apm.js
generated
vendored
133
node_modules/mongodb/lib/core/connection/apm.js
generated
vendored
@@ -1,123 +1,16 @@
|
||||
'use strict';
|
||||
const Msg = require('../connection/msg').Msg;
|
||||
const KillCursor = require('../connection/commands').KillCursor;
|
||||
const GetMore = require('../connection/commands').GetMore;
|
||||
const calculateDurationInMs = require('../../utils').calculateDurationInMs;
|
||||
|
||||
/** Commands that we want to redact because of the sensitive nature of their contents */
|
||||
const SENSITIVE_COMMANDS = new Set([
|
||||
'authenticate',
|
||||
'saslStart',
|
||||
'saslContinue',
|
||||
'getnonce',
|
||||
'createUser',
|
||||
'updateUser',
|
||||
'copydbgetnonce',
|
||||
'copydbsaslstart',
|
||||
'copydb'
|
||||
]);
|
||||
const extractCommand = require('../../command_utils').extractCommand;
|
||||
|
||||
// helper methods
|
||||
const extractCommandName = commandDoc => Object.keys(commandDoc)[0];
|
||||
const namespace = command => command.ns;
|
||||
const databaseName = command => command.ns.split('.')[0];
|
||||
const collectionName = command => command.ns.split('.')[1];
|
||||
const generateConnectionId = pool =>
|
||||
pool.options ? `${pool.options.host}:${pool.options.port}` : pool.address;
|
||||
const maybeRedact = (commandName, result) => (SENSITIVE_COMMANDS.has(commandName) ? {} : result);
|
||||
const isLegacyPool = pool => pool.s && pool.queue;
|
||||
|
||||
const LEGACY_FIND_QUERY_MAP = {
|
||||
$query: 'filter',
|
||||
$orderby: 'sort',
|
||||
$hint: 'hint',
|
||||
$comment: 'comment',
|
||||
$maxScan: 'maxScan',
|
||||
$max: 'max',
|
||||
$min: 'min',
|
||||
$returnKey: 'returnKey',
|
||||
$showDiskLoc: 'showRecordId',
|
||||
$maxTimeMS: 'maxTimeMS',
|
||||
$snapshot: 'snapshot'
|
||||
};
|
||||
|
||||
const LEGACY_FIND_OPTIONS_MAP = {
|
||||
numberToSkip: 'skip',
|
||||
numberToReturn: 'batchSize',
|
||||
returnFieldsSelector: 'projection'
|
||||
};
|
||||
|
||||
const OP_QUERY_KEYS = [
|
||||
'tailable',
|
||||
'oplogReplay',
|
||||
'noCursorTimeout',
|
||||
'awaitData',
|
||||
'partial',
|
||||
'exhaust'
|
||||
];
|
||||
|
||||
/**
|
||||
* Extract the actual command from the query, possibly upconverting if it's a legacy
|
||||
* format
|
||||
*
|
||||
* @param {Object} command the command
|
||||
*/
|
||||
const extractCommand = command => {
|
||||
if (command instanceof GetMore) {
|
||||
return {
|
||||
getMore: command.cursorId,
|
||||
collection: collectionName(command),
|
||||
batchSize: command.numberToReturn
|
||||
};
|
||||
}
|
||||
|
||||
if (command instanceof KillCursor) {
|
||||
return {
|
||||
killCursors: collectionName(command),
|
||||
cursors: command.cursorIds
|
||||
};
|
||||
}
|
||||
|
||||
if (command instanceof Msg) {
|
||||
return command.command;
|
||||
}
|
||||
|
||||
if (command.query && command.query.$query) {
|
||||
let result;
|
||||
if (command.ns === 'admin.$cmd') {
|
||||
// upconvert legacy command
|
||||
result = Object.assign({}, command.query.$query);
|
||||
} else {
|
||||
// upconvert legacy find command
|
||||
result = { find: collectionName(command) };
|
||||
Object.keys(LEGACY_FIND_QUERY_MAP).forEach(key => {
|
||||
if (typeof command.query[key] !== 'undefined')
|
||||
result[LEGACY_FIND_QUERY_MAP[key]] = command.query[key];
|
||||
});
|
||||
}
|
||||
|
||||
Object.keys(LEGACY_FIND_OPTIONS_MAP).forEach(key => {
|
||||
if (typeof command[key] !== 'undefined') result[LEGACY_FIND_OPTIONS_MAP[key]] = command[key];
|
||||
});
|
||||
|
||||
OP_QUERY_KEYS.forEach(key => {
|
||||
if (command[key]) result[key] = command[key];
|
||||
});
|
||||
|
||||
if (typeof command.pre32Limit !== 'undefined') {
|
||||
result.limit = command.pre32Limit;
|
||||
}
|
||||
|
||||
if (command.query.$explain) {
|
||||
return { explain: result };
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
return command.query ? command.query : command;
|
||||
};
|
||||
|
||||
const extractReply = (command, reply) => {
|
||||
if (command instanceof GetMore) {
|
||||
return {
|
||||
@@ -177,21 +70,15 @@ class CommandStartedEvent {
|
||||
* @param {Object} command the command
|
||||
*/
|
||||
constructor(pool, command) {
|
||||
const cmd = extractCommand(command);
|
||||
const commandName = extractCommandName(cmd);
|
||||
const extractedCommand = extractCommand(command);
|
||||
const commandName = extractedCommand.name;
|
||||
const connectionDetails = extractConnectionDetails(pool);
|
||||
|
||||
// NOTE: remove in major revision, this is not spec behavior
|
||||
if (SENSITIVE_COMMANDS.has(commandName)) {
|
||||
this.commandObj = {};
|
||||
this.commandObj[commandName] = true;
|
||||
}
|
||||
|
||||
Object.assign(this, connectionDetails, {
|
||||
requestId: command.requestId,
|
||||
databaseName: databaseName(command),
|
||||
commandName,
|
||||
command: cmd
|
||||
command: extractedCommand.shouldRedact ? {} : extractedCommand.cmd
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -207,15 +94,15 @@ class CommandSucceededEvent {
|
||||
* @param {Array} started a high resolution tuple timestamp of when the command was first sent, to calculate duration
|
||||
*/
|
||||
constructor(pool, command, reply, started) {
|
||||
const cmd = extractCommand(command);
|
||||
const commandName = extractCommandName(cmd);
|
||||
const extractedCommand = extractCommand(command);
|
||||
const commandName = extractedCommand.name;
|
||||
const connectionDetails = extractConnectionDetails(pool);
|
||||
|
||||
Object.assign(this, connectionDetails, {
|
||||
requestId: command.requestId,
|
||||
commandName,
|
||||
duration: calculateDurationInMs(started),
|
||||
reply: maybeRedact(commandName, extractReply(command, reply))
|
||||
reply: extractedCommand.shouldRedact ? {} : extractReply(command, reply)
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -231,15 +118,15 @@ class CommandFailedEvent {
|
||||
* @param {Array} started a high resolution tuple timestamp of when the command was first sent, to calculate duration
|
||||
*/
|
||||
constructor(pool, command, error, started) {
|
||||
const cmd = extractCommand(command);
|
||||
const commandName = extractCommandName(cmd);
|
||||
const extractedCommand = extractCommand(command);
|
||||
const commandName = extractedCommand.name;
|
||||
const connectionDetails = extractConnectionDetails(pool);
|
||||
|
||||
Object.assign(this, connectionDetails, {
|
||||
requestId: command.requestId,
|
||||
commandName,
|
||||
duration: calculateDurationInMs(started),
|
||||
failure: maybeRedact(commandName, error)
|
||||
failure: extractedCommand.shouldRedact ? {} : error
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
13
node_modules/mongodb/lib/core/connection/commands.js
generated
vendored
13
node_modules/mongodb/lib/core/connection/commands.js
generated
vendored
@@ -398,7 +398,12 @@ KillCursor.prototype.toBin = function() {
|
||||
};
|
||||
|
||||
var Response = function(bson, message, msgHeader, msgBody, opts) {
|
||||
opts = opts || { promoteLongs: true, promoteValues: true, promoteBuffers: false };
|
||||
opts = opts || {
|
||||
promoteLongs: true,
|
||||
promoteValues: true,
|
||||
promoteBuffers: false,
|
||||
bsonRegExp: false
|
||||
};
|
||||
this.parsed = false;
|
||||
this.raw = message;
|
||||
this.data = msgBody;
|
||||
@@ -429,6 +434,7 @@ var Response = function(bson, message, msgHeader, msgBody, opts) {
|
||||
this.promoteLongs = typeof opts.promoteLongs === 'boolean' ? opts.promoteLongs : true;
|
||||
this.promoteValues = typeof opts.promoteValues === 'boolean' ? opts.promoteValues : true;
|
||||
this.promoteBuffers = typeof opts.promoteBuffers === 'boolean' ? opts.promoteBuffers : false;
|
||||
this.bsonRegExp = typeof opts.bsonRegExp === 'boolean' ? opts.bsonRegExp : false;
|
||||
};
|
||||
|
||||
Response.prototype.isParsed = function() {
|
||||
@@ -449,13 +455,16 @@ Response.prototype.parse = function(options) {
|
||||
typeof options.promoteValues === 'boolean' ? options.promoteValues : this.opts.promoteValues;
|
||||
var promoteBuffers =
|
||||
typeof options.promoteBuffers === 'boolean' ? options.promoteBuffers : this.opts.promoteBuffers;
|
||||
var bsonRegExp =
|
||||
typeof options.bsonRegExp === 'boolean' ? options.bsonRegExp : this.opts.bsonRegExp;
|
||||
var bsonSize, _options;
|
||||
|
||||
// Set up the options
|
||||
_options = {
|
||||
promoteLongs: promoteLongs,
|
||||
promoteValues: promoteValues,
|
||||
promoteBuffers: promoteBuffers
|
||||
promoteBuffers: promoteBuffers,
|
||||
bsonRegExp: bsonRegExp
|
||||
};
|
||||
|
||||
// Position within OP_REPLY at which documents start
|
||||
|
||||
288
node_modules/mongodb/lib/core/connection/connect.js
generated
vendored
288
node_modules/mongodb/lib/core/connection/connect.js
generated
vendored
@@ -2,10 +2,11 @@
|
||||
const net = require('net');
|
||||
const tls = require('tls');
|
||||
const Connection = require('./connection');
|
||||
const Query = require('./commands').Query;
|
||||
const MongoError = require('../error').MongoError;
|
||||
const MongoNetworkError = require('../error').MongoNetworkError;
|
||||
const MongoNetworkTimeoutError = require('../error').MongoNetworkTimeoutError;
|
||||
const defaultAuthProviders = require('../auth/defaultAuthProviders').defaultAuthProviders;
|
||||
const AuthContext = require('../auth/auth_provider').AuthContext;
|
||||
const WIRE_CONSTANTS = require('../wireprotocol/constants');
|
||||
const makeClientMetadata = require('../utils').makeClientMetadata;
|
||||
const MAX_SUPPORTED_WIRE_VERSION = WIRE_CONSTANTS.MAX_SUPPORTED_WIRE_VERSION;
|
||||
@@ -37,30 +38,7 @@ function connect(options, cancellationToken, callback) {
|
||||
}
|
||||
|
||||
function isModernConnectionType(conn) {
|
||||
return typeof conn.command === 'function';
|
||||
}
|
||||
|
||||
function getSaslSupportedMechs(options) {
|
||||
if (!(options && options.credentials)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const credentials = options.credentials;
|
||||
|
||||
// TODO: revisit whether or not items like `options.user` and `options.dbName` should be checked here
|
||||
const authMechanism = credentials.mechanism;
|
||||
const authSource = credentials.source || options.dbName || 'admin';
|
||||
const user = credentials.username || options.user;
|
||||
|
||||
if (typeof authMechanism === 'string' && authMechanism.toUpperCase() !== 'DEFAULT') {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (!user) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return { saslSupportedMechs: `${authSource}.${user}` };
|
||||
return !(conn instanceof Connection);
|
||||
}
|
||||
|
||||
function checkSupportedServer(ismaster, options) {
|
||||
@@ -97,77 +75,130 @@ function performInitialHandshake(conn, options, _callback) {
|
||||
_callback(err, ret);
|
||||
};
|
||||
|
||||
let compressors = [];
|
||||
if (options.compression && options.compression.compressors) {
|
||||
compressors = options.compression.compressors;
|
||||
const credentials = options.credentials;
|
||||
if (credentials) {
|
||||
if (!credentials.mechanism.match(/DEFAULT/i) && !AUTH_PROVIDERS[credentials.mechanism]) {
|
||||
callback(new MongoError(`authMechanism '${credentials.mechanism}' not supported`));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const handshakeDoc = Object.assign(
|
||||
{
|
||||
ismaster: true,
|
||||
client: options.metadata || makeClientMetadata(options),
|
||||
compression: compressors
|
||||
},
|
||||
getSaslSupportedMechs(options)
|
||||
);
|
||||
|
||||
const handshakeOptions = Object.assign({}, options);
|
||||
|
||||
// The handshake technically is a monitoring check, so its socket timeout should be connectTimeoutMS
|
||||
if (options.connectTimeoutMS || options.connectionTimeout) {
|
||||
handshakeOptions.socketTimeout = options.connectTimeoutMS || options.connectionTimeout;
|
||||
}
|
||||
|
||||
const start = new Date().getTime();
|
||||
runCommand(conn, 'admin.$cmd', handshakeDoc, handshakeOptions, (err, ismaster) => {
|
||||
const authContext = new AuthContext(conn, credentials, options);
|
||||
prepareHandshakeDocument(authContext, (err, handshakeDoc) => {
|
||||
if (err) {
|
||||
callback(err);
|
||||
return;
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
if (ismaster.ok === 0) {
|
||||
callback(new MongoError(ismaster));
|
||||
return;
|
||||
const handshakeOptions = Object.assign({}, options);
|
||||
if (options.connectTimeoutMS || options.connectionTimeout) {
|
||||
// The handshake technically is a monitoring check, so its socket timeout should be connectTimeoutMS
|
||||
handshakeOptions.socketTimeout = options.connectTimeoutMS || options.connectionTimeout;
|
||||
}
|
||||
|
||||
const supportedServerErr = checkSupportedServer(ismaster, options);
|
||||
if (supportedServerErr) {
|
||||
callback(supportedServerErr);
|
||||
return;
|
||||
}
|
||||
handshakeDoc.helloOk = !!options.useUnifiedTopology;
|
||||
|
||||
if (!isModernConnectionType(conn)) {
|
||||
// resolve compression
|
||||
if (ismaster.compression) {
|
||||
const agreedCompressors = compressors.filter(
|
||||
compressor => ismaster.compression.indexOf(compressor) !== -1
|
||||
);
|
||||
const start = new Date().getTime();
|
||||
conn.command('admin.$cmd', handshakeDoc, handshakeOptions, (err, result) => {
|
||||
if (err) {
|
||||
callback(err);
|
||||
return;
|
||||
}
|
||||
|
||||
if (agreedCompressors.length) {
|
||||
conn.agreedCompressor = agreedCompressors[0];
|
||||
}
|
||||
const response = result.result;
|
||||
if (response.ok === 0) {
|
||||
callback(new MongoError(response));
|
||||
return;
|
||||
}
|
||||
|
||||
if (options.compression && options.compression.zlibCompressionLevel) {
|
||||
conn.zlibCompressionLevel = options.compression.zlibCompressionLevel;
|
||||
if ('isWritablePrimary' in response) {
|
||||
// Provide pre-hello-style response document.
|
||||
response.ismaster = response.isWritablePrimary;
|
||||
}
|
||||
|
||||
if (options.useUnifiedTopology && response.helloOk) {
|
||||
conn.helloOk = true;
|
||||
}
|
||||
|
||||
const supportedServerErr = checkSupportedServer(response, options);
|
||||
if (supportedServerErr) {
|
||||
callback(supportedServerErr);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isModernConnectionType(conn)) {
|
||||
// resolve compression
|
||||
if (response.compression) {
|
||||
const agreedCompressors = handshakeDoc.compression.filter(
|
||||
compressor => response.compression.indexOf(compressor) !== -1
|
||||
);
|
||||
|
||||
if (agreedCompressors.length) {
|
||||
conn.agreedCompressor = agreedCompressors[0];
|
||||
}
|
||||
|
||||
if (options.compression && options.compression.zlibCompressionLevel) {
|
||||
conn.zlibCompressionLevel = options.compression.zlibCompressionLevel;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: This is metadata attached to the connection while porting away from
|
||||
// handshake being done in the `Server` class. Likely, it should be
|
||||
// relocated, or at very least restructured.
|
||||
conn.ismaster = ismaster;
|
||||
conn.lastIsMasterMS = new Date().getTime() - start;
|
||||
// NOTE: This is metadata attached to the connection while porting away from
|
||||
// handshake being done in the `Server` class. Likely, it should be
|
||||
// relocated, or at very least restructured.
|
||||
conn.ismaster = response;
|
||||
conn.lastIsMasterMS = new Date().getTime() - start;
|
||||
|
||||
const credentials = options.credentials;
|
||||
if (!ismaster.arbiterOnly && credentials) {
|
||||
credentials.resolveAuthMechanism(ismaster);
|
||||
authenticate(conn, credentials, callback);
|
||||
if (!response.arbiterOnly && credentials) {
|
||||
// store the response on auth context
|
||||
Object.assign(authContext, { response });
|
||||
|
||||
const resolvedCredentials = credentials.resolveAuthMechanism(response);
|
||||
const authProvider = AUTH_PROVIDERS[resolvedCredentials.mechanism];
|
||||
authProvider.auth(authContext, err => {
|
||||
if (err) return callback(err);
|
||||
callback(undefined, conn);
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
callback(undefined, conn);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function prepareHandshakeDocument(authContext, callback) {
|
||||
const options = authContext.options;
|
||||
const serverApi = authContext.connection.serverApi;
|
||||
const compressors =
|
||||
options.compression && options.compression.compressors ? options.compression.compressors : [];
|
||||
|
||||
const handshakeDoc = {
|
||||
[serverApi ? 'hello' : 'ismaster']: true,
|
||||
client: options.metadata || makeClientMetadata(options),
|
||||
compression: compressors
|
||||
};
|
||||
|
||||
const credentials = authContext.credentials;
|
||||
if (credentials) {
|
||||
if (credentials.mechanism.match(/DEFAULT/i) && credentials.username) {
|
||||
Object.assign(handshakeDoc, {
|
||||
saslSupportedMechs: `${credentials.source}.${credentials.username}`
|
||||
});
|
||||
|
||||
AUTH_PROVIDERS['scram-sha-256'].prepare(handshakeDoc, authContext, callback);
|
||||
return;
|
||||
}
|
||||
|
||||
callback(undefined, conn);
|
||||
});
|
||||
const authProvider = AUTH_PROVIDERS[credentials.mechanism];
|
||||
if (authProvider == null) {
|
||||
return callback(new MongoError(`No AuthProvider for ${credentials.mechanism} defined.`));
|
||||
}
|
||||
authProvider.prepare(handshakeDoc, authContext, callback);
|
||||
return;
|
||||
}
|
||||
|
||||
callback(undefined, handshakeDoc);
|
||||
}
|
||||
|
||||
const LEGAL_SSL_SOCKET_OPTIONS = [
|
||||
@@ -227,7 +258,7 @@ function parseSslOptions(family, options) {
|
||||
}
|
||||
|
||||
// Set default sni servername to be the same as host
|
||||
if (result.servername == null) {
|
||||
if (result.servername == null && !net.isIP(result.host)) {
|
||||
result.servername = result.host;
|
||||
}
|
||||
|
||||
@@ -239,7 +270,7 @@ function makeConnection(family, options, cancellationToken, _callback) {
|
||||
const useSsl = typeof options.ssl === 'boolean' ? options.ssl : false;
|
||||
const keepAlive = typeof options.keepAlive === 'boolean' ? options.keepAlive : true;
|
||||
let keepAliveInitialDelay =
|
||||
typeof options.keepAliveInitialDelay === 'number' ? options.keepAliveInitialDelay : 300000;
|
||||
typeof options.keepAliveInitialDelay === 'number' ? options.keepAliveInitialDelay : 120000;
|
||||
const noDelay = typeof options.noDelay === 'boolean' ? options.noDelay : true;
|
||||
const connectionTimeout =
|
||||
typeof options.connectionTimeout === 'number'
|
||||
@@ -247,12 +278,17 @@ function makeConnection(family, options, cancellationToken, _callback) {
|
||||
: typeof options.connectTimeoutMS === 'number'
|
||||
? options.connectTimeoutMS
|
||||
: 30000;
|
||||
const socketTimeout = typeof options.socketTimeout === 'number' ? options.socketTimeout : 360000;
|
||||
const socketTimeoutMS =
|
||||
typeof options.socketTimeoutMS === 'number'
|
||||
? options.socketTimeoutMS
|
||||
: typeof options.socketTimeout === 'number'
|
||||
? options.socketTimeout
|
||||
: 0;
|
||||
const rejectUnauthorized =
|
||||
typeof options.rejectUnauthorized === 'boolean' ? options.rejectUnauthorized : true;
|
||||
|
||||
if (keepAliveInitialDelay > socketTimeout) {
|
||||
keepAliveInitialDelay = Math.round(socketTimeout / 2);
|
||||
if (keepAliveInitialDelay > socketTimeoutMS) {
|
||||
keepAliveInitialDelay = Math.round(socketTimeoutMS / 2);
|
||||
}
|
||||
|
||||
let socket;
|
||||
@@ -305,7 +341,7 @@ function makeConnection(family, options, cancellationToken, _callback) {
|
||||
return callback(socket.authorizationError);
|
||||
}
|
||||
|
||||
socket.setTimeout(socketTimeout);
|
||||
socket.setTimeout(socketTimeoutMS);
|
||||
callback(null, socket);
|
||||
}
|
||||
|
||||
@@ -318,92 +354,12 @@ function makeConnection(family, options, cancellationToken, _callback) {
|
||||
socket.once(connectEvent, connectHandler);
|
||||
}
|
||||
|
||||
const CONNECTION_ERROR_EVENTS = ['error', 'close', 'timeout', 'parseError'];
|
||||
function runCommand(conn, ns, command, options, callback) {
|
||||
if (typeof options === 'function') (callback = options), (options = {});
|
||||
|
||||
// are we using the new connection type? if so, no need to simulate a rpc `command` method
|
||||
if (isModernConnectionType(conn)) {
|
||||
conn.command(ns, command, options, (err, result) => {
|
||||
if (err) {
|
||||
callback(err);
|
||||
return;
|
||||
}
|
||||
|
||||
// NODE-2382: raw wire protocol messages, or command results should not be used anymore
|
||||
callback(undefined, result.result);
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const socketTimeout = typeof options.socketTimeout === 'number' ? options.socketTimeout : 360000;
|
||||
const bson = conn.options.bson;
|
||||
const query = new Query(bson, ns, command, {
|
||||
numberToSkip: 0,
|
||||
numberToReturn: 1
|
||||
});
|
||||
|
||||
const noop = () => {};
|
||||
function _callback(err, result) {
|
||||
callback(err, result);
|
||||
callback = noop;
|
||||
}
|
||||
|
||||
function errorHandler(err) {
|
||||
conn.resetSocketTimeout();
|
||||
CONNECTION_ERROR_EVENTS.forEach(eventName => conn.removeListener(eventName, errorHandler));
|
||||
conn.removeListener('message', messageHandler);
|
||||
|
||||
if (err == null) {
|
||||
err = new MongoError(`runCommand failed for connection to '${conn.address}'`);
|
||||
}
|
||||
|
||||
// ignore all future errors
|
||||
conn.on('error', noop);
|
||||
|
||||
_callback(err);
|
||||
}
|
||||
|
||||
function messageHandler(msg) {
|
||||
if (msg.responseTo !== query.requestId) {
|
||||
return;
|
||||
}
|
||||
|
||||
conn.resetSocketTimeout();
|
||||
CONNECTION_ERROR_EVENTS.forEach(eventName => conn.removeListener(eventName, errorHandler));
|
||||
conn.removeListener('message', messageHandler);
|
||||
|
||||
msg.parse({ promoteValues: true });
|
||||
_callback(undefined, msg.documents[0]);
|
||||
}
|
||||
|
||||
conn.setSocketTimeout(socketTimeout);
|
||||
CONNECTION_ERROR_EVENTS.forEach(eventName => conn.once(eventName, errorHandler));
|
||||
conn.on('message', messageHandler);
|
||||
conn.write(query.toBin());
|
||||
}
|
||||
|
||||
function authenticate(conn, credentials, callback) {
|
||||
const mechanism = credentials.mechanism;
|
||||
if (!AUTH_PROVIDERS[mechanism]) {
|
||||
callback(new MongoError(`authMechanism '${mechanism}' not supported`));
|
||||
return;
|
||||
}
|
||||
|
||||
const provider = AUTH_PROVIDERS[mechanism];
|
||||
provider.auth(runCommand, [conn], credentials, err => {
|
||||
if (err) return callback(err);
|
||||
callback(undefined, conn);
|
||||
});
|
||||
}
|
||||
|
||||
function connectionFailureError(type, err) {
|
||||
switch (type) {
|
||||
case 'error':
|
||||
return new MongoNetworkError(err);
|
||||
case 'timeout':
|
||||
return new MongoNetworkError(`connection timed out`);
|
||||
return new MongoNetworkTimeoutError(`connection timed out`);
|
||||
case 'close':
|
||||
return new MongoNetworkError(`connection closed`);
|
||||
case 'cancel':
|
||||
|
||||
88
node_modules/mongodb/lib/core/connection/connection.js
generated
vendored
88
node_modules/mongodb/lib/core/connection/connection.js
generated
vendored
@@ -8,12 +8,15 @@ const decompress = require('../wireprotocol/compression').decompress;
|
||||
const Response = require('./commands').Response;
|
||||
const BinMsg = require('./msg').BinMsg;
|
||||
const MongoNetworkError = require('../error').MongoNetworkError;
|
||||
const MongoNetworkTimeoutError = require('../error').MongoNetworkTimeoutError;
|
||||
const MongoError = require('../error').MongoError;
|
||||
const Logger = require('./logger');
|
||||
const OP_COMPRESSED = require('../wireprotocol/shared').opcodes.OP_COMPRESSED;
|
||||
const OP_MSG = require('../wireprotocol/shared').opcodes.OP_MSG;
|
||||
const MESSAGE_HEADER_SIZE = require('../wireprotocol/shared').MESSAGE_HEADER_SIZE;
|
||||
const Buffer = require('safe-buffer').Buffer;
|
||||
const Query = require('./commands').Query;
|
||||
const CommandResult = require('./command_result');
|
||||
|
||||
let _id = 0;
|
||||
|
||||
@@ -35,6 +38,7 @@ const DEBUG_FIELDS = [
|
||||
'promoteLongs',
|
||||
'promoteValues',
|
||||
'promoteBuffers',
|
||||
'bsonRegExp',
|
||||
'checkServerIdentity'
|
||||
];
|
||||
|
||||
@@ -64,12 +68,13 @@ class Connection extends EventEmitter {
|
||||
* @param {string} [options.host='localhost'] The host the socket is connected to
|
||||
* @param {number} [options.port=27017] The port used for the socket connection
|
||||
* @param {boolean} [options.keepAlive=true] TCP Connection keep alive enabled
|
||||
* @param {number} [options.keepAliveInitialDelay=300000] Initial delay before TCP keep alive enabled
|
||||
* @param {number} [options.keepAliveInitialDelay=120000] Initial delay before TCP keep alive enabled
|
||||
* @param {number} [options.connectionTimeout=30000] TCP Connection timeout setting
|
||||
* @param {number} [options.socketTimeout=360000] TCP Socket timeout setting
|
||||
* @param {number} [options.socketTimeout=0] TCP Socket timeout setting
|
||||
* @param {boolean} [options.promoteLongs] Convert Long values from the db into Numbers if they fit into 53 bits
|
||||
* @param {boolean} [options.promoteValues] Promotes BSON values to native types where possible, set to false to only receive wrapper types.
|
||||
* @param {boolean} [options.promoteBuffers] Promotes Binary BSON values to native Node Buffers.
|
||||
* @param {boolean} [options.bsonRegExp] By default, regex returned from MDB will be native to the language. Setting to true will ensure that a BSON.BSONRegExp object is returned.
|
||||
* @param {number} [options.maxBsonMessageSize=0x4000000] Largest possible size of a BSON message (for legacy purposes)
|
||||
*/
|
||||
constructor(socket, options) {
|
||||
@@ -86,15 +91,16 @@ class Connection extends EventEmitter {
|
||||
this.bson = options.bson;
|
||||
this.tag = options.tag;
|
||||
this.maxBsonMessageSize = options.maxBsonMessageSize || DEFAULT_MAX_BSON_MESSAGE_SIZE;
|
||||
this.helloOk = undefined;
|
||||
|
||||
this.port = options.port || 27017;
|
||||
this.host = options.host || 'localhost';
|
||||
this.socketTimeout = typeof options.socketTimeout === 'number' ? options.socketTimeout : 360000;
|
||||
this.socketTimeout = typeof options.socketTimeout === 'number' ? options.socketTimeout : 0;
|
||||
|
||||
// These values are inspected directly in tests, but maybe not necessary to keep around
|
||||
this.keepAlive = typeof options.keepAlive === 'boolean' ? options.keepAlive : true;
|
||||
this.keepAliveInitialDelay =
|
||||
typeof options.keepAliveInitialDelay === 'number' ? options.keepAliveInitialDelay : 300000;
|
||||
typeof options.keepAliveInitialDelay === 'number' ? options.keepAliveInitialDelay : 120000;
|
||||
this.connectionTimeout =
|
||||
typeof options.connectionTimeout === 'number' ? options.connectionTimeout : 30000;
|
||||
if (this.keepAliveInitialDelay > this.socketTimeout) {
|
||||
@@ -114,7 +120,8 @@ class Connection extends EventEmitter {
|
||||
this.responseOptions = {
|
||||
promoteLongs: typeof options.promoteLongs === 'boolean' ? options.promoteLongs : true,
|
||||
promoteValues: typeof options.promoteValues === 'boolean' ? options.promoteValues : true,
|
||||
promoteBuffers: typeof options.promoteBuffers === 'boolean' ? options.promoteBuffers : false
|
||||
promoteBuffers: typeof options.promoteBuffers === 'boolean' ? options.promoteBuffers : false,
|
||||
bsonRegExp: typeof options.bsonRegExp === 'boolean' ? options.bsonRegExp : false
|
||||
};
|
||||
|
||||
// Flushing
|
||||
@@ -184,6 +191,7 @@ class Connection extends EventEmitter {
|
||||
* Unref this connection
|
||||
* @method
|
||||
* @return {boolean}
|
||||
* @deprecated This function is deprecated and will be removed in the next major version.
|
||||
*/
|
||||
unref() {
|
||||
if (this.socket == null) {
|
||||
@@ -251,10 +259,10 @@ class Connection extends EventEmitter {
|
||||
// Debug Log
|
||||
if (this.logger.isDebug()) {
|
||||
if (!Array.isArray(buffer)) {
|
||||
this.logger.debug(`writing buffer [${buffer.toString('hex')}] to ${this.address}`);
|
||||
this.logger.debug(`writing buffer [ ${buffer.length} ] to ${this.address}`);
|
||||
} else {
|
||||
for (let i = 0; i < buffer.length; i++)
|
||||
this.logger.debug(`writing buffer [${buffer[i].toString('hex')}] to ${this.address}`);
|
||||
this.logger.debug(`writing buffer [ ${buffer[i].length} ] to ${this.address}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -305,8 +313,70 @@ class Connection extends EventEmitter {
|
||||
if (this.destroyed) return false;
|
||||
return !this.socket.destroyed && this.socket.writable;
|
||||
}
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
command(ns, command, options, callback) {
|
||||
if (typeof options === 'function') (callback = options), (options = {});
|
||||
|
||||
const conn = this;
|
||||
const socketTimeout = typeof options.socketTimeout === 'number' ? options.socketTimeout : 0;
|
||||
const bson = conn.options.bson;
|
||||
const query = new Query(bson, ns, command, {
|
||||
numberToSkip: 0,
|
||||
numberToReturn: 1
|
||||
});
|
||||
|
||||
const noop = () => {};
|
||||
function _callback(err, result) {
|
||||
callback(err, result);
|
||||
callback = noop;
|
||||
}
|
||||
|
||||
function errorHandler(err) {
|
||||
conn.resetSocketTimeout();
|
||||
CONNECTION_ERROR_EVENTS.forEach(eventName => conn.removeListener(eventName, errorHandler));
|
||||
conn.removeListener('message', messageHandler);
|
||||
|
||||
if (err == null) {
|
||||
err = new MongoError(`runCommand failed for connection to '${conn.address}'`);
|
||||
}
|
||||
|
||||
// ignore all future errors
|
||||
conn.on('error', noop);
|
||||
_callback(err);
|
||||
}
|
||||
|
||||
function messageHandler(msg) {
|
||||
if (msg.responseTo !== query.requestId) {
|
||||
return;
|
||||
}
|
||||
|
||||
conn.resetSocketTimeout();
|
||||
CONNECTION_ERROR_EVENTS.forEach(eventName => conn.removeListener(eventName, errorHandler));
|
||||
conn.removeListener('message', messageHandler);
|
||||
|
||||
msg.parse({ promoteValues: true });
|
||||
|
||||
const response = msg.documents[0];
|
||||
if (response.ok === 0 || response.$err || response.errmsg || response.code) {
|
||||
_callback(new MongoError(response));
|
||||
return;
|
||||
}
|
||||
|
||||
_callback(undefined, new CommandResult(response, this, msg));
|
||||
}
|
||||
|
||||
conn.setSocketTimeout(socketTimeout);
|
||||
CONNECTION_ERROR_EVENTS.forEach(eventName => conn.once(eventName, errorHandler));
|
||||
conn.on('message', messageHandler);
|
||||
conn.write(query.toBin());
|
||||
}
|
||||
}
|
||||
|
||||
const CONNECTION_ERROR_EVENTS = ['error', 'close', 'timeout', 'parseError'];
|
||||
|
||||
function deleteConnection(id) {
|
||||
// console.log("=== deleted connection " + id + " :: " + (connections[id] ? connections[id].port : ''))
|
||||
delete connections[id];
|
||||
@@ -352,7 +422,9 @@ function timeoutHandler(conn) {
|
||||
conn.timedOut = true;
|
||||
conn.emit(
|
||||
'timeout',
|
||||
new MongoNetworkError(`connection ${conn.id} to ${conn.address} timed out`),
|
||||
new MongoNetworkTimeoutError(`connection ${conn.id} to ${conn.address} timed out`, {
|
||||
beforeHandshake: conn.ismaster == null
|
||||
}),
|
||||
conn
|
||||
);
|
||||
};
|
||||
|
||||
1
node_modules/mongodb/lib/core/connection/logger.js
generated
vendored
1
node_modules/mongodb/lib/core/connection/logger.js
generated
vendored
@@ -37,6 +37,7 @@ var Logger = function(className, options) {
|
||||
if (options.logger) {
|
||||
currentLogger = options.logger;
|
||||
} else if (currentLogger == null) {
|
||||
// eslint-disable-next-line no-console
|
||||
currentLogger = console.log;
|
||||
}
|
||||
|
||||
|
||||
17
node_modules/mongodb/lib/core/connection/msg.js
generated
vendored
17
node_modules/mongodb/lib/core/connection/msg.js
generated
vendored
@@ -31,6 +31,7 @@ const Buffer = require('safe-buffer').Buffer;
|
||||
const opcodes = require('../wireprotocol/shared').opcodes;
|
||||
const databaseNamespace = require('../wireprotocol/shared').databaseNamespace;
|
||||
const ReadPreference = require('../topologies/read_preference');
|
||||
const MongoError = require('../../core/error').MongoError;
|
||||
|
||||
// Incrementing request id
|
||||
let _requestId = 0;
|
||||
@@ -138,7 +139,12 @@ Msg.getRequestId = function() {
|
||||
|
||||
class BinMsg {
|
||||
constructor(bson, message, msgHeader, msgBody, opts) {
|
||||
opts = opts || { promoteLongs: true, promoteValues: true, promoteBuffers: false };
|
||||
opts = opts || {
|
||||
promoteLongs: true,
|
||||
promoteValues: true,
|
||||
promoteBuffers: false,
|
||||
bsonRegExp: false
|
||||
};
|
||||
this.parsed = false;
|
||||
this.raw = message;
|
||||
this.data = msgBody;
|
||||
@@ -160,6 +166,7 @@ class BinMsg {
|
||||
this.promoteLongs = typeof opts.promoteLongs === 'boolean' ? opts.promoteLongs : true;
|
||||
this.promoteValues = typeof opts.promoteValues === 'boolean' ? opts.promoteValues : true;
|
||||
this.promoteBuffers = typeof opts.promoteBuffers === 'boolean' ? opts.promoteBuffers : false;
|
||||
this.bsonRegExp = typeof opts.bsonRegExp === 'boolean' ? opts.bsonRegExp : false;
|
||||
|
||||
this.documents = [];
|
||||
}
|
||||
@@ -185,18 +192,22 @@ class BinMsg {
|
||||
typeof options.promoteBuffers === 'boolean'
|
||||
? options.promoteBuffers
|
||||
: this.opts.promoteBuffers;
|
||||
const bsonRegExp =
|
||||
typeof options.bsonRegExp === 'boolean' ? options.bsonRegExp : this.opts.bsonRegExp;
|
||||
|
||||
// Set up the options
|
||||
const _options = {
|
||||
promoteLongs: promoteLongs,
|
||||
promoteValues: promoteValues,
|
||||
promoteBuffers: promoteBuffers
|
||||
promoteBuffers: promoteBuffers,
|
||||
bsonRegExp: bsonRegExp
|
||||
};
|
||||
|
||||
while (this.index < this.data.length) {
|
||||
const payloadType = this.data.readUInt8(this.index++);
|
||||
if (payloadType === 1) {
|
||||
console.error('TYPE 1');
|
||||
// It was decided that no driver makes use of payload type 1
|
||||
throw new MongoError('OP_MSG Payload Type 1 detected unsupported protocol');
|
||||
} else if (payloadType === 0) {
|
||||
const bsonSize = this.data.readUInt32LE(this.index);
|
||||
const bin = this.data.slice(this.index, this.index + bsonSize);
|
||||
|
||||
19
node_modules/mongodb/lib/core/connection/pool.js
generated
vendored
19
node_modules/mongodb/lib/core/connection/pool.js
generated
vendored
@@ -60,11 +60,11 @@ var _id = 0;
|
||||
* @param {number} [options.reconnectTries=30] Server attempt to reconnect #times
|
||||
* @param {number} [options.reconnectInterval=1000] Server will wait # milliseconds between retries
|
||||
* @param {boolean} [options.keepAlive=true] TCP Connection keep alive enabled
|
||||
* @param {number} [options.keepAliveInitialDelay=300000] Initial delay before TCP keep alive enabled
|
||||
* @param {number} [options.keepAliveInitialDelay=120000] Initial delay before TCP keep alive enabled
|
||||
* @param {boolean} [options.noDelay=true] TCP Connection no delay
|
||||
* @param {number} [options.connectionTimeout=30000] TCP Connection timeout setting
|
||||
* @param {number} [options.socketTimeout=360000] TCP Socket timeout setting
|
||||
* @param {number} [options.monitoringSocketTimeout=30000] TCP Socket timeout setting for replicaset monitoring socket
|
||||
* @param {number} [options.socketTimeout=0] TCP Socket timeout setting
|
||||
* @param {number} [options.monitoringSocketTimeout=0] TCP Socket timeout setting for replicaset monitoring socket
|
||||
* @param {boolean} [options.ssl=false] Use SSL for connection
|
||||
* @param {boolean|function} [options.checkServerIdentity=true] Ensure we check server identify during SSL, set to false to disable checking. Only works for Node 0.12.x or higher. You can pass in a boolean or your own checkServerIdentity override function.
|
||||
* @param {Buffer} [options.ca] SSL Certificate store binary buffer
|
||||
@@ -76,6 +76,7 @@ var _id = 0;
|
||||
* @param {boolean} [options.promoteLongs=true] Convert Long values from the db into Numbers if they fit into 53 bits
|
||||
* @param {boolean} [options.promoteValues=true] Promotes BSON values to native types where possible, set to false to only receive wrapper types.
|
||||
* @param {boolean} [options.promoteBuffers=false] Promotes Binary BSON values to native Node Buffers.
|
||||
* @param {boolean} [options.bsonRegExp=false] By default, regex returned from MDB will be native to the language. Setting to true will ensure that a BSON.BSONRegExp object is returned.
|
||||
* @param {boolean} [options.domainsEnabled=false] Enable the wrapping of the callback in the current domain, disabled by default to avoid perf hit.
|
||||
* @fires Pool#connect
|
||||
* @fires Pool#close
|
||||
@@ -111,9 +112,9 @@ var Pool = function(topology, options) {
|
||||
minSize: 0,
|
||||
// socket settings
|
||||
connectionTimeout: 30000,
|
||||
socketTimeout: 360000,
|
||||
socketTimeout: 0,
|
||||
keepAlive: true,
|
||||
keepAliveInitialDelay: 300000,
|
||||
keepAliveInitialDelay: 120000,
|
||||
noDelay: true,
|
||||
// SSL Settings
|
||||
ssl: false,
|
||||
@@ -127,6 +128,7 @@ var Pool = function(topology, options) {
|
||||
promoteLongs: true,
|
||||
promoteValues: true,
|
||||
promoteBuffers: false,
|
||||
bsonRegExp: false,
|
||||
// Reconnection options
|
||||
reconnect: true,
|
||||
reconnectInterval: 1000,
|
||||
@@ -390,8 +392,8 @@ function messageHandler(self) {
|
||||
if (self.logger.isDebug()) {
|
||||
self.logger.debug(
|
||||
f(
|
||||
'message [%s] received from %s:%s',
|
||||
message.raw.toString('hex'),
|
||||
'message [ %s ] received from %s:%s',
|
||||
message.raw.length,
|
||||
self.options.host,
|
||||
self.options.port
|
||||
)
|
||||
@@ -602,6 +604,7 @@ Pool.prototype.logout = function(dbName, callback) {
|
||||
/**
|
||||
* Unref the pool
|
||||
* @method
|
||||
* @deprecated This function is deprecated and will be removed in the next major version.
|
||||
*/
|
||||
Pool.prototype.unref = function() {
|
||||
// Get all the known connections
|
||||
@@ -870,6 +873,7 @@ Pool.prototype.write = function(command, options, cb) {
|
||||
promoteLongs: true,
|
||||
promoteValues: true,
|
||||
promoteBuffers: false,
|
||||
bsonRegExp: false,
|
||||
fullResult: false
|
||||
};
|
||||
|
||||
@@ -879,6 +883,7 @@ Pool.prototype.write = function(command, options, cb) {
|
||||
typeof options.promoteValues === 'boolean' ? options.promoteValues : true;
|
||||
operation.promoteBuffers =
|
||||
typeof options.promoteBuffers === 'boolean' ? options.promoteBuffers : false;
|
||||
operation.bsonRegExp = typeof options.bsonRegExp === 'boolean' ? options.bsonRegExp : false;
|
||||
operation.raw = typeof options.raw === 'boolean' ? options.raw : false;
|
||||
operation.immediateRelease =
|
||||
typeof options.immediateRelease === 'boolean' ? options.immediateRelease : false;
|
||||
|
||||
56
node_modules/mongodb/lib/core/connection/utils.js
generated
vendored
56
node_modules/mongodb/lib/core/connection/utils.js
generated
vendored
@@ -1,9 +1,12 @@
|
||||
'use strict';
|
||||
|
||||
const require_optional = require('require_optional');
|
||||
const parsePackageVersion = require('../../utils').parsePackageVersion;
|
||||
const MongoError = require('../error').MongoError;
|
||||
|
||||
const require_optional = require('optional-require')(require);
|
||||
|
||||
function debugOptions(debugFields, options) {
|
||||
var finaloptions = {};
|
||||
const finaloptions = {};
|
||||
debugFields.forEach(function(n) {
|
||||
finaloptions[n] = options[n];
|
||||
});
|
||||
@@ -12,16 +15,22 @@ function debugOptions(debugFields, options) {
|
||||
}
|
||||
|
||||
function retrieveBSON() {
|
||||
var BSON = require('bson');
|
||||
const BSON = require('bson');
|
||||
BSON.native = false;
|
||||
|
||||
try {
|
||||
var optionalBSON = require_optional('bson-ext');
|
||||
if (optionalBSON) {
|
||||
optionalBSON.native = true;
|
||||
return optionalBSON;
|
||||
const optionalBSON = require_optional('bson-ext');
|
||||
const bsonExtVersion = parsePackageVersion(
|
||||
require_optional('bson-ext/package.json') || { version: '0.0.0' }
|
||||
);
|
||||
if (optionalBSON) {
|
||||
if (bsonExtVersion.major >= 4) {
|
||||
throw new MongoError(
|
||||
'bson-ext version 4 and above does not work with the 3.x version of the mongodb driver'
|
||||
);
|
||||
}
|
||||
} catch (err) {} // eslint-disable-line
|
||||
optionalBSON.native = true;
|
||||
return optionalBSON;
|
||||
}
|
||||
|
||||
return BSON;
|
||||
}
|
||||
@@ -33,24 +42,43 @@ function noSnappyWarning() {
|
||||
);
|
||||
}
|
||||
|
||||
const PKG_VERSION = Symbol('kPkgVersion');
|
||||
|
||||
// Facilitate loading Snappy optionally
|
||||
function retrieveSnappy() {
|
||||
var snappy = null;
|
||||
try {
|
||||
snappy = require_optional('snappy');
|
||||
} catch (error) {} // eslint-disable-line
|
||||
const snappy = require_optional('snappy');
|
||||
if (!snappy) {
|
||||
snappy = {
|
||||
return {
|
||||
compress: noSnappyWarning,
|
||||
uncompress: noSnappyWarning,
|
||||
compressSync: noSnappyWarning,
|
||||
uncompressSync: noSnappyWarning
|
||||
};
|
||||
}
|
||||
|
||||
const snappyPkg = require_optional('snappy/package.json') || { version: '0.0.0' };
|
||||
const version = parsePackageVersion(snappyPkg);
|
||||
snappy[PKG_VERSION] = version;
|
||||
if (version.major >= 7) {
|
||||
const compressOriginal = snappy.compress;
|
||||
const uncompressOriginal = snappy.uncompress;
|
||||
snappy.compress = (data, callback) => {
|
||||
compressOriginal(data)
|
||||
.then(res => callback(undefined, res))
|
||||
.catch(error => callback(error));
|
||||
};
|
||||
snappy.uncompress = (data, callback) => {
|
||||
uncompressOriginal(data)
|
||||
.then(res => callback(undefined, res))
|
||||
.catch(error => callback(error));
|
||||
};
|
||||
}
|
||||
|
||||
return snappy;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
PKG_VERSION,
|
||||
debugOptions,
|
||||
retrieveBSON,
|
||||
retrieveSnappy
|
||||
|
||||
108
node_modules/mongodb/lib/core/cursor.js
generated
vendored
108
node_modules/mongodb/lib/core/cursor.js
generated
vendored
@@ -11,6 +11,7 @@ const executeOperation = require('../operations/execute_operation');
|
||||
const Readable = require('stream').Readable;
|
||||
const SUPPORTS = require('../utils').SUPPORTS;
|
||||
const MongoDBNamespace = require('../utils').MongoDBNamespace;
|
||||
const mergeOptions = require('../utils').mergeOptions;
|
||||
const OperationBase = require('../operations/operation').OperationBase;
|
||||
|
||||
const BSON = retrieveBSON();
|
||||
@@ -145,6 +146,13 @@ class CoreCursor extends Readable {
|
||||
this.cursorState.promoteBuffers = options.promoteBuffers;
|
||||
}
|
||||
|
||||
// Add bsonRegExp to cursor state
|
||||
if (typeof topologyOptions.bsonRegExp === 'boolean') {
|
||||
this.cursorState.bsonRegExp = topologyOptions.bsonRegExp;
|
||||
} else if (typeof options.bsonRegExp === 'boolean') {
|
||||
this.cursorState.bsonRegExp = options.bsonRegExp;
|
||||
}
|
||||
|
||||
if (topologyOptions.reconnect) {
|
||||
this.cursorState.reconnect = topologyOptions.reconnect;
|
||||
}
|
||||
@@ -207,7 +215,9 @@ class CoreCursor extends Readable {
|
||||
* @return {Cursor}
|
||||
*/
|
||||
clone() {
|
||||
return this.topology.cursor(this.ns, this.cmd, this.options);
|
||||
const clonedOptions = mergeOptions({}, this.options);
|
||||
delete clonedOptions.session;
|
||||
return this.topology.cursor(this.ns, this.cmd, clonedOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -464,50 +474,41 @@ class CoreCursor extends Readable {
|
||||
}
|
||||
|
||||
const result = r.message;
|
||||
if (result.queryFailure) {
|
||||
return done(new MongoError(result.documents[0]), null);
|
||||
}
|
||||
|
||||
// Check if we have a command cursor
|
||||
if (
|
||||
Array.isArray(result.documents) &&
|
||||
result.documents.length === 1 &&
|
||||
(!cursor.cmd.find || (cursor.cmd.find && cursor.cmd.virtual === false)) &&
|
||||
(typeof result.documents[0].cursor !== 'string' ||
|
||||
result.documents[0]['$err'] ||
|
||||
result.documents[0]['errmsg'] ||
|
||||
Array.isArray(result.documents[0].result))
|
||||
) {
|
||||
// We have an error document, return the error
|
||||
if (result.documents[0]['$err'] || result.documents[0]['errmsg']) {
|
||||
return done(new MongoError(result.documents[0]), null);
|
||||
if (Array.isArray(result.documents) && result.documents.length === 1) {
|
||||
const document = result.documents[0];
|
||||
|
||||
if (result.queryFailure) {
|
||||
return done(new MongoError(document), null);
|
||||
}
|
||||
|
||||
// We have a cursor document
|
||||
if (result.documents[0].cursor != null && typeof result.documents[0].cursor !== 'string') {
|
||||
const id = result.documents[0].cursor.id;
|
||||
// If we have a namespace change set the new namespace for getmores
|
||||
if (result.documents[0].cursor.ns) {
|
||||
cursor.ns = result.documents[0].cursor.ns;
|
||||
}
|
||||
// Promote id to long if needed
|
||||
cursor.cursorState.cursorId = typeof id === 'number' ? Long.fromNumber(id) : id;
|
||||
cursor.cursorState.lastCursorId = cursor.cursorState.cursorId;
|
||||
cursor.cursorState.operationTime = result.documents[0].operationTime;
|
||||
|
||||
// If we have a firstBatch set it
|
||||
if (Array.isArray(result.documents[0].cursor.firstBatch)) {
|
||||
cursor.cursorState.documents = result.documents[0].cursor.firstBatch; //.reverse();
|
||||
// Check if we have a command cursor
|
||||
if (!cursor.cmd.find || (cursor.cmd.find && cursor.cmd.virtual === false)) {
|
||||
// We have an error document, return the error
|
||||
if (document.$err || document.errmsg) {
|
||||
return done(new MongoError(document), null);
|
||||
}
|
||||
|
||||
// Return after processing command cursor
|
||||
return done(null, result);
|
||||
}
|
||||
// We have a cursor document
|
||||
if (document.cursor != null && typeof document.cursor !== 'string') {
|
||||
const id = document.cursor.id;
|
||||
// If we have a namespace change set the new namespace for getmores
|
||||
if (document.cursor.ns) {
|
||||
cursor.ns = document.cursor.ns;
|
||||
}
|
||||
// Promote id to long if needed
|
||||
cursor.cursorState.cursorId = typeof id === 'number' ? Long.fromNumber(id) : id;
|
||||
cursor.cursorState.lastCursorId = cursor.cursorState.cursorId;
|
||||
cursor.cursorState.operationTime = document.operationTime;
|
||||
|
||||
if (Array.isArray(result.documents[0].result)) {
|
||||
cursor.cursorState.documents = result.documents[0].result;
|
||||
cursor.cursorState.cursorId = Long.ZERO;
|
||||
return done(null, result);
|
||||
// If we have a firstBatch set it
|
||||
if (Array.isArray(document.cursor.firstBatch)) {
|
||||
cursor.cursorState.documents = document.cursor.firstBatch; //.reverse();
|
||||
}
|
||||
|
||||
// Return after processing command cursor
|
||||
return done(null, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -754,9 +755,10 @@ function nextFunction(self, callback) {
|
||||
|
||||
if (self.cursorState.limit > 0 && self.cursorState.currentLimit >= self.cursorState.limit) {
|
||||
// Ensure we kill the cursor on the server
|
||||
self.kill();
|
||||
// Set cursor in dead and notified state
|
||||
return setCursorDeadAndNotified(self, callback);
|
||||
self.kill(() =>
|
||||
// Set cursor in dead and notified state
|
||||
setCursorDeadAndNotified(self, callback)
|
||||
);
|
||||
} else if (
|
||||
self.cursorState.cursorIndex === self.cursorState.documents.length &&
|
||||
!Long.ZERO.equals(self.cursorState.cursorId)
|
||||
@@ -836,9 +838,12 @@ function nextFunction(self, callback) {
|
||||
} else {
|
||||
if (self.cursorState.limit > 0 && self.cursorState.currentLimit >= self.cursorState.limit) {
|
||||
// Ensure we kill the cursor on the server
|
||||
self.kill();
|
||||
// Set cursor in dead and notified state
|
||||
return setCursorDeadAndNotified(self, callback);
|
||||
self.kill(() =>
|
||||
// Set cursor in dead and notified state
|
||||
setCursorDeadAndNotified(self, callback)
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Increment the current cursor limit
|
||||
@@ -850,11 +855,14 @@ function nextFunction(self, callback) {
|
||||
// Doc overflow
|
||||
if (!doc || doc.$err) {
|
||||
// Ensure we kill the cursor on the server
|
||||
self.kill();
|
||||
// Set cursor in dead and notified state
|
||||
return setCursorDeadAndNotified(self, function() {
|
||||
handleCallback(callback, new MongoError(doc ? doc.$err : undefined));
|
||||
});
|
||||
self.kill(() =>
|
||||
// Set cursor in dead and notified state
|
||||
setCursorDeadAndNotified(self, function() {
|
||||
handleCallback(callback, new MongoError(doc ? doc.$err : undefined));
|
||||
})
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Transform the doc with passed in transformation method if provided
|
||||
|
||||
158
node_modules/mongodb/lib/core/error.js
generated
vendored
158
node_modules/mongodb/lib/core/error.js
generated
vendored
@@ -1,5 +1,9 @@
|
||||
'use strict';
|
||||
|
||||
const MONGODB_ERROR_CODES = require('../error_codes').MONGODB_ERROR_CODES;
|
||||
|
||||
const kErrorLabels = Symbol('errorLabels');
|
||||
|
||||
/**
|
||||
* Creates a new MongoError
|
||||
*
|
||||
@@ -18,8 +22,12 @@ class MongoError extends Error {
|
||||
super(message);
|
||||
} else {
|
||||
super(message.message || message.errmsg || message.$err || 'n/a');
|
||||
if (message.errorLabels) {
|
||||
this[kErrorLabels] = new Set(message.errorLabels);
|
||||
}
|
||||
|
||||
for (var name in message) {
|
||||
if (name === 'errmsg') {
|
||||
if (name === 'errorLabels' || name === 'errmsg') {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -57,8 +65,29 @@ class MongoError extends Error {
|
||||
* @returns {boolean} returns true if the error has the provided error label
|
||||
*/
|
||||
hasErrorLabel(label) {
|
||||
return this.errorLabels && this.errorLabels.indexOf(label) !== -1;
|
||||
if (this[kErrorLabels] == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return this[kErrorLabels].has(label);
|
||||
}
|
||||
|
||||
addErrorLabel(label) {
|
||||
if (this[kErrorLabels] == null) {
|
||||
this[kErrorLabels] = new Set();
|
||||
}
|
||||
|
||||
this[kErrorLabels].add(label);
|
||||
}
|
||||
|
||||
get errorLabels() {
|
||||
return this[kErrorLabels] ? Array.from(this[kErrorLabels]) : [];
|
||||
}
|
||||
}
|
||||
|
||||
const kBeforeHandshake = Symbol('beforeHandshake');
|
||||
function isNetworkErrorBeforeHandshake(err) {
|
||||
return err[kBeforeHandshake] === true;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -71,9 +100,28 @@ class MongoError extends Error {
|
||||
* @extends MongoError
|
||||
*/
|
||||
class MongoNetworkError extends MongoError {
|
||||
constructor(message) {
|
||||
constructor(message, options) {
|
||||
super(message);
|
||||
this.name = 'MongoNetworkError';
|
||||
|
||||
if (options && typeof options.beforeHandshake === 'boolean') {
|
||||
this[kBeforeHandshake] = options.beforeHandshake;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An error indicating a network timeout occurred
|
||||
*
|
||||
* @param {Error|string|object} message The error message
|
||||
* @property {string} message The error message
|
||||
* @property {object} [options.beforeHandshake] Indicates the timeout happened before a connection handshake completed
|
||||
* @extends MongoError
|
||||
*/
|
||||
class MongoNetworkTimeoutError extends MongoNetworkError {
|
||||
constructor(message, options) {
|
||||
super(message, options);
|
||||
this.name = 'MongoNetworkTimeoutError';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,6 +206,10 @@ class MongoWriteConcernError extends MongoError {
|
||||
super(message);
|
||||
this.name = 'MongoWriteConcernError';
|
||||
|
||||
if (result && Array.isArray(result.errorLabels)) {
|
||||
this[kErrorLabels] = new Set(result.errorLabels);
|
||||
}
|
||||
|
||||
if (result != null) {
|
||||
this.result = makeWriteConcernResultObject(result);
|
||||
}
|
||||
@@ -166,19 +218,45 @@ class MongoWriteConcernError extends MongoError {
|
||||
|
||||
// see: https://github.com/mongodb/specifications/blob/master/source/retryable-writes/retryable-writes.rst#terms
|
||||
const RETRYABLE_ERROR_CODES = new Set([
|
||||
6, // HostUnreachable
|
||||
7, // HostNotFound
|
||||
89, // NetworkTimeout
|
||||
91, // ShutdownInProgress
|
||||
189, // PrimarySteppedDown
|
||||
9001, // SocketException
|
||||
10107, // NotMaster
|
||||
11600, // InterruptedAtShutdown
|
||||
11602, // InterruptedDueToReplStateChange
|
||||
13435, // NotMasterNoSlaveOk
|
||||
13436 // NotMasterOrSecondary
|
||||
MONGODB_ERROR_CODES.HostUnreachable,
|
||||
MONGODB_ERROR_CODES.HostNotFound,
|
||||
MONGODB_ERROR_CODES.NetworkTimeout,
|
||||
MONGODB_ERROR_CODES.ShutdownInProgress,
|
||||
MONGODB_ERROR_CODES.PrimarySteppedDown,
|
||||
MONGODB_ERROR_CODES.SocketException,
|
||||
MONGODB_ERROR_CODES.NotMaster,
|
||||
MONGODB_ERROR_CODES.InterruptedAtShutdown,
|
||||
MONGODB_ERROR_CODES.InterruptedDueToReplStateChange,
|
||||
MONGODB_ERROR_CODES.NotMasterNoSlaveOk,
|
||||
MONGODB_ERROR_CODES.NotMasterOrSecondary
|
||||
]);
|
||||
|
||||
const RETRYABLE_WRITE_ERROR_CODES = new Set([
|
||||
MONGODB_ERROR_CODES.InterruptedAtShutdown,
|
||||
MONGODB_ERROR_CODES.InterruptedDueToReplStateChange,
|
||||
MONGODB_ERROR_CODES.NotMaster,
|
||||
MONGODB_ERROR_CODES.NotMasterNoSlaveOk,
|
||||
MONGODB_ERROR_CODES.NotMasterOrSecondary,
|
||||
MONGODB_ERROR_CODES.PrimarySteppedDown,
|
||||
MONGODB_ERROR_CODES.ShutdownInProgress,
|
||||
MONGODB_ERROR_CODES.HostNotFound,
|
||||
MONGODB_ERROR_CODES.HostUnreachable,
|
||||
MONGODB_ERROR_CODES.NetworkTimeout,
|
||||
MONGODB_ERROR_CODES.SocketException,
|
||||
MONGODB_ERROR_CODES.ExceededTimeLimit
|
||||
]);
|
||||
|
||||
function isRetryableWriteError(error) {
|
||||
if (error instanceof MongoWriteConcernError) {
|
||||
return (
|
||||
RETRYABLE_WRITE_ERROR_CODES.has(error.code) ||
|
||||
RETRYABLE_WRITE_ERROR_CODES.has(error.result.code)
|
||||
);
|
||||
}
|
||||
|
||||
return RETRYABLE_WRITE_ERROR_CODES.has(error.code);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether an error is something the driver should attempt to retry
|
||||
*
|
||||
@@ -195,41 +273,44 @@ function isRetryableError(error) {
|
||||
}
|
||||
|
||||
const SDAM_RECOVERING_CODES = new Set([
|
||||
91, // ShutdownInProgress
|
||||
189, // PrimarySteppedDown
|
||||
11600, // InterruptedAtShutdown
|
||||
11602, // InterruptedDueToReplStateChange
|
||||
13436 // NotMasterOrSecondary
|
||||
MONGODB_ERROR_CODES.ShutdownInProgress,
|
||||
MONGODB_ERROR_CODES.PrimarySteppedDown,
|
||||
MONGODB_ERROR_CODES.InterruptedAtShutdown,
|
||||
MONGODB_ERROR_CODES.InterruptedDueToReplStateChange,
|
||||
MONGODB_ERROR_CODES.NotMasterOrSecondary
|
||||
]);
|
||||
|
||||
const SDAM_NOTMASTER_CODES = new Set([
|
||||
10107, // NotMaster
|
||||
13435 // NotMasterNoSlaveOk
|
||||
MONGODB_ERROR_CODES.NotMaster,
|
||||
MONGODB_ERROR_CODES.NotMasterNoSlaveOk,
|
||||
MONGODB_ERROR_CODES.LegacyNotPrimary
|
||||
]);
|
||||
|
||||
const SDAM_NODE_SHUTTING_DOWN_ERROR_CODES = new Set([
|
||||
11600, // InterruptedAtShutdown
|
||||
91 // ShutdownInProgress
|
||||
MONGODB_ERROR_CODES.InterruptedAtShutdown,
|
||||
MONGODB_ERROR_CODES.ShutdownInProgress
|
||||
]);
|
||||
|
||||
function isRecoveringError(err) {
|
||||
if (err.code && SDAM_RECOVERING_CODES.has(err.code)) {
|
||||
return true;
|
||||
if (typeof err.code === 'number') {
|
||||
// If any error code exists, we ignore the error.message
|
||||
return SDAM_RECOVERING_CODES.has(err.code);
|
||||
}
|
||||
|
||||
return err.message.match(/not master or secondary/) || err.message.match(/node is recovering/);
|
||||
return /not master or secondary/.test(err.message) || /node is recovering/.test(err.message);
|
||||
}
|
||||
|
||||
function isNotMasterError(err) {
|
||||
if (err.code && SDAM_NOTMASTER_CODES.has(err.code)) {
|
||||
return true;
|
||||
if (typeof err.code === 'number') {
|
||||
// If any error code exists, we ignore the error.message
|
||||
return SDAM_NOTMASTER_CODES.has(err.code);
|
||||
}
|
||||
|
||||
if (isRecoveringError(err)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return err.message.match(/not master/);
|
||||
return /not master/.test(err.message);
|
||||
}
|
||||
|
||||
function isNodeShuttingDownError(err) {
|
||||
@@ -240,10 +321,9 @@ function isNodeShuttingDownError(err) {
|
||||
* Determines whether SDAM can recover from a given error. If it cannot
|
||||
* then the pool will be cleared, and server state will completely reset
|
||||
* locally.
|
||||
*
|
||||
* @ignore
|
||||
* @see https://github.com/mongodb/specifications/blob/master/source/server-discovery-and-monitoring/server-discovery-and-monitoring.rst#not-master-and-node-is-recovering
|
||||
* @param {MongoError|Error} error
|
||||
* @param {MongoError} error
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function isSDAMUnrecoverableError(error) {
|
||||
// NOTE: null check is here for a strictly pre-CMAP world, a timeout or
|
||||
@@ -252,20 +332,13 @@ function isSDAMUnrecoverableError(error) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (isRecoveringError(error) || isNotMasterError(error)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function isNetworkTimeoutError(err) {
|
||||
return err instanceof MongoNetworkError && err.message.match(/timed out/);
|
||||
return isRecoveringError(error) || isNotMasterError(error);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
MongoError,
|
||||
MongoNetworkError,
|
||||
MongoNetworkTimeoutError,
|
||||
MongoParseError,
|
||||
MongoTimeoutError,
|
||||
MongoServerSelectionError,
|
||||
@@ -273,5 +346,6 @@ module.exports = {
|
||||
isRetryableError,
|
||||
isSDAMUnrecoverableError,
|
||||
isNodeShuttingDownError,
|
||||
isNetworkTimeoutError
|
||||
isRetryableWriteError,
|
||||
isNetworkErrorBeforeHandshake
|
||||
};
|
||||
|
||||
12
node_modules/mongodb/lib/core/index.js
generated
vendored
12
node_modules/mongodb/lib/core/index.js
generated
vendored
@@ -1,10 +1,11 @@
|
||||
'use strict';
|
||||
|
||||
let BSON = require('bson');
|
||||
const require_optional = require('require_optional');
|
||||
const require_optional = require('optional-require')(require);
|
||||
const EJSON = require('./utils').retrieveEJSON();
|
||||
|
||||
try {
|
||||
// Ensure you always wrap an optional require in the try block NODE-3199
|
||||
// Attempt to grab the native BSON parser
|
||||
const BSONNative = require_optional('bson-ext');
|
||||
// If we got the native parser, use it instead of the
|
||||
@@ -14,7 +15,16 @@ try {
|
||||
}
|
||||
} catch (err) {} // eslint-disable-line
|
||||
|
||||
/** An enumeration of valid server API versions */
|
||||
const ServerApiVersion = Object.freeze({
|
||||
v1: '1'
|
||||
});
|
||||
const ValidServerApiVersions = Object.keys(ServerApiVersion).map(key => ServerApiVersion[key]);
|
||||
|
||||
module.exports = {
|
||||
// Versioned API
|
||||
ServerApiVersion,
|
||||
ValidServerApiVersions,
|
||||
// Errors
|
||||
MongoError: require('./error').MongoError,
|
||||
MongoNetworkError: require('./error').MongoNetworkError,
|
||||
|
||||
8
node_modules/mongodb/lib/core/sdam/common.js
generated
vendored
8
node_modules/mongodb/lib/core/sdam/common.js
generated
vendored
@@ -28,6 +28,13 @@ const ServerType = {
|
||||
Unknown: 'Unknown'
|
||||
};
|
||||
|
||||
// helper to get a server's type that works for both legacy and unified topologies
|
||||
function serverType(server) {
|
||||
let description = server.s.description || server.s.serverDescription;
|
||||
if (description.topologyType === TopologyType.Single) return description.servers[0].type;
|
||||
return description.type;
|
||||
}
|
||||
|
||||
const TOPOLOGY_DEFAULTS = {
|
||||
useUnifiedTopology: true,
|
||||
localThresholdMS: 15,
|
||||
@@ -54,6 +61,7 @@ module.exports = {
|
||||
TOPOLOGY_DEFAULTS,
|
||||
TopologyType,
|
||||
ServerType,
|
||||
serverType,
|
||||
drainTimerQueue,
|
||||
clearAndRemoveTimerFrom
|
||||
};
|
||||
|
||||
352
node_modules/mongodb/lib/core/sdam/monitor.js
generated
vendored
352
node_modules/mongodb/lib/core/sdam/monitor.js
generated
vendored
@@ -6,7 +6,8 @@ const connect = require('../connection/connect');
|
||||
const Connection = require('../../cmap/connection').Connection;
|
||||
const common = require('./common');
|
||||
const makeStateMachine = require('../utils').makeStateMachine;
|
||||
const MongoError = require('../error').MongoError;
|
||||
const MongoNetworkError = require('../error').MongoNetworkError;
|
||||
const BSON = require('../connection/utils').retrieveBSON();
|
||||
const makeInterruptableAsyncInterval = require('../../utils').makeInterruptableAsyncInterval;
|
||||
const calculateDurationInMs = require('../../utils').calculateDurationInMs;
|
||||
const now = require('../../utils').now;
|
||||
@@ -20,13 +21,15 @@ const kServer = Symbol('server');
|
||||
const kMonitorId = Symbol('monitorId');
|
||||
const kConnection = Symbol('connection');
|
||||
const kCancellationToken = Symbol('cancellationToken');
|
||||
const kRTTPinger = Symbol('rttPinger');
|
||||
const kRoundTripTime = Symbol('roundTripTime');
|
||||
|
||||
const STATE_CLOSED = common.STATE_CLOSED;
|
||||
const STATE_CLOSING = common.STATE_CLOSING;
|
||||
const STATE_IDLE = 'idle';
|
||||
const STATE_MONITORING = 'monitoring';
|
||||
const stateTransition = makeStateMachine({
|
||||
[STATE_CLOSING]: [STATE_CLOSING, STATE_CLOSED],
|
||||
[STATE_CLOSING]: [STATE_CLOSING, STATE_IDLE, STATE_CLOSED],
|
||||
[STATE_CLOSED]: [STATE_CLOSED, STATE_MONITORING],
|
||||
[STATE_IDLE]: [STATE_IDLE, STATE_MONITORING, STATE_CLOSING],
|
||||
[STATE_MONITORING]: [STATE_MONITORING, STATE_IDLE, STATE_CLOSING]
|
||||
@@ -62,32 +65,39 @@ class Monitor extends EventEmitter {
|
||||
heartbeatFrequencyMS:
|
||||
typeof options.heartbeatFrequencyMS === 'number' ? options.heartbeatFrequencyMS : 10000,
|
||||
minHeartbeatFrequencyMS:
|
||||
typeof options.minHeartbeatFrequencyMS === 'number' ? options.minHeartbeatFrequencyMS : 500
|
||||
typeof options.minHeartbeatFrequencyMS === 'number' ? options.minHeartbeatFrequencyMS : 500,
|
||||
useUnifiedTopology: options.useUnifiedTopology
|
||||
});
|
||||
|
||||
// TODO: refactor this to pull it directly from the pool, requires new ConnectionPool integration
|
||||
const addressParts = server.description.address.split(':');
|
||||
this.connectOptions = Object.freeze(
|
||||
Object.assign(
|
||||
{
|
||||
id: '<monitor>',
|
||||
host: addressParts[0],
|
||||
port: parseInt(addressParts[1], 10),
|
||||
bson: server.s.bson,
|
||||
connectionType: Connection
|
||||
},
|
||||
server.s.options,
|
||||
this.options,
|
||||
const connectOptions = Object.assign(
|
||||
{
|
||||
id: '<monitor>',
|
||||
host: server.description.host,
|
||||
port: server.description.port,
|
||||
bson: server.s.bson,
|
||||
connectionType: Connection
|
||||
},
|
||||
server.s.options,
|
||||
this.options,
|
||||
|
||||
// force BSON serialization options
|
||||
{
|
||||
raw: false,
|
||||
promoteLongs: true,
|
||||
promoteValues: true,
|
||||
promoteBuffers: true
|
||||
}
|
||||
)
|
||||
// force BSON serialization options
|
||||
{
|
||||
raw: false,
|
||||
promoteLongs: true,
|
||||
promoteValues: true,
|
||||
promoteBuffers: true,
|
||||
bsonRegExp: true
|
||||
}
|
||||
);
|
||||
|
||||
// ensure no authentication is used for monitoring
|
||||
delete connectOptions.credentials;
|
||||
|
||||
// ensure encryption is not requested for monitoring
|
||||
delete connectOptions.autoEncrypter;
|
||||
|
||||
this.connectOptions = Object.freeze(connectOptions);
|
||||
}
|
||||
|
||||
connect() {
|
||||
@@ -113,88 +123,182 @@ class Monitor extends EventEmitter {
|
||||
this[kMonitorId].wake();
|
||||
}
|
||||
|
||||
reset() {
|
||||
const topologyVersion = this[kServer].description.topologyVersion;
|
||||
if (isInCloseState(this) || topologyVersion == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
stateTransition(this, STATE_CLOSING);
|
||||
resetMonitorState(this);
|
||||
|
||||
// restart monitor
|
||||
stateTransition(this, STATE_IDLE);
|
||||
|
||||
// restart monitoring
|
||||
const heartbeatFrequencyMS = this.options.heartbeatFrequencyMS;
|
||||
const minHeartbeatFrequencyMS = this.options.minHeartbeatFrequencyMS;
|
||||
this[kMonitorId] = makeInterruptableAsyncInterval(monitorServer(this), {
|
||||
interval: heartbeatFrequencyMS,
|
||||
minInterval: minHeartbeatFrequencyMS
|
||||
});
|
||||
}
|
||||
|
||||
close() {
|
||||
if (isInCloseState(this)) {
|
||||
return;
|
||||
}
|
||||
|
||||
stateTransition(this, STATE_CLOSING);
|
||||
this[kCancellationToken].emit('cancel');
|
||||
if (this[kMonitorId]) {
|
||||
this[kMonitorId].stop();
|
||||
this[kMonitorId] = null;
|
||||
}
|
||||
|
||||
if (this[kConnection]) {
|
||||
this[kConnection].destroy({ force: true });
|
||||
}
|
||||
resetMonitorState(this);
|
||||
|
||||
// close monitor
|
||||
this.emit('close');
|
||||
stateTransition(this, STATE_CLOSED);
|
||||
}
|
||||
}
|
||||
|
||||
function checkServer(monitor, callback) {
|
||||
if (monitor[kConnection] && monitor[kConnection].closed) {
|
||||
monitor[kConnection] = undefined;
|
||||
function resetMonitorState(monitor) {
|
||||
if (monitor[kMonitorId]) {
|
||||
monitor[kMonitorId].stop();
|
||||
monitor[kMonitorId] = null;
|
||||
}
|
||||
|
||||
const start = now();
|
||||
if (monitor[kRTTPinger]) {
|
||||
monitor[kRTTPinger].close();
|
||||
monitor[kRTTPinger] = undefined;
|
||||
}
|
||||
|
||||
monitor[kCancellationToken].emit('cancel');
|
||||
if (monitor[kMonitorId]) {
|
||||
clearTimeout(monitor[kMonitorId]);
|
||||
monitor[kMonitorId] = undefined;
|
||||
}
|
||||
|
||||
if (monitor[kConnection]) {
|
||||
monitor[kConnection].destroy({ force: true });
|
||||
}
|
||||
}
|
||||
|
||||
function checkServer(monitor, callback) {
|
||||
let start = now();
|
||||
monitor.emit('serverHeartbeatStarted', new ServerHeartbeatStartedEvent(monitor.address));
|
||||
|
||||
function failureHandler(err) {
|
||||
if (monitor[kConnection]) {
|
||||
monitor[kConnection].destroy({ force: true });
|
||||
monitor[kConnection] = undefined;
|
||||
}
|
||||
|
||||
monitor.emit(
|
||||
'serverHeartbeatFailed',
|
||||
new ServerHeartbeatFailedEvent(calculateDurationInMs(start), err, monitor.address)
|
||||
);
|
||||
|
||||
monitor.emit('resetServer', err);
|
||||
monitor.emit('resetConnectionPool');
|
||||
callback(err);
|
||||
}
|
||||
|
||||
function successHandler(isMaster) {
|
||||
monitor.emit(
|
||||
'serverHeartbeatSucceeded',
|
||||
new ServerHeartbeatSucceededEvent(calculateDurationInMs(start), isMaster, monitor.address)
|
||||
);
|
||||
|
||||
return callback(undefined, isMaster);
|
||||
}
|
||||
|
||||
if (monitor[kConnection] != null) {
|
||||
if (monitor[kConnection] != null && !monitor[kConnection].closed) {
|
||||
const connectTimeoutMS = monitor.options.connectTimeoutMS;
|
||||
monitor[kConnection].command(
|
||||
'admin.$cmd',
|
||||
{ ismaster: true },
|
||||
{ socketTimeout: connectTimeoutMS },
|
||||
(err, result) => {
|
||||
if (err) {
|
||||
failureHandler(err);
|
||||
return;
|
||||
const maxAwaitTimeMS = monitor.options.heartbeatFrequencyMS;
|
||||
const topologyVersion = monitor[kServer].description.topologyVersion;
|
||||
const isAwaitable = topologyVersion != null;
|
||||
const serverApi = monitor[kConnection].serverApi;
|
||||
const helloOk = monitor[kConnection].helloOk;
|
||||
|
||||
const cmd = {
|
||||
[serverApi || helloOk ? 'hello' : 'ismaster']: true
|
||||
};
|
||||
|
||||
// written this way omit helloOk from the command if its false-y (do not want -> helloOk: null)
|
||||
if (helloOk) cmd.helloOk = helloOk;
|
||||
|
||||
const options = { socketTimeout: connectTimeoutMS };
|
||||
|
||||
if (isAwaitable) {
|
||||
cmd.maxAwaitTimeMS = maxAwaitTimeMS;
|
||||
cmd.topologyVersion = makeTopologyVersion(topologyVersion);
|
||||
if (connectTimeoutMS) {
|
||||
options.socketTimeout = connectTimeoutMS + maxAwaitTimeMS;
|
||||
}
|
||||
options.exhaustAllowed = true;
|
||||
if (monitor[kRTTPinger] == null) {
|
||||
monitor[kRTTPinger] = new RTTPinger(monitor[kCancellationToken], monitor.connectOptions);
|
||||
}
|
||||
}
|
||||
|
||||
monitor[kConnection].command('admin.$cmd', cmd, options, (err, result) => {
|
||||
if (err) {
|
||||
failureHandler(err);
|
||||
return;
|
||||
}
|
||||
|
||||
const isMaster = result.result;
|
||||
const rttPinger = monitor[kRTTPinger];
|
||||
|
||||
if ('isWritablePrimary' in isMaster) {
|
||||
// Provide pre-hello-style response document.
|
||||
isMaster.ismaster = isMaster.isWritablePrimary;
|
||||
}
|
||||
|
||||
const duration =
|
||||
isAwaitable && rttPinger ? rttPinger.roundTripTime : calculateDurationInMs(start);
|
||||
|
||||
monitor.emit(
|
||||
'serverHeartbeatSucceeded',
|
||||
new ServerHeartbeatSucceededEvent(duration, isMaster, monitor.address)
|
||||
);
|
||||
|
||||
// if we are using the streaming protocol then we immediately issue another `started`
|
||||
// event, otherwise the "check" is complete and return to the main monitor loop
|
||||
if (isAwaitable && isMaster.topologyVersion) {
|
||||
monitor.emit('serverHeartbeatStarted', new ServerHeartbeatStartedEvent(monitor.address));
|
||||
start = now();
|
||||
} else {
|
||||
if (monitor[kRTTPinger]) {
|
||||
monitor[kRTTPinger].close();
|
||||
monitor[kRTTPinger] = undefined;
|
||||
}
|
||||
|
||||
successHandler(result.result);
|
||||
callback(undefined, isMaster);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// connecting does an implicit `ismaster`
|
||||
connect(monitor.connectOptions, monitor[kCancellationToken], (err, conn) => {
|
||||
if (conn && isInCloseState(monitor)) {
|
||||
conn.destroy({ force: true });
|
||||
return;
|
||||
}
|
||||
|
||||
if (err) {
|
||||
monitor[kConnection] = undefined;
|
||||
|
||||
// we already reset the connection pool on network errors in all cases
|
||||
if (!(err instanceof MongoNetworkError)) {
|
||||
monitor.emit('resetConnectionPool');
|
||||
}
|
||||
|
||||
failureHandler(err);
|
||||
return;
|
||||
}
|
||||
|
||||
if (isInCloseState(monitor)) {
|
||||
conn.destroy({ force: true });
|
||||
failureHandler(new MongoError('monitor was destroyed'));
|
||||
return;
|
||||
}
|
||||
|
||||
monitor[kConnection] = conn;
|
||||
successHandler(conn.ismaster);
|
||||
monitor.emit(
|
||||
'serverHeartbeatSucceeded',
|
||||
new ServerHeartbeatSucceededEvent(
|
||||
calculateDurationInMs(start),
|
||||
conn.ismaster,
|
||||
monitor.address
|
||||
)
|
||||
);
|
||||
|
||||
callback(undefined, conn.ismaster);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -212,33 +316,113 @@ function monitorServer(monitor) {
|
||||
// TODO: the next line is a legacy event, remove in v4
|
||||
process.nextTick(() => monitor.emit('monitoring', monitor[kServer]));
|
||||
|
||||
checkServer(monitor, e0 => {
|
||||
if (e0 == null) {
|
||||
return done();
|
||||
}
|
||||
|
||||
// otherwise an error occured on initial discovery, also bail
|
||||
if (monitor[kServer].description.type === ServerType.Unknown) {
|
||||
monitor.emit('resetServer', e0);
|
||||
return done();
|
||||
}
|
||||
|
||||
// According to the SDAM specification's "Network error during server check" section, if
|
||||
// an ismaster call fails we reset the server's pool. If a server was once connected,
|
||||
// change its type to `Unknown` only after retrying once.
|
||||
monitor.emit('resetConnectionPool');
|
||||
|
||||
checkServer(monitor, e1 => {
|
||||
if (e1) {
|
||||
monitor.emit('resetServer', e1);
|
||||
checkServer(monitor, (err, isMaster) => {
|
||||
if (err) {
|
||||
// otherwise an error occured on initial discovery, also bail
|
||||
if (monitor[kServer].description.type === ServerType.Unknown) {
|
||||
monitor.emit('resetServer', err);
|
||||
return done();
|
||||
}
|
||||
}
|
||||
|
||||
done();
|
||||
});
|
||||
// if the check indicates streaming is supported, immediately reschedule monitoring
|
||||
if (isMaster && isMaster.topologyVersion) {
|
||||
setTimeout(() => {
|
||||
if (!isInCloseState(monitor)) {
|
||||
monitor[kMonitorId].wake();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
done();
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
function makeTopologyVersion(tv) {
|
||||
return {
|
||||
processId: tv.processId,
|
||||
counter: BSON.Long.fromNumber(tv.counter)
|
||||
};
|
||||
}
|
||||
|
||||
class RTTPinger {
|
||||
constructor(cancellationToken, options) {
|
||||
this[kConnection] = null;
|
||||
this[kCancellationToken] = cancellationToken;
|
||||
this[kRoundTripTime] = 0;
|
||||
this.closed = false;
|
||||
|
||||
const heartbeatFrequencyMS = options.heartbeatFrequencyMS;
|
||||
this[kMonitorId] = setTimeout(() => measureRoundTripTime(this, options), heartbeatFrequencyMS);
|
||||
}
|
||||
|
||||
get roundTripTime() {
|
||||
return this[kRoundTripTime];
|
||||
}
|
||||
|
||||
close() {
|
||||
this.closed = true;
|
||||
|
||||
clearTimeout(this[kMonitorId]);
|
||||
this[kMonitorId] = undefined;
|
||||
|
||||
if (this[kConnection]) {
|
||||
this[kConnection].destroy({ force: true });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function measureRoundTripTime(rttPinger, options) {
|
||||
const start = now();
|
||||
const cancellationToken = rttPinger[kCancellationToken];
|
||||
const heartbeatFrequencyMS = options.heartbeatFrequencyMS;
|
||||
if (rttPinger.closed) {
|
||||
return;
|
||||
}
|
||||
|
||||
function measureAndReschedule(conn) {
|
||||
if (rttPinger.closed) {
|
||||
conn.destroy({ force: true });
|
||||
return;
|
||||
}
|
||||
|
||||
if (rttPinger[kConnection] == null) {
|
||||
rttPinger[kConnection] = conn;
|
||||
}
|
||||
|
||||
rttPinger[kRoundTripTime] = calculateDurationInMs(start);
|
||||
rttPinger[kMonitorId] = setTimeout(
|
||||
() => measureRoundTripTime(rttPinger, options),
|
||||
heartbeatFrequencyMS
|
||||
);
|
||||
}
|
||||
|
||||
if (rttPinger[kConnection] == null) {
|
||||
connect(options, cancellationToken, (err, conn) => {
|
||||
if (err) {
|
||||
rttPinger[kConnection] = undefined;
|
||||
rttPinger[kRoundTripTime] = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
measureAndReschedule(conn);
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
rttPinger[kConnection].command('admin.$cmd', { ismaster: 1 }, err => {
|
||||
if (err) {
|
||||
rttPinger[kConnection] = undefined;
|
||||
rttPinger[kRoundTripTime] = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
measureAndReschedule();
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
Monitor
|
||||
};
|
||||
|
||||
110
node_modules/mongodb/lib/core/sdam/server.js
generated
vendored
110
node_modules/mongodb/lib/core/sdam/server.js
generated
vendored
@@ -7,17 +7,23 @@ const relayEvents = require('../utils').relayEvents;
|
||||
const BSON = require('../connection/utils').retrieveBSON();
|
||||
const Logger = require('../connection/logger');
|
||||
const ServerDescription = require('./server_description').ServerDescription;
|
||||
const compareTopologyVersion = require('./server_description').compareTopologyVersion;
|
||||
const ReadPreference = require('../topologies/read_preference');
|
||||
const Monitor = require('./monitor').Monitor;
|
||||
const MongoNetworkError = require('../error').MongoNetworkError;
|
||||
const MongoNetworkTimeoutError = require('../error').MongoNetworkTimeoutError;
|
||||
const collationNotSupported = require('../utils').collationNotSupported;
|
||||
const debugOptions = require('../connection/utils').debugOptions;
|
||||
const isSDAMUnrecoverableError = require('../error').isSDAMUnrecoverableError;
|
||||
const isNetworkTimeoutError = require('../error').isNetworkTimeoutError;
|
||||
const isRetryableWriteError = require('../error').isRetryableWriteError;
|
||||
const isNodeShuttingDownError = require('../error').isNodeShuttingDownError;
|
||||
const isNetworkErrorBeforeHandshake = require('../error').isNetworkErrorBeforeHandshake;
|
||||
const maxWireVersion = require('../utils').maxWireVersion;
|
||||
const makeStateMachine = require('../utils').makeStateMachine;
|
||||
const extractCommand = require('../../command_utils').extractCommand;
|
||||
const common = require('./common');
|
||||
const ServerType = common.ServerType;
|
||||
const isTransactionCommand = require('../transactions').isTransactionCommand;
|
||||
|
||||
// Used for filtering out fields for logging
|
||||
const DEBUG_FIELDS = [
|
||||
@@ -44,6 +50,7 @@ const DEBUG_FIELDS = [
|
||||
'promoteLongs',
|
||||
'promoteValues',
|
||||
'promoteBuffers',
|
||||
'bsonRegExp',
|
||||
'servername'
|
||||
];
|
||||
|
||||
@@ -107,12 +114,12 @@ class Server extends EventEmitter {
|
||||
credentials: options.credentials,
|
||||
topology
|
||||
};
|
||||
this.serverApi = options.serverApi;
|
||||
|
||||
// create the connection pool
|
||||
// NOTE: this used to happen in `connect`, we supported overriding pool options there
|
||||
const addressParts = this.description.address.split(':');
|
||||
const poolOptions = Object.assign(
|
||||
{ host: addressParts[0], port: parseInt(addressParts[1], 10), bson: this.s.bson },
|
||||
{ host: this.description.host, port: this.description.port, bson: this.s.bson },
|
||||
options
|
||||
);
|
||||
|
||||
@@ -162,6 +169,10 @@ class Server extends EventEmitter {
|
||||
return this.s.description;
|
||||
}
|
||||
|
||||
get supportsRetryableWrites() {
|
||||
return supportsRetryableWrites(this);
|
||||
}
|
||||
|
||||
get name() {
|
||||
return this.s.description.address;
|
||||
}
|
||||
@@ -241,6 +252,7 @@ class Server extends EventEmitter {
|
||||
if (typeof options === 'function') {
|
||||
(callback = options), (options = {}), (options = options || {});
|
||||
}
|
||||
options.serverApi = this.serverApi;
|
||||
|
||||
if (this.s.state === STATE_CLOSING || this.s.state === STATE_CLOSED) {
|
||||
callback(new MongoError('server is closed'));
|
||||
@@ -257,10 +269,11 @@ class Server extends EventEmitter {
|
||||
|
||||
// Debug log
|
||||
if (this.s.logger.isDebug()) {
|
||||
const extractedCommand = extractCommand(cmd);
|
||||
this.s.logger.debug(
|
||||
`executing command [${JSON.stringify({
|
||||
ns,
|
||||
cmd,
|
||||
cmd: extractedCommand.shouldRedact ? `${extractedCommand.name} details REDACTED` : cmd,
|
||||
options: debugOptions(DEBUG_FIELDS, options)
|
||||
})}] against ${this.name}`
|
||||
);
|
||||
@@ -278,7 +291,7 @@ class Server extends EventEmitter {
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
conn.command(ns, cmd, options, makeOperationHandler(this, options, cb));
|
||||
conn.command(ns, cmd, options, makeOperationHandler(this, conn, cmd, options, cb));
|
||||
}, callback);
|
||||
}
|
||||
|
||||
@@ -302,7 +315,7 @@ class Server extends EventEmitter {
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
conn.query(ns, cmd, cursorState, options, makeOperationHandler(this, options, cb));
|
||||
conn.query(ns, cmd, cursorState, options, makeOperationHandler(this, conn, cmd, options, cb));
|
||||
}, callback);
|
||||
}
|
||||
|
||||
@@ -326,7 +339,13 @@ class Server extends EventEmitter {
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
conn.getMore(ns, cursorState, batchSize, options, makeOperationHandler(this, options, cb));
|
||||
conn.getMore(
|
||||
ns,
|
||||
cursorState,
|
||||
batchSize,
|
||||
options,
|
||||
makeOperationHandler(this, conn, null, options, cb)
|
||||
);
|
||||
}, callback);
|
||||
}
|
||||
|
||||
@@ -352,7 +371,7 @@ class Server extends EventEmitter {
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
conn.killCursors(ns, cursorState, makeOperationHandler(this, null, cb));
|
||||
conn.killCursors(ns, cursorState, makeOperationHandler(this, conn, null, undefined, cb));
|
||||
}, callback);
|
||||
}
|
||||
|
||||
@@ -414,6 +433,14 @@ Object.defineProperty(Server.prototype, 'clusterTime', {
|
||||
}
|
||||
});
|
||||
|
||||
function supportsRetryableWrites(server) {
|
||||
return (
|
||||
server.description.maxWireVersion >= 6 &&
|
||||
server.description.logicalSessionTimeoutMinutes &&
|
||||
server.description.type !== ServerType.Standalone
|
||||
);
|
||||
}
|
||||
|
||||
function calculateRoundTripTime(oldRtt, duration) {
|
||||
if (oldRtt === -1) {
|
||||
return duration;
|
||||
@@ -448,6 +475,13 @@ function executeWriteOperation(args, options, callback) {
|
||||
callback(new MongoError(`server ${server.name} does not support collation`));
|
||||
return;
|
||||
}
|
||||
const unacknowledgedWrite = options.writeConcern && options.writeConcern.w === 0;
|
||||
if (unacknowledgedWrite || maxWireVersion(server) < 5) {
|
||||
if ((op === 'update' || op === 'remove') && ops.find(o => o.hint)) {
|
||||
callback(new MongoError(`servers < 3.4 do not support hint on ${op}`));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
server.s.pool.withConnection((err, conn, cb) => {
|
||||
if (err) {
|
||||
@@ -455,38 +489,78 @@ function executeWriteOperation(args, options, callback) {
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
conn[op](ns, ops, options, makeOperationHandler(server, options, cb));
|
||||
conn[op](ns, ops, options, makeOperationHandler(server, conn, ops, options, cb));
|
||||
}, callback);
|
||||
}
|
||||
|
||||
function markServerUnknown(server, error) {
|
||||
if (error instanceof MongoNetworkError && !(error instanceof MongoNetworkTimeoutError)) {
|
||||
server[kMonitor].reset();
|
||||
}
|
||||
|
||||
server.emit(
|
||||
'descriptionReceived',
|
||||
new ServerDescription(server.description.address, null, { error })
|
||||
new ServerDescription(server.description.address, null, {
|
||||
error,
|
||||
topologyVersion:
|
||||
error && error.topologyVersion ? error.topologyVersion : server.description.topologyVersion
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
function makeOperationHandler(server, options, callback) {
|
||||
function connectionIsStale(pool, connection) {
|
||||
return connection.generation !== pool.generation;
|
||||
}
|
||||
|
||||
function shouldHandleStateChangeError(server, err) {
|
||||
const etv = err.topologyVersion;
|
||||
const stv = server.description.topologyVersion;
|
||||
|
||||
return compareTopologyVersion(stv, etv) < 0;
|
||||
}
|
||||
|
||||
function inActiveTransaction(session, cmd) {
|
||||
return session && session.inTransaction() && !isTransactionCommand(cmd);
|
||||
}
|
||||
|
||||
function makeOperationHandler(server, connection, cmd, options, callback) {
|
||||
const session = options && options.session;
|
||||
|
||||
return function handleOperationResult(err, result) {
|
||||
if (err) {
|
||||
if (err && !connectionIsStale(server.s.pool, connection)) {
|
||||
if (err instanceof MongoNetworkError) {
|
||||
if (session && !session.hasEnded) {
|
||||
session.serverSession.isDirty = true;
|
||||
}
|
||||
|
||||
if (!isNetworkTimeoutError(err)) {
|
||||
if (supportsRetryableWrites(server) && !inActiveTransaction(session, cmd)) {
|
||||
err.addErrorLabel('RetryableWriteError');
|
||||
}
|
||||
|
||||
if (!(err instanceof MongoNetworkTimeoutError) || isNetworkErrorBeforeHandshake(err)) {
|
||||
markServerUnknown(server, err);
|
||||
server.s.pool.clear();
|
||||
}
|
||||
} else if (isSDAMUnrecoverableError(err)) {
|
||||
if (maxWireVersion(server) <= 7 || isNodeShuttingDownError(err)) {
|
||||
server.s.pool.clear();
|
||||
} else {
|
||||
// if pre-4.4 server, then add error label if its a retryable write error
|
||||
if (
|
||||
maxWireVersion(server) < 9 &&
|
||||
isRetryableWriteError(err) &&
|
||||
!inActiveTransaction(session, cmd)
|
||||
) {
|
||||
err.addErrorLabel('RetryableWriteError');
|
||||
}
|
||||
|
||||
markServerUnknown(server, err);
|
||||
process.nextTick(() => server.requestCheck());
|
||||
if (isSDAMUnrecoverableError(err)) {
|
||||
if (shouldHandleStateChangeError(server, err)) {
|
||||
if (maxWireVersion(server) <= 7 || isNodeShuttingDownError(err)) {
|
||||
server.s.pool.clear();
|
||||
}
|
||||
|
||||
markServerUnknown(server, err);
|
||||
process.nextTick(() => server.requestCheck());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
53
node_modules/mongodb/lib/core/sdam/server_description.js
generated
vendored
53
node_modules/mongodb/lib/core/sdam/server_description.js
generated
vendored
@@ -53,6 +53,8 @@ class ServerDescription {
|
||||
* @param {Object} [ismaster] An optional ismaster response for this server
|
||||
* @param {Object} [options] Optional settings
|
||||
* @param {Number} [options.roundTripTime] The round trip time to ping this server (in ms)
|
||||
* @param {Error} [options.error] An Error used for better reporting debugging
|
||||
* @param {any} [options.topologyVersion] The topologyVersion
|
||||
*/
|
||||
constructor(address, ismaster, options) {
|
||||
options = options || {};
|
||||
@@ -68,6 +70,10 @@ class ServerDescription {
|
||||
ismaster
|
||||
);
|
||||
|
||||
if (ismaster.isWritablePrimary != null) {
|
||||
ismaster.ismaster = ismaster.isWritablePrimary;
|
||||
}
|
||||
|
||||
this.address = address;
|
||||
this.error = options.error;
|
||||
this.roundTripTime = options.roundTripTime || -1;
|
||||
@@ -75,6 +81,7 @@ class ServerDescription {
|
||||
this.lastWriteDate = ismaster.lastWrite ? ismaster.lastWrite.lastWriteDate : null;
|
||||
this.opTime = ismaster.lastWrite ? ismaster.lastWrite.opTime : null;
|
||||
this.type = parseServerType(ismaster);
|
||||
this.topologyVersion = options.topologyVersion || ismaster.topologyVersion;
|
||||
|
||||
// direct mappings
|
||||
ISMASTER_FIELDS.forEach(field => {
|
||||
@@ -113,6 +120,16 @@ class ServerDescription {
|
||||
return WRITABLE_SERVER_TYPES.has(this.type);
|
||||
}
|
||||
|
||||
get host() {
|
||||
const chopLength = `:${this.port}`.length;
|
||||
return this.address.slice(0, -chopLength);
|
||||
}
|
||||
|
||||
get port() {
|
||||
const port = this.address.split(':').pop();
|
||||
return port ? Number.parseInt(port, 10) : port;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if another `ServerDescription` is equal to this one per the rules defined
|
||||
* in the {@link https://github.com/mongodb/specifications/blob/master/source/server-discovery-and-monitoring/server-discovery-and-monitoring.rst#serverdescription|SDAM spec}
|
||||
@@ -121,6 +138,10 @@ class ServerDescription {
|
||||
* @return {Boolean}
|
||||
*/
|
||||
equals(other) {
|
||||
const topologyVersionsEqual =
|
||||
this.topologyVersion === other.topologyVersion ||
|
||||
compareTopologyVersion(this.topologyVersion, other.topologyVersion) === 0;
|
||||
|
||||
return (
|
||||
other != null &&
|
||||
errorStrictEqual(this.error, other.error) &&
|
||||
@@ -135,7 +156,8 @@ class ServerDescription {
|
||||
? other.electionId && this.electionId.equals(other.electionId)
|
||||
: this.electionId === other.electionId) &&
|
||||
this.primary === other.primary &&
|
||||
this.logicalSessionTimeoutMinutes === other.logicalSessionTimeoutMinutes
|
||||
this.logicalSessionTimeoutMinutes === other.logicalSessionTimeoutMinutes &&
|
||||
topologyVersionsEqual
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -176,7 +198,34 @@ function parseServerType(ismaster) {
|
||||
return ServerType.Standalone;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares two topology versions.
|
||||
*
|
||||
* @param {object} lhs
|
||||
* @param {object} rhs
|
||||
* @returns A negative number if `lhs` is older than `rhs`; positive if `lhs` is newer than `rhs`; 0 if they are equivalent.
|
||||
*/
|
||||
function compareTopologyVersion(lhs, rhs) {
|
||||
if (lhs == null || rhs == null) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (lhs.processId.equals(rhs.processId)) {
|
||||
// TODO: handle counters as Longs
|
||||
if (lhs.counter === rhs.counter) {
|
||||
return 0;
|
||||
} else if (lhs.counter < rhs.counter) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
ServerDescription,
|
||||
parseServerType
|
||||
parseServerType,
|
||||
compareTopologyVersion
|
||||
};
|
||||
|
||||
126
node_modules/mongodb/lib/core/sdam/topology.js
generated
vendored
126
node_modules/mongodb/lib/core/sdam/topology.js
generated
vendored
@@ -9,12 +9,10 @@ const events = require('./events');
|
||||
const Server = require('./server').Server;
|
||||
const relayEvents = require('../utils').relayEvents;
|
||||
const ReadPreference = require('../topologies/read_preference');
|
||||
const isRetryableWritesSupported = require('../topologies/shared').isRetryableWritesSupported;
|
||||
const CoreCursor = require('../cursor').CoreCursor;
|
||||
const deprecate = require('util').deprecate;
|
||||
const BSON = require('../connection/utils').retrieveBSON();
|
||||
const createCompressionInfo = require('../topologies/shared').createCompressionInfo;
|
||||
const isRetryableError = require('../error').isRetryableError;
|
||||
const ClientSession = require('../sessions').ClientSession;
|
||||
const MongoError = require('../error').MongoError;
|
||||
const MongoServerSelectionError = require('../error').MongoServerSelectionError;
|
||||
@@ -27,6 +25,8 @@ const emitDeprecationWarning = require('../../utils').emitDeprecationWarning;
|
||||
const ServerSessionPool = require('../sessions').ServerSessionPool;
|
||||
const makeClientMetadata = require('../utils').makeClientMetadata;
|
||||
const CMAP_EVENT_NAMES = require('../../cmap/events').CMAP_EVENT_NAMES;
|
||||
const compareTopologyVersion = require('./server_description').compareTopologyVersion;
|
||||
const emitWarning = require('../../utils').emitWarning;
|
||||
|
||||
const common = require('./common');
|
||||
const drainTimerQueue = common.drainTimerQueue;
|
||||
@@ -200,6 +200,7 @@ class Topology extends EventEmitter {
|
||||
// timer management
|
||||
connectionTimers: new Set()
|
||||
};
|
||||
this.serverApi = options.serverApi;
|
||||
|
||||
if (options.srvHost) {
|
||||
this.s.srvPoller =
|
||||
@@ -275,9 +276,9 @@ class Topology extends EventEmitter {
|
||||
// connect all known servers, then attempt server selection to connect
|
||||
connectServers(this, Array.from(this.s.description.servers.values()));
|
||||
|
||||
translateReadPreference(options);
|
||||
ReadPreference.translate(options);
|
||||
const readPreference = options.readPreference || ReadPreference.primary;
|
||||
this.selectServer(readPreferenceServerSelector(readPreference), options, err => {
|
||||
const connectHandler = err => {
|
||||
if (err) {
|
||||
this.close();
|
||||
|
||||
@@ -295,7 +296,15 @@ class Topology extends EventEmitter {
|
||||
this.emit('connect', this);
|
||||
|
||||
if (typeof callback === 'function') callback(err, this);
|
||||
});
|
||||
};
|
||||
|
||||
// TODO: NODE-2471
|
||||
if (this.s.credentials) {
|
||||
this.command('admin.$cmd', { ping: 1 }, { readPreference }, connectHandler);
|
||||
return;
|
||||
}
|
||||
|
||||
this.selectServer(readPreferenceServerSelector(readPreference), options, connectHandler);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -350,7 +359,6 @@ class Topology extends EventEmitter {
|
||||
this.emit('topologyClosed', new events.TopologyClosedEvent(this.s.id));
|
||||
|
||||
stateTransition(this, STATE_CLOSED);
|
||||
this.emit('close');
|
||||
|
||||
if (typeof callback === 'function') {
|
||||
callback(err);
|
||||
@@ -381,7 +389,7 @@ class Topology extends EventEmitter {
|
||||
} else if (typeof selector === 'string') {
|
||||
readPreference = new ReadPreference(selector);
|
||||
} else {
|
||||
translateReadPreference(options);
|
||||
ReadPreference.translate(options);
|
||||
readPreference = options.readPreference || ReadPreference.primary;
|
||||
}
|
||||
|
||||
@@ -487,7 +495,11 @@ class Topology extends EventEmitter {
|
||||
this.command(
|
||||
'admin.$cmd',
|
||||
{ endSessions: sessions },
|
||||
{ readPreference: ReadPreference.primaryPreferred, noResponse: true },
|
||||
{
|
||||
readPreference: ReadPreference.primaryPreferred,
|
||||
noResponse: true,
|
||||
serverApi: this.serverApi
|
||||
},
|
||||
() => {
|
||||
// intentionally ignored, per spec
|
||||
if (typeof callback === 'function') callback();
|
||||
@@ -505,6 +517,11 @@ class Topology extends EventEmitter {
|
||||
return;
|
||||
}
|
||||
|
||||
// ignore this server update if its from an outdated topologyVersion
|
||||
if (isStaleServerDescription(this.s.description, serverDescription)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// these will be used for monitoring events later
|
||||
const previousTopologyDescription = this.s.description;
|
||||
const previousServerDescription = this.s.description.servers.get(serverDescription.address);
|
||||
@@ -647,7 +664,7 @@ class Topology extends EventEmitter {
|
||||
(callback = options), (options = {}), (options = options || {});
|
||||
}
|
||||
|
||||
translateReadPreference(options);
|
||||
ReadPreference.translate(options);
|
||||
const readPreference = options.readPreference || ReadPreference.primary;
|
||||
|
||||
this.selectServer(readPreferenceServerSelector(readPreference), options, (err, server) => {
|
||||
@@ -656,17 +673,22 @@ class Topology extends EventEmitter {
|
||||
return;
|
||||
}
|
||||
|
||||
const notAlreadyRetrying = !options.retrying;
|
||||
const retryWrites = !!options.retryWrites;
|
||||
const hasSession = !!options.session;
|
||||
const supportsRetryableWrites = server.supportsRetryableWrites;
|
||||
const notInTransaction = !hasSession || !options.session.inTransaction();
|
||||
const willRetryWrite =
|
||||
!options.retrying &&
|
||||
!!options.retryWrites &&
|
||||
options.session &&
|
||||
isRetryableWritesSupported(this) &&
|
||||
!options.session.inTransaction() &&
|
||||
notAlreadyRetrying &&
|
||||
retryWrites &&
|
||||
hasSession &&
|
||||
supportsRetryableWrites &&
|
||||
notInTransaction &&
|
||||
isWriteCommand(cmd);
|
||||
|
||||
const cb = (err, result) => {
|
||||
if (!err) return callback(null, result);
|
||||
if (!isRetryableError(err)) {
|
||||
if (!shouldRetryOperation(err)) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
@@ -708,7 +730,7 @@ class Topology extends EventEmitter {
|
||||
options = options || {};
|
||||
const topology = options.topology || this;
|
||||
const CursorClass = options.cursorFactory || this.s.Cursor;
|
||||
translateReadPreference(options);
|
||||
ReadPreference.translate(options);
|
||||
|
||||
return new CursorClass(topology, ns, cmd, options);
|
||||
}
|
||||
@@ -725,8 +747,11 @@ class Topology extends EventEmitter {
|
||||
return this.s.state === STATE_CLOSED;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated This function is deprecated and will be removed in the next major version.
|
||||
*/
|
||||
unref() {
|
||||
console.log('not implemented: `unref`');
|
||||
emitWarning('`unref` is a noop and will be removed in the next major version');
|
||||
}
|
||||
|
||||
// NOTE: There are many places in code where we explicitly check the last isMaster
|
||||
@@ -771,6 +796,16 @@ function isWriteCommand(command) {
|
||||
return RETRYABLE_WRITE_OPERATIONS.some(op => command[op]);
|
||||
}
|
||||
|
||||
function isStaleServerDescription(topologyDescription, incomingServerDescription) {
|
||||
const currentServerDescription = topologyDescription.servers.get(
|
||||
incomingServerDescription.address
|
||||
);
|
||||
const currentTopologyVersion = currentServerDescription.topologyVersion;
|
||||
return (
|
||||
compareTopologyVersion(currentTopologyVersion, incomingServerDescription.topologyVersion) > 0
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroys a server, and removes all event listeners from the instance
|
||||
*
|
||||
@@ -806,10 +841,16 @@ function parseStringSeedlist(seedlist) {
|
||||
}
|
||||
|
||||
function topologyTypeFromSeedlist(seedlist, options) {
|
||||
if (options.directConnection) {
|
||||
return TopologyType.Single;
|
||||
}
|
||||
|
||||
const replicaSet = options.replicaSet || options.setName || options.rs_name;
|
||||
if (seedlist.length === 1 && !replicaSet) return TopologyType.Single;
|
||||
if (replicaSet) return TopologyType.ReplicaSetNoPrimary;
|
||||
return TopologyType.Unknown;
|
||||
if (replicaSet == null) {
|
||||
return TopologyType.Unknown;
|
||||
}
|
||||
|
||||
return TopologyType.ReplicaSetNoPrimary;
|
||||
}
|
||||
|
||||
function randomSelection(array) {
|
||||
@@ -896,22 +937,29 @@ function executeWriteOperation(args, options, callback) {
|
||||
const ns = args.ns;
|
||||
const ops = args.ops;
|
||||
|
||||
const willRetryWrite =
|
||||
!args.retrying &&
|
||||
!!options.retryWrites &&
|
||||
options.session &&
|
||||
isRetryableWritesSupported(topology) &&
|
||||
!options.session.inTransaction();
|
||||
|
||||
topology.selectServer(writableServerSelector(), options, (err, server) => {
|
||||
if (err) {
|
||||
callback(err, null);
|
||||
return;
|
||||
}
|
||||
|
||||
const notAlreadyRetrying = !args.retrying;
|
||||
const retryWrites = !!options.retryWrites;
|
||||
const hasSession = !!options.session;
|
||||
const supportsRetryableWrites = server.supportsRetryableWrites;
|
||||
const notInTransaction = !hasSession || !options.session.inTransaction();
|
||||
const notExplaining = options.explain === undefined;
|
||||
const willRetryWrite =
|
||||
notAlreadyRetrying &&
|
||||
retryWrites &&
|
||||
hasSession &&
|
||||
supportsRetryableWrites &&
|
||||
notInTransaction &&
|
||||
notExplaining;
|
||||
|
||||
const handler = (err, result) => {
|
||||
if (!err) return callback(null, result);
|
||||
if (!isRetryableError(err)) {
|
||||
if (!shouldRetryOperation(err)) {
|
||||
err = getMMAPError(err);
|
||||
return callback(err);
|
||||
}
|
||||
@@ -939,26 +987,8 @@ function executeWriteOperation(args, options, callback) {
|
||||
});
|
||||
}
|
||||
|
||||
function translateReadPreference(options) {
|
||||
if (options.readPreference == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
let r = options.readPreference;
|
||||
if (typeof r === 'string') {
|
||||
options.readPreference = new ReadPreference(r);
|
||||
} else if (r && !(r instanceof ReadPreference) && typeof r === 'object') {
|
||||
const mode = r.mode || r.preference;
|
||||
if (mode && typeof mode === 'string') {
|
||||
options.readPreference = new ReadPreference(mode, r.tags, {
|
||||
maxStalenessSeconds: r.maxStalenessSeconds
|
||||
});
|
||||
}
|
||||
} else if (!(r instanceof ReadPreference)) {
|
||||
throw new TypeError('Invalid read preference: ' + r);
|
||||
}
|
||||
|
||||
return options;
|
||||
function shouldRetryOperation(err) {
|
||||
return err instanceof MongoError && err.hasErrorLabel('RetryableWriteError');
|
||||
}
|
||||
|
||||
function srvPollingHandler(topology) {
|
||||
|
||||
55
node_modules/mongodb/lib/core/sdam/topology_description.js
generated
vendored
55
node_modules/mongodb/lib/core/sdam/topology_description.js
generated
vendored
@@ -72,12 +72,30 @@ class TopologyDescription {
|
||||
// value among ServerDescriptions of all data-bearing server types. If any have a null
|
||||
// logicalSessionTimeoutMinutes, then TopologyDescription.logicalSessionTimeoutMinutes MUST be
|
||||
// set to null.
|
||||
const readableServers = Array.from(this.servers.values()).filter(s => s.isReadable);
|
||||
this.logicalSessionTimeoutMinutes = readableServers.reduce((result, server) => {
|
||||
if (server.logicalSessionTimeoutMinutes == null) return null;
|
||||
if (result == null) return server.logicalSessionTimeoutMinutes;
|
||||
return Math.min(result, server.logicalSessionTimeoutMinutes);
|
||||
}, null);
|
||||
this.logicalSessionTimeoutMinutes = null;
|
||||
for (const addressServerTuple of this.servers) {
|
||||
const server = addressServerTuple[1];
|
||||
if (server.isReadable) {
|
||||
if (server.logicalSessionTimeoutMinutes == null) {
|
||||
// If any of the servers have a null logicalSessionsTimeout, then the whole topology does
|
||||
this.logicalSessionTimeoutMinutes = null;
|
||||
break;
|
||||
}
|
||||
|
||||
if (this.logicalSessionTimeoutMinutes == null) {
|
||||
// First server with a non null logicalSessionsTimeout
|
||||
this.logicalSessionTimeoutMinutes = server.logicalSessionTimeoutMinutes;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Always select the smaller of the:
|
||||
// current server logicalSessionsTimeout and the topologies logicalSessionsTimeout
|
||||
this.logicalSessionTimeoutMinutes = Math.min(
|
||||
this.logicalSessionTimeoutMinutes,
|
||||
server.logicalSessionTimeoutMinutes
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -132,6 +150,10 @@ class TopologyDescription {
|
||||
let maxElectionId = this.maxElectionId;
|
||||
let commonWireVersion = this.commonWireVersion;
|
||||
|
||||
if (serverDescription.setName && setName && serverDescription.setName !== setName) {
|
||||
serverDescription = new ServerDescription(address, null);
|
||||
}
|
||||
|
||||
const serverType = serverDescription.type;
|
||||
let serverDescriptions = new Map(this.servers);
|
||||
|
||||
@@ -161,7 +183,7 @@ class TopologyDescription {
|
||||
}
|
||||
|
||||
if (topologyType === TopologyType.Unknown) {
|
||||
if (serverType === ServerType.Standalone) {
|
||||
if (serverType === ServerType.Standalone && this.servers.size !== 1) {
|
||||
serverDescriptions.delete(address);
|
||||
} else {
|
||||
topologyType = topologyTypeForServerType(serverType);
|
||||
@@ -246,6 +268,7 @@ class TopologyDescription {
|
||||
if (descriptionsWithError.length > 0) {
|
||||
return descriptionsWithError[0].error;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -274,8 +297,22 @@ class TopologyDescription {
|
||||
}
|
||||
|
||||
function topologyTypeForServerType(serverType) {
|
||||
if (serverType === ServerType.Mongos) return TopologyType.Sharded;
|
||||
if (serverType === ServerType.RSPrimary) return TopologyType.ReplicaSetWithPrimary;
|
||||
if (serverType === ServerType.Standalone) {
|
||||
return TopologyType.Single;
|
||||
}
|
||||
|
||||
if (serverType === ServerType.Mongos) {
|
||||
return TopologyType.Sharded;
|
||||
}
|
||||
|
||||
if (serverType === ServerType.RSPrimary) {
|
||||
return TopologyType.ReplicaSetWithPrimary;
|
||||
}
|
||||
|
||||
if (serverType === ServerType.RSGhost || serverType === ServerType.Unknown) {
|
||||
return TopologyType.Unknown;
|
||||
}
|
||||
|
||||
return TopologyType.ReplicaSetNoPrimary;
|
||||
}
|
||||
|
||||
|
||||
111
node_modules/mongodb/lib/core/sessions.js
generated
vendored
111
node_modules/mongodb/lib/core/sessions.js
generated
vendored
@@ -13,6 +13,7 @@ const Transaction = require('./transactions').Transaction;
|
||||
const TxnState = require('./transactions').TxnState;
|
||||
const isPromiseLike = require('./utils').isPromiseLike;
|
||||
const ReadPreference = require('./topologies/read_preference');
|
||||
const maybePromise = require('../utils').maybePromise;
|
||||
const isTransactionCommand = require('./transactions').isTransactionCommand;
|
||||
const resolveClusterTime = require('./topologies/shared').resolveClusterTime;
|
||||
const isSharded = require('./wireprotocol/shared').isSharded;
|
||||
@@ -125,25 +126,36 @@ class ClientSession extends EventEmitter {
|
||||
if (typeof options === 'function') (callback = options), (options = {});
|
||||
options = options || {};
|
||||
|
||||
if (this.hasEnded) {
|
||||
if (typeof callback === 'function') callback(null, null);
|
||||
return;
|
||||
}
|
||||
const session = this;
|
||||
return maybePromise(this, callback, done => {
|
||||
if (session.hasEnded) {
|
||||
return done();
|
||||
}
|
||||
|
||||
if (this.serverSession && this.inTransaction()) {
|
||||
this.abortTransaction(); // pass in callback?
|
||||
}
|
||||
function completeEndSession() {
|
||||
// release the server session back to the pool
|
||||
session.sessionPool.release(session.serverSession);
|
||||
session[kServerSession] = undefined;
|
||||
|
||||
// release the server session back to the pool
|
||||
this.sessionPool.release(this.serverSession);
|
||||
this[kServerSession] = undefined;
|
||||
// mark the session as ended, and emit a signal
|
||||
session.hasEnded = true;
|
||||
session.emit('ended', session);
|
||||
|
||||
// mark the session as ended, and emit a signal
|
||||
this.hasEnded = true;
|
||||
this.emit('ended', this);
|
||||
// spec indicates that we should ignore all errors for `endSessions`
|
||||
done();
|
||||
}
|
||||
|
||||
// spec indicates that we should ignore all errors for `endSessions`
|
||||
if (typeof callback === 'function') callback(null, null);
|
||||
if (session.serverSession && session.inTransaction()) {
|
||||
session.abortTransaction(err => {
|
||||
if (err) return done(err);
|
||||
completeEndSession();
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
completeEndSession();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -227,16 +239,7 @@ class ClientSession extends EventEmitter {
|
||||
* @return {Promise} A promise is returned if no callback is provided
|
||||
*/
|
||||
commitTransaction(callback) {
|
||||
if (typeof callback === 'function') {
|
||||
endTransaction(this, 'commitTransaction', callback);
|
||||
return;
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
endTransaction(this, 'commitTransaction', (err, reply) =>
|
||||
err ? reject(err) : resolve(reply)
|
||||
);
|
||||
});
|
||||
return maybePromise(this, callback, done => endTransaction(this, 'commitTransaction', done));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -246,16 +249,7 @@ class ClientSession extends EventEmitter {
|
||||
* @return {Promise} A promise is returned if no callback is provided
|
||||
*/
|
||||
abortTransaction(callback) {
|
||||
if (typeof callback === 'function') {
|
||||
endTransaction(this, 'abortTransaction', callback);
|
||||
return;
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
endTransaction(this, 'abortTransaction', (err, reply) =>
|
||||
err ? reject(err) : resolve(reply)
|
||||
);
|
||||
});
|
||||
return maybePromise(this, callback, done => endTransaction(this, 'abortTransaction', done));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -386,10 +380,7 @@ function attemptTransaction(session, startTime, fn, options) {
|
||||
}
|
||||
|
||||
if (isMaxTimeMSExpiredError(err)) {
|
||||
if (err.errorLabels == null) {
|
||||
err.errorLabels = [];
|
||||
}
|
||||
err.errorLabels.push('UnknownTransactionCommitResult');
|
||||
err.addErrorLabel('UnknownTransactionCommitResult');
|
||||
}
|
||||
|
||||
throw err;
|
||||
@@ -481,26 +472,20 @@ function endTransaction(session, commandName, callback) {
|
||||
if (commandName === 'commitTransaction') {
|
||||
session.transaction.transition(TxnState.TRANSACTION_COMMITTED);
|
||||
|
||||
if (
|
||||
e &&
|
||||
(e instanceof MongoNetworkError ||
|
||||
if (e) {
|
||||
if (
|
||||
e instanceof MongoNetworkError ||
|
||||
e instanceof MongoWriteConcernError ||
|
||||
isRetryableError(e) ||
|
||||
isMaxTimeMSExpiredError(e))
|
||||
) {
|
||||
if (e.errorLabels) {
|
||||
const idx = e.errorLabels.indexOf('TransientTransactionError');
|
||||
if (idx !== -1) {
|
||||
e.errorLabels.splice(idx, 1);
|
||||
isMaxTimeMSExpiredError(e)
|
||||
) {
|
||||
if (isUnknownTransactionCommitResult(e)) {
|
||||
e.addErrorLabel('UnknownTransactionCommitResult');
|
||||
|
||||
// per txns spec, must unpin session in this case
|
||||
session.transaction.unpinServer();
|
||||
}
|
||||
} else {
|
||||
e.errorLabels = [];
|
||||
}
|
||||
|
||||
if (isUnknownTransactionCommitResult(e)) {
|
||||
e.errorLabels.push('UnknownTransactionCommitResult');
|
||||
|
||||
// per txns spec, must unpin session in this case
|
||||
} else if (e.hasErrorLabel('TransientTransactionError')) {
|
||||
session.transaction.unpinServer();
|
||||
}
|
||||
}
|
||||
@@ -685,7 +670,12 @@ function commandSupportsReadConcern(command, options) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (command.mapReduce && options.out && (options.out.inline === 1 || options.out === 'inline')) {
|
||||
if (
|
||||
command.mapReduce &&
|
||||
options &&
|
||||
options.out &&
|
||||
(options.out.inline === 1 || options.out === 'inline')
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -708,6 +698,11 @@ function applySession(session, command, options) {
|
||||
return new MongoError('Cannot use a session that has ended');
|
||||
}
|
||||
|
||||
// SPEC-1019: silently ignore explicit session with unacknowledged write for backwards compatibility
|
||||
if (options && options.writeConcern && options.writeConcern.w === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const serverSession = session.serverSession;
|
||||
serverSession.lastUse = now();
|
||||
command.lsid = serverSession.id;
|
||||
@@ -715,7 +710,7 @@ function applySession(session, command, options) {
|
||||
// first apply non-transaction-specific sessions data
|
||||
const inTransaction = session.inTransaction() || isTransactionCommand(command);
|
||||
const isRetryableWrite = options.willRetryWrite;
|
||||
const shouldApplyReadConcern = commandSupportsReadConcern(command);
|
||||
const shouldApplyReadConcern = commandSupportsReadConcern(command, options);
|
||||
|
||||
if (serverSession.txnNumber && (isRetryableWrite || inTransaction)) {
|
||||
command.txnNumber = BSON.Long.fromNumber(serverSession.txnNumber);
|
||||
|
||||
1
node_modules/mongodb/lib/core/tools/smoke_plugin.js
generated
vendored
1
node_modules/mongodb/lib/core/tools/smoke_plugin.js
generated
vendored
@@ -52,6 +52,7 @@ exports.attachToRunner = function(runner, outputFile) {
|
||||
fs.writeFileSync(outputFile, JSON.stringify(smokeOutput));
|
||||
|
||||
// Standard NodeJS uncaught exception handler
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(err.stack);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
28
node_modules/mongodb/lib/core/topologies/mongos.js
generated
vendored
28
node_modules/mongodb/lib/core/topologies/mongos.js
generated
vendored
@@ -13,10 +13,10 @@ const cloneOptions = require('./shared').cloneOptions;
|
||||
const SessionMixins = require('./shared').SessionMixins;
|
||||
const isRetryableWritesSupported = require('./shared').isRetryableWritesSupported;
|
||||
const relayEvents = require('../utils').relayEvents;
|
||||
const isRetryableError = require('../error').isRetryableError;
|
||||
const BSON = retrieveBSON();
|
||||
const getMMAPError = require('./shared').getMMAPError;
|
||||
const makeClientMetadata = require('../utils').makeClientMetadata;
|
||||
const legacyIsRetryableWriteError = require('./shared').legacyIsRetryableWriteError;
|
||||
|
||||
/**
|
||||
* @fileOverview The **Mongos** class is a class that represents a Mongos Proxy topology and is
|
||||
@@ -71,7 +71,7 @@ var handlers = ['connect', 'close', 'error', 'timeout', 'parseError'];
|
||||
* @param {Cursor} [options.cursorFactory=Cursor] The cursor factory class used for all query cursors
|
||||
* @param {number} [options.size=5] Server connection pool size
|
||||
* @param {boolean} [options.keepAlive=true] TCP Connection keep alive enabled
|
||||
* @param {number} [options.keepAliveInitialDelay=0] Initial delay before TCP keep alive enabled
|
||||
* @param {number} [options.keepAliveInitialDelay=120000] Initial delay before TCP keep alive enabled
|
||||
* @param {number} [options.localThresholdMS=15] Cutoff latency point in MS for MongoS proxy selection
|
||||
* @param {boolean} [options.noDelay=true] TCP Connection no delay
|
||||
* @param {number} [options.connectionTimeout=1000] TCP Connection timeout setting
|
||||
@@ -88,6 +88,7 @@ var handlers = ['connect', 'close', 'error', 'timeout', 'parseError'];
|
||||
* @param {boolean} [options.promoteLongs=true] Convert Long values from the db into Numbers if they fit into 53 bits
|
||||
* @param {boolean} [options.promoteValues=true] Promotes BSON values to native types where possible, set to false to only receive wrapper types.
|
||||
* @param {boolean} [options.promoteBuffers=false] Promotes Binary BSON values to native Node Buffers.
|
||||
* @param {boolean} [options.bsonRegExp=false] By default, regex returned from MDB will be native to the language. Setting to true will ensure that a BSON.BSONRegExp object is returned.
|
||||
* @param {boolean} [options.domainsEnabled=false] Enable the wrapping of the callback in the current domain, disabled by default to avoid perf hit.
|
||||
* @param {boolean} [options.monitorCommands=false] Enable command monitoring for this topology
|
||||
* @return {Mongos} A cursor instance
|
||||
@@ -113,6 +114,18 @@ var Mongos = function(seedlist, options) {
|
||||
// Get replSet Id
|
||||
this.id = id++;
|
||||
|
||||
// deduplicate seedlist
|
||||
if (Array.isArray(seedlist)) {
|
||||
seedlist = seedlist.reduce((seeds, seed) => {
|
||||
if (seeds.find(s => s.host === seed.host && s.port === seed.port)) {
|
||||
return seeds;
|
||||
}
|
||||
|
||||
seeds.push(seed);
|
||||
return seeds;
|
||||
}, []);
|
||||
}
|
||||
|
||||
// Internal state
|
||||
this.s = {
|
||||
options: Object.assign({ metadata: makeClientMetadata(options) }, options),
|
||||
@@ -907,11 +920,12 @@ function executeWriteOperation(args, options, callback) {
|
||||
!!options.retryWrites &&
|
||||
options.session &&
|
||||
isRetryableWritesSupported(self) &&
|
||||
!options.session.inTransaction();
|
||||
!options.session.inTransaction() &&
|
||||
options.explain === undefined;
|
||||
|
||||
const handler = (err, result) => {
|
||||
if (!err) return callback(null, result);
|
||||
if (!isRetryableError(err) || !willRetryWrite) {
|
||||
if (!legacyIsRetryableWriteError(err, self) || !willRetryWrite) {
|
||||
err = getMMAPError(err);
|
||||
return callback(err);
|
||||
}
|
||||
@@ -1107,7 +1121,7 @@ Mongos.prototype.command = function(ns, cmd, options, callback) {
|
||||
|
||||
const cb = (err, result) => {
|
||||
if (!err) return callback(null, result);
|
||||
if (!isRetryableError(err)) {
|
||||
if (!legacyIsRetryableWriteError(err, self)) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
@@ -1121,8 +1135,8 @@ Mongos.prototype.command = function(ns, cmd, options, callback) {
|
||||
|
||||
// increment and assign txnNumber
|
||||
if (willRetryWrite) {
|
||||
options.session.incrementTransactionNumber();
|
||||
options.willRetryWrite = willRetryWrite;
|
||||
clonedOptions.session.incrementTransactionNumber();
|
||||
clonedOptions.willRetryWrite = willRetryWrite;
|
||||
}
|
||||
|
||||
// Execute the command
|
||||
|
||||
81
node_modules/mongodb/lib/core/topologies/read_preference.js
generated
vendored
81
node_modules/mongodb/lib/core/topologies/read_preference.js
generated
vendored
@@ -1,4 +1,5 @@
|
||||
'use strict';
|
||||
const emitWarningOnce = require('../../utils').emitWarningOnce;
|
||||
|
||||
/**
|
||||
* The **ReadPreference** class is a class that represents a MongoDB ReadPreference and is
|
||||
@@ -8,6 +9,8 @@
|
||||
* @param {array} tags The tags object
|
||||
* @param {object} [options] Additional read preference options
|
||||
* @param {number} [options.maxStalenessSeconds] Max secondary read staleness in seconds, Minimum value is 90 seconds.
|
||||
* @param {object} [options.hedge] Server mode in which the same query is dispatched in parallel to multiple replica set members.
|
||||
* @param {boolean} [options.hedge.enabled] Explicitly enable or disable hedged reads.
|
||||
* @see https://docs.mongodb.com/manual/core/read-preference/
|
||||
* @return {ReadPreference}
|
||||
*/
|
||||
@@ -18,11 +21,14 @@ const ReadPreference = function(mode, tags, options) {
|
||||
|
||||
// TODO(major): tags MUST be an array of tagsets
|
||||
if (tags && !Array.isArray(tags)) {
|
||||
console.warn(
|
||||
emitWarningOnce(
|
||||
'ReadPreference tags must be an array, this will change in the next major version'
|
||||
);
|
||||
|
||||
if (typeof tags.maxStalenessSeconds !== 'undefined') {
|
||||
const tagsHasMaxStalenessSeconds = typeof tags.maxStalenessSeconds !== 'undefined';
|
||||
const tagsHasHedge = typeof tags.hedge !== 'undefined';
|
||||
const tagsHasOptions = tagsHasMaxStalenessSeconds || tagsHasHedge;
|
||||
if (tagsHasOptions) {
|
||||
// this is likely an options object
|
||||
options = tags;
|
||||
tags = undefined;
|
||||
@@ -33,6 +39,7 @@ const ReadPreference = function(mode, tags, options) {
|
||||
|
||||
this.mode = mode;
|
||||
this.tags = tags;
|
||||
this.hedge = options && options.hedge;
|
||||
|
||||
options = options || {};
|
||||
if (options.maxStalenessSeconds != null) {
|
||||
@@ -55,6 +62,10 @@ const ReadPreference = function(mode, tags, options) {
|
||||
if (this.maxStalenessSeconds) {
|
||||
throw new TypeError('Primary read preference cannot be combined with maxStalenessSeconds');
|
||||
}
|
||||
|
||||
if (this.hedge) {
|
||||
throw new TypeError('Primary read preference cannot be combined with hedge');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -91,20 +102,19 @@ const VALID_MODES = [
|
||||
* @return {ReadPreference}
|
||||
*/
|
||||
ReadPreference.fromOptions = function(options) {
|
||||
if (!options) return null;
|
||||
const readPreference = options.readPreference;
|
||||
if (!readPreference) return null;
|
||||
const readPreferenceTags = options.readPreferenceTags;
|
||||
|
||||
if (readPreference == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const maxStalenessSeconds = options.maxStalenessSeconds;
|
||||
if (typeof readPreference === 'string') {
|
||||
return new ReadPreference(readPreference, readPreferenceTags);
|
||||
} else if (!(readPreference instanceof ReadPreference) && typeof readPreference === 'object') {
|
||||
const mode = readPreference.mode || readPreference.preference;
|
||||
if (mode && typeof mode === 'string') {
|
||||
return new ReadPreference(mode, readPreference.tags, {
|
||||
maxStalenessSeconds: readPreference.maxStalenessSeconds
|
||||
maxStalenessSeconds: readPreference.maxStalenessSeconds || maxStalenessSeconds,
|
||||
hedge: readPreference.hedge
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -112,6 +122,60 @@ ReadPreference.fromOptions = function(options) {
|
||||
return readPreference;
|
||||
};
|
||||
|
||||
/**
|
||||
* Resolves a read preference based on well-defined inheritance rules. This method will not only
|
||||
* determine the read preference (if there is one), but will also ensure the returned value is a
|
||||
* properly constructed instance of `ReadPreference`.
|
||||
*
|
||||
* @param {Collection|Db|MongoClient} parent The parent of the operation on which to determine the read
|
||||
* preference, used for determining the inherited read preference.
|
||||
* @param {object} options The options passed into the method, potentially containing a read preference
|
||||
* @returns {(ReadPreference|null)} The resolved read preference
|
||||
*/
|
||||
ReadPreference.resolve = function(parent, options) {
|
||||
options = options || {};
|
||||
const session = options.session;
|
||||
|
||||
const inheritedReadPreference = parent && parent.readPreference;
|
||||
|
||||
let readPreference;
|
||||
if (options.readPreference) {
|
||||
readPreference = ReadPreference.fromOptions(options);
|
||||
} else if (session && session.inTransaction() && session.transaction.options.readPreference) {
|
||||
// The transaction’s read preference MUST override all other user configurable read preferences.
|
||||
readPreference = session.transaction.options.readPreference;
|
||||
} else if (inheritedReadPreference != null) {
|
||||
readPreference = inheritedReadPreference;
|
||||
} else {
|
||||
readPreference = ReadPreference.primary;
|
||||
}
|
||||
|
||||
return typeof readPreference === 'string' ? new ReadPreference(readPreference) : readPreference;
|
||||
};
|
||||
|
||||
/**
|
||||
* Replaces options.readPreference with a ReadPreference instance
|
||||
*/
|
||||
ReadPreference.translate = function(options) {
|
||||
if (options.readPreference == null) return options;
|
||||
const r = options.readPreference;
|
||||
|
||||
if (typeof r === 'string') {
|
||||
options.readPreference = new ReadPreference(r);
|
||||
} else if (r && !(r instanceof ReadPreference) && typeof r === 'object') {
|
||||
const mode = r.mode || r.preference;
|
||||
if (mode && typeof mode === 'string') {
|
||||
options.readPreference = new ReadPreference(mode, r.tags, {
|
||||
maxStalenessSeconds: r.maxStalenessSeconds
|
||||
});
|
||||
}
|
||||
} else if (!(r instanceof ReadPreference)) {
|
||||
throw new TypeError('Invalid read preference: ' + r);
|
||||
}
|
||||
|
||||
return options;
|
||||
};
|
||||
|
||||
/**
|
||||
* Validate if a mode is legal
|
||||
*
|
||||
@@ -165,6 +229,7 @@ ReadPreference.prototype.toJSON = function() {
|
||||
const readPreference = { mode: this.mode };
|
||||
if (Array.isArray(this.tags)) readPreference.tags = this.tags;
|
||||
if (this.maxStalenessSeconds) readPreference.maxStalenessSeconds = this.maxStalenessSeconds;
|
||||
if (this.hedge) readPreference.hedge = this.hedge;
|
||||
return readPreference;
|
||||
};
|
||||
|
||||
|
||||
31
node_modules/mongodb/lib/core/topologies/replset.js
generated
vendored
31
node_modules/mongodb/lib/core/topologies/replset.js
generated
vendored
@@ -15,10 +15,10 @@ const Interval = require('./shared').Interval;
|
||||
const SessionMixins = require('./shared').SessionMixins;
|
||||
const isRetryableWritesSupported = require('./shared').isRetryableWritesSupported;
|
||||
const relayEvents = require('../utils').relayEvents;
|
||||
const isRetryableError = require('../error').isRetryableError;
|
||||
const BSON = retrieveBSON();
|
||||
const getMMAPError = require('./shared').getMMAPError;
|
||||
const makeClientMetadata = require('../utils').makeClientMetadata;
|
||||
const legacyIsRetryableWriteError = require('./shared').legacyIsRetryableWriteError;
|
||||
const now = require('../../utils').now;
|
||||
const calculateDurationInMs = require('../../utils').calculateDurationInMs;
|
||||
|
||||
@@ -72,7 +72,7 @@ var handlers = ['connect', 'close', 'error', 'timeout', 'parseError'];
|
||||
* @param {Cursor} [options.cursorFactory=Cursor] The cursor factory class used for all query cursors
|
||||
* @param {number} [options.size=5] Server connection pool size
|
||||
* @param {boolean} [options.keepAlive=true] TCP Connection keep alive enabled
|
||||
* @param {number} [options.keepAliveInitialDelay=0] Initial delay before TCP keep alive enabled
|
||||
* @param {number} [options.keepAliveInitialDelay=120000] Initial delay before TCP keep alive enabled
|
||||
* @param {boolean} [options.noDelay=true] TCP Connection no delay
|
||||
* @param {number} [options.connectionTimeout=10000] TCP Connection timeout setting
|
||||
* @param {number} [options.socketTimeout=0] TCP Socket timeout setting
|
||||
@@ -88,6 +88,7 @@ var handlers = ['connect', 'close', 'error', 'timeout', 'parseError'];
|
||||
* @param {boolean} [options.promoteLongs=true] Convert Long values from the db into Numbers if they fit into 53 bits
|
||||
* @param {boolean} [options.promoteValues=true] Promotes BSON values to native types where possible, set to false to only receive wrapper types.
|
||||
* @param {boolean} [options.promoteBuffers=false] Promotes Binary BSON values to native Node Buffers.
|
||||
* @param {boolean} [options.bsonRegExp=false] By default, regex returned from MDB will be native to the language. Setting to true will ensure that a BSON.BSONRegExp object is returned.
|
||||
* @param {number} [options.pingInterval=5000] Ping interval to check the response time to the different servers
|
||||
* @param {number} [options.localThresholdMS=15] Cutoff latency point in MS for Replicaset member selection
|
||||
* @param {boolean} [options.domainsEnabled=false] Enable the wrapping of the callback in the current domain, disabled by default to avoid perf hit.
|
||||
@@ -541,7 +542,7 @@ var monitorServer = function(host, self, options) {
|
||||
self.s.options.secondaryOnlyConnectionAllowed) ||
|
||||
self.s.replicaSetState.hasPrimary())
|
||||
) {
|
||||
self.state = CONNECTED;
|
||||
stateTransition(self, CONNECTED);
|
||||
|
||||
// Emit connected sign
|
||||
process.nextTick(function() {
|
||||
@@ -558,7 +559,7 @@ var monitorServer = function(host, self, options) {
|
||||
self.s.options.secondaryOnlyConnectionAllowed) ||
|
||||
self.s.replicaSetState.hasPrimary())
|
||||
) {
|
||||
self.state = CONNECTED;
|
||||
stateTransition(self, CONNECTING);
|
||||
|
||||
// Rexecute any stalled operation
|
||||
rexecuteOperations(self);
|
||||
@@ -787,7 +788,7 @@ function handleInitialConnectEvent(self, event) {
|
||||
// Do we have a primary or primaryAndSecondary
|
||||
if (shouldTriggerConnect(self)) {
|
||||
// We are connected
|
||||
self.state = CONNECTED;
|
||||
stateTransition(self, CONNECTED);
|
||||
|
||||
// Set initial connect state
|
||||
self.initialConnectState.connect = true;
|
||||
@@ -919,7 +920,7 @@ ReplSet.prototype.connect = function(options) {
|
||||
);
|
||||
});
|
||||
|
||||
// Error out as high availbility interval must be < than socketTimeout
|
||||
// Error out as high availability interval must be < than socketTimeout
|
||||
if (
|
||||
this.s.options.socketTimeout > 0 &&
|
||||
this.s.options.socketTimeout <= this.s.options.haInterval
|
||||
@@ -975,14 +976,19 @@ ReplSet.prototype.destroy = function(options, callback) {
|
||||
// Emit toplogy closing event
|
||||
emitSDAMEvent(this, 'topologyClosed', { topologyId: this.id });
|
||||
|
||||
// Transition state
|
||||
stateTransition(this, DESTROYED);
|
||||
|
||||
if (typeof callback === 'function') {
|
||||
callback(null, null);
|
||||
}
|
||||
};
|
||||
|
||||
if (this.state === DESTROYED) {
|
||||
if (typeof callback === 'function') callback(null, null);
|
||||
return;
|
||||
}
|
||||
|
||||
// Transition state
|
||||
stateTransition(this, DESTROYED);
|
||||
|
||||
// Clear out any monitoring process
|
||||
if (this.haTimeoutId) clearTimeout(this.haTimeoutId);
|
||||
|
||||
@@ -1188,7 +1194,8 @@ function executeWriteOperation(args, options, callback) {
|
||||
!!options.retryWrites &&
|
||||
options.session &&
|
||||
isRetryableWritesSupported(self) &&
|
||||
!options.session.inTransaction();
|
||||
!options.session.inTransaction() &&
|
||||
options.explain === undefined;
|
||||
|
||||
if (!self.s.replicaSetState.hasPrimary()) {
|
||||
if (self.s.disconnectHandler) {
|
||||
@@ -1202,7 +1209,7 @@ function executeWriteOperation(args, options, callback) {
|
||||
|
||||
const handler = (err, result) => {
|
||||
if (!err) return callback(null, result);
|
||||
if (!isRetryableError(err)) {
|
||||
if (!legacyIsRetryableWriteError(err, self)) {
|
||||
err = getMMAPError(err);
|
||||
return callback(err);
|
||||
}
|
||||
@@ -1365,7 +1372,7 @@ ReplSet.prototype.command = function(ns, cmd, options, callback) {
|
||||
|
||||
const cb = (err, result) => {
|
||||
if (!err) return callback(null, result);
|
||||
if (!isRetryableError(err)) {
|
||||
if (!legacyIsRetryableWriteError(err, self)) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
|
||||
8
node_modules/mongodb/lib/core/topologies/replset_state.js
generated
vendored
8
node_modules/mongodb/lib/core/topologies/replset_state.js
generated
vendored
@@ -34,7 +34,7 @@ var ReplSetState = function(options) {
|
||||
// Add event listener
|
||||
EventEmitter.call(this);
|
||||
// Topology state
|
||||
this.topologyType = TopologyType.ReplicaSetNoPrimary;
|
||||
this.topologyType = options.setName ? TopologyType.ReplicaSetNoPrimary : TopologyType.Unknown;
|
||||
this.setName = options.setName;
|
||||
|
||||
// Server set
|
||||
@@ -218,7 +218,8 @@ const isArbiter = ismaster => ismaster.arbiterOnly && ismaster.setName;
|
||||
ReplSetState.prototype.update = function(server) {
|
||||
var self = this;
|
||||
// Get the current ismaster
|
||||
var ismaster = server.lastIsMaster();
|
||||
const ismaster = server.lastIsMaster();
|
||||
if (ismaster && ismaster.isWritablePrimary) ismaster.ismaster = ismaster.isWritablePrimary;
|
||||
|
||||
// Get the server name and lowerCase it
|
||||
var serverName = server.name.toLowerCase();
|
||||
@@ -358,7 +359,8 @@ ReplSetState.prototype.update = function(server) {
|
||||
// Standalone server, destroy and return
|
||||
//
|
||||
if (ismaster && ismaster.ismaster && !ismaster.setName) {
|
||||
this.topologyType = this.primary ? TopologyType.ReplicaSetWithPrimary : TopologyType.Unknown;
|
||||
// We should not mark the topology as Unknown because of one standalone
|
||||
// we should just remove this server from the set
|
||||
this.remove(server, { force: true });
|
||||
return false;
|
||||
}
|
||||
|
||||
20
node_modules/mongodb/lib/core/topologies/server.js
generated
vendored
20
node_modules/mongodb/lib/core/topologies/server.js
generated
vendored
@@ -16,6 +16,7 @@ var inherits = require('util').inherits,
|
||||
createCompressionInfo = require('./shared').createCompressionInfo,
|
||||
resolveClusterTime = require('./shared').resolveClusterTime,
|
||||
SessionMixins = require('./shared').SessionMixins,
|
||||
extractCommand = require('../../command_utils').extractCommand,
|
||||
relayEvents = require('../utils').relayEvents;
|
||||
|
||||
const collationNotSupported = require('../utils').collationNotSupported;
|
||||
@@ -46,6 +47,7 @@ var debugFields = [
|
||||
'promoteLongs',
|
||||
'promoteValues',
|
||||
'promoteBuffers',
|
||||
'bsonRegExp',
|
||||
'servername'
|
||||
];
|
||||
|
||||
@@ -72,10 +74,10 @@ function topologyId(server) {
|
||||
* @param {number} options.port The server port
|
||||
* @param {number} [options.size=5] Server connection pool size
|
||||
* @param {boolean} [options.keepAlive=true] TCP Connection keep alive enabled
|
||||
* @param {number} [options.keepAliveInitialDelay=300000] Initial delay before TCP keep alive enabled
|
||||
* @param {number} [options.keepAliveInitialDelay=120000] Initial delay before TCP keep alive enabled
|
||||
* @param {boolean} [options.noDelay=true] TCP Connection no delay
|
||||
* @param {number} [options.connectionTimeout=30000] TCP Connection timeout setting
|
||||
* @param {number} [options.socketTimeout=360000] TCP Socket timeout setting
|
||||
* @param {number} [options.socketTimeout=0] TCP Socket timeout setting
|
||||
* @param {boolean} [options.ssl=false] Use SSL for connection
|
||||
* @param {boolean|function} [options.checkServerIdentity=true] Ensure we check server identify during SSL, set to false to disable checking. Only works for Node 0.12.x or higher. You can pass in a boolean or your own checkServerIdentity override function.
|
||||
* @param {Buffer} [options.ca] SSL Certificate store binary buffer
|
||||
@@ -88,6 +90,7 @@ function topologyId(server) {
|
||||
* @param {boolean} [options.promoteLongs=true] Convert Long values from the db into Numbers if they fit into 53 bits
|
||||
* @param {boolean} [options.promoteValues=true] Promotes BSON values to native types where possible, set to false to only receive wrapper types.
|
||||
* @param {boolean} [options.promoteBuffers=false] Promotes Binary BSON values to native Node Buffers.
|
||||
* @param {boolean} [options.bsonRegExp=false] By default, regex returned from MDB will be native to the language. Setting to true will ensure that a BSON.BSONRegExp object is returned.
|
||||
* @param {string} [options.appname=null] Application name, passed in on ismaster call and logged in mongod server logs. Maximum size 128 bytes.
|
||||
* @param {boolean} [options.domainsEnabled=false] Enable the wrapping of the callback in the current domain, disabled by default to avoid perf hit.
|
||||
* @param {boolean} [options.monitorCommands=false] Enable command monitoring for this topology
|
||||
@@ -389,7 +392,7 @@ var eventHandler = function(self, event) {
|
||||
event === 'timeout' ||
|
||||
event === 'reconnect' ||
|
||||
event === 'attemptReconnect' ||
|
||||
'reconnectFailed'
|
||||
event === 'reconnectFailed'
|
||||
) {
|
||||
// Remove server instance from accounting
|
||||
if (
|
||||
@@ -608,18 +611,20 @@ Server.prototype.command = function(ns, cmd, options, callback) {
|
||||
options = Object.assign({}, options, { wireProtocolCommand: false });
|
||||
|
||||
// Debug log
|
||||
if (self.s.logger.isDebug())
|
||||
if (self.s.logger.isDebug()) {
|
||||
const extractedCommand = extractCommand(cmd);
|
||||
self.s.logger.debug(
|
||||
f(
|
||||
'executing command [%s] against %s',
|
||||
JSON.stringify({
|
||||
ns: ns,
|
||||
cmd: cmd,
|
||||
cmd: extractedCommand.shouldRedact ? `${extractedCommand.name} details REDACTED` : cmd,
|
||||
options: debugOptions(debugFields, options)
|
||||
}),
|
||||
self.name
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// If we are not connected or have a disconnectHandler specified
|
||||
if (disconnectHandler(self, 'command', ns, cmd, options, callback)) return;
|
||||
@@ -864,12 +869,14 @@ Server.prototype.destroy = function(options, callback) {
|
||||
}
|
||||
|
||||
// No pool, return
|
||||
if (!self.s.pool) {
|
||||
if (!self.s.pool || this._destroyed) {
|
||||
this._destroyed = true;
|
||||
if (typeof callback === 'function') callback(null, null);
|
||||
return;
|
||||
}
|
||||
|
||||
this._destroyed = true;
|
||||
|
||||
// Emit close event
|
||||
if (options.emitClose) {
|
||||
self.emit('close', self);
|
||||
@@ -900,7 +907,6 @@ Server.prototype.destroy = function(options, callback) {
|
||||
|
||||
// Destroy the pool
|
||||
this.s.pool.destroy(options.force, callback);
|
||||
this._destroyed = true;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
58
node_modules/mongodb/lib/core/topologies/shared.js
generated
vendored
58
node_modules/mongodb/lib/core/topologies/shared.js
generated
vendored
@@ -1,9 +1,14 @@
|
||||
'use strict';
|
||||
|
||||
const MONGODB_ERROR_CODES = require('../../error_codes').MONGODB_ERROR_CODES;
|
||||
const ReadPreference = require('./read_preference');
|
||||
const TopologyType = require('../sdam/common').TopologyType;
|
||||
const MongoError = require('../error').MongoError;
|
||||
const isRetryableWriteError = require('../error').isRetryableWriteError;
|
||||
const maxWireVersion = require('../utils').maxWireVersion;
|
||||
const MongoNetworkError = require('../error').MongoNetworkError;
|
||||
|
||||
const MMAPv1_RETRY_WRITES_ERROR_CODE = 20;
|
||||
const MMAPv1_RETRY_WRITES_ERROR_CODE = MONGODB_ERROR_CODES.IllegalOperation;
|
||||
|
||||
/**
|
||||
* Emit event if it exists
|
||||
@@ -416,18 +421,39 @@ function getMMAPError(err) {
|
||||
return newErr;
|
||||
}
|
||||
|
||||
module.exports.SessionMixins = SessionMixins;
|
||||
module.exports.resolveClusterTime = resolveClusterTime;
|
||||
module.exports.inquireServerState = inquireServerState;
|
||||
module.exports.getTopologyType = getTopologyType;
|
||||
module.exports.emitServerDescriptionChanged = emitServerDescriptionChanged;
|
||||
module.exports.emitTopologyDescriptionChanged = emitTopologyDescriptionChanged;
|
||||
module.exports.cloneOptions = cloneOptions;
|
||||
module.exports.createCompressionInfo = createCompressionInfo;
|
||||
module.exports.clone = clone;
|
||||
module.exports.diff = diff;
|
||||
module.exports.Interval = Interval;
|
||||
module.exports.Timeout = Timeout;
|
||||
module.exports.isRetryableWritesSupported = isRetryableWritesSupported;
|
||||
module.exports.getMMAPError = getMMAPError;
|
||||
module.exports.topologyType = topologyType;
|
||||
// NOTE: only used for legacy topology types
|
||||
function legacyIsRetryableWriteError(err, topology) {
|
||||
if (!(err instanceof MongoError)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// if pre-4.4 server, then add error label if its a retryable write error
|
||||
if (
|
||||
isRetryableWritesSupported(topology) &&
|
||||
(err instanceof MongoNetworkError ||
|
||||
(maxWireVersion(topology) < 9 && isRetryableWriteError(err)))
|
||||
) {
|
||||
err.addErrorLabel('RetryableWriteError');
|
||||
}
|
||||
|
||||
return err.hasErrorLabel('RetryableWriteError');
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
SessionMixins,
|
||||
resolveClusterTime,
|
||||
inquireServerState,
|
||||
getTopologyType,
|
||||
emitServerDescriptionChanged,
|
||||
emitTopologyDescriptionChanged,
|
||||
cloneOptions,
|
||||
createCompressionInfo,
|
||||
clone,
|
||||
diff,
|
||||
Interval,
|
||||
Timeout,
|
||||
isRetryableWritesSupported,
|
||||
getMMAPError,
|
||||
topologyType,
|
||||
legacyIsRetryableWriteError
|
||||
};
|
||||
|
||||
6
node_modules/mongodb/lib/core/transactions.js
generated
vendored
6
node_modules/mongodb/lib/core/transactions.js
generated
vendored
@@ -150,7 +150,11 @@ class Transaction {
|
||||
const nextStates = stateMachine[this.state];
|
||||
if (nextStates && nextStates.indexOf(nextState) !== -1) {
|
||||
this.state = nextState;
|
||||
if (this.state === TxnState.NO_TRANSACTION || this.state === TxnState.STARTING_TRANSACTION) {
|
||||
if (
|
||||
this.state === TxnState.NO_TRANSACTION ||
|
||||
this.state === TxnState.STARTING_TRANSACTION ||
|
||||
this.state === TxnState.TRANSACTION_ABORTED
|
||||
) {
|
||||
this.unpinServer();
|
||||
}
|
||||
return;
|
||||
|
||||
79
node_modules/mongodb/lib/core/uri_parser.js
generated
vendored
79
node_modules/mongodb/lib/core/uri_parser.js
generated
vendored
@@ -4,6 +4,7 @@ const qs = require('querystring');
|
||||
const dns = require('dns');
|
||||
const MongoParseError = require('./error').MongoParseError;
|
||||
const ReadPreference = require('./topologies/read_preference');
|
||||
const emitWarningOnce = require('../utils').emitWarningOnce;
|
||||
|
||||
/**
|
||||
* The following regular expression validates a connection string and breaks the
|
||||
@@ -11,6 +12,11 @@ const ReadPreference = require('./topologies/read_preference');
|
||||
*/
|
||||
const HOSTS_RX = /(mongodb(?:\+srv|)):\/\/(?: (?:[^:]*) (?: : ([^@]*) )? @ )?([^/?]*)(?:\/|)(.*)/;
|
||||
|
||||
// Options that reference file paths should not be parsed
|
||||
const FILE_PATH_OPTIONS = new Set(
|
||||
['sslCA', 'sslCert', 'sslKey', 'tlsCAFile', 'tlsCertificateKeyFile'].map(key => key.toLowerCase())
|
||||
);
|
||||
|
||||
/**
|
||||
* Determines whether a provided address matches the provided parent domain in order
|
||||
* to avoid certain attack vectors.
|
||||
@@ -37,12 +43,18 @@ function matchesParentDomain(srvAddress, parentDomain) {
|
||||
function parseSrvConnectionString(uri, options, callback) {
|
||||
const result = URL.parse(uri, true);
|
||||
|
||||
if (options.directConnection || options.directconnection) {
|
||||
return callback(new MongoParseError('directConnection not supported with SRV URI'));
|
||||
}
|
||||
|
||||
if (result.hostname.split('.').length < 3) {
|
||||
return callback(new MongoParseError('URI does not have hostname, domain name and tld'));
|
||||
}
|
||||
|
||||
result.domainLength = result.hostname.split('.').length;
|
||||
if (result.pathname && result.pathname.match(',')) {
|
||||
|
||||
const hostname = uri.substring('mongodb+srv://'.length).split('/')[0];
|
||||
if (hostname.match(',')) {
|
||||
return callback(new MongoParseError('Invalid URI, cannot contain multiple hostnames'));
|
||||
}
|
||||
|
||||
@@ -82,7 +94,7 @@ function parseSrvConnectionString(uri, options, callback) {
|
||||
// Resolve TXT record and add options from there if they exist.
|
||||
dns.resolveTxt(lookupAddress, (err, record) => {
|
||||
if (err) {
|
||||
if (err.code !== 'ENODATA') {
|
||||
if (err.code !== 'ENODATA' && err.code !== 'ENOTFOUND') {
|
||||
return callback(err);
|
||||
}
|
||||
record = null;
|
||||
@@ -94,6 +106,11 @@ function parseSrvConnectionString(uri, options, callback) {
|
||||
}
|
||||
|
||||
record = qs.parse(record[0].join(''));
|
||||
|
||||
if (Object.keys(record).some(k => k.toLowerCase() === 'loadbalanced')) {
|
||||
return callback(new MongoParseError('Load balancer mode requires driver version 4+'));
|
||||
}
|
||||
|
||||
if (Object.keys(record).some(key => key !== 'authSource' && key !== 'replicaSet')) {
|
||||
return callback(
|
||||
new MongoParseError('Text record must only set `authSource` or `replicaSet`')
|
||||
@@ -171,6 +188,7 @@ const STRING_OPTIONS = new Set(['authsource', 'replicaset']);
|
||||
// NOTE: this list exists in native already, if it is merged here we should deduplicate
|
||||
const AUTH_MECHANISMS = new Set([
|
||||
'GSSAPI',
|
||||
'MONGODB-AWS',
|
||||
'MONGODB-X509',
|
||||
'MONGODB-CR',
|
||||
'DEFAULT',
|
||||
@@ -214,7 +232,8 @@ const CASE_TRANSLATION = {
|
||||
tlscertificatekeyfile: 'tlsCertificateKeyFile',
|
||||
tlscertificatekeyfilepassword: 'tlsCertificateKeyFilePassword',
|
||||
wtimeout: 'wTimeoutMS',
|
||||
j: 'journal'
|
||||
j: 'journal',
|
||||
directconnection: 'directConnection'
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -257,7 +276,9 @@ function applyConnectionStringOption(obj, key, value, options) {
|
||||
|
||||
if (key === 'authmechanism' && !AUTH_MECHANISMS.has(value)) {
|
||||
throw new MongoParseError(
|
||||
'Value for `authMechanism` must be one of: `DEFAULT`, `GSSAPI`, `PLAIN`, `MONGODB-X509`, `SCRAM-SHA-1`, `SCRAM-SHA-256`'
|
||||
`Value for authMechanism must be one of: ${Array.from(AUTH_MECHANISMS).join(
|
||||
', '
|
||||
)}, found: ${value}`
|
||||
);
|
||||
}
|
||||
|
||||
@@ -357,6 +378,16 @@ function applyAuthExpectations(parsed) {
|
||||
parsed.auth = Object.assign({}, parsed.auth, { db: '$external' });
|
||||
}
|
||||
|
||||
if (authMechanism === 'MONGODB-AWS') {
|
||||
if (authSource != null && authSource !== '$external') {
|
||||
throw new MongoParseError(
|
||||
`Invalid source \`${authSource}\` for mechanism \`${authMechanism}\` specified.`
|
||||
);
|
||||
}
|
||||
|
||||
parsed.auth = Object.assign({}, parsed.auth, { db: '$external' });
|
||||
}
|
||||
|
||||
if (authMechanism === 'MONGODB-X509') {
|
||||
if (parsed.auth && parsed.auth.password != null) {
|
||||
throw new MongoParseError(`Password not allowed for mechanism \`${authMechanism}\``);
|
||||
@@ -406,14 +437,21 @@ function parseQueryString(query, options) {
|
||||
}
|
||||
|
||||
const normalizedKey = key.toLowerCase();
|
||||
const parsedValue = parseQueryStringItemValue(normalizedKey, value);
|
||||
if (normalizedKey === 'serverapi') {
|
||||
throw new MongoParseError(
|
||||
'URI cannot contain `serverApi`, it can only be passed to the client'
|
||||
);
|
||||
}
|
||||
const parsedValue = FILE_PATH_OPTIONS.has(normalizedKey)
|
||||
? value
|
||||
: parseQueryStringItemValue(normalizedKey, value);
|
||||
applyConnectionStringOption(result, normalizedKey, parsedValue, options);
|
||||
}
|
||||
|
||||
// special cases for known deprecated options
|
||||
if (result.wtimeout && result.wtimeoutms) {
|
||||
delete result.wtimeout;
|
||||
console.warn('Unsupported option `wtimeout` specified');
|
||||
emitWarningOnce('Unsupported option `wtimeout` specified');
|
||||
}
|
||||
|
||||
return Object.keys(result).length ? result : null;
|
||||
@@ -552,10 +590,6 @@ function parseConnectionString(uri, options, callback) {
|
||||
return callback(new MongoParseError('Invalid protocol provided'));
|
||||
}
|
||||
|
||||
if (protocol === PROTOCOL_MONGODB_SRV) {
|
||||
return parseSrvConnectionString(uri, options, callback);
|
||||
}
|
||||
|
||||
const dbAndQuery = cap[4].split('?');
|
||||
const db = dbAndQuery.length > 0 ? dbAndQuery[0] : null;
|
||||
const query = dbAndQuery.length > 1 ? dbAndQuery[1] : null;
|
||||
@@ -568,6 +602,15 @@ function parseConnectionString(uri, options, callback) {
|
||||
}
|
||||
|
||||
parsedOptions = Object.assign({}, parsedOptions, options);
|
||||
|
||||
if (Object.keys(parsedOptions).some(k => k.toLowerCase() === 'loadbalanced')) {
|
||||
return callback(new MongoParseError('Load balancer mode requires driver version 4+'));
|
||||
}
|
||||
|
||||
if (protocol === PROTOCOL_MONGODB_SRV) {
|
||||
return parseSrvConnectionString(uri, parsedOptions, callback);
|
||||
}
|
||||
|
||||
const auth = { username: null, password: null, db: db && db !== '' ? qs.unescape(db) : null };
|
||||
if (parsedOptions.auth) {
|
||||
// maintain support for legacy options passed into `MongoClient`
|
||||
@@ -661,6 +704,22 @@ function parseConnectionString(uri, options, callback) {
|
||||
return callback(new MongoParseError('No hostname or hostnames provided in connection string'));
|
||||
}
|
||||
|
||||
const directConnection = !!parsedOptions.directConnection;
|
||||
if (directConnection && hosts.length !== 1) {
|
||||
// If the option is set to true, the driver MUST validate that there is exactly one host given
|
||||
// in the host list in the URI, and fail client creation otherwise.
|
||||
return callback(new MongoParseError('directConnection option requires exactly one host'));
|
||||
}
|
||||
|
||||
// NOTE: this behavior will go away in v4.0, we will always auto discover there
|
||||
if (
|
||||
parsedOptions.directConnection == null &&
|
||||
hosts.length === 1 &&
|
||||
parsedOptions.replicaSet == null
|
||||
) {
|
||||
parsedOptions.directConnection = true;
|
||||
}
|
||||
|
||||
const result = {
|
||||
hosts: hosts,
|
||||
auth: auth.db || auth.username ? auth : null,
|
||||
|
||||
38
node_modules/mongodb/lib/core/utils.js
generated
vendored
38
node_modules/mongodb/lib/core/utils.js
generated
vendored
@@ -1,7 +1,7 @@
|
||||
'use strict';
|
||||
const os = require('os');
|
||||
const crypto = require('crypto');
|
||||
const requireOptional = require('require_optional');
|
||||
const requireOptional = require('optional-require')(require);
|
||||
|
||||
/**
|
||||
* Generate a UUIDv4
|
||||
@@ -27,6 +27,7 @@ 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') {
|
||||
@@ -46,10 +47,7 @@ const noEJSONError = function() {
|
||||
|
||||
// Facilitate loading EJSON optionally
|
||||
function retrieveEJSON() {
|
||||
let EJSON = null;
|
||||
try {
|
||||
EJSON = requireOptional('mongodb-extjson');
|
||||
} catch (error) {} // eslint-disable-line
|
||||
let EJSON = requireOptional('mongodb-extjson');
|
||||
if (!EJSON) {
|
||||
EJSON = {
|
||||
parse: noEJSONError,
|
||||
@@ -149,6 +147,35 @@ function eachAsync(arr, eachFn, 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;
|
||||
}
|
||||
@@ -257,6 +284,7 @@ module.exports = {
|
||||
maxWireVersion,
|
||||
isPromiseLike,
|
||||
eachAsync,
|
||||
eachAsyncSeries,
|
||||
isUnifiedTopology,
|
||||
arrayStrictEqual,
|
||||
tagsStrictEqual,
|
||||
|
||||
51
node_modules/mongodb/lib/core/wireprotocol/command.js
generated
vendored
51
node_modules/mongodb/lib/core/wireprotocol/command.js
generated
vendored
@@ -45,14 +45,43 @@ function _command(server, ns, cmd, options, callback) {
|
||||
const shouldUseOpMsg = supportsOpMsg(server);
|
||||
const session = options.session;
|
||||
|
||||
let clusterTime = server.clusterTime;
|
||||
const serverClusterTime = server.clusterTime;
|
||||
let clusterTime = serverClusterTime;
|
||||
let finalCmd = Object.assign({}, cmd);
|
||||
|
||||
const serverApi = options.serverApi;
|
||||
if (serverApi) {
|
||||
finalCmd.apiVersion = serverApi.version || serverApi;
|
||||
if (serverApi.strict != null) {
|
||||
finalCmd.apiStrict = serverApi.strict;
|
||||
}
|
||||
if (serverApi.deprecationErrors != null) {
|
||||
finalCmd.apiDeprecationErrors = serverApi.deprecationErrors;
|
||||
}
|
||||
}
|
||||
|
||||
if (hasSessionSupport(server) && session) {
|
||||
const sessionClusterTime = session.clusterTime;
|
||||
if (
|
||||
session.clusterTime &&
|
||||
session.clusterTime.clusterTime.greaterThan(clusterTime.clusterTime)
|
||||
serverClusterTime &&
|
||||
serverClusterTime.clusterTime &&
|
||||
sessionClusterTime &&
|
||||
sessionClusterTime.clusterTime &&
|
||||
sessionClusterTime.clusterTime.greaterThan(serverClusterTime.clusterTime)
|
||||
) {
|
||||
clusterTime = session.clusterTime;
|
||||
clusterTime = sessionClusterTime;
|
||||
}
|
||||
|
||||
// We need to unpin any read or write commands that happen outside of a pinned
|
||||
// transaction, so we check if we have a pinned transaction that is no longer
|
||||
// active, and unpin for all except start or commit.
|
||||
if (
|
||||
!session.transaction.isActive &&
|
||||
session.transaction.isPinned &&
|
||||
!finalCmd.startTransaction &&
|
||||
!finalCmd.commitTransaction
|
||||
) {
|
||||
session.transaction.unpinServer();
|
||||
}
|
||||
|
||||
const err = applySession(session, finalCmd, options);
|
||||
@@ -61,17 +90,12 @@ function _command(server, ns, cmd, options, callback) {
|
||||
}
|
||||
}
|
||||
|
||||
// if we have a known cluster time, gossip it
|
||||
if (clusterTime) {
|
||||
// if we have a known cluster time, gossip it
|
||||
finalCmd.$clusterTime = clusterTime;
|
||||
}
|
||||
|
||||
if (
|
||||
isSharded(server) &&
|
||||
!shouldUseOpMsg &&
|
||||
readPreference &&
|
||||
readPreference.preference !== 'primary'
|
||||
) {
|
||||
if (isSharded(server) && !shouldUseOpMsg && readPreference && readPreference.mode !== 'primary') {
|
||||
finalCmd = {
|
||||
$query: finalCmd,
|
||||
$readPreference: readPreference.toJSON()
|
||||
@@ -105,10 +129,7 @@ function _command(server, ns, cmd, options, callback) {
|
||||
err instanceof MongoNetworkError &&
|
||||
!err.hasErrorLabel('TransientTransactionError')
|
||||
) {
|
||||
if (err.errorLabels == null) {
|
||||
err.errorLabels = [];
|
||||
}
|
||||
err.errorLabels.push('TransientTransactionError');
|
||||
err.addErrorLabel('TransientTransactionError');
|
||||
}
|
||||
|
||||
if (
|
||||
|
||||
4
node_modules/mongodb/lib/core/wireprotocol/constants.js
generated
vendored
4
node_modules/mongodb/lib/core/wireprotocol/constants.js
generated
vendored
@@ -1,9 +1,9 @@
|
||||
'use strict';
|
||||
|
||||
const MIN_SUPPORTED_SERVER_VERSION = '2.6';
|
||||
const MAX_SUPPORTED_SERVER_VERSION = '4.2';
|
||||
const MAX_SUPPORTED_SERVER_VERSION = '5.0';
|
||||
const MIN_SUPPORTED_WIRE_VERSION = 2;
|
||||
const MAX_SUPPORTED_WIRE_VERSION = 8;
|
||||
const MAX_SUPPORTED_WIRE_VERSION = 13;
|
||||
|
||||
module.exports = {
|
||||
MIN_SUPPORTED_SERVER_VERSION,
|
||||
|
||||
12
node_modules/mongodb/lib/core/wireprotocol/kill_cursors.js
generated
vendored
12
node_modules/mongodb/lib/core/wireprotocol/kill_cursors.js
generated
vendored
@@ -5,10 +5,16 @@ const MongoError = require('../error').MongoError;
|
||||
const MongoNetworkError = require('../error').MongoNetworkError;
|
||||
const collectionNamespace = require('./shared').collectionNamespace;
|
||||
const maxWireVersion = require('../utils').maxWireVersion;
|
||||
const emitWarning = require('../utils').emitWarning;
|
||||
const command = require('./command');
|
||||
|
||||
function killCursors(server, ns, cursorState, callback) {
|
||||
function killCursors(server, ns, cursorState, defaultOptions, callback) {
|
||||
if (typeof defaultOptions === 'function') {
|
||||
callback = defaultOptions;
|
||||
defaultOptions = {};
|
||||
}
|
||||
callback = typeof callback === 'function' ? callback : () => {};
|
||||
|
||||
const cursorId = cursorState.cursorId;
|
||||
|
||||
if (maxWireVersion(server) < 4) {
|
||||
@@ -31,7 +37,7 @@ function killCursors(server, ns, cursorState, callback) {
|
||||
if (typeof callback === 'function') {
|
||||
callback(err, null);
|
||||
} else {
|
||||
console.warn(err);
|
||||
emitWarning(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -44,7 +50,7 @@ function killCursors(server, ns, cursorState, callback) {
|
||||
cursors: [cursorId]
|
||||
};
|
||||
|
||||
const options = {};
|
||||
const options = defaultOptions || {};
|
||||
if (typeof cursorState.session === 'object') options.session = cursorState.session;
|
||||
|
||||
command(server, ns, killCursorCmd, options, (err, result) => {
|
||||
|
||||
35
node_modules/mongodb/lib/core/wireprotocol/query.js
generated
vendored
35
node_modules/mongodb/lib/core/wireprotocol/query.js
generated
vendored
@@ -8,6 +8,8 @@ const isSharded = require('./shared').isSharded;
|
||||
const maxWireVersion = require('../utils').maxWireVersion;
|
||||
const applyCommonQueryOptions = require('./shared').applyCommonQueryOptions;
|
||||
const command = require('./command');
|
||||
const decorateWithExplain = require('../../utils').decorateWithExplain;
|
||||
const Explain = require('../../explain').Explain;
|
||||
|
||||
function query(server, ns, cmd, cursorState, options, callback) {
|
||||
options = options || {};
|
||||
@@ -31,7 +33,18 @@ function query(server, ns, cmd, cursorState, options, callback) {
|
||||
}
|
||||
|
||||
const readPreference = getReadPreference(cmd, options);
|
||||
const findCmd = prepareFindCommand(server, ns, cmd, cursorState, options);
|
||||
let findCmd = prepareFindCommand(server, ns, cmd, cursorState, options);
|
||||
|
||||
// If we have explain, we need to rewrite the find command
|
||||
// to wrap it in the explain command
|
||||
try {
|
||||
const explain = Explain.fromOptions(options);
|
||||
if (explain) {
|
||||
findCmd = decorateWithExplain(findCmd, explain);
|
||||
}
|
||||
} catch (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
// NOTE: This actually modifies the passed in cmd, and our code _depends_ on this
|
||||
// side-effect. Change this ASAP
|
||||
@@ -59,7 +72,7 @@ function query(server, ns, cmd, cursorState, options, callback) {
|
||||
|
||||
function prepareFindCommand(server, ns, cmd, cursorState) {
|
||||
cursorState.batchSize = cmd.batchSize || cursorState.batchSize;
|
||||
let findCmd = {
|
||||
const findCmd = {
|
||||
find: collectionNamespace(ns)
|
||||
};
|
||||
|
||||
@@ -100,6 +113,10 @@ function prepareFindCommand(server, ns, cmd, cursorState) {
|
||||
sortValue = sortObject;
|
||||
}
|
||||
|
||||
if (typeof cmd.allowDiskUse === 'boolean') {
|
||||
findCmd.allowDiskUse = cmd.allowDiskUse;
|
||||
}
|
||||
|
||||
if (cmd.sort) findCmd.sort = sortValue;
|
||||
if (cmd.fields) findCmd.projection = cmd.fields;
|
||||
if (cmd.hint) findCmd.hint = cmd.hint;
|
||||
@@ -127,8 +144,8 @@ function prepareFindCommand(server, ns, cmd, cursorState) {
|
||||
if (cmd.maxTimeMS) findCmd.maxTimeMS = cmd.maxTimeMS;
|
||||
if (cmd.min) findCmd.min = cmd.min;
|
||||
if (cmd.max) findCmd.max = cmd.max;
|
||||
findCmd.returnKey = cmd.returnKey ? cmd.returnKey : false;
|
||||
findCmd.showRecordId = cmd.showDiskLoc ? cmd.showDiskLoc : false;
|
||||
if (typeof cmd.returnKey === 'boolean') findCmd.returnKey = cmd.returnKey;
|
||||
if (typeof cmd.showDiskLoc === 'boolean') findCmd.showRecordId = cmd.showDiskLoc;
|
||||
if (cmd.snapshot) findCmd.snapshot = cmd.snapshot;
|
||||
if (cmd.tailable) findCmd.tailable = cmd.tailable;
|
||||
if (cmd.oplogReplay) findCmd.oplogReplay = cmd.oplogReplay;
|
||||
@@ -139,14 +156,6 @@ function prepareFindCommand(server, ns, cmd, cursorState) {
|
||||
if (cmd.collation) findCmd.collation = cmd.collation;
|
||||
if (cmd.readConcern) findCmd.readConcern = cmd.readConcern;
|
||||
|
||||
// If we have explain, we need to rewrite the find command
|
||||
// to wrap it in the explain command
|
||||
if (cmd.explain) {
|
||||
findCmd = {
|
||||
explain: findCmd
|
||||
};
|
||||
}
|
||||
|
||||
return findCmd;
|
||||
}
|
||||
|
||||
@@ -184,7 +193,7 @@ function prepareLegacyFindQuery(server, ns, cmd, cursorState, options) {
|
||||
if (typeof cmd.showDiskLoc !== 'undefined') findCmd['$showDiskLoc'] = cmd.showDiskLoc;
|
||||
if (cmd.comment) findCmd['$comment'] = cmd.comment;
|
||||
if (cmd.maxTimeMS) findCmd['$maxTimeMS'] = cmd.maxTimeMS;
|
||||
if (cmd.explain) {
|
||||
if (options.explain !== undefined) {
|
||||
// nToReturn must be 0 (match all) or negative (match N and close cursor)
|
||||
// nToReturn > 0 will give explain results equivalent to limit(0)
|
||||
numberToReturn = -Math.abs(cmd.limit || 0);
|
||||
|
||||
1
node_modules/mongodb/lib/core/wireprotocol/shared.js
generated
vendored
1
node_modules/mongodb/lib/core/wireprotocol/shared.js
generated
vendored
@@ -57,6 +57,7 @@ function applyCommonQueryOptions(queryOptions, options) {
|
||||
promoteLongs: typeof options.promoteLongs === 'boolean' ? options.promoteLongs : true,
|
||||
promoteValues: typeof options.promoteValues === 'boolean' ? options.promoteValues : true,
|
||||
promoteBuffers: typeof options.promoteBuffers === 'boolean' ? options.promoteBuffers : false,
|
||||
bsonRegExp: typeof options.bsonRegExp === 'boolean' ? options.bsonRegExp : false,
|
||||
monitoring: typeof options.monitoring === 'boolean' ? options.monitoring : false,
|
||||
fullResult: typeof options.fullResult === 'boolean' ? options.fullResult : false
|
||||
});
|
||||
|
||||
11
node_modules/mongodb/lib/core/wireprotocol/write_command.js
generated
vendored
11
node_modules/mongodb/lib/core/wireprotocol/write_command.js
generated
vendored
@@ -3,6 +3,8 @@
|
||||
const MongoError = require('../error').MongoError;
|
||||
const collectionNamespace = require('./shared').collectionNamespace;
|
||||
const command = require('./command');
|
||||
const decorateWithExplain = require('../../utils').decorateWithExplain;
|
||||
const Explain = require('../../explain').Explain;
|
||||
|
||||
function writeCommand(server, type, opsField, ns, ops, options, callback) {
|
||||
if (ops.length === 0) throw new MongoError(`${type} must contain at least one document`);
|
||||
@@ -15,7 +17,7 @@ function writeCommand(server, type, opsField, ns, ops, options, callback) {
|
||||
const ordered = typeof options.ordered === 'boolean' ? options.ordered : true;
|
||||
const writeConcern = options.writeConcern;
|
||||
|
||||
const writeCommand = {};
|
||||
let writeCommand = {};
|
||||
writeCommand[type] = collectionNamespace(ns);
|
||||
writeCommand[opsField] = ops;
|
||||
writeCommand.ordered = ordered;
|
||||
@@ -36,6 +38,13 @@ function writeCommand(server, type, opsField, ns, ops, options, callback) {
|
||||
writeCommand.bypassDocumentValidation = options.bypassDocumentValidation;
|
||||
}
|
||||
|
||||
// If a command is to be explained, we need to reformat the command after
|
||||
// the other command properties are specified.
|
||||
const explain = Explain.fromOptions(options);
|
||||
if (explain) {
|
||||
writeCommand = decorateWithExplain(writeCommand, explain);
|
||||
}
|
||||
|
||||
const commandOptions = Object.assign(
|
||||
{
|
||||
checkKeys: type === 'insert',
|
||||
|
||||
Reference in New Issue
Block a user