'use strict'; module.exports.create = function (deps, conf) { var network = deps.PromiseA.promisifyAll(deps.recase.camelCopy(require('network'))); var loopback = require('./loopback').create(deps, conf); var dnsCtrl = require('./dns-ctrl').create(deps, conf); var loopbackDomain; function updateConf() { loopbackDomain = 'oauth3.org'; if (conf.ddns && conf.ddns.loopback) { if (conf.ddns.loopback.type === 'tunnel@oauth3.org' && conf.ddns.loopback.domain) { loopbackDomain = conf.ddns.loopback.domain; } else { console.error('invalid loopback configuration: bad type or missing domain'); } } } updateConf(); function iterateAllModules(action) { var promises = conf.ddns.modules.map(function (mod) { return action(mod, mod.domains); }); conf.domains.forEach(function (dom) { if (!dom.modules || !Array.isArray(dom.modules.ddns) || !dom.modules.ddns.length) { return null; } // For the time being all of our things should only be tried once (regardless if it succeeded) // TODO: revisit this behavior when we support multiple ways of setting records, and/or // if we want to allow later modules to run if early modules fail. promises.push(dom.modules.ddns.reduce(function (prom, mod) { if (prom) { return prom; } return action(mod, dom.names); }, null)); }); return deps.PromiseA.all(promises.filter(Boolean)); } var tunnelActive = false; async function connectTunnel() { var sessionCache = {}; var sessionOverride; if (conf.ddns.tunnel) { sessionOverride = await deps.storage.tokens.get(conf.ddns.tunnel.tokenId); } async function getSession(id) { if (sessionOverride) { return sessionOverride; } if (!sessionCache.hasOwnProperty(id)) { sessionCache[id] = await deps.storage.tokens.get(conf.ddns.tunnel.tokenId); } if (!sessionCache[id]) { throw new Error('no user token with ID "'+id+'"'); } return sessionCache[id]; } await iterateAllModules(function startTunnel(mod, domainsList) { if (mod.type !== 'dns@oauth3.org') { return null; } return getSession(mod.token_id).then(function (session) { return deps.tunnelClients.start(session, domainsList); }).catch(function (err) { console.log('error starting tunnel for', domainsList.join(', ')); console.log(err); }); }); tunnelActive = true; } async function disconnectTunnel() { deps.tunnelClients.disconnect(); tunnelActive = false; } var localAddr, gateway; async function checkNetworkEnv() { // Since we can't detect the OS level events when a user plugs in an ethernet cable to recheck // what network environment we are in we check our local network address and the gateway to // determine if we need to run the loopback check and router configuration again. var gw = await network.getGatewayIpAsync(); var addr = await network.getPrivateIpAsync(); if (localAddr === addr && gateway === gw) { return; } localAddr = addr; gateway = gw; var loopResult = await loopback(loopbackDomain); var notLooped = Object.keys(loopResult.ports).filter(function (port) { return !loopResult.ports[port]; }); // if (notLooped.length) { // // TODO: try to automatically configure router to forward ports to us. // } // If we are on a public address or all ports we are listening on are forwarded to us then // we don't need the tunnel and we can set the DNS records for all our domains to our public // address. Otherwise we need to use the tunnel to accept traffic. if (!notLooped.length) { if (tunnelActive) { await disconnectTunnel(); } } else { if (!tunnelActive) { await connectTunnel(); } } } var publicAddress; async function recheckPubAddr() { await checkNetworkEnv(); if (tunnelActive) { return; } var addr = await loopback.checkPublicAddr(loopbackDomain); if (publicAddress === addr) { return; } if (conf.debug) { console.log('previous public address',publicAddress, 'does not match current public address', addr); } publicAddress = addr; var sessionCache = {}; async function getSession(id) { if (!sessionCache.hasOwnProperty(id)) { sessionCache[id] = await deps.storage.tokens.get(conf.ddns.tunnel.tokenId); } if (!sessionCache[id]) { throw new Error('no user token with ID "'+id+'"'); } return sessionCache[id]; } await iterateAllModules(function setModuleDNS(mod, domainsList) { if (mod.type !== 'dns@oauth3.org' || mod.disabled) { return null; } return getSession(mod.token_id).then(function (session) { return dnsCtrl.setDeviceAddress(session, addr, domainsList); }).catch(function (err) { console.log('error setting DNS records for', domainsList.join(', ')); console.log(err); }); }); } recheckPubAddr(); setInterval(recheckPubAddr, 5*60*1000); return { loopbackServer: loopback.server , setDeviceAddress: dnsCtrl.setDeviceAddress , getDeviceAddresses: dnsCtrl.getDeviceAddresses , recheckPubAddr: recheckPubAddr , updateConf: updateConf }; };