dbus-sdk
Version:
A Node.js SDK for interacting with DBus, enabling seamless service calling and exposure with TypeScript support
407 lines (315 loc) • 20.7 kB
Markdown
# DBus SDK API Documentation
This document provides a detailed overview of the `dbus-sdk` library's API, designed for interacting with DBus (Desktop
Bus) in Node.js environments with TypeScript support. The library enables seamless inter-process communication (IPC) on
Linux and Unix-like systems by offering a robust, type-safe interface for connecting to DBus, invoking methods, handling
signals, and defining custom services. Below, each class's purpose and functionality are explained, followed by how they
integrate to form a cohesive system for DBus interactions.
## Table of Contents
- [Overview](#overview)
- [Class: DBus](#class-dbus)
- [Class: DBusService](#class-dbusservice)
- [Class: DBusObject](#class-dbusobject)
- [Class: DBusInterface](#class-dbusinterface)
- [Class: LocalService](#class-localservice)
- [Class: LocalObject](#class-localobject)
- [Class: LocalInterface](#class-localinterface)
- [Integration and Usage](#integration-and-usage)
- [Examples](#examples)
## Overview
`dbus-sdk` is structured around a hierarchy of classes that mirror the DBus object model, facilitating both consumption
of existing DBus services and the creation of custom ones. The library handles low-level DBus protocol details (e.g.,
message encoding, type conversion) while providing a high-level, object-oriented API. The primary classes are divided
into two categories:
- **Client-Side Classes**: `DBus`, `DBusService`, `DBusObject`, and `DBusInterface` for interacting with remote DBus
services.
- **Server-Side Classes**: `LocalService`, `LocalObject`, and `LocalInterface` for defining and exposing custom DBus
services.
## Class: DBus
### Purpose
The `DBus` class is the central entry point for all DBus interactions. It manages the connection to a DBus bus (system
or session), handles low-level communication with the DBus daemon, and provides methods for invoking remote methods,
emitting signals, and managing services and signal subscriptions. Extending `EventEmitter`, it supports an event-driven
approach to handle connection status changes and incoming messages.
### Key Properties
- **`uniqueName`**: A string representing the unique name assigned by the DBus daemon (e.g., `:1.123`) after connection
initialization.
### Key Methods
- **`static async connect(opts: ConnectOpts): Promise<DBus>`**: Establishes a connection to a DBus bus using provided
options (e.g., socket path, TCP details). Returns an initialized `DBus` instance.
- **`async invoke(opts: InvokeOpts, noReply?: boolean): Promise<any[]> | void`**: Invokes a remote DBus method. Supports
both reply-expected (returns a Promise with response data) and no-reply modes (returns void).
- **`async getProperty(opts: GetPropertyValueOpts): Promise<any>`**: Retrieves a property value from a remote DBus
object using the `org.freedesktop.DBus.Properties` interface.
- **`async setProperty(opts: SetPropertyValueOpts): Promise<void>`**: Sets a property value on a remote DBus object
using the `org.freedesktop.DBus.Properties` interface.
- **`emitSignal(opts: EmitSignalOpts): void`**: Broadcasts a DBus signal to other processes with specified options (
e.g., object path, interface, signal name).
- **`reply(opts: ReplyOpts): void`**: Sends a reply to a method call, supporting both success (`METHOD_RETURN`) and
error (`ERROR`) responses.
- **`createSignalEmitter(opts: CreateSignalEmitterOpts): DBusSignalEmitter`**: Creates a signal emitter for subscribing
to specific DBus signals based on service, object path, and interface.
- **`on(eventName: string, listener: Function): this`**: Registers event listeners for various DBus events (e.g.,
`online`, `offline`, `NameOwnerChanged`) related to service status and connection state.
### Events
- **`online(name: string)`**: Emitted when a service becomes available.
- **`offline(name: string)`**: Emitted when a service goes offline.
- **`replaced(name: string)`**: Emitted when a service's owner changes.
- **`NameOwnerChanged(name: string, oldOwner: string, newOwner: string)`**: Emitted when a service's ownership changes.
- **`connectionClose()`**: Emitted when the DBus connection is closed.
- **`connectionError(error: Error)`**: Emitted when a connection error occurs.
- **`methodCall(message: DBusMessage)`**: Emitted for incoming method calls (used internally for local service
handling).
## Class: DBusService
### Purpose
The `DBusService` class represents a remote DBus service identified by a well-known name (e.g.,
`org.freedesktop.NetworkManager`). It provides methods to discover and access the service's objects, facilitating
interaction with the service's structure.
### Key Properties
- **`name: string`**: The well-known name of the service.
- **`uniqueName: string`**: The unique name of the current owner of the service (e.g., `:1.123`), which may change if
ownership changes.
### Key Methods
- **`async listObjects(): Promise<string[]>`**: Retrieves a list of object paths available under this service by
recursively introspecting the object hierarchy.
- **`async getObjects(): Promise<DBusObject[]>`**: Returns an array of `DBusObject` instances for all object paths under
this service.
- **`async getObject(objectPath: string): Promise<DBusObject>`**: Retrieves a specific `DBusObject` instance for the
given object path.
## Class: DBusObject
### Purpose
The `DBusObject` class encapsulates a specific object within a DBus service, identified by an object path (e.g.,
`/org/freedesktop/NetworkManager`). It enables introspection of the object's structure and provides access to its
interfaces.
### Key Properties
- **`name: string`**: The object path of this DBus object.
### Key Methods
- **`async introspect(): Promise<IntrospectNode>`**: Performs introspection on the object to retrieve metadata about its
interfaces, methods, properties, and signals as an XML-parsed structure.
- **`async listInterfaces(): Promise<string[]>`**: Returns a list of interface names available on this object.
- **`async getInterfaces(): Promise<DBusInterface[]>`**: Retrieves all `DBusInterface` instances for the interfaces on
this object.
- **`async getInterface(iface: string): Promise<DBusInterface>`**: Returns a specific `DBusInterface` instance for the
given interface name.
## Class: DBusInterface
### Purpose
The `DBusInterface` class provides access to the methods, properties, and signals of a specific interface on a DBus
object (e.g., `org.freedesktop.NetworkManager`). It offers type-safe operations based on introspection data.
### Key Properties
- **`name: string`**: The name of the interface (e.g., `org.freedesktop.NetworkManager`).
- **`method: Record<string, ReplyModeMethodCall>`**: A dynamically generated object mapping method names to callable
functions that expect a reply.
- **`noReplyMethod: Record<string, NoReplyModeMethodCall>`**: A dynamically generated object mapping method names to
callable functions that do not expect a reply.
- **`property: Record<string, PropertyOperation>`**: A dynamically generated object mapping property names to get/set
operations with access control.
- **`signal: DBusSignalEmitter`**: A lazily initialized signal emitter for subscribing to signals on this interface.
### Key Methods
- **`listMethods(): IntrospectMethod[]`**: Returns an array of method metadata from introspection data.
- **`listProperties(): IntrospectProperty[]`**: Returns an array of property metadata from introspection data.
- **`listSignals(): IntrospectSignal[]`**: Returns an array of signal metadata from introspection data.
## Class: LocalService
### Purpose
The `LocalService` class enables the creation and management of a custom DBus service that can be exposed to other
processes. It handles incoming method calls, manages associated objects, and connects to a DBus bus to publish the
service.
### Key Properties
- **`name: string`**: The well-known name of the local service (e.g., `org.test.service`).
- **`objectManager: ObjectManagerInterface | undefined`**: Access to the `org.freedesktop.DBus.ObjectManager` interface
on the root object, if available, for managing object hierarchies.
### Key Methods
- **`async run(opts: ConnectOpts): Promise<void>`**: Connects to a DBus bus using the provided options and starts the
service, listening for incoming method calls.
- **`async stop(): Promise<void>`**: Stops the service by releasing the name and disconnecting from the DBus bus.
- **`addObject(localObject: LocalObject): boolean`**: Adds a `LocalObject` to the service, associating it with the
service's context and notifying the object manager if available.
- **`removeObject(inp: LocalObject | string): boolean`**: Removes a `LocalObject` by instance or object path, notifying
the object manager if available.
- **`listObjects(): Record<string, LocalObject>`**: Returns a record of all objects associated with this service.
- **`findObjectByPath<T extends LocalObject>(objectPath: string): T | undefined`**: Finds a `LocalObject` by its path.
- **`listObjectPaths(): string[]`**: Returns an array of object paths for all objects in this service.
## Class: LocalObject
### Purpose
The `LocalObject` class represents a custom DBus object within a local service, identified by an object path (e.g.,
`/`). It manages associated interfaces and supports standard DBus interfaces like `org.freedesktop.DBus.Properties` for
property management.
### Key Properties
- **`name: string`**: The object path of this local object.
- **`propertiesInterface: PropertiesInterface`**: Access to the standard properties interface for handling
property-related operations.
- **`introspectableInterface: IntrospectableInterface`**: Access to the standard introspectable interface for
introspection operations.
- **`peerInterface: PeerInterface`**: Access to the standard peer interface for peer-related operations.
### Key Methods
- **`addInterface(localInterface: LocalInterface): boolean`**: Adds a `LocalInterface` to this object, associating it
with the object's context and notifying the service's object manager if applicable.
- **`removeInterface(inp: LocalInterface | string): boolean`**: Removes a `LocalInterface` by instance or name,
notifying the service's object manager if applicable.
- **`listInterfaces(): Record<string, LocalInterface>`**: Returns a record of all interfaces associated with this
object.
- **`interfaceNames(): string[]`**: Returns an array of interface names associated with this object.
- **`findInterfaceByName<T extends LocalInterface>(name: string): T | undefined`**: Finds a `LocalInterface` by its
name.
- **`getManagedInterfaces(): Record<string, Record<string, DBusSignedValue>>`**: Returns a record of all interfaces and
their managed properties as `DBusSignedValue` instances.
- **`introspectNode: IntrospectNode`**: Getter for introspection data of this object, including all associated
interfaces.
## Class: LocalInterface
### Purpose
The `LocalInterface` class allows developers to define a custom DBus interface with methods, properties, and signals
that can be exposed through a local service. It handles incoming method calls, property operations, and emits signals as
configured.
### Key Properties
- **`name: string`**: The name of the local interface (e.g., `org.test.iface`).
- **`introspectInterface: IntrospectInterface`**: Getter for introspection data of this interface, including defined
methods, properties, and signals.
### Key Methods
- **`defineMethod(opts: DefineMethodOpts): this`**: Defines a custom method with input/output signatures and an
implementation.
- **`removeMethod(name: string): this`**: Removes a defined method by name.
- **`defineProperty(opts: DefinePropertyOpts): this`**: Defines a custom property with type, access mode (read/write),
getter/setter functions, and optional property change signal emission.
- **`removeProperty(name: string): this`**: Removes a defined property by name.
- **`defineSignal(opts: DefineSignalOpts): this`**: Defines a custom signal with arguments and an associated
`EventEmitter` for triggering.
- **`removeSignal(name: string): this`**: Removes a defined signal by name.
- **
`async callMethod(name: string, payloadSignature: string | undefined, ...args: any[]): Promise<{signature?: string, result: any}>`
**: Calls a defined method with validated arguments and returns the result.
- **`async setProperty(name: string, value: any): Promise<void>`**: Sets a property value if the property is writable,
triggering change signals if configured.
- **`async getProperty(name: string): Promise<any>`**: Gets a property value if the property is readable.
- **`methodNames(): string[]`**: Lists names of defined methods.
- **`propertyNames(): string[]`**: Lists names of defined properties.
- **`signalNames(): string[]`**: Lists names of defined signals.
## Integration and Usage
The classes in `dbus-sdk` are designed to work together in a hierarchical and modular fashion, reflecting the DBus
object model. Below is an explanation of how they integrate for common use cases.
### Client-Side Interaction (Consuming Remote Services)
1. **Connecting to DBus**: Start with `DBus.connect()` to establish a connection to a DBus bus (system or session). This
returns a `DBus` instance, which is the entry point for all interactions.
2. **Accessing Services**: Use `DBus` to obtain a `DBusService` instance for a specific service (e.g.,
`org.freedesktop.NetworkManager`). The `DBusService` class allows discovery of objects under that service.
3. **Exploring Objects**: From a `DBusService`, retrieve `DBusObject` instances for specific object paths (e.g.,
`/org/freedesktop/NetworkManager`). Use `DBusObject` to introspect the object's structure and access its interfaces.
4. **Interacting with Interfaces**: Use `DBusObject` to get `DBusInterface` instances for specific interfaces on the
object. With `DBusInterface`, you can invoke methods (`method` or `noReplyMethod`), access properties (`property`),
and subscribe to signals (`signal`).
5. **Handling Events**: Leverage `DBus` event listeners (e.g., `on('online', ...)` or signal emitters created via
`createSignalEmitter`) to react to service availability changes or incoming signals.
### Server-Side Implementation (Exposing Custom Services)
1. **Defining a Service**: Create a `LocalService` instance with a unique service name (e.g., `org.test.service`). This
class manages the custom service and connects to a DBus bus.
2. **Creating Objects**: Define `LocalObject` instances for specific object paths (e.g., `/`) and add them to the
`LocalService`. Each `LocalObject` represents a node in the service's object hierarchy.
3. **Defining Interfaces**: Create `LocalInterface` instances with custom methods, properties, and signals using
`defineMethod`, `defineProperty`, and `defineSignal`. Add these interfaces to `LocalObject` instances.
4. **Running the Service**: Call `LocalService.run()` to connect to the DBus bus and publish the service. The library
automatically handles incoming method calls, routing them to the appropriate `LocalInterface` methods or properties.
5. **Emitting Signals**: Use `EventEmitter` instances associated with signals in `LocalInterface` to trigger signal
emissions, which are broadcast to subscribed clients via the `DBus` instance.
6. **Managing Object Hierarchies**: Utilize the `objectManager` property on `LocalService` to access the
`ObjectManagerInterface` (if available on the root object), which notifies clients of added or removed objects and
interfaces.
### Type Safety and Data Handling
- The library handles type conversion between DBus's strict type system and TypeScript/JavaScript using
`DBusSignedValue`, ensuring signature validation and proper serialization/deserialization.
- Introspection data (`IntrospectNode`, `IntrospectInterface`, etc.) dynamically informs method calls and property
access, enforcing access control and type compatibility.
## Examples
### Example 1: Interacting with a Remote DBus Service
This example demonstrates connecting to a remote DBus service, accessing an object, invoking a method, and listening for
signals.
```typescript
import {DBus} from 'dbus-sdk'
async function interactWithRemoteService(): Promise<void> {
try {
// Connect to a DBus bus (adjust bus address as needed)
const dbus = await DBus.connect({busAddress: 'tcp:host=192.168.1.236,port=44444'})
console.log('Connected to DBus successfully')
// Access a specific service
const service = await dbus.getService('org.test.service13')
// Get an object from the service
const object = await service.getObject('/')
// Access a specific interface
const customInterface = await object.getInterface('test.iface')
const propertiesInterface = await object.getInterface('org.freedesktop.DBus.Properties')
// Listen for property change signals
propertiesInterface.signal.on('PropertiesChanged', console.log)
// Get and set a property value
const propValue = await customInterface.property.testProp.get()
console.log('Current property value:', propValue)
await customInterface.property.testProp.set([12345678])
console.log('Property value updated')
// Invoke a method
const result = await customInterface.method.test(123)
console.log('Method result:', result)
} catch (error) {
console.error('Error interacting with service:', error)
}
}
interactWithRemoteService()
```
### Example 2: Exposing a Custom DBus Service
This example shows how to define and run a custom DBus service with a local interface, method, property, and signal.
```typescript
import {LocalService, LocalInterface, LocalObject} from 'dbus-sdk'
import EventEmitter from 'node:events'
async function runCustomService(): Promise<void> {
try {
// Initialize a local service with a unique name
const service = new LocalService('org.test.service13')
// Create a local object at the root path
const object = new LocalObject('/')
// Define a custom interface
const iface = new LocalInterface('test.iface')
// Define a property with getter and setter
let testProp = 'initial'
iface.defineProperty({
name: 'testProp',
type: 'av', // Array of variants
emitPropertiesChanged: {emitValue: true},
getter: () => testProp,
setter: (value: string) => {
testProp = value
}
})
// Define a method with input and output arguments
iface.defineMethod({
name: 'test',
inputArgs: [{type: 'u'}], // Unsigned integer input
outputArgs: [{type: 'v'}], // Variant output
method: (name: number = 1234) => {
console.log('Method called with name:', name)
return {name, haha: true, sleep: 'oh!'}
}
})
// Define a signal with an event emitter
const eventEmitter = new EventEmitter()
iface.defineSignal({
name: 'testSignal',
args: [{name: 'timestamp', type: 's'}], // String argument
eventEmitter
})
// Associate the interface with the object and the object with the service
object.addInterface(iface)
service.addObject(object)
// Connect to a DBus bus and run the service
await service.run({busAddress: 'tcp:host=192.168.1.236,port=44444'})
console.log('Custom DBus service is running...')
// Periodically emit a signal (optional)
setInterval(() => {
eventEmitter.emit('testSignal', `${Date.now()}`)
}, 3000)
} catch (error) {
console.error('Failed to run custom service:', error)
}
}
runCustomService()
```
## Conclusion
The `dbus-sdk` library provides a comprehensive, type-safe API for DBus interactions in Node.js and TypeScript
environments. Its client-side classes (`DBus`, `DBusService`, `DBusObject`, `DBusInterface`) enable seamless interaction
with remote services, while server-side classes (`LocalService`, `LocalObject`, `LocalInterface`) facilitate the
creation of custom services. By integrating these components, developers can build robust IPC solutions, abstracting
away the complexities of the DBus protocol while retaining full control over service interactions and implementations.
For further details or to contribute, visit the [GitHub repository](https://github.com/myq1991/node-dbus-sdk).