diff --git a/bin/telebit-remote.js b/bin/telebit-remote.js index 14a7b51..d97c0d2 100755 --- a/bin/telebit-remote.js +++ b/bin/telebit-remote.js @@ -73,7 +73,6 @@ if (!confpath || /^--/.test(confpath)) { } function askForConfig(state, mainCb) { - var fs = require('fs'); var ttyname = '/dev/tty'; var stdin = useTty ? fs.createReadStream(ttyname, { fd: fs.openSync(ttyname, fs.constants.O_RDONLY | fs.constants.O_NOCTTY) @@ -318,11 +317,20 @@ var utils = { request: function request(opts, fn) { if (!opts) { opts = {}; } var service = opts.service || 'config'; - var req = http.request({ - socketPath: state._ipc.path - , method: opts.method || 'GET' + + var reqOpts = { + method: opts.method || 'GET' , path: '/rpc/' + service - }, function (resp) { + }; + var portFile = path.join(path.dirname(state._ipc.path), 'telebit.port'); + if (fs.existsSync(portFile)) { + reqOpts.host = 'localhost'; + reqOpts.port = parseInt(fs.readFileSync(portFile, 'utf8').trim(), 10); + } else { + reqOpts.socketPath = state._ipc.path; + } + + var req = http.request(reqOpts, function (resp) { var body = ''; function finish() { @@ -383,12 +391,20 @@ var utils = { req.end(); } , putConfig: function putConfig(service, args, fn) { - var req = http.request({ - socketPath: state._ipc.path - , method: 'POST' - , path: '/rpc/' + service + '?_body=' + encodeURIComponent(JSON.stringify(args)) - }, function (resp) { + var reqOpts = { + method: 'POST' + , path: '/rpc/' + service + '?_body=' + encodeURIComponent(JSON.stringify(args)) + }; + var portFile = path.join(path.dirname(state._ipc.path), 'telebit.port'); + if (fs.existsSync(portFile)) { + reqOpts.host = 'localhost'; + reqOpts.port = parseInt(fs.readFileSync(portFile, 'utf8').trim(), 10); + } else { + reqOpts.socketPath = state._ipc.path; + } + + var req = http.request(reqOpts, function (resp) { function finish() { if ('function' === typeof fn) { fn(null, resp); @@ -434,6 +450,9 @@ var utils = { } else if ('ssh' === body.module) { //console.info('> Forwarding ' + state.config.relay + ' -p ' + JSON.stringify(body) + ' => localhost:' + body.local); console.info('> Forwarding ssh+https (openssl proxy) => localhost:' + body.local); + } else if ('status' === body.module) { + console.info('http://localhost:' + reqOpts.port); + console.info(JSON.stringify(body, null, 2)); } else { console.info(JSON.stringify(body, null, 2)); } diff --git a/bin/telebitd.js b/bin/telebitd.js index d70f4df..722cbd8 100755 --- a/bin/telebitd.js +++ b/bin/telebitd.js @@ -326,329 +326,339 @@ controllers.ssh = function (req, res, opts) { state.config.sshAuto = sshAuto; sshSuccess(); }; -function serveControlsHelper() { - controlServer = http.createServer(function (req, res) { - var opts = url.parse(req.url, true); - if (opts.query._body) { - try { - opts.body = JSON.parse(decodeURIComponent(opts.query._body, true)); - } catch(e) { + +var serveStatic = require('serve-static')(path.join(__dirname, '../lib/admin/')); +function handleRemoteClient(req, res) { + if (/^\/(rpc|api)\//.test(req.url)) { + return handleApi(req, res); + } + serveStatic(req, res, require('finalhandler')(req, res)); +} +function handleApi(req, res) { + var opts = url.parse(req.url, true); + if (opts.query._body) { + try { + opts.body = JSON.parse(decodeURIComponent(opts.query._body, true)); + } catch(e) { + res.statusCode = 500; + res.end('{"error":{"message":"?_body={{bad_format}}"}}'); + return; + } + } + + function listSuccess() { + var dumpy = { + servernames: state.servernames + , ports: state.ports + , ssh: state.config.sshAuto || 'disabled' + , code: 'CONFIG' + }; + if (state.otp) { + dumpy.device_pair_code = state.otp; + } + + if (state._can_pair && state.config.email && !state.token) { + dumpy.code = "AWAIT_AUTH"; + dumpy.message = "Please run 'telebit init' to authenticate."; + } + + res.end(JSON.stringify(dumpy)); + } + + function getConfigOnly() { + var resp = JSON.parse(JSON.stringify(state.config)); + resp.version = pkg.version; + res.setHeader('Content-Type', 'application/json'); + res.end(JSON.stringify(resp)); + } + + // + // without proper config + // + function saveAndReport() { + console.log('[DEBUG] saveAndReport config write', confpath); + console.log(YAML.safeDump(snakeCopy(state.config))); + fs.writeFile(confpath, YAML.safeDump(snakeCopy(state.config)), function (err) { + if (err) { res.statusCode = 500; - res.end('{"error":{"message":"?_body={{bad_format}}"}}'); + res.setHeader('Content-Type', 'application/json'); + res.end('{"error":{"message":"Could not save config file after init: ' + err.message.replace(/"/g, "'") + + '.\nPerhaps check that the file exists and your user has permissions to write it?"}}'); return; } + + listSuccess(); + }); + } + + function initOrConfig() { + var conf = {}; + if (!opts.body) { + res.statusCode = 422; + res.end('{"error":{"message":"module \'init\' needs more arguments"}}'); + return; + } + // relay, email, agree_tos, servernames, ports + // + opts.body.forEach(function (opt) { + var parts = opt.split(/:/); + if ('true' === parts[1]) { + parts[1] = true; + } else if ('false' === parts[1]) { + parts[1] = false; + } else if ('null' === parts[1]) { + parts[1] = null; + } else if ('undefined' === parts[1]) { + parts[1] = undefined; + } + conf[parts[0]] = parts[1]; + }); + + // TODO camelCase query + state.config.email = conf.email || state.config.email || ''; + if ('undefined' !== typeof conf.agreeTos + || 'undefined' !== typeof conf.agreeTos ) { + state.config.agreeTos = conf.agreeTos || conf.agree_tos; + } + state.otp = conf._otp; // this should only be done on the client side + state.config.relay = conf.relay || state.config.relay || ''; + console.log(); + console.log('conf.token', typeof conf.token, conf.token); + console.log('state.config.token', typeof state.config.token, state.config.token); + state.config.token = conf.token || state.config.token || null; + state.config.secret = conf.secret || state.config.secret || null; + state.pretoken = conf.pretoken || state.config.pretoken || null; + if (state.secret) { + console.log('state.secret'); + state.token = common.signToken(state); + } + if (!state.token) { + console.log('!state.token'); + state.token = conf._token; + } + console.log(); + console.log('JSON.stringify(conf)'); + console.log(JSON.stringify(conf)); + console.log(); + console.log('JSON.stringify(state)'); + console.log(JSON.stringify(state)); + console.log(); + if ('undefined' !== typeof conf.newsletter) { + state.config.newsletter = conf.newsletter; + } + if ('undefined' !== typeof conf.communityMember + || 'undefined' !== typeof conf.community_member) { + state.config.communityMember = conf.communityMember || conf.community_member; + } + if ('undefined' !== typeof conf.telemetry) { + state.config.telemetry = conf.telemetry; + } + if (conf._servernames) { + (conf._servernames||'').split(/,/g).forEach(function (key) { + if (!state.config.servernames[key]) { + state.config.servernames[key] = { sub: undefined }; + } + }); + } + if (conf._ports) { + (conf._ports||'').split(/,/g).forEach(function (key) { + if (!state.config.ports[key]) { + state.config.ports[key] = {}; + } + }); } - function listSuccess() { - var dumpy = { - servernames: state.servernames - , ports: state.ports - , ssh: state.config.sshAuto || 'disabled' - , code: 'CONFIG' - }; - if (state.otp) { - dumpy.device_pair_code = state.otp; - } + if (!state.config.relay || !state.config.email || !state.config.agreeTos) { + console.warn('missing config'); + res.statusCode = 400; - if (state._can_pair && state.config.email && !state.token) { - dumpy.code = "AWAIT_AUTH"; - dumpy.message = "Please run 'telebit init' to authenticate."; - } - - res.end(JSON.stringify(dumpy)); - } - - function getConfigOnly() { - var resp = JSON.parse(JSON.stringify(state.config)); - resp.version = pkg.version; res.setHeader('Content-Type', 'application/json'); - res.end(JSON.stringify(resp)); + res.end(JSON.stringify({ + error: { + code: "E_INIT" + , message: "Missing important config file params" + , _params: JSON.stringify(conf) + , _config: JSON.stringify(state.config) + , _body: JSON.stringify(opts.body) + } + })); + return; } - // - // without proper config - // - function saveAndReport() { - console.log('[DEBUG] saveAndReport config write', confpath); - console.log(YAML.safeDump(snakeCopy(state.config))); - fs.writeFile(confpath, YAML.safeDump(snakeCopy(state.config)), function (err) { - if (err) { - res.statusCode = 500; - res.setHeader('Content-Type', 'application/json'); - res.end('{"error":{"message":"Could not save config file after init: ' + err.message.replace(/"/g, "'") - + '.\nPerhaps check that the file exists and your user has permissions to write it?"}}'); - return; - } + // init also means enable + delete state.config.disable; + safeStartTelebitRemote(true).then(saveAndReport).catch(handleError); + } - listSuccess(); - }); + function restart() { + console.info("[telebitd.js] server closing..."); + state.keepAlive.state = false; + if (myRemote) { + myRemote.end(); + myRemote.on('end', respondAndClose); + // failsafe + setTimeout(function () { + console.info("[telebitd.js] closing too slowly, force quit"); + respondAndClose(); + }, 5 * 1000); + } else { + respondAndClose(); } - function initOrConfig() { - var conf = {}; - if (!opts.body) { - res.statusCode = 422; - res.end('{"error":{"message":"module \'init\' needs more arguments"}}'); - return; - } - // relay, email, agree_tos, servernames, ports - // - opts.body.forEach(function (opt) { - var parts = opt.split(/:/); - if ('true' === parts[1]) { - parts[1] = true; - } else if ('false' === parts[1]) { - parts[1] = false; - } else if ('null' === parts[1]) { - parts[1] = null; - } else if ('undefined' === parts[1]) { - parts[1] = undefined; - } - conf[parts[0]] = parts[1]; + function respondAndClose() { + res.setHeader('Content-Type', 'application/json'); + res.end(JSON.stringify({ success: true })); + controlServer.close(function () { + console.info("[telebitd.js] server closed"); + setTimeout(function () { + // system daemon will restart the process + process.exit(22); // use non-success exit code + }, 100); }); + } + } - // TODO camelCase query - state.config.email = conf.email || state.config.email || ''; - if ('undefined' !== typeof conf.agreeTos - || 'undefined' !== typeof conf.agreeTos ) { - state.config.agreeTos = conf.agreeTos || conf.agree_tos; - } - state.otp = conf._otp; // this should only be done on the client side - state.config.relay = conf.relay || state.config.relay || ''; - console.log(); - console.log('conf.token', typeof conf.token, conf.token); - console.log('state.config.token', typeof state.config.token, state.config.token); - state.config.token = conf.token || state.config.token || null; - state.config.secret = conf.secret || state.config.secret || null; - state.pretoken = conf.pretoken || state.config.pretoken || null; - if (state.secret) { - console.log('state.secret'); - state.token = common.signToken(state); - } - if (!state.token) { - console.log('!state.token'); - state.token = conf._token; - } - console.log(); - console.log('JSON.stringify(conf)'); - console.log(JSON.stringify(conf)); - console.log(); - console.log('JSON.stringify(state)'); - console.log(JSON.stringify(state)); - console.log(); - if ('undefined' !== typeof conf.newsletter) { - state.config.newsletter = conf.newsletter; - } - if ('undefined' !== typeof conf.communityMember - || 'undefined' !== typeof conf.community_member) { - state.config.communityMember = conf.communityMember || conf.community_member; - } - if ('undefined' !== typeof conf.telemetry) { - state.config.telemetry = conf.telemetry; - } - if (conf._servernames) { - (conf._servernames||'').split(/,/g).forEach(function (key) { - if (!state.config.servernames[key]) { - state.config.servernames[key] = { sub: undefined }; - } - }); - } - if (conf._ports) { - (conf._ports||'').split(/,/g).forEach(function (key) { - if (!state.config.ports[key]) { - state.config.ports[key] = {}; - } - }); - } - - if (!state.config.relay || !state.config.email || !state.config.agreeTos) { - console.warn('missing config'); - res.statusCode = 400; + function invalidConfig() { + res.statusCode = 400; + res.setHeader('Content-Type', 'application/json'); + res.end(JSON.stringify({ + error: { code: "E_CONFIG", message: "Invalid config file. Please run 'telebit init'" } + })); + } + function saveAndCommit() { + state.config.servernames = state.servernames; + state.config.ports = state.ports; + fs.writeFile(confpath, YAML.safeDump(snakeCopy(state.config)), function (err) { + if (err) { + res.statusCode = 500; res.setHeader('Content-Type', 'application/json'); res.end(JSON.stringify({ - error: { - code: "E_INIT" - , message: "Missing important config file params" - , _params: JSON.stringify(conf) - , _config: JSON.stringify(state.config) - , _body: JSON.stringify(opts.body) - } + "error":{"message":"Could not save config file. Perhaps you're not running as root?"} })); return; } - - // init also means enable - delete state.config.disable; - safeStartTelebitRemote(true).then(saveAndReport).catch(handleError); - } - - function restart() { - console.info("[telebitd.js] server closing..."); - state.keepAlive.state = false; - if (myRemote) { - myRemote.end(); - myRemote.on('end', respondAndClose); - // failsafe - setTimeout(function () { - console.info("[telebitd.js] closing too slowly, force quit"); - respondAndClose(); - }, 5 * 1000); - } else { - respondAndClose(); - } - - function respondAndClose() { - res.setHeader('Content-Type', 'application/json'); - res.end(JSON.stringify({ success: true })); - controlServer.close(function () { - console.info("[telebitd.js] server closed"); - setTimeout(function () { - // system daemon will restart the process - process.exit(22); // use non-success exit code - }, 100); - }); - } - } - - function invalidConfig() { - res.statusCode = 400; - res.setHeader('Content-Type', 'application/json'); - res.end(JSON.stringify({ - error: { code: "E_CONFIG", message: "Invalid config file. Please run 'telebit init'" } - })); - } - - function saveAndCommit() { - state.config.servernames = state.servernames; - state.config.ports = state.ports; - fs.writeFile(confpath, YAML.safeDump(snakeCopy(state.config)), function (err) { - if (err) { - res.statusCode = 500; - res.setHeader('Content-Type', 'application/json'); - res.end(JSON.stringify({ - "error":{"message":"Could not save config file. Perhaps you're not running as root?"} - })); - return; - } - listSuccess(); - }); - } - - function handleError(err) { - res.statusCode = 500; - res.setHeader('Content-Type', 'application/json'); - res.end(JSON.stringify({ - error: { message: err.message, code: err.code } - })); - } - - function enable() { - delete state.config.disable;// = undefined; - state.keepAlive.state = true; - - fs.writeFile(confpath, YAML.safeDump(snakeCopy(state.config)), function (err) { - if (err) { - err.message = "Could not save config file. Perhaps you're user doesn't have permission?"; - handleError(err); - return; - } - // TODO XXX myRemote.active - if (myRemote) { - listSuccess(); - return; - } - safeStartTelebitRemote(true).then(listSuccess).catch(handleError); - }); - } - - function disable() { - state.config.disable = true; - state.keepAlive.state = false; - - if (myRemote) { myRemote.end(); myRemote = null; } - fs.writeFile(confpath, YAML.safeDump(snakeCopy(state.config)), function (err) { - res.setHeader('Content-Type', 'application/json'); - if (err) { - err.message = "Could not save config file. Perhaps you're user doesn't have permission?"; - handleError(err); - return; - } - res.end('{"success":true}'); - }); - } - - function getStatus() { - res.setHeader('Content-Type', 'application/json'); - res.end(JSON.stringify( - { status: (state.config.disable ? 'disabled' : 'enabled') - , ready: ((state.config.relay && (state.config.token || state.config.agreeTos)) ? true : false) - , active: !!myRemote - , connected: 'maybe (todo)' - , version: pkg.version - , servernames: state.servernames - } - )); - } - - if (/\b(config)\b/.test(opts.pathname) && /get/i.test(req.method)) { - getConfigOnly(); - return; - } - if (/\b(init|config)\b/.test(opts.pathname)) { - initOrConfig(); - return; - } - if (/restart/.test(opts.pathname)) { - restart(); - return; - } - // - // Check for proper config - // - if (!state.config.relay || !state.config.email || !state.config.agreeTos) { - invalidConfig(); - return; - } - // - // With proper config - // - if (/http/.test(opts.pathname)) { - controllers.http(req, res, opts); - return; - } - if (/tcp/.test(opts.pathname)) { - controllers.tcp(req, res, opts); - return; - } - if (/save|commit/.test(opts.pathname)) { - saveAndCommit(); - return; - } - if (/ssh/.test(opts.pathname)) { - controllers.ssh(req, res, opts); - return; - } - if (/enable/.test(opts.pathname)) { - enable(); - return; - } - if (/disable/.test(opts.pathname)) { - disable(); - return; - } - if (/status/.test(opts.pathname)) { - getStatus(); - return; - } - if (/list/.test(opts.pathname)) { listSuccess(); - return; - } + }); + } + function handleError(err) { + res.statusCode = 500; res.setHeader('Content-Type', 'application/json'); - res.end(JSON.stringify({"error":{"message":"unrecognized rpc"}})); - }); + res.end(JSON.stringify({ + error: { message: err.message, code: err.code } + })); + } + + function enable() { + delete state.config.disable;// = undefined; + state.keepAlive.state = true; + + fs.writeFile(confpath, YAML.safeDump(snakeCopy(state.config)), function (err) { + if (err) { + err.message = "Could not save config file. Perhaps you're user doesn't have permission?"; + handleError(err); + return; + } + // TODO XXX myRemote.active + if (myRemote) { + listSuccess(); + return; + } + safeStartTelebitRemote(true).then(listSuccess).catch(handleError); + }); + } + + function disable() { + state.config.disable = true; + state.keepAlive.state = false; + + if (myRemote) { myRemote.end(); myRemote = null; } + fs.writeFile(confpath, YAML.safeDump(snakeCopy(state.config)), function (err) { + res.setHeader('Content-Type', 'application/json'); + if (err) { + err.message = "Could not save config file. Perhaps you're user doesn't have permission?"; + handleError(err); + return; + } + res.end('{"success":true}'); + }); + } + + function getStatus() { + res.setHeader('Content-Type', 'application/json'); + res.end(JSON.stringify( + { module: 'status' + , status: (state.config.disable ? 'disabled' : 'enabled') + , ready: ((state.config.relay && (state.config.token || state.config.agreeTos)) ? true : false) + , active: !!myRemote + , connected: 'maybe (todo)' + , version: pkg.version + , servernames: state.servernames + } + )); + } + + if (/\b(config)\b/.test(opts.pathname) && /get/i.test(req.method)) { + getConfigOnly(); + return; + } + if (/\b(init|config)\b/.test(opts.pathname)) { + initOrConfig(); + return; + } + if (/restart/.test(opts.pathname)) { + restart(); + return; + } + // + // Check for proper config + // + if (!state.config.relay || !state.config.email || !state.config.agreeTos) { + invalidConfig(); + return; + } + // + // With proper config + // + if (/http/.test(opts.pathname)) { + controllers.http(req, res, opts); + return; + } + if (/tcp/.test(opts.pathname)) { + controllers.tcp(req, res, opts); + return; + } + if (/save|commit/.test(opts.pathname)) { + saveAndCommit(); + return; + } + if (/ssh/.test(opts.pathname)) { + controllers.ssh(req, res, opts); + return; + } + if (/enable/.test(opts.pathname)) { + enable(); + return; + } + if (/disable/.test(opts.pathname)) { + disable(); + return; + } + if (/status/.test(opts.pathname)) { + getStatus(); + return; + } + if (/list/.test(opts.pathname)) { + listSuccess(); + return; + } + + res.setHeader('Content-Type', 'application/json'); + res.end(JSON.stringify({"error":{"message":"unrecognized rpc"}})); +} +function serveControlsHelper() { + controlServer = http.createServer(handleRemoteClient); if (fs.existsSync(state._ipc.path)) { fs.unlinkSync(state._ipc.path); @@ -661,15 +671,30 @@ function serveControlsHelper() { , readableAll: true , exclusive: false }; + if (!state.config.ipc) { + state.config.ipc = {}; + } + if (!state.config.ipc.path) { + state.config.ipc.path = path.dirname(state._ipc.path); + } + require('mkdirp').sync(state.config.ipc.path); + if (!state.config.ipc.type) { + state.config.ipc.type = 'port'; + } + var portFile = path.join(state.config.ipc.path, 'telebit.port'); + if (fs.existsSync(portFile)) { + state._ipc.port = parseInt(fs.readFileSync(portFile, 'utf8').trim(), 10); + } + if ('socket' === state._ipc.type) { require('mkdirp').sync(path.dirname(state._ipc.path)); } // https://nodejs.org/api/net.html#net_server_listen_options_callback // path is ignore if port is defined // https://git.coolaj86.com/coolaj86/telebit.js/issues/23#issuecomment-326 - if (state._ipc.port) { + if ('port' === state.config.ipc.type) { serverOpts.host = 'localhost'; - serverOpts.port = state._ipc.port; + serverOpts.port = state._ipc.port || 0; } else { serverOpts.path = state._ipc.path; } @@ -682,6 +707,21 @@ function serveControlsHelper() { //console.log(this.address()); console.info("[info] Listening for commands on", address); }); + controlServer.on('error', function (err) { + if ('EADDRINUSE' === err.code) { + try { + fs.unlinkSync(portFile); + } catch(e) { + // nada + } + setTimeout(function () { + console.log("trying again"); + serveControlsHelper(); + }, 1000); + return; + } + console.error('failed to start c&c server:', err); + }); } function serveControls() { diff --git a/lib/admin/index.html b/lib/admin/index.html new file mode 100644 index 0000000..0b180dc --- /dev/null +++ b/lib/admin/index.html @@ -0,0 +1,60 @@ + + +
+{{ config }}
+ {{ status }}
+ {{ init }}
+ {{ http }}
+ {{ tcp }}
+ {{ ssh }}
+ , or missing
. Bailing hydration and performing ' + + 'full client-side render.' + ); + } + } + // either not server-rendered, or hydration failed. + // create an empty node and replace it + oldVnode = emptyNodeAt(oldVnode); + } + + // replacing existing element + var oldElm = oldVnode.elm; + var parentElm$1 = nodeOps.parentNode(oldElm); + + // create new node + createElm( + vnode, + insertedVnodeQueue, + // extremely rare edge case: do not insert if old element is in a + // leaving transition. Only happens when combining transition + + // keep-alive + HOCs. (#4590) + oldElm._leaveCb ? null : parentElm$1, + nodeOps.nextSibling(oldElm) + ); + + // update parent placeholder node element, recursively + if (isDef(vnode.parent)) { + var ancestor = vnode.parent; + var patchable = isPatchable(vnode); + while (ancestor) { + for (var i = 0; i < cbs.destroy.length; ++i) { + cbs.destroy[i](ancestor); + } + ancestor.elm = vnode.elm; + if (patchable) { + for (var i$1 = 0; i$1 < cbs.create.length; ++i$1) { + cbs.create[i$1](emptyNode, ancestor); + } + // #6513 + // invoke insert hooks that may have been merged by create hooks. + // e.g. for directives that uses the "inserted" hook. + var insert = ancestor.data.hook.insert; + if (insert.merged) { + // start at index 1 to avoid re-invoking component mounted hook + for (var i$2 = 1; i$2 < insert.fns.length; i$2++) { + insert.fns[i$2](); + } + } + } else { + registerRef(ancestor); + } + ancestor = ancestor.parent; + } + } + + // destroy old node + if (isDef(parentElm$1)) { + removeVnodes(parentElm$1, [oldVnode], 0, 0); + } else if (isDef(oldVnode.tag)) { + invokeDestroyHook(oldVnode); + } + } + } + + invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch); + return vnode.elm + } +} + +/* */ + +var directives = { + create: updateDirectives, + update: updateDirectives, + destroy: function unbindDirectives (vnode) { + updateDirectives(vnode, emptyNode); + } +} + +function updateDirectives (oldVnode, vnode) { + if (oldVnode.data.directives || vnode.data.directives) { + _update(oldVnode, vnode); + } +} + +function _update (oldVnode, vnode) { + var isCreate = oldVnode === emptyNode; + var isDestroy = vnode === emptyNode; + var oldDirs = normalizeDirectives$1(oldVnode.data.directives, oldVnode.context); + var newDirs = normalizeDirectives$1(vnode.data.directives, vnode.context); + + var dirsWithInsert = []; + var dirsWithPostpatch = []; + + var key, oldDir, dir; + for (key in newDirs) { + oldDir = oldDirs[key]; + dir = newDirs[key]; + if (!oldDir) { + // new directive, bind + callHook$1(dir, 'bind', vnode, oldVnode); + if (dir.def && dir.def.inserted) { + dirsWithInsert.push(dir); + } + } else { + // existing directive, update + dir.oldValue = oldDir.value; + callHook$1(dir, 'update', vnode, oldVnode); + if (dir.def && dir.def.componentUpdated) { + dirsWithPostpatch.push(dir); + } + } + } + + if (dirsWithInsert.length) { + var callInsert = function () { + for (var i = 0; i < dirsWithInsert.length; i++) { + callHook$1(dirsWithInsert[i], 'inserted', vnode, oldVnode); + } + }; + if (isCreate) { + mergeVNodeHook(vnode, 'insert', callInsert); + } else { + callInsert(); + } + } + + if (dirsWithPostpatch.length) { + mergeVNodeHook(vnode, 'postpatch', function () { + for (var i = 0; i < dirsWithPostpatch.length; i++) { + callHook$1(dirsWithPostpatch[i], 'componentUpdated', vnode, oldVnode); + } + }); + } + + if (!isCreate) { + for (key in oldDirs) { + if (!newDirs[key]) { + // no longer present, unbind + callHook$1(oldDirs[key], 'unbind', oldVnode, oldVnode, isDestroy); + } + } + } +} + +var emptyModifiers = Object.create(null); + +function normalizeDirectives$1 ( + dirs, + vm +) { + var res = Object.create(null); + if (!dirs) { + // $flow-disable-line + return res + } + var i, dir; + for (i = 0; i < dirs.length; i++) { + dir = dirs[i]; + if (!dir.modifiers) { + // $flow-disable-line + dir.modifiers = emptyModifiers; + } + res[getRawDirName(dir)] = dir; + dir.def = resolveAsset(vm.$options, 'directives', dir.name, true); + } + // $flow-disable-line + return res +} + +function getRawDirName (dir) { + return dir.rawName || ((dir.name) + "." + (Object.keys(dir.modifiers || {}).join('.'))) +} + +function callHook$1 (dir, hook, vnode, oldVnode, isDestroy) { + var fn = dir.def && dir.def[hook]; + if (fn) { + try { + fn(vnode.elm, dir, vnode, oldVnode, isDestroy); + } catch (e) { + handleError(e, vnode.context, ("directive " + (dir.name) + " " + hook + " hook")); + } + } +} + +var baseModules = [ + ref, + directives +] + +/* */ + +function updateAttrs (oldVnode, vnode) { + var opts = vnode.componentOptions; + if (isDef(opts) && opts.Ctor.options.inheritAttrs === false) { + return + } + if (isUndef(oldVnode.data.attrs) && isUndef(vnode.data.attrs)) { + return + } + var key, cur, old; + var elm = vnode.elm; + var oldAttrs = oldVnode.data.attrs || {}; + var attrs = vnode.data.attrs || {}; + // clone observed objects, as the user probably wants to mutate it + if (isDef(attrs.__ob__)) { + attrs = vnode.data.attrs = extend({}, attrs); + } + + for (key in attrs) { + cur = attrs[key]; + old = oldAttrs[key]; + if (old !== cur) { + setAttr(elm, key, cur); + } + } + // #4391: in IE9, setting type can reset value for input[type=radio] + // #6666: IE/Edge forces progress value down to 1 before setting a max + /* istanbul ignore if */ + if ((isIE || isEdge) && attrs.value !== oldAttrs.value) { + setAttr(elm, 'value', attrs.value); + } + for (key in oldAttrs) { + if (isUndef(attrs[key])) { + if (isXlink(key)) { + elm.removeAttributeNS(xlinkNS, getXlinkProp(key)); + } else if (!isEnumeratedAttr(key)) { + elm.removeAttribute(key); + } + } + } +} + +function setAttr (el, key, value) { + if (el.tagName.indexOf('-') > -1) { + baseSetAttr(el, key, value); + } else if (isBooleanAttr(key)) { + // set attribute for blank value + // e.g. + if (isFalsyAttrValue(value)) { + el.removeAttribute(key); + } else { + // technically allowfullscreen is a boolean attribute for