Giter Club home page Giter Club logo

node-dbus-next's Introduction

dbus-next

The next great DBus library for NodeJS.

Documentation

Chat

About

dbus-next is a fully featured high level library for DBus geared primarily towards integration of applications into Linux desktop and mobile environments.

Desktop application developers can use this library for integrating their applications into desktop environments by implementing common DBus standard interfaces or creating custom plugin interfaces.

Desktop users can use this library to create their own scripts and utilities to interact with those interfaces for customization of their desktop environment.

Node Compatibility

As of now, dbus-next targets the latest features of JavaScript. The earliest version supported is 6.3.0. However, the library uses BigInt by default for the long integer types which was introduced in 10.8.0. If you need to support versions earlier than this, set BigInt compatibility mode. This will configure the library to use JSBI as a polyfill for long types.

const dbus = require('dbus-next');
dbus.setBigIntCompat(true);

The Client Interface

You can get a proxy object for a name on the bus with the bus.getProxyObject() function, passing the name and the path. The proxy object contains introspection data about the object including a list of nodes and interfaces. You can get an interface with the object.getInterface() function passing the name of the interface.

The interface object has methods you can call that correspond to the methods in the introspection data. Pass normal JavaScript objects to the parameters of the function and they will automatically be converted into the advertised DBus type. However, you must use the Variant class to represent DBus variants.

Methods will similarly return JavaScript objects converted from the advertised DBus type, with the Variant class used to represent returned variants. If the method returns multiple values, they will be returned within an array.

The interface object is an event emitter that will emit the name of a signal when it is emitted on the bus. Arguments to the callback should correspond to the arguments of the signal.

This is a brief example of using a proxy object with the MPRIS media player interface.

let dbus = require('dbus-next');
let bus = dbus.sessionBus();
let Variant = dbus.Variant;

// getting an object introspects it on the bus and creates the interfaces
let obj = await bus.getProxyObject('org.mpris.MediaPlayer2.vlc', '/org/mpris/MediaPlayer2');

// the interfaces are the primary way of interacting with objects on the bus
let player = obj.getInterface('org.mpris.MediaPlayer2.Player');
let properties = obj.getInterface('org.freedesktop.DBus.Properties');

// call methods on the interface
await player.Play()

// get properties with the properties interface (this returns a variant)
let volumeVariant = await properties.Get('org.mpris.MediaPlayer2.Player', 'Volume');
console.log('current volume: ' + volumeVariant.value);

// set properties with the properties interface using a variant
await properties.Set('org.mpris.MediaPlayer2.Player', 'Volume', new Variant('d', volumeVariant.value + 0.05));

// listen to signals
properties.on('PropertiesChanged', (iface, changed, invalidated) => {
  for (let prop of Object.keys(changed)) {
    console.log(`property changed: ${prop}`);
  }
});

For a complete example, see the MPRIS client example which can be used to control media players on the command line.

The Service Interface

You can use the Interface class to define your interfaces. This interfaces uses the proposed decorators syntax which is not yet part of the ECMAScript standard, but should be included one day. Unfortunately, you'll need a Babel plugin to make this code work for now.

let dbus = require('dbus-next');
let Variant = dbus.Variant;

let {
  Interface, property, method, signal, DBusError,
  ACCESS_READ, ACCESS_WRITE, ACCESS_READWRITE
} = dbus.interface;

let bus = dbus.sessionBus();

class ExampleInterface extends Interface {
  @property({signature: 's', access: ACCESS_READWRITE})
  SimpleProperty = 'foo';

  _MapProperty = {
    'foo': new Variant('s', 'bar'),
    'bat': new Variant('i', 53)
  };

  @property({signature: 'a{sv}'})
  get MapProperty() {
    return this._MapProperty;
  }

  set MapProperty(value) {
    this._MapProperty = value;

    Interface.emitPropertiesChanged(this, {
      MapProperty: value
    });
  }

  @method({inSignature: 's', outSignature: 's'})
  Echo(what) {
    return what;
  }

  @method({inSignature: 'ss', outSignature: 'vv'})
  ReturnsMultiple(what, what2) {
    return [
      new Variant('s', what),
      new Variant('s', what2)
    ];
  }

  @method({inSignature: '', outSignature: ''})
  ThrowsError() {
    // the error is returned to the client
    throw new DBusError('org.test.iface.Error', 'something went wrong');
  }

  @method({inSignature: '', outSignature: '', noReply: true})
  NoReply() {
    // by setting noReply to true, dbus-next will NOT send a return reply through dbus 
    // after the method is called.
  }

  @signal({signature: 's'})
  HelloWorld(value) {
    return value;
  }

  @signal({signature: 'ss'})
  SignalMultiple(x) {
    return [
      'hello',
      'world'
    ];
  }
}

let example = new ExampleInterface('org.test.iface');

setTimeout(() => {
  // emit the HelloWorld signal by calling the method with the parameters to
  // send to the listeners
  example.HelloWorld('hello');
}, 500);

async function main() {
  // make a request for the name on the bus
  await bus.requestName('org.test.name');
  // export the interface on the path
  bus.export('/org/test/path', example);
}

main().catch((err) => {
  console.log('Error: ' + err);
});

Interfaces extend the Interface class. Declare service methods, properties, and signals with the decorators provided from the library. You can optionally request a name on the bus with bus.requestName() so clients have a well-known name to connect to. Then call bus.export() with the path and interface to expose this interface on the bus.

