var Conf = { appName: 'Verdnatura', odbcPath: 'HKCU\\SOFTWARE\\ODBC\\ODBC.INI\\', regPath: 'HKCU\\SOFTWARE\\Verdnatura\\vn-access', defaultModule: 'vn', defaultLocale: 'es', defaultBranch: 'master', defaultDatasource: 'verdnatura', defaultApiSource: 'production', defaultRemoteURL: 'https://salix.verdnatura.es', defaultServer: 'db.verdnatura.es', dbName: 'vn2008', maxCorruptSize: 600, odbcDriver: 'MySQL ODBC 8.0 Unicode Driver', driverPath: '\\MySQL\\Connector ODBC 8.0\\myodbc8w.dll', version: 7, cdnURL: 'https://cdn.verdnatura.es/vn-access', identifier: '.vn', productionDatasource: 'production.vn' }; 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é", "Session has been closed": "Se han cerrado la sesión" } }; var App = { shell: new ActiveXObject('WScript.Shell'), fso: new ActiveXObject('scripting.filesystemobject'), init: function() { // Specify the size of window var width = 450; var height = 610; 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.certFile = this.appDir +'\\cacert.pem'; this.lockFile = this.moduleDir +'\\' + this.module +'.ldb'; this.mdbFile = this.moduleDir +'\\' + this.module +'.mdb'; this.hasDeveloperMode = this.regRead(Conf.regPath, 'hasDeveloperMode'); document.title = "vn-access v" + Conf.version + " (" + this.module + ")"; var mdbDsName = this.mdbGetValue( 'SELECT dsName FROM TblConfig', 'dsName', String ); var userDefaultDatasource = this.regRead(Conf.regPath, 'defaultDatasource'); this.dsName = mdbDsName || userDefaultDatasource || Conf.defaultDatasource; this.heartClicks = 0; // Change title document.title = document.title + ' - ' + this.module; // 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.defaultDatasource, Conf.odbcDriver, 'keep for backward compatibility' ); this.regWrite(Conf.regPath, 'remoteURL', Conf.defaultRemoteURL); this.regWrite(Conf.regPath, 'lastExecutedVersion', Conf.version); this.regWrite(Conf.regPath, 'selectedApiSource', Conf.defaultApiSource); this.regWrite(Conf.regPath + '\\apiSources', Conf.defaultApiSource, Conf.defaultRemoteURL); this.createODBC( Conf.odbcPath, Conf.productionDatasource, Conf.odbcDriver, Conf.productionDatasource ); this.regDelete(Conf.regPath +'\\configured'); this.regDelete(Conf.regPath + '\\remember'); } var notSignOut = this.regRead(Conf.regPath, 'notSignOut'); this.$('notSignOut').checked = notSignOut; if (!notSignOut) this.resetForm(true); var autoLogin = this.regRead(Conf.regPath, 'autoLogin'); this.$('autoLogin').checked = autoLogin; this.refreshLogout(); // Branch options try { this.request('GET', 'mdbBranches', null, function(err, res) { var selectBranch = document.querySelector('#branch'); var option = []; for (var x in res) { option[x] = document.createElement('option'); option[x].text = res[x].name option[x].value = res[x].name selectBranch.options.add(option[x]) } }); } catch (err) { this.catchError(err); } var branch = this.mdbGetValue( 'SELECT branch FROM tblVariables', 'branch', String ); if (!branch) { userDefaultBranch = this.regRead(Conf.regPath, 'defaultBranch'); branch = userDefaultBranch || Conf.defaultBranch; } this.$('branch').value = branch; this.onChangeBranch(); // Datasource options var selectDatarouce = document.querySelector('#datasource'); var option = []; var allOdbc = this.enumValues(Conf.odbcPath +'ODBC Data Sources\\'); for (var y in allOdbc) { var odbcName = allOdbc[y]; if (this.isDs(odbcName) || odbcName == 'verdnatura') { var odbcName = allOdbc[y]; option[y] = document.createElement('option'); option[y].text = this.getDatasource(odbcName); option[y].value = odbcName; selectDatarouce.options.add(option[y]) } } this.$('datasource').value = this.dsName; if (this.regRead(Conf.regPath, 'autoDisplayDevOptions') && this.hasDeveloperMode) App.openOptions() this.refreshDeveloperMode(); if (autoLogin) { this.onChangeDatasource(); this.onEnterClick(); } else { if (!notSignOut) this.resetForm(true); this.onChangeDatasource(); } }, isDs: function(dsName) { return /\.vn$/.test(dsName); }, getDatasource: function(dsName) { return this.isDs(dsName) ? dsName.substr(0, dsName.length - Conf.identifier.length) : dsName; }, resetForm: function(clearPassword) { if (clearPassword) { this.$('password').value = ''; this.regWrite(this.getOdbcPath(), 'PWD', ''); } this.$('password').focus(); this.$('password').select(); }, createODBC: function(path, dsName, driverName, description) { var params = { Driver: this.getEnv('ProgramFiles') + Conf.driverPath, DESCRIPTION: description, SERVER: Conf.defaultServer, DATABASE: Conf.dbName, SSLCA: this.certFile, SSLMODE: 'VERIFY_IDENTITY', SSLCIPHER: 'AES256-SHA', AUTO_RECONNECT: '1', NO_PROMPT: '1', NO_BIGINT: '1' }; this.regWrite(path + 'ODBC Data Sources', dsName, driverName); 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.$('autoLogin').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 = ''; }, onHeartClick: function() { this.heartClicks++ if (this.heartClicks >= 5) { var action = this.hasDeveloperMode ? 'disabled' : 'enabled' var isActive = !this.hasDeveloperMode ? true : false this.regWrite(Conf.regPath, 'hasDeveloperMode', isActive) this.hasDeveloperMode = isActive; this.showMessage(_('Developer mode ' + action), 'notice'); this.refreshDeveloperMode(); this.heartClicks = 0; } }, refreshLogout: function() { var style = this.$('logout').style; var password = this.regRead(this.getOdbcPath(), 'PWD'); if (password) style.display = 'inline'; else style.display = 'none'; }, refreshDeveloperMode: function() { var stylesName = ['branchOptions', 'datasourceOptions']; for (var n in stylesName) { var style = this.$(stylesName[n]).style; style.visibility = this.regRead(Conf.regPath, 'hasDeveloperMode') ? 'visible' : 'hidden'; } }, 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 */ onChangeDatasource: function() { this.dsName = this.$('datasource').value; var myDatasource = this.getDatasource(this.dsName); if (myDatasource == 'verdnatura') myDatasource = 'production'; if (myDatasource == 'production'||'test'||'development') { this.$('datasourceButton').className = myDatasource; this.$('datasource').className = myDatasource; } else { this.$('datasourceButton').className = null; this.$('datasource').className = null; }; var odbcPath = this.getOdbcPath(); var user = this.regRead(odbcPath, 'UID') if (user) this.$('user').value = this.regRead(odbcPath, 'UID') var password = this.regRead(odbcPath, 'PWD') if (password) this.$('password').value = password this.refreshLogout(); 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(this.getOdbcPath(), 'UID', user); this.regWrite(this.getOdbcPath(), 'PWD', password); var mysqlConn = new ActiveXObject('ADODB.Connection'); // Check credentials try { mysqlConn.open(this.dsName) } 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(); // Check the cretentials and return the last version number this.fetchVersion(); } catch (err) { this.catchError(err); } }, logout: function() { this.resetForm(true); this.refreshLogout(); this.showMessage(_('Session has been closed'), 'notice'); }, /** * 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() { // To obtain the lastest version of this module var params = { filter: { fields: ['version'], where: { app: this.module, branchFk: this.$('branch').value }, } }; this.request('GET', 'MdbVersions/findOne', params, function(err, res) { App.onVersionRequest(err, res); } ); }, onVersionRequest: function(err, res) { try { if (err) throw new Error ('Version could not be retrieved: '+ err.message +': '); var lastVersion; // Checks if it's already open if (this.fso.fileExists(this.lockFile)) try { this.fso.deleteFile(this.lockFile); lastVersion = res.version; } catch (e) { throw new Error(_('Application it\'s already open')); } // Checks if MDB exists if (!this.fso.fileExists(this.mdbFile)) lastVersion = res.version; // If it's abnormaly bigger, maybe is corrupted, so force download if (!lastVersion) { var file = this.fso.getFile(this.mdbFile); if (file.size > Conf.maxCorruptSize * 1024 * 1024) lastVersion = res.version; // 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 !localVersion || res.version === false || localVersion != res.version ? lastVersion = res.version : lastVersion = null; } // Check if there is a new version, and if there is, download it if (lastVersion) { this.disableUi(true, _('Updating')); var remoteFile = lastVersion ? '.archive/'+ this.module +'/'+ lastVersion +'.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 App.openMdb(); } catch (err) { this.catchError(err); } }, 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); } }, openMdb: function() { var autoLogin = this.$('autoLogin').checked; this.regWrite(Conf.regPath, 'autoLogin', autoLogin); if (autoLogin) var notSignOut = true; else var notSignOut = this.$('notSignOut').checked; this.regWrite(Conf.regPath, 'notSignOut', notSignOut); this.$('notSignOut').checked = true; 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')); // Open the mdb and pass it the argument this.shell.exec('"'+ accessBin +'" "'+ this.mdbFile +'" /cmd "'+ this.dsName +'"'); window.close(); }, catchError: function(err) { var clearPassword = err.name == 'BadLogin'; this.regWrite(Conf.regPath, 'autoLogin', 0) this.$('autoLogin').checked = false 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(); }, openOptions: function() { this.onShowBranchOptionsClick(); this.onShowDatasourceOptionsClick(); }, /** * 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) {} }, request: function (method, url, data, cb) { var remoteURL = this.regRead(Conf.regPath, 'remoteURL'); if (!remoteURL) remoteURL = Conf.defaultRemoteURL; var fullUrl = remoteURL +'/api/'+ url; var isGet = method == 'GET'; if (isGet) { var isFirst = true; for (var param in data) { fullUrl += isFirst ? '?' : '&'; isFirst = false; var value = data[param]; if (typeof value == 'object') value = JSON.stringify(value); fullUrl += param +'='+ encodeURIComponent(value); } } var req = new ActiveXObject('MSXML2.XMLHTTP.6.0'); req.open(method, fullUrl, false); req.onreadystatechange = function() { App.onRequestStateChange(req, cb) }; if (isGet) { req.setRequestHeader('Content-Type', 'application/json'); req.send(JSON.stringify(data)); } else req.send(); }, onRequestStateChange: function(req, cb) { if (req.readyState !== 4) return; var status = req.status; if (status >= 100 && status < 400) { var res = JSON.parse(req.responseText); cb(null, res); } else { err = new Error(req.statusText); err.name = 'HttpError'; err.status = status; err.req = req; if (status >= 400 && status < 600) alert(_('Bad request') +': '+ err); cb(err); } }, /** * Get the odbc path * * @return {string} Remote server */ getOdbcPath: function() { return Conf.odbcPath + this.dsName; }, enumValues: function(RegKey) { var RootKey = new Object() RootKey['HKCR'] = RootKey['HKEY_CLASSES_ROOT'] = 0x80000000; RootKey['HKCU'] = RootKey['HKEY_CURRENT_USER'] = 0x80000001; RootKey['HKLM'] = RootKey['HKEY_LOCAL_MACHINE'] = 0x80000002; RootKey['HKUS'] = RootKey['HKEY_USERS'] = 0x80000003; RootKey['HKCC'] = RootKey['HKEY_CURRENT_CONFIG'] = 0x80000005; var RootVal = RootKey[RegKey.substr(0, RegKey.indexOf('\\'))] if (RootVal != undefined) { Locator = new ActiveXObject('WbemScripting.SWbemLocator'); ServerConn = Locator.ConnectServer(null, 'root\\default'); Registry = ServerConn.Get('StdRegProv'); Method = Registry.Methods_.Item('enumValues'); p_In = Method.InParameters.SpawnInstance_(); p_In.hDefKey = RootVal; p_In.sSubKeyName = RegKey.substr(RegKey.indexOf('\\') + 1) p_Out = Registry.ExecMethod_(Method.Name, p_In); return p_Out.sNames.toArray(); } }, }; App.init(); function _(string) { var translation = Locale[Conf.defaultLocale][string]; return translation ? translation : string; }