// -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*- /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ "use strict"; this.EXPORTED_SYMBOLS = ["MulticastDNS"]; const { classes: Cc, interfaces: Ci, utils: Cu } = Components; Cu.import("resource://gre/modules/Messaging.jsm"); Cu.import("resource://gre/modules/Services.jsm"); var log = Cu.import("resource://gre/modules/AndroidLog.jsm", {}).AndroidLog.d.bind(null, "MulticastDNS"); const FAILURE_INTERNAL_ERROR = -65537; // Helper function for sending commands to Java. function send(type, data, callback) { let msg = { type: type }; for (let i in data) { try { msg[i] = data[i]; } catch (e) { } } Messaging.sendRequestForResult(msg) .then(result => callback(result, null), err => callback(null, typeof err === "number" ? err : FAILURE_INTERNAL_ERROR)); } // Receives service found/lost event from NsdManager function ServiceManager() { } ServiceManager.prototype = { listeners: {}, numListeners: 0, registerEvent: function() { log("registerEvent"); Messaging.addListener(this.onServiceFound.bind(this), "NsdManager:ServiceFound"); Messaging.addListener(this.onServiceLost.bind(this), "NsdManager:ServiceLost"); }, unregisterEvent: function() { log("unregisterEvent"); Messaging.removeListener("NsdManager:ServiceFound"); Messaging.removeListener("NsdManager:ServiceLost"); }, addListener: function(aServiceType, aListener) { log("addListener: " + aServiceType + ", " + aListener); if (!this.listeners[aServiceType]) { this.listeners[aServiceType] = []; } if (this.listeners[aServiceType].includes(aListener)) { log("listener already exists"); return; } this.listeners[aServiceType].push(aListener); ++this.numListeners; if (this.numListeners === 1) { this.registerEvent(); } log("listener added: " + this); }, removeListener: function(aServiceType, aListener) { log("removeListener: " + aServiceType + ", " + aListener); if (!this.listeners[aServiceType]) { log("listener doesn't exist"); return; } let index = this.listeners[aServiceType].indexOf(aListener); if (index < 0) { log("listener doesn't exist"); return; } this.listeners[aServiceType].splice(index, 1); --this.numListeners; if (this.numListeners === 0) { this.unregisterEvent(); } log("listener removed" + this); }, onServiceFound: function(aServiceInfo) { let listeners = this.listeners[aServiceInfo.serviceType]; if (listeners) { for (let listener of listeners) { listener.onServiceFound(aServiceInfo); } } else { log("no listener"); } return {}; }, onServiceLost: function(aServiceInfo) { let listeners = this.listeners[aServiceInfo.serviceType]; if (listeners) { for (let listener of listeners) { listener.onServiceLost(aServiceInfo); } } else { log("no listener"); } return {}; } }; // make an object from nsIPropertyBag2 function parsePropertyBag2(bag) { if (!bag || !(bag instanceof Ci.nsIPropertyBag2)) { throw new TypeError("Not a property bag"); } let attributes = []; let enumerator = bag.enumerator; while (enumerator.hasMoreElements()) { let name = enumerator.getNext().QueryInterface(Ci.nsIProperty).name; let value = bag.getPropertyAsACString(name); attributes.push({ "name": name, "value": value }); } return attributes; } function MulticastDNS() { this.serviceManager = new ServiceManager(); } MulticastDNS.prototype = { startDiscovery: function(aServiceType, aListener) { this.serviceManager.addListener(aServiceType, aListener); let serviceInfo = { serviceType: aServiceType, uniqueId: aListener.uuid }; send("NsdManager:DiscoverServices", serviceInfo, (result, err) => { if (err) { log("onStartDiscoveryFailed: " + aServiceType + " (" + err + ")"); this.serviceManager.removeListener(aServiceType, aListener); aListener.onStartDiscoveryFailed(aServiceType, err); } else { aListener.onDiscoveryStarted(result); } }); }, stopDiscovery: function(aServiceType, aListener) { this.serviceManager.removeListener(aServiceType, aListener); let serviceInfo = { uniqueId: aListener.uuid }; send("NsdManager:StopServiceDiscovery", serviceInfo, (result, err) => { if (err) { log("onStopDiscoveryFailed: " + aServiceType + " (" + err + ")"); aListener.onStopDiscoveryFailed(aServiceType, err); } else { aListener.onDiscoveryStopped(aServiceType); } }); }, registerService: function(aServiceInfo, aListener) { let serviceInfo = { port: aServiceInfo.port, serviceType: aServiceInfo.serviceType, uniqueId: aListener.uuid }; try { serviceInfo.host = aServiceInfo.host; } catch(e) { // host unspecified } try { serviceInfo.serviceName = aServiceInfo.serviceName; } catch(e) { // serviceName unspecified } try { serviceInfo.attributes = parsePropertyBag2(aServiceInfo.attributes); } catch(e) { // attributes unspecified } send("NsdManager:RegisterService", serviceInfo, (result, err) => { if (err) { log("onRegistrationFailed: (" + err + ")"); aListener.onRegistrationFailed(aServiceInfo, err); } else { aListener.onServiceRegistered(result); } }); }, unregisterService: function(aServiceInfo, aListener) { let serviceInfo = { uniqueId: aListener.uuid }; send("NsdManager:UnregisterService", serviceInfo, (result, err) => { if (err) { log("onUnregistrationFailed: (" + err + ")"); aListener.onUnregistrationFailed(aServiceInfo, err); } else { aListener.onServiceUnregistered(aServiceInfo); } }); }, resolveService: function(aServiceInfo, aListener) { send("NsdManager:ResolveService", aServiceInfo, (result, err) => { if (err) { log("onResolveFailed: (" + err + ")"); aListener.onResolveFailed(aServiceInfo, err); } else { aListener.onServiceResolved(result); } }); } };