//--------------------------------------------------------------------------- // @syndicate-lang/driver-mdns, mDNS support for Syndicate. // Copyright (C) 2016-2018 Tony Garnock-Jones // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . //--------------------------------------------------------------------------- // TODO: support other than the default (usually ".local") domain. const { Observe, currentFacet, genUuid, Dataspace } = require("@syndicate-lang/core"); const S = activate require("@syndicate-lang/driver-streams-node"); const { TimeLaterThan } = activate require("@syndicate-lang/driver-timer"); const bonjour = require('bonjour')(); assertion type Service(name, serviceType) = Symbol.for("mdns-service"); assertion type Publish(svc, hostName, port, txtData) = Symbol.for("mdns-publish"); assertion type Published(svc, hostName, port, txtData) = Symbol.for("mdns-published"); assertion type Discovered(svc, hostName, port, txtData, address) = Symbol.for("mdns-discovered"); // ^ Avahi gave us "family" and "interfaceName", too, but `bonjour` doesn't. assertion type WildcardBrowserActive() = Symbol.for("-mdns-wildcard-browser-active"); export { Service, Publish, Published, Discovered, }; function parseServiceType(serviceType) { const splitType = /_([^_]+)\._([^_]+)/.exec(serviceType); if (!splitType) { throw new Error('Cannot parse serviceType + ' + JSON.stringify(serviceType)) } return [splitType[1], splitType[2]]; } spawn named 'driver/mdns-publish' { during Observe(Published($svc, $hostName, $port, $txtData)) { assert Publish(svc, hostName, port, txtData); } // TODO: The `bonjour` module is limited to publishing a single TXT // record with key-value strings. It'd be nice to support the full // range of TXT content allowed by the mDNS and DNS RFCs, including // non-key-value-formatted data and multiple TXT records. during Publish($svc(Service($name, $serviceType)), $hostName, $port, $txtData) spawn named ['bonjour', svc] { const options = {}; options.name = name; if (hostName !== null) options.host = hostName; options.port = port; [options.type, options.protocol] = parseServiceType(serviceType); options.txt = txtData.toJS(); console.log('publish', options); const service = bonjour.publish(options); on stop service.stop(); field this.established = false; assert Published(svc, hostName, port, txtData) when (this.established); service.on('up', Dataspace.wrapExternal(() => { this.established = true; })); service.on('error', Dataspace.wrapExternal((err) => { throw err; })); } } spawn named 'driver/mdns-browse' { during Observe(Discovered(Service(_, $serviceType), _, _, _, _)) { const options = {}; if (typeof serviceType === 'string') { [options.type, options.protocol] = parseServiceType(serviceType); } const browser = bonjour.find(options); on stop browser.stop(); // field this.nextUpdate = +(new Date()); // on asserted TimeLaterThan(this.nextUpdate) { // console.log('updating'); // browser.update(); // } const eachRecord = (service, cb) => { const svc = Service(service.name, '_' + service.type + '._' + service.protocol); for (const addr of service.addresses) { cb(Discovered(svc, service.host, service.port, service.txt, addr)); } // const now = +(new Date()); // if (this.nextUpdate < now) { // this.nextUpdate = now + 200; // } }; browser.on('up', Dataspace.wrapExternal((service) => { eachRecord(service, (a) => { currentFacet().actor.adhocAssert(a) }); })); browser.on('down', Dataspace.wrapExternal((service) => { eachRecord(service, (a) => { currentFacet().actor.adhocRetract(a) }); })); if (typeof serviceType !== 'string') { assert WildcardBrowserActive(); } else { stop on asserted WildcardBrowserActive(); } } }