matterbridge 3.4.1-dev-20251128-441f8db → 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 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
- - check_updates;
18
- - npm_install;
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
 
@@ -25,24 +25,64 @@ export class BroadcastServer extends EventEmitter {
25
25
  this.broadcastChannel.close();
26
26
  }
27
27
  getUniqueId() {
28
- return Math.floor(Math.random() * (999999 - 100000 + 1)) + 100000;
28
+ return Math.floor(Math.random() * 900000000) + 100000000;
29
29
  }
30
- isWorkerRequest(msg, type) {
31
- return typeof msg === 'object' && msg !== null && 'id' in msg && 'timestamp' in msg && 'type' in msg && msg.type === type && !('response' in msg) && 'src' in msg && 'dst' in msg;
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;
32
54
  }
33
- isWorkerResponse(msg, type) {
34
- return typeof msg === 'object' && msg !== null && 'id' in msg && 'timestamp' in msg && 'type' in msg && msg.type === type && 'response' in msg && 'src' in msg && 'dst' in msg;
55
+ isWorkerResponseOfType(value, type) {
56
+ return this.isWorkerResponse(value) && value.type === type;
35
57
  }
36
58
  broadcastMessageHandler(event) {
37
- const data = event.data;
38
- if (this.verbose && (data.dst === this.name || data.dst === 'all'))
39
- this.log.debug(`Server ${CYAN}${this.name}${db} received broadcast message: ${debugStringify(data)}`);
40
- this.emit('broadcast_message', data);
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
+ }
41
69
  }
42
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;
43
78
  if (this.verbose)
44
79
  this.log.debug(`Broadcasting message: ${debugStringify(message)}`);
45
- this.broadcastChannel.postMessage(message);
80
+ try {
81
+ this.broadcastChannel.postMessage(message);
82
+ }
83
+ catch (error) {
84
+ logError(this.log, `Failed to broadcast message ${debugStringify(message)}${er}`, error);
85
+ }
46
86
  }
