dbus-native

repository·master·Indexed 42 days ago

https://github.com/sidorares/dbus-native

A pure JavaScript D-Bus protocol client and server for Node.js. It provides a portable implementation requiring no native addons, featuring support for ESM/CJS, async-iterable signals, ObjectManager integration, and a CLI for bus operations and TypeScript type generation. Version 0.15.2 includes updated type mappings using bigint for 64-bit integers and Buffer for byte arrays.

Tokens
43.6K
Snippets
143
Records
180
Agent score
53%

What's inside dbus-native

  1. Understand the positioning of dbus-native vs forks

    master

    As of the current roadmap, dbus-native is an active, maintained package and is not being deprecated. It has been modernized to reduce runtime dependencies and improve stability.

    Users should be aware of the following related packages:

    • @homebridge/dbus-native: A soft fork of this repository used by Homebridge. Many of its specific requirements (like abstract socket support or ARMv6 workarounds) have been integrated into the main dbus-native line.
    • dbus-next: An older fork/rewrite that has been largely dormant since 2022.

    dbus-native is currently the most modern and least dependency-heavy option among these three.

  2. Understand the E2E testing environment and coverage

    master

    The E2E testing environment uses a node:24-bookworm container to simulate a Linux desktop environment. It provides both a system bus and a session bus.

    Available Services

    BusServices
    systemDBus, UPower, Accounts, Avahi, NetworkManager, UDisks2, PolicyKit1
    sessionDBus, Notifications (dunst), portal.Desktop, portal.Documents, impl.portal.PermissionStore

    Available Clients for Interop Testing

    • gdbus (GLib)
    • busctl (systemd/sd-bus)
    • python3-dbus
    • dbus-send
    • dbus-monitor

    Limitations

    • systemd/logind/hostnamed: These require PID 1 and are not available in the standard container.
    • UNIX_FD: Passing file descriptors is not supported on Node.js (though it is supported in the Bun implementation).
    • Real Hardware: Services like UPower and UDisks2 run without hardware, so device trees will be shallow.
  3. Understand connection error handling and handlerError

    master

    The connection distinguishes between transport failures and application-level errors:

    1. error: Emitted on transport or protocol failures (e.g., malformed or oversized messages). These are unrecoverable, and the connection is destroyed.
    2. handlerError: Emitted when an exception is thrown inside one of your own message or signal event listeners. This is treated as an application bug; the connection remains usable. If no listener is present for this event, the exception is re-thrown asynchronously, following Node.js behavior.
  4. Instrument dbus-native with diagnostics_channel

    master

    You can monitor D-Bus activity using Node.js node:diagnostics_channel. This has zero overhead when no one is subscribed.

    Available channels:

    • dbus:message:send: Emits { message } for every message written.
    • dbus:message:receive: Emits { message } for every message read.
    • tracing:dbus:call:start: Emits the call context.
    • tracing:dbus:call:end: Emits the call context with result or error.
    • tracing:dbus:call:error: Emits the call context with error.

    The call context object is { destination, path, interface, member, signature, result?, error? }. The object is identical across start and end events, allowing it to be used as a Map key for timing calls.

    const dc = require('node:diagnostics_channel');
    dc.subscribe('tracing:dbus:call:error', ctx =>
      console.error(`${ctx.member} failed:`, ctx.error.message)
    );
  5. Use the `variants: 'wrap'` option for D-Bus Variants

    master
    By default, version 2.0 attempts to unwrap D-Bus variants into their underlying JavaScript types. If you need to preserve the type information (e.g., to distinguish between an integer and a variant containing an integer), use the variants: 'wrap' option. This will cause the library to return values as Variant objects.
  6. Passing File Descriptors (Unix FDs) over D-Bus

    master

    D-Bus allows passing file descriptors using the UNIX_FD type. In a message, the body contains a uint32 index (h) pointing to the array of descriptors provided in msg.fds.

    Platform Support

    • Bun: Works out of the box using bun:ffi. A connection under Bun automatically supports passing FDs (bus.connection.canPassFds will be true).
    • Node.js: Does not support passing file descriptors. Messages attempting to carry them will be refused with an error.

    Implementation Details

    • Ordering: Descriptors must arrive in order and must not arrive after the message bytes.
    • Batching: Messages carrying file descriptors are never batched; they flush the cork and travel individually.
    • Ownership: Received descriptors are live FDs in your process; you are responsible for closing them. Descriptors you send stay yours, but Bun's transport dups them, so you can close your copy immediately after the call returns.

    Custom Transports

    You can provide a custom transport via opts.stream. The transport must implement:

    • writeWithFds(bytes, fds): To send descriptors.
    • emit('fds', fds): To receive descriptors.
    • closeFds(fds) (optional): To close unclaimed descriptors.

    Sending an FD:

    // Sending
    bus.connection.message({
      destination: 'org.example.Sink',
      path: '/org/example/Sink',
      interface: 'org.example.Sink',
      member: 'Take',
      signature: 'sh',
      body: ['a name', 0], // 0 is the index into fds
      fds: [fd]
    });
    
    // Receiving
    bus.connection.on('message', msg => {
      if (msg.fds) console.log(msg.fds[msg.body[1]]);
    });
  7. Handle multiple output arguments from service methods

    master

    When a service method declares multiple output types in its interface descriptor (e.g., (si)), dbus-native now supports returning them as an array.

    Mapping rules:

    • Multiple arguments: Represented as an array of values in the reply body.
    • Single argument: Represented as the value itself.
    • Struct: Represented as a single value (the struct).
    • null: Represents no reply body.

    If a handler returns a shape that does not match the declared signature, the library will return an error reply naming the method and its declared signature, rather than throwing an unhandled rejection.

  8. Manage property access and notifications

    master

    Properties in dbus-native default to readwrite. You can explicitly set access to read or write.

    Property Access Enforcement

    readwritereadwrite
    Properties.Get✓AccessDenied✓
    Properties.SetPropertyReadOnly✓✓
    Properties.GetAllincludedomittedincluded

    Emitting Property Changes

    When using exportInterface (the positional form), Properties.Set automatically emits org.freedesktop.DBus.Properties.PropertiesChanged.

    If you are manually updating an implementation object, you must notify the bus yourself: bus.emitPropertiesChanged(path, iface.name, { PropertyName: newValue });

    To notify that a property has been invalidated (e.g., a write-only property or an expensive computation), use the invalidated argument: bus.emitPropertiesChanged(path, iface.name, {}, ['ExpensiveProperty']);

  9. Understand name validation rules

    master

    The library enforces D-Bus name validation rules on outgoing messages to prevent routing errors. Validation is applied to:

    • exportInterface (path, interface name, and all member names)
    • sendSignal
    • sendError
    • The o (object path) marshaller

    Key behaviors:

    • Validation Errors: The library throws an error if an invalid name is used. This is preferred over warnings because an invalid name results in a message that no peer can route.
    • Signals vs. Calls: Signals are validated before sending. Ordinary method calls are not validated locally; instead, invalid interface or member names will result in an error reply from the D-Bus daemon or the peer.
    • Incoming Names: The library does not validate incoming names to maintain interoperability with potentially non-compliant peers.
  10. Configure how values are read using `plainValues` and `variants`

    master

    The library provides configuration options to control how D-Bus messages are unmarshalled into JavaScript.

    plainValues (Boolean)

    Controls the shape of dictionaries and variants.

    • true (default): Variants read as the raw value; dicts read as plain objects.
    • false: Variants read as [signatureTree, [value]]; dicts read as arrays of pairs.

    variants (String: 'plain' | 'tree' | 'wrap')

    Controls how a variant (v) is returned, independent of the dict shape.

    • 'plain' (default): Returns the raw value. The signature is discarded.
    • 'tree': Returns [signatureTree, [value]]. Useful for inspection.
    • 'wrap': Returns a Variant object. This is the recommended shape for routers or services that need to inspect and then forward values, as the Variant can be written back to the bus without losing its original type information.
    // Example: Using 'wrap' to preserve type information for inspection/forwarding
    const bus = dbus.sessionBus({ variants: 'wrap' });
    
    const props = await iface.$readProp('Metadata');
    // props.Volume is a Variant object
    console.log(props.Volume.signature); // e.g., 'd'
    console.log(props.Volume.value);     // e.g., 0.5
    
    // You can pass this directly back to another service
    await sink.SetOptions(props);
  11. Use Callbacks or Promises for asynchronous methods

    master

    All asynchronous methods in dbus-native support both Node-style callbacks and Promises (thenables).

    Callbacks

    Pass a (err, result) callback as the last argument.

    Promises (Thenables)

    Omit the callback to receive a thenable. Note that the returned object is a thenable, not a Promise (instanceof Promise is false). This is a deliberate design to allow "fire-and-forget" calls; the underlying promise is only constructed lazily when you observe the outcome (e.g., via await or .then()). If you ignore the return value, no promise is constructed, preventing unhandled rejections.

    Resolution Logic

    Resolution depends on the number of values in the D-Bus reply body:

    • No values: Resolves to undefined.
    • One value: Resolves to the value.
    • Two or more values: Resolves to an array of values.

    Rejections always return a DBusError containing stack frames from where the call was made.

    // Using a callback
    bus.getId((err, id) => {
      if (err) throw err;
      console.log(id);
    });
    
    // Using await (Promise-style)
    const id = await bus.getId();
  12. Understand UNIX_FD (`h`) support and limitations

    master

    The library supports the D-Bus UNIX_FD (h) type, allowing the passing of arbitrary file descriptors (files, pipes, etc.) between processes.

    Current Support Status:

    • Bun: Full support is available via a built-in transport (lib/transport-bun.js) that uses bun:ffi to call sendmsg/recvmsg directly. This allows passing descriptors without external dependencies.
    • Node.js: Built-in support is currently unavailable because the Node.js net module does not expose an API for passing ancillary data (file descriptors) on Unix sockets.

    How to use it (Custom Transports): If you provide a custom transport that implements the necessary seam (stream.writeWithFds(bytes, fds) and an 'fds' event), you can use UNIX_FD even in Node.js.

    Key API properties for FD negotiation:

    • connection.canPassFds: Reports if the local side can pass descriptors.
    • connection.unixFdsAgreed: Reports if the peer has agreed to the NEGOTIATE_UNIX_FD protocol.
    • Messages containing FDs are never batched; ancillary data is attached to specific writes.