@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.
Files changed (81) hide show
  1. package/LICENSE +23 -0
  2. package/README.md +253 -25
  3. package/dist/cjs/adapters/DBusEntrypoint.d.ts +89 -0
  4. package/dist/cjs/adapters/DBusEntrypoint.js +280 -0
  5. package/dist/cjs/channel/DBusChannel.d.ts +36 -0
  6. package/dist/cjs/channel/DBusChannel.js +70 -0
  7. package/dist/cjs/components/DBusClientComponent.d.ts +494 -0
  8. package/dist/cjs/components/DBusClientComponent.js +272 -0
  9. package/dist/cjs/components/DBusObjectManager.d.ts +164 -0
  10. package/dist/cjs/components/DBusObjectManager.js +200 -0
  11. package/dist/cjs/context/DBusContext.d.ts +52 -0
  12. package/dist/cjs/context/DBusContext.js +68 -0
  13. package/dist/cjs/decorators/DBusInterface.d.ts +74 -0
  14. package/dist/cjs/decorators/DBusInterface.js +63 -0
  15. package/dist/cjs/decorators/DBusMethod.d.ts +115 -0
  16. package/dist/cjs/decorators/DBusMethod.js +20 -0
  17. package/dist/cjs/exports/DBus.d.ts +11 -0
  18. package/dist/cjs/exports/DBus.js +42 -0
  19. package/dist/cjs/lib/DBusClientOptions.d.ts +30 -0
  20. package/dist/cjs/lib/Declarations.d.ts +187 -0
  21. package/dist/cjs/lib/Declarations.js +113 -0
  22. package/dist/cjs/lib/Names.d.ts +35 -0
  23. package/dist/cjs/lib/Names.js +80 -0
  24. package/dist/cjs/lib/Signature.d.ts +14 -0
  25. package/dist/cjs/lib/Signature.js +53 -0
  26. package/dist/cjs/package.json +1 -0
  27. package/dist/cjs/types/DBusContextParams.d.ts +22 -0
  28. package/dist/cjs/types/DBusContextParams.js +2 -0
  29. package/dist/esm/adapters/DBusEntrypoint.js +274 -0
  30. package/dist/esm/channel/DBusChannel.js +67 -0
  31. package/dist/esm/components/DBusClientComponent.js +267 -0
  32. package/dist/esm/components/DBusObjectManager.js +197 -0
  33. package/dist/esm/context/DBusContext.js +64 -0
  34. package/dist/esm/decorators/DBusInterface.js +60 -0
  35. package/dist/esm/decorators/DBusMethod.js +17 -0
  36. package/dist/esm/exports/DBus.js +12 -0
  37. package/dist/esm/lib/DBusClientOptions.js +1 -0
  38. package/dist/esm/lib/Declarations.js +107 -0
  39. package/dist/esm/lib/Names.js +73 -0
  40. package/dist/esm/lib/Signature.js +49 -0
  41. package/dist/esm/types/DBusContextParams.js +1 -0
  42. package/dist/types/adapters/DBusEntrypoint.d.ts +89 -0
  43. package/dist/types/channel/DBusChannel.d.ts +36 -0
  44. package/dist/types/components/DBusClientComponent.d.ts +494 -0
  45. package/dist/types/components/DBusObjectManager.d.ts +164 -0
  46. package/dist/types/context/DBusContext.d.ts +52 -0
  47. package/dist/types/decorators/DBusInterface.d.ts +74 -0
  48. package/dist/types/decorators/DBusMethod.d.ts +115 -0
  49. package/dist/types/exports/DBus.d.ts +11 -0
  50. package/dist/types/lib/DBusClientOptions.d.ts +30 -0
  51. package/dist/types/lib/Declarations.d.ts +187 -0
  52. package/dist/types/lib/Names.d.ts +35 -0
  53. package/dist/types/lib/Signature.d.ts +14 -0
  54. package/dist/types/types/DBusContextParams.d.ts +22 -0
  55. package/package.json +38 -39
  56. package/CHANGELOG.md +0 -153
  57. package/dist/CommonExports.d.ts +0 -2
  58. package/dist/CommonExports.d.ts.map +0 -1
  59. package/dist/CommonExports.js +0 -44
  60. package/dist/ComponentExports.d.ts +0 -2
  61. package/dist/ComponentExports.d.ts.map +0 -1
  62. package/dist/ComponentExports.js +0 -5
  63. package/dist/ProviderExports.d.ts +0 -2
  64. package/dist/ProviderExports.d.ts.map +0 -1
  65. package/dist/ProviderExports.js +0 -5
  66. package/dist/components/DBusClientComponent.d.ts +0 -75
  67. package/dist/components/DBusClientComponent.d.ts.map +0 -1
  68. package/dist/components/DBusClientComponent.js +0 -125
  69. package/dist/interfaces/DBusClientOptions.d.ts +0 -7
  70. package/dist/interfaces/DBusClientOptions.d.ts.map +0 -1
  71. package/dist/providers/DBusClientProvider.d.ts +0 -75
  72. package/dist/providers/DBusClientProvider.d.ts.map +0 -1
  73. package/dist/providers/DBusClientProvider.js +0 -125
  74. package/dist/tests/DBusTest.spec.d.ts +0 -2
  75. package/dist/tests/DBusTest.spec.d.ts.map +0 -1
  76. package/dist/tests/DBusTest.spec.js +0 -26
  77. package/dist/tests/Test.d.ts +0 -9
  78. package/dist/tests/Test.d.ts.map +0 -1
  79. package/dist/tests/Test.js +0 -80
  80. package/tsconfig.json +0 -7
  81. /package/dist/{interfaces → cjs/lib}/DBusClientOptions.js +0 -0