Methods are called when a DBus client calls that method on the server. Properties can be gotten and set with the org.freedesktop.DBus.Properties interface and are included in the introspection xml.

To emit a signal, just call the method marked with the signal decorator and the signal will be emitted with the returned value.

If you have an interface xml description, which can be gotten from the org.freedesktop.DBus.Introspect method on an exported interface, you can generate dbus-next JavaScript classes from the xml file with the bin/generate-interfaces.js utility.

The Low-Level Interface

The low-level interface can be used to interact with messages directly. Create new messages with the Message class to be sent on the bus as method calls, signals, method returns, or errors. Method calls can be called with the call() method of the MessageBus to await a reply and send() can be use for messages that don't expect a reply.

let dbus = require('dbus-next');
let Message = dbus.Message;

let bus = dbus.sessionBus();

// send a method call to list the names on the bus
let methodCall = new Message({
  destination: 'org.freedesktop.DBus',
  path: '/org/freedesktop/DBus',
  interface: 'org.freedesktop.DBus',
  member: 'ListNames'
});

let reply = await bus.call(methodCall);
console.log('names on the bus: ', reply.body[0]);

// add a custom handler for a particular method
bus.addMethodHandler((msg) => {
  if (msg.path === '/org/test/path' &&
      msg.interface === 'org.test.interface'
      && msg.member === 'SomeMethod') {
    // handle the method by sending a reply
    let someMethodReply = Message.newMethodReturn(msg, 's', ['hello']);
    bus.send(someMethodReply);
    return true;
  }
});

// listen to any messages that are sent to the bus
bus.on('message', (msg) => {
  console.log('got a message: ', msg);
});

For a complete example of how to use the low-level interface to send messages, see the dbus-next-send.js script in the bin directory.

The Type System

Values that are sent or received over the message bus always have an associated signature that specifies the types of those values. For the high-level client and service, these signatures are specified in XML data which is advertised in a standard DBus interface. The high-level client dynamically creates classes based on this introspection data with methods and signals with arguments based on the type signature. The high-level service does the inverse by introspecting the class to create the introspection XML data which is advertised on the bus for clients.

Each code in the signature is mapped to a JavaScript type as shown in the table below.

