ea-crm-service-connector
Version:
A connector for Microsoft Dynamics CRM instances
234 lines (217 loc) • 8.07 kB
JavaScript
const Wreck = require('wreck')
const oauth = require('./oauth')
const configHelper = require('../../helpers/config-helper')
const crmConfig = configHelper.getCrmConfig()
const wreck = Wreck.defaults({
headers: {'Content-Type': 'application/json'},
events: true
})
const wreckReadOptions = {
json: true
}
if (process.env.CRM_API_DEBUG === 'all' || process.env.CRM_API_DEBUG === 'error') {
wreck.events.on('response', async (err, details) => {
try {
const uri = details.uri
const status = details.res ? details.res.statusCode : 400
const statusFamily = status / 100
const errorResponse = statusFamily === 4 || statusFamily === 5
if (process.env.CRM_API_DEBUG === 'all'
|| (process.env.CRM_API_DEBUG === 'error' && errorResponse)) {
const time = Date.now() - details.start
console.log(`CRM ${uri.method} request completed in (${time}ms)`)
console[errorResponse ? 'error' : 'log'](`[${status}] ${decodeURI(uri.href)}`)
}
} catch (e) {
console.error(`Error logging wreck request`, e)
}
})
}
function decorateError (err, errorPayload = null) {
// For backwards compatibility with previous versions of the crm-service-connector, which didn't return an Error object
err.status = !!err.status
err.error = errorPayload || err
return err
}
const self = module.exports = {
checkConnection: function (callback) {
if (self.connected === true) {
crmConfig.dynamics.connectionConfig.headers.Authorization = oauth.getActiveToken().tokenType + ' ' + oauth.getActiveToken().accessToken
callback(null, true)
} else {
self.init(function (err, result) {
if (err) {
callback(decorateError(err))
} else {
crmConfig.dynamics.connectionConfig.headers.Authorization = oauth.getActiveToken().tokenType + ' ' + oauth.getActiveToken().accessToken
self.connected = true
callback(null, true)
}
})
}
},
connected: false,
request: function (params) {
params.time = process.hrtime()
self.checkConnection(function (errCon, conResult) {
if (errCon) return params.callback(decorateError(errCon))
params.options.headers.Authorization = crmConfig.dynamics.connectionConfig.headers.Authorization
// Create the error object outside of the request callback for a more useful stacktrace
const defaultResponseError = new Error(`Unexpected response from CRM endpoint ${params.uri}`)
const crmResponseHandler = async function (err, result) {
params.retryCounter++
let statusCode = result && result.statusCode ? parseInt(result.statusCode) : 400
if (err) {
// console.log(`Error returned by CRM for URL ${params.options.baseUrl} with full URI ${params.uri}`, err)
if (err.output && err.output.statusCode) {
statusCode = err.output.statusCode
}
}
if (result) {
const responseJson = {headers: result.headers, statusCode: statusCode, payload: null}
responseJson.headers.returnedId = getOdataIdFromHeader(result.headers)
try {
const payload = await Wreck.read(result, wreckReadOptions)
if (payload && payload.error) {
// console.log('not found data 404: ', params.options.baseUrl, params.uri)
err = err || defaultResponseError
return params.callback(decorateError(err, payload.error))
} else {
responseJson.payload = payload
return params.callback(null, responseJson)
}
} catch (responseReadError) {
return params.callback(decorateError(responseReadError))
}
} else if ((err || statusCode > 399) && params.retryIfFailed && params.retryCounter < crmConfig.dynamics.endpointRetries) {
setTimeout(() => { self.request(params) }, params.retryWaitMillisecs)
} else {
return params.callback(decorateError(err || defaultResponseError))
}
}
;(async () => {
let result, error
try {
result = await wreck.request(params.method, params.uri, params.options)
} catch (err) {
error = err
} finally {
await crmResponseHandler(error, result)
}
})()
})
},
get: function (uri, retryIfFailed, callback) {
const params = _getParams(retryIfFailed)
params.method = 'GET'
params.uri = cleanString(uri)
params.callback = callback
self.request(params)
},
post: function (payload, uri, retryIfFailed, callback) {
let params = _getParams(retryIfFailed)
params.method = 'POST'
params = self.checkIfPostOnBehalfIsRequired(payload, params)
params.options.payload = JSON.stringify(payload)
params.callback = callback
params.uri = cleanString(uri)
self.request(params)
},
put: function (payload, uri, retryIfFailed, callback) {
let params = _getParams(retryIfFailed)
params.method = 'PUT'
params = self.checkIfPostOnBehalfIsRequired(payload, params)
params.options.payload = JSON.stringify(payload)
params.callback = callback
params.uri = cleanString(uri)
self.request(params)
},
patch: function (payload, uri, retryIfFailed, callback) {
let params = _getParams(retryIfFailed)
params.method = 'PATCH'
params = self.checkIfPostOnBehalfIsRequired(payload, params)
params.options.payload = JSON.stringify(payload)
params.callback = callback
params.uri = cleanString(uri)
self.request(params)
},
delete: function (uri, retryIfFailed, callback) {
const params = _getParams(retryIfFailed)
params.method = 'DELETE'
params.callback = callback
params.uri = uri
self.request(params)
},
init: function (callback) {
oauth.init(callback)
},
authenticate: function (username, password, callback) {
oauth.authenticate(username, password, function (err, activeToken) {
if (err) return callback(err)
callback(null, activeToken)
})
},
checkIfPostOnBehalfIsRequired: function (payload, params) {
try {
if (payload && payload.actAsUser && payload.actAsUser.userId) {
params.options.headers.MSCRMCallerID = payload.actAsUser.userId
try {
delete payload.actAsUser
} catch (err) {}
}
} catch (error) {
return params
}
return params
}
}
function getOdataIdFromHeader (header) {
let id = null
if (header['odata-entityid']) {
try {
const strId = header['odata-entityid'].toString()
id = strId.substring(strId.indexOf('(') + 1, strId.indexOf(')'))
} catch (error) {
id = null
}
}
return id
}
// Default params
function _getParams (retryIfFailed) {
const params = {
method: 'GET', // GET, POST, PUT, DELETE
uri: '/',
retryCounter: 0,
oAuthToken: true,
headers: {},
callback: null,
retryIfFailed: retryIfFailed,
endpointTimeOut: crmConfig.dynamics.endpointTimeOut,
endpointRetries: crmConfig.dynamics.endpointRetries,
retryWaitMillisecs: crmConfig.dynamics.endpointRetryWaitMillisecs,
options: {
baseUrl: crmConfig.dynamics.endpointUrl,
payload: null,
headers: crmConfig.dynamics.connectionConfig.headers,
timeout: crmConfig.dynamics.endpointTimeOut, // 30 second, default: unlimited
maxBytes: 99048576 // 1 MB, default: unlimited
}
}
return JSON.parse(JSON.stringify(params))
}
function cleanString (str) {
let newStr = ''
newStr = str.replace(/\s{2,10}/g, ' ')
newStr = newStr.replace(/[\n\r]+/g, '')
newStr = newStr.replace(/(\r\n|\n|\r)/gm, '')
newStr = newStr.replace(/[^\x20-\x7E]/gmi, '')
newStr = newStr.replace(/[\n\r]+/g, '')
newStr = newStr.replace(/[\f]+/g, '')
newStr = newStr.replace(/[\b]+/g, '')
newStr = newStr.replace(/[\t]+/g, '')
newStr = newStr.replace(/[\v]+/g, '')
newStr = newStr.replace(/'/g, '%27%27')
return newStr
}