@@ -0,0 +1,274 @@
1
+ import EventEmitter from 'node:events';
2
+ import { FlexibleDTO } from '@lakutata/core/dtos';
3
+ import { LocalInterface, LocalObject, LocalService, RequestNameFlags, RequestNameResultCode } from 'dbus-sdk';
4
+ import { DBusChannel } from '../channel/DBusChannel.js';
5
+ import { DBusContext } from '../context/DBusContext.js';
6
+ import { ATTACH, DBusObjectManager, STORE } from '../components/DBusObjectManager.js';
7
+ import { getDBusInterfaceDeclaration } from '../lib/Declarations.js';
8
+ import { checkInterfaceName, compilePathTemplate, errorName } from '../lib/Names.js';
9
+ import { toEncodable, schemaSignature } from '../lib/Signature.js';
10
+ /**
11
+ * The connection options of a bus
12
+ * @param bus
13
+ */
14
+ function connectOptions(bus) {
15
+ if (bus === 'session')
16
+ return { busAddress: process.env.DBUS_SESSION_BUS_ADDRESS };
17
+ if (bus === 'system')
18
+ return { busAddress: process.env.DBUS_SYSTEM_BUS_ADDRESS ?? 'unix:path=/var/run/dbus/system_bus_socket' };
19
+ if (/^[a-z]+:/.test(bus))
20
+ return { busAddress: bus };
21
+ return { socket: bus };
22
+ }
23
+ /**
24
+ * The error given to the caller of a method
25
+ * @param error
26
+ */
27
+ function toMethodError(error) {
28
+ const { errMsg, message } = (error && typeof error === 'object' ? error : {});
29
+ const methodError = new Error(typeof errMsg === 'string' && errMsg ? errMsg : typeof message === 'string' ? message : String(error));
30
+ methodError.name = errorName(error);
31
+ return methodError;
32
+ }
33
+ /**
34
+ * Build the D-Bus adapter of the entrypoint (its `dbus` option): the application owns a service name on a bus and
35
+ * serves the controllers declared with `@DBusInterface` as the interfaces of its objects, their `@DBusMethod` actions
36
+ * as the methods. The objects of the paths without variables are exported when the adapter starts, until it stops
37
+ * ({@link DBusObjectManager.removeObject} does not remove them); the objects of the path templates are exported when
38
+ * they are added with {@link DBusObjectManager.addObject}, before or after the start.
39
+ *
40
+ * Starting connects to the bus and requests the name. The adapter stops gracefully with the entrypoint: it releases the
41
+ * name (no new call reaches it), the calls in progress end, then the connection closes. It needs a D-Bus daemon, as on
42
+ * Linux: the system bus, a session bus, or a private `dbus-daemon`.
43
+ * @param options The bus, the service name and how it is owned, the adapter's middlewares.
44
+ * @returns The adapter, to give to the `dbus` option of `buildEntrypoints()`.
45
+ * @throws {TypeError} At once, when the service name is invalid; when the adapter starts, when an action declared with
46
+ * `@DBusMethod` belongs to a controller without `@DBusInterface`.
47
+ * @throws {Error} When the adapter starts, if the bus cannot be reached, or if another connection owns the name
48
+ * (`The DBus name … is owned by another service`): the entrypoint fails to start.
49
+ * @example
50
+ * ```typescript
51
+ * import {Application} from 'lakutata'
52
+ * import {buildEntrypoints, Controller} from 'lakutata/com/entrypoint'
53
+ * import {dbusEntrypoint, DBusInterface, DBusMethod} from '@lakutata/dbus'
54
+ *
55
+ * @DBusInterface('com.example.Manager', {path: '/com/example/Manager'})
56
+ * class ManagerController extends Controller {
57
+ * @DBusMethod('Ping', {returns: 's'})
58
+ * public async ping(): Promise<string> {
59
+ * return 'pong'
60
+ * }
61
+ * }
62
+ *
63
+ * //busctl --system call com.example.DeviceService /com/example/Manager com.example.Manager Ping
64
+ * Application.run(() => ({
65
+ * id: 'devices.app',
66
+ * name: 'Devices',
67
+ * components: {
68
+ * entrypoint: buildEntrypoints({
69
+ * controllers: [ManagerController],
70
+ * dbus: dbusEntrypoint({bus: 'system', service: 'com.example.DeviceService'})
71
+ * })
72
+ * },
73
+ * bootstrap: ['entrypoint']
74
+ * }))
75
+ * ```
76
+ */
77
+ export function dbusEntrypoint(options) {
78
+ checkInterfaceName(options.service);
79
+ let service;
80
+ let detach;
81
+ const exported = new Map();
82
+ /**
83
+ * The interfaces of the controllers and of the actions, by path template
84
+ * @param runtime
85
+ */
86
+ const collect = (runtime) => {
87
+ const templates = new Map();
88
+ const interfacesAt = (path, name) => {
89
+ let template = templates.get(path);
90
+ if (!template) {
91
+ template = { template: compilePathTemplate(path), interfaces: new Map() };
92
+ templates.set(path, template);
93
+ }
94
+ let interfaces = template.interfaces.get(name);
95
+ if (!interfaces) {
96
+ interfaces = { methods: [] };
97
+ template.interfaces.set(name, interfaces);
98
+ }
99
+ return interfaces;
100
+ };
101
+ for (const controller of runtime.controllers) {
102
+ const declaration = getDBusInterfaceDeclaration(controller);
103
+ if (declaration)
104
+ interfacesAt(declaration.path, declaration.interface).declaration = declaration;
105
+ }
106
+ for (const action of runtime.actions) {
107
+ const { path, interface: interfaceName, member } = action.address;
108
+ if (path === undefined || interfaceName === undefined)
109
+ throw new TypeError(`The DBus method ${member} of ${action.controller} has no interface: declare its controller with @DBusInterface`);
110
+ interfacesAt(path, interfaceName).methods.push(action);
111
+ }
112
+ return templates;
113
+ };
114
+ /**
115
+ * The signature of the result of a method: its returns option, or the schema of @Return
116
+ * @param action
117
+ */
118
+ const resultSignature = (action) => {
119
+ if (action.address.returns !== undefined)
120
+ return action.address.returns;
121
+ return action.returnSchema ? schemaSignature(action.returnSchema) : undefined;
122
+ };
123
+ return {
124
+ channel: 'dbus',
125
+ definition: DBusChannel,
126
+ name: options.name,
127
+ middlewares: options.middlewares,
128
+ start: async (runtime) => {
129
+ const templates = collect(runtime);
130
+ const manager = await runtime.getObject(DBusObjectManager);
131
+ const localService = new LocalService(options.service);
132
+ service = localService;
133
+ /**
134
+ * Build the interface of an object
135
+ * @param path
136
+ * @param name
137
+ * @param served
138
+ * @param variables
139
+ * @param object
140
+ */
141
+ const buildInterface = (path, name, served, variables, object) => {
142
+ const localInterface = new LocalInterface(name);
143
+ for (const action of served.methods) {
144
+ const returns = resultSignature(action);
145
+ const hasInput = action.dto !== FlexibleDTO;
146
+ localInterface.defineMethod({
147
+ name: action.address.member,
148
+ inputArgs: hasInput ? [{ name: 'input', type: 'a{sv}' }] : [],
149
+ outputArgs: returns ? [{ name: 'output', type: returns }] : [],
150
+ method: async (input) => {
151
+ const context = new DBusContext({ path: path, interface: name, member: action.address.member, data: input && typeof input === 'object' ? input : {} });
152
+ let result;
153
+ try {
154
+ result = await runtime.dispatchTo(action, context, { params: variables });
155
+ }
156
+ catch (error) {
157
+ throw toMethodError(error);
158
+ }
159
+ if (!returns)
160
+ return undefined;
161
+ return toEncodable(returns === 'a{sv}' && (result === undefined || result === null) ? {} : result);
162
+ }
163
+ });
164
+ }
165
+ for (const property of served.declaration?.properties ?? []) {
166
+ localInterface.defineProperty({
167
+ name: property.name,
168
+ type: property.type,
169
+ getter: () => toEncodable(manager.getPropertyValue(path, property)),
170
+ //A value written by a client is validated synchronously: its errors are given to the client
171
+ setter: property.writable ? (value) => {
172
+ let validated;
173
+ try {
174
+ validated = property.validate(value);
175
+ }
176
+ catch (error) {
177
+ //The errors given to DBus are plain errors (the name of an exception is read only)
178
+ throw toMethodError(error);
179
+ }
180
+ manager[STORE]({ path: path, interface: name, property: property, value: validated });
181
+ } : undefined,
182
+ emitPropertiesChanged: property.writable
183
+ });
184
+ object.properties.set(property, [...(object.properties.get(property) ?? []), name]);
185
+ }
186
+ for (const signal of served.declaration?.signals ?? []) {
187
+ const emitter = new EventEmitter();
188
+ localInterface.defineSignal({ name: signal.name, args: [{ name: 'data', type: signal.type }], eventEmitter: emitter });
189
+ const signals = object.signals.get(signal.name) ?? [];
190
+ signals.push({ signal: signal, emitter: emitter });
191
+ object.signals.set(signal.name, signals);
192
+ }
193
+ return localInterface;
194
+ };
195
+ /**
196
+ * Export an object
197
+ * @param path
198
+ */
199
+ const addObject = (path) => {
200
+ if (exported.has(path))
201
+ return true;
202
+ const object = { object: new LocalObject(path), signals: new Map(), properties: new Map() };
203
+ let served = false;
204
+ for (const { template, interfaces } of templates.values()) {
205
+ const variables = template.match(path);
206
+ if (!variables)
207
+ continue;
208
+ interfaces.forEach((interfaceServed, name) => object.object.addInterface(buildInterface(path, name, interfaceServed, variables, object)));
209
+ served = true;
210
+ }
211
+ if (!served)
212
+ return false;
213
+ exported.set(path, object);
214
+ localService.addObject(object.object);
215
+ return true;
216
+ };
217
+ //The objects of the paths without variables are exported at once, and kept until the adapter closes
218
+ const fixed = new Set();
219
+ for (const { template } of templates.values()) {
220
+ if (!template.static)
221
+ continue;
222
+ fixed.add(template.template);
223
+ addObject(template.template);
224
+ }
225
+ const connect = connectOptions(options.bus ?? 'session');
226
+ let flags = RequestNameFlags.DBUS_NAME_FLAG_DO_NOT_QUEUE;
227
+ if (options.replaceExisting)
228
+ flags |= RequestNameFlags.DBUS_NAME_FLAG_REPLACE_EXISTING;
229
+ if (options.allowReplacement)
230
+ flags |= RequestNameFlags.DBUS_NAME_FLAG_ALLOW_REPLACEMENT;
231
+ const result = await localService.run({ ...connect, flags: flags });
232
+ if (result !== RequestNameResultCode.DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER && result !== RequestNameResultCode.DBUS_REQUEST_NAME_REPLY_ALREADY_OWNER) {
233
+ await localService.stop().catch(() => undefined);
234
+ throw new Error(`The DBus name ${options.service} is owned by another service`);
235
+ }
236
+ const sink = {
237
+ addObject: addObject,
238
+ removeObject: (path) => {
239
+ if (fixed.has(path) || !exported.delete(path))
240
+ return false;
241
+ return localService.removeObject(path);
242
+ },
243
+ isFixed: (path) => fixed.has(path),
244
+ emitSignal: (path, signal, data) => {
245
+ const emitters = exported.get(path)?.signals.get(signal.name)?.filter((candidate) => candidate.signal === signal);
246
+ if (!emitters?.length)
247
+ return false;
248
+ emitters.forEach(({ emitter }) => emitter.emit(signal.name, toEncodable(data)));
249
+ return true;
250
+ },
251
+ propertyChanged: (path, property) => {
252
+ const object = exported.get(path);
253
+ const interfaces = object?.properties.get(property);
254
+ if (!object || !interfaces)
255
+ return;
256
+ //The interfaces read the new value with their getters (the manager stored it), typed by the property
257
+ interfaces.forEach((name) => object.object.getInterface(name)?.emitPropertiesChanged(property.name));
258
+ }
259
+ };
260
+ detach = manager[ATTACH](sink);
261
+ },
262
+ stop: async () => {
263
+ //The name is released: no new call reaches the service, the calls in progress end
264
+ detach?.();
265
+ detach = undefined;
266
+ await service?.dbus?.releaseName(options.service).catch(() => undefined);
267
+ },
268
+ close: async () => {
269
+ await service?.dbus?.disconnect().catch(() => undefined);
270
+ exported.clear();
271
+ service = undefined;
272
+ }
273
+ };
274
+ }
@@ -0,0 +1,67 @@
1
+ import { ControllerActionNotFoundException } from '@lakutata/entrypoint';
2
+ import { defineChannel, getChannel, hasChannel } from '@lakutata/entrypoint/channel';
3
+ import { DBUS_CONTEXT_TYPE } from '../context/DBusContext.js';
4
+ import { checkInterfaceName, checkMemberName, checkPathTemplate, compilePathTemplate } from '../lib/Names.js';
5
+ /**
6
+ * The key of the address of a method
7
+ * @param interfaceName
8
+ * @param member
9
+ * @param path
10
+ */
11
+ function addressKey(interfaceName, member, path) {
12
+ return `${interfaceName}.${member}@${path}`;
13
+ }
14
+ /**
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 from
17
+ * `@DBusInterface`). A call matches an action by its interface, its method and its object path; a call matching none
18
+ * fails with `ControllerActionNotFoundException`. `@DBusMethod` and {@link dbusEntrypoint} use it: an application only
19
+ * needs it to write an adapter of its own.
20
+ */
21
+ export const DBusChannel = hasChannel('dbus') ? getChannel('dbus') : defineChannel({
22
+ name: 'dbus',
23
+ kind: 'request',
24
+ contextType: DBUS_CONTEXT_TYPE,
25
+ validate: (pattern) => {
26
+ checkMemberName(pattern.member, 'method');
27
+ if (pattern.interface !== undefined)
28
+ checkInterfaceName(pattern.interface);
29
+ if (pattern.path !== undefined)
30
+ checkPathTemplate(pattern.path);
31
+ return pattern;
32
+ },
33
+ identify: (pattern) => addressKey(pattern.interface ?? '?', pattern.member, pattern.path ?? '?'),
34
+ describe: (pattern) => {
35
+ const { returns, ...address } = pattern;
36
+ return returns === undefined ? address : { ...address, returns: returns };
37
+ },
38
+ prefix: (prefix, pattern) => {
39
+ const dbus = prefix.dbus;
40
+ if (!dbus || (pattern.path !== undefined && pattern.interface !== undefined))
41
+ return undefined;
42
+ return { ...pattern, path: pattern.path ?? dbus.path, interface: pattern.interface ?? dbus.interface };
43
+ },
44
+ createMatcher: (entries) => {
45
+ const templates = entries
46
+ .filter((entry) => entry.details.pattern.path !== undefined)
47
+ .map((entry) => ({ entry: entry, template: compilePathTemplate(entry.details.pattern.path) }));
48
+ return {
49
+ match: (context) => {
50
+ const { path, interface: interfaceName, member } = context;
51
+ for (const { entry, template } of templates) {
52
+ if (entry.details.pattern.interface !== interfaceName || entry.details.pattern.member !== member)
53
+ continue;
54
+ const params = template.match(path);
55
+ if (params)
56
+ return { entry: entry, params: params };
57
+ }
58
+ return undefined;
59
+ }
60
+ };
61
+ },
62
+ notFound: (context) => new ControllerActionNotFoundException('The DBus method {member} of the interface {interface} of {path} is not found', {
63
+ member: context.member,
64
+ interface: context.interface,
65
+ path: context.path
66
+ })
67
+ });
@@ -0,0 +1,267 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ 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;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ var __metadata = (this && this.__metadata) || function (k, v) {
8
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9
+ };
10
+ import { DBus } from 'dbus-sdk';
11
+ import { Component, DTO } from '@lakutata/core';
12
+ import { Configurable } from '@lakutata/core/decorator/di';
13
+ /**
14
+ * Build the declaration of a {@link DBusClientComponent} for the `components` of an application or a module, from its
15
+ * typed options.
16
+ * @param options The bus address and the connection options.
17
+ * @returns The declaration: the options with `class: DBusClientComponent`.
18
+ * @example
19
+ * ```typescript
20
+ * import {Application} from 'lakutata'
21
+ * import {buildDBusClientOptions, DBusClientComponent} from '@lakutata/dbus'
22
+ *
23
+ * Application
24
+ * .run(() => ({
25
+ * id: 'client.app',
26
+ * name: 'Client',
27
+ * components: {
28
+ * dbus: buildDBusClientOptions({busAddress: 'unix:path=/var/run/dbus/system_bus_socket', timeout: 5000})
29
+ * }
30
+ * }))
31
+ * .onLaunched(async (app: Application): Promise<void> => {
32
+ * const dbus: DBusClientComponent = await app.getObject('dbus')
33
+ * console.log(await dbus.listNames())
34
+ * })
35
+ * ```
36
+ */
37
+ export const buildDBusClientOptions = (options) => ({
38
+ class: DBusClientComponent,
39
+ busAddress: options.busAddress,
40
+ timeout: options.timeout,
41
+ callTimeout: options.callTimeout,
42
+ decode: options.decode
43
+ });
44
+ /**
45
+ * A D-Bus client: a connection to a bus, to call the services on it, read their objects, properties and introspection,
46
+ * and listen to their signals. Declare it in the `components` of an application with {@link buildDBusClientOptions}
47
+ * (or `{class: DBusClientComponent, busAddress: …}`) and get it with `getObject()` or `@Inject`; it is a singleton
48
+ * component. It connects when it is created, so a bus that cannot be reached fails its creation; it disconnects when it
49
+ * is destroyed (the application stopping). It is a thin layer over the `DBus` connection of `dbus-sdk`, whose classes
50
+ * (`DBusService`, `DBusObject`, `DBusRemoteInterface`…) its methods return.
51
+ *
52
+ * Its `on`, `once`, `off` and `removeListener` listen to the connection's events (`'nameOwnerChanged'`,
53
+ * `'connectionClose'`…), not to events of the component.
54
+ * @example
55
+ * ```typescript
56
+ * import {Application} from 'lakutata'
57
+ * import {DBusClientComponent, type DBusRemoteInterface} from '@lakutata/dbus'
58
+ *
59
+ * Application
60
+ * .run(() => ({
61
+ * id: 'client.app',
62
+ * name: 'Client',
63
+ * components: {
64
+ * dbus: {class: DBusClientComponent, busAddress: 'unix:path=/var/run/dbus/system_bus_socket'}
65
+ * }
66
+ * }))
67
+ * .onLaunched(async (app: Application): Promise<void> => {
68
+ * const dbus: DBusClientComponent = await app.getObject('dbus')
69
+ * //A method call: the output arguments, as an array
70
+ * const [owner] = await dbus.invoke({
71
+ * service: 'org.freedesktop.DBus',
72
+ * objectPath: '/org/freedesktop/DBus',
73
+ * interface: 'org.freedesktop.DBus',
74
+ * method: 'GetNameOwner',
75
+ * signature: 's',
76
+ * args: ['org.freedesktop.DBus']
77
+ * })
78
+ * console.log(owner)
79
+ * //An interface, from the introspection of its object: its methods, properties and signals
80
+ * const device: DBusRemoteInterface = await dbus.interface('com.example.DeviceService', '/com/example/Devices/A1', 'com.example.Device')
81
+ * console.log(await device.method.Status({sn: 'A1'}))
82
+ * console.log(await device.property.State.get())
83
+ * device.signal.on('Changed', (data: unknown): void => console.log(data))
84
+ * dbus.on('nameOwnerChanged', (name: string, oldOwner: string, newOwner: string): void => console.log(name, oldOwner, newOwner))
85
+ * })
86
+ * ```
87
+ */
88
+ export class DBusClientComponent extends Component {
89
+ /**
90
+ * The unique name the bus gave this connection (`':1.42'`).
91
+ */
92
+ get uniqueName() {
93
+ return this.dbus.uniqueName;
94
+ }
95
+ /**
96
+ * Connect to the bus, when the component is created.
97
+ * @throws {Error} When the bus cannot be reached, or the authentication fails.
98
+ */
99
+ async init() {
100
+ this.dbus = await DBus.connect({
101
+ busAddress: this.busAddress,
102
+ timeout: this.timeout,
103
+ callTimeout: this.callTimeout,
104
+ //The 64-bit integers are numbers unless the decoding says otherwise (as the client of lakutata 2.x)
105
+ decode: { int64: 'number', ...this.decode }
106
+ });
107
+ }
108
+ /**
109
+ * Disconnect from the bus, when the component is destroyed.
110
+ */
111
+ async destroy() {
112
+ await this.dbus.disconnect();
113
+ }
114
+ invoke(opts) {
115
+ return this.dbus.invoke(opts);
116
+ }
117
+ on(eventName, listener) {
118
+ this.dbus.on(eventName, listener);
119
+ return this;
120
+ }
121
+ once(eventName, listener) {
122
+ this.dbus.once(eventName, listener);
123
+ return this;
124
+ }
125
+ off(eventName, listener) {
126
+ this.dbus.off(eventName, listener);
127
+ return this;
128
+ }
129
+ removeListener(eventName, listener) {
130
+ this.dbus.removeListener(eventName, listener);
131
+ return this;
132
+ }
133
+ /**
134
+ * Ask the bus to route the messages matching a rule to this connection, without waiting for the bus's answer (the
135
+ * signal emitters of {@link interface} and {@link interfaceSignal} add their own rules).
136
+ * @param rule The match rule (`"type='signal',interface='com.example.Device'"`).
137
+ */
138
+ addMatch(rule) {
139
+ return this.dbus.addMatch(rule);
140
+ }
141
+ /**
142
+ * Remove a match rule added with {@link addMatch}, without waiting for the bus's answer.
143
+ * @param rule The match rule, as it was added.
144
+ */
145
+ removeMatch(rule) {
146
+ return this.dbus.removeMatch(rule);
147
+ }
148
+ /**
149
+ * Get the connection owning a name.
150
+ * @param name The bus name (`'com.example.DeviceService'`).
151
+ * @returns The unique name of its owner (`':1.42'`), or `undefined` when nobody owns it or the query fails.
152
+ */
153
+ async getNameOwner(name) {
154
+ return this.dbus.getNameOwner(name);
155
+ }
156
+ /**
157
+ * List the names the bus can start a service for (D-Bus activation), running or not.
158
+ * @returns The names.
159
+ */
160
+ async listActivatableNames() {
161
+ return this.dbus.listActivatableNames();
162
+ }
163
+ /**
164
+ * List the names owned on the bus now: the well-known names and the unique names of the connections.
165
+ * @returns The names.
166
+ */
167
+ async listNames() {
168
+ return this.dbus.listNames();
169
+ }
170
+ /**
171
+ * Tell whether a name is owned on the bus now.
172
+ * @param name The bus name.
173
+ * @returns `true` when a connection owns it.
174
+ */
175
+ async nameHasOwner(name) {
176
+ return this.dbus.nameHasOwner(name);
177
+ }
178
+ /**
179
+ * List the names of the bus, owned or activatable, with their owner, whether they are active or activatable, and the
180
+ * process id of their owner.
181
+ * @returns The names and their details.
182
+ */
183
+ async listBusNames() {
184
+ return this.dbus.listBusNames();
185
+ }
186
+ /**
187
+ * List the services of the bus: its well-known names (the unique names `:1.42` left out), with their details.
188
+ * @returns The services and their details.
189
+ */
190
+ async listServices() {
191
+ return this.dbus.listServices();
192
+ }
193
+ /**
194
+ * Get the services of the bus which are running, to browse their objects (the activatable services which are not
195
+ * running are left out, not started: get one with {@link service} to start it).
196
+ * @returns The services.
197
+ */
198
+ async services() {
199
+ return this.dbus.getServices();
200
+ }
201
+ /**
202
+ * Get a service, starting it when it is activatable and not running.
203
+ * @param service The service's well-known name (`'org.freedesktop.NetworkManager'`).
204
+ * @returns The service.
205
+ * @throws {Error} A `ServiceNotFoundError` of `dbus-sdk` when the name is neither owned nor activatable.
206
+ */
207
+ async service(service) {
208
+ return this.dbus.getService(service);
209
+ }
210
+ /**
211
+ * Get an object of a service, to read its introspection and its interfaces.
212
+ * @param service The service's well-known name.
213
+ * @param objectPath The object path (`'/org/freedesktop/NetworkManager'`).
214
+ * @returns The object.
215
+ * @throws {Error} A `ServiceNotFoundError` of `dbus-sdk` when the service is neither running nor activatable.
216
+ */
217
+ async object(service, objectPath) {
218
+ return this.dbus.getObject(service, objectPath);
219
+ }
220
+ /**
221
+ * Get an interface of an object, built from the object's introspection: its methods (`method.Name(...args)`, or
222
+ * `noReplyMethod`), its properties (`property.Name.get()`, `.set(value)`) and its signals (`signal.on('Name', …)`).
223
+ * @param service The service's well-known name.
224
+ * @param objectPath The object path.
225
+ * @param iface The interface name (`'org.freedesktop.NetworkManager'`).
226
+ * @returns The interface.
227
+ * @throws {Error} When the service is not found, or the object does not implement the interface
228
+ * (`InterfaceNotFoundError` of `dbus-sdk`).
229
+ */
230
+ async interface(service, objectPath, iface) {
231
+ return this.dbus.getInterface(service, objectPath, iface);
232
+ }
233
+ /**
234
+ * Get the signals of an interface of an object: an event emitter emitting each signal under its name, with its
235
+ * arguments.
236
+ * @param service The service's well-known name.
237
+ * @param objectPath The object path.
238
+ * @param iface The interface name.
239
+ * @returns The emitter of the interface's signals.
240
+ * @throws {Error} When the service is not found, or the object does not implement the interface
241
+ * (`InterfaceNotFoundError` of `dbus-sdk`).
242
+ */
243
+ async interfaceSignal(service, objectPath, iface) {
244
+ const dbusIface = await this.dbus.getInterface(service, objectPath, iface);
245
+ return dbusIface.signal;
246
+ }
247
+ }
248
+ __decorate([
249
+ Configurable(DTO.String().required()),
250
+ __metadata("design:type", String)
251
+ ], DBusClientComponent.prototype, "busAddress", void 0);
252
+ __decorate([
253
+ Configurable(DTO.Number().positive().integer().optional()),
254
+ __metadata("design:type", Number)
255
+ ], DBusClientComponent.prototype, "timeout", void 0);
256
+ __decorate([
257
+ Configurable(DTO.Number().positive().integer().optional()),
258
+ __metadata("design:type", Number)
259
+ ], DBusClientComponent.prototype, "callTimeout", void 0);
260
+ __decorate([
261
+ Configurable(DTO.Object({
262
+ types: DTO.String().valid('none', 'variants', 'all').optional(),
263
+ dictionaries: DTO.String().valid('object', 'map').optional(),
264
+ int64: DTO.String().valid('bigint', 'number').optional()
265
+ }).optional()),
266
+ __metadata("design:type", Object)
267
+ ], DBusClientComponent.prototype, "decode", void 0);