122 lines
4.5 KiB
JavaScript
122 lines
4.5 KiB
JavaScript
//---------------------------------------------------------------------------
|
|
// @syndicate-lang/driver-mdns, mDNS support for Syndicate.
|
|
// Copyright (C) 2016-2018 Tony Garnock-Jones <tonyg@leastfixedpoint.com>
|
|
//
|
|
// 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 <https://www.gnu.org/licenses/>.
|
|
//---------------------------------------------------------------------------
|
|
|
|
// 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();
|
|
}
|
|
}
|
|
}
|