Name Code JS Type Notes
BYTE y number
BOOLEAN b boolean
INT16 n number
UINT16 q number
INT32 i number
UINT32 u number
INT64 x BigInt Use dbus.setBigIntCompat(true) to use JSBI
UINT64 t BigInt Use dbus.setBigIntCompat(true) to use JSBI
DOUBLE d number
STRING s string
OBJECT_PATH o string Must be a valid object path
SIGNATURE g string Must be a valid signature
UNIX_FD h number Must be a valid unix file descriptor. e.g. from fs.open()
ARRAY a Array Must be followed by a complete type which specifies the child type
STRUCT ( Array Types in the JS Array must match the types between the parens
VARIANT v Variant This class is provided by the library.
DICT_ENTRY { Object Must be included in an array type to be an object.

Unix file descriptors are only supported on abstract or domain (path) sockets. This is the default for both system and session bus. When sending a file descriptor it must be kept open until the message is delivered. When receiving a file descriptor the application is responsible for closing it.

The types a, (, v, and { are container types that hold other values. Examples of container types and JavaScript examples are in the table below.

Signature Example Notes
(su) [ 'foo', 5 ] Each element in the array must match the corresponding type of the struct member.
as [ 'foo', 'bar' ] The child type comes immediately after the a. The array can have any number of elements, but they all must match the child type.
a{su} { 'foo': 5 } An "array of dict entries" is represented by an Object. The type after { is the key type and the type before the } is the value type.
ay Buffer.from([0x62, 0x75, 0x66]) Special case: an array of bytes is represented by a Node Buffer.
v new Variant('s', 'hello') Signature must be a single type. Value may be a container type.
(asv) [ ['foo'], new Variant('s', 'bar') ] Containers may be nested.

For more information on the DBus type system, see the specification.

Negotiating Unix File Descriptors

To support negotiating Unix file descriptors (DBus type h), set negotiateUnixFd to true in the message bus constructor options. The value of any type h in messages sent or received should be the file descriptor itself. You are responsible for closing any file descriptor sent or received by the bus.

Contributing

Contributions are welcome. Development happens on Github.

Similar Projects

dbus-next is a fork of dbus-native library. While this library is great, it has many bugs which I don't think can be fixed without completely redesigning the user API. Another library exists node-dbus which is similar, but also not provide enough features to create full-featured DBus services.

Copyright

You can use this code under an MIT license (see LICENSE).

ยฉ 2012, Andrey Sidorov

ยฉ 2018, Tony Crisci

node-dbus-next's People

Contributors

acrisci avatar cerebrumy avatar dependabot[bot] avatar egasimus avatar eventualbuddha avatar f3nrir92 avatar ffissore avatar figueredo avatar gcampax avatar goldenice avatar greenkeeper[bot] avatar ivshti avatar jetforme avatar jumplink avatar lesha1201 avatar martenjacobs avatar mspanc avatar mvduin avatar niels-be avatar pabigot avatar pimterry avatar pztrick avatar sandeepmistry avatar sbender9 avatar sdrik avatar sidorares avatar swanav avatar timbertson avatar wiktor-k avatar yitzchak avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

node-dbus-next's Issues

Remove the old interfaces

The library has diverged so significantly from dbus-native that I don't believe it can be used as a drop-in replacement as a path to upgrade anymore. I'm not testing the old interfaces and they are probably all broken.

Remove the old high-level interfaces.

Make all the low-level interfaces private and work on making them public at a later time if that is worthwhile in the future.

Typescript support

Hi. Thank you for you work. Keep it going.
But i'd like to know do you plan to add typescript support for your lib?

Support earlier node versions

Try to support earlier node versions by hiding new language features for versions that don't support them.

@CyDragon80 proposed a solution here: sidorares/dbus-native#251 (comment)

function getNodeMajor()
{
    var reg = /^[^\d]*(\d+)/;
    var temp = reg.exec(process.version);
    return (temp==null||temp[1]==null?0:parseInt(temp[1]));
}
module.exports.NodeMajor = getNodeMajor(); // only need to compute once
. . .
if (module.exports.NodeMajor >= 7)
{
    // new feature just for 7+
    const OptionalBit = require('optional-bit'); // module containing newer language features
    module.exports.newThing = OptionalBit.newThing; // new package feature export
    // etc etc
}
else
{
    // fallback if any? or leave new stuff undefined?
    module.exports.newThing = null; // ?
    module.exports.newThing = function() { throw new Error('Feature needs Node 7+')}; // ?
}

Expose low-level api

A low-level api would allow clients to send and receive arbitrary messages on the bus. This api would be needed for implementing dbus-send or dbus-monitor with this library.

let serial = bus.serial();
let msg = {
  type: METHOD_CALL,
  serial: serial,
  interface: 'org.test.interface',
  destination: 'org.test.name',
  member: 'SomeMethod',
  signature: 's',
  body: 'hello world'
};

bus.sendMessage(msg);

bus.on('message', (msg) => {
  if (msg.replySerial === serial) {
    // handle reply
  }
});

Change member name in options

Right now the name of the method, property, or signal must be the same as the name of the function.

Provide an option to set the name from the decorator.

@method({ name: "SomeName" })
someOtherName() {
  // will appear on the bus to be named "SomeName"
}

Debug logging

Provide debug logging to make issues on the bug tracker easier to handle.

Decorators cause issues in other variants

Hi, Any chance you could provide non-decorator examples of usage?

I love that you are using new syntax, but currently decorators are unusable in other variants like Coffeescript, Lightscript, Typescript etc..

Also expecting users to run Babel on the Node.js side requires a lot of overhead that will keep people from using your package. Unless of course you are prepared to support explaining how to set up babel, node.js and get that running with the various compilers.

Can't monitor notifications

Im trying to monitor notifications but it stops listening after the first notification.

var dbus = require('dbus-next')
const bus = dbus.sessionBus();

bus.getProxyObject('org.freedesktop.DBus', '/org/freedesktop/DBus').then((obj) => {
  let monitor = obj.getInterface('org.freedesktop.DBus.Monitoring');

  monitor.BecomeMonitor([
    "type='signal',member='Notify',path='/org/freedesktop/Notifications',interface='org.freedesktop.Notifications'",
    "type='method_call',member='Notify',path='/org/freedesktop/Notifications',interface='org.freedesktop.Notifications'",
    "type='method_return',member='Notify',path='/org/freedesktop/Notifications',interface='org.freedesktop.Notifications'",
    "type='error',member='Notify',path='/org/freedesktop/Notifications',interface='org.freedesktop.Notifications'"
  ], 0);


  bus.on('message', (msg) => {
    console.log(msg);
  });

});

Documentation

Add some docs and make it clear what the current supported api is.

  • server: interface
  • server: name
  • bus
  • client: proxy object
  • client: proxy interface
  • module toplevel

Package generators

@acrisci

Thank you for an awesomely simplified library to deal with dbus in node.

I've a request. Can you please include generators from bin directory in packaging?

getProxyObject doesn't always return

I'm trying to use this library as a way to consume signal-cli's bus service. However, dbus-next won't ever return after calling
let obj = await bus.getProxyObject('org.asamk.Signal', '/org/asamk/Signal');

I also run into a similar issue when running the following lower level code:

  let methodCall = new Message({
    destination: 'org.asamk.Signal',
    path: '/org/asamk/Signal',
    interface: 'org.asamk.Signal',
    member: 'sendMessage',
    signature: 'sass',
    body: ['MessageText2', [], '17876776273']
  });

  let reply = await bus.call(methodCall);
  console.log(reply) #never runs

D-Bus Proxy: addListener mulitple times (3) to the same event, emits the D-Bus signal mulitple times (3 * 3)

I have a simple D-Bus service. The service has only one signal called test and the service emits every ten seconds the current date.

import * as dbus from 'dbus-next';

class MyInterface extends dbus.interface.Interface {

    /* https://github.com/dbusjs/node-dbus-next/blob/master/lib/service/interface.js#L266 */
    static configureDBusIntf() {
        MyInterface.configureMembers({
            signals: {
                test: {
                    signature: 's'
                }
            }
        });
    }

    constructor() {
        super('org.test.iface');
    }

    test(msg: string) {
        return msg;
    }
}

async function main() {
    MyInterface.configureDBusIntf();
    const example = new MyInterface();

    const bus = dbus.sessionBus();
    await bus.requestName('org.test.test', 0);
    bus.export('/org/test/test', example);

    setInterval(
        () => {
            const date = new Date();
            console.log(date);
            example.test(date.toUTCString());
        },
        10000
    );
}

main().catch((err) => {
    console.log('Error:' + err);
});

/*
$ node dist/dbus_service.js 
2021-02-16T00:17:52.457Z
2021-02-16T00:18:02.469Z
2021-02-16T00:18:12.480Z
*/

The client adds a listener to the test D-Bus signal three times => Instead of receiving the signal three times the client receives the signal nine times!

import * as dbus from 'dbus-next';

async function main() {

    const log1 = (str: string) => { console.log('1  ', str) };
    const log2 = (str: string) => { console.log(' 2 ', str) };
    const log3 = (str: string) => { console.log('  3', str) };

    const bus = dbus.sessionBus();
    const obj = await bus.getProxyObject('org.test.test', '/org/test/test');
    const testIface = obj.getInterface('org.test.iface');
    testIface.on('test', log1);
    testIface.on('test', log2);
    testIface.on('test', log3);
}

main().catch((err) => {
    console.log('Error:' + err);
});

/*
$ node dist/dbus_client.js
1   Tue, 16 Feb 2021 00:17:52 GMT
 2  Tue, 16 Feb 2021 00:17:52 GMT
  3 Tue, 16 Feb 2021 00:17:52 GMT
1   Tue, 16 Feb 2021 00:17:52 GMT
 2  Tue, 16 Feb 2021 00:17:52 GMT
  3 Tue, 16 Feb 2021 00:17:52 GMT
1   Tue, 16 Feb 2021 00:17:52 GMT
 2  Tue, 16 Feb 2021 00:17:52 GMT
  3 Tue, 16 Feb 2021 00:17:52 GMT

1   Tue, 16 Feb 2021 00:18:02 GMT
 2  Tue, 16 Feb 2021 00:18:02 GMT
  3 Tue, 16 Feb 2021 00:18:02 GMT
1   Tue, 16 Feb 2021 00:18:02 GMT
 2  Tue, 16 Feb 2021 00:18:02 GMT
  3 Tue, 16 Feb 2021 00:18:02 GMT
1   Tue, 16 Feb 2021 00:18:02 GMT
 2  Tue, 16 Feb 2021 00:18:02 GMT
  3 Tue, 16 Feb 2021 00:18:02 GMT

1   Tue, 16 Feb 2021 00:18:12 GMT
 2  Tue, 16 Feb 2021 00:18:12 GMT
  3 Tue, 16 Feb 2021 00:18:12 GMT
1   Tue, 16 Feb 2021 00:18:12 GMT
 2  Tue, 16 Feb 2021 00:18:12 GMT
  3 Tue, 16 Feb 2021 00:18:12 GMT
1   Tue, 16 Feb 2021 00:18:12 GMT
 2  Tue, 16 Feb 2021 00:18:12 GMT
  3 Tue, 16 Feb 2021 00:18:12 GMT
*/

I found a fix for this issue but maybe not the best: Simply check the amount of listeners

lib/client/proxy-interface.js

...
    this.on('removeListener', (eventName, listener) => {
      // FIX
      if (this.listeners(eventName).length > 0) {
        return;
      }

      const [signal, detailedEvent] = getEventDetails(eventName);

      if (!signal) {
        return;
      }

      if (this.$object.bus._connection.stream.writable) {
        this.$object.bus._removeMatch(this._signalMatchRuleString(eventName))
          .catch(error => {
            this.$object.bus.emit('error', error);
          });
      }
      this.$object.bus._signals.removeListener(detailedEvent, this._getEventListener(signal));
    });

    this.on('newListener', (eventName, listener) => {
      // FIX
      if (this.listeners(eventName).length > 0) {
        return;
      }

      const [signal, detailedEvent] = getEventDetails(eventName);

      if (!signal) {
        return;
      }

      this.$object.bus._addMatch(this._signalMatchRuleString(eventName))
        .catch(error => {
          this.$object.bus.emit('error', error);
        });
      this.$object.bus._signals.on(detailedEvent, this._getEventListener(signal));
    });
  }
...

Bug? Process not terminating

Hey,

if I initialize a system or session bus, the program won't terminate.

// example.js
'use strict' // happens without strict mode as well

const dbus = require('dbus-next')

const bus = dbus.systemBus() // happens with dbus.sessionBus() as well

When I run it on the terminal like this, it will run forever:

$ node example.js

I have to use Strg+C to terminate it.

Those are the versions I'm using:

Ubuntu: 20.10
node:  14.16.0
dbus-next: ^0.9.2

As I couldn't find any information that dbus needs to be closed/terminated/unset in some way, I assume that this is a bug.

I started to use dbus-next just a couple of days ago (when 0.9.2 was already released) but I still think that I didn't have this issue yesterday :-P But I'm not certain and I have no idea what could have introduced the issue.

Any ideas what's going on here?

Cheers
Fred

Additional interfaces created with bus.export()

Hello, Please can you help me to understand this issue I am having when attempting to export multiple interfaces in a single JavaScript App. I am finding in the case where two or more bus.export() are used, investigation of the resulting D-BUS structure in d-feet shows that multiple interfaces have been exported/created for each bus.export(). For example in this simplified case:

const EventEmitter = require ('events');
const dbus = require('dbus-next');
const {Interface} = dbus.interface;

class Iface01 extends Interface {
    constructor() { 
      super('org.test.Interface1'); 
    }
}
  
class Iface02 extends Interface {
    constructor() { 
      super('org.test.Interface2');
    }
}

var bus = dbus.systemBus(); 
interface1 = new Iface01();
interface2 = new Iface02();

async function main() {
 await bus.requestName('org.test.services');
 bus.export('/org/test/path1', interface1); 
 bus.export('/org/test/path2', interface2);
}

main().catch(console.log);

the d-feet output looks like this
InkedexportIssue02

where it appears that each of the two interfaces has been created four times.

further observations are:

  • the issue on ly occurs when multiple bus.export() are used.
  • if the two interfaces were to be exported to the same path, then the repeating does not occur.
  • the number or replications is proportional to the number of / in the path for the given interface export.

I am trying to create the interface structure needed to create a bluetooth GATT application structure as set out in the last section of https://git.kernel.org/pub/scm/bluetooth/bluez.git/tree/doc/gatt-api.txt

However am struggling to get over this apparent issue. Thanks in advance for any advice.

happy to investigate further and report given any direction.
Regards, Simon

Type 'x' (int64) don't come as Long.js Long types

Methods with inSignature: 'x' should be required to give parameters of Long.js Long types. Instead they come in as truncated js numbers.

Return values are not affected (they are already required to be Long).

Missing direct connection

I cant find direct connecti functionality which is need for e.g 'org.PulseAudio.Core1.Device'

const pulseServerClient = dbusNative.createClient({
socket: unixSocketPath,
direct: true,
});

Get rid of the Name class

The api assumes that interfaces are exported on names when they are not. They are exported on the bus directly. Names are not like namespaces for interfaces like I originally thought. They are just an alias for the unique name.

Allow async execution of service methods

Correct me if I am wrong but I did not find a way to archive async execution in a Service function.
There is neither a callback nor can I return a Promise in a Service function.
So all execution inside a method call needs to be synchronous.
This breaks for example if you want to do a database lookup in response the the method call.

Consider following example:

class BluezAgent {
  @method({inSignatur: "os", outSignatur: "b"})
  RequestConfirmation(device, passkey) {
    // check if the key is in the database
    // this is an async operation that returns a promise
    const res = database.isKeyValid(passkey);
    // call a callback once the key is checked
    res.then(callback);
    // or return res as a promise
    return res;
  }
}

Implementing the promise return could be as easy as adding an await to the handler function call. See:

result = method.fn.apply(iface, msg.body);

node event listener warning

hi, that's a great library :)...but i have a problem using it :(
i'm wasn't able to find a method to remove the listeners from the internal interfaces of the library .
i'm working on an app that use bluez and bluetooth , and client can connect/disconnect very rapidly.
every time i connect a device a proxy obj is created and i read some property(mac address,name ecc ecc) , if the device is of a specific type, i put a call .on('PropertiesChanged') on the interface that i need to check.
after a few devices connection/disconnection , node tells a warn saying that i am adding too mutch event listeners for the same event ('PropertiesChanged event) but every time a device is removed i call removeListeners method on the all interfaces that listen to this event and i've got no errors ,also i destroy the interfaces with
iface=null
so ... there is a right way to remove obj and interfaces? what iam i doing wrong?

Property getter executed statically when using configureMembers

Hi,

Property getters seem to be executed as soon as you call configureMembers. This can result in errors as instance state is not initialized yet.

Running the below script with node results in a TypeError, since the private property is initialized in the constructor.

const dbus = require('dbus-next')

const { Interface } = dbus.interface

class TestInterface extends Interface {
   constructor() {
     this._myPrivateProperty = 'HELLO'
   }

   get myProperty() {
     console.log('--> get myProperty')
     return this._myPrivateProperty.toLowerCase()
   }
}

TestInterface.configureMembers({
  properties: {
    myProperty: { signature: 's' }
  }
})

Terminal output:

--> get myProperty
/private/tmp/test-dbus/index.js:12
     return this._myPrivateProperty.toLowerCase()
                                    ^

TypeError: Cannot read property 'toLowerCase' of undefined
    at Interface.get myProperty [as myProperty] (/private/tmp/test-dbus/index.js:12:37)
    at applyDecorator (/private/tmp/test-dbus/node_modules/dbus-next/lib/service/interface.js:314:32)
    at Function.configureMembers (/private/tmp/test-dbus/node_modules/dbus-next/lib/service/interface.js:323:7)
    at Object.<anonymous> (/private/tmp/test-dbus/index.js:16:15)
    at Module._compile (internal/modules/cjs/loader.js:1200:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1220:10)
    at Module.load (internal/modules/cjs/loader.js:1049:32)
    at Function.Module._load (internal/modules/cjs/loader.js:937:14)
    at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:71:12)
    at internal/main/run_main_module.js:17:47

The issue seems to originate here:

value: this.prototype[key]

Type 'aay' misses special case of Buffer

In this method:

    <method name="ResolveService">
      <arg name="interface" type="i" direction="in"/>
      <arg name="protocol" type="i" direction="in"/>
      <arg name="name" type="s" direction="in"/>
      <arg name="type" type="s" direction="in"/>
      <arg name="domain" type="s" direction="in"/>
      <arg name="aprotocol" type="i" direction="in"/>
      <arg name="flags" type="u" direction="in"/>

      <arg name="interface" type="i" direction="out"/>
      <arg name="protocol" type="i" direction="out"/>
      <arg name="name" type="s" direction="out"/>
      <arg name="type" type="s" direction="out"/>
      <arg name="domain" type="s" direction="out"/>
      <arg name="host" type="s" direction="out"/>
      <arg name="aprotocol" type="i" direction="out"/>
      <arg name="address" type="s" direction="out"/>
      <arg name="port" type="q" direction="out"/>
      <arg name="txt" type="aay" direction="out"/>
      <arg name="flags" type="u" direction="out"/>
    </method>

The txt out parameter has a type of aay, so using a client proxy object I would expect it to be unmarshaled as and Array of Buffer objects since ay is treated as a special type (according to the README). However it seems to be unmarshaled as an Array of Array objects.

Dynamically disable interface members

Some properties, methods, or signals might be optional. Mpris for instance has optional properties. Optional members should be omitted from introspection xml and ignored when they are gotten/set/called.

Maybe like this:

iface.disableProperty('OptionalProp');

Or maybe like this for more extensibility:

iface.getPropertyInfo('OptionalProp').disabled = true;

NetworkManager

how can i listen for messages when wifi and LAN adapters are connected/disconnect?

i already tried:

// add a custom handler for a particular method bus.addMethodHandler((msg) => { if (String(msg.path).startsWith('/org/freedesktop/NetworkManager')) { console.log(msg); } });

bus.on('message', (msg) => { console.log('got a message: ', msg); });

but no success.

Message interface in typescript doesn't match up with class.

Message interface here:

export interface Message {

In the help:

let methodCall = new Message({
  destination: 'org.freedesktop.DBus',
  path: '/org/freedesktop/DBus',
  interface: 'org.freedesktop.DBus',
  member: 'ListNames'
});

Message is exposed as interface in typescript file, so I have to do this in order to get the class.

import dbus from "dbus-next";
let Message = (<any>dbus).Message;

Looks like an unintended mismatch.

Can't create NotifyingProperty without using decorators

So for simplicity sake I'm not using decorators. I created few services without properties no problem. But when i wanted define notifying property I'm faced with weird problem.

throw new Error(got properties changed with unknown property: ${p});
        ^

Error: got properties changed with unknown property: Speed
    at Function.emitPropertiesChanged (/home/kryq/Projects/nodejs/dbus_service_test/node_modules/dbus-next/lib/service/interface.js:382:15)
class SpeedListenerInterface extends Interface {
  constructor() {
    super('pl.kryq.SpeedListener1');
  }

  _speed = 0.0;

  get Speed() {
    return this._speed;
  }
  set Speed(value) {
    this._speed = value;

    Interface.emitPropertiesChanged(this, {
      Speed: value
    }, ['invalid']);
  }

  SSpeed(val) {
    return val;
  }
}

SpeedListenerInterface.configureMembers({
  properties: {
    Speed: {
      name: 'lel',
      signature: 'd',
      access: 'read'
    }
  },
  /*methods: {
    SetBrightness: {
      inSignature: 'u',
      outSignature: 'n'
    }
  },*/
  signals: {
    SSpeed: {
      signature: 'd'
    }
  }
});```

Overriding uid for tcp connections

Hi, I'm using node-dbus-next to connect to a remote dbus server from macOS. To successfully perform authentication, the correct uid (1000) must be passed with the EXTERNAL authentication handshake. However, the default user account id on macOS is 501. It would be really helpful if there would be some way of overriding the uid for authentication purposes, for example by passing an env var.

Clean up?

What's the proper way to clean up an interface? I'm using dbus-next (thank you, I appreciate it) for a NetworkManager implementation in a long running process. There is a lot of object turnover; when I set up an object I add a listener to 'PropertiesChanged' and when I destroy it I remove all listeners, but my process inevitably crashes on the following unhandled error:

Connection ":1.104" is not allowed to add more match rules (increase limits in configuration file if required; max_match_rules_per_connection=2048)

DBusError: Connection ":1.104" is not allowed to add more match rules (increase limits in configuration file if required; max_match_rules_per_connection=2048)
    at _methodReturnHandlers.(anonymous function) (/path/to/external/node_modules/dbus-next/lib/bus.js:326:27)
    at handleMessage (/path/to/external/node_modules/dbus-next/lib/bus.js:86:11)
    at EventEmitter.MessageBus.conn.on (/path/to/external/node_modules/dbus-next/lib/bus.js:135:9)
    at EventEmitter.emit (events.js:189:13)
    at /path/to/external/node_modules/dbus-next/lib/connection.js:112:14
    at Socket.<anonymous> (/path/to/external/node_modules/dbus-next/lib/message.js:55:9)

Edit/Appended: I use the same require('dbus-next').systemBus() instance for every object (AccessPoint,Connection,Device,etc), would it be recommended to use a new instance in each case?

JSBI.BigInt is not a function /node_modules/dbus-next/lib/marshallers.js?:255:24

Just importing dbus-next in a new javascript file causes this error:

DBus.js :

import { sessionBus, } from 'dbus-next';

image

Environment Info:

  System:
    OS: Linux 5.3 Ubuntu 18.04.4 LTS (Bionic Beaver)
    CPU: (8) x64 Intel(R) Core(TM) i7-4790K CPU @ 4.00GHz
  Binaries:
    Node: 14.3.0 - ~/.nvm/versions/node/v14.3.0/bin/node
    Yarn: 1.22.4 - /usr/bin/yarn
    npm: 6.14.5 - ~/.nvm/versions/node/v14.3.0/bin/npm
  Browsers:
    Chrome: 83.0.4103.116
    Firefox: 78.0.1
  npmGlobalPackages:
    @vue/cli: 4.4.4

"dbus-next": "^0.8.2"

How to solve the problem?

Marco

Include bin/ in package

It would be nice to include the bin/ directory in the package so that the tools are available without having to clone the source code repository.

Add reply info to DBusError

When a DBusError is thrown, it should contain the error reply when possible. This change should affect the high-level client and the low-level client.

Add access to message in method handler

It would be nice to have an access to a message in method handler. Currently, we only have message body but sometimes you need to know, for example, who is sending the message (sender). It can be added without a breaking change by adding it as a last argument:

class ExampleInterface extends Interface {
    // ....

    @method({inSignature: 's', outSignature: 's'})
    Echo(what, message) {
        return what;
    }
    
    // Or it can be an object that contains any other additional info
    @method({inSignature: 's', outSignature: 's'})
    Echo(what, { message }) {
        return what;
    }
}

Get session bus address from X11

Right now to connect to a session bus, it is required that the DBUS_SESSION_BUS_ADDRESS variable is set. I know that some custom session managers like xinit will not set these environment variables automatically, and there might be some other session managers that don't either.

There is a section in the DBus specification for getting the address from a window property on X11 that is not currently followed by this library.

This is stubbed out in lib/address-x11.js but not implemented.

https://github.com/acrisci/node-dbus-next/blob/8a287c2a7995c785df8b2ac9622cd930a9def8e1/lib/address-x11.js#L7-L36

If this code can be made to work, it would require an optional dependency on the node-x11 library.

An alternative to this would be to look in $HOME/.dbus/session-bus for the session bus address based on the machine id and DISPLAY environment variable. This contains the location of the session bus that we need and might be sufficient to accomplishing the goal of not requiring the DBUS_SESSION_BUS_ADDRESS to be set in the environment. That should be tried first.

ref: martpie/museeks#93

Unable to pass numeric values as an object key.

There appears to currently be no way to use a numeric key for a dict variant.

I'm trying to add BlueZ manufacture data which requires the signature "a{qay}". E.g. {0xFFFF, [0x70, 0x74]}

When attempting to pass a variant of {0xFFFF, [0x70, 0x74]}, dbus-next converts it to { '65535': [ 112, 116 ] }. Where Object.keys converts 0xFFF to a string '65535'. Is there anyway to allow non-string keys for dict entries? (Possibly parsing strings for numeric signature types or allowing arrays in addition to objects. e.g. [[Key,Value],[Key,[Key,Value]]] for {Key:Value, Key:{Key:Value}}.)

Thanks

How can I get my custom service object registered on the system bus?

I'm looking for the way to register my custom service on D-Bus with dbus-next API. But I'm not getting it working.

What I did is:

  • Create a couple of classes inheriting dbus-next Interface
  • Invoke dbus.systemBus().export('/service/path', aboveObject) for each dbus-next Interface object to be exported

Then when I got a ProxyObject of the service path, I couldn't find anything in interfaces property of the ProxyObject. What am I missing?

# dbus.systemBus().getProxyObject(':1.2', '/service/path').then(x => console.log(x))
=>
ProxyObject {
  bus: MessageBus {
    _events: [Object: null prototype] {},
    _eventsCount: 0,
    _maxListeners: undefined,
    _builder: Builder { options: [Object] },
    _connection: EventEmitter {
      (snip)
    },
    _serial: 8,
    _methodReturnHandlers: {},
    _signals: EventEmitter {
      (snip)
    },
    _nameOwners: {
      'org.freedesktop.DBus': 'org.freedesktop.DBus',
      'org.bluez': ':1.428',
      'org.freedesktop.DBus.ObjectManager': 'org.freedesktop.DBus',
      ':1.2': ':1.2'
    },
    _methodHandlers: [ [Function] ],
    _serviceObjects: {  ======================> Exported paths are described here
      '/service/path': [ServiceObject],
      '/service/path/subpath': [ServiceObject],
      (snip) 
    }, 
  name: ':1.420',
    [Symbol(kCapture)]: false
  },
  name: ':1.2',
  path: '/service/path',
  interfaces: {},  ==========================> but no exported interfaces appear
  (snip) 
}

With node-dbus, it has dbus.registerService() to register a Service Object (not Interface). I'd say the Service Object registration function is required to register a set of D-Bus interfaces but I don't have any idea of the equivalent function in dbus-next.

Gracefully handle errors in services

Right now if a service throws an error that's not a DBusError, it crashes the service.

We should catch all errors and return a friendly message to the client that something went wrong.

How to avoid signal race condition when creating new ClientProxy object?

When calling getProxyObject() it optionally asynchronous calls the D-Bus introspection method and then asynchronously parses XML before resolving the promise. During this time, it is possible to receive signals for this object, but they are missed since we can't attach signal handlers yet (even if XML is supplied and introspection step is skipped, it takes too long).

Real-world case: I'm trying to use this library with Avahi. It has methods (e.g. ServiceBrowserNew) that create a new D-Bus object and return the object path. Then we take the path and create a new proxy object. However, these objects immediately start receiving signals (e.g. ItemNew) as can be seen using the dbus-monitor command line tool. However dbus-next misses these because the signals happen before we ever get a proxy object.

How can we avoid this race condition?

How to unmarshal results from GetAll()?

Hey,

this is some code I wrote to scan for available wireless access points on a given device and get their properties.

I can't guarantee that this example works as I stripped out some stuff to simplify it. But it should serve the purpose to illustrate how the result below is created.

'use strict'

// params:
const device_name = 'wlan0'

// config:
const dbusBaseName = 'org.freedesktop.NetworkManager'
const dbusBasePath = '/org/freedesktop/NetworkManager'
const dbusPropertiesName = 'org.freedesktop.DBus.Properties'

// setup dbus stuff:
const bus = dbus.systemBus()
const proxyObject = await this.bus.getProxyObject(dbusBaseName, dbusBasePath)
const propertiesInterface = proxyObject.getInterface(dbusPropertiesName)
const managerInterface = proxyObject.getInterface(dbusBaseName)

// get list of network interfaces on the given device:
const device_path = await managerInterface.GetDeviceByIpIface(device_name)
const deviceProxyObject = await this.bus.getProxyObject(this.dbusBaseName, device_path)
const deviceInterfaceNames = Object.keys(deviceProxyObject.interfaces)

// check if it has wireless capabilities:
if (deviceInterfaceNames.includes(this.dbusBaseName + '.Device.Wireless')) {
    // get the wireless interface:
    const deviceManagerInterface = deviceProxyObject.getInterface(dbusBaseName + '.Device.Wireless')

    // request a scan for available access points, then fetch the list:
    deviceManagerInterface.RequestScan({})
    const accessPointPaths = await new Promise(async (resolve, reject) => {
        const promiseScan = new Promise((resolve, reject) => {
            deviceManagerInterface.on('AccessPointAdded', (iface, changed, invalidated) => {
                resolve()
            })
        })

        const promiseTimeout = new Promise((resolve, reject) => {
            setTimeout(() => {
                resolve()
            }, 5000)
        })

        await Promise.race([promiseScan, promiseTimeout])
        
        resolve(deviceManagerInterface.GetAllAccessPoints())
    })

    // Get properties of access points (this is a loop, I picked one item as an example):
    const apProxyObject = await bus.getProxyObject(dbusBaseName, accessPointPaths[0])
    const apPropertiesInterface = apProxyObject.getInterface(dbusPropertiesName)
    const allProperties = await apPropertiesInterface.GetAll(dbusBaseName + '.AccessPoint')

    // print:
    console.log(allProperties)
}

It took me ages to get to this point because the docs neither DBus, nor NetworkManager, nor this library are very helpful if you don't already know how this stuff works.

The above code prints the following:

{
  Flags: Variant { signature: 'u', value: 1 },
  WpaFlags: Variant { signature: 'u', value: 0 },
  RsnFlags: Variant { signature: 'u', value: 392 },
  Ssid: Variant { signature: 'ay', value: <Buffer 46 52 49 54 5a 21 42 6f 78 20 47 61 73 74 7a 75 67 61 6e 67> },
  Frequency: Variant { signature: 'u', value: 5220 },
  HwAddress: Variant { signature: 's', value: 'E0:18:60:6F:50:46' },
  Mode: Variant { signature: 'u', value: 2 },
  MaxBitrate: Variant { signature: 'u', value: 540000 },
  Strength: Variant { signature: 'y', value: 78 },
  LastSeen: Variant { signature: 'i', value: 28070 }
}

I understand the signatures approach of DBus and I spent quite some time to figure out if there is any mechanism to unmarshal those values with some built-in functions of dbus-next. I found it in the code but I didn't figure out how to apply it. Do I have to load some function to unmarshal this. Or should there be any method I can use on Variants to get the value?

The result should be an object with the same structure as the above, but instead of the Variant I would like to have the unmarshalled values. Which function can I use to do this?

Is this even the right approach? Shouldn't this be way easier?

Thanks in advance,
Fred

Version 1.0.0 API stabalization

I'm pretty happy with how things are right now so I think I'm going to mark all the apis of the library as stable in the next release.

Missing support for selecting signals for specific interfaces at dbus level

When using python to access ofono, it is possible to listen to a specific signal and interface instance by specifying the signal name and path. dbus-next getInterface() only has a string for the interface name but it would be nice to also select the instance of the interface with a path.

Python example to listen for a specific modem:
systemBus.add_signal_receiver(someCallbackForSignalsToPhone, signal_name="CallAdded", bus_name="org.ofono", dbus_interface="org.ofono.VoiceCallManager", path="/hfp/org/bluez/hci0/dev_FC_D4_36_C0_2A_5A")

MPRIS example exposes incorrect validation

I was messing about with the MPRIS example on my KDE based desktop and discovered that KDE's browser intergration was casusing issues with this libary as its bus name is org.mpris.MediaPlayer2.plasma-browser-integration and is causing this error

Error: Invalid interface name: org.mpris.MediaPlayer2.plasma-browser-integration
    at assertInterfaceNameValid (/home/iloosley/node_modules/dbus-next/lib/validators.js:108:11)
    at new ProxyObject (/home/iloosley/node_modules/dbus-next/lib/client/proxy-object.js:35:5)
    at MessageBus.getProxyObject (/home/iloosley/node_modules/dbus-next/lib/bus.js:162:15)
    at main (/home/iloosley/mpris.js:111:23)
    at processTicksAndRejections (internal/process/task_queues.js:86:5)

image

This appears to be because the library is incorrectly validating the bus name as an interface. MPRIS player addresses are bus names, interfaces are things like

org.mpris.MediaPlayer2.Player
org.mpris.MediaPlayer2.TrackList
org.mpris.MediaPlayer2.Playlists

systemd service

I know that this is probably not the right place to ask, but I just created a small app which is checking a MediaPlayer for its status. The service is running great when manually started from the command line. But fails miserably when I try to create a systemd service to automatically start it when the system restarts.

Has anyone an idea how to tackle that issue?

Make the low level api private

The low-level api is a bit rough right now. It can't be public in the shape it's in.

Make the low level api private including the underlying dbus connection. Anything that doesn't have a use documented in the readme, examples, or test cases should be private.

If you have a use case for the low-level api, let me know and we'll work on exposing it in a better way.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.