@lakutata/dbus 3.0.0 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +83 -68
- package/README.zh-CN.md +236 -0
- package/dist/cjs/adapters/DBusEntrypoint.d.ts +13 -4
- package/dist/cjs/adapters/DBusEntrypoint.js +9 -3
- package/dist/cjs/channel/DBusChannel.d.ts +6 -4
- package/dist/cjs/channel/DBusChannel.js +6 -4
- package/dist/cjs/decorators/DBusMethod.d.ts +4 -4
- package/dist/cjs/decorators/DBusMethod.js +2 -1
- package/dist/cjs/lib/DBusClientOptions.d.ts +1 -1
- package/dist/cjs/lib/Declarations.d.ts +3 -2
- package/dist/cjs/lib/Declarations.js +6 -2
- package/dist/cjs/lib/Names.d.ts +12 -0
- package/dist/cjs/lib/Names.js +32 -1
- package/dist/esm/adapters/DBusEntrypoint.js +10 -4
- package/dist/esm/channel/DBusChannel.js +6 -4
- package/dist/esm/decorators/DBusMethod.js +2 -1
- package/dist/esm/lib/Declarations.js +7 -3
- package/dist/esm/lib/Names.js +30 -1
- package/dist/types/adapters/DBusEntrypoint.d.ts +13 -4
- package/dist/types/channel/DBusChannel.d.ts +6 -4
- package/dist/types/decorators/DBusMethod.d.ts +4 -4
- package/dist/types/lib/DBusClientOptions.d.ts +1 -1
- package/dist/types/lib/Declarations.d.ts +3 -2
- package/dist/types/lib/Names.d.ts +12 -0
- package/package.json +23 -4
package/README.md
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
# @lakutata/dbus
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
bus,
|
|
3
|
+
English | [中文](README.zh-CN.md)
|
|
4
|
+
|
|
5
|
+
The D-Bus channel of the [lakutata](https://github.com/lakutata/lakutata) framework. The application owns a service
|
|
6
|
+
name on a bus (the system bus, the session bus or a private bus) and serves its controllers as interfaces of D-Bus
|
|
7
|
+
objects, with methods, properties and signals. The package also provides a D-Bus client component that calls other
|
|
8
|
+
services on a bus, reads their properties and listens to their signals. The protocol is implemented by
|
|
7
9
|
[dbus-sdk](https://github.com/myq1991/node-dbus-sdk), whose classes this package re-exports. The umbrella package
|
|
8
|
-
`lakutata` does not re-export
|
|
10
|
+
`lakutata` does not re-export this package: import it from `@lakutata/dbus`.
|
|
9
11
|
|
|
10
12
|
## Installation
|
|
11
13
|
|
|
@@ -13,17 +15,18 @@ bus, read their properties and listen to their signals. The protocol is implemen
|
|
|
13
15
|
npm install lakutata @lakutata/dbus
|
|
14
16
|
```
|
|
15
17
|
|
|
16
|
-
- It needs a D-Bus daemon, as on Linux: the system bus (`/var/run/dbus/system_bus_socket`), the session bus of
|
|
17
|
-
desktop session (`DBUS_SESSION_BUS_ADDRESS`), or a private `dbus-daemon`.
|
|
18
|
-
policy
|
|
19
|
-
- Node.js 22
|
|
18
|
+
- It needs a D-Bus daemon, as found on Linux: the system bus (`/var/run/dbus/system_bus_socket`), the session bus of
|
|
19
|
+
a desktop session (`DBUS_SESSION_BUS_ADDRESS`), or a private `dbus-daemon`. To own a name on the system bus, a bus
|
|
20
|
+
policy must allow it (a file in `/etc/dbus-1/system.d/`).
|
|
21
|
+
- It needs Node.js `^22.13.0 || >=24.11.0`. Its peer dependencies, `@lakutata/core` and `@lakutata/entrypoint`, are
|
|
22
|
+
installed with `lakutata`.
|
|
20
23
|
|
|
21
24
|
## Usage
|
|
22
25
|
|
|
23
26
|
### A D-Bus service
|
|
24
27
|
|
|
25
|
-
`dbusEntrypoint` is the `dbus` adapter
|
|
26
|
-
|
|
28
|
+
`dbusEntrypoint` is the entrypoint's `dbus` adapter. A controller declared with `@DBusInterface` serves an interface at
|
|
29
|
+
an object path, and its `@DBusMethod` actions are the methods of that interface.
|
|
27
30
|
|
|
28
31
|
```typescript
|
|
29
32
|
import {Application, DTO} from 'lakutata'
|
|
@@ -69,19 +72,22 @@ Application.run(() => ({
|
|
|
69
72
|
}))
|
|
70
73
|
```
|
|
71
74
|
|
|
72
|
-
- **
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
- **
|
|
76
|
-
|
|
77
|
-
array `av`,
|
|
78
|
-
strings,
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
75
|
+
- **Input**: a method with a DTO takes one argument, a dictionary of variants (`a{sv}`) holding the DTO's properties;
|
|
76
|
+
a method without a DTO takes no argument. The input goes through the entrypoint's middlewares, validation and access
|
|
77
|
+
control, as on the other channels.
|
|
78
|
+
- **Result**: its type is given by the `returns` option (a DTO, sent as `a{sv}`, or a signature such as `'s'`), or
|
|
79
|
+
else by the `@Return` schema: an object becomes `a{sv}`, a string `s`, a boolean `b`, an integer or a bigint `x`, any
|
|
80
|
+
other number `d`, an array `av`, and any other value a variant `v`. Without either, the method has no result. Dates
|
|
81
|
+
are sent as ISO strings, and `null` and `undefined` values are left out of objects and arrays (a variant cannot hold
|
|
82
|
+
null). A method whose result is a single type (`'s'`, `'i'`…) must return a value: `undefined` fails the call.
|
|
83
|
+
- **Errors** reach the callers as D-Bus errors named after their `errno` and prefixed with the service name
|
|
84
|
+
(`com.example.DeviceService.E_OFFLINE`), with their message. An element of the `errno` that does not start with a
|
|
85
|
+
letter gets an `E` prefix (`500` becomes `E500`, `_HIDDEN` becomes `E_HIDDEN`).
|
|
86
|
+
- **Context**: the context of the methods is a `DBusContext`, with `context.path`, `context.interface` and
|
|
87
|
+
`context.member`.
|
|
88
|
+
- **Several controllers** with the same path are interfaces of the same objects. Every object also implements
|
|
83
89
|
`org.freedesktop.DBus.Properties`, `Introspectable` and `Peer`, and the root object `/` implements `ObjectManager`.
|
|
84
|
-
- A method
|
|
90
|
+
- A method can also carry the decorators of other channels (`@ServiceAction`, `@GET`…).
|
|
85
91
|
|
|
86
92
|
### The adapter
|
|
87
93
|
|
|
@@ -89,29 +95,35 @@ Application.run(() => ({
|
|
|
89
95
|
dbus: dbusEntrypoint({bus: 'system', service: 'com.example.DeviceService', replaceExisting: true})
|
|
90
96
|
```
|
|
91
97
|
|
|
92
|
-
- `bus`: `'session'` (default
|
|
93
|
-
`unix:path=/var/run/dbus/system_bus_socket`),
|
|
94
|
-
|
|
95
|
-
- `service`: the well-known name of the service
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
98
|
+
- `bus`: `'session'` (the default: the address in `DBUS_SESSION_BUS_ADDRESS`), `'system'` (`DBUS_SYSTEM_BUS_ADDRESS`,
|
|
99
|
+
else `unix:path=/var/run/dbus/system_bus_socket`), a bus address (`unix:path=…`, `tcp:host=…,port=…`), or the socket
|
|
100
|
+
path of a private bus.
|
|
101
|
+
- `service`: the well-known name of the service, two or more elements of letters, digits and `_` separated by dots.
|
|
102
|
+
D-Bus also allows `-` in service names, but this name prefixes the error names, which cannot contain it. The adapter
|
|
103
|
+
fails to start when another connection owns the name. `replaceExisting` takes the name over when its owner allows
|
|
104
|
+
it, and `allowReplacement` lets another service take it over: the replaced adapter then keeps running without the
|
|
105
|
+
name, and does not get it back.
|
|
106
|
+
- `decode`: how the arguments of method calls and the property values written by clients are decoded (`DecodeOpts`
|
|
107
|
+
of `dbus-sdk`). By default (`{int64: 'number'}`), 64-bit integers arrive as numbers when they are safe integers, else
|
|
108
|
+
as bigints, so that `DTO.Number()` accepts them; `{int64: 'bigint'}` decodes them all as bigints, for `DTO.BigInt()`.
|
|
109
|
+
- `name` and `middlewares`: the adapter's name, and the middlewares for its calls (run after the global ones).
|
|
110
|
+
- It stops gracefully with the entrypoint: it releases the name, lets the calls in progress finish, then closes the
|
|
111
|
+
connection.
|
|
100
112
|
|
|
101
113
|
### Objects, properties and signals
|
|
102
114
|
|
|
103
|
-
The path of an interface is either a path (`/com/example/Manager`)
|
|
104
|
-
|
|
105
|
-
(
|
|
106
|
-
variables of the path are merged into the input of the methods,
|
|
107
|
-
client
|
|
115
|
+
The path of an interface is either a fixed path (`/com/example/Manager`) or a template (`/com/example/Devices/{sn}`).
|
|
116
|
+
The object at a fixed path is exported when the adapter starts and stays exported while it runs
|
|
117
|
+
(`DBusObjectManager.removeObject()` does not remove it). The objects of a template are added and removed with
|
|
118
|
+
`DBusObjectManager`. The variables of the path are merged into the input of the methods, like route parameters, and
|
|
119
|
+
take precedence over the values sent by the client.
|
|
108
120
|
|
|
109
|
-
A property is a value of an object, kept by `DBusObjectManager
|
|
110
|
-
notified
|
|
111
|
-
property
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
is emitted.
|
|
121
|
+
A property is a value of an object, kept by `DBusObjectManager`. The application sets it, and clients read it and are
|
|
122
|
+
notified when it changes (`PropertiesChanged`); until it is set, it has its `initial` value. Clients can also write a
|
|
123
|
+
`writable` property: the value is validated by the property's `schema` (validation errors are returned to the client),
|
|
124
|
+
stored, and `DBusObjectManager` emits `propertyWritten`. A write that must reach a device, or that may fail after
|
|
125
|
+
validation, is better made a method, whose errors are returned to the caller. A signal's data is validated by its DTO
|
|
126
|
+
when the signal is emitted.
|
|
115
127
|
|
|
116
128
|
```typescript
|
|
117
129
|
import {Component, DTO} from 'lakutata'
|
|
@@ -179,15 +191,14 @@ export class Devices extends Component {
|
|
|
179
191
|
}
|
|
180
192
|
```
|
|
181
193
|
|
|
182
|
-
`DBusObjectManager` is autoloaded, one per application: inject it
|
|
183
|
-
it
|
|
184
|
-
|
|
185
|
-
`InterfacesRemoved`).
|
|
194
|
+
`DBusObjectManager` is autoloaded, one per application: inject it into any object (`@Inject(DBusObjectManager)`) or get
|
|
195
|
+
it with `getObject(DBusObjectManager)`. Objects added before the adapter starts are exported when it starts. The
|
|
196
|
+
service's `ObjectManager` tells clients about added and removed objects (`InterfacesAdded`, `InterfacesRemoved`).
|
|
186
197
|
|
|
187
198
|
### The client
|
|
188
199
|
|
|
189
|
-
`DBusClientComponent` connects to a bus when it is created
|
|
190
|
-
and listen to their signals
|
|
200
|
+
`DBusClientComponent` connects to a bus when it is created; use it to call services, read their objects and
|
|
201
|
+
properties, and listen to their signals. It disconnects when the application stops.
|
|
191
202
|
|
|
192
203
|
```typescript
|
|
193
204
|
import {Application} from 'lakutata'
|
|
@@ -229,38 +240,42 @@ Application
|
|
|
229
240
|
|
|
230
241
|
| Export | What it is |
|
|
231
242
|
|---|---|
|
|
232
|
-
| `dbusEntrypoint`, `DBusEntrypointOptions` | The adapter
|
|
243
|
+
| `dbusEntrypoint`, `DBusEntrypointOptions` | The entrypoint's adapter (for its `dbus` option) and its options: `bus`, `service`, `replaceExisting`, `allowReplacement`, `decode`, `name`, `middlewares` |
|
|
233
244
|
| `DBusInterface`, `DBusInterfaceOptions` | Class decorator: a controller served as a D-Bus interface at an object path or path template |
|
|
234
|
-
| `DBusMethod`, `DBusMethodOptions` | Method decorator: an action
|
|
235
|
-
| `defineDBusSignal`, `DBusSignalDefinition` | Declares a signal, its DTO or signature |
|
|
245
|
+
| `DBusMethod`, `DBusMethodOptions` | Method decorator: serves an action as a method of the interface; the options include the type of its result (`returns`) |
|
|
246
|
+
| `defineDBusSignal`, `DBusSignalDefinition` | Declares a signal, with its DTO or signature |
|
|
236
247
|
| `defineDBusProperty`, `DBusPropertyDefinition`, `DBusPropertyOptions` | Declares a property: its signature, `writable`, `initial`, `schema` |
|
|
237
|
-
| `DBusInterfaceDeclaration` | The declaration `@DBusInterface`
|
|
238
|
-
| `DBusObjectManager` |
|
|
239
|
-
| `DBusPropertyWrite` | A property
|
|
240
|
-
| `DBusContext`, `DBusContextParams`, `DBUS_CONTEXT_TYPE` | The context of
|
|
241
|
-
| `DBusChannel` | The definition of the `dbus` channel, for
|
|
242
|
-
| `DBusClientComponent` | The client: `invoke`, `interface`, `interfaceSignal`, `object`, `service`, `services`, `listNames`, `listServices`, `nameHasOwner`, `getNameOwner`, `addMatch`, the connection events |
|
|
243
|
-
| `buildDBusClientOptions`, `DBusClientOptions` | The client's declaration and options: `busAddress`, `timeout`, `callTimeout`, `decode` (`DecodeOpts` of `dbus-sdk`;
|
|
248
|
+
| `DBusInterfaceDeclaration` | The declaration recorded by `@DBusInterface` |
|
|
249
|
+
| `DBusObjectManager` | Manages the objects of path templates, property values and signals: `addObject`, `removeObject`, `hasObject`, `objects`, `setPropertyValue`, `getPropertyValue`, `emitSignal`; the `propertyWritten` event |
|
|
250
|
+
| `DBusPropertyWrite` | A property write by a client: the argument of `propertyWritten` |
|
|
251
|
+
| `DBusContext`, `DBusContextParams`, `DBUS_CONTEXT_TYPE` | The context of method calls: `path`, `interface`, `member` |
|
|
252
|
+
| `DBusChannel` | The definition of the `dbus` channel, for custom adapters |
|
|
253
|
+
| `DBusClientComponent` | The client: `invoke`, `interface`, `interfaceSignal`, `object`, `service`, `services`, `listNames`, `listBusNames`, `listServices`, `listActivatableNames`, `nameHasOwner`, `getNameOwner`, `addMatch`, `removeMatch`, `uniqueName`; `on`, `once`, `off` and `removeListener` for the connection events (`serviceOnline`, `serviceOffline`, `serviceReplaced`, `methodCall`, `nameOwnerChanged`, `nameLost`, `nameAcquired`, `connectionClose`, `connectionError`) |
|
|
254
|
+
| `buildDBusClientOptions`, `DBusClientOptions` | The client's declaration and options: `busAddress`, `timeout`, `callTimeout`, `decode` (`DecodeOpts` of `dbus-sdk`; 64-bit integers are numbers by default) |
|
|
244
255
|
| `DBus`, `DBusRemoteInterface`, `DBusObject`, `DBusService`, `LocalService`, `LocalObject`, `LocalInterface`, `DBusSignedValue`, `DBusSignalEmitter`, `Signature`, `createDBusError`, `RequestNameFlags`, `RequestNameResultCode`, `BusNameBasicInfo`, `ConnectOpts`, `DecodeOpts`, `InvokeOpts` | Re-exported from dbus-sdk 2 (`DBusRemoteInterface` is its `DBusInterface`; see its MIGRATION.md for the changes from 1.x) |
|
|
245
256
|
|
|
246
|
-
The typings document
|
|
257
|
+
The typings document every declaration, with examples.
|
|
247
258
|
|
|
248
259
|
## Errors
|
|
249
260
|
|
|
250
261
|
| Error | When |
|
|
251
262
|
|---|---|
|
|
252
|
-
| `TypeError` | An invalid interface, member or service name,
|
|
263
|
+
| `TypeError` | An invalid interface, member or service name, object path, or signature (of a property, a signal or a `returns` option), when it is declared or `dbusEntrypoint()` is called; `DBusObjectManager.addObject()` with a template; the adapter's start, when a `@DBusMethod` belongs to a controller without `@DBusInterface` |
|
|
253
264
|
| `Error` (`The DBus name … is owned by another service`) | The adapter's start, when another connection owns the service name; the entrypoint fails to start |
|
|
254
|
-
| The errors of dbus-sdk (`UnknownBusAddressError`, a connection error…) | The adapter's start or the client's creation, when the bus cannot be reached; the client's calls, when a method
|
|
255
|
-
| `InvalidValueException` (from `lakutata`) | `setPropertyValue()` or `emitSignal()` with a value its schema or DTO
|
|
265
|
+
| The errors of dbus-sdk (`UnknownBusAddressError`, a connection error…) | The adapter's start or the client's creation, when the bus cannot be reached; the client's calls, when a method replies with a D-Bus error |
|
|
266
|
+
| `InvalidValueException` (from `lakutata`) | `setPropertyValue()` or `emitSignal()` with a value that its schema or DTO rejects; the client's creation, when `busAddress` is missing |
|
|
267
|
+
| `InvalidObjectOptionsException` (from `lakutata`) | An option of the client has an invalid value (`timeout`, `decode`…); the application fails to start |
|
|
256
268
|
|
|
257
|
-
|
|
258
|
-
(`com.example.DeviceService.E_OFFLINE`), with their message
|
|
259
|
-
error
|
|
269
|
+
Errors thrown by the actions reach the callers as D-Bus errors named after their `errno`
|
|
270
|
+
(`com.example.DeviceService.E_OFFLINE`), with their message. A client that writes an invalid property value also gets
|
|
271
|
+
a D-Bus error.
|
|
260
272
|
|
|
261
273
|
## See also
|
|
262
274
|
|
|
263
|
-
-
|
|
264
|
-
|
|
275
|
+
- The guide on the website: [D-Bus](https://lakutata.com/guide/DBus).
|
|
276
|
+
- `doc/en/DBus.md` in the `lakutata` package (Chinese version in `doc/zh`): the guide to D-Bus.
|
|
277
|
+
- `@lakutata/entrypoint` (`lakutata/com/entrypoint`): the entrypoint, controllers, middlewares and access control (see
|
|
278
|
+
`doc/en/Controllers.md`, `Entrypoint.md` and `Middlewares.md` in the `lakutata` package, and their Chinese versions
|
|
279
|
+
in `doc/zh`).
|
|
265
280
|
- [dbus-sdk](https://github.com/myq1991/node-dbus-sdk): the D-Bus protocol, its client classes and its local services.
|
|
266
|
-
- `@lakutata/core` (`lakutata`):
|
|
281
|
+
- `@lakutata/core` (`lakutata`): components, `Application` and dependency injection.
|
package/README.zh-CN.md
ADDED
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
# @lakutata/dbus
|
|
2
|
+
|
|
3
|
+
[English](README.md) | 中文
|
|
4
|
+
|
|
5
|
+
[lakutata](https://github.com/lakutata/lakutata) 框架的 D-Bus 传输方式。应用在一条总线(系统总线、会话总线或私有总线)上持有一个服务名称,把控制器作为 D-Bus 对象的接口提供出来,包括方法、属性和信号。这个包还提供一个 D-Bus 客户端组件,用来调用总线上的其他服务、读取它们的属性、监听它们的信号。协议由 [dbus-sdk](https://github.com/myq1991/node-dbus-sdk) 实现,这个包也重新导出了它的类。`lakutata` 包不重新导出这个包:请从 `@lakutata/dbus` 导入。
|
|
6
|
+
|
|
7
|
+
## 安装
|
|
8
|
+
|
|
9
|
+
```shell
|
|
10
|
+
npm install lakutata @lakutata/dbus
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
- 需要一个 D-Bus 守护进程,Linux 上一般都有:系统总线(`/var/run/dbus/system_bus_socket`)、桌面会话的会话总线(`DBUS_SESSION_BUS_ADDRESS`),或者私有的 `dbus-daemon`。要在系统总线上持有名称,需要总线策略允许(`/etc/dbus-1/system.d/` 中的一个文件)。
|
|
14
|
+
- 需要 Node.js `^22.13.0 || >=24.11.0`。它的 peer 依赖 `@lakutata/core` 和 `@lakutata/entrypoint` 会随 `lakutata` 一起安装。
|
|
15
|
+
|
|
16
|
+
## 用法
|
|
17
|
+
|
|
18
|
+
### 一个 D-Bus 服务
|
|
19
|
+
|
|
20
|
+
`dbusEntrypoint` 是入口的 `dbus` 适配器。用 `@DBusInterface` 声明的控制器在一个对象路径上提供一个接口,它的 `@DBusMethod` 动作就是这个接口的方法。
|
|
21
|
+
|
|
22
|
+
```typescript
|
|
23
|
+
import {Application, DTO} from 'lakutata'
|
|
24
|
+
import {buildEntrypoints, Controller} from 'lakutata/com/entrypoint'
|
|
25
|
+
import {Expect, Return} from 'lakutata/decorator/dto'
|
|
26
|
+
import {dbusEntrypoint, DBusInterface, DBusMethod} from '@lakutata/dbus'
|
|
27
|
+
|
|
28
|
+
class ConfigureDTO extends DTO {
|
|
29
|
+
@Expect(DTO.String().required())
|
|
30
|
+
public sn: string
|
|
31
|
+
|
|
32
|
+
@Expect(DTO.Number().integer().min(0).optional())
|
|
33
|
+
public retries?: number
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
@DBusInterface('com.example.Manager', {path: '/com/example/Manager'})
|
|
37
|
+
class ManagerController extends Controller {
|
|
38
|
+
//没有参数,返回一个字符串(s)
|
|
39
|
+
@DBusMethod('Ping', {returns: 's'})
|
|
40
|
+
public async ping(): Promise<string> {
|
|
41
|
+
return 'pong'
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
//一个 a{sv} 参数,返回一个 a{sv} 字典(@Return 的类型)
|
|
45
|
+
@DBusMethod('Configure', ConfigureDTO)
|
|
46
|
+
@Return(ConfigureDTO)
|
|
47
|
+
public async configure(input: ConfigureDTO): Promise<ConfigureDTO> {
|
|
48
|
+
return input
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
//busctl --user call com.example.DeviceService /com/example/Manager com.example.Manager Ping
|
|
53
|
+
Application.run(() => ({
|
|
54
|
+
id: 'devices.app',
|
|
55
|
+
name: 'Devices',
|
|
56
|
+
components: {
|
|
57
|
+
entrypoint: buildEntrypoints({
|
|
58
|
+
controllers: [ManagerController],
|
|
59
|
+
dbus: dbusEntrypoint({bus: 'session', service: 'com.example.DeviceService'})
|
|
60
|
+
})
|
|
61
|
+
},
|
|
62
|
+
bootstrap: ['entrypoint']
|
|
63
|
+
}))
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
- **输入**:带 DTO 的方法接受一个参数,即包含 DTO 各个属性的变体字典(`a{sv}`);没有 DTO 的方法不接受参数。和其他传输方式一样,输入会经过入口的中间件、校验和访问控制。
|
|
67
|
+
- **结果**:类型由 `returns` 选项给出(一个 DTO,以 `a{sv}` 发送;或者 `'s'` 这样的签名),没有这个选项时取自 `@Return` 的校验规则:对象对应 `a{sv}`,字符串对应 `s`,布尔值对应 `b`,整数和 bigint 对应 `x`,其他数字对应 `d`,数组对应 `av`,其他值对应变体 `v`。两者都没有时,方法没有结果。日期以 ISO 字符串发送,对象和数组中值为 `null` 和 `undefined` 的项会被去掉(变体不能为 null)。结果为单个类型(`'s'`、`'i'`……)的方法必须返回一个值:返回 `undefined` 会让调用失败。
|
|
68
|
+
- **错误**作为 D-Bus 错误连同消息一起交给调用方,错误名称是服务名称加上错误的 `errno`(`com.example.DeviceService.E_OFFLINE`)。`errno` 中不以字母开头的段会加上前缀 `E`(`500` 变成 `E500`,`_HIDDEN` 变成 `E_HIDDEN`)。
|
|
69
|
+
- **上下文**:方法的上下文是 `DBusContext`,带有 `context.path`、`context.interface` 和 `context.member`。
|
|
70
|
+
- **路径相同的多个控制器**是同一个对象的多个接口。每个对象还实现了 `org.freedesktop.DBus.Properties`、`Introspectable` 和 `Peer`;根对象 `/` 实现了 `ObjectManager`。
|
|
71
|
+
- 方法还可以同时带有其他传输方式的装饰器(`@ServiceAction`、`@GET`……)。
|
|
72
|
+
|
|
73
|
+
### 适配器
|
|
74
|
+
|
|
75
|
+
```text
|
|
76
|
+
dbus: dbusEntrypoint({bus: 'system', service: 'com.example.DeviceService', replaceExisting: true})
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
- `bus`:`'session'`(默认值,使用 `DBUS_SESSION_BUS_ADDRESS` 中的地址)、`'system'`(`DBUS_SYSTEM_BUS_ADDRESS`,没有设置时为 `unix:path=/var/run/dbus/system_bus_socket`)、一个总线地址(`unix:path=…`、`tcp:host=…,port=…`),或者私有总线的套接字路径。
|
|
80
|
+
- `service`:服务的公认名称,由字母、数字和 `_` 组成的两段或更多段,以点分隔。D-Bus 也允许服务名称中有 `-`,但这个名称会作为错误名称的前缀,而错误名称中不能有这个字符。名称已被其他连接持有时,适配器启动失败。设置 `replaceExisting` 时,如果名称的持有者允许,就接管名称;设置 `allowReplacement` 时,允许其他服务接管名称:被替换的适配器会继续运行,但没有名称,也拿不回来。
|
|
81
|
+
- `decode`:方法调用的参数和客户端写入的属性值如何解码(`dbus-sdk` 的 `DecodeOpts`)。使用默认值(`{int64: 'number'}`)时,64 位整数如果是安全整数就解码为数字,否则解码为 bigint,这样 `DTO.Number()` 才能接受它们;`{int64: 'bigint'}` 把它们全部解码为 bigint,供 `DTO.BigInt()` 使用。
|
|
82
|
+
- `name` 和 `middlewares`:适配器的名字,以及它的调用使用的中间件(在全局中间件之后执行)。
|
|
83
|
+
- 它随入口一起平稳停止:先释放名称,等正在处理的调用结束,再关闭连接。
|
|
84
|
+
|
|
85
|
+
### 对象、属性与信号
|
|
86
|
+
|
|
87
|
+
接口的路径可以是一个具体路径(`/com/example/Manager`),也可以是一个模板(`/com/example/Devices/{sn}`)。具体路径上的对象在适配器启动时导出,在适配器运行期间一直保持导出(`DBusObjectManager.removeObject()` 不会移除它)。模板的对象通过 `DBusObjectManager` 添加和移除。和路由参数一样,路径中的变量会合并到方法的输入中,并覆盖客户端发送的值。
|
|
88
|
+
|
|
89
|
+
属性是对象的一个值,由 `DBusObjectManager` 保存。应用设置它,客户端读取它,并在它变化时收到通知(`PropertiesChanged`);在设置之前,它的值是 `initial`。客户端也可以写入 `writable` 属性:值先经过属性的 `schema` 校验(校验错误返回给客户端),然后保存,并由 `DBusObjectManager` 发出 `propertyWritten` 事件。需要传到设备上或者校验通过后仍可能失败的写入,更适合做成方法,这样错误能返回给调用方。发出信号时,信号的数据由它的 DTO 校验。
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
import {Component, DTO} from 'lakutata'
|
|
93
|
+
import {Controller} from 'lakutata/com/entrypoint'
|
|
94
|
+
import {Expect, Return} from 'lakutata/decorator/dto'
|
|
95
|
+
import {Inject} from 'lakutata/decorator/di'
|
|
96
|
+
import {
|
|
97
|
+
DBusInterface,
|
|
98
|
+
DBusMethod,
|
|
99
|
+
DBusObjectManager,
|
|
100
|
+
defineDBusProperty,
|
|
101
|
+
defineDBusSignal,
|
|
102
|
+
type DBusPropertyWrite
|
|
103
|
+
} from '@lakutata/dbus'
|
|
104
|
+
|
|
105
|
+
class DeviceSnDTO extends DTO {
|
|
106
|
+
@Expect(DTO.String().required())
|
|
107
|
+
public sn: string
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
class StatusDTO extends DTO {
|
|
111
|
+
@Expect(DTO.String().required())
|
|
112
|
+
public sn: string
|
|
113
|
+
|
|
114
|
+
@Expect(DTO.Boolean().required())
|
|
115
|
+
public online: boolean
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export const DeviceChanged = defineDBusSignal<StatusDTO>('Changed', StatusDTO)
|
|
119
|
+
export const DeviceState = defineDBusProperty<string>('State', 's', {initial: 'idle'})
|
|
120
|
+
export const DeviceLevel = defineDBusProperty<number>('Level', 'i', {writable: true, initial: 1, schema: DTO.Number().integer().min(0).max(10)})
|
|
121
|
+
|
|
122
|
+
@DBusInterface('com.example.Device', {path: '/com/example/Devices/{sn}', signals: [DeviceChanged], properties: [DeviceState, DeviceLevel]})
|
|
123
|
+
export class DeviceController extends Controller {
|
|
124
|
+
//输入中的 sn 取自对象路径
|
|
125
|
+
@DBusMethod('Status', DeviceSnDTO)
|
|
126
|
+
@Return(StatusDTO)
|
|
127
|
+
public async status(input: DeviceSnDTO): Promise<StatusDTO> {
|
|
128
|
+
return {sn: input.sn, online: true} as StatusDTO
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
//在应用的 components 中声明
|
|
133
|
+
export class Devices extends Component {
|
|
134
|
+
@Inject(DBusObjectManager)
|
|
135
|
+
protected readonly manager: DBusObjectManager
|
|
136
|
+
|
|
137
|
+
protected async init(): Promise<void> {
|
|
138
|
+
this.manager.on('propertyWritten', (write: DBusPropertyWrite): void => {
|
|
139
|
+
if (write.property === DeviceLevel) console.log(`${write.path}: level ${write.value}`)
|
|
140
|
+
})
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
public plug(sn: string): void {
|
|
144
|
+
const path: string = `/com/example/Devices/${sn}`
|
|
145
|
+
//路径模板与它匹配的各个接口会导出这个对象
|
|
146
|
+
this.manager.addObject(path)
|
|
147
|
+
this.manager.setPropertyValue(path, DeviceState, 'running')
|
|
148
|
+
this.manager.emitSignal(path, DeviceChanged, {sn: sn, online: true} as StatusDTO)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
public unplug(sn: string): void {
|
|
152
|
+
this.manager.removeObject(`/com/example/Devices/${sn}`)
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
`DBusObjectManager` 会自动注册,每个应用一个:可以注入到任何对象中(`@Inject(DBusObjectManager)`),也可以用 `getObject(DBusObjectManager)` 获取。在适配器启动之前添加的对象,会在它启动时导出。服务的 `ObjectManager` 会在对象添加和移除时通知客户端(`InterfacesAdded`、`InterfacesRemoved`)。
|
|
158
|
+
|
|
159
|
+
### 客户端
|
|
160
|
+
|
|
161
|
+
`DBusClientComponent` 在创建时连接总线,用来调用服务、读取它们的对象和属性、监听它们的信号。应用停止时,它断开连接。
|
|
162
|
+
|
|
163
|
+
```typescript
|
|
164
|
+
import {Application} from 'lakutata'
|
|
165
|
+
import {DBusClientComponent, type DBusRemoteInterface} from '@lakutata/dbus'
|
|
166
|
+
|
|
167
|
+
Application
|
|
168
|
+
.run(() => ({
|
|
169
|
+
id: 'client.app',
|
|
170
|
+
name: 'Client',
|
|
171
|
+
components: {
|
|
172
|
+
dbus: {class: DBusClientComponent, busAddress: 'unix:path=/var/run/dbus/system_bus_socket', timeout: 5000}
|
|
173
|
+
}
|
|
174
|
+
}))
|
|
175
|
+
.onLaunched(async (app: Application): Promise<void> => {
|
|
176
|
+
const dbus: DBusClientComponent = await app.getObject('dbus')
|
|
177
|
+
//显式给出签名的方法调用:得到输出参数组成的数组
|
|
178
|
+
const [status] = await dbus.invoke({
|
|
179
|
+
service: 'com.example.DeviceService',
|
|
180
|
+
objectPath: '/com/example/Devices/A1',
|
|
181
|
+
interface: 'com.example.Device',
|
|
182
|
+
method: 'Status',
|
|
183
|
+
signature: 'a{sv}',
|
|
184
|
+
args: [{sn: 'A1'}]
|
|
185
|
+
})
|
|
186
|
+
console.log(status)
|
|
187
|
+
//根据对象的内省信息构建的接口
|
|
188
|
+
const device: DBusRemoteInterface = await dbus.interface('com.example.DeviceService', '/com/example/Devices/A1', 'com.example.Device')
|
|
189
|
+
console.log(await device.property.State.get())
|
|
190
|
+
device.signal.on('Changed', (data: unknown): void => console.log(data))
|
|
191
|
+
//连接的事件
|
|
192
|
+
dbus.on('nameOwnerChanged', (name: string, oldOwner: string, newOwner: string): void => console.log(name, oldOwner, newOwner))
|
|
193
|
+
})
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
`buildDBusClientOptions(options)` 用带类型的选项生成同样的声明:`dbus: buildDBusClientOptions({busAddress: 'unix:path=/var/run/dbus/system_bus_socket'})`。
|
|
197
|
+
|
|
198
|
+
## API
|
|
199
|
+
|
|
200
|
+
| 导出 | 说明 |
|
|
201
|
+
|---|---|
|
|
202
|
+
| `dbusEntrypoint`、`DBusEntrypointOptions` | 供入口的 `dbus` 选项使用的适配器,及其选项:`bus`、`service`、`replaceExisting`、`allowReplacement`、`decode`、`name`、`middlewares` |
|
|
203
|
+
| `DBusInterface`、`DBusInterfaceOptions` | 类装饰器:把控制器作为 D-Bus 接口,在一个对象路径或路径模板上提供 |
|
|
204
|
+
| `DBusMethod`、`DBusMethodOptions` | 方法装饰器:把动作作为接口的方法提供;选项包括结果的类型(`returns`) |
|
|
205
|
+
| `defineDBusSignal`、`DBusSignalDefinition` | 声明一个信号,以及它的 DTO 或签名 |
|
|
206
|
+
| `defineDBusProperty`、`DBusPropertyDefinition`、`DBusPropertyOptions` | 声明一个属性:它的签名、`writable`、`initial`、`schema` |
|
|
207
|
+
| `DBusInterfaceDeclaration` | `@DBusInterface` 记录下来的声明 |
|
|
208
|
+
| `DBusObjectManager` | 管理路径模板的对象、属性值和信号:`addObject`、`removeObject`、`hasObject`、`objects`、`setPropertyValue`、`getPropertyValue`、`emitSignal`;`propertyWritten` 事件 |
|
|
209
|
+
| `DBusPropertyWrite` | 客户端对属性的一次写入:`propertyWritten` 的参数 |
|
|
210
|
+
| `DBusContext`、`DBusContextParams`、`DBUS_CONTEXT_TYPE` | 方法调用的上下文:`path`、`interface`、`member` |
|
|
211
|
+
| `DBusChannel` | `dbus` 传输方式的定义,供自定义适配器使用 |
|
|
212
|
+
| `DBusClientComponent` | 客户端:`invoke`、`interface`、`interfaceSignal`、`object`、`service`、`services`、`listNames`、`listBusNames`、`listServices`、`listActivatableNames`、`nameHasOwner`、`getNameOwner`、`addMatch`、`removeMatch`、`uniqueName`;用 `on`、`once`、`off` 和 `removeListener` 处理连接的事件(`serviceOnline`、`serviceOffline`、`serviceReplaced`、`methodCall`、`nameOwnerChanged`、`nameLost`、`nameAcquired`、`connectionClose`、`connectionError`) |
|
|
213
|
+
| `buildDBusClientOptions`、`DBusClientOptions` | 客户端的声明及其选项:`busAddress`、`timeout`、`callTimeout`、`decode`(`dbus-sdk` 的 `DecodeOpts`;64 位整数默认解码为数字) |
|
|
214
|
+
| `DBus`、`DBusRemoteInterface`、`DBusObject`、`DBusService`、`LocalService`、`LocalObject`、`LocalInterface`、`DBusSignedValue`、`DBusSignalEmitter`、`Signature`、`createDBusError`、`RequestNameFlags`、`RequestNameResultCode`、`BusNameBasicInfo`、`ConnectOpts`、`DecodeOpts`、`InvokeOpts` | 从 dbus-sdk 2 重新导出(`DBusRemoteInterface` 就是它的 `DBusInterface`;相对 1.x 的变化见它的 MIGRATION.md) |
|
|
215
|
+
|
|
216
|
+
类型声明中的每一项都附有说明和示例。
|
|
217
|
+
|
|
218
|
+
## 错误
|
|
219
|
+
|
|
220
|
+
| 错误 | 什么时候 |
|
|
221
|
+
|---|---|
|
|
222
|
+
| `TypeError` | 声明时或调用 `dbusEntrypoint()` 时,接口名、成员名、服务名称、对象路径或签名(属性、信号或 `returns` 选项的签名)不合法;用模板调用 `DBusObjectManager.addObject()`;适配器启动时,某个 `@DBusMethod` 所在的控制器没有 `@DBusInterface` |
|
|
223
|
+
| `Error`(`The DBus name … is owned by another service`) | 适配器启动时,服务名称已被其他连接持有;入口启动失败 |
|
|
224
|
+
| dbus-sdk 的错误(`UnknownBusAddressError`、连接错误……) | 适配器启动或客户端创建时连不上总线;客户端调用的方法以 D-Bus 错误回复 |
|
|
225
|
+
| `InvalidValueException`(来自 `lakutata`) | `setPropertyValue()` 或 `emitSignal()` 的值被校验规则或 DTO 拒绝;客户端创建时缺少 `busAddress` |
|
|
226
|
+
| `InvalidObjectOptionsException`(来自 `lakutata`) | 客户端的某个选项的值不合法(`timeout`、`decode`……);应用启动失败 |
|
|
227
|
+
|
|
228
|
+
动作抛出的错误作为 D-Bus 错误连同消息一起交给调用方,错误名称取自它的 `errno`(`com.example.DeviceService.E_OFFLINE`)。客户端写入不合法的属性值时,也会收到 D-Bus 错误。
|
|
229
|
+
|
|
230
|
+
## 另见
|
|
231
|
+
|
|
232
|
+
- 网站上的指南:[D-Bus](https://lakutata.com/zh/guide/DBus)。
|
|
233
|
+
- `lakutata` 包中的 `doc/zh/DBus.md`:D-Bus 指南。
|
|
234
|
+
- `@lakutata/entrypoint`(`lakutata/com/entrypoint`):入口、控制器、中间件和访问控制(见 `lakutata` 包中的 `doc/zh/Controllers.md`、`Entrypoint.md` 和 `Middlewares.md`)。
|
|
235
|
+
- [dbus-sdk](https://github.com/myq1991/node-dbus-sdk):D-Bus 协议、它的客户端类和本地服务。
|
|
236
|
+
- `@lakutata/core`(`lakutata`):组件、`Application` 和依赖注入。
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { EntrypointAdapter, MiddlewareDefinition } from '@lakutata/entrypoint';
|
|
2
|
+
import { type DecodeOpts } from 'dbus-sdk';
|
|
2
3
|
import { DBusContext } from '../context/DBusContext.js';
|
|
3
4
|
/**
|
|
4
5
|
* The options of {@link dbusEntrypoint}: the bus to connect to, and the service name to own on it.
|
|
@@ -19,9 +20,10 @@ export type DBusEntrypointOptions = {
|
|
|
19
20
|
*/
|
|
20
21
|
bus?: 'session' | 'system' | string;
|
|
21
22
|
/**
|
|
22
|
-
* The well-known name the service owns on the bus (`'com.example.DeviceService'`): two or more elements
|
|
23
|
-
* dots. The clients address the service by it, and it prefixes the names of the D-Bus
|
|
24
|
-
* the
|
|
23
|
+
* The well-known name the service owns on the bus (`'com.example.DeviceService'`): two or more elements of letters,
|
|
24
|
+
* digits and `_`, separated by dots. The clients address the service by it, and it prefixes the names of the D-Bus
|
|
25
|
+
* errors (which may not hold the `-` that D-Bus allows in the names of the services). On the system bus, the bus
|
|
26
|
+
* policy (`/etc/dbus-1/system.d/`) must allow the process to own it.
|
|
25
27
|
*/
|
|
26
28
|
service: string;
|
|
27
29
|
/**
|
|
@@ -32,10 +34,17 @@ export type DBusEntrypointOptions = {
|
|
|
32
34
|
replaceExisting?: boolean;
|
|
33
35
|
/**
|
|
34
36
|
* Let another connection take the name with `replaceExisting`. The adapter is not stopped when the name is taken:
|
|
35
|
-
* the clients reach the new owner
|
|
37
|
+
* the clients reach the new owner, and the adapter keeps running without the name, which it does not get back when
|
|
38
|
+
* the new owner releases it.
|
|
36
39
|
* @default false
|
|
37
40
|
*/
|
|
38
41
|
allowReplacement?: boolean;
|
|
42
|
+
/**
|
|
43
|
+
* How the arguments of the method calls and the values the clients write to the properties are decoded
|
|
44
|
+
* (`DecodeOpts` of `dbus-sdk`): `types`, `dictionaries`, `int64` (`'number'` or `'bigint'`), as for the client.
|
|
45
|
+
* @default {int64: 'number'} (the 64-bit integers as numbers when they are safe integers, else bigints)
|
|
46
|
+
*/
|
|
47
|
+
decode?: DecodeOpts;
|
|
39
48
|
/**
|
|
40
49
|
* The middlewares of this adapter's method calls, run after the entrypoint's global middlewares.
|
|
41
50
|
* @default []
|
|
@@ -81,7 +81,7 @@ function toMethodError(error) {
|
|
|
81
81
|
* ```
|
|
82
82
|
*/
|
|
83
83
|
function dbusEntrypoint(options) {
|
|
84
|
-
(0, Names_js_1.
|
|
84
|
+
(0, Names_js_1.checkServiceName)(options.service);
|
|
85
85
|
let service;
|
|
86
86
|
let detach;
|
|
87
87
|
const exported = new Map();
|
|
@@ -113,7 +113,12 @@ function dbusEntrypoint(options) {
|
|
|
113
113
|
const { path, interface: interfaceName, member } = action.address;
|
|
114
114
|
if (path === undefined || interfaceName === undefined)
|
|
115
115
|
throw new TypeError(`The DBus method ${member} of ${action.controller} has no interface: declare its controller with @DBusInterface`);
|
|
116
|
-
|
|
116
|
+
//A method declared twice in an interface is served by the declaration registered last
|
|
117
|
+
const methods = interfacesAt(path, interfaceName).methods;
|
|
118
|
+
const declared = methods.findIndex((method) => method.address.member === member);
|
|
119
|
+
if (declared >= 0)
|
|
120
|
+
methods.splice(declared, 1);
|
|
121
|
+
methods.push(action);
|
|
117
122
|
}
|
|
118
123
|
return templates;
|
|
119
124
|
};
|
|
@@ -234,7 +239,8 @@ function dbusEntrypoint(options) {
|
|
|
234
239
|
flags |= dbus_sdk_1.RequestNameFlags.DBUS_NAME_FLAG_REPLACE_EXISTING;
|
|
235
240
|
if (options.allowReplacement)
|
|
236
241
|
flags |= dbus_sdk_1.RequestNameFlags.DBUS_NAME_FLAG_ALLOW_REPLACEMENT;
|
|
237
|
-
|
|
242
|
+
//The 64-bit integers as numbers, as the client gives them: the schemas of numbers accept them
|
|
243
|
+
const result = await localService.run({ ...connect, decode: { int64: 'number', ...options.decode }, flags: flags });
|
|
238
244
|
if (result !== dbus_sdk_1.RequestNameResultCode.DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER && result !== dbus_sdk_1.RequestNameResultCode.DBUS_REQUEST_NAME_REPLY_ALREADY_OWNER) {
|
|
239
245
|
await localService.stop().catch(() => undefined);
|
|
240
246
|
throw new Error(`The DBus name ${options.service} is owned by another service`);
|
|
@@ -28,9 +28,11 @@ declare module '@lakutata/entrypoint' {
|
|
|
28
28
|
}
|
|
29
29
|
/**
|
|
30
30
|
* The definition of the D-Bus channel of the entrypoint, named `dbus`: its actions are the methods of D-Bus interfaces,
|
|
31
|
-
* addressed by `{member, interface, path}` (the method name from `@DBusMethod`, the interface and the path template
|
|
32
|
-
* `@DBusInterface`). A call matches an action by its interface, its method and its object path; a call matching
|
|
33
|
-
* fails with `ControllerActionNotFoundException
|
|
34
|
-
*
|
|
31
|
+
* addressed by `{member, interface, path}` (the method name from `@DBusMethod`, the interface and the path template
|
|
32
|
+
* from `@DBusInterface`). A call matches an action by its interface, its method and its object path; a call matching
|
|
33
|
+
* none fails with `ControllerActionNotFoundException` in an adapter dispatching through the channel (with
|
|
34
|
+
* {@link dbusEntrypoint}, the bus answers such a call with `org.freedesktop.DBus.Error.UnknownObject` or
|
|
35
|
+
* `UnknownMethod`).
|
|
36
|
+
* `@DBusMethod` and {@link dbusEntrypoint} use it: an application only needs it to write an adapter of its own.
|
|
35
37
|
*/
|
|
36
38
|
export declare const DBusChannel: ChannelDefinition;
|
|
@@ -16,10 +16,12 @@ function addressKey(interfaceName, member, path) {
|
|
|
16
16
|
}
|
|
17
17
|
/**
|
|
18
18
|
* The definition of the D-Bus channel of the entrypoint, named `dbus`: its actions are the methods of D-Bus interfaces,
|
|
19
|
-
* addressed by `{member, interface, path}` (the method name from `@DBusMethod`, the interface and the path template
|
|
20
|
-
* `@DBusInterface`). A call matches an action by its interface, its method and its object path; a call matching
|
|
21
|
-
* fails with `ControllerActionNotFoundException
|
|
22
|
-
*
|
|
19
|
+
* addressed by `{member, interface, path}` (the method name from `@DBusMethod`, the interface and the path template
|
|
20
|
+
* from `@DBusInterface`). A call matches an action by its interface, its method and its object path; a call matching
|
|
21
|
+
* none fails with `ControllerActionNotFoundException` in an adapter dispatching through the channel (with
|
|
22
|
+
* {@link dbusEntrypoint}, the bus answers such a call with `org.freedesktop.DBus.Error.UnknownObject` or
|
|
23
|
+
* `UnknownMethod`).
|
|
24
|
+
* `@DBusMethod` and {@link dbusEntrypoint} use it: an application only needs it to write an adapter of its own.
|
|
23
25
|
*/
|
|
24
26
|
exports.DBusChannel = (0, channel_1.hasChannel)('dbus') ? (0, channel_1.getChannel)('dbus') : (0, channel_1.defineChannel)({
|
|
25
27
|
name: 'dbus',
|
|
@@ -27,7 +27,7 @@ export type DBusMethodOptions = ActionOptions<DBusContext> & {
|
|
|
27
27
|
* (`com.example.DeviceService.E_OFFLINE`), with its message; an `errno` already containing dots is used as it is.
|
|
28
28
|
* The same method may also carry the decorators of the other channels (`@ServiceAction`, `@GET`…).
|
|
29
29
|
* @param name The name of the method (`'Reboot'`): letters, digits and `_`, not starting with a digit; 255 characters at
|
|
30
|
-
* most.
|
|
30
|
+
* most. A name declared twice in an interface is served by the declaration registered last, as on the other channels.
|
|
31
31
|
* @returns The method decorator.
|
|
32
32
|
* @throws {TypeError} When the method is declared, if its name is invalid; when the adapter starts, if its controller
|
|
33
33
|
* has no `@DBusInterface`.
|
|
@@ -76,7 +76,7 @@ export declare function DBusMethod<ClassPrototype extends Controller, Method>(na
|
|
|
76
76
|
* of the object path template are merged into it. An error thrown by the action is answered as a D-Bus error named
|
|
77
77
|
* after its `errno`, prefixed by the service name (`com.example.DeviceService.E_OFFLINE`).
|
|
78
78
|
* @param name The name of the method (`'Reboot'`): letters, digits and `_`, not starting with a digit; 255 characters at
|
|
79
|
-
* most.
|
|
79
|
+
* most. A name declared twice in an interface is served by the declaration registered last, as on the other channels.
|
|
80
80
|
* @param dtoConstructor The DTO of the method's input, given by the client as an `a{sv}` dictionary.
|
|
81
81
|
* @returns The method decorator.
|
|
82
82
|
* @throws {TypeError} When the method is declared, if its name is invalid; when the adapter starts, if its controller
|
|
@@ -90,7 +90,7 @@ export declare function DBusMethod<ClassPrototype extends Controller, Method, DT
|
|
|
90
90
|
* of the object path template are merged into it. An error thrown by the action is answered as a D-Bus error named
|
|
91
91
|
* after its `errno`, prefixed by the service name (`com.example.DeviceService.E_OFFLINE`).
|
|
92
92
|
* @param name The name of the method (`'Reboot'`): letters, digits and `_`, not starting with a digit; 255 characters at
|
|
93
|
-
* most.
|
|
93
|
+
* most. A name declared twice in an interface is served by the declaration registered last, as on the other channels.
|
|
94
94
|
* @param options The options of the action, and the type of its result (`returns`).
|
|
95
95
|
* @returns The method decorator.
|
|
96
96
|
* @throws {TypeError} When the method is declared, if its name is invalid; when the adapter starts, if its controller
|
|
@@ -105,7 +105,7 @@ export declare function DBusMethod<ClassPrototype extends Controller, Method>(na
|
|
|
105
105
|
* of the object path template are merged into it. An error thrown by the action is answered as a D-Bus error named
|
|
106
106
|
* after its `errno`, prefixed by the service name (`com.example.DeviceService.E_OFFLINE`).
|
|
107
107
|
* @param name The name of the method (`'Reboot'`): letters, digits and `_`, not starting with a digit; 255 characters at
|
|
108
|
-
* most.
|
|
108
|
+
* most. A name declared twice in an interface is served by the declaration registered last, as on the other channels.
|
|
109
109
|
* @param dtoConstructor The DTO of the method's input, given by the client as an `a{sv}` dictionary.
|
|
110
110
|
* @param options The options of the action, and the type of its result (`returns`).
|
|
111
111
|
* @returns The method decorator.
|
|
@@ -4,6 +4,7 @@ exports.DBusMethod = DBusMethod;
|
|
|
4
4
|
const channel_1 = require("@lakutata/entrypoint/channel");
|
|
5
5
|
const DBusChannel_js_1 = require("../channel/DBusChannel.js");
|
|
6
6
|
const Signature_js_1 = require("../lib/Signature.js");
|
|
7
|
+
const Names_js_1 = require("../lib/Names.js");
|
|
7
8
|
function DBusMethod(name, dtoConstructorOrOptions, options) {
|
|
8
9
|
return (target, propertyKey, descriptor) => {
|
|
9
10
|
const isDTO = typeof dtoConstructorOrOptions === 'function';
|
|
@@ -13,7 +14,7 @@ function DBusMethod(name, dtoConstructorOrOptions, options) {
|
|
|
13
14
|
: (0, channel_1.getActionDTOAndOptions)(actionOptions);
|
|
14
15
|
const pattern = { member: name };
|
|
15
16
|
if (returns !== undefined)
|
|
16
|
-
pattern.returns = typeof returns === 'string' ? returns : (0, Signature_js_1.schemaSignature)(returns);
|
|
17
|
+
pattern.returns = typeof returns === 'string' ? (0, Names_js_1.checkSingleType)(returns, `the result of the method ${name}`) : (0, Signature_js_1.schemaSignature)(returns);
|
|
17
18
|
(0, channel_1.registerControllerAction)(DBusChannel_js_1.DBusChannel, target, propertyKey, pattern, dtoConstructor, validatedOptions);
|
|
18
19
|
return descriptor;
|
|
19
20
|
};
|
|
@@ -17,7 +17,7 @@ export interface DBusClientOptions {
|
|
|
17
17
|
timeout?: number;
|
|
18
18
|
/**
|
|
19
19
|
* How long a call may wait for its reply, in milliseconds (a positive integer); a call's own `timeout` overrides it.
|
|
20
|
-
* @default
|
|
20
|
+
* @default none: a call waits for its reply
|
|
21
21
|
*/
|
|
22
22
|
callTimeout?: number;
|
|
23
23
|
/**
|
|
@@ -151,11 +151,12 @@ export declare function defineDBusSignal<T = Record<string, unknown>>(name: stri
|
|
|
151
151
|
* @typeParam T The values of the property.
|
|
152
152
|
* @param name The member name of the property (`'State'`): letters, digits and `_`, not starting with a digit; 255
|
|
153
153
|
* characters at most.
|
|
154
|
-
* @param type The D-Bus signature of its values (`'s'`, `'i'`, `'b'`, `'a{sv}'`…); it is not
|
|
154
|
+
* @param type The D-Bus signature of its values (`'s'`, `'i'`, `'b'`, `'a{sv}'`…), a single complete type; it is not
|
|
155
|
+
* checked against `schema`.
|
|
155
156
|
* @param options Whether the clients may write it, its initial value and the schema of its values; none by default
|
|
156
157
|
* (read-only, no initial value, every value accepted).
|
|
157
158
|
* @returns The property's definition.
|
|
158
|
-
* @throws {TypeError} When the name is invalid.
|
|
159
|
+
* @throws {TypeError} When the name or the signature is invalid.
|
|
159
160
|
* @example
|
|
160
161
|
* ```typescript
|
|
161
162
|
* import {DTO} from 'lakutata'
|
|
@@ -45,6 +45,8 @@ const Signature_js_1 = require("./Signature.js");
|
|
|
45
45
|
function defineDBusSignal(name, data = 'a{sv}') {
|
|
46
46
|
(0, Names_js_1.checkMemberName)(name, 'signal');
|
|
47
47
|
const isDTO = typeof data === 'function';
|
|
48
|
+
if (!isDTO)
|
|
49
|
+
(0, Names_js_1.checkSingleType)(data, `the signal ${name}`);
|
|
48
50
|
return {
|
|
49
51
|
kind: 'signal',
|
|
50
52
|
name: name,
|
|
@@ -59,11 +61,12 @@ function defineDBusSignal(name, data = 'a{sv}') {
|
|
|
59
61
|
* @typeParam T The values of the property.
|
|
60
62
|
* @param name The member name of the property (`'State'`): letters, digits and `_`, not starting with a digit; 255
|
|
61
63
|
* characters at most.
|
|
62
|
-
* @param type The D-Bus signature of its values (`'s'`, `'i'`, `'b'`, `'a{sv}'`…); it is not
|
|
64
|
+
* @param type The D-Bus signature of its values (`'s'`, `'i'`, `'b'`, `'a{sv}'`…), a single complete type; it is not
|
|
65
|
+
* checked against `schema`.
|
|
63
66
|
* @param options Whether the clients may write it, its initial value and the schema of its values; none by default
|
|
64
67
|
* (read-only, no initial value, every value accepted).
|
|
65
68
|
* @returns The property's definition.
|
|
66
|
-
* @throws {TypeError} When the name is invalid.
|
|
69
|
+
* @throws {TypeError} When the name or the signature is invalid.
|
|
67
70
|
* @example
|
|
68
71
|
* ```typescript
|
|
69
72
|
* import {DTO} from 'lakutata'
|
|
@@ -83,6 +86,7 @@ function defineDBusSignal(name, data = 'a{sv}') {
|
|
|
83
86
|
*/
|
|
84
87
|
function defineDBusProperty(name, type, options = {}) {
|
|
85
88
|
(0, Names_js_1.checkMemberName)(name, 'property');
|
|
89
|
+
(0, Names_js_1.checkSingleType)(type, `the property ${name}`);
|
|
86
90
|
const schema = options.schema;
|
|
87
91
|
return {
|
|
88
92
|
kind: 'property',
|
package/dist/cjs/lib/Names.d.ts
CHANGED
|
@@ -3,6 +3,18 @@
|
|
|
3
3
|
* @param name
|
|
4
4
|
*/
|
|
5
5
|
export declare function checkInterfaceName(name: string): string;
|
|
6
|
+
/**
|
|
7
|
+
* Check the well-known name of a service. D-Bus allows "-" in the bus names, but the names of the errors are prefixed by
|
|
8
|
+
* the name of the service, and an error name may not hold it
|
|
9
|
+
* @param name
|
|
10
|
+
*/
|
|
11
|
+
export declare function checkServiceName(name: string): string;
|
|
12
|
+
/**
|
|
13
|
+
* Check a D-Bus signature holding a single complete type (the type of a property, of a signal's data, of a result)
|
|
14
|
+
* @param signature
|
|
15
|
+
* @param what what the type is of, for the message
|
|
16
|
+
*/
|
|
17
|
+
export declare function checkSingleType(signature: string, what: string): string;
|
|
6
18
|
/**
|
|
7
19
|
* Check the name of a member
|
|
8
20
|
* @param name
|
package/dist/cjs/lib/Names.js
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.checkInterfaceName = checkInterfaceName;
|
|
4
|
+
exports.checkServiceName = checkServiceName;
|
|
5
|
+
exports.checkSingleType = checkSingleType;
|
|
4
6
|
exports.checkMemberName = checkMemberName;
|
|
5
7
|
exports.checkPathTemplate = checkPathTemplate;
|
|
6
8
|
exports.compilePathTemplate = compilePathTemplate;
|
|
7
9
|
exports.errorName = errorName;
|
|
10
|
+
const dbus_sdk_1 = require("dbus-sdk");
|
|
8
11
|
/**
|
|
9
12
|
* The names of the interfaces and of the errors: dot-separated elements
|
|
10
13
|
*/
|
|
@@ -26,6 +29,33 @@ function checkInterfaceName(name) {
|
|
|
26
29
|
throw new TypeError(`Invalid DBus interface name "${name}": dot-separated elements of letters, digits and "_", such as "com.example.Device"`);
|
|
27
30
|
return name;
|
|
28
31
|
}
|
|
32
|
+
/**
|
|
33
|
+
* Check the well-known name of a service. D-Bus allows "-" in the bus names, but the names of the errors are prefixed by
|
|
34
|
+
* the name of the service, and an error name may not hold it
|
|
35
|
+
* @param name
|
|
36
|
+
*/
|
|
37
|
+
function checkServiceName(name) {
|
|
38
|
+
if (typeof name !== 'string' || name.length > 255 || !INTERFACE_NAME.test(name))
|
|
39
|
+
throw new TypeError(`Invalid DBus service name "${name}": dot-separated elements of letters, digits and "_", such as "com.example.DeviceService" (it prefixes the names of the errors, which may not hold "-")`);
|
|
40
|
+
return name;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Check a D-Bus signature holding a single complete type (the type of a property, of a signal's data, of a result)
|
|
44
|
+
* @param signature
|
|
45
|
+
* @param what what the type is of, for the message
|
|
46
|
+
*/
|
|
47
|
+
function checkSingleType(signature, what) {
|
|
48
|
+
let types;
|
|
49
|
+
try {
|
|
50
|
+
types = dbus_sdk_1.Signature.parse(signature).length;
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
throw new TypeError(`Invalid DBus signature "${signature}" of ${what}: ${error.message}`, { cause: error });
|
|
54
|
+
}
|
|
55
|
+
if (types !== 1)
|
|
56
|
+
throw new TypeError(`Invalid DBus signature "${signature}" of ${what}: a single complete type, such as "s" or "a{sv}"`);
|
|
57
|
+
return signature;
|
|
58
|
+
}
|
|
29
59
|
/**
|
|
30
60
|
* Check the name of a member
|
|
31
61
|
* @param name
|
|
@@ -76,5 +106,6 @@ function errorName(error) {
|
|
|
76
106
|
if (INTERFACE_NAME.test(candidate))
|
|
77
107
|
return candidate;
|
|
78
108
|
const element = candidate.replace(/[^A-Za-z0-9_]/g, '_');
|
|
79
|
-
|
|
109
|
+
//An element of an error name starts with a letter
|
|
110
|
+
return /^[A-Za-z]/.test(element) ? element : `E${element}`;
|
|
80
111
|
}
|
|
@@ -5,7 +5,7 @@ import { DBusChannel } from '../channel/DBusChannel.js';
|
|
|
5
5
|
import { DBusContext } from '../context/DBusContext.js';
|
|
6
6
|
import { ATTACH, DBusObjectManager, STORE } from '../components/DBusObjectManager.js';
|
|
7
7
|
import { getDBusInterfaceDeclaration } from '../lib/Declarations.js';
|
|
8
|
-
import {
|
|
8
|
+
import { checkServiceName, compilePathTemplate, errorName } from '../lib/Names.js';
|
|
9
9
|
import { toEncodable, schemaSignature } from '../lib/Signature.js';
|
|
10
10
|
/**
|
|
11
11
|
* The connection options of a bus
|
|
@@ -75,7 +75,7 @@ function toMethodError(error) {
|
|
|
75
75
|
* ```
|
|
76
76
|
*/
|
|
77
77
|
export function dbusEntrypoint(options) {
|
|
78
|
-
|
|
78
|
+
checkServiceName(options.service);
|
|
79
79
|
let service;
|
|
80
80
|
let detach;
|
|
81
81
|
const exported = new Map();
|
|
@@ -107,7 +107,12 @@ export function dbusEntrypoint(options) {
|
|
|
107
107
|
const { path, interface: interfaceName, member } = action.address;
|
|
108
108
|
if (path === undefined || interfaceName === undefined)
|
|
109
109
|
throw new TypeError(`The DBus method ${member} of ${action.controller} has no interface: declare its controller with @DBusInterface`);
|
|
110
|
-
|
|
110
|
+
//A method declared twice in an interface is served by the declaration registered last
|
|
111
|
+
const methods = interfacesAt(path, interfaceName).methods;
|
|
112
|
+
const declared = methods.findIndex((method) => method.address.member === member);
|
|
113
|
+
if (declared >= 0)
|
|
114
|
+
methods.splice(declared, 1);
|
|
115
|
+
methods.push(action);
|
|
111
116
|
}
|
|
112
117
|
return templates;
|
|
113
118
|
};
|
|
@@ -228,7 +233,8 @@ export function dbusEntrypoint(options) {
|
|
|
228
233
|
flags |= RequestNameFlags.DBUS_NAME_FLAG_REPLACE_EXISTING;
|
|
229
234
|
if (options.allowReplacement)
|
|
230
235
|
flags |= RequestNameFlags.DBUS_NAME_FLAG_ALLOW_REPLACEMENT;
|
|
231
|
-
|
|
236
|
+
//The 64-bit integers as numbers, as the client gives them: the schemas of numbers accept them
|
|
237
|
+
const result = await localService.run({ ...connect, decode: { int64: 'number', ...options.decode }, flags: flags });
|
|
232
238
|
if (result !== RequestNameResultCode.DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER && result !== RequestNameResultCode.DBUS_REQUEST_NAME_REPLY_ALREADY_OWNER) {
|
|
233
239
|
await localService.stop().catch(() => undefined);
|
|
234
240
|
throw new Error(`The DBus name ${options.service} is owned by another service`);
|
|
@@ -13,10 +13,12 @@ function addressKey(interfaceName, member, path) {
|
|
|
13
13
|
}
|
|
14
14
|
/**
|
|
15
15
|
* The definition of the D-Bus channel of the entrypoint, named `dbus`: its actions are the methods of D-Bus interfaces,
|
|
16
|
-
* addressed by `{member, interface, path}` (the method name from `@DBusMethod`, the interface and the path template
|
|
17
|
-
* `@DBusInterface`). A call matches an action by its interface, its method and its object path; a call matching
|
|
18
|
-
* fails with `ControllerActionNotFoundException
|
|
19
|
-
*
|
|
16
|
+
* addressed by `{member, interface, path}` (the method name from `@DBusMethod`, the interface and the path template
|
|
17
|
+
* from `@DBusInterface`). A call matches an action by its interface, its method and its object path; a call matching
|
|
18
|
+
* none fails with `ControllerActionNotFoundException` in an adapter dispatching through the channel (with
|
|
19
|
+
* {@link dbusEntrypoint}, the bus answers such a call with `org.freedesktop.DBus.Error.UnknownObject` or
|
|
20
|
+
* `UnknownMethod`).
|
|
21
|
+
* `@DBusMethod` and {@link dbusEntrypoint} use it: an application only needs it to write an adapter of its own.
|
|
20
22
|
*/
|
|
21
23
|
export const DBusChannel = hasChannel('dbus') ? getChannel('dbus') : defineChannel({
|
|
22
24
|
name: 'dbus',
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { getActionDTOAndOptions, registerControllerAction } from '@lakutata/entrypoint/channel';
|
|
2
2
|
import { DBusChannel } from '../channel/DBusChannel.js';
|
|
3
3
|
import { schemaSignature } from '../lib/Signature.js';
|
|
4
|
+
import { checkSingleType } from '../lib/Names.js';
|
|
4
5
|
export function DBusMethod(name, dtoConstructorOrOptions, options) {
|
|
5
6
|
return (target, propertyKey, descriptor) => {
|
|
6
7
|
const isDTO = typeof dtoConstructorOrOptions === 'function';
|
|
@@ -10,7 +11,7 @@ export function DBusMethod(name, dtoConstructorOrOptions, options) {
|
|
|
10
11
|
: getActionDTOAndOptions(actionOptions);
|
|
11
12
|
const pattern = { member: name };
|
|
12
13
|
if (returns !== undefined)
|
|
13
|
-
pattern.returns = typeof returns === 'string' ? returns : schemaSignature(returns);
|
|
14
|
+
pattern.returns = typeof returns === 'string' ? checkSingleType(returns, `the result of the method ${name}`) : schemaSignature(returns);
|
|
14
15
|
registerControllerAction(DBusChannel, target, propertyKey, pattern, dtoConstructor, validatedOptions);
|
|
15
16
|
return descriptor;
|
|
16
17
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { DTO } from '@lakutata/core';
|
|
2
|
-
import { checkInterfaceName, checkMemberName, checkPathTemplate } from './Names.js';
|
|
2
|
+
import { checkInterfaceName, checkMemberName, checkPathTemplate, checkSingleType } from './Names.js';
|
|
3
3
|
import { schemaSignature } from './Signature.js';
|
|
4
4
|
/**
|
|
5
5
|
* Declare a D-Bus signal, to list in the `signals` of one or more `@DBusInterface`. The signal has one argument: a
|
|
@@ -39,6 +39,8 @@ import { schemaSignature } from './Signature.js';
|
|
|
39
39
|
export function defineDBusSignal(name, data = 'a{sv}') {
|
|
40
40
|
checkMemberName(name, 'signal');
|
|
41
41
|
const isDTO = typeof data === 'function';
|
|
42
|
+
if (!isDTO)
|
|
43
|
+
checkSingleType(data, `the signal ${name}`);
|
|
42
44
|
return {
|
|
43
45
|
kind: 'signal',
|
|
44
46
|
name: name,
|
|
@@ -53,11 +55,12 @@ export function defineDBusSignal(name, data = 'a{sv}') {
|
|
|
53
55
|
* @typeParam T The values of the property.
|
|
54
56
|
* @param name The member name of the property (`'State'`): letters, digits and `_`, not starting with a digit; 255
|
|
55
57
|
* characters at most.
|
|
56
|
-
* @param type The D-Bus signature of its values (`'s'`, `'i'`, `'b'`, `'a{sv}'`…); it is not
|
|
58
|
+
* @param type The D-Bus signature of its values (`'s'`, `'i'`, `'b'`, `'a{sv}'`…), a single complete type; it is not
|
|
59
|
+
* checked against `schema`.
|
|
57
60
|
* @param options Whether the clients may write it, its initial value and the schema of its values; none by default
|
|
58
61
|
* (read-only, no initial value, every value accepted).
|
|
59
62
|
* @returns The property's definition.
|
|
60
|
-
* @throws {TypeError} When the name is invalid.
|
|
63
|
+
* @throws {TypeError} When the name or the signature is invalid.
|
|
61
64
|
* @example
|
|
62
65
|
* ```typescript
|
|
63
66
|
* import {DTO} from 'lakutata'
|
|
@@ -77,6 +80,7 @@ export function defineDBusSignal(name, data = 'a{sv}') {
|
|
|
77
80
|
*/
|
|
78
81
|
export function defineDBusProperty(name, type, options = {}) {
|
|
79
82
|
checkMemberName(name, 'property');
|
|
83
|
+
checkSingleType(type, `the property ${name}`);
|
|
80
84
|
const schema = options.schema;
|
|
81
85
|
return {
|
|
82
86
|
kind: 'property',
|
package/dist/esm/lib/Names.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Signature } from 'dbus-sdk';
|
|
1
2
|
/**
|
|
2
3
|
* The names of the interfaces and of the errors: dot-separated elements
|
|
3
4
|
*/
|
|
@@ -19,6 +20,33 @@ export function checkInterfaceName(name) {
|
|
|
19
20
|
throw new TypeError(`Invalid DBus interface name "${name}": dot-separated elements of letters, digits and "_", such as "com.example.Device"`);
|
|
20
21
|
return name;
|
|
21
22
|
}
|
|
23
|
+
/**
|
|
24
|
+
* Check the well-known name of a service. D-Bus allows "-" in the bus names, but the names of the errors are prefixed by
|
|
25
|
+
* the name of the service, and an error name may not hold it
|
|
26
|
+
* @param name
|
|
27
|
+
*/
|
|
28
|
+
export function checkServiceName(name) {
|
|
29
|
+
if (typeof name !== 'string' || name.length > 255 || !INTERFACE_NAME.test(name))
|
|
30
|
+
throw new TypeError(`Invalid DBus service name "${name}": dot-separated elements of letters, digits and "_", such as "com.example.DeviceService" (it prefixes the names of the errors, which may not hold "-")`);
|
|
31
|
+
return name;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Check a D-Bus signature holding a single complete type (the type of a property, of a signal's data, of a result)
|
|
35
|
+
* @param signature
|
|
36
|
+
* @param what what the type is of, for the message
|
|
37
|
+
*/
|
|
38
|
+
export function checkSingleType(signature, what) {
|
|
39
|
+
let types;
|
|
40
|
+
try {
|
|
41
|
+
types = Signature.parse(signature).length;
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
throw new TypeError(`Invalid DBus signature "${signature}" of ${what}: ${error.message}`, { cause: error });
|
|
45
|
+
}
|
|
46
|
+
if (types !== 1)
|
|
47
|
+
throw new TypeError(`Invalid DBus signature "${signature}" of ${what}: a single complete type, such as "s" or "a{sv}"`);
|
|
48
|
+
return signature;
|
|
49
|
+
}
|
|
22
50
|
/**
|
|
23
51
|
* Check the name of a member
|
|
24
52
|
* @param name
|
|
@@ -69,5 +97,6 @@ export function errorName(error) {
|
|
|
69
97
|
if (INTERFACE_NAME.test(candidate))
|
|
70
98
|
return candidate;
|
|
71
99
|
const element = candidate.replace(/[^A-Za-z0-9_]/g, '_');
|
|
72
|
-
|
|
100
|
+
//An element of an error name starts with a letter
|
|
101
|
+
return /^[A-Za-z]/.test(element) ? element : `E${element}`;
|
|
73
102
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { EntrypointAdapter, MiddlewareDefinition } from '@lakutata/entrypoint';
|
|
2
|
+
import { type DecodeOpts } from 'dbus-sdk';
|
|
2
3
|
import { DBusContext } from '../context/DBusContext.js';
|
|
3
4
|
/**
|
|
4
5
|
* The options of {@link dbusEntrypoint}: the bus to connect to, and the service name to own on it.
|
|
@@ -19,9 +20,10 @@ export type DBusEntrypointOptions = {
|
|
|
19
20
|
*/
|
|
20
21
|
bus?: 'session' | 'system' | string;
|
|
21
22
|
/**
|
|
22
|
-
* The well-known name the service owns on the bus (`'com.example.DeviceService'`): two or more elements
|
|
23
|
-
* dots. The clients address the service by it, and it prefixes the names of the D-Bus
|
|
24
|
-
* the
|
|
23
|
+
* The well-known name the service owns on the bus (`'com.example.DeviceService'`): two or more elements of letters,
|
|
24
|
+
* digits and `_`, separated by dots. The clients address the service by it, and it prefixes the names of the D-Bus
|
|
25
|
+
* errors (which may not hold the `-` that D-Bus allows in the names of the services). On the system bus, the bus
|
|
26
|
+
* policy (`/etc/dbus-1/system.d/`) must allow the process to own it.
|
|
25
27
|
*/
|
|
26
28
|
service: string;
|
|
27
29
|
/**
|
|
@@ -32,10 +34,17 @@ export type DBusEntrypointOptions = {
|
|
|
32
34
|
replaceExisting?: boolean;
|
|
33
35
|
/**
|
|
34
36
|
* Let another connection take the name with `replaceExisting`. The adapter is not stopped when the name is taken:
|
|
35
|
-
* the clients reach the new owner
|
|
37
|
+
* the clients reach the new owner, and the adapter keeps running without the name, which it does not get back when
|
|
38
|
+
* the new owner releases it.
|
|
36
39
|
* @default false
|
|
37
40
|
*/
|
|
38
41
|
allowReplacement?: boolean;
|
|
42
|
+
/**
|
|
43
|
+
* How the arguments of the method calls and the values the clients write to the properties are decoded
|
|
44
|
+
* (`DecodeOpts` of `dbus-sdk`): `types`, `dictionaries`, `int64` (`'number'` or `'bigint'`), as for the client.
|
|
45
|
+
* @default {int64: 'number'} (the 64-bit integers as numbers when they are safe integers, else bigints)
|
|
46
|
+
*/
|
|
47
|
+
decode?: DecodeOpts;
|
|
39
48
|
/**
|
|
40
49
|
* The middlewares of this adapter's method calls, run after the entrypoint's global middlewares.
|
|
41
50
|
* @default []
|
|
@@ -28,9 +28,11 @@ declare module '@lakutata/entrypoint' {
|
|
|
28
28
|
}
|
|
29
29
|
/**
|
|
30
30
|
* The definition of the D-Bus channel of the entrypoint, named `dbus`: its actions are the methods of D-Bus interfaces,
|
|
31
|
-
* addressed by `{member, interface, path}` (the method name from `@DBusMethod`, the interface and the path template
|
|
32
|
-
* `@DBusInterface`). A call matches an action by its interface, its method and its object path; a call matching
|
|
33
|
-
* fails with `ControllerActionNotFoundException
|
|
34
|
-
*
|
|
31
|
+
* addressed by `{member, interface, path}` (the method name from `@DBusMethod`, the interface and the path template
|
|
32
|
+
* from `@DBusInterface`). A call matches an action by its interface, its method and its object path; a call matching
|
|
33
|
+
* none fails with `ControllerActionNotFoundException` in an adapter dispatching through the channel (with
|
|
34
|
+
* {@link dbusEntrypoint}, the bus answers such a call with `org.freedesktop.DBus.Error.UnknownObject` or
|
|
35
|
+
* `UnknownMethod`).
|
|
36
|
+
* `@DBusMethod` and {@link dbusEntrypoint} use it: an application only needs it to write an adapter of its own.
|
|
35
37
|
*/
|
|
36
38
|
export declare const DBusChannel: ChannelDefinition;
|
|
@@ -27,7 +27,7 @@ export type DBusMethodOptions = ActionOptions<DBusContext> & {
|
|
|
27
27
|
* (`com.example.DeviceService.E_OFFLINE`), with its message; an `errno` already containing dots is used as it is.
|
|
28
28
|
* The same method may also carry the decorators of the other channels (`@ServiceAction`, `@GET`…).
|
|
29
29
|
* @param name The name of the method (`'Reboot'`): letters, digits and `_`, not starting with a digit; 255 characters at
|
|
30
|
-
* most.
|
|
30
|
+
* most. A name declared twice in an interface is served by the declaration registered last, as on the other channels.
|
|
31
31
|
* @returns The method decorator.
|
|
32
32
|
* @throws {TypeError} When the method is declared, if its name is invalid; when the adapter starts, if its controller
|
|
33
33
|
* has no `@DBusInterface`.
|
|
@@ -76,7 +76,7 @@ export declare function DBusMethod<ClassPrototype extends Controller, Method>(na
|
|
|
76
76
|
* of the object path template are merged into it. An error thrown by the action is answered as a D-Bus error named
|
|
77
77
|
* after its `errno`, prefixed by the service name (`com.example.DeviceService.E_OFFLINE`).
|
|
78
78
|
* @param name The name of the method (`'Reboot'`): letters, digits and `_`, not starting with a digit; 255 characters at
|
|
79
|
-
* most.
|
|
79
|
+
* most. A name declared twice in an interface is served by the declaration registered last, as on the other channels.
|
|
80
80
|
* @param dtoConstructor The DTO of the method's input, given by the client as an `a{sv}` dictionary.
|
|
81
81
|
* @returns The method decorator.
|
|
82
82
|
* @throws {TypeError} When the method is declared, if its name is invalid; when the adapter starts, if its controller
|
|
@@ -90,7 +90,7 @@ export declare function DBusMethod<ClassPrototype extends Controller, Method, DT
|
|
|
90
90
|
* of the object path template are merged into it. An error thrown by the action is answered as a D-Bus error named
|
|
91
91
|
* after its `errno`, prefixed by the service name (`com.example.DeviceService.E_OFFLINE`).
|
|
92
92
|
* @param name The name of the method (`'Reboot'`): letters, digits and `_`, not starting with a digit; 255 characters at
|
|
93
|
-
* most.
|
|
93
|
+
* most. A name declared twice in an interface is served by the declaration registered last, as on the other channels.
|
|
94
94
|
* @param options The options of the action, and the type of its result (`returns`).
|
|
95
95
|
* @returns The method decorator.
|
|
96
96
|
* @throws {TypeError} When the method is declared, if its name is invalid; when the adapter starts, if its controller
|
|
@@ -105,7 +105,7 @@ export declare function DBusMethod<ClassPrototype extends Controller, Method>(na
|
|
|
105
105
|
* of the object path template are merged into it. An error thrown by the action is answered as a D-Bus error named
|
|
106
106
|
* after its `errno`, prefixed by the service name (`com.example.DeviceService.E_OFFLINE`).
|
|
107
107
|
* @param name The name of the method (`'Reboot'`): letters, digits and `_`, not starting with a digit; 255 characters at
|
|
108
|
-
* most.
|
|
108
|
+
* most. A name declared twice in an interface is served by the declaration registered last, as on the other channels.
|
|
109
109
|
* @param dtoConstructor The DTO of the method's input, given by the client as an `a{sv}` dictionary.
|
|
110
110
|
* @param options The options of the action, and the type of its result (`returns`).
|
|
111
111
|
* @returns The method decorator.
|
|
@@ -17,7 +17,7 @@ export interface DBusClientOptions {
|
|
|
17
17
|
timeout?: number;
|
|
18
18
|
/**
|
|
19
19
|
* How long a call may wait for its reply, in milliseconds (a positive integer); a call's own `timeout` overrides it.
|
|
20
|
-
* @default
|
|
20
|
+
* @default none: a call waits for its reply
|
|
21
21
|
*/
|
|
22
22
|
callTimeout?: number;
|
|
23
23
|
/**
|
|
@@ -151,11 +151,12 @@ export declare function defineDBusSignal<T = Record<string, unknown>>(name: stri
|
|
|
151
151
|
* @typeParam T The values of the property.
|
|
152
152
|
* @param name The member name of the property (`'State'`): letters, digits and `_`, not starting with a digit; 255
|
|
153
153
|
* characters at most.
|
|
154
|
-
* @param type The D-Bus signature of its values (`'s'`, `'i'`, `'b'`, `'a{sv}'`…); it is not
|
|
154
|
+
* @param type The D-Bus signature of its values (`'s'`, `'i'`, `'b'`, `'a{sv}'`…), a single complete type; it is not
|
|
155
|
+
* checked against `schema`.
|
|
155
156
|
* @param options Whether the clients may write it, its initial value and the schema of its values; none by default
|
|
156
157
|
* (read-only, no initial value, every value accepted).
|
|
157
158
|
* @returns The property's definition.
|
|
158
|
-
* @throws {TypeError} When the name is invalid.
|
|
159
|
+
* @throws {TypeError} When the name or the signature is invalid.
|
|
159
160
|
* @example
|
|
160
161
|
* ```typescript
|
|
161
162
|
* import {DTO} from 'lakutata'
|
|
@@ -3,6 +3,18 @@
|
|
|
3
3
|
* @param name
|
|
4
4
|
*/
|
|
5
5
|
export declare function checkInterfaceName(name: string): string;
|
|
6
|
+
/**
|
|
7
|
+
* Check the well-known name of a service. D-Bus allows "-" in the bus names, but the names of the errors are prefixed by
|
|
8
|
+
* the name of the service, and an error name may not hold it
|
|
9
|
+
* @param name
|
|
10
|
+
*/
|
|
11
|
+
export declare function checkServiceName(name: string): string;
|
|
12
|
+
/**
|
|
13
|
+
* Check a D-Bus signature holding a single complete type (the type of a property, of a signal's data, of a result)
|
|
14
|
+
* @param signature
|
|
15
|
+
* @param what what the type is of, for the message
|
|
16
|
+
*/
|
|
17
|
+
export declare function checkSingleType(signature: string, what: string): string;
|
|
6
18
|
/**
|
|
7
19
|
* Check the name of a member
|
|
8
20
|
* @param name
|
package/package.json
CHANGED
|
@@ -1,7 +1,26 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lakutata/dbus",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.1.0",
|
|
4
4
|
"description": "The DBus channel of the lakutata framework: controller actions served as the methods, properties and signals of DBus objects, and the DBus client component.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"lakutata",
|
|
7
|
+
"dbus",
|
|
8
|
+
"d-bus",
|
|
9
|
+
"linux",
|
|
10
|
+
"ipc",
|
|
11
|
+
"typescript"
|
|
12
|
+
],
|
|
13
|
+
"homepage": "https://lakutata.com/packages/dbus",
|
|
14
|
+
"bugs": {
|
|
15
|
+
"url": "https://github.com/lakutata/lakutata/issues",
|
|
16
|
+
"email": "admin@lakutata.com"
|
|
17
|
+
},
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "https://github.com/lakutata/lakutata.git",
|
|
21
|
+
"directory": "packages/dbus"
|
|
22
|
+
},
|
|
23
|
+
"author": "alex.ma",
|
|
5
24
|
"type": "module",
|
|
6
25
|
"main": "./dist/cjs/exports/DBus.js",
|
|
7
26
|
"types": "./dist/cjs/exports/DBus.d.ts",
|
|
@@ -32,11 +51,11 @@
|
|
|
32
51
|
"dbus-sdk": "^2.0.1"
|
|
33
52
|
},
|
|
34
53
|
"peerDependencies": {
|
|
35
|
-
"@lakutata/core": "3.
|
|
36
|
-
"@lakutata/entrypoint": "3.
|
|
54
|
+
"@lakutata/core": "3.1.0",
|
|
55
|
+
"@lakutata/entrypoint": "3.1.0"
|
|
37
56
|
},
|
|
38
57
|
"engines": {
|
|
39
|
-
"node": ">=
|
|
58
|
+
"node": "^22.13.0 || >=24.11.0"
|
|
40
59
|
},
|
|
41
60
|
"files": [
|
|
42
61
|
"dist",
|