47
87
  request(message) {
48
88
  if (message.id === undefined) {
@@ -51,8 +91,9 @@ export class BroadcastServer extends EventEmitter {
51
91
  if (message.timestamp === undefined) {
52
92
  message.timestamp = Date.now();
53
93
  }
54
- if (!this.isWorkerRequest(message, message.type)) {
55
- this.log.error(`Invalid request message format for broadcast: ${debugStringify(message)}`);
94
+ message.src = this.name;
95
+ if (!this.isWorkerRequest(message)) {
96
+ this.log.error(`Invalid request message format: ${debugStringify(message)}`);
56
97
  return;
57
98
  }
58
99
  if (this.verbose)
@@ -65,11 +106,18 @@ export class BroadcastServer extends EventEmitter {
65
106
  }
66
107
  }
67
108
  respond(message) {
109
+ if (typeof message.timestamp === 'number') {
110
+ message.elapsed = Date.now() - message.timestamp;
111
+ }
68
112
  if (message.timestamp === undefined) {
69
113
  message.timestamp = Date.now();
70
114
  }
71
- if (!this.isWorkerResponse(message, message.type)) {
72
- this.log.error(`Invalid response message format for broadcast: ${debugStringify(message)}`);
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)}`);
73
121
  return;
74
122
  }
75
123
  if (this.verbose)
@@ -92,16 +140,21 @@ export class BroadcastServer extends EventEmitter {
92
140
  this.log.debug(`Fetching message: ${debugStringify(message)}`);
93
141
  return new Promise((resolve, reject) => {
94
142
  const responseHandler = (msg) => {
95
- if (this.isWorkerResponse(msg, message.type) && msg.id === message.id) {
143
+ if (this.isWorkerResponseOfType(msg, message.type) && msg.id === message.id) {
96
144
  clearTimeout(timeoutId);
97
145
  this.off('broadcast_message', responseHandler);
98
146
  if (this.verbose)
99
147
  this.log.debug(`Fetch response: ${debugStringify(msg)}`);
100
- resolve(msg);
101
- }
102
- else if (this.isWorkerResponse(msg, message.type) && msg.id !== message.id) {
103
- if (this.verbose)
104
- this.log.debug(`Fetch received unrelated response: ${debugStringify(msg)}`);
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;
105
158
  }
106
159
  };
107
160
  this.on('broadcast_message', responseHandler);
@@ -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, msg.type) && (msg.dst === 'all' || msg.dst === 'devices')) {
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, response: { success: true, logLevel: this.log.logLevel } });
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, response: { success: true, logLevel: this.log.logLevel } });
56
+ this.server.respond({ ...msg, result: { logLevel: this.log.logLevel } });
57
57
  break;
58
58
  case 'devices_length':
59
- this.server.respond({ ...msg, response: { length: this.length } });
59
+ this.server.respond({ ...msg, result: { length: this.length } });
60
60
  break;
61
61
  case 'devices_size':
62
- this.server.respond({ ...msg, response: { size: this.size } });
62
+ this.server.respond({ ...msg, result: { size: this.size } });
63
63
  break;
64
64
  case 'devices_has':
65
- this.server.respond({ ...msg, response: { has: this.has(msg.params.uniqueId) } });
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, response: { device: endpoint ? toBaseDevice(endpoint) : undefined } });
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, response: { device: this.set(toBaseDevice(msg.params.device)) } });
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, response: { success: this.remove(toBaseDevice(msg.params.device)) } });
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, response: { success: true } });
81
+ this.server.respond({ ...msg, result: { success: true } });
82
82
  break;
83
83
  case 'devices_basearray':
84
- this.server.respond({ ...msg, response: { devices: this.baseArray(msg.params.pluginName) } });
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,94 +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, msg.type) && (msg.dst === 'all' || msg.dst === 'frontend')) {
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, response: { success: true, logLevel: this.log.logLevel } });
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, response: { success: true, logLevel: this.log.logLevel } });
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, response: { success: true } });
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, response: { success: true } });
66
+ this.server.respond({ ...msg, result: { success: true } });
67
67
  break;
68
68
  case 'frontend_refreshrequired':
69
69
  this.wssSendRefreshRequired(msg.params.changed, msg.params.matter ? { matter: msg.params.matter } : undefined);
70
- this.server.respond({ ...msg, response: { success: true } });
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, response: { success: true } });
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, response: { success: true } });
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, response: { success: true } });
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, response: { success: true } });
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, response: { success: true } });
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, response: { success: true } });
94
+ this.server.respond({ ...msg, result: { success: true } });
95
95
  break;
96
96
  case 'frontend_broadcast_message':
97
97
  this.wssBroadcastMessage(msg.params.msg);
98
- this.server.respond({ ...msg, response: { success: true } });
98
+ this.server.respond({ ...msg, result: { success: true } });
99
99
  break;
100
100
  default:
101
101
  if (this.verbose)
102
102
  this.log.debug(`Unknown broadcast request ${CYAN}${msg.type}${db} from ${CYAN}${msg.src}${db}`);
103
103
  }
104
104
  }
105
- if (this.server.isWorkerResponse(msg, msg.type)) {
105
+ if (this.server.isWorkerResponse(msg) && msg.result && (msg.dst === 'all' || msg.dst === 'frontend')) {
106
106
  if (this.verbose)
107
107
  this.log.debug(`Received broadcast response ${CYAN}${msg.type}${db} from ${CYAN}${msg.src}${db}: ${debugStringify(msg)}${db}`);
108
108
  switch (msg.type) {
109
- case 'get_log_level':
110
- case 'set_log_level':
111
- break;
112
109
  case 'plugins_install':
113
- this.wssSendCloseSnackbarMessage(`Installing package ${msg.response.packageName}...`);
114
- if (msg.response.success) {
110
+ this.wssSendCloseSnackbarMessage(`Installing package ${msg.result.packageName}...`);
111
+ if (msg.result.success) {
115
112
  this.wssSendRestartRequired(true, true);
116
113
  this.wssSendRefreshRequired('plugins');
117
- this.wssSendSnackbarMessage(`Installed package ${msg.response.packageName}`, 5, 'success');
114
+ this.wssSendSnackbarMessage(`Installed package ${msg.result.packageName}`, 5, 'success');
118
115
  }
119
116
  else {
120
- this.wssSendSnackbarMessage(`Package ${msg.response.packageName} not installed`, 10, 'error');
117
+ this.wssSendSnackbarMessage(`Package ${msg.result.packageName} not installed`, 10, 'error');
121
118
  }
122
119
  break;
123
120
  case 'plugins_uninstall':
124
- this.wssSendCloseSnackbarMessage(`Uninstalling package ${msg.response.packageName}...`);
125
- if (msg.response.success) {
121
+ this.wssSendCloseSnackbarMessage(`Uninstalling package ${msg.result.packageName}...`);
122
+ if (msg.result.success) {
126
123
  this.wssSendRestartRequired(true, true);
127
124
  this.wssSendRefreshRequired('plugins');
128
- this.wssSendSnackbarMessage(`Uninstalled package ${msg.response.packageName}`, 5, 'success');
125
+ this.wssSendSnackbarMessage(`Uninstalled package ${msg.result.packageName}`, 5, 'success');
129
126
  }
130
127
  else {
131
- this.wssSendSnackbarMessage(`Package ${msg.response.packageName} not uninstalled`, 10, 'error');
128
+ this.wssSendSnackbarMessage(`Package ${msg.result.packageName} not uninstalled`, 10, 'error');
132
129
  }
133
130
  break;
134
- default:
135
- if (this.verbose)
136
- this.log.debug(`Unknown broadcast response ${CYAN}${msg.type}${db} from ${CYAN}${msg.src}${db}`);
137
131
  }
138
132
  }
139
133
  }
@@ -50,6 +50,7 @@ export let wssSendRestartRequiredSpy;
50
50
  export let wssSendRestartNotRequiredSpy;
51
51
  export let broadcastServerIsWorkerRequestSpy;
52
52
  export let broadcastServerIsWorkerResponseSpy;
53
+ export let broadcastServerBroadcastSpy;
53
54
  export let broadcastServerRequestSpy;
54
55
  export let broadcastServerRespondSpy;
55
56
  export let broadcastServerFetchSpy;
@@ -116,6 +117,7 @@ export async function setupTest(name, debug = false) {
116
117
  wssSendRestartNotRequiredSpy = jest.spyOn(Frontend.prototype, 'wssSendRestartNotRequired');
117
118
  broadcastServerIsWorkerRequestSpy = jest.spyOn(BroadcastServer.prototype, 'isWorkerRequest');
118
119
  broadcastServerIsWorkerResponseSpy = jest.spyOn(BroadcastServer.prototype, 'isWorkerResponse');
120
+ broadcastServerBroadcastSpy = jest.spyOn(BroadcastServer.prototype, 'broadcast');
119
121
  broadcastServerRequestSpy = jest.spyOn(BroadcastServer.prototype, 'request');
120
122
  broadcastServerRespondSpy = jest.spyOn(BroadcastServer.prototype, 'respond');
121
123
  broadcastServerFetchSpy = jest.spyOn(BroadcastServer.prototype, 'fetch');
@@ -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, msg.type) && (msg.dst === 'all' || msg.dst === 'matter')) {
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, response: { success: true, logLevel: this.log.logLevel } });
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, response: { success: true, logLevel: this.log.logLevel } });
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, msg.type) && (msg.dst === 'all' || msg.dst === 'matter')) {
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 } })).response.devices;
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) {
@@ -133,52 +133,45 @@ export class Matterbridge extends EventEmitter {
133
133
  this.server.close();
134
134
  }
135
135
  async msgHandler(msg) {
136
- if (this.server.isWorkerRequest(msg, msg.type) && (msg.dst === 'all' || msg.dst === 'matterbridge')) {
136
+ if (this.server.isWorkerRequest(msg) && (msg.dst === 'all' || msg.dst === 'matterbridge')) {
137
137
  if (this.verbose)
138
138
  this.log.debug(`Received broadcast request ${CYAN}${msg.type}${db} from ${CYAN}${msg.src}${db}: ${debugStringify(msg)}${db}`);
139
139
  switch (msg.type) {
140
140
  case 'get_log_level':
141
- this.server.respond({ ...msg, response: { success: true, logLevel: this.log.logLevel } });
141
+ this.server.respond({ ...msg, result: { logLevel: this.log.logLevel } });
142
142
  break;
143
143
  case 'set_log_level':
144
144
  this.log.logLevel = msg.params.logLevel;
145
- this.server.respond({ ...msg, response: { success: true, logLevel: this.log.logLevel } });
145
+ this.server.respond({ ...msg, result: { logLevel: this.log.logLevel } });
146
146
  break;
147
147
  case 'matterbridge_latest_version':
148
148
  this.matterbridgeLatestVersion = msg.params.version;
149
149
  await this.nodeContext?.set('matterbridgeLatestVersion', msg.params.version);
150
- this.server.respond({ ...msg, response: { success: true } });
150
+ this.server.respond({ ...msg, result: { success: true } });
151
151
  break;
152
152
  case 'matterbridge_dev_version':
153
153
  this.matterbridgeDevVersion = msg.params.version;
154
154
  await this.nodeContext?.set('matterbridgeDevVersion', msg.params.version);
155
- this.server.respond({ ...msg, response: { success: true } });
155
+ this.server.respond({ ...msg, result: { success: true } });
156
+ break;
157
+ case 'matterbridge_global_prefix':
158
+ this.globalModulesDirectory = msg.params.prefix;
159
+ await this.nodeContext?.set('globalModulesDirectory', msg.params.prefix);
160
+ this.server.respond({ ...msg, result: { success: true } });
156
161
  break;
157
162
  case 'matterbridge_sys_update':
158
163
  this.shellySysUpdate = true;
159
- this.server.respond({ ...msg, response: { success: true } });
164
+ this.server.respond({ ...msg, result: { success: true } });
160
165
  break;
161
166
  case 'matterbridge_main_update':
162
167
  this.shellyMainUpdate = true;
163
- this.server.respond({ ...msg, response: { success: true } });
168
+ this.server.respond({ ...msg, result: { success: true } });
164
169
  break;
165
170
  default:
166
171
  if (this.verbose)
167
172
  this.log.debug(`Unknown broadcast request ${CYAN}${msg.type}${db} from ${CYAN}${msg.src}${db}`);
168
173
  }
169
174
  }
170
- if (this.server.isWorkerResponse(msg, msg.type)) {
171
- if (this.verbose)
172
- this.log.debug(`Received broadcast response ${CYAN}${msg.type}${db} from ${CYAN}${msg.src}${db}: ${debugStringify(msg)}${db}`);
173
- switch (msg.type) {
174
- case 'get_log_level':
175
- case 'set_log_level':
176
- break;
177
- default:
178
- if (this.verbose)
179
- this.log.debug(`Unknown broadcast response ${CYAN}${msg.type}${db} from ${CYAN}${msg.src}${db}`);
180
- }
181
- }
182
175
  }
183
176
  static async loadInstance(initialize = false) {
184
177
  if (!Matterbridge.instance) {
@@ -832,16 +825,9 @@ export class Matterbridge extends EventEmitter {
832
825
  }
833
826
  }
834
827
  else {
835
- this.log.debug(`Checking global node_modules directory: ${this.globalModulesDirectory}`);
836
- try {
837
- const { getGlobalNodeModules } = await import('./utils/network.js');
838
- this.globalModulesDirectory = await getGlobalNodeModules();
839
- this.log.debug(`Global node_modules Directory: ${this.globalModulesDirectory}`);
840
- await this.nodeContext?.set('globalModulesDirectory', this.globalModulesDirectory);
841
- }
842
- catch (error) {
843
- this.log.error(`Error checking global node_modules directory: ${error}`);
844
- }
828
+ this.log.debug(`Global node_modules Directory: ${this.globalModulesDirectory}`);
829
+ const { createESMWorker } = await import('./workers.js');
830
+ createESMWorker('NpmGlobalPrefix', './dist/workerGlobalPrefix.js');
845
831
  }
846
832
  this.log.debug(`Reading matterbridge package.json...`);
847
833
  const packageJson = JSON.parse(await fs.promises.readFile(path.join(this.rootDirectory, 'package.json'), 'utf-8'));
@@ -124,7 +124,7 @@ export class MatterbridgePlatform {
124
124
  this.#server.request({ type: 'plugins_saveconfigfromjson', src: 'platform', dst: 'plugins', params: { name: this.name, config } });
125
125
  }
126
126
  async getSchema() {
127
- return (await this.#server.fetch({ type: 'plugins_getschema', src: 'platform', dst: 'plugins', params: { name: this.name } })).response.schema;
127
+ return (await this.#server.fetch({ type: 'plugins_getschema', src: 'platform', dst: 'plugins', params: { name: this.name } })).result.schema;
128
128
  }
129
129
  setSchema(schema) {
130
130
  this.#server.request({ type: 'plugins_setschema', src: 'platform', dst: 'plugins', params: { name: this.name, schema } });
@@ -24,61 +24,61 @@ export class PluginManager extends EventEmitter {
24
24
  this.server.close();
25
25
  }
26
26
  async msgHandler(msg) {
27
- if (this.server.isWorkerRequest(msg, msg.type) && (msg.dst === 'all' || msg.dst === 'plugins')) {
27
+ if (this.server.isWorkerRequest(msg) && (msg.dst === 'all' || msg.dst === 'plugins')) {
28
28
  if (this.verbose)
29
29
  this.log.debug(`Received request message ${CYAN}${msg.type}${db} from ${CYAN}${msg.src}${db}: ${debugStringify(msg)}${db}`);
30
30
  switch (msg.type) {
31
31
  case 'get_log_level':
32
- this.server.respond({ ...msg, response: { success: true, logLevel: this.log.logLevel } });
32
+ this.server.respond({ ...msg, result: { logLevel: this.log.logLevel } });
33
33
  break;
34
34
  case 'set_log_level':
35
35
  this.log.logLevel = msg.params.logLevel;
36
- this.server.respond({ ...msg, response: { success: true, logLevel: this.log.logLevel } });
36
+ this.server.respond({ ...msg, result: { logLevel: this.log.logLevel } });
37
37
  break;
38
38
  case 'plugins_length':
39
- this.server.respond({ ...msg, response: { length: this.length } });
39
+ this.server.respond({ ...msg, result: { length: this.length } });
40
40
  break;
41
41
  case 'plugins_size':
42
- this.server.respond({ ...msg, response: { size: this.size } });
42
+ this.server.respond({ ...msg, result: { size: this.size } });
43
43
  break;
44
44
  case 'plugins_has':
45
- this.server.respond({ ...msg, response: { has: this.has(msg.params.name) } });
45
+ this.server.respond({ ...msg, result: { has: this.has(msg.params.name) } });
46
46
  break;
47
47
  case 'plugins_get':
48
48
  {
49
49
  const plugin = this.get(msg.params.name);
50
50
  if (plugin) {
51
- this.server.respond({ ...msg, response: { plugin: this.toApiPlugin(plugin) } });
51
+ this.server.respond({ ...msg, result: { plugin: this.toApiPlugin(plugin) } });
52
52
  }
53
53
  else {
54
54
  this.log.debug(`***Plugin ${plg}${msg.params.name}${db} not found in plugins_get`);
55
- this.server.respond({ ...msg, response: { plugin: undefined } });
55
+ this.server.respond({ ...msg, result: { plugin: undefined } });
56
56
  }
57
57
  }
58
58
  break;
59
59
  case 'plugins_set':
60
- this.server.respond({ ...msg, response: { plugin: this.set(msg.params.plugin) } });
60
+ this.server.respond({ ...msg, result: { plugin: this.set(msg.params.plugin) } });
61
61
  break;
62
62
  case 'plugins_storagepluginarray':
63
- this.server.respond({ ...msg, response: { plugins: this.storagePluginArray() } });
63
+ this.server.respond({ ...msg, result: { plugins: this.storagePluginArray() } });
64
64
  break;
65
65
  case 'plugins_apipluginarray':
66
- this.server.respond({ ...msg, response: { plugins: this.apiPluginArray() } });
66
+ this.server.respond({ ...msg, result: { plugins: this.apiPluginArray() } });
67
67
  break;
68
68
  case 'plugins_install':
69
- this.server.respond({ ...msg, response: { packageName: msg.params.packageName, success: await this.install(msg.params.packageName) } });
69
+ this.server.respond({ ...msg, result: { packageName: msg.params.packageName, success: await this.install(msg.params.packageName) } });
70
70
  break;
71
71
  case 'plugins_uninstall':
72
- this.server.respond({ ...msg, response: { packageName: msg.params.packageName, success: await this.uninstall(msg.params.packageName) } });
72
+ this.server.respond({ ...msg, result: { packageName: msg.params.packageName, success: await this.uninstall(msg.params.packageName) } });
73
73
  break;
74
74
  case 'plugins_add':
75
75
  {
76
76
  const plugin = await this.add(msg.params.nameOrPath);
77
77
  if (plugin) {
78
- this.server.respond({ ...msg, response: { plugin: this.toApiPlugin(plugin) } });
78
+ this.server.respond({ ...msg, result: { plugin: this.toApiPlugin(plugin) } });
79
79
  }
80
80
  else {
81
- this.server.respond({ ...msg, response: { plugin } });
81
+ this.server.respond({ ...msg, result: { plugin } });
82
82
  }
83
83
  }
84
84
  break;
@@ -86,10 +86,10 @@ export class PluginManager extends EventEmitter {
86
86
  {
87
87
  const plugin = await this.remove(msg.params.nameOrPath);
88
88
  if (plugin) {
89
- this.server.respond({ ...msg, response: { plugin: this.toApiPlugin(plugin) } });
89
+ this.server.respond({ ...msg, result: { plugin: this.toApiPlugin(plugin) } });
90
90
  }
91
91
  else {
92
- this.server.respond({ ...msg, response: { plugin } });
92
+ this.server.respond({ ...msg, result: { plugin } });
93
93
  }
94
94
  }
95
95
  break;
@@ -97,10 +97,10 @@ export class PluginManager extends EventEmitter {
97
97
  {
98
98
  const plugin = await this.enable(msg.params.nameOrPath);
99
99
  if (plugin) {
100
- this.server.respond({ ...msg, response: { plugin: this.toApiPlugin(plugin) } });
100
+ this.server.respond({ ...msg, result: { plugin: this.toApiPlugin(plugin) } });
101
101
  }
102
102
  else {
103
- this.server.respond({ ...msg, response: { plugin } });
103
+ this.server.respond({ ...msg, result: { plugin } });
104
104
  }
105
105
  }
106
106
  break;
@@ -108,10 +108,10 @@ export class PluginManager extends EventEmitter {
108
108
  {
109
109
  const plugin = await this.disable(msg.params.nameOrPath);
110
110
  if (plugin) {
111
- this.server.respond({ ...msg, response: { plugin: this.toApiPlugin(plugin) } });
111
+ this.server.respond({ ...msg, result: { plugin: this.toApiPlugin(plugin) } });
112
112
  }
113
113
  else {
114
- this.server.respond({ ...msg, response: { plugin } });
114
+ this.server.respond({ ...msg, result: { plugin } });
115
115
  }
116
116
  }
117
117
  break;
@@ -119,10 +119,10 @@ export class PluginManager extends EventEmitter {
119
119
  {
120
120
  const platform = await this.load(msg.params.plugin);
121
121
  if (platform) {
122
- this.server.respond({ ...msg, params: {}, response: { platform: {} } });
122
+ this.server.respond({ ...msg, result: { platform: {} } });
123
123
  }
124
124
  else {
125
- this.server.respond({ ...msg, response: { platform } });
125
+ this.server.respond({ ...msg, result: { platform } });
126
126
  }
127
127
  }
128
128
  break;
@@ -130,10 +130,10 @@ export class PluginManager extends EventEmitter {
130
130
  {
131
131
  const plugin = await this.start(msg.params.plugin, msg.params.message, msg.params.configure);
132
132
  if (plugin) {
133
- this.server.respond({ ...msg, params: {}, response: { plugin: this.toApiPlugin(plugin) } });
133
+ this.server.respond({ ...msg, result: { plugin: this.toApiPlugin(plugin) } });
134
134
  }
135
135
  else {
136
- this.server.respond({ ...msg, response: { plugin } });
136
+ this.server.respond({ ...msg, result: { plugin } });
137
137
  }
138
138
  }
139
139
  break;
@@ -141,10 +141,10 @@ export class PluginManager extends EventEmitter {
141
141
  {
142
142
  const plugin = await this.configure(msg.params.plugin);
143
143
  if (plugin) {
144
- this.server.respond({ ...msg, params: {}, response: { plugin: this.toApiPlugin(plugin) } });
144
+ this.server.respond({ ...msg, result: { plugin: this.toApiPlugin(plugin) } });
145
145
  }
146
146
  else {
147
- this.server.respond({ ...msg, response: { plugin } });
147
+ this.server.respond({ ...msg, result: { plugin } });
148
148
  }
149
149
  }
150
150
  break;
@@ -152,10 +152,10 @@ export class PluginManager extends EventEmitter {
152
152
  {
153
153
  const plugin = await this.shutdown(msg.params.plugin, msg.params.reason, msg.params.removeAllDevices, msg.params.force);
154
154
  if (plugin) {
155
- this.server.respond({ ...msg, params: {}, response: { plugin: this.toApiPlugin(plugin) } });
155
+ this.server.respond({ ...msg, result: { plugin: this.toApiPlugin(plugin) } });
156
156
  }
157
157
  else {
158
- this.server.respond({ ...msg, response: { plugin } });
158
+ this.server.respond({ ...msg, result: { plugin } });
159
159
  }
160
160
  }
161
161
  break;
@@ -163,10 +163,10 @@ export class PluginManager extends EventEmitter {
163
163
  {
164
164
  const plugin = this.get(msg.params.name);
165
165
  if (plugin) {
166
- this.server.respond({ ...msg, response: { schema: plugin.schemaJson } });
166
+ this.server.respond({ ...msg, result: { schema: plugin.schemaJson } });
167
167
  }
168
168
  else {
169
- this.server.respond({ ...msg, response: { schema: undefined } });
169
+ this.server.respond({ ...msg, result: { schema: undefined } });
170
170
  }
171
171
  }
172
172
  break;
@@ -175,10 +175,10 @@ export class PluginManager extends EventEmitter {
175
175
  const plugin = this.get(msg.params.name);
176
176
  if (plugin) {
177
177
  plugin.schemaJson = msg.params.schema;
178
- this.server.respond({ ...msg, response: { success: true } });
178
+ this.server.respond({ ...msg, result: { success: true } });
179
179
  }
180
180
  else {
181
- this.server.respond({ ...msg, response: { success: false } });
181
+ this.server.respond({ ...msg, error: `Plugin ${msg.params.name} not found` });
182
182
  }
183
183
  }
184
184
  break;
@@ -187,10 +187,10 @@ export class PluginManager extends EventEmitter {
187
187
  const plugin = this.get(msg.params.name);
188
188
  if (plugin) {
189
189
  this.saveConfigFromJson(plugin, msg.params.config, msg.params.restartRequired);
190
- this.server.respond({ ...msg, response: { success: true } });
190
+ this.server.respond({ ...msg, result: { success: true } });
191
191
  }
192
192
  else {
193
- this.server.respond({ ...msg, response: { success: false } });
193
+ this.server.respond({ ...msg, error: `Plugin ${msg.params.name} not found` });
194
194
  }
195
195
  }
196
196
  break;
@@ -199,10 +199,10 @@ export class PluginManager extends EventEmitter {
199
199
  const plugin = this.get(msg.params.plugin.name);
200
200
  if (plugin) {
201
201
  plugin.latestVersion = msg.params.version;
202
- this.server.respond({ ...msg, response: { success: true } });
202
+ this.server.respond({ ...msg, result: { success: true } });
203
203
  }
204
204
  else {
205
- this.server.respond({ ...msg, response: { success: false } });
205
+ this.server.respond({ ...msg, error: `Plugin ${msg.params.plugin.name} not found` });
206
206
  }
207
207
  }
208
208
  break;
@@ -211,10 +211,10 @@ export class PluginManager extends EventEmitter {
211
211
  const plugin = this.get(msg.params.plugin.name);
212
212
  if (plugin) {
213
213
  plugin.devVersion = msg.params.version;
214
- this.server.respond({ ...msg, response: { success: true } });
214
+ this.server.respond({ ...msg, result: { success: true } });
215
215
  }
216
216
  else {
217
- this.server.respond({ ...msg, response: { success: false } });
217
+ this.server.respond({ ...msg, error: `Plugin ${msg.params.plugin.name} not found` });
218
218
  }
219
219
  }
220
220
  break;
package/dist/shelly.js CHANGED
@@ -12,7 +12,7 @@ export async function getShellySysUpdate(matterbridge, log, server) {
12
12
  const updates = (await getShelly('/api/updates/sys/check'));
13
13
  if (updates.length === 0)
14
14
  return;
15
- server.request({ type: 'matterbridge_sys_update', src: server.name, dst: 'matterbridge' });
15
+ server.request({ type: 'matterbridge_sys_update', src: server.name, dst: 'matterbridge', params: { available: true } });
16
16
  server.request({ type: 'frontend_broadcast_message', src: server.name, dst: 'frontend', params: { msg: { id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'shelly_sys_update', success: true, response: { available: true } } } });
17
17
  for (const { name } of updates) {
18
18
  if (!name)
@@ -43,7 +43,7 @@ export async function getShellyMainUpdate(matterbridge, log, server) {
43
43
  const updates = (await getShelly('/api/updates/main/check'));
44
44
  if (updates.length === 0)
45
45
  return;
46
- server.request({ type: 'matterbridge_main_update', src: server.name, dst: 'matterbridge' });
46
+ server.request({ type: 'matterbridge_main_update', src: server.name, dst: 'matterbridge', params: { available: true } });
47
47
  server.request({ type: 'frontend_broadcast_message', src: server.name, dst: 'frontend', params: { msg: { id: 0, src: 'Matterbridge', dst: 'Frontend', method: 'shelly_main_update', success: true, response: { available: true } } } });
48
48
  for (const { name } of updates) {
49
49
  if (!name)
package/dist/update.js CHANGED
@@ -12,7 +12,7 @@ export async function checkUpdates(matterbridge) {
12
12
  const pluginsVersionPromises = [];
13
13
  const pluginsDevVersionPromises = [];
14
14
  const shellyUpdatesPromises = [];
15
- const plugins = (await server.fetch({ type: 'plugins_apipluginarray', src: server.name, dst: 'plugins', params: {} })).response.plugins;
15
+ const plugins = (await server.fetch({ type: 'plugins_apipluginarray', src: server.name, dst: 'plugins' })).result.plugins;
16
16
  for (const plugin of plugins) {
17
17
  pluginsVersionPromises.push(getPluginLatestVersion(log, server, plugin));
18
18
  pluginsDevVersionPromises.push(getPluginDevVersion(log, server, plugin));
@@ -6,5 +6,7 @@ export function logError(log, message, error) {
6
6
  export function inspectError(log, message, error) {
7
7
  const errorMessage = error instanceof Error ? `${error.message} \n` : '';
8
8
  const inspectedError = inspect(error, { depth: 10, colors: true, showHidden: false });
9
- log.error(`${message}: ${errorMessage}${RESET}${inspectedError}`);
9
+ const fullMessage = `${message}: ${errorMessage}${RESET}${inspectedError}`;
10
+ log.error(fullMessage);
11
+ return fullMessage;
10
12
  }
@@ -0,0 +1,39 @@
1
+ import { threadId, isMainThread, parentPort, workerData } from 'node:worker_threads';
2
+ import { AnsiLogger } from 'node-ansi-logger';
3
+ import { getGlobalNodeModules } from './utils/network.js';
4
+ import { BroadcastServer } from './broadcastServer.js';
5
+ import { inspectError } from './utils/error.js';
6
+ import { logWorkerInfo, parentLog, parentPost } from './workers.js';
7
+ import { hasParameter } from './utils/commandLine.js';
8
+ const debug = hasParameter('debug') || hasParameter('verbose');
9
+ const verbose = hasParameter('verbose');
10
+ if (!isMainThread && parentPort) {
11
+ parentPost({ type: 'init', threadId, threadName: workerData.threadName, success: true });
12
+ if (debug)
13
+ parentLog('MatterbridgePrefix', "info", `Worker ${workerData.threadName}:${threadId} initialized.`);
14
+ }
15
+ const log = new AnsiLogger({ logName: 'MatterbridgePrefix', logTimestampFormat: 4, logLevel: debug ? "debug" : "info" });
16
+ const server = new BroadcastServer('matterbridge', log);
17
+ if (verbose)
18
+ logWorkerInfo(log, verbose);
19
+ let prefix;
20
+ let success = false;
21
+ try {
22
+ prefix = await getGlobalNodeModules();
23
+ log.debug(`Global node_modules Directory: ${prefix}`);
24
+ server.request({ type: 'matterbridge_global_prefix', src: `matterbridge`, dst: 'matterbridge', params: { prefix } });
25
+ success = true;
26
+ if (!isMainThread && parentPort)
27
+ parentLog('MatterbridgePrefix', "debug", `Global node_modules Directory: ${prefix}`);
28
+ }
29
+ catch (error) {
30
+ const errorMessage = inspectError(log, `Failed to get global node modules`, error);
31
+ if (!isMainThread && parentPort)
32
+ parentLog('MatterbridgePrefix', "error", errorMessage);
33
+ }
34
+ server.close();
35
+ if (!isMainThread && parentPort) {
36
+ parentPost({ type: 'exit', threadId, threadName: workerData.threadName, success });
37
+ if (debug)
38
+ parentLog('MatterbridgePrefix', "info", `Worker ${workerData.threadName}:${threadId} exiting with success: ${success}.`);
39
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,35 @@
1
+ import { isMainThread, parentPort, threadId, Worker, workerData } from 'node:worker_threads';
2
+ import { pathToFileURL } from 'node:url';
3
+ import { resolve } from 'node:path';
4
+ import { inspect } from 'node:util';
5
+ export function parentPost(message) {
6
+ if (!parentPort)
7
+ throw new Error(`WorkerServer ${workerData.threadName}: parentPort is not available.`);
8
+ parentPort.postMessage(message);
9
+ }
10
+ export function parentLog(logName, logLevel, message) {
11
+ if (!parentPort)
12
+ throw new Error(`WorkerServer ${workerData.threadName}: parentPort is not available.`);
13
+ const logMessage = { type: 'log', threadId, threadName: workerData.threadName, logName, logLevel, message };
14
+ parentPort.postMessage(logMessage);
15
+ }
16
+ export function createESMWorker(name, relativePath, workerData, argv, env, execArgv) {
17
+ const fileURL = pathToFileURL(resolve(relativePath));
18
+ const options = {
19
+ workerData: { ...workerData, threadName: name },
20
+ type: 'module',
21
+ name,
22
+ argv: argv ?? process.argv.slice(2),
23
+ env: env ?? process.env,
24
+ execArgv,
25
+ };
26
+ return new Worker(fileURL, options);
27
+ }
28
+ export function logWorkerInfo(log, logEnv = false) {
29
+ log.debug(`${isMainThread ? 'Main thread' : 'Worker thread'}: ${workerData?.threadName}:${threadId} Pid: ${process.pid}`);
30
+ log.debug(`ParentPort: ${parentPort ? 'active' : 'not active'}`);
31
+ log.debug(`WorkerData: ${workerData ? inspect(workerData, true, 10, true) : 'none'}`);
32
+ const argv = process.argv.slice(2);
33
+ log.debug(`Argv: ${argv.length ? argv.join(' ') : 'none'}`);
34
+ log.debug(`Env: ${logEnv ? inspect(process.env, true, 10, true) : 'not logged'}`);
35
+ }
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "matterbridge",
3
- "version": "3.4.1-dev-20251128-441f8db",
3
+ "version": "3.4.1-dev-20251129-ff1e22f",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "matterbridge",
9
- "version": "3.4.1-dev-20251128-441f8db",
9
+ "version": "3.4.1-dev-20251129-ff1e22f",
10
10
  "license": "Apache-2.0",
11
11
  "dependencies": {
12
12
  "@matter/main": "0.15.6",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "matterbridge",
3
- "version": "3.4.1-dev-20251128-441f8db",
3
+ "version": "3.4.1-dev-20251129-ff1e22f",
4
4
  "description": "Matterbridge plugin manager for Matter",
5
5
  "author": "https://github.com/Luligu",
6
6
  "license": "Apache-2.0",