lib/provider/browser.js

Summary

Maintainability
C
1 day
Test Coverage
"use strict";

const logger = require("debug")("leaf:provider:browser");

const mdns = require("leaf-mdns");
const semver = require("semver");

var mDnsBrowser, globalDnsCache = {};

class Browser {
  constructor() {
    function serviceUp(service) {
      logger(`serviceup: ${service.type.name}`, service);
      if (service.type.name !== "leaf") {
        logger('ignoring service, service name not leaf');
        return;
      }
      if (!service.txtRecord || !service.txtRecord.version) {
        logger('ignoring service, service.txtRecord does not exist');
        return;
      }

      let host;
      for (let i = 0; i < service.addresses.length; i++) {
        if (!service.addresses[i].includes(":")) {
          host = service.addresses[i];
        }
      }
      if (!host) {
        logger('ipv4 not found, ignoring service');
        return;
      }
      let type = (globalDnsCache[service.txtRecord.name] = globalDnsCache[service.txtRecord.name] || {});
      let ver = type[service.txtRecord.version] = type[service.txtRecord.version] || {
        indices: [],
        services: {}
      };
      let id = service.host + ":" + service.port;
      ver.services[id] = service;
      ver.indices.push(id);
      logger('adding service to dns cache');
    }

    function serviceDown(service) {
      let type, ver;
      logger(`service ${service} is down`);
      if ((type = (globalDnsCache[service.type.name])) && (ver = type[service.txtRecord.version])) {
        let id = `${service.host}:${service.port}`;
        delete ver.services[id];
        ver.indices.filter(function(e) {
          return e === id;
        });
      }
    }

    function browserError(err, service) {
      logger("browser error:", err, err.stack);
      logger("service:", service);
    }

    if (!mDnsBrowser) {
      // mDnsBrowser = mdns.browseThemAll();
      // probably will require one more for loopback interface
      logger("creating service browser");
      mDnsBrowser = new mdns.Browser(mdns.tcp("leaf"), {
        resolverSequence: [
          mdns.rst.DNSServiceResolve(),
          mdns.rst.DNSServiceGetAddrInfo({
            families: [4]
          })
          // mdns.rst.getaddrinfo({families:[4]})
          // ('DNSServiceGetAddrInfo' in mdns.dns_sd ? mdns.rst.DNSServiceGetAddrInfo : mdns.rst.getaddrinfo)({families:[4]})
        ]
      });
      mDnsBrowser.on("serviceUp", serviceUp);
      mDnsBrowser.on("serviceChanged", serviceUp);
      mDnsBrowser.on("serviceDown", serviceDown);
      mDnsBrowser.on("error", browserError);
      logger("starting service browser");
      mDnsBrowser.start();
    }
  }

  get(name, ver) {
    let time = new Date();
    // think of a way to refactor this into a generator, so we can just store that as a cache and yield everytime
    // hopeful usage would be:
    // var service = cache[service@version].next();
    // service.request(method, data, blabla);
    logger(`getting service(${name}@${ver})`);
    return new Promise((resolve, reject) => {
      function resolver() {
        logger("looping through browser caches");
        // keep trying for a specific amount of time, now hard coded, 1s
        if (new Date() - time > 2000) {
          logger("timeout");
          return reject();
        }
        if (name in globalDnsCache) {
          logger("name is found in globaDnsCache");
          let topver;
          for (let gver in globalDnsCache[name]) {
            if (semver.satisfies(gver, ver)) {
              if (topver === undefined || semver.gt(gver, topver)) {
                topver = gver;
              }
            }
          }
          if (topver) {
            logger("round robinning");
            // lets first do round robin
            let pool = globalDnsCache[name][topver];
            let c = ((pool.lastCounter === null || pool.lastCount === undefined) ? 0 : pool.lastCounter);

            if (c + 1 > pool.indices.length) {
              c = -1;
            }
            pool.lastCounter++;
            logger("pool is:" + JSON.stringify(pool.services));
            logger("resolved with:" + pool.services[pool.indices[c + 1]]);
            // TODO: handle cases where it is not found
            return resolve(pool.services[pool.indices[c + 1]]);
          }
        }
        setTimeout(resolver, 30);
      }
      process.nextTick(resolver);
    });
  }
}

exports = module.exports = Browser;