@matterbridge/core 3.10.13 → 3.10.14-dev-20261010-607ee18

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.
@@ -1,11 +1,25 @@
1
- import { spawn } from 'node:child_process';
2
- import { constants } from 'node:fs';
3
- import { access, readdir } from 'node:fs/promises';
1
+ import { spawn, spawnSync } from 'node:child_process';
2
+ import { accessSync, constants, readdirSync } from 'node:fs';
4
3
  import path from 'node:path';
5
4
  import { getErrorMessage } from '@matterbridge/utils';
5
+ import { hasAnyParameter } from '@matterbridge/utils/cli';
6
6
  import { AnsiLogger, MAGENTA } from 'node-ansi-logger';
7
- const log = new AnsiLogger({ logName: 'Ffmpeg', logLevel: "debug", logNameColor: MAGENTA, logTimestampFormat: 4 });
8
- async function runProbe(command, args) {
7
+ const log = new AnsiLogger({
8
+ logName: 'Ffmpeg',
9
+ logLevel: hasAnyParameter('debug', 'verbose') ? "debug" : "info",
10
+ logNameColor: MAGENTA,
11
+ logTimestampFormat: 4,
12
+ });
13
+ const PROBE_TIMEOUT_MS = 5000;
14
+ function runProbe(command, args) {
15
+ const child = spawnSync(command, args, { stdio: 'ignore', timeout: PROBE_TIMEOUT_MS });
16
+ if (child.error)
17
+ throw child.error;
18
+ if (child.status !== 0) {
19
+ throw new Error(`${command} exited with code ${child.status ?? -1}`);
20
+ }
21
+ }
22
+ async function runCommand(command, args) {
9
23
  await new Promise((resolve, reject) => {
10
24
  const child = spawn(command, args, { stdio: 'ignore' });
11
25
  child.once('error', reject);
@@ -18,10 +32,10 @@ async function runProbe(command, args) {
18
32
  });
19
33
  });
20
34
  }
