/*! h54s v0.7.1 - 2015-11-18 * License: GPL V3 * Author: Boemska */ if (!Object.create) { Object.create = function(proto, props) { if (typeof props !== "undefined") { throw "The multiple-argument version of Object.create is not provided by this browser and cannot be shimmed."; } function ctor() { } ctor.prototype = proto; return new ctor(); }; } // From https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys if (!Object.keys) { Object.keys = (function () { 'use strict'; var hasOwnProperty = Object.prototype.hasOwnProperty, hasDontEnumBug = !({toString: null}).propertyIsEnumerable('toString'), dontEnums = [ 'toString', 'toLocaleString', 'valueOf', 'hasOwnProperty', 'isPrototypeOf', 'propertyIsEnumerable', 'constructor' ], dontEnumsLength = dontEnums.length; return function (obj) { if (typeof obj !== 'object' && (typeof obj !== 'function' || obj === null)) { throw new TypeError('Object.keys called on non-object'); } var result = [], prop, i; for (prop in obj) { if (hasOwnProperty.call(obj, prop)) { result.push(prop); } } if (hasDontEnumBug) { for (i = 0; i < dontEnumsLength; i++) { if (hasOwnProperty.call(obj, dontEnums[i])) { result.push(dontEnums[i]); } } } return result; }; }()); } // From https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/lastIndexOf if (!Array.prototype.lastIndexOf) { Array.prototype.lastIndexOf = function(searchElement /*, fromIndex*/) { 'use strict'; if (this === void 0 || this === null) { throw new TypeError(); } var n, k, t = Object(this), len = t.length >>> 0; if (len === 0) { return -1; } n = len - 1; if (arguments.length > 1) { n = Number(arguments[1]); if (n != n) { n = 0; } else if (n !== 0 && n != (1 / 0) && n != -(1 / 0)) { n = (n > 0 || -1) * Math.floor(Math.abs(n)); } } for (k = n >= 0 ? Math.min(n, len - 1) : len - Math.abs(n); k >= 0; k--) { if (k in t && t[k] === searchElement) { return k; } } return -1; }; } /* global h54s: true */ /* * Represents html5 for sas adapter * @constructor * *@param {object} config - adapter config object, with keys like url, debug, etc. * */ var h54s = function(config) { //default config values this.maxXhrRetries = 5; this.url = "/SASStoredProcess/do"; this.debug = false; this.loginUrl = '/SASLogon/Logon.do'; this.retryAfterLogin = true; this.sasApp = 'Stored Process Web App 9.3'; this.ajaxTimeout = 30000; this.remoteConfigUpdateCallbacks = []; this._pendingCalls = []; if(config && config.isRemoteConfig) { var self = this; this._disableCalls = true; // '/base/test/h54sConfig.json' is for the testing with karma //replaced with grunt concat in build this._utils.ajax.get('h54sConfig.json').success(function(res) { var remoteConfig = JSON.parse(res.responseText); for(var key in remoteConfig) { if(remoteConfig.hasOwnProperty(key) && config[key] === undefined && key !== 'isRemoteConfig') { config[key] = remoteConfig[key]; } } _setConfig.call(self, config); //execute callbacks when we have remote config //note that remote conifg is merged with instance config for(var i = 0, n = self.remoteConfigUpdateCallbacks.length; i < n; i++) { var fn = self.remoteConfigUpdateCallbacks[i]; fn(); } //execute sas calls disabled while waiting for the config self._disableCalls = false; while(self._pendingCalls.length > 0) { var pendingCall = self._pendingCalls.shift(); var sasProgram = pendingCall.sasProgram; var callback = pendingCall.callback; var params = pendingCall.params; //update program with metadataRoot if it's not set if(self.metadataRoot && pendingCall.params._program.indexOf(self.metadataRoot) === -1) { pendingCall.params._program = self.metadataRoot.replace(/\/?$/, '/') + pendingCall.params._program.replace(/^\//, ''); } //update debug because it may change in the meantime params._debug = self.debug ? 131 : 0; self.call(sasProgram, null, callback, params); } }).error(function (err) { throw new h54s.Error('ajaxError', 'Remote config file cannot be loaded. Http status code: ' + err.status); }); } else { _setConfig.call(this, config); } // private function to set h54s instance properties function _setConfig(config) { if(!config) { return; } else if(typeof config !== 'object') { throw new h54s.Error('argumentError', 'First parameter should be config object'); } //merge config object from parameter with this for(var key in config) { if(config.hasOwnProperty(key)) { if((key === 'url' || key === 'loginUrl') && config[key].charAt(0) !== '/') { config[key] = '/' + config[key]; } this[key] = config[key]; } } //if server is remote use the full server url //NOTE: this is not permited by the same-origin policy if(config.hostUrl) { if(config.hostUrl.charAt(config.hostUrl.length - 1) === '/') { config.hostUrl = config.hostUrl.slice(0, -1); } this.hostUrl = config.hostUrl; this.url = config.hostUrl + this.url; this.loginUrl = config.hostUrl + this.loginUrl; } this._utils.ajax.setTimeout(this.ajaxTimeout); } }; /* * Add callback functions executed when properties are updated with remote config * *@callback - callback pushed to array * */ h54s.prototype.onRemoteConfigUpdate = function(callback) { this.remoteConfigUpdateCallbacks.push(callback); }; /* * h54s error constructor * @constructor * *@param {string} type - Error type *@param {string} message - Error message * */ h54s.Error = function(type, message) { if(Error.captureStackTrace) { Error.captureStackTrace(this); } this.message = message; this.type = type; }; h54s.Error.prototype = Object.create(Error.prototype); /* * h54s tables object constructor * @constructor * *@param {array} table - Table added when object is created *@param {string} message - macro name * */ h54s.Tables = function(table, macroName) { this._tables = {}; this.add(table, macroName); }; /* global h54s */ /* * Call Sas program * * @param {string} sasProgram - Path of the sas program * @param {function} callback - Callback function called when ajax call is finished * */ h54s.prototype.call = function(sasProgram, tablesObj, callback, params) { var self = this; var retryCount = 0; var dbg = this.debug; if (!callback || typeof callback !== 'function'){ throw new h54s.Error('argumentError', 'You must provide callback'); } if(!sasProgram) { throw new h54s.Error('argumentError', 'You must provide Sas program file path'); } if(typeof sasProgram !== 'string') { throw new h54s.Error('argumentError', 'First parameter should be string'); } if(!params) { params = { _program: this.metadataRoot ? this.metadataRoot.replace(/\/?$/, '/') + sasProgram.replace(/^\//, '') : sasProgram, _debug: this.debug ? 131 : 0, _service: 'default', }; } if(tablesObj) { if(tablesObj instanceof h54s.Tables) { for(var key in tablesObj._tables) { if(tablesObj._tables.hasOwnProperty(key)) { params[key] = tablesObj._tables[key]; } } } else { throw new h54s.Error('argumentError', 'Wrong type of tables object'); } } if(this._disableCalls) { this._pendingCalls.push({ sasProgram: sasProgram, callback: callback, params: params }); return; } this._utils.ajax.post(this.url, params).success(function(res) { if(self._needToLogin(res)) { //remember the call for latter use self._pendingCalls.push({ sasProgram: sasProgram, callback: callback, params: params }); //there's no need to continue if previous call returned login error if(self._disableCalls) { return; } else { self._disableCalls = true; } try { var sasAppMatches = res.responseURL.match(/_sasapp=([^&]*)/); self.sasApp = sasAppMatches[1].replace(/\+/g, ' '); } catch(e) { self._utils.addApplicationLogs('Cannot extract _sasapp parameter from login URL'); } callback(new h54s.Error('notLoggedinError', 'You are not logged in')); } else { var resObj, unescapedResObj; if(!dbg) { try { //remove new lines in json response resObj = JSON.parse(res.responseText.replace(/(\r\n|\r|\n)/g, '')); resObj = self._utils.convertDates(resObj); unescapedResObj = self._utils.unescapeValues(resObj); } catch(e) { if(retryCount < self.maxXhrRetries) { self._utils.ajax.post(self.url, params).success(this.success).error(this.error); retryCount++; self._utils.addApplicationLogs("Retrying #" + retryCount, sasProgram); } else { self._utils.parseErrorResponse(res.responseText, sasProgram); self._utils.addFailedResponse(res.responseText, sasProgram); callback(new h54s.Error('parseError', 'Unable to parse response json')); } } finally { if(unescapedResObj) { self._utils.addApplicationLogs(resObj.logmessage, sasProgram); callback(undefined, unescapedResObj); } } } else { try { resObj = self._utils.parseDebugRes(res.responseText, sasProgram, params); resObj = self._utils.convertDates(resObj); unescapedResObj = self._utils.unescapeValues(resObj); } catch(e) { self._utils.parseErrorResponse(res.responseText, sasProgram); callback(new h54s.Error('parseError', e.message)); } finally { if(unescapedResObj) { self._utils.addApplicationLogs(resObj.logmessage); if(resObj.hasErrors) { callback(new h54s.Error('sasError', 'Sas program completed with errors'), unescapedResObj); } else { callback(undefined, unescapedResObj); } } } } } }).error(function(res) { self._utils.addApplicationLogs('Request failed with status: ' + res.status, sasProgram); callback(new h54s.Error('httpError', res.statusText)); }); }; /* * Login method * * @param {string} user - Login username * @param {string} pass - Login password * @param {function} callback - Callback function called when ajax call is finished * * OR * * @param {function} callback - Callback function called when ajax call is finished * */ h54s.prototype.login = function(user, pass, callback) { var self = this; if(!user || !pass) { throw new h54s.Error('argumentError', 'Credentials not set'); } if(typeof user !== 'string' || typeof pass !== 'string') { throw new h54s.Error('argumentError', 'User and pass parameters must be strings'); } //NOTE: callback optional? if(!callback || typeof callback !== 'function') { throw new h54s.Error('argumentError', 'You must provide callback'); } var loginParams = { _sasapp: self.sasApp, _service: 'default', ux: user, px: pass, //for SAS 9.4, username: user, password: pass }; for (var key in this._aditionalLoginParams) { loginParams[key] = this._aditionalLoginParams[key]; } this._utils.ajax.post(this.loginUrl, loginParams).success(function(res) { if(self._needToLogin(res)) { //we are getting form again after redirect //and need to login again using the new url //_loginChanged is set in _needToLogin function //but if login url is not different, we are checking if there are aditional parameters if(self._loginChanged || (self._isNewLoginPage && !self._aditionalLoginParams)) { delete self._loginChanged; var inputs = res.responseText.match(/]*>/g); if(inputs) { inputs.forEach(function(inputStr) { var valueMatch = inputStr.match(/name="([^"]*)"\svalue="([^"]*)/); loginParams[valueMatch[1]] = valueMatch[2]; }); } self._utils.ajax.post(self.loginUrl, loginParams).success(this.success).error(this.error); } else { //getting form again, but it wasn't a redirect self._utils.addApplicationLogs('Wrong username or password'); callback(-1); } } else { callback(res.status); self._disableCalls = false; while(self._pendingCalls.length > 0) { var pendingCall = self._pendingCalls.shift(); var sasProgram = pendingCall.sasProgram; var callbackPending = pendingCall.callback; var params = pendingCall.params; //update debug because it may change in the meantime params._debug = self.debug ? 131 : 0; if(self.retryAfterLogin) { self.call(sasProgram, null, callbackPending, params); } } } }).error(function(res) { //NOTE: error 502 if sasApp parameter is wrong self._utils.addApplicationLogs('Login failed with status code: ' + res.status); callback(res.status); }); }; /* * Get sas errors if there are some * */ h54s.prototype.getSasErrors = function() { return h54s._logs.sasErrors; }; /* * Get application logs * */ h54s.prototype.getApplicationLogs = function() { return h54s._logs.applicationLogs; }; /* * Get debug data * */ h54s.prototype.getDebugData = function() { return h54s._logs.debugData; }; /* * Get failed requests * */ h54s.prototype.getFailedRequests = function() { return h54s._logs.failedRequests; }; /* * Enter debug mode * */ h54s.prototype.setDebugMode = function() { this.debug = true; }; /* * Exit debug mode * */ h54s.prototype.unsetDebugMode = function() { this.debug = false; }; /* * Clear application logs * */ h54s.prototype.clearApplicationLogs = function() { h54s._logs.applicationLogs = []; }; /* * Clear debug data * */ h54s.prototype.clearDebugData = function() { h54s._logs.debugData = []; }; /* * Clear Sas errors * */ h54s.prototype.clearSasErrors = function() { h54s._logs.sasErrors = []; }; /* * Clear failed requests * */ h54s.prototype.clearFailedRequests = function() { h54s._logs.failedRequests = []; }; /* * Clear all logs * */ h54s.prototype.clearAllLogs = function() { this.clearApplicationLogs(); this.clearDebugData(); this.clearSasErrors(); this.clearFailedRequests(); }; /* * Add table to tables object * @param {array} table - Array of table objects * @param {string} macroName - Sas macro name * */ h54s.Tables.prototype.add = function(table, macroName) { if(table && macroName) { if(!(table instanceof Array)) { throw new h54s.Error('argumentError', 'First argument must be array'); } if(typeof macroName !== 'string') { throw new h54s.Error('argumentError', 'Second argument must be string'); } if(!isNaN(macroName[macroName.length - 1])) { throw new h54s.Error('argumentError', 'Macro name cannot have number at the end'); } } else { throw new h54s.Error('argumentError', 'Missing arguments'); } var result = this._utils.convertTableObject(table); var tableArray = []; tableArray.push(JSON.stringify(result.spec)); for (var numberOfTables = 0; numberOfTables < result.data.length; numberOfTables++) { var outString = JSON.stringify(result.data[numberOfTables]); tableArray.push(outString); } this._tables[macroName] = tableArray; }; /* global h54s, XMLHttpRequest, ActiveXObject, document, clearTimeout, setTimeout */ h54s.prototype._utils = {}; h54s.Tables.prototype._utils = {}; h54s._logs = { applicationLogs: [], debugData: [], sasErrors: [], failedRequests: [] }; h54s.prototype._utils.ajax = (function () { var timeout = 30000; var timeoutHandle; var xhr = function(type, url, data) { var methods = { success: function() {}, error: function() {} }; var XHR = XMLHttpRequest || ActiveXObject; var request = new XHR('MSXML2.XMLHTTP.3.0'); request.open(type, url, true); request.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); request.onreadystatechange = function () { if (request.readyState === 4) { clearTimeout(timeoutHandle); if (request.status >= 200 && request.status < 300) { methods.success.call(methods, request); } else { methods.error.call(methods, request); } } }; timeoutHandle = setTimeout(function() { request.abort(); }, timeout); request.send(data); return { success: function (callback) { methods.success = callback; return this; }, error: function (callback) { methods.error = callback; return this; } }; }; var serialize = function(obj) { var str = []; for(var p in obj) if (obj.hasOwnProperty(p)) { if(obj[p] instanceof Array) { for(var i = 0, n = obj[p].length; i < n; i++) { str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p][i])); } } else { str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p])); } } return str.join("&"); }; return { get: function(url, data) { var dataStr; if(typeof data === 'object') { dataStr = serialize(data); } var urlWithParams = dataStr ? (url + '?' + dataStr) : url; return xhr('GET', urlWithParams); }, post: function(url, data) { var dataStr; if(typeof data === 'object') { dataStr = serialize(data); } return xhr('POST', url, dataStr); }, setTimeout: function(t) { timeout = t; } }; })(); /* * Convert table object to Sas readable object * * @param {object} inObject - Object to convert * */ h54s.Tables.prototype._utils.convertTableObject = function(inObject) { var self = this; var chunkThreshold = 30000; // this goes to 30k for SAS // first check that the object is an array if (typeof (inObject) !== 'object') { throw new h54s.Error('argumentError', 'The parameter passed to checkAndGetTypeObject is not an object'); } var arrayLength = inObject.length; if (typeof (arrayLength) !== 'number') { throw new h54s.Error('argumentError', 'The parameter passed to checkAndGetTypeObject does not have a valid length and is most likely not an array'); } var existingCols = {}; // this is just to make lookup easier rather than traversing array each time. Will transform after // function checkAndSetArray - this will check an inObject current key against the existing typeArray and either return -1 if there // is a type mismatch or add an element and update/increment the length if needed function checkAndIncrement(colSpec) { if (typeof (existingCols[colSpec.colName]) === 'undefined') { existingCols[colSpec.colName] = {}; existingCols[colSpec.colName].colName = colSpec.colName; existingCols[colSpec.colName].colType = colSpec.colType; existingCols[colSpec.colName].colLength = colSpec.colLength > 0 ? colSpec.colLength : 1; return 0; // all ok } // check type match if (existingCols[colSpec.colName].colType !== colSpec.colType) { return -1; // there is a fudge in the typing } if (existingCols[colSpec.colName].colLength < colSpec.colLength) { existingCols[colSpec.colName].colLength = colSpec.colLength > 0 ? colSpec.colLength : 1; // increment the max length of this column return 0; } } var chunkArrayCount = 0; // this is for keeping tabs on how long the current array string would be var targetArray = []; // this is the array of target arrays var currentTarget = 0; targetArray[currentTarget] = []; var j = 0; for (var i = 0; i < inObject.length; i++) { targetArray[currentTarget][j] = {}; var chunkRowCount = 0; for (var key in inObject[i]) { var thisSpec = {}; var thisValue = inObject[i][key]; //skip undefined values if(thisValue === undefined || thisValue === null) { continue; } //throw an error if there's NaN value if(typeof thisValue === 'number' && isNaN(thisValue)) { throw new h54s.Error('typeError', 'NaN value in one of the values (columns) is not allowed'); } if(thisValue === -Infinity || thisValue === Infinity) { throw new h54s.Error('typeError', thisValue.toString() + ' value in one of the values (columns) is not allowed'); } if(thisValue === true || thisValue === false) { throw new h54s.Error('typeError', 'Boolean value in one of the values (columns) is not allowed'); } // get type... if it is an object then convert it to json and store as a string var thisType = typeof (thisValue); var isDate = thisValue instanceof Date; if (thisType === 'number') { // straightforward number if(thisValue < Number.MIN_SAFE_INTEGER || thisValue > Number.MAX_SAFE_INTEGER) { h54s.prototype._utils.addApplicationLogs.call(null, 'Object[' + i + '].' + key + ' - This value exceeds expected numeric precision.'); } thisSpec.colName = key; thisSpec.colType = 'num'; thisSpec.colLength = 8; thisSpec.encodedLength = thisValue.toString().length; targetArray[currentTarget][j][key] = thisValue; } else if (thisType === 'string' && !isDate) { // straightforward string thisSpec.colName = key; thisSpec.colType = 'string'; thisSpec.colLength = thisValue.length; if (thisValue === "") { targetArray[currentTarget][j][key] = " "; } else { targetArray[currentTarget][j][key] = encodeURIComponent(thisValue).replace(/'/g, '%27'); } thisSpec.encodedLength = targetArray[currentTarget][j][key].length; } else if(isDate) { thisSpec.colName = key; thisSpec.colType = 'date'; thisSpec.colLength = 8; targetArray[currentTarget][j][key] = self.toSasDateTime(thisValue); thisSpec.encodedLength = targetArray[currentTarget][j][key].toString().length; } else if (thisType == 'object') { thisSpec.colName = key; thisSpec.colType = 'json'; thisSpec.colLength = JSON.stringify(thisValue).length; targetArray[currentTarget][j][key] = encodeURIComponent(JSON.stringify(thisValue)).replace(/'/g, '%27'); thisSpec.encodedLength = targetArray[currentTarget][j][key].length; } chunkRowCount = chunkRowCount + 6 + key.length + thisSpec.encodedLength; if (checkAndIncrement(thisSpec) == -1) { throw new h54s.Error('typeError', 'There is a type mismatch in the array between values (columns) of the same name.'); } } if (chunkRowCount > chunkThreshold) { throw new h54s.Error('argumentError', 'Row ' + j + ' exceeds size limit of 32kb'); } else if(chunkArrayCount + chunkRowCount > chunkThreshold) { //create new array if this one is full and move the last item to the new array var lastRow = targetArray[currentTarget].pop(); // get rid of that last row currentTarget++; // move onto the next array targetArray[currentTarget] = [lastRow]; // make it an array j = 0; // initialise new row counter for new array - it will be incremented at the end of the function chunkArrayCount = chunkRowCount; // this is the new chunk max size } else { chunkArrayCount = chunkArrayCount + chunkRowCount; } j++; } // reformat existingCols into an array so sas can parse it; var specArray = []; for (var k in existingCols) { specArray.push(existingCols[k]); } return { spec: specArray, data: targetArray, jsonLength: chunkArrayCount }; // the spec will be the macro[0], with the data split into arrays of macro[1-n] // means in terms of dojo xhr object at least they need to go into the same array }; /* * 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 * */ h54s.prototype._utils.parseDebugRes = function(responseText, sasProgram, params) { //find json var patt = /^(.?--h54s-data-start--)([\S\s]*)(--h54s-data-end--)/m; var matches = responseText.match(patt); var page = responseText.replace(patt, ''); var htmlBodyPatt = /([\s\S]*)<\/body>/; var bodyMatches = page.match(htmlBodyPatt); //remove html tags var debugText = bodyMatches[1].replace(/<[^>]*>/g, ''); debugText = this.decodeHTMLEntities(debugText); h54s._logs.debugData.push({ debugHtml: bodyMatches[1], debugText: debugText, sasProgram: sasProgram, params: params, time: new Date() }); //max 20 debug objects if(h54s._logs.debugData.length > 20) { h54s._logs.debugData.shift(); } this.parseErrorResponse(responseText, sasProgram); //remove new lines in json response var jsonObj = JSON.parse(matches[2].replace(/(\r\n|\r|\n)/g, '')); if(debugText.indexOf('ERROR:') !== -1) { jsonObj.hasErrors = true; } return jsonObj; }; /* * Add failed response to logs - used only if debug=false * * @param {object} responseText - response html from the server * @param {string} sasProgram - sas program path * */ h54s.prototype._utils.addFailedResponse = function(responseText, sasProgram) { var patt = //; var 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, ''); var debugText = responseText.replace(/<[^>]*>/g, ''); debugText = this.decodeHTMLEntities(debugText); h54s._logs.failedRequests.push({ responseHtml: responseText, responseText: debugText, sasProgram: sasProgram, time: new Date() }); //max 20 failed requests if(h54s._logs.failedRequests.length > 20) { h54s._logs.failedRequests.shift(); } }; /* * Unescape all string values in returned object * * @param {object} obj * */ h54s.prototype._utils.unescapeValues = function(obj) { for (var 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 * */ h54s.prototype._utils.parseErrorResponse = function(res, sasProgram) { var patt = /ERROR(.*\.|.*\n.*\.)/g; var errors = res.match(patt); if(!errors) { return; } var errMessage; for(var 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() }; } h54s._logs.sasErrors = h54s._logs.sasErrors.concat(errors); while(h54s._logs.sasErrors.length > 100) { h54s._logs.sasErrors.shift(); } }; /* * Decode HTML entities * * @param {string} res - server response * */ h54s.prototype._utils.decodeHTMLEntities = function (html) { var tempElement = document.createElement('span'); var 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; }; /* * Adds application logs to an array of logs * * @param {string} res - server response * */ h54s.prototype._utils.addApplicationLogs = function(message, sasProgram) { if(message === 'blank') { return; } var log = { message: message, time: new Date(), sasProgram: sasProgram }; h54s._logs.applicationLogs.push(log); //100 log messages max if(h54s._logs.applicationLogs.length > 100) { h54s._logs.applicationLogs.shift(); } }; /* * Convert javascript date to sas time * * @param {object} jsDate - javascript Date object * */ h54s.Tables.prototype._utils.toSasDateTime = function (jsDate) { var basedate = new Date("January 1, 1960 00:00:00"); var currdate = jsDate; // offsets for UTC and timezones and BST var baseOffset = basedate.getTimezoneOffset(); // in minutes var currOffset = currdate.getTimezoneOffset(); // in minutes // convert currdate to a sas datetime var offsetSecs = (currOffset - baseOffset) * 60; // offsetDiff is in minutes to start with var baseDateSecs = basedate.getTime() / 1000; // get rid of ms var currdateSecs = currdate.getTime() / 1000; // get rid of ms var sasDatetime = Math.round(currdateSecs - baseDateSecs - offsetSecs); // adjust return sasDatetime; }; /* * Convert sas time to javascript date * * @param {number} sasDate - sas Tate object * */ h54s.prototype._utils.fromSasDateTime = function (sasDate) { var basedate = new Date("January 1, 1960 00:00:00"); var currdate = sasDate; // offsets for UTC and timezones and BST var baseOffset = basedate.getTimezoneOffset(); // in minutes // convert sas datetime to a current valid javascript date var basedateMs = basedate.getTime(); // in ms var currdateMs = currdate * 1000; // to ms var sasDatetime = currdateMs + basedateMs; var jsDate = new Date(); jsDate.setTime(sasDatetime); // first time to get offset BST daylight savings etc var currOffset = jsDate.getTimezoneOffset(); // adjust for offset in minutes var offsetVar = (baseOffset - currOffset) * 60 * 1000; // difference in milliseconds var offsetTime = sasDatetime - offsetVar; // finding BST and daylight savings jsDate.setTime(offsetTime); // update with offset return jsDate; }; /* * Convert sas timestamps to javascript Date object * * @param {object} obj * */ h54s.prototype._utils.convertDates = function(obj) { for (var key in obj) { if (typeof obj[key] === 'number' && (key.indexOf('dt_') === 0 || key.indexOf('DT_') === 0)) { obj[key] = this.fromSasDateTime(obj[key]); } else if(typeof obj === 'object') { this.convertDates(obj[key]); } } return obj; }; h54s.prototype._needToLogin = function(responseObj) { var patt = //; var matches = patt.exec(responseObj.responseText); var newLoginUrl; if(!matches) { //there's no form, we are in. hooray! return false; } else { var 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 var lastIndOfSlash = responseObj.responseURL.lastIndexOf('/') + 1; //remove everything after the last slash, and everything until the first var 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 var inputs = responseObj.responseText.match(/]*>/g); var hiddenFormParams = {}; if(inputs) { //it's new login page if we have these additional parameters this._isNewLoginPage = true; inputs.forEach(function(inputStr) { var valueMatch = inputStr.match(/name="([^"]*)"\svalue="([^"]*)/); hiddenFormParams[valueMatch[1]] = valueMatch[2]; }); this._aditionalLoginParams = hiddenFormParams; } return true; } };