UNPKG

h54s

Version:

HTML5 Data Adapter for SAS

308 lines (275 loc) 9.73 kB
const logs = require('../logs.js'); const h54sError = require('../error.js'); const programNotFoundPatt = /<title>(Stored Process Error|SASStoredProcess)<\/title>[\s\S]*<h2>(Stored process not found:.*|.*not a valid stored process path.)<\/h2>/; const badJobDefinition = "<h2>Parameter Error <br/>Unable to get job definition.</h2>"; const responseReplace = function(res) { return res }; /** * Parse response from server * * @param {object} responseText - response html from the server * @param {string} sasProgram - sas program path * @param {object} params - params sent to sas program with addTable * */ module.exports.parseRes = function(responseText, sasProgram, params) { const matches = responseText.match(programNotFoundPatt); if(matches) { throw new h54sError('programNotFound', 'You have not been granted permission to perform this action, or the STP is missing.'); } //remove new lines in json response //replace \\(d) with \(d) - SAS json parser is escaping it return JSON.parse(responseReplace(responseText)); }; /** * Parse response from server in debug mode * * @param {object} responseText - response html from the server * @param {string} sasProgram - sas program path * @param {object} params - params sent to sas program with addTable * @param {string} hostUrl - same as in h54s constructor * @param {bool} isViya - same as in h54s constructor * */ module.exports.parseDebugRes = function (responseText, sasProgram, params, hostUrl, isViya) { const self = this let matches = responseText.match(programNotFoundPatt); if (matches) { throw new h54sError('programNotFound', 'Sas program completed with errors'); } if (isViya) { const matchesWrongJob = responseText.match(badJobDefinition); if (matchesWrongJob) { throw new h54sError('programNotFound', 'Sas program completed with errors. Unable to get job definition.'); } } //find json let patt = isViya ? /^(.?<iframe.*src=")([^"]+)(.*iframe>)/m : /^(.?--h54s-data-start--)([\S\s]*?)(--h54s-data-end--)/m; matches = responseText.match(patt); const page = responseText.replace(patt, ''); const htmlBodyPatt = /<body.*>([\s\S]*)<\/body>/; const bodyMatches = page.match(htmlBodyPatt); //remove html tags let debugText = bodyMatches[1].replace(/<[^>]*>/g, ''); debugText = this.decodeHTMLEntities(debugText); logs.addDebugData(bodyMatches[1], debugText, sasProgram, params); if (isViya && this.parseErrorResponse(responseText, sasProgram)) { throw new h54sError('sasError', 'Sas program completed with errors'); } if (!matches) { throw new h54sError('parseError', 'Unable to parse response json'); } const promise = new Promise(function (resolve, reject) { let jsonObj if (isViya) { const xhr = new XMLHttpRequest(); const baseUrl = hostUrl || ""; xhr.open("GET", baseUrl + matches[2]); xhr.onload = function () { if (this.status >= 200 && this.status < 300) { resolve(JSON.parse(xhr.responseText.replace(/(\r\n|\r|\n)/g, ''))); } else { reject(new h54sError('fetchError', xhr.statusText, this.status)) } }; xhr.onerror = function () { reject(new h54sError('fetchError', xhr.statusText)) }; xhr.send(); } else { try { jsonObj = JSON.parse(responseReplace(matches[2])); } catch (e) { reject(new h54sError('parseError', 'Unable to parse response json')) } if (jsonObj && jsonObj.h54sAbort) { resolve(jsonObj); } else if (self.parseErrorResponse(responseText, sasProgram)) { reject(new h54sError('sasError', 'Sas program completed with errors')) } else { resolve(jsonObj); } } }); return promise; }; /** * Add failed response to logs - used only if debug=false * * @param {string} responseText - response html from the server * @param {string} sasProgram - sas program path * */ module.exports.addFailedResponse = function(responseText, sasProgram) { const patt = /<script([\s\S]*)\/form>/; const patt2 = /display\s?:\s?none;?\s?/; //remove script with form for toggling the logs and "display:none" from style responseText = responseText.replace(patt, '').replace(patt2, ''); let debugText = responseText.replace(/<[^>]*>/g, ''); debugText = this.decodeHTMLEntities(debugText); logs.addFailedRequest(responseText, debugText, sasProgram); }; /** * Unescape all string values in returned object * * @param {object} obj * */ module.exports.unescapeValues = function(obj) { for (let key in obj) { if (typeof obj[key] === 'string') { obj[key] = decodeURIComponent(obj[key]); } else if(typeof obj === 'object') { this.unescapeValues(obj[key]); } } return obj; }; /** * Parse error response from server and save errors in memory * * @param {string} res - server response * @param {string} sasProgram - sas program which returned the response * */ module.exports.parseErrorResponse = function(res, sasProgram) { //capture 'ERROR: [text].' or 'ERROR xx [text].' const patt = /^ERROR(:\s|\s\d\d)(.*\.|.*\n.*\.)/gm; let errors = res.replace(/(<([^>]+)>)/ig, '').match(patt); if(!errors) { return; } let errMessage; for(let i = 0, n = errors.length; i < n; i++) { errMessage = errors[i].replace(/<[^>]*>/g, '').replace(/(\n|\s{2,})/g, ' '); errMessage = this.decodeHTMLEntities(errMessage); errors[i] = { sasProgram: sasProgram, message: errMessage, time: new Date() }; } logs.addSasErrors(errors); return true; }; /** * Decode HTML entities - old utility function * * @param {string} res - server response * */ module.exports.decodeHTMLEntities = function (html) { const tempElement = document.createElement('span'); let str = html.replace(/&(#(?:x[0-9a-f]+|\d+)|[a-z]+);/gi, function (str) { tempElement.innerHTML = str; str = tempElement.textContent || tempElement.innerText; return str; } ); return str; }; /** * Convert sas time to javascript date * * @param {number} sasDate - sas Tate object * */ module.exports.fromSasDateTime = function (sasDate) { const basedate = new Date("January 1, 1960 00:00:00"); const currdate = sasDate; // offsets for UTC and timezones and BST const baseOffset = basedate.getTimezoneOffset(); // in minutes // convert sas datetime to a current valid javascript date const basedateMs = basedate.getTime(); // in ms const currdateMs = currdate * 1000; // to ms const sasDatetime = currdateMs + basedateMs; const jsDate = new Date(); jsDate.setTime(sasDatetime); // first time to get offset BST daylight savings etc const currOffset = jsDate.getTimezoneOffset(); // adjust for offset in minutes const offsetVar = (baseOffset - currOffset) * 60 * 1000; // difference in milliseconds const offsetTime = sasDatetime - offsetVar; // finding BST and daylight savings jsDate.setTime(offsetTime); // update with offset return jsDate; }; /** * Checks whether response object is a login redirect * @param {Object} responseObj xhr response to be checked for logon redirect */ module.exports.needToLogin = function(responseObj) { const isSASLogon = responseObj.responseURL && responseObj.responseURL.includes('SASLogon') if (isSASLogon === false) { return false } const patt = /<form.+action="(.*Logon[^"]*).*>/; const matches = patt.exec(responseObj.responseText); let newLoginUrl; if(!matches) { //there's no form, we are in. hooray! return false; } else { const actionUrl = matches[1].replace(/\?.*/, ''); if(actionUrl.charAt(0) === '/') { newLoginUrl = this.hostUrl ? this.hostUrl + actionUrl : actionUrl; if(newLoginUrl !== this.loginUrl) { this._loginChanged = true; this.loginUrl = newLoginUrl; } } else { //relative path const lastIndOfSlash = responseObj.responseURL.lastIndexOf('/') + 1; //remove everything after the last slash, and everything until the first const relativeLoginUrl = responseObj.responseURL.substr(0, lastIndOfSlash).replace(/.*\/{2}[^\/]*/, '') + actionUrl; newLoginUrl = this.hostUrl ? this.hostUrl + relativeLoginUrl : relativeLoginUrl; if(newLoginUrl !== this.loginUrl) { this._loginChanged = true; this.loginUrl = newLoginUrl; } } //save parameters from hidden form fields const parser = new DOMParser(); const doc = parser.parseFromString(responseObj.responseText,"text/html"); const res = doc.querySelectorAll("input[type='hidden']"); const hiddenFormParams = {}; if(res) { //it's new login page if we have these additional parameters this._isNewLoginPage = true; res.forEach(function(node) { hiddenFormParams[node.name] = node.value; }); this._aditionalLoginParams = hiddenFormParams; } return true; } }; /** * Get full program path from metadata root and relative path * * @param {string} metadataRoot - Metadata root (path where all programs for the project are located) * @param {string} sasProgramPath - Sas program path * */ module.exports.getFullProgramPath = function(metadataRoot, sasProgramPath) { return metadataRoot ? metadataRoot.replace(/\/?$/, '/') + sasProgramPath.replace(/^\//, '') : sasProgramPath; }; // Returns object where table rows are groupped by key module.exports.getObjOfTable = function (table, key, value = null) { const obj = {} table.forEach(row => { if (!obj[row[key]]) { obj[row[key]] = [] obj[row[key]].push(value ? row[value] : row) } else { obj[row[key]].push(value ? row[value] : row) } }) return obj } // Returns self uri out of links array module.exports.getSelfUri = function (links) { return links .filter(e => e.rel === 'self') .map(e => e.uri) .shift(); }