matterbridge 3.4.1-dev-20251127-826b2bf → 3.4.1-dev-20251129-ff1e22f
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/CHANGELOG.md +8 -3
- package/README-DEV.md +20 -4
- package/README.md +2 -0
- package/dist/broadcastServer.js +88 -24
- package/dist/deviceManager.js +11 -11
- package/dist/frontend.js +26 -28
- package/dist/jestutils/jestHelpers.js +51 -0
- package/dist/matterNode.js +9 -5
- package/dist/matterbridge.js +29 -25
- package/dist/matterbridgePlatform.js +1 -1
- package/dist/pluginManager.js +59 -35
- package/dist/shelly.js +12 -12
- package/dist/update.js +69 -53
- package/dist/utils/error.js +3 -1
- package/dist/workerGlobalPrefix.js +39 -0
- package/dist/workerTypes.js +1 -0
- package/dist/workers.js +35 -0
- package/frontend/build/assets/index.css +1 -1
- package/frontend/build/assets/index.js +1 -1
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -14,8 +14,9 @@ The project will evolve to a multi-threaded architecture (the CLI will become th
|
|
|
14
14
|
- frontend;
|
|
15
15
|
- plugins;
|
|
16
16
|
- devices;
|
|
17
|
-
-
|
|
18
|
-
-
|
|
17
|
+
- check updates;
|
|
18
|
+
- npm install;
|
|
19
|
+
- ✅ get global node_modules;
|
|
19
20
|
- all plugins in bridge mode;
|
|
20
21
|
- each plugin in childbridge mode;
|
|
21
22
|
|
|
@@ -31,7 +32,9 @@ Advantages:
|
|
|
31
32
|
### Added
|
|
32
33
|
|
|
33
34
|
- [matterbridge.io]: Updated website https://matterbridge.io with all guides.
|
|
34
|
-
- [matterbridge]: Added addVirtualEndpoint() to match thread module.
|
|
35
|
+
- [matterbridge]: Added addVirtualEndpoint() to match Matterbridge thread module.
|
|
36
|
+
- [BroadcastServer]: Backport BroadcastServer v.2.0.0 from Matterbridge thread module.
|
|
37
|
+
- [MatterbridgePrefix]: Added worker thread to get global node_modules.
|
|
35
38
|
|
|
36
39
|
### Changed
|
|
37
40
|
|
|
@@ -39,10 +42,12 @@ Advantages:
|
|
|
39
42
|
- [frontend]: Updated dependencies.
|
|
40
43
|
- [frontend]: Bumped `frontend` version to 3.3.2.
|
|
41
44
|
- [platform]: Bumped MatterbridgePlatform v.1.5.0.
|
|
45
|
+
- [thread]: Bump BroadcastServer to v1.0.4.
|
|
42
46
|
|
|
43
47
|
### Fixed
|
|
44
48
|
|
|
45
49
|
- [frontend]: Fixed when the user put special characters in password. Thanks Dabern (https://github.com/Luligu/matterbridge/issues/443).
|
|
50
|
+
- [frontend]: Fixed minimum size to 1300x1024 and add a max width to Url. Thanks Ricardo B. (https://github.com/Luligu/matterbridge-home-assistant-addon/issues/23).
|
|
46
51
|
|
|
47
52
|
<a href="https://www.buymeacoffee.com/luligugithub"><img src="https://matterbridge.io/bmc-button.svg" alt="Buy me a coffee" width="80"></a>
|
|
48
53
|
|
package/README-DEV.md
CHANGED
|
@@ -251,9 +251,25 @@ It is called when the plugin config has been updated.
|
|
|
251
251
|
|
|
252
252
|
Retrieves the devices registered with the platform.
|
|
253
253
|
|
|
254
|
+
### getDeviceByName(deviceName: string): MatterbridgeEndpoint | undefined
|
|
255
|
+
|
|
256
|
+
### getDeviceByUniqueId(uniqueId: string): MatterbridgeEndpoint | undefined
|
|
257
|
+
|
|
258
|
+
### getDeviceBySerialNumber(serialNumber: string): MatterbridgeEndpoint | undefined
|
|
259
|
+
|
|
260
|
+
### getDeviceById(id: string): MatterbridgeEndpoint | undefined
|
|
261
|
+
|
|
262
|
+
### getDeviceByOriginalId(originalId: string): MatterbridgeEndpoint | undefined
|
|
263
|
+
|
|
264
|
+
### getDeviceByNumber(number: EndpointNumber | number): MatterbridgeEndpoint | undefined
|
|
265
|
+
|
|
266
|
+
They all return MatterbridgeEndpoint or undefined if not found.
|
|
267
|
+
|
|
254
268
|
### hasDeviceName(deviceName: string): boolean
|
|
255
269
|
|
|
256
|
-
|
|
270
|
+
### hasDeviceUniqueId(deviceUniqueId: string): boolean
|
|
271
|
+
|
|
272
|
+
Checks if a device with this name or uniqueId is already registered in the platform.
|
|
257
273
|
|
|
258
274
|
### async registerDevice(device: MatterbridgeEndpoint)
|
|
259
275
|
|
|
@@ -288,7 +304,7 @@ You create a Matter device with a new instance of MatterbridgeEndpoint(definitio
|
|
|
288
304
|
|
|
289
305
|
In the above example we create a contact sensor device type with also a power source device type feature replaceble battery.
|
|
290
306
|
|
|
291
|
-
All device types are defined in src\matterbridgeDeviceTypes.ts and taken from the 'Matter-1.4.
|
|
307
|
+
All device types are defined in src\matterbridgeDeviceTypes.ts and taken from the 'Matter-1.4.2-Device-Library-Specification.pdf'.
|
|
292
308
|
|
|
293
309
|
All default cluster helpers are available as methods of MatterbridgeEndpoint.
|
|
294
310
|
|
|
@@ -388,7 +404,7 @@ const heatPump = new HeatPump('Heat Pump', 'HP1234567890');
|
|
|
388
404
|
|
|
389
405
|
## Plugin config file
|
|
390
406
|
|
|
391
|
-
Each plugin has a minimal default config file injected by Matterbridge when it is loaded:
|
|
407
|
+
Each plugin has a minimal default config file injected by Matterbridge when it is loaded and the plugin doesn't have its own default one:
|
|
392
408
|
|
|
393
409
|
```typescript
|
|
394
410
|
{
|
|
@@ -408,7 +424,7 @@ In all subsequent loads the config file is loaded from the '.matterbridge' direc
|
|
|
408
424
|
|
|
409
425
|
## Plugin schema file
|
|
410
426
|
|
|
411
|
-
Each plugin has a minimal default schema file injected by Matterbridge when it is loaded:
|
|
427
|
+
Each plugin has a minimal default schema file injected by Matterbridge when it is loaded and the plugin doesn't have its own default one:
|
|
412
428
|
|
|
413
429
|
```typescript
|
|
414
430
|
{
|
package/README.md
CHANGED
|
@@ -56,6 +56,8 @@ Join us in the Matterbridge [Discord group](https://discord.gg/QX58CDe6hd) creat
|
|
|
56
56
|
|
|
57
57
|
https://www.youtube.com/watch?v=goNB9Cgh_Fk
|
|
58
58
|
|
|
59
|
+
https://www.youtube.com/watch?v=06zzl7o_IqQ
|
|
60
|
+
|
|
59
61
|
## Reviews
|
|
60
62
|
|
|
61
63
|
https://www.matteralpha.com/how-to/how-to-configure-an-open-source-matter-bridge-at-home
|
package/dist/broadcastServer.js
CHANGED
|
@@ -2,8 +2,9 @@ if (process.argv.includes('--loader') || process.argv.includes('-loader'))
|
|
|
2
2
|
console.log('\u001B[32mBroadcastServer loaded.\u001B[40;0m');
|
|
3
3
|
import { EventEmitter } from 'node:events';
|
|
4
4
|
import { BroadcastChannel } from 'node:worker_threads';
|
|
5
|
-
import { CYAN, db, debugStringify } from 'node-ansi-logger';
|
|
5
|
+
import { CYAN, db, debugStringify, er } from 'node-ansi-logger';
|
|
6
6
|
import { hasParameter } from './utils/commandLine.js';
|
|
7
|
+
import { logError } from './utils/error.js';
|
|
7
8
|
export class BroadcastServer extends EventEmitter {
|
|
8
9
|
name;
|
|
9
10
|
log;
|
|
@@ -24,24 +25,64 @@ export class BroadcastServer extends EventEmitter {
|
|
|
24
25
|
this.broadcastChannel.close();
|
|
25
26
|
}
|
|
26
27
|
getUniqueId() {
|
|
27
|
-
return Math.floor(Math.random() *
|
|
28
|
+
return Math.floor(Math.random() * 900000000) + 100000000;
|
|
28
29
|
}
|
|
29
|
-
isWorkerRequest(
|
|
30
|
-
|
|
30
|
+
isWorkerRequest(value) {
|
|
31
|
+
if (typeof value !== 'object' || value === null) {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
const message = value;
|
|
35
|
+
if (typeof message.type !== 'string' || typeof message.src !== 'string' || typeof message.dst !== 'string' || typeof message.id !== 'number' || typeof message.timestamp !== 'number') {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
return message.result === undefined && message.error === undefined;
|
|
39
|
+
}
|
|
40
|
+
isWorkerRequestOfType(value, type) {
|
|
41
|
+
return this.isWorkerRequest(value) && value.type === type;
|
|
42
|
+
}
|
|
43
|
+
isWorkerResponse(value) {
|
|
44
|
+
if (typeof value !== 'object' || value === null) {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
const message = value;
|
|
48
|
+
if (typeof message.type !== 'string' || typeof message.src !== 'string' || typeof message.dst !== 'string' || typeof message.id !== 'number' || typeof message.timestamp !== 'number') {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
const hasError = typeof message.error === 'string';
|
|
52
|
+
const hasResult = message.result !== undefined;
|
|
53
|
+
return hasError !== hasResult;
|
|
31
54
|
}
|
|
32
|
-
|
|
33
|
-
return
|
|
55
|
+
isWorkerResponseOfType(value, type) {
|
|
56
|
+
return this.isWorkerResponse(value) && value.type === type;
|
|
34
57
|
}
|
|
35
58
|
broadcastMessageHandler(event) {
|
|
36
|
-
const
|
|
37
|
-
if (
|
|
38
|
-
|
|
39
|
-
|
|
59
|
+
const msg = event.data;
|
|
60
|
+
if (msg.dst === this.name || msg.dst === 'all') {
|
|
61
|
+
if (this.verbose)
|
|
62
|
+
this.log.debug(`Server ${CYAN}${this.name}${db} received broadcast message: ${debugStringify(msg)}`);
|
|
63
|
+
this.emit('broadcast_message', msg);
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
if (this.verbose)
|
|
67
|
+
this.log.debug(`Server ${CYAN}${this.name}${db} received unrelated broadcast message: ${debugStringify(msg)}`);
|
|
68
|
+
}
|
|
40
69
|
}
|
|
41
70
|
broadcast(message) {
|
|
71
|
+
if (message.id === undefined) {
|
|
72
|
+
message.id = this.getUniqueId();
|
|
73
|
+
}
|
|
74
|
+
if (message.timestamp === undefined) {
|
|
75
|
+
message.timestamp = Date.now();
|
|
76
|
+
}
|
|
77
|
+
message.src = this.name;
|
|
42
78
|
if (this.verbose)
|
|
43
79
|
this.log.debug(`Broadcasting message: ${debugStringify(message)}`);
|
|
44
|
-
|
|
80
|
+
try {
|
|
81
|
+
this.broadcastChannel.postMessage(message);
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
logError(this.log, `Failed to broadcast message ${debugStringify(message)}${er}`, error);
|
|
85
|
+
}
|
|
45
86
|
}
|
|
46
87
|
request(message) {
|
|
47
88
|
if (message.id === undefined) {
|
|
@@ -50,25 +91,43 @@ export class BroadcastServer extends EventEmitter {
|
|
|
50
91
|
if (message.timestamp === undefined) {
|
|
51
92
|
message.timestamp = Date.now();
|
|
52
93
|
}
|
|
53
|
-
|
|
54
|
-
|
|
94
|
+
message.src = this.name;
|
|
95
|
+
if (!this.isWorkerRequest(message)) {
|
|
96
|
+
this.log.error(`Invalid request message format: ${debugStringify(message)}`);
|
|
55
97
|
return;
|
|
56
98
|
}
|
|
57
99
|
if (this.verbose)
|
|
58
100
|
this.log.debug(`Broadcasting request message: ${debugStringify(message)}`);
|
|
59
|
-
|
|
101
|
+
try {
|
|
102
|
+
this.broadcastChannel.postMessage(message);
|
|
103
|
+
}
|
|
104
|
+
catch (error) {
|
|
105
|
+
logError(this.log, `Failed to broadcast request message ${debugStringify(message)}${er}`, error);
|
|
106
|
+
}
|
|
60
107
|
}
|
|
61
108
|
respond(message) {
|
|
109
|
+
if (typeof message.timestamp === 'number') {
|
|
110
|
+
message.elapsed = Date.now() - message.timestamp;
|
|
111
|
+
}
|
|
62
112
|
if (message.timestamp === undefined) {
|
|
63
113
|
message.timestamp = Date.now();
|
|
64
114
|
}
|
|
65
|
-
if (
|
|
66
|
-
|
|
115
|
+
if (message.dst === this.name || message.dst === 'all') {
|
|
116
|
+
message.dst = message.src;
|
|
117
|
+
}
|
|
118
|
+
message.src = this.name;
|
|
119
|
+
if (!this.isWorkerResponse(message)) {
|
|
120
|
+
this.log.error(`Invalid response message format: ${debugStringify(message)}`);
|
|
67
121
|
return;
|
|
68
122
|
}
|
|
69
123
|
if (this.verbose)
|
|
70
124
|
this.log.debug(`Broadcasting response message: ${debugStringify(message)}`);
|
|
71
|
-
|
|
125
|
+
try {
|
|
126
|
+
this.broadcastChannel.postMessage(message);
|
|
127
|
+
}
|
|
128
|
+
catch (error) {
|
|
129
|
+
logError(this.log, `Failed to broadcast response message ${debugStringify(message)}${er}`, error);
|
|
130
|
+
}
|
|
72
131
|
}
|
|
73
132
|
async fetch(message, timeout = 250) {
|
|
74
133
|
if (message.id === undefined) {
|
|
@@ -81,23 +140,28 @@ export class BroadcastServer extends EventEmitter {
|
|
|
81
140
|
this.log.debug(`Fetching message: ${debugStringify(message)}`);
|
|
82
141
|
return new Promise((resolve, reject) => {
|
|
83
142
|
const responseHandler = (msg) => {
|
|
84
|
-
if (this.
|
|
143
|
+
if (this.isWorkerResponseOfType(msg, message.type) && msg.id === message.id) {
|
|
85
144
|
clearTimeout(timeoutId);
|
|
86
145
|
this.off('broadcast_message', responseHandler);
|
|
87
146
|
if (this.verbose)
|
|
88
147
|
this.log.debug(`Fetch response: ${debugStringify(msg)}`);
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
if (
|
|
93
|
-
|
|
148
|
+
if ('error' in msg && typeof msg.error === 'string') {
|
|
149
|
+
reject(new Error(`Fetch received error response ${msg.error} to message type ${message.type} id ${message.id} from ${message.src} to ${message.dst}`));
|
|
150
|
+
}
|
|
151
|
+
else if ('result' in msg) {
|
|
152
|
+
resolve(msg);
|
|
153
|
+
}
|
|
154
|
+
else {
|
|
155
|
+
reject(new Error(`Fetch received malformed response for message type ${message.type} id ${message.id} from ${message.src} to ${message.dst}`));
|
|
156
|
+
}
|
|
157
|
+
return;
|
|
94
158
|
}
|
|
95
159
|
};
|
|
96
160
|
this.on('broadcast_message', responseHandler);
|
|
97
161
|
this.request(message);
|
|
98
162
|
const timeoutId = setTimeout(() => {
|
|
99
163
|
this.off('broadcast_message', responseHandler);
|
|
100
|
-
reject(new Error(`Fetch timeout after ${timeout}ms for message id ${message.id}`));
|
|
164
|
+
reject(new Error(`Fetch timeout after ${timeout}ms for message type ${message.type} id ${message.id} from ${message.src} to ${message.dst}`));
|
|
101
165
|
}, timeout);
|
|
102
166
|
});
|
|
103
167
|
}
|
package/dist/deviceManager.js
CHANGED
|
@@ -44,44 +44,44 @@ export class DeviceManager {
|
|
|
44
44
|
this.server.close();
|
|
45
45
|
}
|
|
46
46
|
async msgHandler(msg) {
|
|
47
|
-
if (this.server.isWorkerRequest(msg
|
|
47
|
+
if (this.server.isWorkerRequest(msg) && (msg.dst === 'all' || msg.dst === 'devices')) {
|
|
48
48
|
if (this.verbose)
|
|
49
49
|
this.log.debug(`Received request message ${CYAN}${msg.type}${db} from ${CYAN}${msg.src}${db}: ${debugStringify(msg)}${db}`);
|
|
50
50
|
switch (msg.type) {
|
|
51
51
|
case 'get_log_level':
|
|
52
|
-
this.server.respond({ ...msg,
|
|
52
|
+
this.server.respond({ ...msg, result: { logLevel: this.log.logLevel } });
|
|
53
53
|
break;
|
|
54
54
|
case 'set_log_level':
|
|
55
55
|
this.log.logLevel = msg.params.logLevel;
|
|
56
|
-
this.server.respond({ ...msg,
|
|
56
|
+
this.server.respond({ ...msg, result: { logLevel: this.log.logLevel } });
|
|
57
57
|
break;
|
|
58
58
|
case 'devices_length':
|
|
59
|
-
this.server.respond({ ...msg,
|
|
59
|
+
this.server.respond({ ...msg, result: { length: this.length } });
|
|
60
60
|
break;
|
|
61
61
|
case 'devices_size':
|
|
62
|
-
this.server.respond({ ...msg,
|
|
62
|
+
this.server.respond({ ...msg, result: { size: this.size } });
|
|
63
63
|
break;
|
|
64
64
|
case 'devices_has':
|
|
65
|
-
this.server.respond({ ...msg,
|
|
65
|
+
this.server.respond({ ...msg, result: { has: this.has(msg.params.uniqueId) } });
|
|
66
66
|
break;
|
|
67
67
|
case 'devices_get':
|
|
68
68
|
{
|
|
69
69
|
const endpoint = this.get(msg.params.uniqueId);
|
|
70
|
-
this.server.respond({ ...msg,
|
|
70
|
+
this.server.respond({ ...msg, result: { device: endpoint ? toBaseDevice(endpoint) : undefined } });
|
|
71
71
|
}
|
|
72
72
|
break;
|
|
73
73
|
case 'devices_set':
|
|
74
|
-
this.server.respond({ ...msg,
|
|
74
|
+
this.server.respond({ ...msg, result: { device: this.set(toBaseDevice(msg.params.device)) } });
|
|
75
75
|
break;
|
|
76
76
|
case 'devices_remove':
|
|
77
|
-
this.server.respond({ ...msg,
|
|
77
|
+
this.server.respond({ ...msg, result: { success: this.remove(toBaseDevice(msg.params.device)) } });
|
|
78
78
|
break;
|
|
79
79
|
case 'devices_clear':
|
|
80
80
|
this.clear();
|
|
81
|
-
this.server.respond({ ...msg,
|
|
81
|
+
this.server.respond({ ...msg, result: { success: true } });
|
|
82
82
|
break;
|
|
83
83
|
case 'devices_basearray':
|
|
84
|
-
this.server.respond({ ...msg,
|
|
84
|
+
this.server.respond({ ...msg, result: { devices: this.baseArray(msg.params.pluginName) } });
|
|
85
85
|
break;
|
|
86
86
|
default:
|
|
87
87
|
if (this.verbose)
|
package/dist/frontend.js
CHANGED
|
@@ -46,90 +46,88 @@ export class Frontend extends EventEmitter {
|
|
|
46
46
|
this.server.close();
|
|
47
47
|
}
|
|
48
48
|
async msgHandler(msg) {
|
|
49
|
-
if (this.server.isWorkerRequest(msg
|
|
49
|
+
if (this.server.isWorkerRequest(msg) && (msg.dst === 'all' || msg.dst === 'frontend')) {
|
|
50
50
|
if (this.verbose)
|
|
51
51
|
this.log.debug(`Received broadcast request ${CYAN}${msg.type}${db} from ${CYAN}${msg.src}${db}: ${debugStringify(msg)}${db}`);
|
|
52
52
|
switch (msg.type) {
|
|
53
53
|
case 'get_log_level':
|
|
54
|
-
this.server.respond({ ...msg,
|
|
54
|
+
this.server.respond({ ...msg, result: { logLevel: this.log.logLevel } });
|
|
55
55
|
break;
|
|
56
56
|
case 'set_log_level':
|
|
57
57
|
this.log.logLevel = msg.params.logLevel;
|
|
58
|
-
this.server.respond({ ...msg,
|
|
58
|
+
this.server.respond({ ...msg, result: { logLevel: this.log.logLevel } });
|
|
59
59
|
break;
|
|
60
60
|
case 'frontend_start':
|
|
61
61
|
await this.start(msg.params.port);
|
|
62
|
-
this.server.respond({ ...msg,
|
|
62
|
+
this.server.respond({ ...msg, result: { success: true } });
|
|
63
63
|
break;
|
|
64
64
|
case 'frontend_stop':
|
|
65
65
|
await this.stop();
|
|
66
|
-
this.server.respond({ ...msg,
|
|
66
|
+
this.server.respond({ ...msg, result: { success: true } });
|
|
67
67
|
break;
|
|
68
68
|
case 'frontend_refreshrequired':
|
|
69
|
-
this.wssSendRefreshRequired(msg.params.changed, { matter: msg.params.matter });
|
|
70
|
-
this.server.respond({ ...msg,
|
|
69
|
+
this.wssSendRefreshRequired(msg.params.changed, msg.params.matter ? { matter: msg.params.matter } : undefined);
|
|
70
|
+
this.server.respond({ ...msg, result: { success: true } });
|
|
71
71
|
break;
|
|
72
72
|
case 'frontend_restartrequired':
|
|
73
73
|
this.wssSendRestartRequired(msg.params.snackbar, msg.params.fixed);
|
|
74
|
-
this.server.respond({ ...msg,
|
|
74
|
+
this.server.respond({ ...msg, result: { success: true } });
|
|
75
75
|
break;
|
|
76
76
|
case 'frontend_restartnotrequired':
|
|
77
77
|
this.wssSendRestartNotRequired(msg.params.snackbar);
|
|
78
|
-
this.server.respond({ ...msg,
|
|
78
|
+
this.server.respond({ ...msg, result: { success: true } });
|
|
79
79
|
break;
|
|
80
80
|
case 'frontend_updaterequired':
|
|
81
81
|
this.wssSendUpdateRequired(msg.params.devVersion);
|
|
82
|
-
this.server.respond({ ...msg,
|
|
82
|
+
this.server.respond({ ...msg, result: { success: true } });
|
|
83
83
|
break;
|
|
84
84
|
case 'frontend_snackbarmessage':
|
|
85
85
|
this.wssSendSnackbarMessage(msg.params.message, msg.params.timeout, msg.params.severity);
|
|
86
|
-
this.server.respond({ ...msg,
|
|
86
|
+
this.server.respond({ ...msg, result: { success: true } });
|
|
87
87
|
break;
|
|
88
88
|
case 'frontend_attributechanged':
|
|
89
89
|
this.wssSendAttributeChangedMessage(msg.params.plugin, msg.params.serialNumber, msg.params.uniqueId, msg.params.number, msg.params.id, msg.params.cluster, msg.params.attribute, msg.params.value);
|
|
90
|
-
this.server.respond({ ...msg,
|
|
90
|
+
this.server.respond({ ...msg, result: { success: true } });
|
|
91
91
|
break;
|
|
92
92
|
case 'frontend_logmessage':
|
|
93
93
|
this.wssSendLogMessage(msg.params.level, msg.params.time, msg.params.name, msg.params.message);
|
|
94
|
-
this.server.respond({ ...msg,
|
|
94
|
+
this.server.respond({ ...msg, result: { success: true } });
|
|
95
|
+
break;
|
|
96
|
+
case 'frontend_broadcast_message':
|
|
97
|
+
this.wssBroadcastMessage(msg.params.msg);
|
|
98
|
+
this.server.respond({ ...msg, result: { success: true } });
|
|
95
99
|
break;
|
|
96
100
|
default:
|
|
97
101
|
if (this.verbose)
|
|
98
102
|
this.log.debug(`Unknown broadcast request ${CYAN}${msg.type}${db} from ${CYAN}${msg.src}${db}`);
|
|
99
103
|
}
|
|
100
104
|
}
|
|
101
|
-
if (this.server.isWorkerResponse(msg
|
|
105
|
+
if (this.server.isWorkerResponse(msg) && msg.result && (msg.dst === 'all' || msg.dst === 'frontend')) {
|
|
102
106
|
if (this.verbose)
|
|
103
107
|
this.log.debug(`Received broadcast response ${CYAN}${msg.type}${db} from ${CYAN}${msg.src}${db}: ${debugStringify(msg)}${db}`);
|
|
104
108
|
switch (msg.type) {
|
|
105
|
-
case 'get_log_level':
|
|
106
|
-
case 'set_log_level':
|
|
107
|
-
break;
|
|
108
109
|
case 'plugins_install':
|
|
109
|
-
this.wssSendCloseSnackbarMessage(`Installing package ${msg.
|
|
110
|
-
if (msg.
|
|
110
|
+
this.wssSendCloseSnackbarMessage(`Installing package ${msg.result.packageName}...`);
|
|
111
|
+
if (msg.result.success) {
|
|
111
112
|
this.wssSendRestartRequired(true, true);
|
|
112
113
|
this.wssSendRefreshRequired('plugins');
|
|
113
|
-
this.wssSendSnackbarMessage(`Installed package ${msg.
|
|
114
|
+
this.wssSendSnackbarMessage(`Installed package ${msg.result.packageName}`, 5, 'success');
|
|
114
115
|
}
|
|
115
116
|
else {
|
|
116
|
-
this.wssSendSnackbarMessage(`Package ${msg.
|
|
117
|
+
this.wssSendSnackbarMessage(`Package ${msg.result.packageName} not installed`, 10, 'error');
|
|
117
118
|
}
|
|
118
119
|
break;
|
|
119
120
|
case 'plugins_uninstall':
|
|
120
|
-
this.wssSendCloseSnackbarMessage(`Uninstalling package ${msg.
|
|
121
|
-
if (msg.
|
|
121
|
+
this.wssSendCloseSnackbarMessage(`Uninstalling package ${msg.result.packageName}...`);
|
|
122
|
+
if (msg.result.success) {
|
|
122
123
|
this.wssSendRestartRequired(true, true);
|
|
123
124
|
this.wssSendRefreshRequired('plugins');
|
|
124
|
-
this.wssSendSnackbarMessage(`Uninstalled package ${msg.
|
|
125
|
+
this.wssSendSnackbarMessage(`Uninstalled package ${msg.result.packageName}`, 5, 'success');
|
|
125
126
|
}
|
|
126
127
|
else {
|
|
127
|
-
this.wssSendSnackbarMessage(`Package ${msg.
|
|
128
|
+
this.wssSendSnackbarMessage(`Package ${msg.result.packageName} not uninstalled`, 10, 'error');
|
|
128
129
|
}
|
|
129
130
|
break;
|
|
130
|
-
default:
|
|
131
|
-
if (this.verbose)
|
|
132
|
-
this.log.debug(`Unknown broadcast response ${CYAN}${msg.type}${db} from ${CYAN}${msg.src}${db}`);
|
|
133
131
|
}
|
|
134
132
|
}
|
|
135
133
|
}
|
|
@@ -11,6 +11,9 @@ import { NodeStorageManager } from 'node-persist-manager';
|
|
|
11
11
|
import { Matterbridge } from '../matterbridge.js';
|
|
12
12
|
import { MATTER_STORAGE_NAME, NODE_STORAGE_DIR } from '../matterbridgeTypes.js';
|
|
13
13
|
import { bridge } from '../matterbridgeDeviceTypes.js';
|
|
14
|
+
import { PluginManager } from '../pluginManager.js';
|
|
15
|
+
import { Frontend } from '../frontend.js';
|
|
16
|
+
import { BroadcastServer } from '../broadcastServer.js';
|
|
14
17
|
export const originalProcessArgv = Object.freeze([...process.argv]);
|
|
15
18
|
export const originalProcessEnv = Object.freeze({ ...process.env });
|
|
16
19
|
export let loggerLogSpy;
|
|
@@ -28,6 +31,30 @@ export let consoleErrorSpy;
|
|
|
28
31
|
export let addBridgedEndpointSpy;
|
|
29
32
|
export let removeBridgedEndpointSpy;
|
|
30
33
|
export let removeAllBridgedEndpointsSpy;
|
|
34
|
+
export let addVirtualEndpointSpy;
|
|
35
|
+
export let installPluginSpy;
|
|
36
|
+
export let uninstallPluginSpy;
|
|
37
|
+
export let addPluginSpy;
|
|
38
|
+
export let loadPluginSpy;
|
|
39
|
+
export let startPluginSpy;
|
|
40
|
+
export let configurePluginSpy;
|
|
41
|
+
export let shutdownPluginSpy;
|
|
42
|
+
export let removePluginSpy;
|
|
43
|
+
export let enablePluginSpy;
|
|
44
|
+
export let disablePluginSpy;
|
|
45
|
+
export let wssSendSnackbarMessageSpy;
|
|
46
|
+
export let wssSendCloseSnackbarMessageSpy;
|
|
47
|
+
export let wssSendUpdateRequiredSpy;
|
|
48
|
+
export let wssSendRefreshRequiredSpy;
|
|
49
|
+
export let wssSendRestartRequiredSpy;
|
|
50
|
+
export let wssSendRestartNotRequiredSpy;
|
|
51
|
+
export let broadcastServerIsWorkerRequestSpy;
|
|
52
|
+
export let broadcastServerIsWorkerResponseSpy;
|
|
53
|
+
export let broadcastServerBroadcastSpy;
|
|
54
|
+
export let broadcastServerRequestSpy;
|
|
55
|
+
export let broadcastServerRespondSpy;
|
|
56
|
+
export let broadcastServerFetchSpy;
|
|
57
|
+
export let broadcastMessageHandlerSpy;
|
|
31
58
|
export let NAME;
|
|
32
59
|
export let HOMEDIR;
|
|
33
60
|
export let matterbridge;
|
|
@@ -71,6 +98,30 @@ export async function setupTest(name, debug = false) {
|
|
|
71
98
|
addBridgedEndpointSpy = jest.spyOn(Matterbridge.prototype, 'addBridgedEndpoint');
|
|
72
99
|
removeBridgedEndpointSpy = jest.spyOn(Matterbridge.prototype, 'removeBridgedEndpoint');
|
|
73
100
|
removeAllBridgedEndpointsSpy = jest.spyOn(Matterbridge.prototype, 'removeAllBridgedEndpoints');
|
|
101
|
+
addVirtualEndpointSpy = jest.spyOn(Matterbridge.prototype, 'addVirtualEndpoint');
|
|
102
|
+
installPluginSpy = jest.spyOn(PluginManager.prototype, 'install');
|
|
103
|
+
uninstallPluginSpy = jest.spyOn(PluginManager.prototype, 'uninstall');
|
|
104
|
+
addPluginSpy = jest.spyOn(PluginManager.prototype, 'add');
|
|
105
|
+
loadPluginSpy = jest.spyOn(PluginManager.prototype, 'load');
|
|
106
|
+
startPluginSpy = jest.spyOn(PluginManager.prototype, 'start');
|
|
107
|
+
configurePluginSpy = jest.spyOn(PluginManager.prototype, 'configure');
|
|
108
|
+
shutdownPluginSpy = jest.spyOn(PluginManager.prototype, 'shutdown');
|
|
109
|
+
removePluginSpy = jest.spyOn(PluginManager.prototype, 'remove');
|
|
110
|
+
enablePluginSpy = jest.spyOn(PluginManager.prototype, 'enable');
|
|
111
|
+
disablePluginSpy = jest.spyOn(PluginManager.prototype, 'disable');
|
|
112
|
+
wssSendSnackbarMessageSpy = jest.spyOn(Frontend.prototype, 'wssSendSnackbarMessage');
|
|
113
|
+
wssSendCloseSnackbarMessageSpy = jest.spyOn(Frontend.prototype, 'wssSendCloseSnackbarMessage');
|
|
114
|
+
wssSendUpdateRequiredSpy = jest.spyOn(Frontend.prototype, 'wssSendUpdateRequired');
|
|
115
|
+
wssSendRefreshRequiredSpy = jest.spyOn(Frontend.prototype, 'wssSendRefreshRequired');
|
|
116
|
+
wssSendRestartRequiredSpy = jest.spyOn(Frontend.prototype, 'wssSendRestartRequired');
|
|
117
|
+
wssSendRestartNotRequiredSpy = jest.spyOn(Frontend.prototype, 'wssSendRestartNotRequired');
|
|
118
|
+
broadcastServerIsWorkerRequestSpy = jest.spyOn(BroadcastServer.prototype, 'isWorkerRequest');
|
|
119
|
+
broadcastServerIsWorkerResponseSpy = jest.spyOn(BroadcastServer.prototype, 'isWorkerResponse');
|
|
120
|
+
broadcastServerBroadcastSpy = jest.spyOn(BroadcastServer.prototype, 'broadcast');
|
|
121
|
+
broadcastServerRequestSpy = jest.spyOn(BroadcastServer.prototype, 'request');
|
|
122
|
+
broadcastServerRespondSpy = jest.spyOn(BroadcastServer.prototype, 'respond');
|
|
123
|
+
broadcastServerFetchSpy = jest.spyOn(BroadcastServer.prototype, 'fetch');
|
|
124
|
+
broadcastMessageHandlerSpy = jest.spyOn(BroadcastServer.prototype, 'broadcastMessageHandler');
|
|
74
125
|
}
|
|
75
126
|
export async function setDebug(debug) {
|
|
76
127
|
const { jest } = await import('@jest/globals');
|
package/dist/matterNode.js
CHANGED
|
@@ -92,23 +92,23 @@ export class MatterNode extends EventEmitter {
|
|
|
92
92
|
this.log.debug(`MatterNode ${this.pluginName ? 'for plugin ' + this.pluginName : 'bridge'} loaded`);
|
|
93
93
|
}
|
|
94
94
|
async msgHandler(msg) {
|
|
95
|
-
if (this.server.isWorkerRequest(msg
|
|
95
|
+
if (this.server.isWorkerRequest(msg) && (msg.dst === 'all' || msg.dst === 'matter')) {
|
|
96
96
|
if (this.verbose)
|
|
97
97
|
this.log.debug(`Received broadcast request ${CYAN}${msg.type}${db} from ${CYAN}${msg.src}${db}: ${debugStringify(msg)}${db}`);
|
|
98
98
|
switch (msg.type) {
|
|
99
99
|
case 'get_log_level':
|
|
100
|
-
this.server.respond({ ...msg,
|
|
100
|
+
this.server.respond({ ...msg, result: { logLevel: this.log.logLevel } });
|
|
101
101
|
break;
|
|
102
102
|
case 'set_log_level':
|
|
103
103
|
this.log.logLevel = msg.params.logLevel;
|
|
104
|
-
this.server.respond({ ...msg,
|
|
104
|
+
this.server.respond({ ...msg, result: { logLevel: this.log.logLevel } });
|
|
105
105
|
break;
|
|
106
106
|
default:
|
|
107
107
|
if (this.verbose)
|
|
108
108
|
this.log.debug(`Unknown broadcast request ${CYAN}${msg.type}${db} from ${CYAN}${msg.src}${db}`);
|
|
109
109
|
}
|
|
110
110
|
}
|
|
111
|
-
if (this.server.isWorkerResponse(msg
|
|
111
|
+
if (this.server.isWorkerResponse(msg) && (msg.dst === 'all' || msg.dst === 'matter')) {
|
|
112
112
|
if (this.verbose)
|
|
113
113
|
this.log.debug(`Received broadcast response ${CYAN}${msg.type}${db} from ${CYAN}${msg.src}${db}: ${debugStringify(msg)}${db}`);
|
|
114
114
|
switch (msg.type) {
|
|
@@ -669,6 +669,7 @@ export class MatterNode extends EventEmitter {
|
|
|
669
669
|
await device.construction.ready;
|
|
670
670
|
await this.subscribeAttributeChanged(plugin, device);
|
|
671
671
|
this.log.info(`Added endpoint #${plugin.registeredDevices} ${plg}${pluginName}${nf}:${dev}${device.deviceName}${nf} (${zb}${device.name}${nf})`);
|
|
672
|
+
await this.yieldToNode(10);
|
|
672
673
|
return device;
|
|
673
674
|
}
|
|
674
675
|
async removeBridgedEndpoint(pluginName, device) {
|
|
@@ -697,6 +698,7 @@ export class MatterNode extends EventEmitter {
|
|
|
697
698
|
if (plugin.registeredDevices !== undefined)
|
|
698
699
|
plugin.registeredDevices--;
|
|
699
700
|
await this.server.fetch({ type: 'devices_remove', src: this.server.name, dst: 'devices', params: { device: toBaseDevice(device) } });
|
|
701
|
+
await this.yieldToNode(10);
|
|
700
702
|
return device;
|
|
701
703
|
}
|
|
702
704
|
async removeAllBridgedEndpoints(pluginName, delay = 0) {
|
|
@@ -704,7 +706,7 @@ export class MatterNode extends EventEmitter {
|
|
|
704
706
|
if (!plugin)
|
|
705
707
|
throw new Error(`Error removing all bridged endpoints for plugin ${plg}${pluginName}${er}: plugin not found`);
|
|
706
708
|
this.log.debug(`Removing all #${plugin.registeredDevices} bridged endpoints for plugin ${plg}${pluginName}${db}${delay > 0 ? ` with delay ${delay} ms` : ''}...`);
|
|
707
|
-
const devices = (await this.server.fetch({ type: 'devices_basearray', src: this.server.name, dst: 'devices', params: { pluginName } })).
|
|
709
|
+
const devices = (await this.server.fetch({ type: 'devices_basearray', src: this.server.name, dst: 'devices', params: { pluginName } })).result.devices;
|
|
708
710
|
for (const device of devices) {
|
|
709
711
|
const endpoint = (this.aggregatorNode?.parts.get(device.id || '') || this.serverNode?.parts.get(device.id || ''));
|
|
710
712
|
if (!endpoint)
|
|
@@ -715,6 +717,7 @@ export class MatterNode extends EventEmitter {
|
|
|
715
717
|
if (plugin.registeredDevices !== undefined)
|
|
716
718
|
plugin.registeredDevices--;
|
|
717
719
|
await this.server.fetch({ type: 'devices_remove', src: this.server.name, dst: 'devices', params: { device: toBaseDevice(device) } });
|
|
720
|
+
await this.yieldToNode(10);
|
|
718
721
|
if (delay > 0)
|
|
719
722
|
await wait(delay);
|
|
720
723
|
}
|
|
@@ -742,6 +745,7 @@ export class MatterNode extends EventEmitter {
|
|
|
742
745
|
}
|
|
743
746
|
await addVirtualDevice(this.aggregatorNode, name.slice(0, 32), type, callback);
|
|
744
747
|
this.log.debug(`Created virtual device ${plg}${pluginName}${db}:${dev}${name}${db}`);
|
|
748
|
+
await this.yieldToNode(10);
|
|
745
749
|
return true;
|
|
746
750
|
}
|
|
747
751
|
async subscribeAttributeChanged(plugin, device) {
|