h54s
Version:
HTML5 Data Adapter for SAS
191 lines (164 loc) • 7.65 kB
JavaScript
const h54sError = require('./error.js');
const sasVersionMap = {
v9: {
url: '/SASStoredProcess/do',
loginUrl: '/SASLogon/login',
logoutUrl: '/SASStoredProcess/do?_action=logoff',
RESTAuthLoginUrl: '/SASLogon/v1/tickets'
},
viya: {
url: '/SASJobExecution/',
loginUrl: '/SASLogon/login.do',
logoutUrl: '/SASLogon/logout.do?',
RESTAuthLoginUrl: ''
}
}
/**
*
* @constructor
* @param {Object} config - Configuration object for the H54S SAS Adapter
* @param {String} config.sasVersion - Version of SAS, either 'v9' or 'viya'
* @param {Boolean} config.debug - Whether debug mode is enabled, sets _debug=131
* @param {String} config.metadataRoot - Base path of all project services to be prepended to _program path
* @param {String} config.url - URI of the job executor - SPWA or JES
* @param {String} config.loginUrl - URI of the SASLogon web login path - overridden by form action
* @param {String} config.logoutUrl - URI of the logout action
* @param {String} config.RESTauth - Boolean to toggle use of REST authentication in SAS v9
* @param {String} config.RESTauthLoginUrl - Address of SASLogon tickets endpoint for REST auth
* @param {Boolean} config.retryAfterLogin - Whether to resume requests which were parked with login redirect after a successful re-login
* @param {Number} config.maxXhrRetries - If a program call fails, attempt to call it again N times until it succeeds
* @param {Number} config.ajaxTimeout - Number of milliseconds to wait for a response before closing the request
* @param {Boolean} config.useMultipartFormData - Whether to use multipart for POST - for legacy backend support
* @param {String} config.csrf - CSRF token for JES
* @
*
*/
const h54s = module.exports = function(config) {
// Default config values, overridden by anything in the config object
this.sasVersion = (config && config.sasVersion) || 'v9' //use v9 as default=
this.debug = (config && config.debug) || false;
this.metadataRoot = (config && config.metadataRoot) || '';
this.url = sasVersionMap[this.sasVersion].url;
this.loginUrl = sasVersionMap[this.sasVersion].loginUrl;
this.logoutUrl = sasVersionMap[this.sasVersion].logoutUrl;
this.RESTauth = false;
this.RESTauthLoginUrl = sasVersionMap[this.sasVersion].RESTAuthLoginUrl;
this.retryAfterLogin = true;
this.maxXhrRetries = 5;
this.ajaxTimeout = (config && config.ajaxTimeout) || 300000;
this.useMultipartFormData = (config && config.useMultipartFormData) || true;
this.csrf = ''
this.isViya = this.sasVersion === 'viya';
// Initialising callback stacks for when authentication is paused
this.remoteConfigUpdateCallbacks = [];
this._pendingCalls = [];
this._customPendingCalls = [];
this._disableCalls = false
this._ajax = require('./methods/ajax.js')();
_setConfig.call(this, config);
// If this instance was deployed with a standalone config external to the build use that
if(config && config.isRemoteConfig) {
const self = this;
this._disableCalls = true;
// 'h54sConfig.json' is for the testing with karma
//replaced by gulp in dev build (defined in gulpfile under proxies)
this._ajax.get('h54sConfig.json').success(function(res) {
const remoteConfig = JSON.parse(res.responseText)
// Save local config before updating it with remote config
const localConfig = Object.assign({}, config)
const oldMetadataRoot = localConfig.metadataRoot;
for(let key in remoteConfig) {
if(remoteConfig.hasOwnProperty(key) && key !== 'isRemoteConfig') {
config[key] = remoteConfig[key];
}
}
_setConfig.call(self, config);
// Execute callbacks when overrides from remote config are applied
for(let i = 0, n = self.remoteConfigUpdateCallbacks.length; i < n; i++) {
const fn = self.remoteConfigUpdateCallbacks[i];
fn();
}
// Execute sas calls disabled while waiting for the config
self._disableCalls = false;
while(self._pendingCalls.length > 0) {
const pendingCall = self._pendingCalls.shift();
const sasProgram = pendingCall.options.sasProgram;
const callbackPending = pendingCall.options.callback;
const params = pendingCall.params;
//update debug because it may change in the meantime
params._debug = self.debug ? 131 : 0;
// Update program path with metadataRoot if it's not set
if(self.metadataRoot && params._program.indexOf(self.metadataRoot) === -1) {
params._program = self.metadataRoot.replace(/\/?$/, '/') + params._program.replace(oldMetadataRoot, '').replace(/^\//, '');
}
// Update debug because it may change in the meantime
params._debug = self.debug ? 131 : 0;
self.call(sasProgram, null, callbackPending, params);
}
// Execute custom calls that we made while waitinf for the config
while(self._customPendingCalls.length > 0) {
const pendingCall = self._customPendingCalls.shift()
const callMethod = pendingCall.callMethod
const _url = pendingCall._url
const options = pendingCall.options;
///update program with metadataRoot if it's not set
if(self.metadataRoot && options.params && options.params._program.indexOf(self.metadataRoot) === -1) {
options.params._program = self.metadataRoot.replace(/\/?$/, '/') + options.params._program.replace(oldMetadataRoot, '').replace(/^\//, '');
}
//update debug because it also may have changed from remoteConfig
if (options.params) {
options.params._debug = self.debug ? 131 : 0;
}
self.managedRequest(callMethod, _url, options);
}
}).error(function (err) {
throw new h54sError('ajaxError', 'Remote config file cannot be loaded. Http status code: ' + err.status);
});
}
// private function to set h54s instance properties
function _setConfig(config) {
if(!config) {
this._ajax.setTimeout(this.ajaxTimeout);
return;
} else if(typeof config !== 'object') {
throw new h54sError('argumentError', 'First parameter should be config object');
}
//merge config object from parameter with this
for(let 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 requires CORS and is here for legacy support
if(config.hostUrl) {
if(config.hostUrl.charAt(config.hostUrl.length - 1) === '/') {
config.hostUrl = config.hostUrl.slice(0, -1);
}
this.hostUrl = config.hostUrl;
if (!this.url.includes(this.hostUrl)) {
this.url = config.hostUrl + this.url;
}
if (!this.loginUrl.includes(this.hostUrl)) {
this.loginUrl = config.hostUrl + this.loginUrl;
}
if (!this.RESTauthLoginUrl.includes(this.hostUrl)) {
this.RESTauthLoginUrl = config.hostUrl + this.RESTauthLoginUrl;
}
}
this._ajax.setTimeout(this.ajaxTimeout);
}
};
// replaced by gulp with real version at build time
h54s.version = '__version__';
h54s.prototype = require('./methods');
h54s.Tables = require('./tables');
h54s.Files = require('./files');
h54s.SasData = require('./sasData.js');
h54s.fromSasDateTime = require('./methods/utils.js').fromSasDateTime;
h54s.toSasDateTime = require('./tables/utils.js').toSasDateTime;
//self invoked function module
require('./ie_polyfills.js');