var Conf = { appName: 'Verdnatura', dsName: 'verdnatura', odbcPath: 'HKCU\\Software\\ODBC\\ODBC.INI\\', regPath: 'HKCU\\SOFTWARE\\Verdnatura\\vn-access', defaultModule: 'vn', defaultLocale: 'es', defaultBranch: 'master', defaultDatasource: 'production', defaultRemoteURL: 'https://salix.verdnatura.es', dbName: 'vn2008', maxCorruptSize: 600, odbcDriver: 'MySQL ODBC 8.0 Unicode Driver', driverPath: '\\MySQL\\Connector ODBC 8.0\\myodbc8w.dll', version: 4, cdnURL: 'https://cdn.verdnatura.es/vn-access', datasources: { production: { DESCRIPTION: 'production', SERVER: 'db.verdnatura.es', PORT: '3306', SSLMODE: 'VERIFY_IDENTITY' }, test: { DESCRIPTION: 'test', SERVER: 'test-db.verdnatura.es', PORT: '3306', SSLMODE: 'VERIFY_IDENTITY' }, dev: { DESCRIPTION: 'dev', SERVER: 'dev-db.verdnatura.es', PORT: '3307', SSLMODE: 'DISABLED' }, localhost: { DESCRIPTION: 'local', SERVER: 'localhost', PORT: '3306', SSLMODE: 'DISABLED' } } }; var Locale = { es: { "Enter a username": "Introduce un nombre de usuario", "Enter a password": "Introduce una contraseña", "Server can't be reached": "No se ha podido conectar con el servidor", "Updating": "Actualizando", "Bad login": "Usuario o contraseña incorrectos, recuerda que se hace distinción entre mayúsculas y minúsculas", "Application it's already open": "La aplicación ya está abierta", "Loading": "Cargando", "Error while updating": "Error al actualizar", "Microsoft Access 2003 is not installed": "Microsoft Access 2003 no está instalado en el sistema", "MDB file not found": "No se encontró el fichero MDB", "Cache files have been deleted": "Se han borrado todos los ficheros almacenados en la caché" } }; var App = { shell: new ActiveXObject('WScript.Shell'), fso: new ActiveXObject('scripting.filesystemobject'), init: function() { // Specify the size of window var width = 420; var height = 570; window.resizeTo(width, height); window.moveTo((screen.width - width) / 2, (screen.height - height) / 2); }, onUnload: function() { this.disableUi(false); }, onLoad: function() { // Initializes the global variables var split = Verdnatura.commandLine.match(/(?:[^\s"]+|"[^"]*")+/g); if (split.length > 1) this.module = split[1].replace(/^"+|"+$/g, ''); if (!this.module) this.module = Conf.defaultModule; this.appDir = this.getEnv('ProgramFiles') + '\\' + Conf.appName; this.moduleDir = this.shell.SpecialFolders('AppData') + '\\' + Conf.appName; this.compressFile = this.getEnv('TEMP') + '\\' + this.module + '.7z'; this.mdbFile = this.moduleDir + '\\' + this.module + '.mdb'; this.lockFile = this.moduleDir + '\\' + this.module + '.ldb'; this.certFile = this.appDir + '\\cacert.pem'; // Creates the necessary registry entries var lastVersion = this.regRead(Conf.regPath, 'lastExecutedVersion'); if (!lastVersion || lastVersion != Conf.version) { var path = 'HKCU\\Software\\Microsoft\\Office\\11.0\\Access\\'; // Creates the Access configuration entries this.regWrites(path + 'Settings', { 'Confirm Document Deletions': false, 'Confirm Action Queries': false, 'Confirm Record Changes': false }); this.regWrite(path + 'Security', 'Level', 1); // Creates the MySQL ODBC connection this.createODBC( Conf.odbcPath, Conf.dsName, Conf.odbcDriver ); this.createDatasources(); this.regWrite(Conf.regPath, 'remoteURL', Conf.defaultRemoteURL); this.regWrite(Conf.regPath, 'lastExecutedVersion', Conf.version); // Delete old strings values (REMOVE IN VERSION 5) this.regDelete(Conf.regPath + 'configured'); this.regDelete(Conf.regPath + 'remember'); } var notSignOut = this.regRead(Conf.regPath, 'notSignOut'); var password = this.regRead(Conf.odbcPath + Conf.dsName, 'PWD'); this.$('user').value = this.regRead(Conf.odbcPath + Conf.dsName, 'UID'); this.$('password').value = password; // Branch options this.$('branch').value = this.getBranch(); this.onChangeBranch(); // Datasource options this.$('datasource').value = this.getDatasource(); if (notSignOut && password) { this.$('password').value = password; this.$('notSignOut').checked = true; this.onChangeDatasource(true, false); this.onEnterClick(); } else { this.resetForm(true); this.onChangeDatasource(true, true); } }, resetForm: function(clearPassword) { if (clearPassword) this.$('password').value = ''; this.$('user').focus(); this.$('user').select(); }, createODBC: function(path, dsName, driverName) { var driverPath = this.getEnv('ProgramFiles') + Conf.driverPath; var datasource = Conf.defaultDatasource; var params = { Driver: driverPath, DESCRIPTION: Conf.appName, SERVER: Conf.datasources[datasource].SERVER, DATABASE: Conf.dbName, SSLCA: this.certFile, SSLMODE: Conf.datasources[datasource].SSLMODE, SSLCIPHER: 'AES256-SHA', AUTO_RECONNECT: 1, NO_PROMPT: 1, ENABLE_CLEARTEXT_PLUGIN: 1 }; this.regWrite(path + 'ODBC Data Sources', dsName, driverName); this.regWrites(path + dsName, params); }, updateODBC: function(path, dsName, datasource) { var params = Conf.datasources[datasource] var datasourcePath = this.getDatasourcePath(); var myUID = this.regRead(datasourcePath, 'UID'); var myPWD = this.regRead(datasourcePath, 'PWD'); if (myUID && myPWD) { params['UID'] = myUID; params['PWD'] = myPWD; } this.regWrites(path + dsName, params); }, disableUi: function(disabled, loadMessage) { if (disabled) this.hideMessage(); else loadMessage = ''; this.$('loading-message').innerHTML = loadMessage; this.$('user').disabled = disabled; this.$('password').disabled = disabled; this.$('notSignOut').disabled = disabled; this.$('enter').disabled = disabled; var display = disabled ? 'block' : 'none'; this.$('background').style.display = display; this.$('spinner').style.display = display; }, onCleanCacheClick: function() { setTimeout(function() { App.cleanCache(); }); }, onShowBranchOptionsClick: function() { var style = this.$("branchSelector").style; style.display = style.display == 'none' || !style.display ? 'inline' : 'none'; }, onShowDatasourceOptionsClick: function() { var style = this.$("datasourceSelector").style; style.display = style.display == 'none' || !style.display ? 'inline' : 'none'; if (!this.$("datasourceLogo").className) this.$("datasourceLogo").className = "on"; else this.$("datasourceLogo").className = ""; }, onChangeBranch: function() { if (this.$("branch").value == "master"||"test"||"dev") { this.$("branchButton").className = this.$("branch").value; this.$("branch").className = this.$("branch").value; } else { this.$("branchButton").className = null; this.$("branch").className = null; } this.$('user').focus(); }, /** * Changes the datasource, and optionally do the following * * @param {Boolean} hasUpdate * @param {Boolean} hasChangeCredentials */ onChangeDatasource: function(hasUpdate, hasChangeCredentials) { var myDatasource = this.$("datasource").value; if (myDatasource == "production"||"test"||"dev") { this.$("datasourceButton").className = myDatasource; this.$("datasource").className = myDatasource; } else { this.$("datasourceButton").className = null; this.$("datasource").className = null; }; if (!hasUpdate) { this.updateDatasource(myDatasource); this.updateODBC( Conf.odbcPath, Conf.dsName, myDatasource ); }; if (hasChangeCredentials) { var datasourcePath = this.getDatasourcePath(); var myUID = this.regRead(datasourcePath, 'UID'); var myPWD = this.regRead(datasourcePath, 'PWD'); if (myUID && myPWD) { this.$('user').value = myUID; this.$('password').value = myPWD; }; }; this.$('user').focus(); }, cleanCache: function() { if (this.fso.folderExists(this.moduleDir)) { var folder = this.fso.getFolder(this.moduleDir); var files = new Enumerator(folder.files); for (; !files.atEnd(); files.moveNext()) { var file = files.item(); if (/\.mdb$/.test(file.name) && file.name != 'config.mdb') try { file.Delete(); } catch (e) {} } } this.showMessage(_('Cache files have been deleted'), 'notice'); }, onKeyPress: function(event) { switch (event.keyCode) { case 13: // Enter this.onEnterPress(event); break; case 27: // Esc window.close(); break; } }, onEnterPress: function(event) { var target = event.target || event.srcElement; if (target && target.id == 'user' && this.$('password').value == '') { this.$('password').focus(); return; } this.onEnterClick(); }, onEnterClick: function() { this.disableUi(true, _('Loading')); setTimeout(function() { App.login(); }); }, login: function() { try { var user = this.$('user').value; if (!user) throw new Error(_('Enter a username')); var password = this.$('password').value; if (!password) throw new Error(_('Enter a password')); this.regWrite(Conf.odbcPath + Conf.dsName, 'UID', user); this.regWrite(Conf.odbcPath + Conf.dsName, 'PWD', password); // Check the cretentials and return the last version number var version = this.fetchVersion(); // Check if there is a new version, and if there is, download it if (version) { this.disableUi(true, _('Updating')); var remoteFile = version ? '.archive/'+ this.module +'/'+ version +'.7z' : this.module +'.7z?'+ new Date().getTime(); remoteFile = Conf.cdnURL +'/'+ remoteFile; var request = new ActiveXObject("MSXML2.XMLHTTP.6.0"); request.open('GET', remoteFile, true); request.onreadystatechange = function() { App.onRequestReady(request); }; request.send(); } else this.openMdb(); } catch (err) { this.catchError(err); } }, /** * Gets information about the version to download. * cmdle * @return {Number|Boolean} Version number, %false if cannot * fetch or %null if local is up-to-date */ fetchVersion: function() { var mysqlConn = new ActiveXObject('ADODB.Connection'); var datasource = this.getDatasource(); var user = this.$('user').value; var password = this.$('password').value; // FIXME: Can't login in dev-db if (datasource == 'dev') { datasource = Conf.defaultDatasource }; // Check credentials try { mysqlConn.open(this.getODBCString({ Driver: '{'+ Conf.odbcDriver +'}', SERVER: Conf.datasources[datasource].SERVER, DATABASE: Conf.dbName, UID: user, PWD: password, SSLCA: this.certFile, SSLMODE: Conf.datasources[datasource].SSLMODE, SSLCIPHER: 'AES256-SHA', ENABLE_CLEARTEXT_PLUGIN : 1 })); } catch (err) { var dbErrors = mysqlConn && mysqlConn.errors; if (dbErrors && dbErrors.count > 0) { var dbError = dbErrors.item(0); switch (dbError.NativeError) { case 1045: // Access denied clearPassword = true; err = new Error(_('Bad login')); err.name = 'BadLogin'; break; case 2003: // Can't connect err = new Error(_('Server can\'t be reached')); break; default: err = new Error(dbError.description); } dbErrors.clear(); } throw err; } mysqlConn.close(); // Request to obtain the token and lastest version of this module try { var request = new ActiveXObject("MSXML2.XMLHTTP.6.0"); urlLoginMethod = this.getRemoteURL() + '/api/Accounts/login' request.open('POST', urlLoginMethod, false); request.setRequestHeader('Content-Type', 'application/json') var params = JSON.stringify({ "user": user, "password": password }); request.onreadystatechange = function() { App.onRequestTokenReady(request); } request.send(params); } catch (err) { throw err; }; // Checks if it's already open if (this.fso.fileExists(this.lockFile)) try { this.fso.deleteFile(this.lockFile); return this.lastVersion; } catch (e) { throw new Error(_('Application it\'s already open')); } // Checks if MDB exists if (!this.fso.fileExists(this.mdbFile)) return this.lastVersion; // If it's abnormaly bigger, maybe is corrupted, so force download var file = this.fso.getFile(this.mdbFile); if (file.size > Conf.maxCorruptSize * 1024 * 1024) return this.lastVersion; // Obtains the local version number from the MDB file var localVersion = this.mdbGetValue( 'SELECT Version FROM tblVariables', 'Version', parseInt ); if (!localVersion) localVersion = false; // Determines if should download return !localVersion || this.lastVersion === false || localVersion != this.lastVersion ? this.lastVersion : null; }, mdbGetValue: function(query, field, parseFn) { var value; try { if (this.fso.fileExists(this.mdbFile)) { var mdbConn = new ActiveXObject('ADODB.Connection'); mdbConn.open(this.getODBCString({ 'Provider': 'Microsoft.Jet.OLEDB.4.0', 'Data Source': this.mdbFile })); try { var rs = new ActiveXObject('ADODB.Recordset'); rs.Open(query, mdbConn); value = rs.EOF ? null : parseFn(rs(field)); rs.close(); } catch (e) {} mdbConn.close(); } } catch (e) {} return value; }, getODBCString: function(options) { var optionsArray = []; for (var option in options) optionsArray.push(option +'='+ options[option]); return optionsArray.join(';'); }, onRequestReady: function(request) { if (request.readyState !== 4) return; try { if (request.status !== 200) throw new Error('HTTP: '+ request.statusText + ' ' + request.status); if (this.fso.fileExists(this.compressFile)) this.fso.deleteFile(this.compressFile); var stream = new ActiveXObject('ADODB.Stream'); stream.open(); stream.Type = 1; // adTypeBinary stream.write(request.responseBody); stream.Position = 0; stream.saveToFile(this.compressFile, 2); stream.close(); if (this.fso.fileExists(this.mdbFile)) this.fso.deleteFile(this.mdbFile); this.run('7za e "'+ this.compressFile +'" -o"'+ this.moduleDir +'"', true); this.fso.deleteFile(this.compressFile); } catch (e) { alert(_('Error while updating') +': '+ e.message); } try { if (!this.fso.fileExists(this.mdbFile)) throw new Error(_('MDB file not found')); this.openMdb(); } catch (e) { this.catchError(e); } }, onRequestTokenReady: function(request) { if (request.readyState !== 4) return; try { if (request.status == 401) // Unhauthorized throw new Error('Bad login in the remoteURL "' + this.getRemoteURL() + '":\nThe latest version could not be obtained'); else if (request.status !== 200 ) throw new Error('HTTP: '+ request.statusText + ' ' + request.status); this.response = JSON.parse(request.responseText) if (this.response) { var request = new ActiveXObject("MSXML2.ServerXMLHTTP"); var filter = '&filter=' + JSON.stringify({ "fields":["version"], "where":{ "app": this.module, "branchFk": this.$('branch').value }, }); var urlVersionMethod = this.getRemoteURL() + '/api/MdbVersions?access_token=' + this.response.token + filter request.open('GET', urlVersionMethod, false); request.onreadystatechange = function() { App.onRequestVersionReady(request); }; request.send(); } } catch (err) { throw err; } }, onRequestVersionReady: function(request) { if (request.readyState !== 4) return; try { if (request.status !== 200 ) throw new Error('HTTP: '+ request.statusText + ' ' + request.status); var response = JSON.parse(request.responseText); this.lastVersion = response[0].version } catch (err) { throw err; } }, openMdb: function() { var notSignOut = !!this.$('notSignOut').checked; this.regWrite(Conf.regPath, 'notSignOut', notSignOut); var programFiles = this.getEnv('ProgramFiles'); var accessBin = programFiles +'\\Microsoft Office\\OFFICE11\\MSACCESS.EXE'; if (!this.fso.fileExists(accessBin)) throw new Error(_('Microsoft Access 2003 is not installed')); this.shell.exec('"'+ accessBin +'" "'+ this.mdbFile +'"'); window.close(); }, catchError: function(err) { var clearPassword = err.name == 'BadLogin'; if (!this.$('notSignOut').checked || clearPassword) this.regWrite(Conf.odbcPath + Conf.dsName, 'PWD', ''); this.disableUi(false); this.showMessage(err.message, 'error'); this.resetForm(clearPassword); }, /** * Displays a non-intrusive message. * * @param {String} message Message to display * @param {String} className Message type */ showMessage: function(message, className) { setTimeout(function() { App.showMessageAsync(message, className); }); }, showMessageAsync: function(message, className) { if (this.messageTimeout) clearTimeout(this.messageTimeout); var messageDiv = this.$('message'); messageDiv.className = className; messageDiv.innerHTML = message; messageDiv.style.display = 'block'; this.messageTimeout = setTimeout(function() { App.hideMessage(); }, 10000); }, onBodyClick: function() { this.hideMessage(); }, /** * Hides the last displayed non-intrusive message. */ hideMessage: function() { if (this.messageTimeout) { this.$('message').style.display = 'none'; clearTimeout(this.messageTimeout); this.messageTimeout = null; } }, /** * Obtains a DOM element by it's identifier. * * @param {String} id The element id */ $: function(id) { return document.getElementById(id); }, run: function(command, wait) { if (!wait) wait = false; this.shell.run(command, 0, wait); }, getEnv: function(varName) { return this.shell.expandEnvironmentStrings('%'+ varName +'%'); }, regRead: function(path, key) { try { var value = this.shell.regRead(path +'\\'+ key); } catch (e) { var value = null; } return value; }, regWrite: function(path, key, value, type) { if (!type) switch (typeof (value)) { case 'boolean': type = 'REG_DWORD'; value = value ? 1 : 0; break; case 'number': type = 'REG_DWORD'; break; default: type = 'REG_SZ'; if (!value) value = ''; } this.shell.regWrite(path + '\\' + key, value.toString(), type); }, regWrites: function(path, values, type) { for(var key in values) this.regWrite(path, key, values[key], type); }, regDelete: function(path) { try { this.shell.regDelete(path); } catch (e) {} }, /** * Gets information about the branch config in access * * @return {string} Branch name, master if cannot */ getBranch: function() { var branch = this.mdbGetValue( 'SELECT branch FROM tblVariables', 'branch', String ); return branch || Conf.defaultBranch; }, /** * Get the current datasource used * * @return {string} Datasource name */ getDatasource: function() { var datasource = this.regRead( Conf.regPath, 'currentDatasource' ); return datasource || Conf.defaultDatasource; }, /** * Get the remote URL * * @return {string} Remote server */ getRemoteURL: function() { var remoteURL = this.regRead( Conf.regPath, 'remoteURL' ); return remoteURL || Conf.defaultRemoteURL; }, /** * Get the current datasource path * * @return {string} Datasource path */ getDatasourcePath: function() { var datasourcePath = Conf.regPath + '\\datasources\\' + this.getDatasource(); return datasourcePath; }, /** * Update the datasource * * @param {String} datasource The datasource */ updateDatasource: function(datasource) { this.regWrite(Conf.regPath, 'currentDatasource', datasource); }, /** * Create all datasources structure */ createDatasources: function() { var params = { 'UID': null, 'PWD': null } for (var datasource in Conf.datasources) { var myPath = Conf.regPath + '\\datasources\\' + datasource for (var stringValues in Conf.datasources[datasource]) { this.regWrite(myPath, stringValues, Conf.datasources[datasource][stringValues] ); } this.regWrites(myPath, params); }; }, }; App.init(); function _(string) { var translation = Locale[Conf.defaultLocale][string]; return translation ? translation : string; }