@matterbridge/thread 3.9.1-dev-20260614-18d1a2e → 3.9.1-dev-20260617-b1e1b99

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -6,10 +6,12 @@
6
6
  [![Docker Pulls](https://img.shields.io/docker/pulls/luligu/matterbridge?label=docker%20pulls)](https://hub.docker.com/r/luligu/matterbridge)
7
7
  ![Node.js CI](https://github.com/Luligu/matterbridge/actions/workflows/build.yml/badge.svg)
8
8
  ![CodeQL](https://github.com/Luligu/matterbridge/actions/workflows/codeql.yml/badge.svg)
9
- [![codecov](https://codecov.io/gh/Luligu/matterbridge/branch/main/graph/badge.svg)](https://codecov.io/gh/Luligu/matterbridge)
10
- [![styled with prettier](https://img.shields.io/badge/styled_with-Prettier-f8bc45.svg?logo=prettier)](https://prettier.io/)
11
- [![linted with eslint](https://img.shields.io/badge/linted_with-ES_Lint-4B32C3.svg?logo=eslint)](https://eslint.org/)
9
+ [![Codecov](https://codecov.io/gh/Luligu/matterbridge/branch/main/graph/badge.svg)](https://codecov.io/gh/Luligu/matterbridge)
10
+ [![tested with Vitest](https://img.shields.io/badge/tested_with-Vitest-6E9F18.svg?logo=vitest&logoColor=white)](https://vitest.dev)
11
+ [![styled with Oxc](https://img.shields.io/badge/styled_with-Oxc-9BE4E0.svg?logo=oxc&logoColor=white)](https://oxc.rs/docs/guide/usage/formatter.html)
12
+ [![linted with Oxc](https://img.shields.io/badge/linted_with-Oxc-9BE4E0.svg?logo=oxc&logoColor=white)](https://oxc.rs/docs/guide/usage/linter.html)
12
13
  [![TypeScript](https://img.shields.io/badge/TypeScript-3178C6?logo=typescript&logoColor=white)](https://www.typescriptlang.org/)
14
+ [![TypeScript Native](https://img.shields.io/badge/TypeScript_Native-3178C6?logo=typescript&logoColor=white)](https://github.com/microsoft/typescript-go)
13
15
  [![ESM](https://img.shields.io/badge/ESM-Node.js-339933?logo=node.js&logoColor=white)](https://nodejs.org/)
14
16
  [![matterbridge.io](https://img.shields.io/badge/matterbridge.io-online-brightgreen)](https://matterbridge.io)
15
17
 
@@ -1,10 +1,10 @@
1
- if (process.argv.includes('--loader'))
2
- console.log('\u001B[32m[' + new Date().toLocaleTimeString('en-US', { hour12: false, hour: '2-digit', minute: '2-digit', second: '2-digit', fractionalSecondDigits: 3 }) + '] BroadcastServer loaded.\u001B[40;0m');
3
1
  import { EventEmitter } from 'node:events';
4
2
  import { BroadcastChannel } from 'node:worker_threads';
5
3
  import { hasParameter } from '@matterbridge/utils/cli';
6
4
  import { logError } from '@matterbridge/utils/error';
5
+ import { logModuleLoaded } from '@matterbridge/utils/loader';
7
6
  import { CYAN, db, debugStringify, er } from 'node-ansi-logger';
7
+ logModuleLoaded('BroadcastServer');
8
8
  export class BroadcastServer extends EventEmitter {
9
9
  name;
10
10
  log;
@@ -88,20 +88,19 @@ export class BroadcastServer extends EventEmitter {
88
88
  this.log.error('Broadcast channel is closed');
89
89
  return;
90
90
  }
91
- if (message.id === undefined) {
92
- message.id = this.getUniqueId();
93
- }
94
- if (message.timestamp === undefined) {
95
- message.timestamp = Date.now();
96
- }
97
- message.src = this.name;
91
+ const broadcastMessage = {
92
+ ...message,
93
+ id: message.id ?? this.getUniqueId(),
94
+ timestamp: message.timestamp ?? Date.now(),
95
+ src: this.name,
96
+ };
98
97
  if (this.verbose)
99
- this.log.debug(`Broadcasting message: ${debugStringify(message)}`);
98
+ this.log.debug(`Broadcasting message: ${debugStringify(broadcastMessage)}`);
100
99
  try {
101
- this.broadcastChannel.postMessage(message);
100
+ this.broadcastChannel.postMessage(broadcastMessage);
102
101
  }
103
102
  catch (error) {
104
- logError(this.log, `Failed to broadcast message ${debugStringify(message)}${er}`, error);
103
+ logError(this.log, `Failed to broadcast message ${debugStringify(broadcastMessage)}${er}`, error);
105
104
  }
106
105
  }
107
106
  request(message) {
@@ -109,24 +108,23 @@ export class BroadcastServer extends EventEmitter {
109
108
  this.log.error('Broadcast channel is closed');
110
109
  return;
111
110
  }
112
- if (message.id === undefined) {
113
- message.id = this.getUniqueId();
114
- }
115
- if (message.timestamp === undefined) {
116
- message.timestamp = Date.now();
117
- }
118
- message.src = this.name;
119
- if (!this.isWorkerRequest(message)) {
120
- this.log.error(`Invalid request message format: ${debugStringify(message)}`);
111
+ const requestMessage = {
112
+ ...message,
113
+ id: message.id ?? this.getUniqueId(),
114
+ timestamp: message.timestamp ?? Date.now(),
115
+ src: this.name,
116
+ };
117
+ if (!this.isWorkerRequest(requestMessage)) {
118
+ this.log.error(`Invalid request message format: ${debugStringify(requestMessage)}`);
121
119
  return;
122
120
  }
123
121
  if (this.verbose)
124
- this.log.debug(`Broadcasting request message: ${debugStringify(message)}`);
122
+ this.log.debug(`Broadcasting request message: ${debugStringify(requestMessage)}`);
125
123
  try {
126
- this.broadcastChannel.postMessage(message);
124
+ this.broadcastChannel.postMessage(requestMessage);
127
125
  }
128
126
  catch (error) {
129
- logError(this.log, `Failed to broadcast request message ${debugStringify(message)}${er}`, error);
127
+ logError(this.log, `Failed to broadcast request message ${debugStringify(requestMessage)}${er}`, error);
130
128
  }
131
129
  }
132
130
  respond(message) {
@@ -134,65 +132,64 @@ export class BroadcastServer extends EventEmitter {
134
132
  this.log.error('Broadcast channel is closed');
135
133
  return;
136
134
  }
137
- if (typeof message.timestamp === 'number') {
138
- message.elapsed = Date.now() - message.timestamp;
139
- }
140
- if (message.timestamp === undefined) {
141
- message.timestamp = Date.now();
142
- }
143
- if (message.dst === this.name) {
144
- message.dst = message.src;
145
- }
146
- message.src = this.name;
147
- if (!this.isWorkerResponse(message)) {
148
- this.log.error(`Invalid response message format: ${debugStringify(message)}`);
135
+ const now = Date.now();
136
+ const responseMessage = {
137
+ ...message,
138
+ ...(typeof message.timestamp === 'number' ? { elapsed: now - message.timestamp } : {}),
139
+ timestamp: message.timestamp ?? now,
140
+ dst: message.dst === this.name ? message.src : message.dst,
141
+ src: this.name,
142
+ };
143
+ if (!this.isWorkerResponse(responseMessage)) {
144
+ this.log.error(`Invalid response message format: ${debugStringify(responseMessage)}`);
149
145
  return;
150
146
  }
151
147
  if (this.verbose)
152
- this.log.debug(`Broadcasting response message: ${debugStringify(message)}`);
148
+ this.log.debug(`Broadcasting response message: ${debugStringify(responseMessage)}`);
153
149
  try {
154
- this.broadcastChannel.postMessage(message);
150
+ this.broadcastChannel.postMessage(responseMessage);
155
151
  }
156
152
  catch (error) {
157
- logError(this.log, `Failed to broadcast response message ${debugStringify(message)}${er}`, error);
153
+ logError(this.log, `Failed to broadcast response message ${debugStringify(responseMessage)}${er}`, error);
158
154
  }
159
155
  }
160
156
  async fetch(message, timeout = 250) {
161
157
  if (this.closed) {
162
158
  return Promise.reject(new Error('Broadcast channel is closed'));
163
159
  }
164
- if (message.id === undefined) {
165
- message.id = this.getUniqueId();
166
- }
167
- if (message.timestamp === undefined) {
168
- message.timestamp = Date.now();
169
- }
160
+ let timeoutId;
161
+ const requestMessage = {
162
+ ...message,
163
+ id: message.id ?? this.getUniqueId(),
164
+ timestamp: message.timestamp ?? Date.now(),
165
+ src: this.name,
166
+ };
170
167
  if (this.verbose)
171
- this.log.debug(`Fetching message: ${debugStringify(message)}`);
168
+ this.log.debug(`Fetching message: ${debugStringify(requestMessage)}`);
172
169
  return new Promise((resolve, reject) => {
173
170
  const responseHandler = (msg) => {
174
- if (this.isWorkerResponseOfType(msg, message.type) && msg.id === message.id) {
171
+ if (this.isWorkerResponseOfType(msg, requestMessage.type) && msg.id === requestMessage.id) {
175
172
  clearTimeout(timeoutId);
176
173
  this.off('broadcast_message', responseHandler);
177
174
  if (this.verbose)
178
175
  this.log.debug(`Fetch response: ${debugStringify(msg)}`);
179
176
  if ('error' in msg && typeof msg.error === 'string') {
180
- reject(new Error(`Fetch received error response ${msg.error} to message type ${message.type} id ${message.id} from ${message.src} to ${message.dst}`));
177
+ reject(new Error(`Fetch received error response ${msg.error} to message type ${requestMessage.type} id ${requestMessage.id} from ${requestMessage.src} to ${requestMessage.dst}`));
181
178
  }
182
179
  else if ('result' in msg) {
183
180
  resolve(msg);
184
181
  }
185
182
  else {
186
- reject(new Error(`Fetch received malformed response for message type ${message.type} id ${message.id} from ${message.src} to ${message.dst}`));
183
+ reject(new Error(`Fetch received malformed response for message type ${requestMessage.type} id ${requestMessage.id} from ${requestMessage.src} to ${requestMessage.dst}`));
187
184
  }
188
185
  return;
189
186
  }
190
187
  };
191
188
  this.on('broadcast_message', responseHandler);
192
- this.request(message);
193
- const timeoutId = setTimeout(() => {
189
+ this.request(requestMessage);
190
+ timeoutId = setTimeout(() => {
194
191
  this.off('broadcast_message', responseHandler);
195
- reject(new Error(`Fetch timeout after ${timeout}ms for message type ${message.type} id ${message.id} from ${message.src} to ${message.dst}`));
192
+ reject(new Error(`Fetch timeout after ${timeout}ms for message type ${requestMessage.type} id ${requestMessage.id} from ${requestMessage.src} to ${requestMessage.dst}`));
196
193
  }, timeout);
197
194
  });
198
195
  }
@@ -1,8 +1,13 @@
1
1
  import { createHash } from 'node:crypto';
2
2
  import { plg } from '@matterbridge/types';
3
+ import { getErrorMessage } from '@matterbridge/utils/error';
4
+ import { logModuleLoaded } from '@matterbridge/utils/loader';
3
5
  import { isValidString } from '@matterbridge/utils/validate';
4
6
  import { AnsiLogger, db, debugStringify, nt, wr } from 'node-ansi-logger';
5
7
  import { BroadcastServer } from './broadcastServer.js';
8
+ logModuleLoaded('CheckUpdates');
9
+ let refreshSettingsRequired = false;
10
+ let refreshPluginsRequired = false;
6
11
  export async function checkUpdates(matterbridge) {
7
12
  const log = new AnsiLogger({ logName: 'MatterbridgeUpdates', logTimestampFormat: 4, logLevel: matterbridge.logLevel });
8
13
  const server = new BroadcastServer('updates', log);
@@ -19,9 +24,15 @@ export async function checkUpdates(matterbridge) {
19
24
  }
20
25
  }
21
26
  catch (error) {
22
- log.debug(`Error fetching plugins for update check: ${error instanceof Error ? error.message : error}`);
27
+ log.debug(`Error fetching plugins for update check: ${getErrorMessage(error)}`);
23
28
  }
24
29
  await Promise.all([checkUpdatePromise, latestVersionPromise, devVersionPromise, ...pluginsVersionPromises, ...pluginsDevVersionPromises]);
30
+ if (refreshSettingsRequired)
31
+ server.request({ type: 'frontend_refreshrequired', src: server.name, dst: 'frontend', params: { changed: 'settings' } });
32
+ if (refreshPluginsRequired)
33
+ server.request({ type: 'frontend_refreshrequired', src: server.name, dst: 'frontend', params: { changed: 'plugins' } });
34
+ refreshSettingsRequired = false;
35
+ refreshPluginsRequired = false;
25
36
  server.close();
26
37
  }
27
38
  export async function checkUpdatesAndLog(matterbridge, log, server) {
@@ -54,7 +65,7 @@ export async function checkUpdatesAndLog(matterbridge, log, server) {
54
65
  }
55
66
  }
56
67
  catch (error) {
57
- log.debug(`Error checking GitHub ${branch} updates: ${error instanceof Error ? error.message : error}`);
68
+ log.debug(`Error checking GitHub ${branch} updates: ${getErrorMessage(error)}`);
58
69
  }
59
70
  }
60
71
  export async function getMatterbridgeLatestVersion(matterbridge, log, server) {
@@ -62,7 +73,10 @@ export async function getMatterbridgeLatestVersion(matterbridge, log, server) {
62
73
  try {
63
74
  const version = await getNpmPackageVersion('matterbridge');
64
75
  server.request({ type: 'matterbridge_latest_version', src: server.name, dst: 'matterbridge', params: { version } });
65
- if (matterbridge.matterbridgeVersion !== version) {
76
+ if (matterbridge.matterbridgeVersion === version) {
77
+ log.debug(`Matterbridge is up to date. Current version: ${matterbridge.matterbridgeVersion}. Latest version: ${version}.`);
78
+ }
79
+ else {
66
80
  log.notice(`Matterbridge is out of date. Current version: ${matterbridge.matterbridgeVersion}. Latest version: ${version}.`);
67
81
  server.request({
68
82
  type: 'frontend_snackbarmessage',
@@ -71,15 +85,13 @@ export async function getMatterbridgeLatestVersion(matterbridge, log, server) {
71
85
  params: { message: 'Matterbridge latest update available', timeout: 0, severity: 'info' },
72
86
  });
73
87
  server.request({ type: 'frontend_updaterequired', src: server.name, dst: 'frontend', params: { devVersion: false } });
74
- server.request({ type: 'frontend_refreshrequired', src: server.name, dst: 'frontend', params: { changed: 'settings' } });
75
- }
76
- else {
77
- log.debug(`Matterbridge is up to date. Current version: ${matterbridge.matterbridgeVersion}. Latest version: ${version}.`);
88
+ refreshSettingsRequired = true;
78
89
  }
79
90
  return version;
80
91
  }
81
92
  catch (error) {
82
- log.warn(`Error getting Matterbridge latest version: ${error instanceof Error ? error.message : error}`);
93
+ log.warn(`Error getting Matterbridge latest version: ${getErrorMessage(error)}`);
94
+ return undefined;
83
95
  }
84
96
  }
85
97
  export async function getMatterbridgeDevVersion(matterbridge, log, server) {
@@ -96,7 +108,7 @@ export async function getMatterbridgeDevVersion(matterbridge, log, server) {
96
108
  params: { message: 'Matterbridge dev update available', timeout: 0, severity: 'info' },
97
109
  });
98
110
  server.request({ type: 'frontend_updaterequired', src: server.name, dst: 'frontend', params: { devVersion: true } });
99
- server.request({ type: 'frontend_refreshrequired', src: server.name, dst: 'frontend', params: { changed: 'settings' } });
111
+ refreshSettingsRequired = true;
100
112
  }
101
113
  else if ((matterbridge.matterbridgeVersion.includes('-dev-') || matterbridge.matterbridgeVersion.includes('-git-')) && matterbridge.matterbridgeVersion === version) {
102
114
  log.debug(`Matterbridge@dev is up to date. Current version: ${matterbridge.matterbridgeVersion}. Latest dev version: ${version}.`);
@@ -104,7 +116,8 @@ export async function getMatterbridgeDevVersion(matterbridge, log, server) {
104
116
  return version;
105
117
  }
106
118
  catch (error) {
107
- log.warn(`Error getting Matterbridge latest dev version: ${error instanceof Error ? error.message : error}`);
119
+ log.warn(`Error getting Matterbridge latest dev version: ${getErrorMessage(error)}`);
120
+ return undefined;
108
121
  }
109
122
  }
110
123
  export async function getPluginLatestVersion(log, server, plugin) {
@@ -113,17 +126,18 @@ export async function getPluginLatestVersion(log, server, plugin) {
113
126
  const version = await getNpmPackageVersion(plugin.name);
114
127
  plugin.latestVersion = version;
115
128
  server.request({ type: 'plugins_set_latest_version', src: server.name, dst: 'plugins', params: { plugin, version } });
116
- if (plugin.version !== plugin.latestVersion) {
117
- log.notice(`The plugin ${plg}${plugin.name}${nt} is out of date. Current version: ${plugin.version}. Latest version: ${plugin.latestVersion}.`);
118
- server.request({ type: 'frontend_refreshrequired', src: server.name, dst: 'frontend', params: { changed: 'plugins' } });
129
+ if (plugin.version === plugin.latestVersion) {
130
+ log.debug(`The plugin ${plg}${plugin.name}${db} is up to date. Current version: ${plugin.version}. Latest version: ${plugin.latestVersion}.`);
119
131
  }
120
132
  else {
121
- log.debug(`The plugin ${plg}${plugin.name}${db} is up to date. Current version: ${plugin.version}. Latest version: ${plugin.latestVersion}.`);
133
+ log.notice(`The plugin ${plg}${plugin.name}${nt} is out of date. Current version: ${plugin.version}. Latest version: ${plugin.latestVersion}.`);
134
+ refreshPluginsRequired = true;
122
135
  }
123
136
  return version;
124
137
  }
125
138
  catch (error) {
126
- log.warn(`Error getting plugin ${plg}${plugin.name}${wr} latest version: ${error instanceof Error ? error.message : error}`);
139
+ log.warn(`Error getting plugin ${plg}${plugin.name}${wr} latest version: ${getErrorMessage(error)}`);
140
+ return undefined;
127
141
  }
128
142
  }
129
143
  export async function getPluginDevVersion(log, server, plugin) {
@@ -134,7 +148,7 @@ export async function getPluginDevVersion(log, server, plugin) {
134
148
  server.request({ type: 'plugins_set_dev_version', src: server.name, dst: 'plugins', params: { plugin, version } });
135
149
  if ((plugin.version.includes('-dev-') || plugin.version.includes('-git-')) && plugin.version !== plugin.devVersion) {
136
150
  log.notice(`The plugin ${plg}${plugin.name}${nt} is out of date. Current version: ${plugin.version}. Latest dev version: ${plugin.devVersion}.`);
137
- server.request({ type: 'frontend_refreshrequired', src: server.name, dst: 'frontend', params: { changed: 'plugins' } });
151
+ refreshPluginsRequired = true;
138
152
  }
139
153
  else if (plugin.version.includes('-dev-') && plugin.version === plugin.devVersion) {
140
154
  log.debug(`The plugin ${plg}${plugin.name}${db} is up to date. Current version: ${plugin.version}. Latest dev version: ${plugin.devVersion}.`);
@@ -142,6 +156,7 @@ export async function getPluginDevVersion(log, server, plugin) {
142
156
  return version;
143
157
  }
144
158
  catch (error) {
145
- log.debug(`Error getting plugin ${plg}${plugin.name}${db} latest dev version: ${error instanceof Error ? error.message : error}`);
159
+ log.debug(`Error getting plugin ${plg}${plugin.name}${db} latest dev version: ${getErrorMessage(error)}`);
160
+ return undefined;
146
161
  }
147
162
  }
@@ -1,4 +1,7 @@
1
+ import { getErrorMessage } from '@matterbridge/utils/error';
2
+ import { logModuleLoaded } from '@matterbridge/utils/loader';
1
3
  import { isValidString } from '@matterbridge/utils/validate';
4
+ logModuleLoaded('DockerVersion');
2
5
  async function httpsGetJson(url, headers, timeoutMs) {
3
6
  const https = await import('node:https');
4
7
  return new Promise((resolve, reject) => {
@@ -46,13 +49,13 @@ async function httpsGetJson(url, headers, timeoutMs) {
46
49
  resolve(JSON.parse(data));
47
50
  }
48
51
  catch (error) {
49
- reject(new Error(`Failed to parse response JSON: ${error instanceof Error ? error.message : error}`));
52
+ reject(new Error(`Failed to parse response JSON: ${getErrorMessage(error)}`));
50
53
  }
51
54
  });
52
55
  });
53
56
  req.on('error', (error) => {
54
57
  clearTimeout(timeoutId);
55
- reject(new Error(`Request failed: ${error instanceof Error ? error.message : error}`));
58
+ reject(new Error(`Request failed: ${getErrorMessage(error)}`));
56
59
  });
57
60
  });
58
61
  }
@@ -1,6 +1,9 @@
1
1
  import { hasParameter } from '@matterbridge/utils/cli';
2
+ import { getErrorMessage } from '@matterbridge/utils/error';
3
+ import { logModuleLoaded } from '@matterbridge/utils/loader';
2
4
  import { AnsiLogger } from 'node-ansi-logger';
3
5
  import { BroadcastServer } from './broadcastServer.js';
6
+ logModuleLoaded('SpawnCommand');
4
7
  export async function spawnCommand(command, args, packageCommand, packageName) {
5
8
  const { spawn } = await import('node:child_process');
6
9
  const debug = hasParameter('debug') || hasParameter('verbose') || hasParameter('debug-spawn') || hasParameter('verbose-spawn');
@@ -12,7 +15,7 @@ export async function spawnCommand(command, args, packageCommand, packageName) {
12
15
  server.request({ type: 'frontend_logmessage', src: 'spawn', dst: 'frontend', params: { level: 'spawn', time: log.now(), name, message } });
13
16
  }
14
17
  catch (err) {
15
- log.debug(`Failed to send log message to frontend: ${err instanceof Error ? err.message : String(err)}`);
18
+ log.debug(`Failed to send log message to frontend: ${getErrorMessage(err)}`);
16
19
  }
17
20
  };
18
21
  if (verbose)
@@ -38,7 +41,7 @@ export async function spawnCommand(command, args, packageCommand, packageName) {
38
41
  stdio: ['inherit', 'pipe', 'pipe'],
39
42
  });
40
43
  childProcess.on('error', (err) => {
41
- log.error(`Failed to start child process "${cmdLine}": ${err.message}`);
44
+ log.error(`Failed to start child process "${cmdLine}": ${getErrorMessage(err)}`);
42
45
  sendLog('Matterbridge:spawn-exit-error', 'Spawn process error');
43
46
  resolve(false);
44
47
  });
@@ -1,12 +1,13 @@
1
- if (process.argv.includes('--loader'))
2
- console.log('\u001B[32m[' + new Date().toLocaleTimeString('en-US', { hour12: false, hour: '2-digit', minute: '2-digit', second: '2-digit', fractionalSecondDigits: 3 }) + '] ThreadsManager loaded.\u001B[40;0m');
3
1
  import fs from 'node:fs';
4
- import path, { resolve } from 'node:path';
2
+ import path from 'node:path';
5
3
  import { fileURLToPath, pathToFileURL } from 'node:url';
6
4
  import { Worker } from 'node:worker_threads';
7
5
  import { hasParameter } from '@matterbridge/utils/cli';
6
+ import { getErrorMessage } from '@matterbridge/utils/error';
7
+ import { logModuleLoaded } from '@matterbridge/utils/loader';
8
8
  import { AnsiLogger, CYAN, db, debugStringify, MAGENTA, wr } from 'node-ansi-logger';
9
9
  import { BroadcastServer } from './broadcastServer.js';
10
+ logModuleLoaded('ThreadsManager');
10
11
  export class ThreadsManager {
11
12
  debug;
12
13
  verbose;
@@ -51,7 +52,7 @@ export class ThreadsManager {
51
52
  if (this.verbose)
52
53
  this.log.notice(`ThreadsManager destroyed. Broadcast server closed.`);
53
54
  }
54
- async msgHandler(msg) {
55
+ msgHandler(msg) {
55
56
  if (this.server.isWorkerRequest(msg) && (msg.dst === 'all' || msg.dst === 'manager')) {
56
57
  if (this.verbose)
57
58
  this.log.debug(`Received broadcast request ${CYAN}${msg.type}${db} from ${CYAN}${msg.src}${db}: ${debugStringify(msg)}${db}`);
@@ -72,7 +73,7 @@ export class ThreadsManager {
72
73
  this.server.respond({ ...msg, result: { success: true } });
73
74
  }
74
75
  catch (err) {
75
- this.log.warn(`Failed to run thread ${CYAN}${msg.params.name}${wr}: ${err.message}`);
76
+ this.log.warn(`Failed to run thread ${CYAN}${msg.params.name}${wr}: ${getErrorMessage(err)}`);
76
77
  this.server.respond({ ...msg, result: { success: false } });
77
78
  }
78
79
  break;
@@ -87,11 +88,11 @@ export class ThreadsManager {
87
88
  this.log.debug(`Thread ${thread.name} running: ${thread.worker ? 'yes' : 'no'}, lastSeen: ${thread.lastSeen ? new Date(thread.lastSeen).toISOString() : 'never'}, runs: ${thread.runCount ?? 0}, errors: ${thread.errorCount ?? 0}`);
88
89
  }
89
90
  for (const thread of this.threads) {
90
- if (thread.worker && Date.now() - (thread.lastSeen || 0) > this.intervalMs) {
91
+ if (thread.worker && Date.now() - (thread.lastSeen ?? 0) > this.intervalMs) {
91
92
  const msg = { type: 'ping', threadId: thread.worker.threadId, threadName: thread.name };
92
93
  thread.worker.postMessage(msg);
93
94
  }
94
- if (thread.worker && Date.now() - (thread.lastSeen || 0) > this.intervalMs * 2) {
95
+ if (thread.worker && Date.now() - (thread.lastSeen ?? 0) > this.intervalMs * 2) {
95
96
  this.log.warn(`Thread ${CYAN}${thread.name}${db} has not been seen for more than ${this.intervalMs * 2} ms. It may be unresponsive.`);
96
97
  }
97
98
  }
@@ -177,7 +178,7 @@ export class ThreadsManager {
177
178
  return candidates[0];
178
179
  }
179
180
  createESMWorker(name, relativePath, workerData, argv, env, execArgv, pipedOutput = false) {
180
- const fileURL = pathToFileURL(resolve(relativePath));
181
+ const fileURL = pathToFileURL(path.resolve(relativePath));
181
182
  const options = {
182
183
  workerData: { ...workerData, threadName: name, debug: this.debug, verbose: this.verbose, logLevel: this.log.logLevel, tracker: this.tracker },
183
184
  type: 'module',
@@ -1,8 +1,8 @@
1
- if (process.argv.includes('--loader'))
2
- console.log('\u001B[35m[' + new Date().toLocaleTimeString('en-US', { hour12: false, hour: '2-digit', minute: '2-digit', second: '2-digit', fractionalSecondDigits: 3 }) + '] ArchiveCommand loaded.\u001B[40;0m');
3
1
  import { isArchiveWorkerData } from '@matterbridge/types';
2
+ import { logModuleLoaded } from '@matterbridge/utils/loader';
4
3
  import { WorkerWrapper } from './workerWrapper.js';
5
4
  import { createZip, readZip, unZip } from './zipjs.js';
5
+ logModuleLoaded('ArchiveCommand', '\u001B[35m');
6
6
  export default new WorkerWrapper('ArchiveCommand', async (worker) => {
7
7
  if (!isArchiveWorkerData(worker.workerData)) {
8
8
  worker.logger("error", `ArchiveCommand invalid parameters`);
@@ -1,8 +1,8 @@
1
- if (process.argv.includes('--loader'))
2
- console.log('\u001B[35m[' + new Date().toLocaleTimeString('en-US', { hour12: false, hour: '2-digit', minute: '2-digit', second: '2-digit', fractionalSecondDigits: 3 }) + '] CheckUpdates loaded.\u001B[40;0m');
3
1
  import { inspectError } from '@matterbridge/utils/error';
2
+ import { logModuleLoaded } from '@matterbridge/utils/loader';
4
3
  import { checkUpdates } from './checkUpdates.js';
5
4
  import { WorkerWrapper } from './workerWrapper.js';
5
+ logModuleLoaded('CheckUpdates', '\u001B[35m');
6
6
  export default new WorkerWrapper('CheckUpdates', async (worker) => {
7
7
  worker.logger("info", `Starting check updates...`);
8
8
  let success = false;
@@ -1,10 +1,10 @@
1
- if (process.argv.includes('--loader'))
2
- console.log('\u001B[35m[' + new Date().toLocaleTimeString('en-US', { hour12: false, hour: '2-digit', minute: '2-digit', second: '2-digit', fractionalSecondDigits: 3 }) + '] DockerVersion loaded.\u001B[40;0m');
3
1
  import { readFileSync } from 'node:fs';
4
2
  import { inspectError } from '@matterbridge/utils/error';
3
+ import { logModuleLoaded } from '@matterbridge/utils/loader';
5
4
  import { debugStringify } from 'node-ansi-logger';
6
5
  import { getDockerVersion } from './dockerVersion.js';
7
6
  import { WorkerWrapper } from './workerWrapper.js';
7
+ logModuleLoaded('DockerVersion', '\u001B[35m');
8
8
  export default new WorkerWrapper('DockerVersion', async (worker) => {
9
9
  worker.logger("info", `Starting docker version check...`);
10
10
  let success = false;
@@ -1,8 +1,8 @@
1
- if (process.argv.includes('--loader'))
2
- console.log('\u001B[35m[' + new Date().toLocaleTimeString('en-US', { hour12: false, hour: '2-digit', minute: '2-digit', second: '2-digit', fractionalSecondDigits: 3 }) + '] GlobalPrefix loaded.\u001B[40;0m');
3
1
  import { inspectError } from '@matterbridge/utils/error';
2
+ import { logModuleLoaded } from '@matterbridge/utils/loader';
4
3
  import { getGlobalNodeModules } from '@matterbridge/utils/npm-prefix';
5
4
  import { WorkerWrapper } from './workerWrapper.js';
5
+ logModuleLoaded('GlobalPrefix', '\u001B[35m');
6
6
  export default new WorkerWrapper('GlobalPrefix', async (worker) => {
7
7
  let prefix;
8
8
  worker.logger("info", `Starting global prefix check...`);
@@ -1,8 +1,8 @@
1
- if (process.argv.includes('--loader'))
2
- console.log('\u001B[35m[' + new Date().toLocaleTimeString('en-US', { hour12: false, hour: '2-digit', minute: '2-digit', second: '2-digit', fractionalSecondDigits: 3 }) + '] SpawnCommand loaded.\u001B[40;0m');
3
1
  import { isSpawnWorkerData } from '@matterbridge/types';
2
+ import { logModuleLoaded } from '@matterbridge/utils/loader';
4
3
  import { spawnCommand } from './spawnCommand.js';
5
4
  import { WorkerWrapper } from './workerWrapper.js';
5
+ logModuleLoaded('SpawnCommand', '\u001B[35m');
6
6
  export default new WorkerWrapper('SpawnCommand', async (worker) => {
7
7
  if (!isSpawnWorkerData(worker.workerData)) {
8
8
  worker.logger("error", `SpawnCommand invalid parameters`);
@@ -1,9 +1,9 @@
1
- if (process.argv.includes('--loader'))
2
- console.log('\u001B[35m[' + new Date().toLocaleTimeString('en-US', { hour12: false, hour: '2-digit', minute: '2-digit', second: '2-digit', fractionalSecondDigits: 3 }) + '] SystemCheck loaded.\u001B[40;0m');
3
1
  import os from 'node:os';
4
2
  import { inspectError } from '@matterbridge/utils/error';
3
+ import { logModuleLoaded } from '@matterbridge/utils/loader';
5
4
  import { excludedInterfaceNamePattern } from '@matterbridge/utils/network';
6
5
  import { WorkerWrapper } from './workerWrapper.js';
6
+ logModuleLoaded('SystemCheck', '\u001B[35m');
7
7
  export default new WorkerWrapper('SystemCheck', async (worker) => {
8
8
  worker.logger("info", `Starting system check...`);
9
9
  let success = false;
@@ -39,7 +39,7 @@ export default new WorkerWrapper('SystemCheck', async (worker) => {
39
39
  }
40
40
  if (excludedInterfaceNamePattern.test(interfaceName))
41
41
  continue;
42
- for (const detail of interfaceDetails || []) {
42
+ for (const detail of interfaceDetails ?? []) {
43
43
  if (detail.internal)
44
44
  foundInternal = true;
45
45
  if (!detail.internal)
@@ -1,12 +1,12 @@
1
- if (process.argv.includes('--loader'))
2
- console.log('\u001B[32m[' + new Date().toLocaleTimeString('en-US', { hour12: false, hour: '2-digit', minute: '2-digit', second: '2-digit', fractionalSecondDigits: 3 }) + '] WorkerWrapper loaded.\u001B[40;0m');
3
1
  import { isMainThread, parentPort, threadId, workerData } from 'node:worker_threads';
4
- import { inspectError } from '@matterbridge/utils';
2
+ import { getErrorMessage, inspectError } from '@matterbridge/utils';
5
3
  import { hasParameter } from '@matterbridge/utils/cli';
6
4
  import { formatBytes } from '@matterbridge/utils/format';
5
+ import { logModuleLoaded } from '@matterbridge/utils/loader';
7
6
  import { AnsiLogger, debugStringify, MAGENTA } from 'node-ansi-logger';
8
7
  import { BroadcastServer } from './broadcastServer.js';
9
8
  import { ThreadsManager } from './threadsManager.js';
9
+ logModuleLoaded('WorkerWrapper');
10
10
  export class WorkerWrapper {
11
11
  name;
12
12
  callback;
@@ -21,9 +21,9 @@ export class WorkerWrapper {
21
21
  this.name = name;
22
22
  this.callback = callback;
23
23
  if (this.workerData) {
24
- this.debug = this.workerData.debug || this.debug;
25
- this.verbose = this.workerData.verbose || this.verbose;
26
- this.useTracker = this.workerData.tracker || this.useTracker;
24
+ this.debug = this.workerData.debug ?? this.debug;
25
+ this.verbose = this.workerData.verbose ?? this.verbose;
26
+ this.useTracker = this.workerData.tracker ?? this.useTracker;
27
27
  }
28
28
  if (this.useTracker) {
29
29
  void import('@matterbridge/utils/tracker')
@@ -34,7 +34,7 @@ export class WorkerWrapper {
34
34
  })
35
35
  .catch((err) => {
36
36
  if (this.debug)
37
- console.error(`WorkerWrapper ${this.name}: failed to load Tracker`, err);
37
+ console.error(`WorkerWrapper ${this.name}: failed to load Tracker ${getErrorMessage(err)}`);
38
38
  return;
39
39
  });
40
40
  }
@@ -75,17 +75,19 @@ export class WorkerWrapper {
75
75
  if (this.verbose)
76
76
  this.logWorkerInfo(this.log, false);
77
77
  if (!isMainThread) {
78
- setImmediate(async () => {
79
- let success = false;
80
- try {
81
- success = await callback(this);
82
- }
83
- catch (err) {
84
- inspectError(this.log, `Worker ${this.name} callback failed`, err);
85
- }
86
- finally {
87
- this.destroy(success);
88
- }
78
+ setImmediate(() => {
79
+ void (async () => {
80
+ let success = false;
81
+ try {
82
+ success = await callback(this);
83
+ }
84
+ catch (err) {
85
+ inspectError(this.log, `Worker ${this.name} callback failed`, err);
86
+ }
87
+ finally {
88
+ this.destroy(success);
89
+ }
90
+ })();
89
91
  });
90
92
  }
91
93
  }
package/dist/zipjs.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { mkdir, readdir, readFile, stat, writeFile } from 'node:fs/promises';
2
- import { basename, dirname, extname, isAbsolute, join, relative, resolve } from 'node:path';
2
+ import path from 'node:path';
3
3
  import { Uint8ArrayReader, Uint8ArrayWriter, ZipReader, ZipWriter } from '@zip.js/zip.js';
4
4
  import { AnsiLogger } from 'node-ansi-logger';
5
5
  export async function createZip(zipPath, sourcePaths) {
@@ -9,11 +9,11 @@ export async function createZip(zipPath, sourcePaths) {
9
9
  const writer = new ZipWriter(new Uint8ArrayWriter());
10
10
  let entryCount = 0;
11
11
  for (const sourcePath of sourcePaths) {
12
- const resolvedSourcePath = resolve(sourcePath);
13
- entryCount += await addZipEntry(writer, resolvedSourcePath, basename(resolvedSourcePath), log);
12
+ const resolvedSourcePath = path.resolve(sourcePath);
13
+ entryCount += await addZipEntry(writer, resolvedSourcePath, path.basename(resolvedSourcePath), log);
14
14
  }
15
15
  const zipData = await writer.close();
16
- await mkdir(dirname(zipPath), { recursive: true });
16
+ await mkdir(path.dirname(zipPath), { recursive: true });
17
17
  await writeFile(zipPath, zipData);
18
18
  log.info(`Created zip ${zipPath} with ${entryCount} entries (${zipData.byteLength} bytes).`);
19
19
  return zipData.byteLength;
@@ -58,7 +58,7 @@ export async function unZip(zipPath, destinationPath = getDefaultDestinationPath
58
58
  extractedEntries++;
59
59
  continue;
60
60
  }
61
- await mkdir(dirname(entryPath), { recursive: true });
61
+ await mkdir(path.dirname(entryPath), { recursive: true });
62
62
  const fileData = await entry.getData(new Uint8ArrayWriter());
63
63
  await writeFile(entryPath, fileData);
64
64
  extractedEntries++;
@@ -83,7 +83,7 @@ async function addZipEntry(writer, sourcePath, entryName, log) {
83
83
  children.sort((left, right) => left.name.localeCompare(right.name));
84
84
  let childEntries = 1;
85
85
  for (const child of children) {
86
- childEntries += await addZipEntry(writer, join(sourcePath, child.name), `${directoryEntryName}${child.name}`, log);
86
+ childEntries += await addZipEntry(writer, path.join(sourcePath, child.name), `${directoryEntryName}${child.name}`, log);
87
87
  }
88
88
  log.debug(`Added directory ${directoryEntryName}`);
89
89
  return childEntries;
@@ -96,18 +96,18 @@ async function addZipEntry(writer, sourcePath, entryName, log) {
96
96
  throw new Error(`Unsupported source path type: ${sourcePath}`);
97
97
  }
98
98
  function getDefaultDestinationPath(zipPath) {
99
- const zipExtension = extname(zipPath);
100
- const zipName = basename(zipPath, zipExtension);
101
- return join(dirname(zipPath), zipName);
99
+ const zipExtension = path.extname(zipPath);
100
+ const zipName = path.basename(zipPath, zipExtension);
101
+ return path.join(path.dirname(zipPath), zipName);
102
102
  }
103
103
  function normalizeEntryName(entryName) {
104
104
  return entryName.replace(/\\/g, '/');
105
105
  }
106
106
  function resolveEntryPath(destinationPath, entryName) {
107
- const resolvedDestinationPath = resolve(destinationPath);
108
- const resolvedEntryPath = resolve(resolvedDestinationPath, normalizeEntryName(entryName));
109
- const relativeEntryPath = relative(resolvedDestinationPath, resolvedEntryPath);
110
- if (isAbsolute(relativeEntryPath) || relativeEntryPath.startsWith('..')) {
107
+ const resolvedDestinationPath = path.resolve(destinationPath);
108
+ const resolvedEntryPath = path.resolve(resolvedDestinationPath, normalizeEntryName(entryName));
109
+ const relativeEntryPath = path.relative(resolvedDestinationPath, resolvedEntryPath);
110
+ if (path.isAbsolute(relativeEntryPath) || relativeEntryPath.startsWith('..')) {
111
111
  throw new Error(`Refusing to extract zip entry outside destination: ${entryName}`);
112
112
  }
113
113
  return resolvedEntryPath;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@matterbridge/thread",
3
- "version": "3.9.1-dev-20260614-18d1a2e",
3
+ "version": "3.9.1-dev-20260617-b1e1b99",
4
4
  "description": "Matterbridge thread library",
5
5
  "author": "https://github.com/Luligu",
6
6
  "homepage": "https://matterbridge.io/",
@@ -69,8 +69,8 @@
69
69
  "CHANGELOG.md"
70
70
  ],
71
71
  "dependencies": {
72
- "@matterbridge/types": "3.9.1-dev-20260614-18d1a2e",
73
- "@matterbridge/utils": "3.9.1-dev-20260614-18d1a2e",
72
+ "@matterbridge/types": "3.9.1-dev-20260617-b1e1b99",
73
+ "@matterbridge/utils": "3.9.1-dev-20260617-b1e1b99",
74
74
  "@zip.js/zip.js": "2.8.26",
75
75
  "node-ansi-logger": "3.3.0-dev-20260607-585945a"
76
76
  }