/* jshint -W097 */ /* jshint strict: false */ /* jslint node: true */ 'use strict'; const express = require('express'); const fs = require('fs'); const path = require('path'); const utils = require(__dirname + '/lib/utils'); // Get common adapter utils const LE = require(utils.controllerDir + '/lib/letsencrypt.js'); const mime = require('mime-types'); let session;// = require('express-session'); let cookieParser;// = require('cookie-parser'); let bodyParser;// = require('body-parser'); let AdapterStore;// = require(__dirname + '/../../lib/session.js')(session); let passportSocketIo;// = require(__dirname + "/lib/passport.socketio.js"); let password;// = require(__dirname + '/../../lib/password.js'); let passport;// = require('passport'); let LocalStrategy;// = require('passport-local').Strategy; let flash;// = require('connect-flash'); // TODO report error to user let webServer = null; let store = null; let secret = 'Zgfr56gFe87jJOM'; // Will be generated by first start let socketUrl = ''; let cache = {}; // cached web files let ownSocket = false; let lang = 'en'; let extensions = {}; let bruteForce = {}; let socketIoFile = null; let webPreSettings = {}; let webByVersion = {}; let loginPage = null; let adapter = new utils.Adapter({ name: 'web', objectChange: (id, obj) => { if (obj && obj.common && obj.common.webExtension && obj.native && (extensions[id.substring('system.adapter.'.length)] || obj.native.webInstance === '*' || obj.native.webInstance === 'adapter.namespace' ) ) { adapter.setForeignState('system.adapter.' + adapter.namespace + '.alive', false, true, () => { process.exit(-100); }); return; } if (obj && obj.common && obj.common.webPreSettings) { updatePreSettings(obj); } // 'system.adapter.'length = 15 const _id = id.substring(15).replace(/\.\d+$/, ''); if (obj && obj.common && obj.common.webByVersion) { webByVersion[_id] = obj.common.version; } else if (webByVersion[_id]) { delete webByVersion[_id]; } if (!ownSocket && id === adapter.config.socketio) { if (obj && obj.common && obj.common.enabled && obj.native) { socketUrl = ':' + obj.native.port; } else { socketUrl = ''; } } if (webServer.io) { webServer.io.publishAll('objectChange', id, obj); } if (webServer.api && adapter.config.auth) { webServer.api.objectChange(id, obj); } if (id === 'system.config') { lang = obj && obj.common && obj.common.language ? obj.common.language : 'en'; } // inform extensions for (let e = 0; e < extensions.length; e++) { try { if (extensions[e].obj && extensions[e].obj.objectChange) { extensions[e].obj.objectChange(id, obj); } } catch (err) { adapter.log.error('Cannot call objectChange for "' + e + '": ' + err); } } }, stateChange: (id, state) => { if (webServer.io) webServer.io.publishAll('stateChange', id, state); }, unload: callback => { try { adapter.log.info('terminating http' + (webServer.settings.secure ? 's' : '') + ' server on port ' + webServer.settings.port); webServer.server.close(); adapter.log.info('terminated http' + (webServer.settings.secure ? 's' : '') + ' server on port ' + webServer.settings.port); callback(); } catch (e) { callback(); } }, ready: () => { // Generate secret for session manager adapter.getForeignObject('system.config', (err, obj) => { if (!err && obj) { if (!obj.native || !obj.native.secret) { obj.native = obj.native || {}; require('crypto').randomBytes(24, (ex, buf) => { secret = buf.toString('hex'); adapter.extendForeignObject('system.config', {native: {secret: secret}}); main(); }); } else { secret = obj.native.secret; main(); } } else { adapter.logger.error('Cannot find object system.config'); } }); // information about connected socket.io adapter if (adapter.config.socketio && adapter.config.socketio.match(/^system\.adapter\./)) { adapter.getForeignObject(adapter.config.socketio, (err, obj) => { if ( obj && obj.common && obj.common.enabled && obj.native) { socketUrl = ':' + obj.native.port; } }); // Listen for changes adapter.subscribeForeignObjects(adapter.config.socketio); } else { socketUrl = adapter.config.socketio; ownSocket = (socketUrl !== 'none'); } // Read language adapter.getForeignObject('system.config', (err, data) => { if (data && data.common) lang = data.common.language || 'en'; }); } }); function extractPreSetting(obj, attr) { const parts = attr.split('.'); if (parts.length === 1) { if ((obj && typeof obj === 'object') || (obj !== null && obj !== undefined)) { return obj[attr]; } else { return null; } } else { attr = parts.shift(); if (obj[attr] && typeof obj[attr] === 'object') { return extractPreSetting(obj[attr], parts.join('.')); } else { return null; } } } function updatePreSettings(obj) { if (!obj || !obj.common) return; if (obj.common.webPreSettings) { for (let attr in obj.common.webPreSettings) { if (!obj.common.webPreSettings.hasOwnProperty(attr)) continue; webPreSettings[obj._id] = webPreSettings[obj._id] || {}; const _attr = attr.replace(/[^\w0-9]/g, '_'); webPreSettings[obj._id][_attr] = extractPreSetting(obj, obj.common.webPreSettings[attr]); if (typeof webPreSettings[obj._id][_attr] === 'object') { webPreSettings[obj._id][_attr] = JSON.stringify(webPreSettings[obj._id][_attr]); } else { webPreSettings[obj._id][_attr] = webPreSettings[obj._id][_attr].replace(/"/g, '\\"'); } } } else if (webPreSettings[obj._id]) { delete webPreSettings[obj._id]; } } function getExtensionsAndSettings(callback) { adapter.objects.getObjectView('system', 'instance', null, (err, doc) => { if (err) { if (callback) callback (err, []); } else { if (doc.rows.length === 0) { if (callback) callback (null, []); } else { let res = []; for (let i = 0; i < doc.rows.length; i++) { const instance = doc.rows[i].value; if (instance && instance.common) { if (instance.common.enabled && instance.common.webExtension && (instance.native.webInstance === adapter.namespace || instance.native.webInstance === '*')) { res.push(doc.rows[i].value); } if (instance.common.webPreSettings) { updatePreSettings(instance); } if (instance.common.webByVersion) { // 'system.adapter.'length = 15 const _id = doc.rows[i].value._id.substring(15).replace(/\.\d+$/, ''); webByVersion[_id] = instance.common.version; } } } if (callback) callback (null, res); } } }); } function main() { getExtensionsAndSettings((err, ext) => { if (err) adapter.log.error('Cannot read extensions: ' + err); if (ext) { for (let e = 0; e < ext.length; e++) { if (ext[e] && ext[e].common) { const instance = ext[e]._id.substring('system.adapter.'.length); const name = instance.split('.')[0]; extensions[instance] = { path: name + '/' + ext[e].common.webExtension, config: ext[e] }; } } } if (adapter.config.secure) { // Load certificates adapter.getCertificates((err, certificates, leConfig) => { adapter.config.certificates = certificates; adapter.config.leConfig = leConfig; webServer = initWebServer(adapter.config); }); } else { webServer = initWebServer(adapter.config); } // monitor extensions and pro keys adapter.subscribeForeignObjects('system.adapter.*'); }); } function readDirs(dirs, cb, result) { result = result || []; if (!dirs || !dirs.length) { return cb && cb(result); } const dir = dirs.shift(); adapter.readDir(dir, '', (err, files) => { if (!err && files && files.length) { for (let f = 0; f < files.length; f++) { if (files[f].file.match(/\.html$/)) { result.push(dir + '/' + files[f].file); } } } setImmediate(readDirs, dirs, cb, result); }); } let indexHtml; function getLinkVar(_var, obj, attr, link) { if (attr === 'protocol') attr = 'secure'; if (_var === 'ip') { link = link.replace('%' + _var + '%', '$host$'); } else if (_var === 'instance') { const instance = obj._id.split('.').pop(); link = link.replace('%' + _var + '%', instance); } else { if (obj) { if (attr.match(/^native_/)) attr = attr.substring(7); let val = obj.native[attr]; if (_var === 'bind' && (!val || val === '0.0.0.0')) val = '$host$'; if (attr === 'secure') { link = link.replace('%' + _var + '%', val ? 'https' : 'http'); } else { if (link.indexOf('%' + _var + '%') === -1) { link = link.replace('%native_' + _var + '%', val); } else { link = link.replace('%' + _var + '%', val); } } } else { if (attr === 'secure') { link = link.replace('%' + _var + '%', 'http'); } else { if (link.indexOf('%' + _var + '%') === -1) { link = link.replace('%native_' + _var + '%', ''); } else { link = link.replace('%' + _var + '%', ''); } } } } return link; } function resolveLink(link, instanceObj, instancesMap) { const vars = link.match(/%(\w+)%/g); let _var; let v; let parts; let result; if (vars) { // first replace simple patterns for (v = vars.length - 1; v >= 0; v--) { _var = vars[v]; _var = _var.replace(/%/g, ''); parts = _var.split('_'); // like "port" if (_var.match(/^native_/)) { link = getLinkVar(_var, instanceObj, _var, link); vars.splice(v, 1); } else if (parts.length === 1) { link = getLinkVar(_var, instanceObj, parts[0], link); vars.splice(v, 1); } else // like "web.0_port" if (parts[0].match(/\.[0-9]+$/)) { link = getLinkVar(_var, instancesMap['system.adapter.' + parts[0]], parts[1], link); vars.splice(v, 1); } } let links = {}; let instances; const adptr = parts[0]; // process web_port for (v = 0; v < vars.length; v++) { _var = vars[v]; _var = _var.replace(/%/g, ''); if (_var.match(/^native_/)) _var = _var.substring(7); parts = _var.split('_'); if (!instances) { instances = []; // TODO ! for (let inst = 0; inst < 10; inst++) { if (that.main.objects['system.adapter.' + adptr + '.' + inst]) { instances.push(inst); } } } for (let i = 0; i < instances.length; i++) { links[adptr + '.' + i] = { instance: adptr + '.' + i, link: getLinkVar(_var, instancesMap['system.adapter.' + adptr + '.' + i], parts[1], links[adptr + '.' + i] ? links[adptr + '.' + i].link : link) }; } } if (instances) { result = []; let count = 0; let firtsLink = ''; for (let d in links) { if (links.hasOwnProperty(d)) { result[links[d].instance] = links[d].link; if (!firtsLink) firtsLink = links[d].link; count++; } } if (count < 2) { link = firtsLink; result = null; } } } return result || link; } function replaceInLink(link, instanceObj, instances) { if (typeof link === 'object') { const links = JSON.parse(JSON.stringify(link)); let first; for (let v in links) { if (links.hasOwnProperty(v)) { links[v] = resolveLink(links[v], instanceObj, instances); if (!first) first = links[v]; } } links.__first = first; return links; } else { return resolveLink(link, instanceObj, instances); } } function getListOfAllAdapters(callback) { try { // read all instances adapter.objects.getObjectView('system', 'instance', {}, (err, instances) => { adapter.objects.getObjectView('system', 'adapter', {}, (err, adapters) => { let list = []; let a; let mapInstance = {}; for (let r = 0; r < instances.rows.length; r++) { mapInstance[instances.rows[r].id] = instances.rows[r].value; } for (a = 0; a < adapters.rows.length; a++) { const obj = adapters.rows[a].value; let found = ''; if (instances && instances.rows) { found = ''; // find if any instance of this adapter is exists and started for (let i = 0; i < instances.rows.length; i++) { let id = instances.rows[i].id; let ids = id.split('.'); ids.pop(); id = ids.join('.'); if (id === obj._id && instances.rows[i].value.common && (instances.rows[i].value.common.enabled || instances.rows[i].value.common.onlyWWW)) { found = instances.rows[i].id; break; } } } if (found) { if (obj.common.welcomeScreen || obj.common.welcomeScreenPro) { if (obj.common.welcomeScreen) { if (obj.common.welcomeScreen instanceof Array) { for (let w = 0; w < obj.common.welcomeScreen.length; w++) { // temporary disabled if (obj.common.welcomeScreen[w].name === 'vis editor') { continue; } if (obj.common.welcomeScreen[w].localLink && typeof obj.common.welcomeScreen[w].localLink === 'boolean') { obj.common.welcomeScreen[w].localLink = obj.common.localLink; } if (obj.common.welcomeScreen[w].localLink) { obj.common.welcomeScreen[w].id = found; } list.push(obj.common.welcomeScreen[w]); } } else { if (obj.common.welcomeScreen.localLink && typeof obj.common.welcomeScreen.localLink === 'boolean') { obj.common.welcomeScreen.localLink = obj.common.localLink; } if (obj.common.welcomeScreen.localLink) { obj.common.welcomeScreen.id = found; } list.push(obj.common.welcomeScreen); } } if (obj.common.welcomeScreenPro) { if (obj.common.welcomeScreenPro instanceof Array) { for (let ww = 0; ww < obj.common.welcomeScreenPro.length; ww++) { let tile = Object.assign({}, obj.common.welcomeScreenPro[ww]); tile.pro = true; if (tile.localLink && typeof tile.localLink === 'boolean') { tile.localLink = obj.common.localLink; } if (tile.localLink) { tile.id = found; } list.push(tile); } } else { let tile_ = Object.assign({}, obj.common.welcomeScreenPro); tile_.pro = true; if (tile_.localLink && typeof tile_.localLink === 'boolean') { tile_.localLink = obj.common.localLink; } if (tile_.localLink) { tile_.id = found; } list.push(tile_); } } } } } indexHtml = /*indexHtml || */fs.readFileSync(__dirname + '/www/index.html').toString(); list.sort((a, b) => { if (a.order === undefined && b.order === undefined) { if (a.name.toLowerCase() > b.name.toLowerCase()) return 1; if (a.name.toLowerCase() < b.name.toLowerCase()) return -1; return 0; } else if (a.order === undefined) { return -1; } else if (b.order === undefined) { return 1; } else { if (a.order > b.order) return 1; if (a.order < b.order) return -1; if (a.name.toLowerCase() > b.name.toLowerCase()) return 1; if (a.name.toLowerCase() < b.name.toLowerCase()) return -1; return 0; } }); // calculate localLinks for (let t = 0; t < list.length; t++) { if (list[t].localLink) { list[t].localLink = resolveLink(list[t].localLink, mapInstance[list[t].id], mapInstance); } } let text = 'systemLang = "' + lang + '";\n'; text += 'list = ' + JSON.stringify(list, null, 2) + ';\n'; // if login text += 'let authEnabled = ' + adapter.config.auth + ';\n'; callback(null, indexHtml.replace('// -- PLACE THE LIST HERE --', text)); }); }); } catch (e) { callback(e); } } function getInfoJs(settings) { let result = [ 'var socketUrl = "' + socketUrl + '";', 'var socketSession = "' + '' + '";', 'window.sysLang = "' + lang + '";', 'window.socketForceWebSockets = ' + (settings.forceWebSockets ? 'true' : 'false') + ';' ]; for (const id in webPreSettings) { if (webPreSettings.hasOwnProperty(id) && webPreSettings[id]) { for (const attr in webPreSettings[id]) { if (webPreSettings[id].hasOwnProperty(attr)) { result.push(`window.${attr} = "${webPreSettings[id][attr]}";`); } } } } return result.join(' '); } function prepareLoginTemplate() { let def = ' font: 13px/20px \'Lucida Grande\', Tahoma, Verdana, sans-serif;\n' + ' color: #404040;\n' + ' background-color: #0ae;\n' + ' background-image: -webkit-gradient(linear, 0 0, 0 100%, color-stop(.5, rgba(255, 255, 255, .2)), color-stop(.5, transparent), to(transparent));\n' + ' background-image: -webkit-linear-gradient(rgba(255, 255, 255, .2) 50%, transparent 50%, transparent);\n' + ' background-image: -moz-linear-gradient(rgba(255, 255, 255, .2) 50%, transparent 50%, transparent);\n' + ' background-image: -ms-linear-gradient(rgba(255, 255, 255, .2) 50%, transparent 50%, transparent);\n' + ' background-image: -o-linear-gradient(rgba(255, 255, 255, .2) 50%, transparent 50%, transparent);\n' + ' background-image: linear-gradient(rgba(255, 255, 255, .2) 50%, transparent 50%, transparent);\n' + ' background-size: 50px 50px;\n' ; let template = fs.readFileSync(__dirname + '/www/login/index.html').toString('utf8'); if (adapter.config.loginBackgroundColor) { def = 'background-color: ' + adapter.config.loginBackgroundColor + ';\n' } if (adapter.config.loginBackgroundImage) { def += ' background-image: url(../' + adapter.namespace + '/login-bg.png);\n'; } return template.replace('background: black;', def); } function initAuth(server, settings) { session = require('express-session'); cookieParser = require('cookie-parser'); bodyParser = require('body-parser'); AdapterStore = require(utils.controllerDir + '/lib/session.js')(session, settings.ttl); passportSocketIo = require('passport.socketio'); password = require(utils.controllerDir + '/lib/password.js'); passport = require('passport'); LocalStrategy = require('passport-local').Strategy; flash = require('connect-flash'); // TODO report error to user store = new AdapterStore({adapter: adapter}); passport.use(new LocalStrategy( function (username, password, done) { if (bruteForce[username] && bruteForce[username].errors > 4) { let minutes = (new Date().getTime() - bruteForce[username].time); if (bruteForce[username].errors < 7) { if ((new Date().getTime() - bruteForce[username].time) < 60000) { minutes = 1; } else { minutes = 0; } } else if (bruteForce[username].errors < 10) { if ((new Date().getTime() - bruteForce[username].time) < 180000) { minutes = Math.ceil((180000 - minutes) / 60000); } else { minutes = 0; } } else if (bruteForce[username].errors < 15) { if ((new Date().getTime() - bruteForce[username].time) < 600000) { minutes = Math.ceil((600000 - minutes) / 60000); } else { minutes = 0; } } else if ((new Date().getTime() - bruteForce[username].time) < 3600000) { minutes = Math.ceil((3600000 - minutes) / 60000); } else { minutes = 0; } if (minutes) { return done('Too many errors. Try again in ' + minutes + ' ' + (minutes === 1 ? 'minute' : 'minutes') + '.', false); } } adapter.checkPassword(username, password, (res) => { if (!res) { bruteForce[username] = bruteForce[username] || {errors: 0}; bruteForce[username].time = new Date().getTime(); bruteForce[username].errors++; } else if (bruteForce[username]) { delete bruteForce[username]; } if (res) { return done(null, username); } else { return done(null, false); } }); } )); passport.serializeUser((user, done) => done(null, user)); passport.deserializeUser((user, done) => done(null, user)); server.app.use(cookieParser()); server.app.use(bodyParser.urlencoded({ extended: true })); server.app.use(bodyParser.json()); server.app.use(bodyParser.text()); server.app.use(session({ secret: secret, saveUninitialized: true, resave: true, cookie: {maxAge: settings.ttl * 1000}, store: store })); server.app.use(passport.initialize()); server.app.use(passport.session()); server.app.use(flash()); } //settings: { // "port": 8080, // "auth": false, // "secure": false, // "bind": "0.0.0.0", // "::" // "cache": false //} function initWebServer(settings) { let server = { app: null, server: null, io: null, settings: settings }; adapter.subscribeForeignObjects('system.config'); settings.ttl = parseInt(settings.ttl, 10) || 3600; if (!settings.whiteListEnabled && settings.whiteListSettings) delete settings.whiteListSettings; settings.defaultUser = settings.defaultUser || 'system.user.admin'; if (!settings.defaultUser.match(/^system\.user\./)) settings.defaultUser = 'system.user.' + settings.defaultUser; if (settings.port) { if (settings.secure) { if (!settings.certificates) { return null; } } server.app = express(); server.app.disable('x-powered-by'); if (settings.auth) { initAuth(server, settings); let autoLogonOrRedirectToLogin = (req, res, next, redirect) => { if (!settings.whiteListSettings) { if (/\.css(\?.*)?$/.test(req.originalUrl)) { return res.status(200).send(''); } else if (/\.js(\?.*)?$/.test(req.originalUrl)) { // return always valid js file for js, because if cache is active it leads to errors const parts = req.originalUrl.split('/'); parts.shift(); const ref = parts.join('/'); // if request for web/lib, ignore it, because no redirect information if (parts[0] === 'lib') return res.status(200).send(''); return res.status(200).send('document.location="/login/index.html?href=" + encodeURI(location.href.replace(location.origin, ""));'); } else { return res.redirect(redirect); } } let remoteIp = req.headers['x-forwarded-for'] || req.connection.remoteAddress; let whiteListIp = server.io.getWhiteListIpForAddress(remoteIp, settings.whiteListSettings); adapter.log.silly('whiteListIp ' + whiteListIp); if (!whiteListIp || settings.whiteListSettings[whiteListIp].user === 'auth') { if (/\.css(\?.*)?$/.test(req.originalUrl)) { return res.status(200).send(''); } else if (/\.js(\?.*)?$/.test(req.originalUrl)) { // return always valid js file for js, because if cache is active it leads to errors let parts = req.originalUrl.split('/'); parts.shift(); const ref = parts.join('/'); if (parts[0] === 'lib') return res.status(200).send(''); return res.status(200).send('document.location="/login/index.html?href=" + encodeURI(location.href.replace(location.origin, ""));'); } else { return res.redirect(redirect); } } req.logIn(settings.whiteListSettings[whiteListIp].user, err => { return next(err); }); }; server.app.post('/login', (req, res, next) => { let redirect = '/'; let parts; if (req.body.origin) { parts = req.body.origin.split('='); if (parts[1]) redirect = decodeURIComponent(parts[1]); } if (req.body && req.body.username && settings.addUserName && redirect.indexOf('?') === -1) { parts = redirect.split('#'); parts[0] += '?' + req.body.username; redirect = parts.join('#'); } let authenticate = passport.authenticate('local', { successRedirect: redirect, failureRedirect: '/login/index.html' + req.body.origin + (req.body.origin ? '&error' : '?error'), failureFlash: 'Invalid username or password.' })(req, res, next); }); server.app.get('/logout', (req, res) => { req.logout(); res.redirect('/login/index.html'); }); // route middleware to make sure a user is logged in server.app.use((req, res, next) => { // if cache.manifest got back not 200 it makes an error if (req.isAuthenticated() || /web\.\d+\/login-bg\.png(\?.*)?$/.test(req.originalUrl) || /cache\.manifest(\?.*)?$/.test(req.originalUrl) || /^\/login\//.test(req.originalUrl) || /\.ico(\?.*)?$/.test(req.originalUrl) ) { return next(); } autoLogonOrRedirectToLogin(req, res, next, '/login/index.html?href=' + encodeURIComponent(req.originalUrl)); }); } else { server.app.get('/login', (req, res) => { res.redirect('/'); }); server.app.get('/logout', (req, res) => { res.redirect('/'); }); if (settings.whiteListEnabled) { initAuth(server, settings); server.app.use((req, res, next) => { let remoteIp = req.headers['x-forwarded-for'] || req.connection.remoteAddress; let whiteListIp = server.io.getWhiteListIpForAddress(remoteIp, settings.whiteListSettings); adapter.log.silly('whiteListIp ' + whiteListIp); if (whiteListIp) { req.logIn(settings.whiteListSettings[whiteListIp].user, err => { return next(err); }); } else { req.logIn(settings.defaultUser, err => { return next(err); }); } }); } } // Init read from states server.app.get('/state/*', (req, res) => { try { const fileName = req.url.split('/', 3)[2].split('?', 2); adapter.getForeignObject(fileName[0], (err, obj) => { let contentType = 'text/plain'; if (obj && obj.common.type === 'file') { contentType = mime.lookup(fileName[0]); } adapter.getBinaryState(fileName[0], {user: req.user ? 'system.user.' + req.user : settings.defaultUser}, (err, obj) => { if (!err && obj !== null && obj !== undefined) { if (obj && typeof obj === 'object' && obj.val !== undefined && obj.ack !== undefined) { res.set('Content-Type', 'application/json'); } else { res.set('Content-Type', contentType || 'text/plain'); } res.status(200).send(obj); } else { res.status(404).send('404 Not found. File ' + fileName[0] + ' not found'); } }); }); } catch (e) { res.status(500).send('500. Error' + e); } }); server.app.get('*/_socket/info.js', (req, res) => { res.set('Content-Type', 'application/javascript'); res.status(200).send(getInfoJs(settings)); }); // Enable CORS if (settings.socketio) { server.app.use((req, res, next) => { res.header('Access-Control-Allow-Origin', '*'); res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE,OPTIONS'); res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization, Content-Length, X-Requested-With, *'); // intercept OPTIONS method if ('OPTIONS' === req.method) { res.status(200).send(200); } else { next(); } }); } let appOptions = {}; if (settings.cache) appOptions.maxAge = 30758400000; server.server = LE.createServer(server.app, settings, settings.certificates, settings.leConfig, adapter.log); server.server.__server = server; } else { adapter.log.error('port missing'); process.exit(1); } if (server.server) { settings.port = parseInt(settings.port, 10); adapter.getPort(settings.port, port => { port = parseInt(port, 10); if (port !== settings.port && !settings.findNextPort) { adapter.log.error('port ' + settings.port + ' already in use'); process.exit(1); } server.server.listen(port, (!settings.bind || settings.bind === '0.0.0.0') ? undefined : settings.bind || undefined); adapter.log.info('http' + (settings.secure ? 's' : '') + ' server listening on port ' + port); }); } // activate extensions for (let e in extensions) { if (!extensions.hasOwnProperty(e)) continue; try { // for debug purposes try to load file in current directory "/lib/file.js" (elsewise node.js cannot debug it) let parts = extensions[e].path.split('/'); parts.shift(); let extAPI; if (fs.existsSync(__dirname + '/' + parts.join('/'))) { extAPI = require(__dirname + '/' + parts.join('/')); } else { extAPI = require(utils.appName + '.' + extensions[e].path); } extensions[e].obj = new extAPI(server.server, {secure: settings.secure, port: settings.port}, adapter, extensions[e].config, server.app); adapter.log.info('Connect extension "' + extensions[e].path + '"'); } catch (err) { adapter.log.error('Cannot start extension "' + e + '": ' + err); } } // Activate integrated simple API if (settings.simpleapi) { let SimpleAPI = require(utils.appName + '.simple-api/lib/simpleapi.js'); server.api = new SimpleAPI(server.server, {secure: settings.secure, port: settings.port}, adapter); } // Activate integrated socket if (ownSocket) { let IOSocket = require(utils.appName + '.socketio/lib/socket.js'); let socketSettings = JSON.parse(JSON.stringify(settings)); // Authentication checked by server itself socketSettings.auth = settings.auth; socketSettings.secret = secret; socketSettings.store = store; socketSettings.ttl = settings.ttl || 3600; socketSettings.forceWebSockets = settings.forceWebSockets || false; server.io = new IOSocket(server.server, socketSettings, adapter); } if (server.app) { // deliver web files from objectDB server.app.use('/', (req, res) => { let url = decodeURI(req.url); // remove all ../ // important: Linux does not normalize "\" but fs.readFile accepts it as '/' url = path.normalize(url.replace(/\\/g, '/')).replace(/\\/g, '/'); // remove '////' at start and let only one if (url[0] === '/' && url[1] === '/') { let i = 2; while (url[i] === '/') i++; url = url.substring(i - 1); } if ((url[0] === '.' && url[1] === '.') || (url[0] === '/' && url[1] === '.' && url[2] === '.')) { res.status(404).send('Not found'); return; } if (server.api && server.api.checkRequest(url)) { server.api.restApi(req, res); return; } if (url === '/' || url === '/index.html') { getListOfAllAdapters((err, data) => { if (err) { res.status(500).send('500. Error' + e); } else { res .set('Content-Type', 'text/html') .status(200) .send(data); } }); return; } // add index.html url = url.replace(/\/($|\?|#)/, '/index.html$1'); if (url.match(/^\/adapter\//)) { // add .admin to adapter name url = url.replace(/^\/adapter\/([a-zA-Z0-9-_]+)\//, '/$1.admin/'); } if (url.match(/^\/lib\//)) { url = '/' + adapter.name + url; } if (url.match(/^\/admin\//)) { url = '/' + adapter.name + url; } url = url.split('/'); // Skip first / url.shift(); // Get ID let id = url.shift(); let versionPrefix = url[0]; url = url.join('/'); let pos = url.indexOf('?'); let noFileCache; if (pos !== -1) { url = url.substring(0, pos); // disable file cache if request like /vis/files/picture.png?noCache noFileCache = true; } // get adapter name if (webByVersion[id]) { if (!versionPrefix.match(/^\d+\.\d+.\d+$/)) { // redirect to version res.set('location', '/' + id + '/' + webByVersion[id] + '/' + url); res.status(301).send(); return; } } if (settings.cache && cache[id + '/' + url] && !noFileCache) { res.contentType(cache[id + '/' + url].mimeType); res.status(200).send(cache[id + '/' + url].buffer); } else { if (id === 'login' && url === 'index.html') { loginPage = loginPage || prepareLoginTemplate(); let buffer = loginPage; if (buffer === null || buffer === undefined) { res.contentType('text/html'); res.status(200).send('File ' + url + ' not found', 404); } else { // Store file in cache if (settings.cache) { cache[id + '/' + url] = {buffer: buffer.toString(), mimeType: 'text/html'}; } res.contentType('text/html'); res.status(200).send(buffer.toString()); } } else { // special solution for socket.io if (socketIoFile !== false && url.match(/\/socket\.io\.js(\?.*)?$/)) { if (socketIoFile) { res.contentType('text/javascript'); res.status(200).send(socketIoFile); return } else { try { const dir = require.resolve('socket.io-client'); const fileDir = path.join(path.dirname(dir), '../dist/'); if (fs.existsSync(fileDir + 'socket.io.min.js')) { socketIoFile = fs.readFileSync(fileDir + 'socket.io.min.js'); } else { socketIoFile = fs.readFileSync(fileDir + 'socket.io.js'); } } catch (e) { socketIoFile = false; } if (socketIoFile) { res.contentType('text/javascript'); res.status(200).send(socketIoFile); return } } } adapter.readFile(id, webByVersion[id] ? url.substring(versionPrefix.length + 1) : url , {user: req.user ? 'system.user.' + req.user : settings.defaultUser, noFileCache: noFileCache}, (err, buffer, mimeType) => { if (buffer === null || buffer === undefined || err) { res.contentType('text/html'); res.status(404).send('File ' + url + ' not found: ' + err); } else { mimeType = mimeType || mime.lookup(url) || 'text/javascript'; // Store file in cache if (settings.cache) { cache[id + '/' + url] = { buffer: buffer, mimeType: mimeType }; } res.contentType(mimeType); res.status(200).send(buffer); } }); } } }); } if (server.server) { return server; } else { return null; } }