node-suitetalk
Version:
NodeJs Suitetalk wrapper.
313 lines (264 loc) • 10.5 kB
JavaScript
;
const crypto = require("crypto");
const path = require("path");
const soap = require("soap");
const BaseObject = require("./baseObject");
/**
* @typedef {Object} ICredentials
* @property {string} email
* @property {string} password
* @property {string} role
* @property {string} applicationId
*/
/**
* @typedef {Object} IToken
* @property {string} consumer_key
* @property {string} consumer_secret
* @property {string} token_key
* @property {string} token_secret
*/
/**
* @typedef {Object} IConfigurationObject
* @property {string} account
* @property {string} apiVersion
* @property {boolean} accountSpecificUrl
* @property {ICredentials} [credentials]
* @property {IToken} [token]
* @property {string} wsdlPath
*/
/**
* Validate configuration object
* @param {IConfigurationObject} configuration
* @private
*/
function _validateConfiguration(configuration) {
// NetSuite account
if (!configuration.account) {
throw new Error("account is a required - NetSuite account number ex. 12345");
}
// API version
if (!configuration.apiVersion) {
throw new Error("apiVersion is required - NetSuite endpoint version ex. 2018_1");
}
// Path to WSDL
if (!configuration.wsdlPath) {
throw new Error("wsdlPath is a required - Path to netsuite.wsdl");
}
if (!configuration.credentials && !configuration.token) {
throw new Error("Must include either token or credentials object");
}
// Credentials
if (configuration.credentials) {
if (!configuration.credentials.email) {
throw new Error("credentials email is a required property");
}
if (!configuration.credentials.password) {
throw new Error("credentials password is a required property");
}
}
// Token
if (configuration.token) {
if (!configuration.token.consumer_key) {
throw new Error("token consumer_key is a required property");
}
if (!configuration.token.consumer_secret) {
throw new Error("token consumer_secret is a required property");
}
if (!configuration.token.token_key) {
throw new Error("token token_key is a required property");
}
if (!configuration.token.token_secret) {
throw new Error("token token_secret is a required property");
}
}
}
/**
* @typedef {Object} INameSpaces
* @property {string} actSched
* @property {string} fileCabinet
* @property {string} listAcct
* @property {string} listEmp
* @property {string} listMkt
* @property {string} listRel
* @property {string} listSupport
* @property {string} listWebsite
* @property {string} platformCommon
* @property {string} platformCore
* @property {string} platformMsgs
* @property {string} setupCustom
* @property {string} tranBank
* @property {string} tranCust
* @property {string} tranGeneral
* @property {string} tranInvt
* @property {string} tranSales
*/
/**
* Namespaces to be added to the SOAP header
* @param {IConfigurationObject} configuration
* @return {INameSpaces}
*/
function _getNameSpaces(configuration) {
return {
actSched: `urn:scheduling_${configuration.apiVersion}.activities.webservices.netsuite.com`,
fileCabinet: `urn:filecabinet_${configuration.apiVersion}.documents.webservices.netsuite.com`,
listAcct: `urn:accounting_${configuration.apiVersion}.lists.webservices.netsuite.com`,
listEmp: `urn:employees_${configuration.apiVersion}.lists.webservices.netsuite.com`,
listMkt: `urn:marketing_${configuration.apiVersion}.lists.webservices.netsuite.com`,
listRel: `urn:relationships_${configuration.apiVersion}.lists.webservices.netsuite.com`,
listSupport: `urn:support_${configuration.apiVersion}.lists.webservices.netsuite.com`,
listWebsite: `urn:website_${configuration.apiVersion}.lists.webservices.netsuite.com`,
platformCommon: `urn:common_${configuration.apiVersion}.platform.webservices.netsuite.com`,
platformCore: `urn:core_${configuration.apiVersion}.platform.webservices.netsuite.com`,
platformMsgs: `urn:messages_${configuration.apiVersion}.platform.webservices.netsuite.com`,
setupCustom: `urn:customization_${configuration.apiVersion}.setup.webservices.netsuite.com`,
tranBank: `urn:bank_${configuration.apiVersion}.transactions.webservices.netsuite.com`,
tranCust: `urn:customers_${configuration.apiVersion}.transactions.webservices.netsuite.com`,
tranGeneral: `urn:general_${configuration.apiVersion}.transactions.webservices.netsuite.com`,
tranInvt: `urn:inventory_${configuration.apiVersion}.transactions.webservices.netsuite.com`,
tranSales: `urn:sales_${configuration.apiVersion}.transactions.webservices.netsuite.com`,
};
}
/**
* @typedef {Object} IAuthKeys
* @property {string} account
* @property {string} consumerKey
* @property {string} nonce
* @property {number} timeStamp
* @property {string} tokenKey
* @property {string} base64hash
*/
/**
* Builds oAuth keys into object
* @param {IConfigurationObject} configuration
* @return {IAuthKeys}
*/
function getOAuthKeys(configuration) {
const res = {};
res.account = configuration.account;
res.consumerKey = configuration.token.consumer_key;
res.tokenKey = configuration.token.token_key;
res.nonce = Math.random().toString(36).substr(2, 15) +
Math.random().toString(36).substr(2, 15);
res.timeStamp = Math.round((new Date()).getTime() / 1000);
const key = `${configuration.token.consumer_secret}&${configuration.token.token_secret}`;
const baseString = configuration.account + "&" + configuration.token.consumer_key + "&" +
configuration.token.token_key + "&" + res.nonce + "&" + res.timeStamp;
res.base64hash = crypto.createHmac("sha256", Buffer.from(key, "utf8"))
.update(baseString).digest(null, null).toString("base64");
return res;
}
/**
* Returns a NS SOAP authentication header
* using either header credentials [NL_AUTH]
* or oauth via consumer/token pairs
* @param {IConfigurationObject} configuration
* @return {Object}
*/
function _createAuthHeader(configuration) {
const soapObj = {};
if (configuration.credentials) {
soapObj["platformMsgs:passport"] = {
"platformCore:account": configuration.account,
"platformCore:email": configuration.credentials.email,
"platformCore:password": configuration.credentials.password,
};
if (configuration.credentials.role) {
soapObj["platformMsgs:passport"]["platformCore:role"] = {
$attributes: {
internalId: configuration.credentials.role,
},
};
}
if (configuration.credentials.applicationId) {
soapObj["platformMsgs:applicationInfo"] = {
"platformMsgs:applicationId": configuration.credentials.applicationId,
};
}
}
if (configuration.token && !configuration.credentials) {
const o = getOAuthKeys(configuration);
soapObj["platformMsgs:tokenPassport"] = {
"platformCore:account": o.account,
"platformCore:consumerKey": o.consumerKey,
"platformCore:nonce": o.nonce,
"platformCore:timestamp": o.timeStamp,
"platformCore:token": o.tokenKey,
"platformCore:version": "1.0",
};
soapObj["platformMsgs:tokenPassport"]["platformCore:signature"] = {
$attributes: {
algorithm: "HMAC_SHA256",
},
$value: o.base64hash,
};
}
return soapObj;
}
class Configuration {
/**
* Object Constructor
* @param {IConfigurationObject} configuration
*/
constructor(configuration) {
_validateConfiguration(configuration);
this.configuration = configuration;
}
/**
* Add SOAP header to current client
* @param {BaseObject} header
*/
addClientSoapHeader(header) {
if (!this.client || !this.client.soapHeaders) {
throw new Error("Client not initialized");
}
const soapHeader = header.getNode();
this.client.addSoapHeader(soapHeader);
}
/**
* Remove SOAP header from current client
* @param {string} headerName
*/
removeClientSoapHeader(headerName) {
if (!this.client || !this.client.soapHeaders) {
return;
}
for (let i = this.client.soapHeaders.length - 1; i >= 0; i--) {
if (this.client.soapHeaders[i].indexOf(`<${headerName}`) === 0) {
this.client.soapHeaders.splice(i, 1);
}
}
}
/**
* Create node-soap client
* @return {Promise<any>}
*/
createConnection() {
const thisRef = this;
return new Promise((resolve, reject) => {
const config = thisRef.configuration;
let wsdlPath = config.wsdlPath;
if (wsdlPath.indexOf("netsuite.wsdl") === -1) {
wsdlPath = path.normalize(`${wsdlPath}/netsuite.wsdl`);
}
const soapOptions = {
attributesKey: "$attributes",
namespaceArrayElements: false
};
const account = config.account.replace("_", "-");
if (config.accountSpecificUrl) {
soapOptions.endpoint = `https://${account}.suitetalk.api.netsuite.com/services/NetSuitePort_${config.apiVersion}`;
}
soap.createClientAsync(wsdlPath, soapOptions).then((client) => {
Object.assign(client.wsdl.definitions.xmlns, _getNameSpaces(this.configuration));
client.wsdl.xmlnsInEnvelope = client.wsdl._xmlnsMap();
const authHeader = _createAuthHeader(config);
client.addSoapHeader(authHeader);
thisRef.client = client;
resolve(client);
}).catch((err) => {
reject(err);
});
});
}
}
module.exports = Configuration;