1 | "use strict";
|
2 | Object.defineProperty(exports, "__esModule", { value: true });
|
3 | exports.ResolvedAdvertiser = exports.AvahiAdvertiser = exports.DBusInvokeError = exports.BonjourHAPAdvertiser = exports.CiaoAdvertiser = exports.AdvertiserEvent = exports.PairingFeatureFlag = exports.StatusFlag = void 0;
|
4 | const tslib_1 = require("tslib");
|
5 |
|
6 |
|
7 | const ciao_1 = tslib_1.__importDefault(require("@homebridge/ciao"));
|
8 | const dbus_native_1 = tslib_1.__importDefault(require("@homebridge/dbus-native"));
|
9 | const assert_1 = tslib_1.__importDefault(require("assert"));
|
10 | const bonjour_hap_1 = tslib_1.__importDefault(require("bonjour-hap"));
|
11 | const crypto_1 = tslib_1.__importDefault(require("crypto"));
|
12 | const debug_1 = tslib_1.__importDefault(require("debug"));
|
13 | const events_1 = require("events");
|
14 | const promise_utils_1 = require("./util/promise-utils");
|
15 | const debug = (0, debug_1.default)("HAP-NodeJS:Advertiser");
|
16 |
|
17 |
|
18 |
|
19 |
|
20 |
|
21 |
|
22 | var StatusFlag;
|
23 | (function (StatusFlag) {
|
24 | StatusFlag[StatusFlag["NOT_PAIRED"] = 1] = "NOT_PAIRED";
|
25 | StatusFlag[StatusFlag["NOT_JOINED_WIFI"] = 2] = "NOT_JOINED_WIFI";
|
26 | StatusFlag[StatusFlag["PROBLEM_DETECTED"] = 4] = "PROBLEM_DETECTED";
|
27 | })(StatusFlag || (exports.StatusFlag = StatusFlag = {}));
|
28 |
|
29 |
|
30 |
|
31 |
|
32 |
|
33 |
|
34 | var PairingFeatureFlag;
|
35 | (function (PairingFeatureFlag) {
|
36 | PairingFeatureFlag[PairingFeatureFlag["SUPPORTS_HARDWARE_AUTHENTICATION"] = 1] = "SUPPORTS_HARDWARE_AUTHENTICATION";
|
37 | PairingFeatureFlag[PairingFeatureFlag["SUPPORTS_SOFTWARE_AUTHENTICATION"] = 2] = "SUPPORTS_SOFTWARE_AUTHENTICATION";
|
38 | })(PairingFeatureFlag || (exports.PairingFeatureFlag = PairingFeatureFlag = {}));
|
39 |
|
40 |
|
41 |
|
42 | var AdvertiserEvent;
|
43 | (function (AdvertiserEvent) {
|
44 | |
45 |
|
46 |
|
47 |
|
48 | AdvertiserEvent["UPDATED_NAME"] = "updated-name";
|
49 | })(AdvertiserEvent || (exports.AdvertiserEvent = AdvertiserEvent = {}));
|
50 |
|
51 |
|
52 |
|
53 |
|
54 |
|
55 |
|
56 |
|
57 |
|
58 |
|
59 |
|
60 | class CiaoAdvertiser extends events_1.EventEmitter {
|
61 | static protocolVersion = "1.1";
|
62 | static protocolVersionService = "1.1.0";
|
63 | accessoryInfo;
|
64 | setupHash;
|
65 | responder;
|
66 | advertisedService;
|
67 | constructor(accessoryInfo, responderOptions, serviceOptions) {
|
68 | super();
|
69 | this.accessoryInfo = accessoryInfo;
|
70 | this.setupHash = CiaoAdvertiser.computeSetupHash(accessoryInfo);
|
71 | this.responder = ciao_1.default.getResponder({
|
72 | ...responderOptions,
|
73 | });
|
74 | this.advertisedService = this.responder.createService({
|
75 | name: this.accessoryInfo.displayName,
|
76 | type: "hap" ,
|
77 | txt: CiaoAdvertiser.createTxt(accessoryInfo, this.setupHash),
|
78 |
|
79 | ...serviceOptions,
|
80 | });
|
81 | this.advertisedService.on("name-change" , this.emit.bind(this, "updated-name" ));
|
82 | debug(`Preparing Advertiser for '${this.accessoryInfo.displayName}' using ciao backend!`);
|
83 | }
|
84 | initPort(port) {
|
85 | this.advertisedService.updatePort(port);
|
86 | }
|
87 | startAdvertising() {
|
88 | debug(`Starting to advertise '${this.accessoryInfo.displayName}' using ciao backend!`);
|
89 | return this.advertisedService.advertise();
|
90 | }
|
91 | updateAdvertisement(silent) {
|
92 | const txt = CiaoAdvertiser.createTxt(this.accessoryInfo, this.setupHash);
|
93 | debug("Updating txt record (txt: %o, silent: %d)", txt, silent);
|
94 | this.advertisedService.updateTxt(txt, silent);
|
95 | }
|
96 | async destroy() {
|
97 |
|
98 | await this.responder.shutdown();
|
99 | this.removeAllListeners();
|
100 | }
|
101 | static createTxt(accessoryInfo, setupHash) {
|
102 | const statusFlags = [];
|
103 | if (!accessoryInfo.paired()) {
|
104 | statusFlags.push(1 );
|
105 | }
|
106 | return {
|
107 | "c#": accessoryInfo.getConfigVersion(),
|
108 | ff: CiaoAdvertiser.ff(),
|
109 | id: accessoryInfo.username,
|
110 | md: accessoryInfo.model,
|
111 | pv: CiaoAdvertiser.protocolVersion,
|
112 | "s#": 1,
|
113 | sf: CiaoAdvertiser.sf(...statusFlags),
|
114 | ci: accessoryInfo.category,
|
115 | sh: setupHash,
|
116 | };
|
117 | }
|
118 | static computeSetupHash(accessoryInfo) {
|
119 | const hash = crypto_1.default.createHash("sha512");
|
120 | hash.update(accessoryInfo.setupID + accessoryInfo.username.toUpperCase());
|
121 | return hash.digest().slice(0, 4).toString("base64");
|
122 | }
|
123 | static ff(...flags) {
|
124 | let value = 0;
|
125 | flags.forEach(flag => value |= flag);
|
126 | return value;
|
127 | }
|
128 | static sf(...flags) {
|
129 | let value = 0;
|
130 | flags.forEach(flag => value |= flag);
|
131 | return value;
|
132 | }
|
133 | }
|
134 | exports.CiaoAdvertiser = CiaoAdvertiser;
|
135 |
|
136 |
|
137 |
|
138 |
|
139 |
|
140 | class BonjourHAPAdvertiser extends events_1.EventEmitter {
|
141 | accessoryInfo;
|
142 | setupHash;
|
143 | serviceOptions;
|
144 | bonjour;
|
145 | advertisement;
|
146 | port;
|
147 | destroyed = false;
|
148 | constructor(accessoryInfo, serviceOptions) {
|
149 | super();
|
150 | this.accessoryInfo = accessoryInfo;
|
151 | this.setupHash = CiaoAdvertiser.computeSetupHash(accessoryInfo);
|
152 | this.serviceOptions = serviceOptions;
|
153 | this.bonjour = (0, bonjour_hap_1.default)();
|
154 | debug(`Preparing Advertiser for '${this.accessoryInfo.displayName}' using bonjour-hap backend!`);
|
155 | }
|
156 | initPort(port) {
|
157 | this.port = port;
|
158 | }
|
159 | startAdvertising() {
|
160 | (0, assert_1.default)(!this.destroyed, "Can't advertise on a destroyed bonjour instance!");
|
161 | if (this.port == null) {
|
162 | throw new Error("Tried starting bonjour-hap advertisement without initializing port!");
|
163 | }
|
164 | debug(`Starting to advertise '${this.accessoryInfo.displayName}' using bonjour-hap backend!`);
|
165 | if (this.advertisement) {
|
166 | this.destroy();
|
167 | }
|
168 | const hostname = this.accessoryInfo.username.replace(/:/ig, "_") + ".local";
|
169 | this.advertisement = this.bonjour.publish({
|
170 | name: this.accessoryInfo.displayName,
|
171 | type: "hap",
|
172 | port: this.port,
|
173 | txt: CiaoAdvertiser.createTxt(this.accessoryInfo, this.setupHash),
|
174 | host: hostname,
|
175 | addUnsafeServiceEnumerationRecord: true,
|
176 | ...this.serviceOptions,
|
177 | });
|
178 | return (0, promise_utils_1.PromiseTimeout)(1);
|
179 | }
|
180 | updateAdvertisement(silent) {
|
181 | const txt = CiaoAdvertiser.createTxt(this.accessoryInfo, this.setupHash);
|
182 | debug("Updating txt record (txt: %o, silent: %d)", txt, silent);
|
183 | if (this.advertisement) {
|
184 | this.advertisement.updateTxt(txt, silent);
|
185 | }
|
186 | }
|
187 | destroy() {
|
188 | if (this.advertisement) {
|
189 | this.advertisement.stop(() => {
|
190 | this.advertisement.destroy();
|
191 | this.advertisement = undefined;
|
192 | this.bonjour.destroy();
|
193 | });
|
194 | }
|
195 | else {
|
196 | this.bonjour.destroy();
|
197 | }
|
198 | }
|
199 | }
|
200 | exports.BonjourHAPAdvertiser = BonjourHAPAdvertiser;
|
201 | function messageBusConnectionResult(bus) {
|
202 | return new Promise((resolve, reject) => {
|
203 | const errorHandler = (error) => {
|
204 |
|
205 | bus.connection.removeListener("connect", connectHandler);
|
206 | reject(error);
|
207 | };
|
208 | const connectHandler = () => {
|
209 | bus.connection.removeListener("error", errorHandler);
|
210 | resolve();
|
211 | };
|
212 | bus.connection.once("connect", connectHandler);
|
213 | bus.connection.once("error", errorHandler);
|
214 | });
|
215 | }
|
216 |
|
217 |
|
218 |
|
219 | class DBusInvokeError extends Error {
|
220 | errorName;
|
221 |
|
222 | constructor(errorObject) {
|
223 | super();
|
224 | Object.setPrototypeOf(this, DBusInvokeError.prototype);
|
225 | this.name = "DBusInvokeError";
|
226 | this.errorName = errorObject.name;
|
227 | if (Array.isArray(errorObject.message) && errorObject.message.length === 1) {
|
228 | this.message = errorObject.message[0];
|
229 | }
|
230 | else {
|
231 | this.message = errorObject.message.toString();
|
232 | }
|
233 | }
|
234 | }
|
235 | exports.DBusInvokeError = DBusInvokeError;
|
236 |
|
237 | function dbusInvoke(bus, destination, path, dbusInterface, member, others) {
|
238 | return new Promise((resolve, reject) => {
|
239 | const command = {
|
240 | destination,
|
241 | path,
|
242 | interface: dbusInterface,
|
243 | member,
|
244 | ...(others || {}),
|
245 | };
|
246 | bus.invoke(command, (err, result) => {
|
247 | if (err) {
|
248 | reject(new DBusInvokeError(err));
|
249 | }
|
250 | else {
|
251 | resolve(result);
|
252 | }
|
253 | });
|
254 | });
|
255 | }
|
256 |
|
257 |
|
258 |
|
259 |
|
260 |
|
261 |
|
262 |
|
263 | var AvahiServerState;
|
264 | (function (AvahiServerState) {
|
265 |
|
266 | AvahiServerState[AvahiServerState["INVALID"] = 0] = "INVALID";
|
267 | AvahiServerState[AvahiServerState["REGISTERING"] = 1] = "REGISTERING";
|
268 | AvahiServerState[AvahiServerState["RUNNING"] = 2] = "RUNNING";
|
269 | AvahiServerState[AvahiServerState["COLLISION"] = 3] = "COLLISION";
|
270 | AvahiServerState[AvahiServerState["FAILURE"] = 4] = "FAILURE";
|
271 | })(AvahiServerState || (AvahiServerState = {}));
|
272 |
|
273 |
|
274 |
|
275 |
|
276 |
|
277 |
|
278 |
|
279 |
|
280 |
|
281 | class AvahiAdvertiser extends events_1.EventEmitter {
|
282 | accessoryInfo;
|
283 | setupHash;
|
284 | port;
|
285 | bus;
|
286 | avahiServerInterface;
|
287 | path;
|
288 | stateChangeHandler;
|
289 | constructor(accessoryInfo) {
|
290 | super();
|
291 | this.accessoryInfo = accessoryInfo;
|
292 | this.setupHash = CiaoAdvertiser.computeSetupHash(accessoryInfo);
|
293 | debug(`Preparing Advertiser for '${this.accessoryInfo.displayName}' using Avahi backend!`);
|
294 | this.bus = dbus_native_1.default.systemBus();
|
295 | this.stateChangeHandler = this.handleStateChangedEvent.bind(this);
|
296 | }
|
297 | createTxt() {
|
298 | return Object
|
299 | .entries(CiaoAdvertiser.createTxt(this.accessoryInfo, this.setupHash))
|
300 | .map((el) => Buffer.from(el[0] + "=" + el[1]));
|
301 | }
|
302 | initPort(port) {
|
303 | this.port = port;
|
304 | }
|
305 | async startAdvertising() {
|
306 | if (this.port == null) {
|
307 | throw new Error("Tried starting Avahi advertisement without initializing port!");
|
308 | }
|
309 | if (!this.bus) {
|
310 | throw new Error("Tried to start Avahi advertisement on a destroyed advertiser!");
|
311 | }
|
312 | debug(`Starting to advertise '${this.accessoryInfo.displayName}' using Avahi backend!`);
|
313 | this.path = await AvahiAdvertiser.avahiInvoke(this.bus, "/", "Server", "EntryGroupNew");
|
314 | await AvahiAdvertiser.avahiInvoke(this.bus, this.path, "EntryGroup", "AddService", {
|
315 | body: [
|
316 | -1,
|
317 | -1,
|
318 | 0,
|
319 | this.accessoryInfo.displayName,
|
320 | "_hap._tcp",
|
321 | "",
|
322 | "",
|
323 | this.port,
|
324 | this.createTxt(),
|
325 | ],
|
326 | signature: "iiussssqaay",
|
327 | });
|
328 | await AvahiAdvertiser.avahiInvoke(this.bus, this.path, "EntryGroup", "Commit");
|
329 | try {
|
330 | if (!this.avahiServerInterface) {
|
331 | this.avahiServerInterface = await AvahiAdvertiser.avahiInterface(this.bus, "Server");
|
332 | this.avahiServerInterface.on("StateChanged", this.stateChangeHandler);
|
333 | }
|
334 | }
|
335 | catch (error) {
|
336 |
|
337 | console.warn("Failed to create listener for avahi-daemon server state. The system will not be notified about restarts of avahi-daemon " +
|
338 | "and will therefore stay undiscoverable in those instances. Error message: " + error);
|
339 | if (error.stack) {
|
340 | debug("Detailed error: " + error.stack);
|
341 | }
|
342 | }
|
343 | }
|
344 | |
345 |
|
346 |
|
347 |
|
348 |
|
349 |
|
350 | handleStateChangedEvent(state) {
|
351 | if (state === 2 && this.path) {
|
352 | debug("Found Avahi daemon to have restarted!");
|
353 | this.startAdvertising()
|
354 | .catch(reason => console.error("Could not (re-)create mDNS advertisement. The HAP-Server won't be discoverable: " + reason));
|
355 | }
|
356 | }
|
357 | async updateAdvertisement(silent) {
|
358 | if (!this.bus) {
|
359 | throw new Error("Tried to update Avahi advertisement on a destroyed advertiser!");
|
360 | }
|
361 | if (!this.path) {
|
362 | debug("Tried to update advertisement without a valid `path`!");
|
363 | return;
|
364 | }
|
365 | debug("Updating txt record (txt: %o, silent: %d)", CiaoAdvertiser.createTxt(this.accessoryInfo, this.setupHash), silent);
|
366 | try {
|
367 | await AvahiAdvertiser.avahiInvoke(this.bus, this.path, "EntryGroup", "UpdateServiceTxt", {
|
368 | body: [-1, -1, 0, this.accessoryInfo.displayName, "_hap._tcp", "", this.createTxt()],
|
369 | signature: "iiusssaay",
|
370 | });
|
371 | }
|
372 | catch (error) {
|
373 | console.error("Failed to update avahi advertisement: " + error);
|
374 | }
|
375 | }
|
376 | async destroy() {
|
377 | if (!this.bus) {
|
378 | throw new Error("Tried to destroy Avahi advertisement on a destroyed advertiser!");
|
379 | }
|
380 | if (this.path) {
|
381 | try {
|
382 | await AvahiAdvertiser.avahiInvoke(this.bus, this.path, "EntryGroup", "Free");
|
383 | }
|
384 | catch (error) {
|
385 |
|
386 | debug("Destroying Avahi advertisement failed: " + error);
|
387 | }
|
388 | this.path = undefined;
|
389 | }
|
390 | if (this.avahiServerInterface) {
|
391 | this.avahiServerInterface.removeListener("StateChanged", this.stateChangeHandler);
|
392 | this.avahiServerInterface = undefined;
|
393 | }
|
394 | this.bus.connection.stream.destroy();
|
395 | this.bus = undefined;
|
396 | }
|
397 | static async isAvailable() {
|
398 | const bus = dbus_native_1.default.systemBus();
|
399 | try {
|
400 | try {
|
401 | await messageBusConnectionResult(bus);
|
402 | }
|
403 | catch (error) {
|
404 | debug("Avahi/DBus classified unavailable due to missing dbus interface!");
|
405 | return false;
|
406 | }
|
407 | try {
|
408 | const version = await this.avahiInvoke(bus, "/", "Server", "GetVersionString");
|
409 | debug("Detected Avahi over DBus interface running version '%s'.", version);
|
410 | }
|
411 | catch (error) {
|
412 | debug("Avahi/DBus classified unavailable due to missing avahi interface!");
|
413 | return false;
|
414 | }
|
415 | return true;
|
416 | }
|
417 | finally {
|
418 | bus.connection.stream.destroy();
|
419 | }
|
420 | }
|
421 |
|
422 | static avahiInvoke(bus, path, dbusInterface, member, others) {
|
423 | return dbusInvoke(bus, "org.freedesktop.Avahi", path, `org.freedesktop.Avahi.${dbusInterface}`, member, others);
|
424 | }
|
425 | static avahiInterface(bus, dbusInterface) {
|
426 | return new Promise((resolve, reject) => {
|
427 | bus
|
428 | .getService("org.freedesktop.Avahi")
|
429 | .getInterface("/", "org.freedesktop.Avahi." + dbusInterface, (error, iface) => {
|
430 | if (error || !iface) {
|
431 | reject(error ?? new Error("Interface not present!"));
|
432 | }
|
433 | else {
|
434 | resolve(iface);
|
435 | }
|
436 | });
|
437 | });
|
438 | }
|
439 | }
|
440 | exports.AvahiAdvertiser = AvahiAdvertiser;
|
441 | const RESOLVED_PERMISSIONS_ERRORS = [
|
442 | "org.freedesktop.DBus.Error.AccessDenied",
|
443 | "org.freedesktop.DBus.Error.AuthFailed",
|
444 | "org.freedesktop.DBus.Error.InteractiveAuthorizationRequired",
|
445 | ];
|
446 |
|
447 |
|
448 |
|
449 |
|
450 |
|
451 |
|
452 | class ResolvedAdvertiser extends events_1.EventEmitter {
|
453 | accessoryInfo;
|
454 | setupHash;
|
455 | port;
|
456 | bus;
|
457 | path;
|
458 | constructor(accessoryInfo) {
|
459 | super();
|
460 | this.accessoryInfo = accessoryInfo;
|
461 | this.setupHash = CiaoAdvertiser.computeSetupHash(accessoryInfo);
|
462 | this.bus = dbus_native_1.default.systemBus();
|
463 | debug(`Preparing Advertiser for '${this.accessoryInfo.displayName}' using systemd-resolved backend!`);
|
464 | }
|
465 | createTxt() {
|
466 | return Object
|
467 | .entries(CiaoAdvertiser.createTxt(this.accessoryInfo, this.setupHash))
|
468 | .map((el) => [el[0].toString(), Buffer.from(el[1].toString())]);
|
469 | }
|
470 | initPort(port) {
|
471 | this.port = port;
|
472 | }
|
473 | async startAdvertising() {
|
474 | if (this.port == null) {
|
475 | throw new Error("Tried starting systemd-resolved advertisement without initializing port!");
|
476 | }
|
477 | if (!this.bus) {
|
478 | throw new Error("Tried to start systemd-resolved advertisement on a destroyed advertiser!");
|
479 | }
|
480 | debug(`Starting to advertise '${this.accessoryInfo.displayName}' using systemd-resolved backend!`);
|
481 | try {
|
482 | this.path = await ResolvedAdvertiser.managerInvoke(this.bus, "RegisterService", {
|
483 | body: [
|
484 | this.accessoryInfo.displayName,
|
485 | this.accessoryInfo.displayName,
|
486 | "_hap._tcp",
|
487 | this.port,
|
488 | 0,
|
489 | 0,
|
490 | [this.createTxt()],
|
491 | ],
|
492 | signature: "sssqqqaa{say}",
|
493 | });
|
494 | }
|
495 | catch (error) {
|
496 | if (error instanceof DBusInvokeError) {
|
497 | if (RESOLVED_PERMISSIONS_ERRORS.includes(error.errorName)) {
|
498 | error.message = `Permissions issue. See https://homebridge.io/w/mDNS-Options for more info. ${error.message}`;
|
499 | }
|
500 | }
|
501 | throw error;
|
502 | }
|
503 | }
|
504 | async updateAdvertisement(silent) {
|
505 | if (!this.bus) {
|
506 | throw new Error("Tried to update systemd-resolved advertisement on a destroyed advertiser!");
|
507 | }
|
508 | debug("Updating txt record (txt: %o, silent: %d)", CiaoAdvertiser.createTxt(this.accessoryInfo, this.setupHash), silent);
|
509 |
|
510 | await this.stopAdvertising();
|
511 | await this.startAdvertising();
|
512 | }
|
513 | async stopAdvertising() {
|
514 | if (!this.bus) {
|
515 | throw new Error("Tried to destroy systemd-resolved advertisement on a destroyed advertiser!");
|
516 | }
|
517 | if (this.path) {
|
518 | try {
|
519 | await ResolvedAdvertiser.managerInvoke(this.bus, "UnregisterService", {
|
520 | body: [this.path],
|
521 | signature: "o",
|
522 | });
|
523 | }
|
524 | catch (error) {
|
525 |
|
526 | debug("Destroying systemd-resolved advertisement failed: " + error);
|
527 | }
|
528 | this.path = undefined;
|
529 | }
|
530 | }
|
531 | async destroy() {
|
532 | if (!this.bus) {
|
533 | throw new Error("Tried to destroy systemd-resolved advertisement on a destroyed advertiser!");
|
534 | }
|
535 | await this.stopAdvertising();
|
536 | this.bus.connection.stream.destroy();
|
537 | this.bus = undefined;
|
538 | }
|
539 | static async isAvailable() {
|
540 | const bus = dbus_native_1.default.systemBus();
|
541 | try {
|
542 | try {
|
543 | await messageBusConnectionResult(bus);
|
544 | }
|
545 | catch (error) {
|
546 | debug("systemd-resolved/DBus classified unavailable due to missing dbus interface!");
|
547 | return false;
|
548 | }
|
549 | try {
|
550 |
|
551 | await this.managerInvoke(bus, "ResolveHostname", {
|
552 | body: [0, "127.0.0.1", 0, 0],
|
553 | signature: "isit",
|
554 | });
|
555 | debug("Detected systemd-resolved over DBus interface running version.");
|
556 | }
|
557 | catch (error) {
|
558 | debug("systemd-resolved/DBus classified unavailable due to missing systemd-resolved interface!");
|
559 | return false;
|
560 | }
|
561 | try {
|
562 | const mdnsStatus = await this.resolvedInvoke(bus, "org.freedesktop.DBus.Properties", "Get", {
|
563 | body: ["org.freedesktop.resolve1.Manager", "MulticastDNS"],
|
564 | signature: "ss",
|
565 | });
|
566 | if (mdnsStatus[0][0].type !== "s") {
|
567 | throw new Error("Invalid type for MulticastDNS");
|
568 | }
|
569 | if (mdnsStatus[1][0] !== "yes") {
|
570 | debug("systemd-resolved/DBus classified unavailable because MulticastDNS is not enabled!");
|
571 | return false;
|
572 | }
|
573 | }
|
574 | catch (error) {
|
575 | debug("systemd-resolved/DBus classified unavailable due to failure checking system status: " + error);
|
576 | return false;
|
577 | }
|
578 | return true;
|
579 | }
|
580 | finally {
|
581 | bus.connection.stream.destroy();
|
582 | }
|
583 | }
|
584 |
|
585 | static resolvedInvoke(bus, dbusInterface, member, others) {
|
586 | return dbusInvoke(bus, "org.freedesktop.resolve1", "/org/freedesktop/resolve1", dbusInterface, member, others);
|
587 | }
|
588 |
|
589 | static managerInvoke(bus, member, others) {
|
590 | return this.resolvedInvoke(bus, "org.freedesktop.resolve1.Manager", member, others);
|
591 | }
|
592 | }
|
593 | exports.ResolvedAdvertiser = ResolvedAdvertiser;
|
594 |
|
\ | No newline at end of file |