21
- async function isRunnable(command) {
35
+ function isRunnable(command) {
22
36
  for (const versionArg of ['--version', '-version']) {
23
37
  try {
24
- await runProbe(command, [versionArg]);
38
+ runProbe(command, [versionArg]);
25
39
  return true;
26
40
  }
27
41
  catch {
@@ -29,20 +43,20 @@ async function isRunnable(command) {
29
43
  }
30
44
  return false;
31
45
  }
32
- async function getWindowsCommandCandidates() {
46
+ function getWindowsCommandCandidates() {
33
47
  if (process.platform !== 'win32')
34
48
  return [];
35
49
  const candidates = [];
36
50
  if (process.env.LOCALAPPDATA) {
37
51
  const wingetPackages = path.join(process.env.LOCALAPPDATA, 'Microsoft', 'WinGet', 'Packages');
38
52
  try {
39
- const packageDirs = await readdir(wingetPackages, { withFileTypes: true });
53
+ const packageDirs = readdirSync(wingetPackages, { withFileTypes: true });
40
54
  for (const packageDir of packageDirs) {
41
55
  if (!packageDir.isDirectory() || !packageDir.name.startsWith('Gyan.FFmpeg_'))
42
56
  continue;
43
57
  const packagePath = path.join(wingetPackages, packageDir.name);
44
58
  try {
45
- const versionDirs = await readdir(packagePath, { withFileTypes: true });
59
+ const versionDirs = readdirSync(packagePath, { withFileTypes: true });
46
60
  for (const versionDir of versionDirs) {
47
61
  if (versionDir.isDirectory() && versionDir.name.startsWith('ffmpeg-'))
48
62
  candidates.push(path.join(packagePath, versionDir.name, 'bin', 'ffmpeg.exe'));
@@ -62,20 +76,20 @@ async function getWindowsCommandCandidates() {
62
76
  }
63
77
  return candidates;
64
78
  }
65
- async function resolveFfmpeg() {
66
- const candidates = ['ffmpeg', '/usr/bin/ffmpeg', '/bin/ffmpeg', '/usr/local/bin/ffmpeg', ...(await getWindowsCommandCandidates())];
79
+ function resolveFfmpeg() {
80
+ const candidates = ['ffmpeg', '/usr/bin/ffmpeg', '/bin/ffmpeg', '/usr/local/bin/ffmpeg', ...getWindowsCommandCandidates()];
67
81
  log.debug(`Resolving ffmpeg: trying ${candidates.length} candidate(s)`);
68
82
  for (const candidate of candidates) {
69
83
  if (path.isAbsolute(candidate)) {
70
84
  try {
71
- await access(candidate, constants.X_OK);
85
+ accessSync(candidate, constants.X_OK);
72
86
  }
73
87
  catch {
74
88
  log.debug(`Candidate ${candidate} is not accessible`);
75
89
  continue;
76
90
  }
77
91
  }
78
- if (await isRunnable(candidate)) {
92
+ if (isRunnable(candidate)) {
79
93
  log.debug(`Found ffmpeg in ${candidate}`);
80
94
  return candidate;
81
95
  }
@@ -84,18 +98,27 @@ async function resolveFfmpeg() {
84
98
  log.debug('Could not resolve ffmpeg');
85
99
  return undefined;
86
100
  }
87
- let ffmpegCommand = await resolveFfmpeg();
88
- if (ffmpegCommand)
89
- log.debug(`Using ffmpeg: ${ffmpegCommand}`);
90
- else
91
- log.warn('ffmpeg could not be resolved on this host; ffmpeg-dependent features will not work');
101
+ let ffmpegCommand;
102
+ let ffmpegResolved = false;
103
+ function ensureFfmpegResolved() {
104
+ if (ffmpegResolved)
105
+ return;
106
+ ffmpegResolved = true;
107
+ ffmpegCommand = resolveFfmpeg();
108
+ if (ffmpegCommand)
109
+ log.debug(`Using ffmpeg: ${ffmpegCommand}`);
110
+ else
111
+ log.warn('ffmpeg could not be resolved on this host; ffmpeg-dependent features will not work');
112
+ }
92
113
  export function hasFfmpeg() {
114
+ ensureFfmpegResolved();
93
115
  return ffmpegCommand !== undefined;
94
116
  }
95
117
  export function redactSource(source) {
96
118
  return source.replace(/^([a-z][a-z\d+.-]*:\/\/)[^/]*@/i, '$1***@');
97
119
  }
98
120
  export function runFfmpeg(args) {
121
+ ensureFfmpegResolved();
99
122
  if (!ffmpegCommand) {
100
123
  throw new Error('Cannot run ffmpeg: not found on this host');
101
124
  }
@@ -168,6 +191,7 @@ export function getPlayWebcamArgs(videoSource, audioSource) {
168
191
  return ['-hide_banner', '-window_title', videoSource, '-f', 'v4l2', '-i', videoSource];
169
192
  }
170
193
  export function playWebcam(videoSource, audioSource) {
194
+ ensureFfmpegResolved();
171
195
  if (!ffmpegCommand) {
172
196
  throw new Error('Cannot play the webcam: ffmpeg not found on this host');
173
197
  }
@@ -186,12 +210,12 @@ export async function installFfmpeg() {
186
210
  return false;
187
211
  }
188
212
  try {
189
- if (await isRunnable('apk')) {
190
- await runProbe('apk', ['add', '--no-cache', 'ffmpeg']);
213
+ if (isRunnable('apk')) {
214
+ await runCommand('apk', ['add', '--no-cache', 'ffmpeg']);
191
215
  }
192
- else if (await isRunnable('apt-get')) {
193
- await runProbe('apt-get', ['update']);
194
- await runProbe('env', ['DEBIAN_FRONTEND=noninteractive', 'apt-get', 'install', '-y', '--no-install-recommends', 'ffmpeg']);
216
+ else if (isRunnable('apt-get')) {
217
+ await runCommand('apt-get', ['update']);
218
+ await runCommand('env', ['DEBIAN_FRONTEND=noninteractive', 'apt-get', 'install', '-y', '--no-install-recommends', 'ffmpeg']);
195
219
  }
196
220
  else {
197
221
  log.warn('Cannot install ffmpeg: no supported package manager found (apk, apt-get)');
@@ -202,7 +226,8 @@ export async function installFfmpeg() {
202
226
  log.error(`Failed to install ffmpeg: ${getErrorMessage(error)}`);
203
227
  return false;
204
228
  }
205
- ffmpegCommand = await resolveFfmpeg();
229
+ ffmpegCommand = resolveFfmpeg();
230
+ ffmpegResolved = true;
206
231
  log.info(ffmpegCommand ? `Installed ffmpeg: ${ffmpegCommand}` : 'ffmpeg was installed but could not be resolved');
207
232
  return ffmpegCommand !== undefined;
208
233
  }
package/dist/cli.d.ts CHANGED
@@ -4,3 +4,4 @@ import type { Matterbridge } from './matterbridge.js';
4
4
  export declare let instance: Matterbridge | undefined;
5
5
  export declare const tracker: Tracker;
6
6
  export declare const inspector: Inspector;
7
+ export declare function main(): Promise<void>;
package/dist/cli.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import path from 'node:path';
2
2
  import { fileURLToPath } from 'node:url';
3
3
  import { ThreadsManager } from '@matterbridge/thread/manager';
4
+ import { BroadcastServer } from '@matterbridge/thread/server';
4
5
  import { hasAnyParameter, hasParameter } from '@matterbridge/utils/cli';
5
6
  import { inspectError } from '@matterbridge/utils/error';
6
7
  import { formatBytes, formatUptime } from '@matterbridge/utils/format';
@@ -13,11 +14,9 @@ logModuleLoaded('Cli');
13
14
  export let instance;
14
15
  export const tracker = new Tracker('Cli', false, false);
15
16
  export const inspector = new Inspector('Cli', false, false);
17
+ const log = new AnsiLogger({ logName: 'Cli', logTimestampFormat: 4, logLevel: hasAnyParameter('debug', 'verbose') ? "debug" : "info" });
16
18
  const manager = new ThreadsManager(path.dirname(fileURLToPath(import.meta.url)));
17
- const colorEnabled = !process.env.NO_COLOR && process.env.TERM !== 'dumb' && process.env.FORCE_COLOR !== '0' && !hasParameter('no-ansi');
18
- if (!colorEnabled)
19
- process.env.NO_COLOR = '1';
20
- const log = new AnsiLogger({ logName: 'Cli', logTimestampFormat: 4, logLevel: hasParameter('debug') ? "debug" : "info" });
19
+ const server = new BroadcastServer('cli', log);
21
20
  function startCpuMemoryCheck() {
22
21
  log.debug(`Cpu memory check starting...`);
23
22
  tracker.start();
@@ -55,8 +54,8 @@ function registerHandlers() {
55
54
  instance.on('shutdown', () => void shutdown().catch((error) => inspectError(log, 'Failed to shutdown', error)));
56
55
  instance.on('restart', () => void restart().catch((error) => inspectError(log, 'Failed to restart', error)));
57
56
  instance.on('update', () => void update().catch((error) => inspectError(log, 'Failed to update', error)));
58
- instance.on('startmemorycheck', () => start());
59
- instance.on('stopmemorycheck', () => stop());
57
+ instance.on('startmemorycheck', () => startCpuMemoryCheck());
58
+ instance.on('stopmemorycheck', () => stopCpuMemoryCheck());
60
59
  instance.on('startinspector', () => void startInspector().catch((error) => inspectError(log, 'Failed to start inspector', error)));
61
60
  instance.on('stopinspector', () => void stopInspector().catch((error) => inspectError(log, 'Failed to stop inspector', error)));
62
61
  instance.on('takeheapsnapshot', () => void takeHeapSnapshot().catch((error) => inspectError(log, 'Failed to take heap snapshot', error)));
@@ -68,6 +67,7 @@ async function shutdown() {
68
67
  if (hasParameter('inspect'))
69
68
  await stopInspector();
70
69
  stopCpuMemoryCheck();
70
+ server.close();
71
71
  manager.destroy();
72
72
  cliEmitter.emit('shutdown');
73
73
  log.debug('Received shutdown event, exit with code 0');
@@ -85,16 +85,10 @@ async function update() {
85
85
  instance = await Matterbridge.loadInstance(true);
86
86
  registerHandlers();
87
87
  }
88
- function start() {
89
- log.debug('Received start memory check event');
90
- startCpuMemoryCheck();
91
- }
92
- function stop() {
93
- log.debug('Received stop memory check event');
94
- stopCpuMemoryCheck();
95
- }
96
- async function main() {
88
+ export async function main() {
97
89
  log.debug(`Cli main() started`);
90
+ if (!(!process.env.NO_COLOR && process.env.TERM !== 'dumb' && process.env.FORCE_COLOR !== '0' && !hasParameter('no-ansi')))
91
+ process.env.NO_COLOR = '1';
98
92
  startCpuMemoryCheck();
99
93
  if (hasParameter('inspect'))
100
94
  await startInspector();
@@ -175,6 +169,7 @@ function help() {
175
169
  --novirtual: disable the creation of the virtual devices Restart, Update and Reboot Matterbridge
176
170
  --root-power-source: add a Power Source cluster for the mains power feed on the Root endpoint
177
171
  --ssl: enable SSL for the frontend and the WebSocketServer (the server will use the certificates and switch to https)
172
+ --tls: enable SSL for the frontend and the WebSocketServer (the server will use the certificates and switch to https)
178
173
  --mtls: enable mTLS for the frontend and the WebSocketServer (both server and client will use and require the certificates and switch to https); it also enables https, so --ssl --tls is not needed
179
174
  --vendorId: override the default vendorId 0xfff1
180
175
  --vendorName: override the default vendorName "Matterbridge"
@@ -863,12 +863,13 @@ export class Matterbridge extends EventEmitter {
863
863
  (hasParameter('controller') ? 'mode controller ' : '') +
864
864
  (this.restartMode === 'none' ? '' : 'restart mode ' + this.restartMode + ' ') +
865
865
  'running on ' + this.systemInformation.osType + ' (v.' + this.systemInformation.osRelease + ') platform ' + this.systemInformation.osPlatform + ' arch ' + this.systemInformation.osArch);
866
- const minNodeVersion = 20;
866
+ const minNodeVersion = 22;
867
+ const minNodeMinorVersion = 13;
867
868
  const nodeVersion = process.versions.node;
868
- const [versionMajor] = nodeVersion.split('.').map(Number);
869
- if (versionMajor < minNodeVersion) {
870
- this.log.error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
871
- throw new Error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
869
+ const [versionMajor, versionMinor] = nodeVersion.split('.').map(Number);
870
+ if (versionMajor < minNodeVersion || (versionMajor === minNodeVersion && versionMinor < minNodeMinorVersion)) {
871
+ this.log.error(`Node version ${nodeVersion} is not supported. Please upgrade to ${minNodeVersion}.${minNodeMinorVersion}.0 or above.`);
872
+ throw new Error(`Node version ${nodeVersion} is not supported. Please upgrade to ${minNodeVersion}.${minNodeMinorVersion}.0 or above.`);
872
873
  }
873
874
  await this.parseCommandLine();
874
875
  this.emit('initialize_completed');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@matterbridge/core",
3
- "version": "3.10.13",
3
+ "version": "3.10.14-dev-20261010-607ee18",
4
4
  "description": "Matterbridge core library",
5
5
  "author": "https://github.com/Luligu",
6
6
  "homepage": "https://matterbridge.io/",
@@ -120,7 +120,7 @@
120
120
  }
121
121
  },
122
122
  "engines": {
123
- "node": ">=20.19.0 <21.0.0 || >=22.13.0 <23.0.0 || >=24.0.0 <25.0.0 || >=26.0.0 <27.0.0"
123
+ "node": ">=22.13.0 <23.0.0 || >=24.0.0 <25.0.0 || >=26.0.0 <27.0.0"
124
124
  },
125
125
  "files": [
126
126
  "bin",
@@ -136,17 +136,17 @@
136
136
  },
137
137
  "dependencies": {
138
138
  "@matter/main": "0.17.9",
139
- "@matterbridge/dgram": "3.10.13",
140
- "@matterbridge/thread": "3.10.13",
141
- "@matterbridge/types": "3.10.13",
142
- "@matterbridge/utils": "3.10.13",
139
+ "@matterbridge/dgram": "3.10.14-dev-20261010-607ee18",
140
+ "@matterbridge/thread": "3.10.14-dev-20261010-607ee18",
141
+ "@matterbridge/types": "3.10.14-dev-20261010-607ee18",
142
+ "@matterbridge/utils": "3.10.14-dev-20261010-607ee18",
143
143
  "escape-html": "1.0.3",
144
144
  "express": "5.2.1",
145
- "express-rate-limit": "8.7.0",
145
+ "express-rate-limit": "8.7.1",
146
146
  "multer": "2.4.0",
147
147
  "node-ansi-logger": "3.3.1",
148
148
  "node-persist-manager": "2.1.1",
149
- "werift": "0.24.4",
149
+ "werift": "0.25.0",
150
150
  "ws": "8.22.0"
151
151
  }
152
152
  }