@lakutata/dbus 2.3.1 → 3.0.0-beta.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/LICENSE +23 -0
- package/README.md +253 -25
- package/dist/cjs/adapters/DBusEntrypoint.d.ts +89 -0
- package/dist/cjs/adapters/DBusEntrypoint.js +280 -0
- package/dist/cjs/channel/DBusChannel.d.ts +36 -0
- package/dist/cjs/channel/DBusChannel.js +70 -0
- package/dist/cjs/components/DBusClientComponent.d.ts +494 -0
- package/dist/cjs/components/DBusClientComponent.js +272 -0
- package/dist/cjs/components/DBusObjectManager.d.ts +164 -0
- package/dist/cjs/components/DBusObjectManager.js +200 -0
- package/dist/cjs/context/DBusContext.d.ts +52 -0
- package/dist/cjs/context/DBusContext.js +68 -0
- package/dist/cjs/decorators/DBusInterface.d.ts +74 -0
- package/dist/cjs/decorators/DBusInterface.js +63 -0
- package/dist/cjs/decorators/DBusMethod.d.ts +115 -0
- package/dist/cjs/decorators/DBusMethod.js +20 -0
- package/dist/cjs/exports/DBus.d.ts +11 -0
- package/dist/cjs/exports/DBus.js +42 -0
- package/dist/cjs/lib/DBusClientOptions.d.ts +30 -0
- package/dist/cjs/lib/Declarations.d.ts +187 -0
- package/dist/cjs/lib/Declarations.js +113 -0
- package/dist/cjs/lib/Names.d.ts +35 -0
- package/dist/cjs/lib/Names.js +80 -0
- package/dist/cjs/lib/Signature.d.ts +14 -0
- package/dist/cjs/lib/Signature.js +53 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/types/DBusContextParams.d.ts +22 -0
- package/dist/cjs/types/DBusContextParams.js +2 -0
- package/dist/esm/adapters/DBusEntrypoint.js +274 -0
- package/dist/esm/channel/DBusChannel.js +67 -0
- package/dist/esm/components/DBusClientComponent.js +267 -0
- package/dist/esm/components/DBusObjectManager.js +197 -0
- package/dist/esm/context/DBusContext.js +64 -0
- package/dist/esm/decorators/DBusInterface.js +60 -0
- package/dist/esm/decorators/DBusMethod.js +17 -0
- package/dist/esm/exports/DBus.js +12 -0
- package/dist/esm/lib/DBusClientOptions.js +1 -0
- package/dist/esm/lib/Declarations.js +107 -0
- package/dist/esm/lib/Names.js +73 -0
- package/dist/esm/lib/Signature.js +49 -0
- package/dist/esm/types/DBusContextParams.js +1 -0
- package/dist/types/adapters/DBusEntrypoint.d.ts +89 -0
- package/dist/types/channel/DBusChannel.d.ts +36 -0
- package/dist/types/components/DBusClientComponent.d.ts +494 -0
- package/dist/types/components/DBusObjectManager.d.ts +164 -0
- package/dist/types/context/DBusContext.d.ts +52 -0
- package/dist/types/decorators/DBusInterface.d.ts +74 -0
- package/dist/types/decorators/DBusMethod.d.ts +115 -0
- package/dist/types/exports/DBus.d.ts +11 -0
- package/dist/types/lib/DBusClientOptions.d.ts +30 -0
- package/dist/types/lib/Declarations.d.ts +187 -0
- package/dist/types/lib/Names.d.ts +35 -0
- package/dist/types/lib/Signature.d.ts +14 -0
- package/dist/types/types/DBusContextParams.d.ts +22 -0
- package/package.json +38 -39
- package/CHANGELOG.md +0 -153
- package/dist/CommonExports.d.ts +0 -2
- package/dist/CommonExports.d.ts.map +0 -1
- package/dist/CommonExports.js +0 -44
- package/dist/ComponentExports.d.ts +0 -2
- package/dist/ComponentExports.d.ts.map +0 -1
- package/dist/ComponentExports.js +0 -5
- package/dist/ProviderExports.d.ts +0 -2
- package/dist/ProviderExports.d.ts.map +0 -1
- package/dist/ProviderExports.js +0 -5
- package/dist/components/DBusClientComponent.d.ts +0 -75
- package/dist/components/DBusClientComponent.d.ts.map +0 -1
- package/dist/components/DBusClientComponent.js +0 -125
- package/dist/interfaces/DBusClientOptions.d.ts +0 -7
- package/dist/interfaces/DBusClientOptions.d.ts.map +0 -1
- package/dist/providers/DBusClientProvider.d.ts +0 -75
- package/dist/providers/DBusClientProvider.d.ts.map +0 -1
- package/dist/providers/DBusClientProvider.js +0 -125
- package/dist/tests/DBusTest.spec.d.ts +0 -2
- package/dist/tests/DBusTest.spec.d.ts.map +0 -1
- package/dist/tests/DBusTest.spec.js +0 -26
- package/dist/tests/Test.d.ts +0 -9
- package/dist/tests/Test.d.ts.map +0 -1
- package/dist/tests/Test.js +0 -80
- package/tsconfig.json +0 -7
- /package/dist/{interfaces → cjs/lib}/DBusClientOptions.js +0 -0
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.DBusClientComponent = exports.buildDBusClientOptions = void 0;
|
|
13
|
+
const dbus_sdk_1 = require("dbus-sdk");
|
|
14
|
+
const core_1 = require("@lakutata/core");
|
|
15
|
+
const di_1 = require("@lakutata/core/decorator/di");
|
|
16
|
+
/**
|
|
17
|
+
* Build the declaration of a {@link DBusClientComponent} for the `components` of an application or a module, from its
|
|
18
|
+
* typed options.
|
|
19
|
+
* @param options The bus address and the connection options.
|
|
20
|
+
* @returns The declaration: the options with `class: DBusClientComponent`.
|
|
21
|
+
* @example
|
|
22
|
+
* ```typescript
|
|
23
|
+
* import {Application} from 'lakutata'
|
|
24
|
+
* import {buildDBusClientOptions, DBusClientComponent} from '@lakutata/dbus'
|
|
25
|
+
*
|
|
26
|
+
* Application
|
|
27
|
+
* .run(() => ({
|
|
28
|
+
* id: 'client.app',
|
|
29
|
+
* name: 'Client',
|
|
30
|
+
* components: {
|
|
31
|
+
* dbus: buildDBusClientOptions({busAddress: 'unix:path=/var/run/dbus/system_bus_socket', timeout: 5000})
|
|
32
|
+
* }
|
|
33
|
+
* }))
|
|
34
|
+
* .onLaunched(async (app: Application): Promise<void> => {
|
|
35
|
+
* const dbus: DBusClientComponent = await app.getObject('dbus')
|
|
36
|
+
* console.log(await dbus.listNames())
|
|
37
|
+
* })
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
const buildDBusClientOptions = (options) => ({
|
|
41
|
+
class: DBusClientComponent,
|
|
42
|
+
busAddress: options.busAddress,
|
|
43
|
+
timeout: options.timeout,
|
|
44
|
+
callTimeout: options.callTimeout,
|
|
45
|
+
decode: options.decode
|
|
46
|
+
});
|
|
47
|
+
exports.buildDBusClientOptions = buildDBusClientOptions;
|
|
48
|
+
/**
|
|
49
|
+
* A D-Bus client: a connection to a bus, to call the services on it, read their objects, properties and introspection,
|
|
50
|
+
* and listen to their signals. Declare it in the `components` of an application with {@link buildDBusClientOptions}
|
|
51
|
+
* (or `{class: DBusClientComponent, busAddress: …}`) and get it with `getObject()` or `@Inject`; it is a singleton
|
|
52
|
+
* component. It connects when it is created, so a bus that cannot be reached fails its creation; it disconnects when it
|
|
53
|
+
* is destroyed (the application stopping). It is a thin layer over the `DBus` connection of `dbus-sdk`, whose classes
|
|
54
|
+
* (`DBusService`, `DBusObject`, `DBusRemoteInterface`…) its methods return.
|
|
55
|
+
*
|
|
56
|
+
* Its `on`, `once`, `off` and `removeListener` listen to the connection's events (`'nameOwnerChanged'`,
|
|
57
|
+
* `'connectionClose'`…), not to events of the component.
|
|
58
|
+
* @example
|
|
59
|
+
* ```typescript
|
|
60
|
+
* import {Application} from 'lakutata'
|
|
61
|
+
* import {DBusClientComponent, type DBusRemoteInterface} from '@lakutata/dbus'
|
|
62
|
+
*
|
|
63
|
+
* Application
|
|
64
|
+
* .run(() => ({
|
|
65
|
+
* id: 'client.app',
|
|
66
|
+
* name: 'Client',
|
|
67
|
+
* components: {
|
|
68
|
+
* dbus: {class: DBusClientComponent, busAddress: 'unix:path=/var/run/dbus/system_bus_socket'}
|
|
69
|
+
* }
|
|
70
|
+
* }))
|
|
71
|
+
* .onLaunched(async (app: Application): Promise<void> => {
|
|
72
|
+
* const dbus: DBusClientComponent = await app.getObject('dbus')
|
|
73
|
+
* //A method call: the output arguments, as an array
|
|
74
|
+
* const [owner] = await dbus.invoke({
|
|
75
|
+
* service: 'org.freedesktop.DBus',
|
|
76
|
+
* objectPath: '/org/freedesktop/DBus',
|
|
77
|
+
* interface: 'org.freedesktop.DBus',
|
|
78
|
+
* method: 'GetNameOwner',
|
|
79
|
+
* signature: 's',
|
|
80
|
+
* args: ['org.freedesktop.DBus']
|
|
81
|
+
* })
|
|
82
|
+
* console.log(owner)
|
|
83
|
+
* //An interface, from the introspection of its object: its methods, properties and signals
|
|
84
|
+
* const device: DBusRemoteInterface = await dbus.interface('com.example.DeviceService', '/com/example/Devices/A1', 'com.example.Device')
|
|
85
|
+
* console.log(await device.method.Status({sn: 'A1'}))
|
|
86
|
+
* console.log(await device.property.State.get())
|
|
87
|
+
* device.signal.on('Changed', (data: unknown): void => console.log(data))
|
|
88
|
+
* dbus.on('nameOwnerChanged', (name: string, oldOwner: string, newOwner: string): void => console.log(name, oldOwner, newOwner))
|
|
89
|
+
* })
|
|
90
|
+
* ```
|
|
91
|
+
*/
|
|
92
|
+
class DBusClientComponent extends core_1.Component {
|
|
93
|
+
/**
|
|
94
|
+
* The unique name the bus gave this connection (`':1.42'`).
|
|
95
|
+
*/
|
|
96
|
+
get uniqueName() {
|
|
97
|
+
return this.dbus.uniqueName;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Connect to the bus, when the component is created.
|
|
101
|
+
* @throws {Error} When the bus cannot be reached, or the authentication fails.
|
|
102
|
+
*/
|
|
103
|
+
async init() {
|
|
104
|
+
this.dbus = await dbus_sdk_1.DBus.connect({
|
|
105
|
+
busAddress: this.busAddress,
|
|
106
|
+
timeout: this.timeout,
|
|
107
|
+
callTimeout: this.callTimeout,
|
|
108
|
+
//The 64-bit integers are numbers unless the decoding says otherwise (as the client of lakutata 2.x)
|
|
109
|
+
decode: { int64: 'number', ...this.decode }
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Disconnect from the bus, when the component is destroyed.
|
|
114
|
+
*/
|
|
115
|
+
async destroy() {
|
|
116
|
+
await this.dbus.disconnect();
|
|
117
|
+
}
|
|
118
|
+
invoke(opts) {
|
|
119
|
+
return this.dbus.invoke(opts);
|
|
120
|
+
}
|
|
121
|
+
on(eventName, listener) {
|
|
122
|
+
this.dbus.on(eventName, listener);
|
|
123
|
+
return this;
|
|
124
|
+
}
|
|
125
|
+
once(eventName, listener) {
|
|
126
|
+
this.dbus.once(eventName, listener);
|
|
127
|
+
return this;
|
|
128
|
+
}
|
|
129
|
+
off(eventName, listener) {
|
|
130
|
+
this.dbus.off(eventName, listener);
|
|
131
|
+
return this;
|
|
132
|
+
}
|
|
133
|
+
removeListener(eventName, listener) {
|
|
134
|
+
this.dbus.removeListener(eventName, listener);
|
|
135
|
+
return this;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Ask the bus to route the messages matching a rule to this connection, without waiting for the bus's answer (the
|
|
139
|
+
* signal emitters of {@link interface} and {@link interfaceSignal} add their own rules).
|
|
140
|
+
* @param rule The match rule (`"type='signal',interface='com.example.Device'"`).
|
|
141
|
+
*/
|
|
142
|
+
addMatch(rule) {
|
|
143
|
+
return this.dbus.addMatch(rule);
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Remove a match rule added with {@link addMatch}, without waiting for the bus's answer.
|
|
147
|
+
* @param rule The match rule, as it was added.
|
|
148
|
+
*/
|
|
149
|
+
removeMatch(rule) {
|
|
150
|
+
return this.dbus.removeMatch(rule);
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Get the connection owning a name.
|
|
154
|
+
* @param name The bus name (`'com.example.DeviceService'`).
|
|
155
|
+
* @returns The unique name of its owner (`':1.42'`), or `undefined` when nobody owns it or the query fails.
|
|
156
|
+
*/
|
|
157
|
+
async getNameOwner(name) {
|
|
158
|
+
return this.dbus.getNameOwner(name);
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* List the names the bus can start a service for (D-Bus activation), running or not.
|
|
162
|
+
* @returns The names.
|
|
163
|
+
*/
|
|
164
|
+
async listActivatableNames() {
|
|
165
|
+
return this.dbus.listActivatableNames();
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* List the names owned on the bus now: the well-known names and the unique names of the connections.
|
|
169
|
+
* @returns The names.
|
|
170
|
+
*/
|
|
171
|
+
async listNames() {
|
|
172
|
+
return this.dbus.listNames();
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Tell whether a name is owned on the bus now.
|
|
176
|
+
* @param name The bus name.
|
|
177
|
+
* @returns `true` when a connection owns it.
|
|
178
|
+
*/
|
|
179
|
+
async nameHasOwner(name) {
|
|
180
|
+
return this.dbus.nameHasOwner(name);
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* List the names of the bus, owned or activatable, with their owner, whether they are active or activatable, and the
|
|
184
|
+
* process id of their owner.
|
|
185
|
+
* @returns The names and their details.
|
|
186
|
+
*/
|
|
187
|
+
async listBusNames() {
|
|
188
|
+
return this.dbus.listBusNames();
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* List the services of the bus: its well-known names (the unique names `:1.42` left out), with their details.
|
|
192
|
+
* @returns The services and their details.
|
|
193
|
+
*/
|
|
194
|
+
async listServices() {
|
|
195
|
+
return this.dbus.listServices();
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Get the services of the bus which are running, to browse their objects (the activatable services which are not
|
|
199
|
+
* running are left out, not started: get one with {@link service} to start it).
|
|
200
|
+
* @returns The services.
|
|
201
|
+
*/
|
|
202
|
+
async services() {
|
|
203
|
+
return this.dbus.getServices();
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Get a service, starting it when it is activatable and not running.
|
|
207
|
+
* @param service The service's well-known name (`'org.freedesktop.NetworkManager'`).
|
|
208
|
+
* @returns The service.
|
|
209
|
+
* @throws {Error} A `ServiceNotFoundError` of `dbus-sdk` when the name is neither owned nor activatable.
|
|
210
|
+
*/
|
|
211
|
+
async service(service) {
|
|
212
|
+
return this.dbus.getService(service);
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Get an object of a service, to read its introspection and its interfaces.
|
|
216
|
+
* @param service The service's well-known name.
|
|
217
|
+
* @param objectPath The object path (`'/org/freedesktop/NetworkManager'`).
|
|
218
|
+
* @returns The object.
|
|
219
|
+
* @throws {Error} A `ServiceNotFoundError` of `dbus-sdk` when the service is neither running nor activatable.
|
|
220
|
+
*/
|
|
221
|
+
async object(service, objectPath) {
|
|
222
|
+
return this.dbus.getObject(service, objectPath);
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Get an interface of an object, built from the object's introspection: its methods (`method.Name(...args)`, or
|
|
226
|
+
* `noReplyMethod`), its properties (`property.Name.get()`, `.set(value)`) and its signals (`signal.on('Name', …)`).
|
|
227
|
+
* @param service The service's well-known name.
|
|
228
|
+
* @param objectPath The object path.
|
|
229
|
+
* @param iface The interface name (`'org.freedesktop.NetworkManager'`).
|
|
230
|
+
* @returns The interface.
|
|
231
|
+
* @throws {Error} When the service is not found, or the object does not implement the interface
|
|
232
|
+
* (`InterfaceNotFoundError` of `dbus-sdk`).
|
|
233
|
+
*/
|
|
234
|
+
async interface(service, objectPath, iface) {
|
|
235
|
+
return this.dbus.getInterface(service, objectPath, iface);
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Get the signals of an interface of an object: an event emitter emitting each signal under its name, with its
|
|
239
|
+
* arguments.
|
|
240
|
+
* @param service The service's well-known name.
|
|
241
|
+
* @param objectPath The object path.
|
|
242
|
+
* @param iface The interface name.
|
|
243
|
+
* @returns The emitter of the interface's signals.
|
|
244
|
+
* @throws {Error} When the service is not found, or the object does not implement the interface
|
|
245
|
+
* (`InterfaceNotFoundError` of `dbus-sdk`).
|
|
246
|
+
*/
|
|
247
|
+
async interfaceSignal(service, objectPath, iface) {
|
|
248
|
+
const dbusIface = await this.dbus.getInterface(service, objectPath, iface);
|
|
249
|
+
return dbusIface.signal;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
exports.DBusClientComponent = DBusClientComponent;
|
|
253
|
+
__decorate([
|
|
254
|
+
(0, di_1.Configurable)(core_1.DTO.String().required()),
|
|
255
|
+
__metadata("design:type", String)
|
|
256
|
+
], DBusClientComponent.prototype, "busAddress", void 0);
|
|
257
|
+
__decorate([
|
|
258
|
+
(0, di_1.Configurable)(core_1.DTO.Number().positive().integer().optional()),
|
|
259
|
+
__metadata("design:type", Number)
|
|
260
|
+
], DBusClientComponent.prototype, "timeout", void 0);
|
|
261
|
+
__decorate([
|
|
262
|
+
(0, di_1.Configurable)(core_1.DTO.Number().positive().integer().optional()),
|
|
263
|
+
__metadata("design:type", Number)
|
|
264
|
+
], DBusClientComponent.prototype, "callTimeout", void 0);
|
|
265
|
+
__decorate([
|
|
266
|
+
(0, di_1.Configurable)(core_1.DTO.Object({
|
|
267
|
+
types: core_1.DTO.String().valid('none', 'variants', 'all').optional(),
|
|
268
|
+
dictionaries: core_1.DTO.String().valid('object', 'map').optional(),
|
|
269
|
+
int64: core_1.DTO.String().valid('bigint', 'number').optional()
|
|
270
|
+
}).optional()),
|
|
271
|
+
__metadata("design:type", Object)
|
|
272
|
+
], DBusClientComponent.prototype, "decode", void 0);
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { Component } from '@lakutata/core';
|
|
2
|
+
import type { DBusPropertyDefinition, DBusSignalDefinition } from '../lib/Declarations.js';
|
|
3
|
+
/**
|
|
4
|
+
* What a DBus adapter does for the object manager while it runs
|
|
5
|
+
*/
|
|
6
|
+
export type DBusObjectSink = {
|
|
7
|
+
/**
|
|
8
|
+
* Export an object, false when no interface of the adapter serves its path
|
|
9
|
+
*/
|
|
10
|
+
addObject(path: string): boolean;
|
|
11
|
+
/**
|
|
12
|
+
* Stop exporting an object added by the object manager, false when it does not export it or exports it as a fixed
|
|
13
|
+
* object (the path of a template without variables), which it keeps
|
|
14
|
+
*/
|
|
15
|
+
removeObject(path: string): boolean;
|
|
16
|
+
/**
|
|
17
|
+
* Whether it exports the object as a fixed object
|
|
18
|
+
*/
|
|
19
|
+
isFixed(path: string): boolean;
|
|
20
|
+
emitSignal(path: string, signal: DBusSignalDefinition<any>, data: unknown): boolean;
|
|
21
|
+
propertyChanged(path: string, property: DBusPropertyDefinition<any>, value: unknown): void;
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* A property value written by a D-Bus client, the argument of the `propertyWritten` event of
|
|
25
|
+
* {@link DBusObjectManager}. The value is already validated and kept when the event is emitted.
|
|
26
|
+
*/
|
|
27
|
+
export type DBusPropertyWrite = {
|
|
28
|
+
/**
|
|
29
|
+
* The object path of the property (`'/com/example/Devices/A1'`).
|
|
30
|
+
*/
|
|
31
|
+
readonly path: string;
|
|
32
|
+
/**
|
|
33
|
+
* The name of the interface the client wrote it through (`'com.example.Device'`).
|
|
34
|
+
*/
|
|
35
|
+
readonly interface: string;
|
|
36
|
+
/**
|
|
37
|
+
* The property's definition, to compare with the one built by `defineDBusProperty`.
|
|
38
|
+
*/
|
|
39
|
+
readonly property: DBusPropertyDefinition<any>;
|
|
40
|
+
/**
|
|
41
|
+
* The value written, validated by the property's schema.
|
|
42
|
+
*/
|
|
43
|
+
readonly value: unknown;
|
|
44
|
+
};
|
|
45
|
+
export declare const ATTACH: unique symbol;
|
|
46
|
+
export declare const STORE: unique symbol;
|
|
47
|
+
/**
|
|
48
|
+
* The application's D-Bus objects: it adds and removes the objects of the path templates of `@DBusInterface`
|
|
49
|
+
* (`/com/example/Devices/{sn}`), keeps the values of their properties, and emits their signals. Inject it
|
|
50
|
+
* (`@Inject(DBusObjectManager)`) or get it (`getObject(DBusObjectManager)`) from any object: it is autoloaded, a
|
|
51
|
+
* singleton of the application, and needs no registration.
|
|
52
|
+
*
|
|
53
|
+
* Its state lives in the application, not on the bus: the objects added before a {@link dbusEntrypoint} starts are
|
|
54
|
+
* exported when it starts, and every running D-Bus adapter whose interfaces match a path exports the object. The
|
|
55
|
+
* clients are told of the objects added and removed by the `org.freedesktop.DBus.ObjectManager` interface of the root
|
|
56
|
+
* object (`InterfacesAdded`, `InterfacesRemoved`).
|
|
57
|
+
*
|
|
58
|
+
* It emits `propertyWritten` (a {@link DBusPropertyWrite}) when a client writes a writable property.
|
|
59
|
+
* @example
|
|
60
|
+
* ```typescript
|
|
61
|
+
* import {Component} from 'lakutata'
|
|
62
|
+
* import {Inject} from 'lakutata/decorator/di'
|
|
63
|
+
* import {DBusObjectManager, defineDBusProperty, defineDBusSignal, type DBusPropertyWrite} from '@lakutata/dbus'
|
|
64
|
+
*
|
|
65
|
+
* export const DeviceState = defineDBusProperty<string>('State', 's', {initial: 'idle'})
|
|
66
|
+
* export const DeviceLevel = defineDBusProperty<number>('Level', 'i', {writable: true, initial: 1})
|
|
67
|
+
* export const DeviceRemoved = defineDBusSignal<string>('Removed', 's')
|
|
68
|
+
*
|
|
69
|
+
* //Declared in the components of the application, with the controllers of the interfaces at /com/example/Devices/{sn}
|
|
70
|
+
* export class Devices extends Component {
|
|
71
|
+
* @Inject(DBusObjectManager)
|
|
72
|
+
* protected readonly manager: DBusObjectManager
|
|
73
|
+
*
|
|
74
|
+
* protected async init(): Promise<void> {
|
|
75
|
+
* this.manager.on('propertyWritten', (write: DBusPropertyWrite): void => {
|
|
76
|
+
* if (write.property === DeviceLevel) console.log(`${write.path}: level ${write.value}`)
|
|
77
|
+
* })
|
|
78
|
+
* }
|
|
79
|
+
*
|
|
80
|
+
* public plug(sn: string): void {
|
|
81
|
+
* const path: string = `/com/example/Devices/${sn}`
|
|
82
|
+
* this.manager.addObject(path)
|
|
83
|
+
* this.manager.setPropertyValue(path, DeviceState, 'running')
|
|
84
|
+
* }
|
|
85
|
+
*
|
|
86
|
+
* public unplug(sn: string): void {
|
|
87
|
+
* const path: string = `/com/example/Devices/${sn}`
|
|
88
|
+
* this.manager.emitSignal(path, DeviceRemoved, sn)
|
|
89
|
+
* this.manager.removeObject(path)
|
|
90
|
+
* }
|
|
91
|
+
* }
|
|
92
|
+
* ```
|
|
93
|
+
*/
|
|
94
|
+
export declare class DBusObjectManager extends Component {
|
|
95
|
+
#private;
|
|
96
|
+
/**
|
|
97
|
+
* Send the changes to an adapter until the returned function is called; the objects already added are exported by it
|
|
98
|
+
* @param sink
|
|
99
|
+
*/
|
|
100
|
+
[ATTACH](sink: DBusObjectSink): () => void;
|
|
101
|
+
/**
|
|
102
|
+
* Keep a value written by a client
|
|
103
|
+
* @param write
|
|
104
|
+
*/
|
|
105
|
+
[STORE](write: DBusPropertyWrite): void;
|
|
106
|
+
/**
|
|
107
|
+
* Add an object: every running D-Bus adapter whose interfaces have a path template matching it (or the same path)
|
|
108
|
+
* exports it, with those interfaces, and the adapters started later export it when they start. The variables of the
|
|
109
|
+
* matching templates are given to the methods called on it. An object whose path matches no template is kept, but
|
|
110
|
+
* not exported.
|
|
111
|
+
* @param path The object path (`'/com/example/Devices/A1'`), not a template.
|
|
112
|
+
* @returns `false` when the object was already added, `true` otherwise.
|
|
113
|
+
* @throws {TypeError} When the path is invalid, or is a template (it contains braces).
|
|
114
|
+
*/
|
|
115
|
+
addObject(path: string): boolean;
|
|
116
|
+
/**
|
|
117
|
+
* Remove an object added with {@link addObject}: the adapters stop exporting it, and its property values are
|
|
118
|
+
* forgotten (added again, it starts from the initial values). A fixed object (the path of a template without
|
|
119
|
+
* variables, exported by the adapters themselves) stays exported, with its property values, when it was also
|
|
120
|
+
* added: only its entry in {@link objects} is removed.
|
|
121
|
+
* @param path The object path given to {@link addObject}.
|
|
122
|
+
* @returns `false` when the object was not added, `true` otherwise.
|
|
123
|
+
*/
|
|
124
|
+
removeObject(path: string): boolean;
|
|
125
|
+
/**
|
|
126
|
+
* Tell whether an object was added with {@link addObject} and not removed since.
|
|
127
|
+
* @param path The object path.
|
|
128
|
+
* @returns `true` when the object is added.
|
|
129
|
+
*/
|
|
130
|
+
hasObject(path: string): boolean;
|
|
131
|
+
/**
|
|
132
|
+
* List the objects added with {@link addObject}, in the order they were added; the objects of the paths without
|
|
133
|
+
* variables, exported by the adapters themselves, are not listed.
|
|
134
|
+
* @returns The object paths.
|
|
135
|
+
*/
|
|
136
|
+
objects(): string[];
|
|
137
|
+
/**
|
|
138
|
+
* Set the value of a property on an object, validated by the property's schema: the clients read it from then on,
|
|
139
|
+
* and the running adapters exporting the object announce it (`PropertiesChanged`). The value is kept even when no
|
|
140
|
+
* adapter exports the object yet.
|
|
141
|
+
* @param path The object path (`'/com/example/Devices/A1'`).
|
|
142
|
+
* @param property The property's definition, listed in the `properties` of the object's interface.
|
|
143
|
+
* @param value The new value.
|
|
144
|
+
* @throws {InvalidValueException} When the value does not match the property's schema; nothing is changed.
|
|
145
|
+
*/
|
|
146
|
+
setPropertyValue<T>(path: string, property: DBusPropertyDefinition<T>, value: T): void;
|
|
147
|
+
/**
|
|
148
|
+
* Get the value of a property on an object: the last value set by the application or written by a client.
|
|
149
|
+
* @param path The object path.
|
|
150
|
+
* @param property The property's definition.
|
|
151
|
+
* @returns The value, or the property's `initial` value until one is set.
|
|
152
|
+
*/
|
|
153
|
+
getPropertyValue<T>(path: string, property: DBusPropertyDefinition<T>): T | undefined;
|
|
154
|
+
/**
|
|
155
|
+
* Emit a signal from an object, through every running adapter exporting the object with an interface listing the
|
|
156
|
+
* signal. The data is validated by the signal's DTO first.
|
|
157
|
+
* @param path The object path (`'/com/example/Devices/A1'`).
|
|
158
|
+
* @param signal The signal's definition, listed in the `signals` of the object's interface.
|
|
159
|
+
* @param data The data of the signal.
|
|
160
|
+
* @returns `false` when no running adapter exports the object with this signal (nothing is sent), `true` otherwise.
|
|
161
|
+
* @throws {InvalidValueException} When the data does not match the signal's DTO.
|
|
162
|
+
*/
|
|
163
|
+
emitSignal<T>(path: string, signal: DBusSignalDefinition<T>, data: T): boolean;
|
|
164
|
+
}
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.DBusObjectManager = exports.STORE = exports.ATTACH = void 0;
|
|
10
|
+
const core_1 = require("@lakutata/core");
|
|
11
|
+
const di_1 = require("@lakutata/core/decorator/di");
|
|
12
|
+
const Names_js_1 = require("../lib/Names.js");
|
|
13
|
+
exports.ATTACH = Symbol('DBUS.OBJECT.MANAGER.ATTACH');
|
|
14
|
+
exports.STORE = Symbol('DBUS.OBJECT.MANAGER.STORE');
|
|
15
|
+
/**
|
|
16
|
+
* The application's D-Bus objects: it adds and removes the objects of the path templates of `@DBusInterface`
|
|
17
|
+
* (`/com/example/Devices/{sn}`), keeps the values of their properties, and emits their signals. Inject it
|
|
18
|
+
* (`@Inject(DBusObjectManager)`) or get it (`getObject(DBusObjectManager)`) from any object: it is autoloaded, a
|
|
19
|
+
* singleton of the application, and needs no registration.
|
|
20
|
+
*
|
|
21
|
+
* Its state lives in the application, not on the bus: the objects added before a {@link dbusEntrypoint} starts are
|
|
22
|
+
* exported when it starts, and every running D-Bus adapter whose interfaces match a path exports the object. The
|
|
23
|
+
* clients are told of the objects added and removed by the `org.freedesktop.DBus.ObjectManager` interface of the root
|
|
24
|
+
* object (`InterfacesAdded`, `InterfacesRemoved`).
|
|
25
|
+
*
|
|
26
|
+
* It emits `propertyWritten` (a {@link DBusPropertyWrite}) when a client writes a writable property.
|
|
27
|
+
* @example
|
|
28
|
+
* ```typescript
|
|
29
|
+
* import {Component} from 'lakutata'
|
|
30
|
+
* import {Inject} from 'lakutata/decorator/di'
|
|
31
|
+
* import {DBusObjectManager, defineDBusProperty, defineDBusSignal, type DBusPropertyWrite} from '@lakutata/dbus'
|
|
32
|
+
*
|
|
33
|
+
* export const DeviceState = defineDBusProperty<string>('State', 's', {initial: 'idle'})
|
|
34
|
+
* export const DeviceLevel = defineDBusProperty<number>('Level', 'i', {writable: true, initial: 1})
|
|
35
|
+
* export const DeviceRemoved = defineDBusSignal<string>('Removed', 's')
|
|
36
|
+
*
|
|
37
|
+
* //Declared in the components of the application, with the controllers of the interfaces at /com/example/Devices/{sn}
|
|
38
|
+
* export class Devices extends Component {
|
|
39
|
+
* @Inject(DBusObjectManager)
|
|
40
|
+
* protected readonly manager: DBusObjectManager
|
|
41
|
+
*
|
|
42
|
+
* protected async init(): Promise<void> {
|
|
43
|
+
* this.manager.on('propertyWritten', (write: DBusPropertyWrite): void => {
|
|
44
|
+
* if (write.property === DeviceLevel) console.log(`${write.path}: level ${write.value}`)
|
|
45
|
+
* })
|
|
46
|
+
* }
|
|
47
|
+
*
|
|
48
|
+
* public plug(sn: string): void {
|
|
49
|
+
* const path: string = `/com/example/Devices/${sn}`
|
|
50
|
+
* this.manager.addObject(path)
|
|
51
|
+
* this.manager.setPropertyValue(path, DeviceState, 'running')
|
|
52
|
+
* }
|
|
53
|
+
*
|
|
54
|
+
* public unplug(sn: string): void {
|
|
55
|
+
* const path: string = `/com/example/Devices/${sn}`
|
|
56
|
+
* this.manager.emitSignal(path, DeviceRemoved, sn)
|
|
57
|
+
* this.manager.removeObject(path)
|
|
58
|
+
* }
|
|
59
|
+
* }
|
|
60
|
+
* ```
|
|
61
|
+
*/
|
|
62
|
+
let DBusObjectManager = class DBusObjectManager extends core_1.Component {
|
|
63
|
+
#objects = new Set();
|
|
64
|
+
#values = new Map();
|
|
65
|
+
#sinks = new Set();
|
|
66
|
+
/**
|
|
67
|
+
* Send the changes to an adapter until the returned function is called; the objects already added are exported by it
|
|
68
|
+
* @param sink
|
|
69
|
+
*/
|
|
70
|
+
[exports.ATTACH](sink) {
|
|
71
|
+
this.#sinks.add(sink);
|
|
72
|
+
this.#objects.forEach((path) => sink.addObject(path));
|
|
73
|
+
return () => void this.#sinks.delete(sink);
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Keep a value written by a client
|
|
77
|
+
* @param write
|
|
78
|
+
*/
|
|
79
|
+
[exports.STORE](write) {
|
|
80
|
+
this.#valuesOf(write.path).set(write.property, write.value);
|
|
81
|
+
this.emit('propertyWritten', write);
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* The values of the properties of an object
|
|
85
|
+
* @param path
|
|
86
|
+
*/
|
|
87
|
+
#valuesOf(path) {
|
|
88
|
+
let values = this.#values.get(path);
|
|
89
|
+
if (!values) {
|
|
90
|
+
values = new Map();
|
|
91
|
+
this.#values.set(path, values);
|
|
92
|
+
}
|
|
93
|
+
return values;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Add an object: every running D-Bus adapter whose interfaces have a path template matching it (or the same path)
|
|
97
|
+
* exports it, with those interfaces, and the adapters started later export it when they start. The variables of the
|
|
98
|
+
* matching templates are given to the methods called on it. An object whose path matches no template is kept, but
|
|
99
|
+
* not exported.
|
|
100
|
+
* @param path The object path (`'/com/example/Devices/A1'`), not a template.
|
|
101
|
+
* @returns `false` when the object was already added, `true` otherwise.
|
|
102
|
+
* @throws {TypeError} When the path is invalid, or is a template (it contains braces).
|
|
103
|
+
*/
|
|
104
|
+
addObject(path) {
|
|
105
|
+
(0, Names_js_1.checkPathTemplate)(path);
|
|
106
|
+
if (/[{}]/.test(path))
|
|
107
|
+
throw new TypeError(`The object path "${path}" is a template, not the path of an object`);
|
|
108
|
+
if (this.#objects.has(path))
|
|
109
|
+
return false;
|
|
110
|
+
this.#objects.add(path);
|
|
111
|
+
this.#sinks.forEach((sink) => sink.addObject(path));
|
|
112
|
+
return true;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Remove an object added with {@link addObject}: the adapters stop exporting it, and its property values are
|
|
116
|
+
* forgotten (added again, it starts from the initial values). A fixed object (the path of a template without
|
|
117
|
+
* variables, exported by the adapters themselves) stays exported, with its property values, when it was also
|
|
118
|
+
* added: only its entry in {@link objects} is removed.
|
|
119
|
+
* @param path The object path given to {@link addObject}.
|
|
120
|
+
* @returns `false` when the object was not added, `true` otherwise.
|
|
121
|
+
*/
|
|
122
|
+
removeObject(path) {
|
|
123
|
+
if (!this.#objects.delete(path))
|
|
124
|
+
return false;
|
|
125
|
+
let fixed = false;
|
|
126
|
+
this.#sinks.forEach((sink) => {
|
|
127
|
+
//An adapter keeps the objects it exports by itself
|
|
128
|
+
if (sink.isFixed(path))
|
|
129
|
+
fixed = true;
|
|
130
|
+
else
|
|
131
|
+
sink.removeObject(path);
|
|
132
|
+
});
|
|
133
|
+
if (!fixed)
|
|
134
|
+
this.#values.delete(path);
|
|
135
|
+
return true;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Tell whether an object was added with {@link addObject} and not removed since.
|
|
139
|
+
* @param path The object path.
|
|
140
|
+
* @returns `true` when the object is added.
|
|
141
|
+
*/
|
|
142
|
+
hasObject(path) {
|
|
143
|
+
return this.#objects.has(path);
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* List the objects added with {@link addObject}, in the order they were added; the objects of the paths without
|
|
147
|
+
* variables, exported by the adapters themselves, are not listed.
|
|
148
|
+
* @returns The object paths.
|
|
149
|
+
*/
|
|
150
|
+
objects() {
|
|
151
|
+
return [...this.#objects];
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Set the value of a property on an object, validated by the property's schema: the clients read it from then on,
|
|
155
|
+
* and the running adapters exporting the object announce it (`PropertiesChanged`). The value is kept even when no
|
|
156
|
+
* adapter exports the object yet.
|
|
157
|
+
* @param path The object path (`'/com/example/Devices/A1'`).
|
|
158
|
+
* @param property The property's definition, listed in the `properties` of the object's interface.
|
|
159
|
+
* @param value The new value.
|
|
160
|
+
* @throws {InvalidValueException} When the value does not match the property's schema; nothing is changed.
|
|
161
|
+
*/
|
|
162
|
+
setPropertyValue(path, property, value) {
|
|
163
|
+
const validated = property.validate(value);
|
|
164
|
+
this.#valuesOf(path).set(property, validated);
|
|
165
|
+
this.#sinks.forEach((sink) => sink.propertyChanged(path, property, validated));
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Get the value of a property on an object: the last value set by the application or written by a client.
|
|
169
|
+
* @param path The object path.
|
|
170
|
+
* @param property The property's definition.
|
|
171
|
+
* @returns The value, or the property's `initial` value until one is set.
|
|
172
|
+
*/
|
|
173
|
+
getPropertyValue(path, property) {
|
|
174
|
+
const values = this.#values.get(path);
|
|
175
|
+
return values?.has(property) ? values.get(property) : property.initial;
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Emit a signal from an object, through every running adapter exporting the object with an interface listing the
|
|
179
|
+
* signal. The data is validated by the signal's DTO first.
|
|
180
|
+
* @param path The object path (`'/com/example/Devices/A1'`).
|
|
181
|
+
* @param signal The signal's definition, listed in the `signals` of the object's interface.
|
|
182
|
+
* @param data The data of the signal.
|
|
183
|
+
* @returns `false` when no running adapter exports the object with this signal (nothing is sent), `true` otherwise.
|
|
184
|
+
* @throws {InvalidValueException} When the data does not match the signal's DTO.
|
|
185
|
+
*/
|
|
186
|
+
emitSignal(path, signal, data) {
|
|
187
|
+
const validated = signal.validate(data);
|
|
188
|
+
let emitted = false;
|
|
189
|
+
this.#sinks.forEach((sink) => {
|
|
190
|
+
if (sink.emitSignal(path, signal, validated))
|
|
191
|
+
emitted = true;
|
|
192
|
+
});
|
|
193
|
+
return emitted;
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
exports.DBusObjectManager = DBusObjectManager;
|
|
197
|
+
exports.DBusObjectManager = DBusObjectManager = __decorate([
|
|
198
|
+
(0, di_1.Autoload)(),
|
|
199
|
+
(0, di_1.Singleton)()
|
|
200
|
+
], DBusObjectManager);
|