UNPKG

@twec/node-suite

Version:

Generic functionality for connecting to NetSuite Web Services from Node

164 lines (147 loc) 5.61 kB
const got = require('got'); const fastxml = require('fast-xml-parser'); const xmlFormat = require('xml-formatter'); const debug = require('debug')('node-suite:soap'); const NetsuiteTBAuth = require('./auth'); const xml = require('./xml'); // "Standard Operations" // https://system.sandbox.netsuite.com/app/help/helpcenter.nl?fid=section_N3478008.html const OPERATIONS = { get: { responseName: 'getResponse', innerResponseName: 'readResponse' }, search: { responseName: 'searchResponse', innerResponseName: 'searchResult' }, upsert: { responseName: 'upsertResponse', innerResponseName: 'writeResponse' }, // Add more as needed (add, update, etc.) }; function handleError(err) { const error = (typeof err === 'string') ? new Error(err) : err; throw error; } function getNamespaces(version, body) { const NAMESPACES = { platformMsgs: `urn:messages_${version}.platform.webservices.netsuite.com`, platformCore: `urn:core_${version}.platform.webservices.netsuite.com`, platformFaults: `urn:faults_${version}.platform.webservices.netsuite.com`, platformCommon: `urn:common_${version}.platform.webservices.netsuite.com`, listRel: `urn:relationships_${version}.lists.webservices.netsuite.com`, tranSales: `urn:sales_${version}.transactions.webservices.netsuite.com`, actSched: `urn:scheduling_${version}.activities.webservices.netsuite.com`, setupCustom: `urn:customization_${version}.setup.webservices.netsuite.com`, listAcct: `urn:accounting_${version}.lists.webservices.netsuite.com`, tranBank: `urn:bank_${version}.transactions.webservices.netsuite.com`, tranCust: `urn:customers_${version}.transactions.webservices.netsuite.com`, tranInvt: `urn:inventory_${version}.transactions.webservices.netsuite.com`, listSupport: `urn:support_${version}.lists.webservices.netsuite.com`, tranGeneral: `urn:general_${version}.transactions.webservices.netsuite.com`, listMkt: `urn:marketing_${version}.lists.webservices.netsuite.com`, listWebsite: `urn:website_${version}.lists.webservices.netsuite.com`, fileCabinet: `urn:filecabinet_${version}.documents.webservices.netsuite.com`, listEmp: `urn:employees_${version}.lists.webservices.netsuite.com`, }; const namespaceAttributes = {}; const str = xml(body); const nameSpaceKeys = Object.keys(NAMESPACES); nameSpaceKeys.forEach((nameSpaceKey) => { if (new RegExp(`${nameSpaceKey}:`, 'ig').test(str)) { namespaceAttributes[`xmlns:${nameSpaceKey}`] = NAMESPACES[nameSpaceKey]; } }); return namespaceAttributes; } async function doXmlSoapRequest(soapEnvelope, requestData, operation) { const { url, method } = requestData; const xmlBody = `<?xml version="1.0" encoding="utf-8" ?>${xml(soapEnvelope)}`; const requestOptions = { method, headers: { 'Content-Type': 'text/xml; charset=utf-8', SOAPAction: operation, }, body: xmlBody, }; debug('request:', xmlFormat(xmlBody)); let response; try { response = await got(url, requestOptions); } catch (err) { if (!err.response) { throw err; } return err.response; } return response; } function parseSoapBody(responseBody) { const parsed = fastxml.parse(responseBody, { attributeNamePrefix: '@', ignoreAttributes: false, ignoreNameSpace: true, }); if (parsed && !parsed.Envelope) { console.error('Invalid SOAP response:'); console.error(JSON.stringify(parsed, null, 2)); } const soapBody = parsed.Envelope.Body; return soapBody; } function parseInnerResponse(soapBody, operation) { const op = OPERATIONS[operation]; if (!op) { handleError(`Operation invalid or unsupported: ${operation}`); } debug('operation', operation, op, 'soapBody', soapBody); const opResponse = soapBody[op.responseName] || {}; const innerResponse = opResponse[op.innerResponseName] || {}; return innerResponse; } function handleSoapFault(soapBodyFault) { const error = new Error(soapBodyFault.faultstring); error.code = soapBodyFault.faultcode; error.detail = soapBodyFault.detail; throw error; } async function soap({ config, operation, body }) { const tokenPassport = new NetsuiteTBAuth({ consumer: config.oauth.consumer, realm: config.passport.account, }); const { version } = config; const requestData = { url: `https://${config.domain}/services/NetSuitePort_${version}`, method: 'POST', }; const authData = tokenPassport.authorize(requestData, config.oauth.token); const soapEnvelope = { name: 'soap:Envelope', attributes: { 'xmlns:soap': 'http://schemas.xmlsoap.org/soap/envelope/', 'xmlns:xsi': 'http://www.w3.org/2001/XMLSchema-instance', 'xmlns:tns': `urn:platform_${version}.webservices.netsuite.com`, }, children: [ { name: 'soap:Header', children: [tokenPassport.toNode(authData)], }, { name: 'soap:Body', children: [ { name: `platformMsgs:${operation}`, children: [body], }, ], }, ], }; const nsAttributes = getNamespaces(version, soapEnvelope); soapEnvelope.attributes = Object.assign(soapEnvelope.attributes, nsAttributes); const response = await doXmlSoapRequest(soapEnvelope, requestData, operation); debug('response status: %s', response.statusCode); // debug('response body:\n%s', xmlFormat(response.body)); const soapBody = parseSoapBody(response.body); if (soapBody.Fault) { handleSoapFault(soapBody.Fault); } return parseInnerResponse(soapBody, operation); } module.exports = soap;