Created
October 11, 2019 12:06
-
-
Save darrensapalo/93ca5e913c00441a514ad24d5fccecdf to your computer and use it in GitHub Desktop.
sequelize connection not working for MSSQL
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| "use strict"; | |
| const crypto = require('crypto'); | |
| const os = require('os'); // $FlowFixMe | |
| const constants = require('constants'); | |
| const _require = require('tls'), | |
| createSecureContext = _require.createSecureContext; | |
| const _require2 = require('adal-node'), | |
| AuthenticationContext = _require2.AuthenticationContext; | |
| const BulkLoad = require('./bulk-load'); | |
| const Debug = require('./debug'); | |
| const EventEmitter = require('events').EventEmitter; | |
| const InstanceLookup = require('./instance-lookup').InstanceLookup; | |
| const TransientErrorLookup = require('./transient-error-lookup.js').TransientErrorLookup; | |
| const TYPE = require('./packet').TYPE; | |
| const PreloginPayload = require('./prelogin-payload'); | |
| const Login7Payload = require('./login7-payload'); | |
| const NTLMResponsePayload = require('./ntlm-payload'); | |
| const Request = require('./request'); | |
| const RpcRequestPayload = require('./rpcrequest-payload'); | |
| const SqlBatchPayload = require('./sqlbatch-payload'); | |
| const MessageIO = require('./message-io'); | |
| const TokenStreamParser = require('./token/token-stream-parser').Parser; | |
| const Transaction = require('./transaction').Transaction; | |
| const ISOLATION_LEVEL = require('./transaction').ISOLATION_LEVEL; | |
| const ConnectionError = require('./errors').ConnectionError; | |
| const RequestError = require('./errors').RequestError; | |
| const Connector = require('./connector').Connector; | |
| const libraryName = require('./library').name; | |
| const versions = require('./tds-versions').versions; | |
| const _require3 = require('./ntlm'), | |
| createNTLMRequest = _require3.createNTLMRequest; // A rather basic state machine for managing a connection. | |
| // Implements something approximating s3.2.1. | |
| const KEEP_ALIVE_INITIAL_DELAY = 30 * 1000; | |
| const DEFAULT_CONNECT_TIMEOUT = 15 * 1000; | |
| const DEFAULT_CLIENT_REQUEST_TIMEOUT = 15 * 1000; | |
| const DEFAULT_CANCEL_TIMEOUT = 5 * 1000; | |
| const DEFAULT_CONNECT_RETRY_INTERVAL = 500; | |
| const DEFAULT_PACKET_SIZE = 4 * 1024; | |
| const DEFAULT_TEXTSIZE = '2147483647'; | |
| const DEFAULT_DATEFIRST = 7; | |
| const DEFAULT_PORT = 1433; | |
| const DEFAULT_TDS_VERSION = '7_4'; | |
| const DEFAULT_LANGUAGE = 'us_english'; | |
| const DEFAULT_DATEFORMAT = 'mdy'; | |
| class Connection extends EventEmitter { | |
| constructor(config) { | |
| super(); | |
| if (typeof config !== 'object' || config === null) { | |
| throw new TypeError('The "config" argument is required and must be of type Object.'); | |
| } | |
| if (typeof config.server !== 'string') { | |
| throw new TypeError('The "config.server" property is required and must be of type string.'); | |
| } | |
| this.fedAuthRequired = false; | |
| this.fedAuthInfoToken = undefined; | |
| let authentication; | |
| if (config.authentication !== undefined) { | |
| if (typeof config.authentication !== 'object' || config.authentication === null) { | |
| throw new TypeError('The "config.authentication" property must be of type Object.'); | |
| } | |
| const type = config.authentication.type; | |
| const options = config.authentication.options === undefined ? {} : config.authentication.options; | |
| if (typeof type !== 'string') { | |
| throw new TypeError('The "config.authentication.type" property must be of type string.'); | |
| } | |
| if (type !== 'default' && type !== 'ntlm' && type !== 'azure-active-directory-password' && type !== 'azure-active-directory-access-token') { | |
| throw new TypeError('The "type" property must one of "default", "ntlm", "azure-active-directory-password" or "azure-active-directory-access-token".'); | |
| } | |
| if (typeof options !== 'object' || options === null) { | |
| throw new TypeError('The "config.authentication.options" property must be of type object.'); | |
| } | |
| if (type === 'ntlm') { | |
| if (typeof options.domain !== 'string') { | |
| throw new TypeError('The "config.authentication.options.domain" property must be of type string.'); | |
| } | |
| if (options.userName !== undefined && typeof options.userName !== 'string') { | |
| throw new TypeError('The "config.authentication.options.userName" property must be of type string.'); | |
| } | |
| if (options.password !== undefined && typeof options.password !== 'string') { | |
| throw new TypeError('The "config.authentication.options.password" property must be of type string.'); | |
| } | |
| authentication = { | |
| type: 'ntlm', | |
| options: { | |
| userName: options.userName, | |
| password: options.password, | |
| domain: options.domain && options.domain.toUpperCase() | |
| } | |
| }; | |
| } else if (type === 'azure-active-directory-password') { | |
| if (options.userName !== undefined && typeof options.userName !== 'string') { | |
| throw new TypeError('The "config.authentication.options.userName" property must be of type string.'); | |
| } | |
| if (options.password !== undefined && typeof options.password !== 'string') { | |
| throw new TypeError('The "config.authentication.options.password" property must be of type string.'); | |
| } | |
| authentication = { | |
| type: 'azure-active-directory-password', | |
| options: { | |
| userName: options.userName, | |
| password: options.password | |
| } | |
| }; | |
| } else if (type === 'azure-active-directory-access-token') { | |
| if (typeof options.token !== 'string') { | |
| throw new TypeError('The "config.authentication.options.token" property must be of type string.'); | |
| } | |
| authentication = { | |
| type: 'azure-active-directory-access-token', | |
| options: { | |
| token: options.token | |
| } | |
| }; | |
| } else { | |
| if (options.userName !== undefined && typeof options.userName !== 'string') { | |
| throw new TypeError('The "config.authentication.options.userName" property must be of type string.'); | |
| } | |
| if (options.password !== undefined && typeof options.password !== 'string') { | |
| throw new TypeError('The "config.authentication.options.password" property must be of type string.'); | |
| } | |
| authentication = { | |
| type: 'default', | |
| options: { | |
| userName: config.userName, | |
| password: config.password | |
| } | |
| }; | |
| } | |
| } else { | |
| authentication = { | |
| type: 'default', | |
| options: { | |
| userName: config.userName, | |
| password: config.password | |
| } | |
| }; | |
| } | |
| this.config = { | |
| server: config.server, | |
| authentication: authentication, | |
| options: { | |
| abortTransactionOnError: false, | |
| appName: undefined, | |
| camelCaseColumns: false, | |
| cancelTimeout: DEFAULT_CANCEL_TIMEOUT, | |
| columnNameReplacer: undefined, | |
| connectionRetryInterval: DEFAULT_CONNECT_RETRY_INTERVAL, | |
| connectTimeout: DEFAULT_CONNECT_TIMEOUT, | |
| connectionIsolationLevel: ISOLATION_LEVEL.READ_COMMITTED, | |
| cryptoCredentialsDetails: {}, | |
| database: undefined, | |
| datefirst: DEFAULT_DATEFIRST, | |
| dateFormat: DEFAULT_DATEFORMAT, | |
| debug: { | |
| data: false, | |
| packet: false, | |
| payload: false, | |
| token: false | |
| }, | |
| enableAnsiNull: true, | |
| enableAnsiNullDefault: true, | |
| enableAnsiPadding: true, | |
| enableAnsiWarnings: true, | |
| enableArithAbort: false, | |
| enableConcatNullYieldsNull: true, | |
| enableCursorCloseOnCommit: null, | |
| enableImplicitTransactions: false, | |
| enableNumericRoundabort: false, | |
| enableQuotedIdentifier: true, | |
| encrypt: false, | |
| fallbackToDefaultDb: false, | |
| instanceName: undefined, | |
| isolationLevel: ISOLATION_LEVEL.READ_COMMITTED, | |
| language: DEFAULT_LANGUAGE, | |
| localAddress: undefined, | |
| maxRetriesOnTransientErrors: 3, | |
| multiSubnetFailover: false, | |
| packetSize: DEFAULT_PACKET_SIZE, | |
| port: DEFAULT_PORT, | |
| readOnlyIntent: false, | |
| requestTimeout: DEFAULT_CLIENT_REQUEST_TIMEOUT, | |
| rowCollectionOnDone: false, | |
| rowCollectionOnRequestCompletion: false, | |
| tdsVersion: DEFAULT_TDS_VERSION, | |
| textsize: DEFAULT_TEXTSIZE, | |
| trustServerCertificate: true, | |
| useColumnNames: false, | |
| useUTC: true | |
| } | |
| }; | |
| if (config.options) { | |
| if (config.options.port && config.options.instanceName) { | |
| throw new Error('Port and instanceName are mutually exclusive, but ' + config.options.port + ' and ' + config.options.instanceName + ' provided'); | |
| } | |
| if (config.options.abortTransactionOnError !== undefined) { | |
| if (typeof config.options.abortTransactionOnError !== 'boolean' && config.options.abortTransactionOnError !== null) { | |
| throw new TypeError('The "config.options.abortTransactionOnError" property must be of type string or null.'); | |
| } | |
| this.config.options.abortTransactionOnError = config.options.abortTransactionOnError; | |
| } | |
| if (config.options.appName !== undefined) { | |
| if (typeof config.options.appName !== 'string') { | |
| throw new TypeError('The "config.options.appName" property must be of type string.'); | |
| } | |
| this.config.options.appName = config.options.appName; | |
| } | |
| if (config.options.camelCaseColumns !== undefined) { | |
| if (typeof config.options.camelCaseColumns !== 'boolean') { | |
| throw new TypeError('The "config.options.camelCaseColumns" property must be of type boolean.'); | |
| } | |
| this.config.options.camelCaseColumns = config.options.camelCaseColumns; | |
| } | |
| if (config.options.cancelTimeout !== undefined) { | |
| if (typeof config.options.cancelTimeout !== 'number') { | |
| throw new TypeError('The "config.options.cancelTimeout" property must be of type number.'); | |
| } | |
| this.config.options.cancelTimeout = config.options.cancelTimeout; | |
| } | |
| if (config.options.columnNameReplacer) { | |
| if (typeof config.options.columnNameReplacer !== 'function') { | |
| throw new TypeError('The "config.options.cancelTimeout" property must be of type function.'); | |
| } | |
| this.config.options.columnNameReplacer = config.options.columnNameReplacer; | |
| } | |
| if (config.options.connectTimeout !== undefined) { | |
| if (typeof config.options.connectTimeout !== 'number') { | |
| throw new TypeError('The "config.options.connectTimeout" property must be of type number.'); | |
| } | |
| this.config.options.connectTimeout = config.options.connectTimeout; | |
| } | |
| if (config.options.connectionIsolationLevel !== undefined) { | |
| this.config.options.connectionIsolationLevel = config.options.connectionIsolationLevel; | |
| } | |
| if (config.options.connectTimeout !== undefined) { | |
| if (typeof config.options.connectTimeout !== 'number') { | |
| throw new TypeError('The "config.options.connectTimeout" property must be of type number.'); | |
| } | |
| this.config.options.connectTimeout = config.options.connectTimeout; | |
| } | |
| if (config.options.cryptoCredentialsDetails !== undefined) { | |
| if (typeof config.options.cryptoCredentialsDetails !== 'object' || config.options.cryptoCredentialsDetails === null) { | |
| throw new TypeError('The "config.options.cryptoCredentialsDetails" property must be of type Object.'); | |
| } | |
| this.config.options.cryptoCredentialsDetails = config.options.cryptoCredentialsDetails; | |
| } | |
| if (config.options.database !== undefined) { | |
| if (typeof config.options.database !== 'string') { | |
| throw new TypeError('The "config.options.database" property must be of type string.'); | |
| } | |
| this.config.options.database = config.options.database; | |
| } | |
| if (config.options.datefirst !== undefined) { | |
| if (typeof config.options.datefirst !== 'number' && config.options.datefirst !== null) { | |
| throw new TypeError('The "config.options.datefirst" property must be of type number.'); | |
| } | |
| if (config.options.datefirst !== null && (config.options.datefirst < 1 || config.options.datefirst > 7)) { | |
| throw new RangeError('The "config.options.datefirst" property must be >= 1 and <= 7'); | |
| } | |
| this.config.options.datefirst = config.options.datefirst; | |
| } | |
| if (config.options.dateFormat !== undefined) { | |
| if (typeof config.options.dateFormat !== 'string' && config.options.dateFormat !== null) { | |
| throw new TypeError('The "config.options.dateFormat" property must be of type string or null.'); | |
| } | |
| this.config.options.dateFormat = config.options.dateFormat; | |
| } | |
| if (config.options.debug) { | |
| if (config.options.debug.data !== undefined) { | |
| if (typeof config.options.debug.data !== 'boolean') { | |
| throw new TypeError('The "config.options.debug.data" property must be of type boolean.'); | |
| } | |
| this.config.options.debug.data = config.options.debug.data; | |
| } | |
| if (config.options.debug.packet !== undefined) { | |
| if (typeof config.options.debug.packet !== 'boolean') { | |
| throw new TypeError('The "config.options.debug.packet" property must be of type boolean.'); | |
| } | |
| this.config.options.debug.packet = config.options.debug.packet; | |
| } | |
| if (config.options.debug.payload !== undefined) { | |
| if (typeof config.options.debug.payload !== 'boolean') { | |
| throw new TypeError('The "config.options.debug.payload" property must be of type boolean.'); | |
| } | |
| this.config.options.debug.payload = config.options.debug.payload; | |
| } | |
| if (config.options.debug.token !== undefined) { | |
| if (typeof config.options.debug.token !== 'boolean') { | |
| throw new TypeError('The "config.options.debug.token" property must be of type boolean.'); | |
| } | |
| this.config.options.debug.token = config.options.debug.token; | |
| } | |
| } | |
| if (config.options.enableAnsiNull !== undefined) { | |
| if (typeof config.options.enableAnsiNull !== 'boolean' && config.options.enableAnsiNull !== null) { | |
| throw new TypeError('The "config.options.enableAnsiNull" property must be of type boolean or null.'); | |
| } | |
| this.config.options.enableAnsiNull = config.options.enableAnsiNull; | |
| } | |
| if (config.options.enableAnsiNullDefault !== undefined) { | |
| if (typeof config.options.enableAnsiNullDefault !== 'boolean' && config.options.enableAnsiNullDefault !== null) { | |
| throw new TypeError('The "config.options.enableAnsiNullDefault" property must be of type boolean or null.'); | |
| } | |
| this.config.options.enableAnsiNullDefault = config.options.enableAnsiNullDefault; | |
| } | |
| if (config.options.enableAnsiPadding !== undefined) { | |
| if (typeof config.options.enableAnsiPadding !== 'boolean' && config.options.enableAnsiPadding !== null) { | |
| throw new TypeError('The "config.options.enableAnsiPadding" property must be of type boolean or null.'); | |
| } | |
| this.config.options.enableAnsiPadding = config.options.enableAnsiPadding; | |
| } | |
| if (config.options.enableAnsiWarnings !== undefined) { | |
| if (typeof config.options.enableAnsiWarnings !== 'boolean' && config.options.enableAnsiWarnings !== null) { | |
| throw new TypeError('The "config.options.enableAnsiWarnings" property must be of type boolean or null.'); | |
| } | |
| this.config.options.enableAnsiWarnings = config.options.enableAnsiWarnings; | |
| } | |
| if (config.options.enableArithAbort !== undefined) { | |
| if (typeof config.options.enableArithAbort !== 'boolean' && config.options.enableArithAbort !== null) { | |
| throw new TypeError('The "config.options.enableArithAbort" property must be of type boolean or null.'); | |
| } | |
| this.config.options.enableArithAbort = config.options.enableArithAbort; | |
| } | |
| if (config.options.enableConcatNullYieldsNull !== undefined) { | |
| if (typeof config.options.enableConcatNullYieldsNull !== 'boolean' && config.options.enableConcatNullYieldsNull !== null) { | |
| throw new TypeError('The "config.options.enableConcatNullYieldsNull" property must be of type boolean or null.'); | |
| } | |
| this.config.options.enableConcatNullYieldsNull = config.options.enableConcatNullYieldsNull; | |
| } | |
| if (config.options.enableCursorCloseOnCommit !== undefined) { | |
| if (typeof config.options.enableCursorCloseOnCommit !== 'boolean' && config.options.enableCursorCloseOnCommit !== null) { | |
| throw new TypeError('The "config.options.enableCursorCloseOnCommit" property must be of type boolean or null.'); | |
| } | |
| this.config.options.enableCursorCloseOnCommit = config.options.enableCursorCloseOnCommit; | |
| } | |
| if (config.options.enableImplicitTransactions !== undefined) { | |
| if (typeof config.options.enableImplicitTransactions !== 'boolean' && config.options.enableImplicitTransactions !== null) { | |
| throw new TypeError('The "config.options.enableImplicitTransactions" property must be of type boolean or null.'); | |
| } | |
| this.config.options.enableImplicitTransactions = config.options.enableImplicitTransactions; | |
| } | |
| if (config.options.enableNumericRoundabort !== undefined) { | |
| if (typeof config.options.enableNumericRoundabort !== 'boolean' && config.options.enableNumericRoundabort !== null) { | |
| throw new TypeError('The "config.options.enableNumericRoundabort" property must be of type boolean or null.'); | |
| } | |
| this.config.options.enableNumericRoundabort = config.options.enableNumericRoundabort; | |
| } | |
| if (config.options.enableQuotedIdentifier !== undefined) { | |
| if (typeof config.options.enableQuotedIdentifier !== 'boolean' && config.options.enableQuotedIdentifier !== null) { | |
| throw new TypeError('The "config.options.enableQuotedIdentifier" property must be of type boolean or null.'); | |
| } | |
| this.config.options.enableQuotedIdentifier = config.options.enableQuotedIdentifier; | |
| } | |
| if (config.options.encrypt !== undefined) { | |
| if (typeof config.options.encrypt !== 'boolean') { | |
| throw new TypeError('The "config.options.encrypt" property must be of type boolean.'); | |
| } | |
| this.config.options.encrypt = config.options.encrypt; | |
| } else { | |
| this.config.options.encrypt = true; | |
| } | |
| if (config.options.fallbackToDefaultDb !== undefined) { | |
| if (typeof config.options.fallbackToDefaultDb !== 'boolean') { | |
| throw new TypeError('The "config.options.fallbackToDefaultDb" property must be of type boolean.'); | |
| } | |
| this.config.options.fallbackToDefaultDb = config.options.fallbackToDefaultDb; | |
| } | |
| if (config.options.instanceName !== undefined) { | |
| if (typeof config.options.instanceName !== 'string') { | |
| throw new TypeError('The "config.options.instanceName" property must be of type string.'); | |
| } | |
| this.config.options.instanceName = config.options.instanceName; | |
| this.config.options.port = undefined; | |
| } | |
| if (config.options.isolationLevel !== undefined) { | |
| if (typeof config.options.isolationLevel !== 'number') { | |
| throw new TypeError('The "config.options.language" property must be of type numer.'); | |
| } | |
| this.config.options.isolationLevel = config.options.isolationLevel; | |
| } | |
| if (config.options.language !== undefined) { | |
| if (typeof config.options.language !== 'string' && config.options.language !== null) { | |
| throw new TypeError('The "config.options.language" property must be of type string or null.'); | |
| } | |
| this.config.options.language = config.options.language; | |
| } | |
| if (config.options.localAddress !== undefined) { | |
| if (typeof config.options.localAddress !== 'string') { | |
| throw new TypeError('The "config.options.localAddress" property must be of type string.'); | |
| } | |
| this.config.options.localAddress = config.options.localAddress; | |
| } | |
| if (config.options.multiSubnetFailover !== undefined) { | |
| if (typeof config.options.multiSubnetFailover !== 'boolean') { | |
| throw new TypeError('The "config.options.multiSubnetFailover" property must be of type boolean.'); | |
| } | |
| this.config.options.multiSubnetFailover = config.options.multiSubnetFailover; | |
| } | |
| if (config.options.packetSize !== undefined) { | |
| if (typeof config.options.packetSize !== 'number') { | |
| throw new TypeError('The "config.options.packetSize" property must be of type number.'); | |
| } | |
| this.config.options.packetSize = config.options.packetSize; | |
| } | |
| if (config.options.port !== undefined) { | |
| if (typeof config.options.port !== 'number') { | |
| throw new TypeError('The "config.options.port" property must be of type number.'); | |
| } | |
| if (config.options.port <= 0 || config.options.port >= 65536) { | |
| throw new RangeError('The "config.options.port" property must be > 0 and < 65536'); | |
| } | |
| this.config.options.port = config.options.port; | |
| this.config.options.instanceName = undefined; | |
| } | |
| if (config.options.readOnlyIntent !== undefined) { | |
| if (typeof config.options.readOnlyIntent !== 'boolean') { | |
| throw new TypeError('The "config.options.readOnlyIntent" property must be of type boolean.'); | |
| } | |
| this.config.options.readOnlyIntent = config.options.readOnlyIntent; | |
| } | |
| if (config.options.requestTimeout !== undefined) { | |
| if (typeof config.options.requestTimeout !== 'number') { | |
| throw new TypeError('The "config.options.requestTimeout" property must be of type number.'); | |
| } | |
| this.config.options.requestTimeout = config.options.requestTimeout; | |
| } | |
| if (config.options.maxRetriesOnTransientErrors !== undefined) { | |
| if (typeof config.options.maxRetriesOnTransientErrors !== 'number') { | |
| throw new TypeError('The "config.options.maxRetriesOnTransientErrors" property must be of type number.'); | |
| } | |
| if (config.options.maxRetriesOnTransientErrors < 0) { | |
| throw new TypeError('The "config.options.maxRetriesOnTransientErrors" property must be equal or greater than 0.'); | |
| } | |
| this.config.options.maxRetriesOnTransientErrors = config.options.maxRetriesOnTransientErrors; | |
| } | |
| if (config.options.connectionRetryInterval !== undefined) { | |
| if (typeof config.options.connectionRetryInterval !== 'number') { | |
| throw new TypeError('The "config.options.connectionRetryInterval" property must be of type number.'); | |
| } | |
| if (config.options.connectionRetryInterval <= 0) { | |
| throw new TypeError('The "config.options.connectionRetryInterval" property must be greater than 0.'); | |
| } | |
| this.config.options.connectionRetryInterval = config.options.connectionRetryInterval; | |
| } | |
| if (config.options.rowCollectionOnDone !== undefined) { | |
| if (typeof config.options.rowCollectionOnDone !== 'boolean') { | |
| throw new TypeError('The "config.options.rowCollectionOnDone" property must be of type boolean.'); | |
| } | |
| this.config.options.rowCollectionOnDone = config.options.rowCollectionOnDone; | |
| } | |
| if (config.options.rowCollectionOnRequestCompletion !== undefined) { | |
| if (typeof config.options.rowCollectionOnRequestCompletion !== 'boolean') { | |
| throw new TypeError('The "config.options.rowCollectionOnRequestCompletion" property must be of type boolean.'); | |
| } | |
| this.config.options.rowCollectionOnRequestCompletion = config.options.rowCollectionOnRequestCompletion; | |
| } | |
| if (config.options.tdsVersion !== undefined) { | |
| if (typeof config.options.tdsVersion !== 'string') { | |
| throw new TypeError('The "config.options.tdsVersion" property must be of type string.'); | |
| } | |
| this.config.options.tdsVersion = config.options.tdsVersion; | |
| } | |
| if (config.options.textsize !== undefined) { | |
| if (typeof config.options.textsize !== 'number' && config.options.textsize !== null) { | |
| throw new TypeError('The "config.options.textsize" property must be of type number or null.'); | |
| } | |
| this.config.options.textsize = config.options.textsize; | |
| } | |
| if (config.options.trustServerCertificate !== undefined) { | |
| if (typeof config.options.trustServerCertificate !== 'boolean') { | |
| throw new TypeError('The "config.options.trustServerCertificate" property must be of type boolean.'); | |
| } | |
| this.config.options.trustServerCertificate = config.options.trustServerCertificate; | |
| } | |
| if (config.options.useColumnNames !== undefined) { | |
| if (typeof config.options.useColumnNames !== 'boolean') { | |
| throw new TypeError('The "config.options.useColumnNames" property must be of type boolean.'); | |
| } | |
| this.config.options.useColumnNames = config.options.useColumnNames; | |
| } | |
| if (config.options.useUTC !== undefined) { | |
| if (typeof config.options.useUTC !== 'boolean') { | |
| throw new TypeError('The "config.options.useUTC" property must be of type boolean.'); | |
| } | |
| this.config.options.useUTC = config.options.useUTC; | |
| } | |
| } | |
| let credentialsDetails = this.config.options.cryptoCredentialsDetails; | |
| if (credentialsDetails.secureOptions === undefined) { | |
| // If the caller has not specified their own `secureOptions`, | |
| // we set `SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS` here. | |
| // Older SQL Server instances running on older Windows versions have | |
| // trouble with the BEAST workaround in OpenSSL. | |
| // As BEAST is a browser specific exploit, we can just disable this option here. | |
| credentialsDetails = Object.create(credentialsDetails, { | |
| secureOptions: { | |
| value: constants.SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS | |
| } | |
| }); | |
| } | |
| this.secureContext = createSecureContext(credentialsDetails); | |
| this.createDebug(); | |
| this.createTokenStreamParser(); | |
| this.inTransaction = false; | |
| this.transactionDescriptors = [Buffer.from([0, 0, 0, 0, 0, 0, 0, 0])]; | |
| this.transitionTo(this.STATE.CONNECTING); | |
| if (this.config.options.tdsVersion < '7_2') { | |
| // 'beginTransaction', 'commitTransaction' and 'rollbackTransaction' | |
| // events are utilized to maintain inTransaction property state which in | |
| // turn is used in managing transactions. These events are only fired for | |
| // TDS version 7.2 and beyond. The properties below are used to emulate | |
| // equivalent behavior for TDS versions before 7.2. | |
| this.transactionDepth = 0; | |
| this.isSqlBatch = false; | |
| } | |
| this.curTransientRetryCount = 0; | |
| this.transientErrorLookup = new TransientErrorLookup(); | |
| this.cleanupTypeEnum = { | |
| NORMAL: 0, | |
| REDIRECT: 1, | |
| RETRY: 2 | |
| }; | |
| } | |
| close() { | |
| this.transitionTo(this.STATE.FINAL); | |
| } | |
| initialiseConnection() { | |
| this.connect(); | |
| this.createConnectTimer(); | |
| } | |
| cleanupConnection(cleanupTypeEnum) { | |
| if (!this.closed) { | |
| this.clearConnectTimer(); | |
| this.clearRequestTimer(); | |
| this.clearRetryTimer(); | |
| this.closeConnection(); | |
| if (cleanupTypeEnum === this.cleanupTypeEnum.REDIRECT) { | |
| this.emit('rerouting'); | |
| } else if (cleanupTypeEnum !== this.cleanupTypeEnum.RETRY) { | |
| process.nextTick(() => { | |
| this.emit('end'); | |
| }); | |
| } | |
| const request = this.request; | |
| if (request) { | |
| const err = RequestError('Connection closed before request completed.', 'ECLOSE'); | |
| request.callback(err); | |
| this.request = undefined; | |
| } | |
| this.closed = true; | |
| this.loggedIn = false; | |
| this.loginError = undefined; | |
| } | |
| } | |
| createDebug() { | |
| this.debug = new Debug(this.config.options.debug); | |
| this.debug.on('debug', message => { | |
| this.emit('debug', message); | |
| }); | |
| } | |
| createTokenStreamParser() { | |
| this.tokenStreamParser = new TokenStreamParser(this.debug, undefined, this.config.options); | |
| this.tokenStreamParser.on('infoMessage', token => { | |
| this.emit('infoMessage', token); | |
| }); | |
| this.tokenStreamParser.on('sspichallenge', token => { | |
| if (token.ntlmpacket) { | |
| this.ntlmpacket = token.ntlmpacket; | |
| this.ntlmpacketBuffer = token.ntlmpacketBuffer; | |
| } | |
| this.emit('sspichallenge', token); | |
| }); | |
| this.tokenStreamParser.on('errorMessage', token => { | |
| this.emit('errorMessage', token); | |
| if (this.loggedIn) { | |
| const request = this.request; | |
| if (request) { | |
| if (!request.canceled) { | |
| const error = new RequestError(token.message, 'EREQUEST'); | |
| error.number = token.number; | |
| error.state = token.state; | |
| error.class = token.class; | |
| error.serverName = token.serverName; | |
| error.procName = token.procName; | |
| error.lineNumber = token.lineNumber; | |
| request.error = error; | |
| } | |
| } | |
| } else { | |
| const error = ConnectionError(token.message, 'ELOGIN'); | |
| const isLoginErrorTransient = this.transientErrorLookup.isTransientError(token.number); | |
| if (isLoginErrorTransient && this.curTransientRetryCount !== this.config.options.maxRetriesOnTransientErrors) { | |
| error.isTransient = true; | |
| } | |
| this.loginError = error; | |
| } | |
| }); | |
| this.tokenStreamParser.on('databaseChange', token => { | |
| this.emit('databaseChange', token.newValue); | |
| }); | |
| this.tokenStreamParser.on('languageChange', token => { | |
| this.emit('languageChange', token.newValue); | |
| }); | |
| this.tokenStreamParser.on('charsetChange', token => { | |
| this.emit('charsetChange', token.newValue); | |
| }); | |
| this.tokenStreamParser.on('fedAuthInfo', token => { | |
| this.dispatchEvent('fedAuthInfo', token); | |
| }); | |
| this.tokenStreamParser.on('featureExtAck', token => { | |
| this.dispatchEvent('featureExtAck', token); | |
| }); | |
| this.tokenStreamParser.on('loginack', token => { | |
| if (!token.tdsVersion) { | |
| // unsupported TDS version | |
| this.loginError = ConnectionError('Server responded with unknown TDS version.', 'ETDS'); | |
| this.loggedIn = false; | |
| return; | |
| } | |
| if (!token.interface) { | |
| // unsupported interface | |
| this.loginError = ConnectionError('Server responded with unsupported interface.', 'EINTERFACENOTSUPP'); | |
| this.loggedIn = false; | |
| return; | |
| } // use negotiated version | |
| this.config.options.tdsVersion = token.tdsVersion; | |
| this.loggedIn = true; | |
| }); | |
| this.tokenStreamParser.on('routingChange', token => { | |
| this.routingData = token.newValue; | |
| this.dispatchEvent('routingChange'); | |
| }); | |
| this.tokenStreamParser.on('packetSizeChange', token => { | |
| this.messageIo.packetSize(token.newValue); | |
| }); // A new top-level transaction was started. This is not fired | |
| // for nested transactions. | |
| this.tokenStreamParser.on('beginTransaction', token => { | |
| this.transactionDescriptors.push(token.newValue); | |
| this.inTransaction = true; | |
| }); // A top-level transaction was committed. This is not fired | |
| // for nested transactions. | |
| this.tokenStreamParser.on('commitTransaction', () => { | |
| this.transactionDescriptors.length = 1; | |
| this.inTransaction = false; | |
| }); // A top-level transaction was rolled back. This is not fired | |
| // for nested transactions. This is also fired if a batch | |
| // aborting error happened that caused a rollback. | |
| this.tokenStreamParser.on('rollbackTransaction', () => { | |
| this.transactionDescriptors.length = 1; // An outermost transaction was rolled back. Reset the transaction counter | |
| this.inTransaction = false; | |
| this.emit('rollbackTransaction'); | |
| }); | |
| this.tokenStreamParser.on('columnMetadata', token => { | |
| const request = this.request; | |
| if (request) { | |
| if (!request.canceled) { | |
| let columns; | |
| if (this.config.options.useColumnNames) { | |
| columns = {}; | |
| for (let j = 0, len = token.columns.length; j < len; j++) { | |
| const col = token.columns[j]; | |
| if (columns[col.colName] == null) { | |
| columns[col.colName] = col; | |
| } | |
| } | |
| } else { | |
| columns = token.columns; | |
| } | |
| request.emit('columnMetadata', columns); | |
| } | |
| } else { | |
| this.emit('error', new Error("Received 'columnMetadata' when no sqlRequest is in progress")); | |
| this.close(); | |
| } | |
| }); | |
| this.tokenStreamParser.on('order', token => { | |
| const request = this.request; | |
| if (request) { | |
| if (!request.canceled) { | |
| request.emit('order', token.orderColumns); | |
| } | |
| } else { | |
| this.emit('error', new Error("Received 'order' when no sqlRequest is in progress")); | |
| this.close(); | |
| } | |
| }); | |
| this.tokenStreamParser.on('row', token => { | |
| const request = this.request; | |
| if (request) { | |
| if (!request.canceled) { | |
| if (this.config.options.rowCollectionOnRequestCompletion) { | |
| request.rows.push(token.columns); | |
| } | |
| if (this.config.options.rowCollectionOnDone) { | |
| request.rst.push(token.columns); | |
| } | |
| if (!(this.state === this.STATE.SENT_ATTENTION && request.paused)) { | |
| request.emit('row', token.columns); | |
| } | |
| } | |
| } else { | |
| this.emit('error', new Error("Received 'row' when no sqlRequest is in progress")); | |
| this.close(); | |
| } | |
| }); | |
| this.tokenStreamParser.on('returnStatus', token => { | |
| const request = this.request; | |
| if (request) { | |
| if (!request.canceled) { | |
| // Keep value for passing in 'doneProc' event. | |
| this.procReturnStatusValue = token.value; | |
| } | |
| } | |
| }); | |
| this.tokenStreamParser.on('returnValue', token => { | |
| const request = this.request; | |
| if (request) { | |
| if (!request.canceled) { | |
| this.request.emit('returnValue', token.paramName, token.value, token.metadata); | |
| } | |
| } | |
| }); | |
| this.tokenStreamParser.on('doneProc', token => { | |
| const request = this.request; | |
| if (request) { | |
| if (!request.canceled) { | |
| request.emit('doneProc', token.rowCount, token.more, this.procReturnStatusValue, request.rst); | |
| this.procReturnStatusValue = undefined; | |
| if (token.rowCount !== undefined) { | |
| request.rowCount += token.rowCount; | |
| } | |
| if (this.config.options.rowCollectionOnDone) { | |
| request.rst = []; | |
| } | |
| } | |
| } | |
| }); | |
| this.tokenStreamParser.on('doneInProc', token => { | |
| const request = this.request; | |
| if (request) { | |
| if (!request.canceled) { | |
| request.emit('doneInProc', token.rowCount, token.more, request.rst); | |
| if (token.rowCount !== undefined) { | |
| request.rowCount += token.rowCount; | |
| } | |
| if (this.config.options.rowCollectionOnDone) { | |
| request.rst = []; | |
| } | |
| } | |
| } | |
| }); | |
| this.tokenStreamParser.on('done', token => { | |
| const request = this.request; | |
| if (request) { | |
| if (token.attention) { | |
| this.dispatchEvent('attention'); | |
| } | |
| if (request.canceled) { | |
| // If we received a `DONE` token with `DONE_ERROR`, but no previous `ERROR` token, | |
| // We assume this is the indication that an in-flight request was canceled. | |
| if (token.sqlError && !request.error) { | |
| this.clearCancelTimer(); | |
| request.error = RequestError('Canceled.', 'ECANCEL'); | |
| } | |
| } else { | |
| if (token.sqlError && !request.error) { | |
| // check if the DONE_ERROR flags was set, but an ERROR token was not sent. | |
| request.error = RequestError('An unknown error has occurred.', 'UNKNOWN'); | |
| } | |
| request.emit('done', token.rowCount, token.more, request.rst); | |
| if (token.rowCount !== undefined) { | |
| request.rowCount += token.rowCount; | |
| } | |
| if (this.config.options.rowCollectionOnDone) { | |
| request.rst = []; | |
| } | |
| } | |
| } | |
| }); | |
| this.tokenStreamParser.on('endOfMessage', () => { | |
| // EOM pseudo token received | |
| if (this.state === this.STATE.SENT_CLIENT_REQUEST) { | |
| this.dispatchEvent('endOfMessageMarkerReceived'); | |
| } | |
| }); | |
| this.tokenStreamParser.on('resetConnection', () => { | |
| this.emit('resetConnection'); | |
| }); | |
| this.tokenStreamParser.on('tokenStreamError', error => { | |
| this.emit('error', error); | |
| this.close(); | |
| }); | |
| this.tokenStreamParser.on('drain', () => { | |
| // Bridge the release of backpressure from the token stream parser | |
| // transform to the packet stream transform. | |
| this.messageIo.resume(); | |
| }); | |
| } | |
| connect() { | |
| if (this.config.options.port) { | |
| return this.connectOnPort(this.config.options.port, this.config.options.multiSubnetFailover); | |
| } else { | |
| return new InstanceLookup().instanceLookup({ | |
| server: this.config.server, | |
| instanceName: this.config.options.instanceName, | |
| timeout: this.config.options.connectTimeout | |
| }, (message, port) => { | |
| if (this.state === this.STATE.FINAL) { | |
| return; | |
| } | |
| if (message) { | |
| this.emit('connect', ConnectionError(message, 'EINSTLOOKUP')); | |
| } else { | |
| this.connectOnPort(port, this.config.options.multiSubnetFailover); | |
| } | |
| }); | |
| } | |
| } | |
| connectOnPort(port, multiSubnetFailover) { | |
| const connectOpts = { | |
| host: this.routingData ? this.routingData.server : this.config.server, | |
| port: this.routingData ? this.routingData.port : port, | |
| localAddress: this.config.options.localAddress | |
| }; | |
| new Connector(connectOpts, multiSubnetFailover).execute((err, socket) => { | |
| if (err) { | |
| return this.socketError(err); | |
| } | |
| if (this.state === this.STATE.FINAL) { | |
| socket.destroy(); | |
| return; | |
| } | |
| this.socket = socket; | |
| this.socket.on('error', error => { | |
| this.socketError(error); | |
| }); | |
| this.socket.on('close', () => { | |
| this.socketClose(); | |
| }); | |
| this.socket.on('end', () => { | |
| this.socketEnd(); | |
| }); | |
| this.messageIo = new MessageIO(this.socket, this.config.options.packetSize, this.debug); | |
| this.messageIo.on('data', data => { | |
| this.dispatchEvent('data', data); | |
| }); | |
| this.messageIo.on('message', () => { | |
| this.dispatchEvent('message'); | |
| }); | |
| this.messageIo.on('secure', cleartext => { | |
| this.emit('secure', cleartext); | |
| }); | |
| this.socketConnect(); | |
| }); | |
| } | |
| closeConnection() { | |
| if (this.socket) { | |
| this.socket.destroy(); | |
| } | |
| } | |
| createConnectTimer() { | |
| this.connectTimer = setTimeout(() => { | |
| this.connectTimeout(); | |
| }, this.config.options.connectTimeout); | |
| } | |
| createCancelTimer() { | |
| this.clearCancelTimer(); | |
| const timeout = this.config.options.cancelTimeout; | |
| if (timeout > 0) { | |
| this.cancelTimer = setTimeout(() => { | |
| this.cancelTimeout(); | |
| }, timeout); | |
| } | |
| } | |
| createRequestTimer() { | |
| this.clearRequestTimer(); // release old timer, just to be safe | |
| const request = this.request; | |
| const timeout = request.timeout !== undefined ? request.timeout : this.config.options.requestTimeout; | |
| if (timeout) { | |
| this.requestTimer = setTimeout(() => { | |
| this.requestTimeout(); | |
| }, timeout); | |
| } | |
| } | |
| createRetryTimer() { | |
| this.clearRetryTimer(); | |
| this.retryTimer = setTimeout(() => { | |
| this.retryTimeout(); | |
| }, this.config.options.connectionRetryInterval); | |
| } | |
| connectTimeout() { | |
| const message = `Failed to connect to ${this.config.server}${this.config.options.port ? `:${this.config.options.port}` : `\\${this.config.options.instanceName}`} in ${this.config.options.connectTimeout}ms`; | |
| this.debug.log(message); | |
| this.emit('connect', ConnectionError(message, 'ETIMEOUT')); | |
| this.connectTimer = undefined; | |
| this.dispatchEvent('connectTimeout'); | |
| } | |
| cancelTimeout() { | |
| const message = `Failed to cancel request in ${this.config.options.cancelTimeout}ms`; | |
| this.debug.log(message); | |
| this.dispatchEvent('socketError', ConnectionError(message, 'ETIMEOUT')); | |
| } | |
| requestTimeout() { | |
| this.requestTimer = undefined; | |
| const request = this.request; | |
| request.cancel(); | |
| const timeout = request.timeout !== undefined ? request.timeout : this.config.options.requestTimeout; | |
| const message = 'Timeout: Request failed to complete in ' + timeout + 'ms'; | |
| request.error = RequestError(message, 'ETIMEOUT'); | |
| } | |
| retryTimeout() { | |
| this.retryTimer = undefined; | |
| this.emit('retry'); | |
| this.transitionTo(this.STATE.CONNECTING); | |
| } | |
| clearConnectTimer() { | |
| if (this.connectTimer) { | |
| clearTimeout(this.connectTimer); | |
| } | |
| } | |
| clearCancelTimer() { | |
| if (this.cancelTimer) { | |
| clearTimeout(this.cancelTimer); | |
| } | |
| } | |
| clearRequestTimer() { | |
| if (this.requestTimer) { | |
| clearTimeout(this.requestTimer); | |
| this.requestTimer = undefined; | |
| } | |
| } | |
| clearRetryTimer() { | |
| if (this.retryTimer) { | |
| clearTimeout(this.retryTimer); | |
| this.retryTimer = undefined; | |
| } | |
| } | |
| transitionTo(newState) { | |
| if (this.state === newState) { | |
| this.debug.log('State is already ' + newState.name); | |
| return; | |
| } | |
| if (this.state && this.state.exit) { | |
| this.state.exit.call(this, newState); | |
| } | |
| this.debug.log('State change: ' + (this.state ? this.state.name : 'undefined') + ' -> ' + newState.name); | |
| this.state = newState; | |
| if (this.state.enter) { | |
| this.state.enter.apply(this); | |
| } | |
| } | |
| dispatchEvent(eventName, ...args) { | |
| if (this.state.events[eventName]) { | |
| this.state.events[eventName].apply(this, args); | |
| } else { | |
| this.emit('error', new Error(`No event '${eventName}' in state '${this.state.name}'`)); | |
| this.close(); | |
| } | |
| } | |
| socketError(error) { | |
| if (this.state === this.STATE.CONNECTING || this.state === this.STATE.SENT_TLSSSLNEGOTIATION) { | |
| const message = `Failed to connect to ${this.config.server}:${this.config.options.port} - ${error.message}`; | |
| this.debug.log(message); | |
| this.emit('connect', ConnectionError(message, 'ESOCKET')); | |
| } else { | |
| const message = `Connection lost - ${error.message}`; | |
| this.debug.log(message); | |
| this.emit('error', ConnectionError(message, 'ESOCKET')); | |
| } | |
| this.dispatchEvent('socketError', error); | |
| } | |
| socketConnect() { | |
| this.socket.setKeepAlive(true, KEEP_ALIVE_INITIAL_DELAY); | |
| this.closed = false; | |
| this.debug.log('connected to ' + this.config.server + ':' + this.config.options.port); | |
| this.dispatchEvent('socketConnect'); | |
| } | |
| socketEnd() { | |
| this.debug.log('socket ended'); | |
| if (this.state !== this.STATE.FINAL) { | |
| const error = new Error('socket hang up'); | |
| error.code = 'ECONNRESET'; | |
| this.socketError(error); | |
| } | |
| } | |
| socketClose() { | |
| this.debug.log('connection to ' + this.config.server + ':' + this.config.options.port + ' closed'); | |
| if (this.state === this.STATE.REROUTING) { | |
| this.debug.log('Rerouting to ' + this.routingData.server + ':' + this.routingData.port); | |
| this.dispatchEvent('reconnect'); | |
| } else if (this.state === this.STATE.TRANSIENT_FAILURE_RETRY) { | |
| const server = this.routingData ? this.routingData.server : this.server; | |
| const port = this.routingData ? this.routingData.port : this.config.options.port; | |
| this.debug.log('Retry after transient failure connecting to ' + server + ':' + port); | |
| this.dispatchEvent('retry'); | |
| } else { | |
| this.transitionTo(this.STATE.FINAL); | |
| } | |
| } | |
| sendPreLogin() { | |
| const payload = new PreloginPayload({ | |
| encrypt: this.config.options.encrypt | |
| }); | |
| this.messageIo.sendMessage(TYPE.PRELOGIN, payload.data); | |
| this.debug.payload(function () { | |
| return payload.toString(' '); | |
| }); | |
| } | |
| emptyMessageBuffer() { | |
| this.messageBuffer = Buffer.alloc(0); | |
| } | |
| addToMessageBuffer(data) { | |
| this.messageBuffer = Buffer.concat([this.messageBuffer, data]); | |
| } | |
| sendLogin7Packet() { | |
| const payload = new Login7Payload({ | |
| tdsVersion: versions[this.config.options.tdsVersion], | |
| packetSize: this.config.options.packetSize, | |
| clientProgVer: 0, | |
| clientPid: process.pid, | |
| connectionId: 0, | |
| clientTimeZone: new Date().getTimezoneOffset(), | |
| clientLcid: 0x00000409 | |
| }); | |
| const authentication = this.config.authentication; | |
| switch (authentication.type) { | |
| case 'azure-active-directory-password': | |
| payload.fedAuth = { | |
| type: 'ADAL', | |
| echo: this.fedAuthRequired, | |
| workflow: 'default' | |
| }; | |
| break; | |
| case 'azure-active-directory-access-token': | |
| payload.fedAuth = { | |
| type: 'SECURITYTOKEN', | |
| echo: this.fedAuthRequired, | |
| fedAuthToken: authentication.options.token | |
| }; | |
| break; | |
| case 'ntlm': | |
| payload.sspi = createNTLMRequest({ | |
| domain: authentication.options.domain | |
| }); | |
| break; | |
| default: | |
| payload.userName = authentication.options.userName; | |
| payload.password = authentication.options.password; | |
| } | |
| payload.hostname = os.hostname(); | |
| payload.serverName = this.routingData ? this.routingData.server : this.config.server; | |
| payload.appName = this.config.options.appName || 'Tedious'; | |
| payload.libraryName = libraryName; | |
| payload.language = this.config.options.language; | |
| payload.database = this.config.options.database; | |
| payload.clientId = Buffer.from([1, 2, 3, 4, 5, 6]); | |
| payload.readOnlyIntent = this.config.options.readOnlyIntent; | |
| payload.initDbFatal = !this.config.options.fallbackToDefaultDb; | |
| this.routingData = undefined; | |
| this.messageIo.sendMessage(TYPE.LOGIN7, payload.toBuffer()); | |
| this.debug.payload(function () { | |
| return payload.toString(' '); | |
| }); | |
| } | |
| sendFedAuthResponsePacket(tokenResponse) { | |
| const accessTokenLen = Buffer.byteLength(tokenResponse.accessToken, 'ucs2'); | |
| const data = Buffer.alloc(8 + accessTokenLen); | |
| let offset = 0; | |
| offset = data.writeUInt32LE(accessTokenLen + 4, offset); | |
| offset = data.writeUInt32LE(accessTokenLen, offset); | |
| data.write(tokenResponse.accessToken, offset, 'ucs2'); | |
| this.messageIo.sendMessage(TYPE.FEDAUTH_TOKEN, data); // sent the fedAuth token message, the rest is similar to standard login 7 | |
| this.transitionTo(this.STATE.SENT_LOGIN7_WITH_STANDARD_LOGIN); | |
| } // Returns false to apply backpressure. | |
| sendDataToTokenStreamParser(data) { | |
| return this.tokenStreamParser.addBuffer(data); | |
| } // This is an internal method that is called from Request.pause(). | |
| // It has to check whether the passed Request object represents the currently | |
| // active request, because the application might have called Request.pause() | |
| // on an old inactive Request object. | |
| pauseRequest(request) { | |
| if (this.isRequestActive(request)) { | |
| this.tokenStreamParser.pause(); | |
| } | |
| } // This is an internal method that is called from Request.resume(). | |
| resumeRequest(request) { | |
| if (this.isRequestActive(request)) { | |
| this.tokenStreamParser.resume(); | |
| } | |
| } // Returns true if the passed request is the currently active request of the connection. | |
| isRequestActive(request) { | |
| return request === this.request && this.state === this.STATE.SENT_CLIENT_REQUEST; | |
| } | |
| sendInitialSql() { | |
| const payload = new SqlBatchPayload(this.getInitialSql(), this.currentTransactionDescriptor(), this.config.options); | |
| return this.messageIo.sendMessage(TYPE.SQL_BATCH, payload.data); | |
| } | |
| getInitialSql() { | |
| const options = []; | |
| if (this.config.options.enableAnsiNull === true) { | |
| options.push('set ansi_nulls on'); | |
| } else if (this.config.options.enableAnsiNull === false) { | |
| options.push('set ansi_nulls off'); | |
| } | |
| if (this.config.options.enableAnsiNullDefault === true) { | |
| options.push('set ansi_null_dflt_on on'); | |
| } else if (this.config.options.enableAnsiNullDefault === false) { | |
| options.push('set ansi_null_dflt_on off'); | |
| } | |
| if (this.config.options.enableAnsiPadding === true) { | |
| options.push('set ansi_padding on'); | |
| } else if (this.config.options.enableAnsiPadding === false) { | |
| options.push('set ansi_padding off'); | |
| } | |
| if (this.config.options.enableAnsiWarnings === true) { | |
| options.push('set ansi_warnings on'); | |
| } else if (this.config.options.enableAnsiWarnings === false) { | |
| options.push('set ansi_warnings off'); | |
| } | |
| if (this.config.options.enableArithAbort === true) { | |
| options.push('set arithabort on'); | |
| } else if (this.config.options.enableArithAbort === false) { | |
| options.push('set arithabort off'); | |
| } | |
| if (this.config.options.enableConcatNullYieldsNull === true) { | |
| options.push('set concat_null_yields_null on'); | |
| } else if (this.config.options.enableArithAbort === false) { | |
| options.push('set concat_null_yields_null off'); | |
| } | |
| if (this.config.options.enableCursorCloseOnCommit === true) { | |
| options.push('set cursor_close_on_commit on'); | |
| } else if (this.config.options.enableCursorCloseOnCommit === false) { | |
| options.push('set cursor_close_on_commit off'); | |
| } | |
| if (this.config.options.datefirst !== null) { | |
| options.push(`set datefirst ${this.config.options.datefirst}`); | |
| } | |
| if (this.config.options.dateFormat !== null) { | |
| options.push(`set dateformat ${this.config.options.dateFormat}`); | |
| } | |
| if (this.config.options.enableImplicitTransactions === true) { | |
| options.push('set implicit_transactions on'); | |
| } else if (this.config.options.enableImplicitTransactions === false) { | |
| options.push('set implicit_transactions off'); | |
| } | |
| if (this.config.options.language !== null) { | |
| options.push(`set language ${this.config.options.language}`); | |
| } | |
| if (this.config.options.enableNumericRoundabort === true) { | |
| options.push('set numeric_roundabort on'); | |
| } else if (this.config.options.enableNumericRoundabort === false) { | |
| options.push('set numeric_roundabort off'); | |
| } | |
| if (this.config.options.enableQuotedIdentifier === true) { | |
| options.push('set quoted_identifier on'); | |
| } else if (this.config.options.enableQuotedIdentifier === false) { | |
| options.push('set quoted_identifier off'); | |
| } | |
| if (this.config.options.textsize !== null) { | |
| options.push(`set textsize ${this.config.options.textsize}`); | |
| } | |
| if (this.config.options.connectionIsolationLevel !== null) { | |
| options.push(`set transaction isolation level ${this.getIsolationLevelText(this.config.options.connectionIsolationLevel)}`); | |
| } | |
| if (this.config.options.abortTransactionOnError === true) { | |
| options.push('set xact_abort on'); | |
| } else if (this.config.options.abortTransactionOnError === false) { | |
| options.push('set xact_abort off'); | |
| } | |
| return options.join('\n'); | |
| } | |
| processedInitialSql() { | |
| this.clearConnectTimer(); | |
| this.emit('connect'); | |
| } | |
| execSqlBatch(request) { | |
| this.makeRequest(request, TYPE.SQL_BATCH, new SqlBatchPayload(request.sqlTextOrProcedure, this.currentTransactionDescriptor(), this.config.options)); | |
| } | |
| execSql(request) { | |
| request.transformIntoExecuteSqlRpc(); | |
| const error = request.error; | |
| if (error != null) { | |
| process.nextTick(() => { | |
| this.debug.log(error.message); | |
| request.callback(error); | |
| }); | |
| return; | |
| } | |
| this.makeRequest(request, TYPE.RPC_REQUEST, new RpcRequestPayload(request, this.currentTransactionDescriptor(), this.config.options)); | |
| } | |
| /** | |
| @function newBulkLoad | |
| @param {string} table - Table's name. | |
| @param {Object} [options] - BulkLoad options. | |
| @param {boolean} [options.checkConstraints=false] - Honors constraints during bulk load, it is disabled by default. | |
| @param {boolean} [options.fireTriggers=false] - Honors insert triggers during bulk load, it is disabled by default. | |
| @param {boolean} [options.keepNulls=false] - Honors null value passed, ignores the default values set on table. | |
| @param {boolean} [options.tableLock=false] - Places a bulk update(BU) lock on table while performing bulk load. Uses row locks by default. | |
| @param {callback} callback - Function to call after BulkLoad executes. | |
| */ | |
| newBulkLoad(table, options, callback) { | |
| if (callback === undefined) { | |
| callback = options; | |
| options = {}; | |
| } | |
| if (typeof options !== 'object') { | |
| throw new TypeError('"options" argument must be an object'); | |
| } | |
| return new BulkLoad(table, this.config.options, options, callback); | |
| } | |
| execBulkLoad(bulkLoad) { | |
| bulkLoad.executionStarted = true; | |
| const request = new Request(bulkLoad.getBulkInsertSql(), error => { | |
| if (error) { | |
| if (error.code === 'UNKNOWN') { | |
| error.message += ' This is likely because the schema of the BulkLoad does not match the schema of the table you are attempting to insert into.'; | |
| } | |
| bulkLoad.error = error; | |
| bulkLoad.callback(error); | |
| return; | |
| } | |
| this.makeRequest(bulkLoad, TYPE.BULK_LOAD, undefined); | |
| }); | |
| bulkLoad.once('cancel', () => { | |
| request.cancel(); | |
| }); | |
| this.execSqlBatch(request); | |
| } | |
| prepare(request) { | |
| request.transformIntoPrepareRpc(); | |
| this.makeRequest(request, TYPE.RPC_REQUEST, new RpcRequestPayload(request, this.currentTransactionDescriptor(), this.config.options)); | |
| } | |
| unprepare(request) { | |
| request.transformIntoUnprepareRpc(); | |
| this.makeRequest(request, TYPE.RPC_REQUEST, new RpcRequestPayload(request, this.currentTransactionDescriptor(), this.config.options)); | |
| } | |
| execute(request, parameters) { | |
| request.transformIntoExecuteRpc(parameters); | |
| const error = request.error; | |
| if (error != null) { | |
| process.nextTick(() => { | |
| this.debug.log(error.message); | |
| request.callback(error); | |
| }); | |
| return; | |
| } | |
| this.makeRequest(request, TYPE.RPC_REQUEST, new RpcRequestPayload(request, this.currentTransactionDescriptor(), this.config.options)); | |
| } | |
| callProcedure(request) { | |
| request.validateParameters(); | |
| const error = request.error; | |
| if (error != null) { | |
| process.nextTick(() => { | |
| this.debug.log(error.message); | |
| request.callback(error); | |
| }); | |
| return; | |
| } | |
| this.makeRequest(request, TYPE.RPC_REQUEST, new RpcRequestPayload(request, this.currentTransactionDescriptor(), this.config.options)); | |
| } | |
| beginTransaction(callback, name, isolationLevel) { | |
| isolationLevel || (isolationLevel = this.config.options.isolationLevel); | |
| const transaction = new Transaction(name || '', isolationLevel); | |
| if (this.config.options.tdsVersion < '7_2') { | |
| return this.execSqlBatch(new Request('SET TRANSACTION ISOLATION LEVEL ' + transaction.isolationLevelToTSQL() + ';BEGIN TRAN ' + transaction.name, (...args) => { | |
| this.transactionDepth++; | |
| if (this.transactionDepth === 1) { | |
| this.inTransaction = true; | |
| } | |
| callback(...args); | |
| })); | |
| } | |
| const request = new Request(undefined, err => { | |
| return callback(err, this.currentTransactionDescriptor()); | |
| }); | |
| return this.makeRequest(request, TYPE.TRANSACTION_MANAGER, transaction.beginPayload(this.currentTransactionDescriptor())); | |
| } | |
| commitTransaction(callback, name) { | |
| const transaction = new Transaction(name || ''); | |
| if (this.config.options.tdsVersion < '7_2') { | |
| return this.execSqlBatch(new Request('COMMIT TRAN ' + transaction.name, (...args) => { | |
| this.transactionDepth--; | |
| if (this.transactionDepth === 0) { | |
| this.inTransaction = false; | |
| } | |
| callback(...args); | |
| })); | |
| } | |
| const request = new Request(undefined, callback); | |
| return this.makeRequest(request, TYPE.TRANSACTION_MANAGER, transaction.commitPayload(this.currentTransactionDescriptor())); | |
| } | |
| rollbackTransaction(callback, name) { | |
| const transaction = new Transaction(name || ''); | |
| if (this.config.options.tdsVersion < '7_2') { | |
| return this.execSqlBatch(new Request('ROLLBACK TRAN ' + transaction.name, (...args) => { | |
| this.transactionDepth--; | |
| if (this.transactionDepth === 0) { | |
| this.inTransaction = false; | |
| } | |
| callback(...args); | |
| })); | |
| } | |
| const request = new Request(undefined, callback); | |
| return this.makeRequest(request, TYPE.TRANSACTION_MANAGER, transaction.rollbackPayload(this.currentTransactionDescriptor())); | |
| } | |
| saveTransaction(callback, name) { | |
| const transaction = new Transaction(name); | |
| if (this.config.options.tdsVersion < '7_2') { | |
| return this.execSqlBatch(new Request('SAVE TRAN ' + transaction.name, (...args) => { | |
| this.transactionDepth++; | |
| callback(...args); | |
| })); | |
| } | |
| const request = new Request(undefined, callback); | |
| return this.makeRequest(request, TYPE.TRANSACTION_MANAGER, transaction.savePayload(this.currentTransactionDescriptor())); | |
| } | |
| transaction(cb, isolationLevel) { | |
| if (typeof cb !== 'function') { | |
| throw new TypeError('`cb` must be a function'); | |
| } | |
| const useSavepoint = this.inTransaction; | |
| const name = '_tedious_' + crypto.randomBytes(10).toString('hex'); | |
| const txDone = (err, done, ...args) => { | |
| if (err) { | |
| if (this.inTransaction && this.state === this.STATE.LOGGED_IN) { | |
| this.rollbackTransaction(txErr => { | |
| done(txErr || err, ...args); | |
| }, name); | |
| } else { | |
| done(err, ...args); | |
| } | |
| } else if (useSavepoint) { | |
| if (this.config.options.tdsVersion < '7_2') { | |
| this.transactionDepth--; | |
| } | |
| done(null, ...args); | |
| } else { | |
| this.commitTransaction(txErr => { | |
| done(txErr, ...args); | |
| }, name); | |
| } | |
| }; | |
| if (useSavepoint) { | |
| return this.saveTransaction(err => { | |
| if (err) { | |
| return cb(err); | |
| } | |
| if (isolationLevel) { | |
| return this.execSqlBatch(new Request('SET transaction isolation level ' + this.getIsolationLevelText(isolationLevel), err => { | |
| return cb(err, txDone); | |
| })); | |
| } else { | |
| return cb(null, txDone); | |
| } | |
| }, name); | |
| } else { | |
| return this.beginTransaction(err => { | |
| if (err) { | |
| return cb(err); | |
| } | |
| return cb(null, txDone); | |
| }, name, isolationLevel); | |
| } | |
| } | |
| makeRequest(request, packetType, payload) { | |
| if (this.state !== this.STATE.LOGGED_IN) { | |
| const message = 'Requests can only be made in the ' + this.STATE.LOGGED_IN.name + ' state, not the ' + this.state.name + ' state'; | |
| this.debug.log(message); | |
| request.callback(RequestError(message, 'EINVALIDSTATE')); | |
| } else if (request.canceled) { | |
| process.nextTick(() => { | |
| request.callback(RequestError('Canceled.', 'ECANCEL')); | |
| }); | |
| } else { | |
| if (packetType === TYPE.SQL_BATCH) { | |
| this.isSqlBatch = true; | |
| } else { | |
| this.isSqlBatch = false; | |
| } | |
| this.request = request; | |
| this.request.connection = this; | |
| this.request.rowCount = 0; | |
| this.request.rows = []; | |
| this.request.rst = []; | |
| let message; | |
| this.request.once('cancel', () => { | |
| if (!this.isRequestActive(request)) { | |
| // Cancel was called on a request that is no longer active on this connection | |
| return; | |
| } // There's two ways to handle request cancelation: | |
| if (message.writable) { | |
| // - if the message is still writable, we'll set the ignore bit | |
| // and end the message. | |
| message.ignore = true; | |
| message.end(); | |
| } else { | |
| // - but if the message has been ended (and thus has been fully sent off), | |
| // we need to send an `ATTENTION` message to the server | |
| this.messageIo.sendMessage(TYPE.ATTENTION); | |
| this.transitionTo(this.STATE.SENT_ATTENTION); | |
| } | |
| this.clearRequestTimer(); | |
| this.createCancelTimer(); | |
| }); | |
| if (request instanceof BulkLoad) { | |
| message = request.getMessageStream(); // If the bulkload was not put into streaming mode by the user, | |
| // we end the rowToPacketTransform here for them. | |
| // | |
| // If it was put into streaming mode, it's the user's responsibility | |
| // to end the stream. | |
| if (!request.streamingMode) { | |
| request.rowToPacketTransform.end(); | |
| } | |
| this.messageIo.outgoingMessageStream.write(message); | |
| } else { | |
| this.createRequestTimer(); | |
| message = this.messageIo.sendMessage(packetType, payload.data, this.resetConnectionOnNextRequest); | |
| this.resetConnectionOnNextRequest = false; | |
| this.debug.payload(function () { | |
| return payload.toString(' '); | |
| }); | |
| } | |
| this.transitionTo(this.STATE.SENT_CLIENT_REQUEST); | |
| if (request.paused) { | |
| // Request.pause() has been called before the request was started | |
| this.pauseRequest(request); | |
| } | |
| } | |
| } | |
| cancel() { | |
| if (!this.request) { | |
| return false; | |
| } | |
| if (this.request.canceled) { | |
| return false; | |
| } | |
| this.request.cancel(); | |
| return true; | |
| } | |
| reset(callback) { | |
| const request = new Request(this.getInitialSql(), err => { | |
| if (this.config.options.tdsVersion < '7_2') { | |
| this.inTransaction = false; | |
| } | |
| callback(err); | |
| }); | |
| this.resetConnectionOnNextRequest = true; | |
| this.execSqlBatch(request); | |
| } | |
| currentTransactionDescriptor() { | |
| return this.transactionDescriptors[this.transactionDescriptors.length - 1]; | |
| } | |
| getIsolationLevelText(isolationLevel) { | |
| switch (isolationLevel) { | |
| case ISOLATION_LEVEL.READ_UNCOMMITTED: | |
| return 'read uncommitted'; | |
| case ISOLATION_LEVEL.REPEATABLE_READ: | |
| return 'repeatable read'; | |
| case ISOLATION_LEVEL.SERIALIZABLE: | |
| return 'serializable'; | |
| case ISOLATION_LEVEL.SNAPSHOT: | |
| return 'snapshot'; | |
| default: | |
| return 'read committed'; | |
| } | |
| } | |
| } | |
| module.exports = Connection; | |
| Connection.prototype.STATE = { | |
| CONNECTING: { | |
| name: 'Connecting', | |
| enter: function enter() { | |
| this.initialiseConnection(); | |
| }, | |
| events: { | |
| socketError: function socketError() { | |
| this.transitionTo(this.STATE.FINAL); | |
| }, | |
| connectTimeout: function connectTimeout() { | |
| this.transitionTo(this.STATE.FINAL); | |
| }, | |
| socketConnect: function socketConnect() { | |
| this.sendPreLogin(); | |
| this.transitionTo(this.STATE.SENT_PRELOGIN); | |
| } | |
| } | |
| }, | |
| SENT_PRELOGIN: { | |
| name: 'SentPrelogin', | |
| enter: function enter() { | |
| this.emptyMessageBuffer(); | |
| }, | |
| events: { | |
| socketError: function socketError() { | |
| this.transitionTo(this.STATE.FINAL); | |
| }, | |
| connectTimeout: function connectTimeout() { | |
| this.transitionTo(this.STATE.FINAL); | |
| }, | |
| data: function data(_data) { | |
| this.addToMessageBuffer(_data); | |
| }, | |
| message: function message() { | |
| const preloginPayload = new PreloginPayload(this.messageBuffer); | |
| this.debug.payload(function () { | |
| return preloginPayload.toString(' '); | |
| }); | |
| if (preloginPayload.fedAuthRequired === 1) { | |
| this.fedAuthRequired = true; | |
| } | |
| if (preloginPayload.encryptionString === 'ON' || preloginPayload.encryptionString === 'REQ') { | |
| if (!this.config.options.encrypt) { | |
| this.emit('connect', ConnectionError("Server requires encryption, set 'encrypt' config option to true.", 'EENCRYPT')); | |
| return this.close(); | |
| } | |
| this.messageIo.startTls(this.secureContext, this.config.server, this.config.options.trustServerCertificate); | |
| this.transitionTo(this.STATE.SENT_TLSSSLNEGOTIATION); | |
| } else { | |
| this.sendLogin7Packet(); | |
| const authentication = this.config.authentication; | |
| if (authentication.type === 'ntlm') { | |
| this.transitionTo(this.STATE.SENT_LOGIN7_WITH_NTLM); | |
| } else { | |
| this.transitionTo(this.STATE.SENT_LOGIN7_WITH_STANDARD_LOGIN); | |
| } | |
| } | |
| } | |
| } | |
| }, | |
| REROUTING: { | |
| name: 'ReRouting', | |
| enter: function enter() { | |
| this.cleanupConnection(this.cleanupTypeEnum.REDIRECT); | |
| }, | |
| events: { | |
| message: function message() {}, | |
| socketError: function socketError() { | |
| this.transitionTo(this.STATE.FINAL); | |
| }, | |
| connectTimeout: function connectTimeout() { | |
| this.transitionTo(this.STATE.FINAL); | |
| }, | |
| reconnect: function reconnect() { | |
| this.transitionTo(this.STATE.CONNECTING); | |
| } | |
| } | |
| }, | |
| TRANSIENT_FAILURE_RETRY: { | |
| name: 'TRANSIENT_FAILURE_RETRY', | |
| enter: function enter() { | |
| this.curTransientRetryCount++; | |
| this.cleanupConnection(this.cleanupTypeEnum.RETRY); | |
| }, | |
| events: { | |
| message: function message() {}, | |
| socketError: function socketError() { | |
| this.transitionTo(this.STATE.FINAL); | |
| }, | |
| connectTimeout: function connectTimeout() { | |
| this.transitionTo(this.STATE.FINAL); | |
| }, | |
| retry: function retry() { | |
| this.createRetryTimer(); | |
| } | |
| } | |
| }, | |
| SENT_TLSSSLNEGOTIATION: { | |
| name: 'SentTLSSSLNegotiation', | |
| events: { | |
| socketError: function socketError() { | |
| this.transitionTo(this.STATE.FINAL); | |
| }, | |
| connectTimeout: function connectTimeout() { | |
| this.transitionTo(this.STATE.FINAL); | |
| }, | |
| data: function data(_data2) { | |
| this.messageIo.tlsHandshakeData(_data2); | |
| }, | |
| message: function message() { | |
| if (this.messageIo.tlsNegotiationComplete) { | |
| this.sendLogin7Packet(); | |
| const authentication = this.config.authentication; | |
| if (authentication.type === 'azure-active-directory-password') { | |
| this.transitionTo(this.STATE.SENT_LOGIN7_WITH_FEDAUTH); | |
| } else if (authentication.type === 'ntlm') { | |
| this.transitionTo(this.STATE.SENT_LOGIN7_WITH_NTLM); | |
| } else { | |
| this.transitionTo(this.STATE.SENT_LOGIN7_WITH_STANDARD_LOGIN); | |
| } | |
| } | |
| } | |
| } | |
| }, | |
| SENT_LOGIN7_WITH_STANDARD_LOGIN: { | |
| name: 'SentLogin7WithStandardLogin', | |
| events: { | |
| socketError: function socketError() { | |
| this.transitionTo(this.STATE.FINAL); | |
| }, | |
| connectTimeout: function connectTimeout() { | |
| this.transitionTo(this.STATE.FINAL); | |
| }, | |
| data: function data(_data3) { | |
| this.sendDataToTokenStreamParser(_data3); | |
| }, | |
| routingChange: function routingChange() { | |
| this.transitionTo(this.STATE.REROUTING); | |
| }, | |
| featureExtAck: function featureExtAck(token) { | |
| const authentication = this.config.authentication; | |
| if (authentication.type === 'azure-active-directory-password' || authentication.type === 'azure-active-directory-access-token') { | |
| if (token.fedAuth === undefined) { | |
| this.loginError = ConnectionError('Did not receive Active Directory authentication acknowledgement'); | |
| this.loggedIn = false; | |
| } else if (token.fedAuth.length !== 0) { | |
| this.loginError = ConnectionError(`Active Directory authentication acknowledgment for ${authentication.type} authentication method includes extra data`); | |
| this.loggedIn = false; | |
| } | |
| } else if (token.fedAuth === undefined) { | |
| this.loginError = ConnectionError('Received acknowledgement for unknown feature'); | |
| this.loggedIn = false; | |
| } else { | |
| this.loginError = ConnectionError('Did not request Active Directory authentication, but received the acknowledgment'); | |
| this.loggedIn = false; | |
| } | |
| }, | |
| message: function message() { | |
| if (this.loggedIn) { | |
| this.transitionTo(this.STATE.LOGGED_IN_SENDING_INITIAL_SQL); | |
| } else if (this.loginError) { | |
| if (this.loginError.isTransient) { | |
| this.debug.log('Initiating retry on transient error'); | |
| this.transitionTo(this.STATE.TRANSIENT_FAILURE_RETRY); | |
| } else { | |
| this.emit('connect', this.loginError); | |
| this.transitionTo(this.STATE.FINAL); | |
| } | |
| } else { | |
| this.emit('connect', ConnectionError('Login failed.', 'ELOGIN')); | |
| this.transitionTo(this.STATE.FINAL); | |
| } | |
| } | |
| } | |
| }, | |
| SENT_LOGIN7_WITH_NTLM: { | |
| name: 'SentLogin7WithNTLMLogin', | |
| events: { | |
| socketError: function socketError() { | |
| this.transitionTo(this.STATE.FINAL); | |
| }, | |
| connectTimeout: function connectTimeout() { | |
| this.transitionTo(this.STATE.FINAL); | |
| }, | |
| data: function data(_data4) { | |
| this.sendDataToTokenStreamParser(_data4); | |
| }, | |
| message: function message() { | |
| if (this.ntlmpacket) { | |
| const authentication = this.config.authentication; | |
| const payload = new NTLMResponsePayload({ | |
| domain: authentication.options.domain, | |
| userName: authentication.options.userName, | |
| password: authentication.options.password, | |
| database: this.config.options.database, | |
| appName: this.config.options.appName, | |
| packetSize: this.config.options.packetSize, | |
| tdsVersion: this.config.options.tdsVersion, | |
| ntlmpacket: this.ntlmpacket, | |
| additional: this.additional | |
| }); | |
| this.messageIo.sendMessage(TYPE.NTLMAUTH_PKT, payload.data); | |
| this.debug.payload(function () { | |
| return payload.toString(' '); | |
| }); | |
| this.ntlmpacket = undefined; | |
| } else if (this.loggedIn) { | |
| this.transitionTo(this.STATE.LOGGED_IN_SENDING_INITIAL_SQL); | |
| } else if (this.loginError) { | |
| if (this.loginError.isTransient) { | |
| this.debug.log('Initiating retry on transient error'); | |
| this.transitionTo(this.STATE.TRANSIENT_FAILURE_RETRY); | |
| } else { | |
| this.emit('connect', this.loginError); | |
| this.transitionTo(this.STATE.FINAL); | |
| } | |
| } else { | |
| this.emit('connect', ConnectionError('Login failed.', 'ELOGIN')); | |
| this.transitionTo(this.STATE.FINAL); | |
| } | |
| } | |
| } | |
| }, | |
| SENT_LOGIN7_WITH_FEDAUTH: { | |
| name: 'SentLogin7Withfedauth', | |
| events: { | |
| socketError: function socketError() { | |
| this.transitionTo(this.STATE.FINAL); | |
| }, | |
| connectTimeout: function connectTimeout() { | |
| this.transitionTo(this.STATE.FINAL); | |
| }, | |
| data: function data(_data5) { | |
| this.sendDataToTokenStreamParser(_data5); | |
| }, | |
| routingChange: function routingChange() { | |
| this.transitionTo(this.STATE.REROUTING); | |
| }, | |
| fedAuthInfo: function fedAuthInfo(token) { | |
| this.fedAuthInfoToken = token; | |
| }, | |
| message: function message() { | |
| if (this.fedAuthInfoToken && this.fedAuthInfoToken.stsurl && this.fedAuthInfoToken.spn) { | |
| const clientId = '7f98cb04-cd1e-40df-9140-3bf7e2cea4db'; | |
| const context = new AuthenticationContext(this.fedAuthInfoToken.stsurl); | |
| const authentication = this.config.authentication; | |
| context.acquireTokenWithUsernamePassword(this.fedAuthInfoToken.spn, authentication.options.userName, authentication.options.password, clientId, (err, tokenResponse) => { | |
| if (err) { | |
| this.loginError = ConnectionError('Security token could not be authenticated or authorized.', 'EFEDAUTH'); | |
| this.emit('connect', this.loginError); | |
| this.transitionTo(this.STATE.FINAL); | |
| return; | |
| } | |
| this.sendFedAuthResponsePacket(tokenResponse); | |
| }); | |
| } else if (this.loginError) { | |
| if (this.loginError.isTransient) { | |
| this.debug.log('Initiating retry on transient error'); | |
| this.transitionTo(this.STATE.TRANSIENT_FAILURE_RETRY); | |
| } else { | |
| this.emit('connect', this.loginError); | |
| this.transitionTo(this.STATE.FINAL); | |
| } | |
| } else { | |
| this.emit('connect', ConnectionError('Login failed.', 'ELOGIN')); | |
| this.transitionTo(this.STATE.FINAL); | |
| } | |
| } | |
| } | |
| }, | |
| LOGGED_IN_SENDING_INITIAL_SQL: { | |
| name: 'LoggedInSendingInitialSql', | |
| enter: function enter() { | |
| this.sendInitialSql(); | |
| }, | |
| events: { | |
| socketError: function socketError() { | |
| this.transitionTo(this.STATE.FINAL); | |
| }, | |
| connectTimeout: function connectTimeout() { | |
| this.transitionTo(this.STATE.FINAL); | |
| }, | |
| data: function data(_data6) { | |
| this.sendDataToTokenStreamParser(_data6); | |
| }, | |
| message: function message() { | |
| this.transitionTo(this.STATE.LOGGED_IN); | |
| this.processedInitialSql(); | |
| } | |
| } | |
| }, | |
| LOGGED_IN: { | |
| name: 'LoggedIn', | |
| events: { | |
| socketError: function socketError() { | |
| this.transitionTo(this.STATE.FINAL); | |
| } | |
| } | |
| }, | |
| SENT_CLIENT_REQUEST: { | |
| name: 'SentClientRequest', | |
| exit: function exit(nextState) { | |
| this.clearRequestTimer(); | |
| if (nextState !== this.STATE.FINAL) { | |
| this.tokenStreamParser.resume(); | |
| } | |
| }, | |
| events: { | |
| socketError: function socketError(err) { | |
| const sqlRequest = this.request; | |
| this.request = undefined; | |
| this.transitionTo(this.STATE.FINAL); | |
| sqlRequest.callback(err); | |
| }, | |
| data: function data(_data7) { | |
| this.clearRequestTimer(); // request timer is stopped on first data package | |
| const ret = this.sendDataToTokenStreamParser(_data7); | |
| if (ret === false) { | |
| // Bridge backpressure from the token stream parser transform to the | |
| // packet stream transform. | |
| this.messageIo.pause(); | |
| } | |
| }, | |
| message: function message() { | |
| // We have to channel the 'message' (EOM) event through the token stream | |
| // parser transform, to keep it in line with the flow of the tokens, when | |
| // the incoming data flow is paused and resumed. | |
| this.tokenStreamParser.addEndOfMessageMarker(); | |
| }, | |
| endOfMessageMarkerReceived: function endOfMessageMarkerReceived() { | |
| this.transitionTo(this.STATE.LOGGED_IN); | |
| const sqlRequest = this.request; | |
| this.request = undefined; | |
| if (this.config.options.tdsVersion < '7_2' && sqlRequest.error && this.isSqlBatch) { | |
| this.inTransaction = false; | |
| } | |
| sqlRequest.callback(sqlRequest.error, sqlRequest.rowCount, sqlRequest.rows); | |
| } | |
| } | |
| }, | |
| SENT_ATTENTION: { | |
| name: 'SentAttention', | |
| enter: function enter() { | |
| this.attentionReceived = false; | |
| }, | |
| events: { | |
| socketError: function socketError(err) { | |
| const sqlRequest = this.request; | |
| this.request = undefined; | |
| this.transitionTo(this.STATE.FINAL); | |
| sqlRequest.callback(err); | |
| }, | |
| data: function data(_data8) { | |
| this.sendDataToTokenStreamParser(_data8); | |
| }, | |
| attention: function attention() { | |
| this.attentionReceived = true; | |
| }, | |
| message: function message() { | |
| // 3.2.5.7 Sent Attention State | |
| // Discard any data contained in the response, until we receive the attention response | |
| if (this.attentionReceived) { | |
| this.clearCancelTimer(); | |
| const sqlRequest = this.request; | |
| this.request = undefined; | |
| this.transitionTo(this.STATE.LOGGED_IN); | |
| if (sqlRequest.error && sqlRequest.error instanceof RequestError && sqlRequest.error.code === 'ETIMEOUT') { | |
| sqlRequest.callback(sqlRequest.error); | |
| } else { | |
| sqlRequest.callback(RequestError('Canceled.', 'ECANCEL')); | |
| } | |
| } | |
| } | |
| } | |
| }, | |
| FINAL: { | |
| name: 'Final', | |
| enter: function enter() { | |
| this.cleanupConnection(this.cleanupTypeEnum.NORMAL); | |
| }, | |
| events: { | |
| loginFailed: function loginFailed() {// Do nothing. The connection was probably closed by the client code. | |
| }, | |
| connectTimeout: function connectTimeout() {// Do nothing, as the timer should be cleaned up. | |
| }, | |
| message: function message() {// Do nothing | |
| }, | |
| socketError: function socketError() {// Do nothing | |
| } | |
| } | |
| } | |
| }; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment