@matterbridge/core 3.7.1-dev-20260324-9c29691 → 3.7.1-dev-20260326-40eef29
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/dist/matterbridge.js +5 -5
- package/dist/mb_coap.d.ts +24 -0
- package/dist/mb_coap.js +153 -0
- package/dist/mb_health.d.ts +10 -0
- package/dist/mb_health.js +84 -0
- package/dist/mb_mdns.d.ts +34 -0
- package/dist/mb_mdns.js +278 -0
- package/package.json +5 -5
package/dist/matterbridge.js
CHANGED
|
@@ -85,7 +85,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
85
85
|
logName: 'Matter',
|
|
86
86
|
logNameColor: '\x1b[34m',
|
|
87
87
|
logTimestampFormat: 4,
|
|
88
|
-
logLevel:
|
|
88
|
+
logLevel: "debug",
|
|
89
89
|
});
|
|
90
90
|
matterLogLevel = this.matterLog.logLevel;
|
|
91
91
|
matterFileLogger = false;
|
|
@@ -338,7 +338,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
338
338
|
await createDirectory(this.matterbridgeCertDirectory, 'Matterbridge Matter Certificate Directory', this.log);
|
|
339
339
|
const currentFileDirectory = path.dirname(fileURLToPath(import.meta.url));
|
|
340
340
|
this.rootDirectory = currentFileDirectory.includes(path.join('packages', 'core')) ? path.resolve(currentFileDirectory, '../', '../', '../') : path.resolve(currentFileDirectory, '../', '../', '..', '../');
|
|
341
|
-
this.environment.vars.set('log.level', MatterLogLevel.
|
|
341
|
+
this.environment.vars.set('log.level', MatterLogLevel.DEBUG);
|
|
342
342
|
this.environment.vars.set('log.format', hasParameter('no-ansi') || process.env.NO_COLOR === '1' ? MatterLogFormat.PLAIN : MatterLogFormat.ANSI);
|
|
343
343
|
this.environment.vars.set('path.root', path.join(this.matterbridgeDirectory, MATTER_STORAGE_NAME));
|
|
344
344
|
this.environment.vars.set('runtime.signals', false);
|
|
@@ -468,7 +468,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
468
468
|
}
|
|
469
469
|
}
|
|
470
470
|
else {
|
|
471
|
-
this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel',
|
|
471
|
+
this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', "info");
|
|
472
472
|
}
|
|
473
473
|
this.logLevel = this.log.logLevel;
|
|
474
474
|
this.frontend.logLevel = this.log.logLevel;
|
|
@@ -508,9 +508,9 @@ export class Matterbridge extends EventEmitter {
|
|
|
508
508
|
}
|
|
509
509
|
}
|
|
510
510
|
else {
|
|
511
|
-
Logger.level = (await this.nodeContext.get('matterLogLevel',
|
|
511
|
+
Logger.level = (await this.nodeContext.get('matterLogLevel', MatterLogLevel.INFO));
|
|
512
512
|
}
|
|
513
|
-
Logger.format = MatterLogFormat.ANSI;
|
|
513
|
+
Logger.format = hasParameter('no-ansi') || process.env.NO_COLOR === '1' ? MatterLogFormat.PLAIN : MatterLogFormat.ANSI;
|
|
514
514
|
this.matterLogLevel = MatterLogLevel.names[Logger.level];
|
|
515
515
|
if (hasParameter('matterfilelogger') || (await this.nodeContext.get('matterFileLog', false))) {
|
|
516
516
|
this.matterFileLogger = true;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Coap } from '@matterbridge/dgram';
|
|
2
|
+
export declare const MB_COAP_DEFAULT_REQUEST_INTERVAL_MS = 10000;
|
|
3
|
+
export declare const MB_COAP_DEFAULT_TIMEOUT_MS = 600000;
|
|
4
|
+
export interface MbCoapOptions {
|
|
5
|
+
showHelp: boolean;
|
|
6
|
+
requestIntervalMs?: number;
|
|
7
|
+
interfaceName?: string;
|
|
8
|
+
ipv4InterfaceAddress: string;
|
|
9
|
+
ipv6InterfaceAddress: string;
|
|
10
|
+
disableLoopback: boolean;
|
|
11
|
+
disableIpv4: boolean;
|
|
12
|
+
disableIpv6: boolean;
|
|
13
|
+
disableTimeout: boolean;
|
|
14
|
+
}
|
|
15
|
+
export interface MbCoapRuntime {
|
|
16
|
+
coapIpv4?: Coap;
|
|
17
|
+
coapIpv6?: Coap;
|
|
18
|
+
cleanupAndLogAndExit: () => void;
|
|
19
|
+
}
|
|
20
|
+
export declare function getMbCoapHelpText(): string;
|
|
21
|
+
export declare function printMbCoapHelp(log?: (message: string) => void): void;
|
|
22
|
+
export declare function getMbCoapOptions(): MbCoapOptions;
|
|
23
|
+
export declare function startMbCoap(options: MbCoapOptions, exitFn?: (code: number) => never | void, registerSignalHandlers?: boolean): MbCoapRuntime;
|
|
24
|
+
export declare function mbCoapMain(exitFn?: (code: number) => never | void, log?: (message: string) => void): MbCoapRuntime | undefined;
|
package/dist/mb_coap.js
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { Coap, COAP_MULTICAST_IPV4_ADDRESS, COAP_MULTICAST_IPV6_ADDRESS, COAP_MULTICAST_PORT, COAP_OPTION_URI_PATH } from '@matterbridge/dgram';
|
|
2
|
+
import { getIntParameter, getParameter, hasParameter } from '@matterbridge/utils/cli';
|
|
3
|
+
export const MB_COAP_DEFAULT_REQUEST_INTERVAL_MS = 10000;
|
|
4
|
+
export const MB_COAP_DEFAULT_TIMEOUT_MS = 600000;
|
|
5
|
+
function defaultConsoleLog(message) {
|
|
6
|
+
console.log(message);
|
|
7
|
+
}
|
|
8
|
+
export function getMbCoapHelpText() {
|
|
9
|
+
return `Copyright (c) Matterbridge. All rights reserved. Version 2.0.0.
|
|
10
|
+
|
|
11
|
+
Usage: mb_coap [options...]
|
|
12
|
+
|
|
13
|
+
If no command line is provided, mb_coap starts IPv4 and IPv6 CoAP multicast listeners and waits for incoming traffic for up to 10 minutes.
|
|
14
|
+
|
|
15
|
+
Options:
|
|
16
|
+
-h, --help Show this help message and exit.
|
|
17
|
+
--request <interval> Send a multicast CoAP request to /cit/d (default interval: 10000ms).
|
|
18
|
+
--interfaceName <name> Network interface name to bind to (default all interfaces).
|
|
19
|
+
--ipv4InterfaceAddress <address> IPv4 address of the network interface to bind to (default: 0.0.0.0).
|
|
20
|
+
--ipv6InterfaceAddress <address> IPv6 address of the network interface to bind to (default: ::).
|
|
21
|
+
--no-loopback Disable multicast loopback (default: enabled).
|
|
22
|
+
--noIpv4 Disable IPv4 CoAP server (default: enabled).
|
|
23
|
+
--noIpv6 Disable IPv6 CoAP server (default: enabled).
|
|
24
|
+
--no-timeout Disable automatic timeout of 10 minutes.
|
|
25
|
+
-d, --debug Enable debug logging (default: disabled).
|
|
26
|
+
-v, --verbose Enable verbose logging (default: disabled).
|
|
27
|
+
-s, --silent Enable silent mode, only log notices, warnings and errors.
|
|
28
|
+
|
|
29
|
+
Examples:
|
|
30
|
+
# Start IPv4 and IPv6 CoAP listeners and wait for incoming traffic
|
|
31
|
+
mb_coap
|
|
32
|
+
|
|
33
|
+
# Start listeners and send multicast CoAP requests every 10 seconds
|
|
34
|
+
mb_coap --request
|
|
35
|
+
|
|
36
|
+
# Start listeners and send multicast CoAP requests every 5 seconds
|
|
37
|
+
mb_coap --request 5000
|
|
38
|
+
|
|
39
|
+
# Start listeners only on the eth0 interface
|
|
40
|
+
mb_coap --interfaceName eth0
|
|
41
|
+
|
|
42
|
+
# Start listeners with verbose logging enabled
|
|
43
|
+
mb_coap --verbose
|
|
44
|
+
|
|
45
|
+
# Start only the IPv4 listener and send multicast CoAP requests every 10 seconds
|
|
46
|
+
mb_coap --request --noIpv6
|
|
47
|
+
|
|
48
|
+
`;
|
|
49
|
+
}
|
|
50
|
+
export function printMbCoapHelp(log = defaultConsoleLog) {
|
|
51
|
+
log(getMbCoapHelpText());
|
|
52
|
+
}
|
|
53
|
+
export function getMbCoapOptions() {
|
|
54
|
+
const requestIntervalMs = hasParameter('request') ? getIntParameter('request') || MB_COAP_DEFAULT_REQUEST_INTERVAL_MS : undefined;
|
|
55
|
+
return {
|
|
56
|
+
showHelp: hasParameter('h') || hasParameter('help'),
|
|
57
|
+
requestIntervalMs,
|
|
58
|
+
interfaceName: getParameter('interfaceName'),
|
|
59
|
+
ipv4InterfaceAddress: getParameter('ipv4InterfaceAddress') || '0.0.0.0',
|
|
60
|
+
ipv6InterfaceAddress: getParameter('ipv6InterfaceAddress') || '::',
|
|
61
|
+
disableLoopback: hasParameter('no-loopback'),
|
|
62
|
+
disableIpv4: hasParameter('noIpv4'),
|
|
63
|
+
disableIpv6: hasParameter('noIpv6'),
|
|
64
|
+
disableTimeout: hasParameter('no-timeout'),
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
export function startMbCoap(options, exitFn = process.exit, registerSignalHandlers = true) {
|
|
68
|
+
let coapIpv4;
|
|
69
|
+
let coapIpv6;
|
|
70
|
+
if (!options.disableIpv4) {
|
|
71
|
+
coapIpv4 = new Coap('CoAP Server udp4', COAP_MULTICAST_IPV4_ADDRESS, COAP_MULTICAST_PORT, 'udp4', true, options.interfaceName, options.ipv4InterfaceAddress);
|
|
72
|
+
if (hasParameter('v') || hasParameter('verbose'))
|
|
73
|
+
coapIpv4.listNetworkInterfaces();
|
|
74
|
+
}
|
|
75
|
+
if (!options.disableIpv6) {
|
|
76
|
+
coapIpv6 = new Coap('CoAP Server udp6', COAP_MULTICAST_IPV6_ADDRESS, COAP_MULTICAST_PORT, 'udp6', true, options.interfaceName, options.ipv6InterfaceAddress);
|
|
77
|
+
if (options.disableIpv4 && (hasParameter('v') || hasParameter('verbose')))
|
|
78
|
+
coapIpv6.listNetworkInterfaces();
|
|
79
|
+
}
|
|
80
|
+
function cleanupAndLogAndExit() {
|
|
81
|
+
coapIpv4?.stop();
|
|
82
|
+
coapIpv6?.stop();
|
|
83
|
+
exitFn(0);
|
|
84
|
+
}
|
|
85
|
+
const requestUdp4 = () => {
|
|
86
|
+
coapIpv4?.sendRequest(32000, [
|
|
87
|
+
{ number: COAP_OPTION_URI_PATH, value: Buffer.from('cit') },
|
|
88
|
+
{ number: COAP_OPTION_URI_PATH, value: Buffer.from('d') },
|
|
89
|
+
], {}, undefined, COAP_MULTICAST_IPV4_ADDRESS, COAP_MULTICAST_PORT);
|
|
90
|
+
};
|
|
91
|
+
const requestUdp6 = () => {
|
|
92
|
+
coapIpv6?.sendRequest(32000, [
|
|
93
|
+
{ number: COAP_OPTION_URI_PATH, value: Buffer.from('cit') },
|
|
94
|
+
{ number: COAP_OPTION_URI_PATH, value: Buffer.from('d') },
|
|
95
|
+
], {}, undefined, COAP_MULTICAST_IPV6_ADDRESS, COAP_MULTICAST_PORT);
|
|
96
|
+
};
|
|
97
|
+
if (registerSignalHandlers) {
|
|
98
|
+
process.on('SIGINT', () => {
|
|
99
|
+
cleanupAndLogAndExit();
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
if (coapIpv4) {
|
|
103
|
+
coapIpv4.start();
|
|
104
|
+
coapIpv4.on('ready', (address) => {
|
|
105
|
+
if (options.disableLoopback) {
|
|
106
|
+
coapIpv4.socket.setMulticastLoopback(false);
|
|
107
|
+
coapIpv4.log.info('Multicast loopback disabled for coapIpv4');
|
|
108
|
+
}
|
|
109
|
+
coapIpv4.log.info(`coapIpv4 server ready on ${address.family} ${address.address}:${address.port}`);
|
|
110
|
+
if (options.requestIntervalMs === undefined)
|
|
111
|
+
return;
|
|
112
|
+
requestUdp4();
|
|
113
|
+
setInterval(() => {
|
|
114
|
+
requestUdp4();
|
|
115
|
+
}, options.requestIntervalMs).unref();
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
if (coapIpv6) {
|
|
119
|
+
coapIpv6.start();
|
|
120
|
+
coapIpv6.on('ready', (address) => {
|
|
121
|
+
if (options.disableLoopback) {
|
|
122
|
+
coapIpv6.socket.setMulticastLoopback(false);
|
|
123
|
+
coapIpv6.log.info('Multicast loopback disabled for coapIpv6');
|
|
124
|
+
}
|
|
125
|
+
coapIpv6.log.info(`coapIpv6 server ready on ${address.family} ${address.address}:${address.port}`);
|
|
126
|
+
if (options.requestIntervalMs === undefined)
|
|
127
|
+
return;
|
|
128
|
+
requestUdp6();
|
|
129
|
+
setInterval(() => {
|
|
130
|
+
requestUdp6();
|
|
131
|
+
}, options.requestIntervalMs).unref();
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
if (!options.disableTimeout) {
|
|
135
|
+
setTimeout(() => {
|
|
136
|
+
cleanupAndLogAndExit();
|
|
137
|
+
}, MB_COAP_DEFAULT_TIMEOUT_MS).unref();
|
|
138
|
+
}
|
|
139
|
+
return {
|
|
140
|
+
coapIpv4,
|
|
141
|
+
coapIpv6,
|
|
142
|
+
cleanupAndLogAndExit,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
export function mbCoapMain(exitFn = process.exit, log = defaultConsoleLog) {
|
|
146
|
+
const options = getMbCoapOptions();
|
|
147
|
+
if (options.showHelp) {
|
|
148
|
+
printMbCoapHelp(log);
|
|
149
|
+
exitFn(0);
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
return startMbCoap(options, exitFn);
|
|
153
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare function checkHealth(url: string, timeoutMs: number): Promise<boolean>;
|
|
2
|
+
export declare function fetchHealth(url: string, timeoutMs: number): Promise<{
|
|
3
|
+
ok: boolean;
|
|
4
|
+
statusCode: number;
|
|
5
|
+
body: string;
|
|
6
|
+
json?: unknown;
|
|
7
|
+
}>;
|
|
8
|
+
export declare function mbHealthExitCode(url: string, timeoutMs: number): Promise<number>;
|
|
9
|
+
export declare function mbHealthCli(url: string, timeoutMs: number, exitFn?: (code: number) => never | void): Promise<void>;
|
|
10
|
+
export declare function mbHealthMain(exitFn?: (code: number) => never | void, url?: string): Promise<void>;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import http from 'node:http';
|
|
2
|
+
import https from 'node:https';
|
|
3
|
+
const DEFAULT_MB_HEALTH_URL = 'http://localhost:8283/health';
|
|
4
|
+
export async function checkHealth(url, timeoutMs) {
|
|
5
|
+
try {
|
|
6
|
+
const { ok } = await fetchHealth(url, timeoutMs);
|
|
7
|
+
return ok;
|
|
8
|
+
}
|
|
9
|
+
catch {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
export function fetchHealth(url, timeoutMs) {
|
|
14
|
+
return new Promise((resolve) => {
|
|
15
|
+
const parsedUrl = new URL(url);
|
|
16
|
+
const requestImpl = parsedUrl.protocol === 'https:' ? https : http;
|
|
17
|
+
const isHttps = parsedUrl.protocol === 'https:';
|
|
18
|
+
const request = requestImpl.request({
|
|
19
|
+
protocol: parsedUrl.protocol,
|
|
20
|
+
hostname: parsedUrl.hostname,
|
|
21
|
+
port: parsedUrl.port,
|
|
22
|
+
path: parsedUrl.pathname + parsedUrl.search,
|
|
23
|
+
method: 'GET',
|
|
24
|
+
...(isHttps ? { rejectUnauthorized: false } : {}),
|
|
25
|
+
headers: {
|
|
26
|
+
'cache-control': 'no-store',
|
|
27
|
+
'accept': 'application/json',
|
|
28
|
+
},
|
|
29
|
+
}, (response) => {
|
|
30
|
+
const chunks = [];
|
|
31
|
+
response.on('data', (chunk) => {
|
|
32
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
33
|
+
});
|
|
34
|
+
response.on('end', () => {
|
|
35
|
+
const statusCode = response.statusCode ?? 0;
|
|
36
|
+
const ok = statusCode >= 200 && statusCode < 300;
|
|
37
|
+
const body = Buffer.concat(chunks).toString('utf8');
|
|
38
|
+
let json;
|
|
39
|
+
try {
|
|
40
|
+
if (body.trim().length > 0)
|
|
41
|
+
json = JSON.parse(body);
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
json = undefined;
|
|
45
|
+
}
|
|
46
|
+
resolve({ ok, statusCode, body, json });
|
|
47
|
+
});
|
|
48
|
+
response.on('error', () => {
|
|
49
|
+
resolve({ ok: false, statusCode: response.statusCode ?? 0, body: '' });
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
request.on('error', () => resolve({ ok: false, statusCode: 0, body: '' }));
|
|
53
|
+
request.setTimeout(timeoutMs, () => {
|
|
54
|
+
request.destroy();
|
|
55
|
+
resolve({ ok: false, statusCode: 0, body: '' });
|
|
56
|
+
});
|
|
57
|
+
request.end();
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
export async function mbHealthExitCode(url, timeoutMs) {
|
|
61
|
+
try {
|
|
62
|
+
const { ok } = await fetchHealth(url, timeoutMs);
|
|
63
|
+
return ok ? 0 : 1;
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
return 1;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
export async function mbHealthCli(url, timeoutMs, exitFn = process.exit) {
|
|
70
|
+
const { ok, statusCode, body, json } = await fetchHealth(url, timeoutMs);
|
|
71
|
+
if (json !== undefined) {
|
|
72
|
+
console.log(JSON.stringify(json, null, 2));
|
|
73
|
+
}
|
|
74
|
+
else if (body) {
|
|
75
|
+
console.log(body);
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
console.log(JSON.stringify({ ok, statusCode }, null, 2));
|
|
79
|
+
}
|
|
80
|
+
exitFn(ok ? 0 : 1);
|
|
81
|
+
}
|
|
82
|
+
export async function mbHealthMain(exitFn = process.exit, url = DEFAULT_MB_HEALTH_URL) {
|
|
83
|
+
await mbHealthCli(url, 5000, exitFn);
|
|
84
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { Mdns } from '@matterbridge/dgram';
|
|
2
|
+
export declare const MB_MDNS_DEFAULT_INTERVAL_MS = 10000;
|
|
3
|
+
export declare const MB_MDNS_DEFAULT_TIMEOUT_MS = 600000;
|
|
4
|
+
export declare const MB_MDNS_CLEANUP_DELAY_MS = 250;
|
|
5
|
+
export interface MbMdnsOptions {
|
|
6
|
+
showHelp: boolean;
|
|
7
|
+
interfaceName?: string;
|
|
8
|
+
ipv4InterfaceAddress: string;
|
|
9
|
+
ipv6InterfaceAddress: string;
|
|
10
|
+
outgoingIpv4InterfaceAddress?: string;
|
|
11
|
+
outgoingIpv6InterfaceAddress?: string;
|
|
12
|
+
advertiseIntervalMs?: number;
|
|
13
|
+
queryIntervalMs?: number;
|
|
14
|
+
unicast: boolean;
|
|
15
|
+
filters: string[];
|
|
16
|
+
ipFilters: string[];
|
|
17
|
+
disableIpv4: boolean;
|
|
18
|
+
disableIpv6: boolean;
|
|
19
|
+
disableLoopback: boolean;
|
|
20
|
+
disableTimeout: boolean;
|
|
21
|
+
verbose: boolean;
|
|
22
|
+
}
|
|
23
|
+
export interface MbMdnsRuntime {
|
|
24
|
+
mdnsIpv4?: Mdns;
|
|
25
|
+
mdnsIpv6?: Mdns;
|
|
26
|
+
cleanupAndLogAndExit: () => Promise<void>;
|
|
27
|
+
}
|
|
28
|
+
export declare function getMbMdnsHelpText(): string;
|
|
29
|
+
export declare function printMbMdnsHelp(log?: (message: string) => void): void;
|
|
30
|
+
export declare function getMbMdnsOptions(): MbMdnsOptions;
|
|
31
|
+
export declare function sendMdnsQuery(mdns: Mdns, unicast?: boolean): void;
|
|
32
|
+
export declare function advertiseMatterbridgeService(mdns: Mdns, ttl?: number): void;
|
|
33
|
+
export declare function startMbMdns(options: MbMdnsOptions, registerSignalHandlers?: boolean): MbMdnsRuntime;
|
|
34
|
+
export declare function mbMdnsMain(exitFn?: (code: number) => never | void, log?: (message: string) => void): MbMdnsRuntime | undefined;
|
package/dist/mb_mdns.js
ADDED
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
import os from 'node:os';
|
|
2
|
+
import { Mdns, MDNS_MULTICAST_IPV4_ADDRESS, MDNS_MULTICAST_IPV6_ADDRESS, MDNS_MULTICAST_PORT } from '@matterbridge/dgram';
|
|
3
|
+
import { getIntParameter, getParameter, getStringArrayParameter, hasParameter } from '@matterbridge/utils/cli';
|
|
4
|
+
import { excludedInterfaceNamePattern } from '@matterbridge/utils/network';
|
|
5
|
+
import pkg from '../package.json' with { type: 'json' };
|
|
6
|
+
export const MB_MDNS_DEFAULT_INTERVAL_MS = 10000;
|
|
7
|
+
export const MB_MDNS_DEFAULT_TIMEOUT_MS = 600000;
|
|
8
|
+
export const MB_MDNS_CLEANUP_DELAY_MS = 250;
|
|
9
|
+
function defaultConsoleLog(message) {
|
|
10
|
+
console.log(message);
|
|
11
|
+
}
|
|
12
|
+
export function getMbMdnsHelpText() {
|
|
13
|
+
return `Copyright (c) Matterbridge. All rights reserved. Version 2.0.0.
|
|
14
|
+
|
|
15
|
+
Usage: mb_mdns [options...]
|
|
16
|
+
|
|
17
|
+
If no command line is provided, mb_mdns shows all incoming mDNS records on all interfaces (0.0.0.0 and ::).
|
|
18
|
+
|
|
19
|
+
Options:
|
|
20
|
+
-h, --help Show this help message and exit.
|
|
21
|
+
--interfaceName <name> Network interface name to bind to (default all interfaces).
|
|
22
|
+
--ipv4InterfaceAddress <address> IPv4 address of the network interface to bind to (default: 0.0.0.0).
|
|
23
|
+
--ipv6InterfaceAddress <address> IPv6 address of the network interface to bind to (default: ::).
|
|
24
|
+
--outgoingIpv4InterfaceAddress <address> Outgoing IPv4 address of the network interface (default first external address).
|
|
25
|
+
--outgoingIpv6InterfaceAddress <address> Outgoing IPv6 address of the network interface (default first external address).
|
|
26
|
+
--advertise <interval> Enable matterbridge mDNS advertisement each ms (default interval: 10000ms).
|
|
27
|
+
--query <interval> Enable common mDNS services query each ms (default interval: 10000ms).
|
|
28
|
+
--unicast Enable unicast responses for mDNS queries (default: disabled).
|
|
29
|
+
--filter <string...> Filter strings to match in the mDNS record name (default: no filter).
|
|
30
|
+
--ip-filter <string...> Filter strings to match in the mDNS sender ip address (default: no filter).
|
|
31
|
+
--noIpv4 Disable IPv4 mDNS server (default: enabled).
|
|
32
|
+
--noIpv6 Disable IPv6 mDNS server (default: enabled).
|
|
33
|
+
--no-loopback Disable multicast loopback (default: enabled).
|
|
34
|
+
--no-timeout Disable automatic timeout of 10 minutes.
|
|
35
|
+
-d, --debug Enable debug logging (default: disabled).
|
|
36
|
+
-v, --verbose Enable verbose logging (default: disabled).
|
|
37
|
+
-s, --silent Enable silent mode, only log notices, warnings and errors.
|
|
38
|
+
|
|
39
|
+
Examples:
|
|
40
|
+
# Listen for Matter device commissioner service records only on eth0 interface
|
|
41
|
+
mb_mdns --interfaceName eth0 --filter _matterc._udp
|
|
42
|
+
|
|
43
|
+
# Listen for Matter device discovery service records only on eth0 interface
|
|
44
|
+
mb_mdns --interfaceName eth0 --filter _matter._tcp
|
|
45
|
+
|
|
46
|
+
# Listen for Matter commissioner and discovery service records on all interfaces
|
|
47
|
+
mb_mdns --filter _matterc._udp _matter._tcp
|
|
48
|
+
|
|
49
|
+
# Listen for Matter commissioner and discovery service records on all interfaces from specific ipv4 and ipv6 ips
|
|
50
|
+
mb_mdns --filter _matterc._udp _matter._tcp --ip-filter 192.168.69.20 fe80::1077:2e0d:2c91:aa90
|
|
51
|
+
|
|
52
|
+
# Query for mDNS devices every 10s on a specific interface
|
|
53
|
+
mb_mdns --interfaceName eth0 --query
|
|
54
|
+
|
|
55
|
+
# Advertise _matterbridge._tcp.local every 5s with filter
|
|
56
|
+
mb_mdns --advertise 5000 --filter _matterbridge._tcp.local
|
|
57
|
+
|
|
58
|
+
# Query each 5s and listen for _matterbridge._tcp.local service records
|
|
59
|
+
mb_mdns --query 5000 --filter _matterbridge._tcp.local
|
|
60
|
+
`;
|
|
61
|
+
}
|
|
62
|
+
export function printMbMdnsHelp(log = defaultConsoleLog) {
|
|
63
|
+
log(getMbMdnsHelpText());
|
|
64
|
+
}
|
|
65
|
+
export function getMbMdnsOptions() {
|
|
66
|
+
const advertiseIntervalMs = hasParameter('advertise') ? getIntParameter('advertise') || MB_MDNS_DEFAULT_INTERVAL_MS : undefined;
|
|
67
|
+
const queryIntervalMs = hasParameter('query') ? getIntParameter('query') || MB_MDNS_DEFAULT_INTERVAL_MS : undefined;
|
|
68
|
+
return {
|
|
69
|
+
showHelp: hasParameter('h') || hasParameter('help'),
|
|
70
|
+
interfaceName: getParameter('interfaceName'),
|
|
71
|
+
ipv4InterfaceAddress: getParameter('ipv4InterfaceAddress') || '0.0.0.0',
|
|
72
|
+
ipv6InterfaceAddress: getParameter('ipv6InterfaceAddress') || '::',
|
|
73
|
+
outgoingIpv4InterfaceAddress: getParameter('outgoingIpv4InterfaceAddress'),
|
|
74
|
+
outgoingIpv6InterfaceAddress: getParameter('outgoingIpv6InterfaceAddress'),
|
|
75
|
+
advertiseIntervalMs,
|
|
76
|
+
queryIntervalMs,
|
|
77
|
+
unicast: hasParameter('unicast'),
|
|
78
|
+
filters: getStringArrayParameter('filter') ?? [],
|
|
79
|
+
ipFilters: getStringArrayParameter('ip-filter') ?? [],
|
|
80
|
+
disableIpv4: hasParameter('noIpv4'),
|
|
81
|
+
disableIpv6: hasParameter('noIpv6'),
|
|
82
|
+
disableLoopback: hasParameter('no-loopback'),
|
|
83
|
+
disableTimeout: hasParameter('no-timeout'),
|
|
84
|
+
verbose: hasParameter('v') || hasParameter('verbose'),
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
export function sendMdnsQuery(mdns, unicast = false) {
|
|
88
|
+
mdns.log.info('Sending mDNS query for common services...');
|
|
89
|
+
try {
|
|
90
|
+
mdns.sendQuery([
|
|
91
|
+
{ name: '_matterc._udp.local', type: 12, class: 1, unicastResponse: unicast },
|
|
92
|
+
{ name: '_matter._tcp.local', type: 12, class: 1, unicastResponse: unicast },
|
|
93
|
+
{ name: '_matterbridge._tcp.local', type: 12, class: 1, unicastResponse: unicast },
|
|
94
|
+
{ name: '_home-assistant._tcp.local', type: 12, class: 1, unicastResponse: unicast },
|
|
95
|
+
{ name: '_shelly._tcp.local', type: 12, class: 1, unicastResponse: unicast },
|
|
96
|
+
{ name: '_mqtt._tcp.local', type: 12, class: 1, unicastResponse: unicast },
|
|
97
|
+
{ name: '_http._tcp.local', type: 12, class: 1, unicastResponse: unicast },
|
|
98
|
+
{ name: '_googlecast._tcp.local', type: 12, class: 1, unicastResponse: unicast },
|
|
99
|
+
{ name: '_airplay._tcp.local', type: 12, class: 1, unicastResponse: unicast },
|
|
100
|
+
{ name: '_amzn-alexa._tcp.local', type: 12, class: 1, unicastResponse: unicast },
|
|
101
|
+
{ name: '_companion-link._tcp.local', type: 12, class: 1, unicastResponse: unicast },
|
|
102
|
+
{ name: '_hap._tcp.local', type: 12, class: 1, unicastResponse: unicast },
|
|
103
|
+
{ name: '_hap._udp.local', type: 12, class: 1, unicastResponse: unicast },
|
|
104
|
+
{ name: '_ipp._tcp.local', type: 12, class: 1, unicastResponse: unicast },
|
|
105
|
+
{ name: '_ipps._tcp.local', type: 12, class: 1, unicastResponse: unicast },
|
|
106
|
+
{ name: '_meshcop._tcp.local', type: 12, class: 1, unicastResponse: unicast },
|
|
107
|
+
{ name: '_printer._tcp.local', type: 12, class: 1, unicastResponse: unicast },
|
|
108
|
+
{ name: '_raop._tcp.local', type: 12, class: 1, unicastResponse: unicast },
|
|
109
|
+
{ name: '_sleep-proxy._tcp.local', type: 12, class: 1, unicastResponse: unicast },
|
|
110
|
+
{ name: '_ssh._tcp.local', type: 12, class: 1, unicastResponse: unicast },
|
|
111
|
+
{ name: '_services._dns-sd._udp.local', type: 12, class: 1, unicastResponse: unicast },
|
|
112
|
+
]);
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
mdns.log.error(`Error sending mDNS query: ${error.message}`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
export function advertiseMatterbridgeService(mdns, ttl = 120) {
|
|
119
|
+
mdns.log.info(`Sending mDNS advertisement for matterbridge service with TTL ${ttl ? ttl.toString() : 'goodbye'}...`);
|
|
120
|
+
const httpServiceType = '_http._tcp.local';
|
|
121
|
+
const matterbridgeServiceType = '_matterbridge._tcp.local';
|
|
122
|
+
const httpInstanceName = 'matterbridge._http._tcp.local';
|
|
123
|
+
const matterbridgeInstanceName = 'matterbridge._matterbridge._tcp.local';
|
|
124
|
+
const hostName = 'matterbridge.local';
|
|
125
|
+
const port = 8283;
|
|
126
|
+
const ptrHttpServiceTypeRdata = mdns.encodeDnsName(httpServiceType);
|
|
127
|
+
const ptrMatterbridgeServiceTypeRdata = mdns.encodeDnsName(matterbridgeServiceType);
|
|
128
|
+
const ptrHttpInstanceRdata = mdns.encodeDnsName(httpInstanceName);
|
|
129
|
+
const ptrMatterbridgeInstanceRdata = mdns.encodeDnsName(matterbridgeInstanceName);
|
|
130
|
+
const srvRdata = mdns.encodeSrvRdata(0, 0, port, hostName);
|
|
131
|
+
const txtRdata = mdns.encodeTxtRdata([`version=${pkg.version}`, 'path=/']);
|
|
132
|
+
const answers = [
|
|
133
|
+
{ name: '_services._dns-sd._udp.local', rtype: 12, rclass: 1, ttl, rdata: ptrHttpServiceTypeRdata },
|
|
134
|
+
{ name: httpServiceType, rtype: 12, rclass: 1, ttl, rdata: ptrHttpInstanceRdata },
|
|
135
|
+
{ name: '_services._dns-sd._udp.local', rtype: 12, rclass: 1, ttl, rdata: ptrMatterbridgeServiceTypeRdata },
|
|
136
|
+
{ name: matterbridgeServiceType, rtype: 12, rclass: 1, ttl, rdata: ptrMatterbridgeInstanceRdata },
|
|
137
|
+
{ name: httpInstanceName, rtype: 33, rclass: 1 | 32768, ttl, rdata: srvRdata },
|
|
138
|
+
{ name: matterbridgeInstanceName, rtype: 33, rclass: 1 | 32768, ttl, rdata: srvRdata },
|
|
139
|
+
{ name: httpInstanceName, rtype: 16, rclass: 1 | 32768, ttl, rdata: txtRdata },
|
|
140
|
+
{ name: matterbridgeInstanceName, rtype: 16, rclass: 1 | 32768, ttl, rdata: txtRdata },
|
|
141
|
+
];
|
|
142
|
+
const interfaces = os.networkInterfaces();
|
|
143
|
+
let interfaceInfos = mdns.interfaceName ? (interfaces[mdns.interfaceName] ?? []) : [];
|
|
144
|
+
if (interfaceInfos.length === 0) {
|
|
145
|
+
for (const name of Object.keys(interfaces)) {
|
|
146
|
+
if (excludedInterfaceNamePattern.test(name))
|
|
147
|
+
continue;
|
|
148
|
+
const infos = interfaces[name];
|
|
149
|
+
if (infos && infos.length > 0 && !infos[0].internal) {
|
|
150
|
+
interfaceInfos = infos;
|
|
151
|
+
break;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
for (const info of interfaceInfos) {
|
|
156
|
+
if (info.family === 'IPv4' && !info.internal) {
|
|
157
|
+
answers.push({ name: hostName, rtype: 1, rclass: 1 | 32768, ttl, rdata: mdns.encodeA(info.address) });
|
|
158
|
+
}
|
|
159
|
+
else if (info.family === 'IPv6' && !info.internal) {
|
|
160
|
+
answers.push({ name: hostName, rtype: 28, rclass: 1 | 32768, ttl, rdata: mdns.encodeAAAA(info.address) });
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
try {
|
|
164
|
+
mdns.sendResponse(answers);
|
|
165
|
+
}
|
|
166
|
+
catch (error) {
|
|
167
|
+
mdns.log.error(`Error sending mDNS advertisement: ${error.message}`);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
export function startMbMdns(options, registerSignalHandlers = true) {
|
|
171
|
+
let mdnsIpv4QueryInterval;
|
|
172
|
+
let mdnsIpv6QueryInterval;
|
|
173
|
+
let mdnsIpv4AdvertiseInterval;
|
|
174
|
+
let mdnsIpv6AdvertiseInterval;
|
|
175
|
+
let mdnsIpv4;
|
|
176
|
+
let mdnsIpv6;
|
|
177
|
+
async function cleanupAndLogAndExit() {
|
|
178
|
+
clearInterval(mdnsIpv4QueryInterval);
|
|
179
|
+
clearInterval(mdnsIpv6QueryInterval);
|
|
180
|
+
clearInterval(mdnsIpv4AdvertiseInterval);
|
|
181
|
+
clearInterval(mdnsIpv6AdvertiseInterval);
|
|
182
|
+
if (options.advertiseIntervalMs !== undefined) {
|
|
183
|
+
if (mdnsIpv4)
|
|
184
|
+
advertiseMatterbridgeService(mdnsIpv4, 0);
|
|
185
|
+
if (mdnsIpv6)
|
|
186
|
+
advertiseMatterbridgeService(mdnsIpv6, 0);
|
|
187
|
+
}
|
|
188
|
+
await new Promise((resolve) => setTimeout(resolve, MB_MDNS_CLEANUP_DELAY_MS));
|
|
189
|
+
mdnsIpv4?.stop();
|
|
190
|
+
mdnsIpv6?.stop();
|
|
191
|
+
mdnsIpv4?.logDevices();
|
|
192
|
+
mdnsIpv6?.logDevices();
|
|
193
|
+
await new Promise((resolve) => setTimeout(resolve, MB_MDNS_CLEANUP_DELAY_MS));
|
|
194
|
+
}
|
|
195
|
+
if (!options.disableIpv4) {
|
|
196
|
+
mdnsIpv4 = new Mdns('mDNS Server udp4', MDNS_MULTICAST_IPV4_ADDRESS, MDNS_MULTICAST_PORT, 'udp4', true, options.interfaceName, options.ipv4InterfaceAddress, options.outgoingIpv4InterfaceAddress);
|
|
197
|
+
if (options.verbose)
|
|
198
|
+
mdnsIpv4.listNetworkInterfaces();
|
|
199
|
+
if (options.filters.length > 0)
|
|
200
|
+
mdnsIpv4.filters.push(...options.filters);
|
|
201
|
+
if (options.ipFilters.length > 0)
|
|
202
|
+
mdnsIpv4.ipFilters.push(...options.ipFilters);
|
|
203
|
+
mdnsIpv4.on('error', (err) => {
|
|
204
|
+
mdnsIpv4?.log.error(`mDNS udp4 Server error: ${err.message}\n${err.stack}`);
|
|
205
|
+
});
|
|
206
|
+
mdnsIpv4.start();
|
|
207
|
+
mdnsIpv4.on('ready', (address) => {
|
|
208
|
+
if (options.disableLoopback) {
|
|
209
|
+
mdnsIpv4?.socket.setMulticastLoopback(false);
|
|
210
|
+
mdnsIpv4?.log.info('Multicast loopback disabled for mdnsIpv4');
|
|
211
|
+
}
|
|
212
|
+
mdnsIpv4?.log.info(`mdnsIpv4 server ready on ${address.family} ${address.address}:${address.port}`);
|
|
213
|
+
if (options.advertiseIntervalMs !== undefined) {
|
|
214
|
+
advertiseMatterbridgeService(mdnsIpv4);
|
|
215
|
+
mdnsIpv4AdvertiseInterval = setInterval(() => advertiseMatterbridgeService(mdnsIpv4), options.advertiseIntervalMs).unref();
|
|
216
|
+
}
|
|
217
|
+
if (options.queryIntervalMs !== undefined) {
|
|
218
|
+
sendMdnsQuery(mdnsIpv4, options.unicast);
|
|
219
|
+
mdnsIpv4QueryInterval = setInterval(() => sendMdnsQuery(mdnsIpv4, options.unicast), options.queryIntervalMs).unref();
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
if (!options.disableIpv6) {
|
|
224
|
+
mdnsIpv6 = new Mdns('mDNS Server udp6', MDNS_MULTICAST_IPV6_ADDRESS, MDNS_MULTICAST_PORT, 'udp6', true, options.interfaceName, options.ipv6InterfaceAddress, options.outgoingIpv6InterfaceAddress);
|
|
225
|
+
if (options.disableIpv4 && options.verbose)
|
|
226
|
+
mdnsIpv6.listNetworkInterfaces();
|
|
227
|
+
if (options.filters.length > 0)
|
|
228
|
+
mdnsIpv6.filters.push(...options.filters);
|
|
229
|
+
if (options.ipFilters.length > 0)
|
|
230
|
+
mdnsIpv6.ipFilters.push(...options.ipFilters);
|
|
231
|
+
mdnsIpv6.on('error', (err) => {
|
|
232
|
+
mdnsIpv6?.log.error(`mDNS udp6 Server error: ${err.message}\n${err.stack}`);
|
|
233
|
+
});
|
|
234
|
+
mdnsIpv6.start();
|
|
235
|
+
mdnsIpv6.on('ready', (address) => {
|
|
236
|
+
if (options.disableLoopback) {
|
|
237
|
+
mdnsIpv6?.socket.setMulticastLoopback(false);
|
|
238
|
+
mdnsIpv6?.log.info('Multicast loopback disabled for mdnsIpv6');
|
|
239
|
+
}
|
|
240
|
+
mdnsIpv6?.log.info(`mdnsIpv6 server ready on ${address.family} ${address.address}:${address.port}`);
|
|
241
|
+
if (options.advertiseIntervalMs !== undefined) {
|
|
242
|
+
advertiseMatterbridgeService(mdnsIpv6);
|
|
243
|
+
mdnsIpv6AdvertiseInterval = setInterval(() => advertiseMatterbridgeService(mdnsIpv6), options.advertiseIntervalMs).unref();
|
|
244
|
+
}
|
|
245
|
+
if (options.queryIntervalMs !== undefined) {
|
|
246
|
+
sendMdnsQuery(mdnsIpv6, options.unicast);
|
|
247
|
+
mdnsIpv6QueryInterval = setInterval(() => sendMdnsQuery(mdnsIpv6, options.unicast), options.queryIntervalMs).unref();
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
if (registerSignalHandlers) {
|
|
252
|
+
process.on('SIGINT', async () => {
|
|
253
|
+
await cleanupAndLogAndExit();
|
|
254
|
+
});
|
|
255
|
+
process.on('SIGTERM', async () => {
|
|
256
|
+
await cleanupAndLogAndExit();
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
if (!options.disableTimeout) {
|
|
260
|
+
setTimeout(async () => {
|
|
261
|
+
await cleanupAndLogAndExit();
|
|
262
|
+
}, MB_MDNS_DEFAULT_TIMEOUT_MS).unref();
|
|
263
|
+
}
|
|
264
|
+
return {
|
|
265
|
+
mdnsIpv4,
|
|
266
|
+
mdnsIpv6,
|
|
267
|
+
cleanupAndLogAndExit,
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
export function mbMdnsMain(exitFn = process.exit, log = defaultConsoleLog) {
|
|
271
|
+
const options = getMbMdnsOptions();
|
|
272
|
+
if (options.showHelp) {
|
|
273
|
+
printMbMdnsHelp(log);
|
|
274
|
+
exitFn(0);
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
return startMbMdns(options);
|
|
278
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@matterbridge/core",
|
|
3
|
-
"version": "3.7.1-dev-
|
|
3
|
+
"version": "3.7.1-dev-20260326-40eef29",
|
|
4
4
|
"description": "Matterbridge core library",
|
|
5
5
|
"author": "https://github.com/Luligu",
|
|
6
6
|
"homepage": "https://matterbridge.io/",
|
|
@@ -122,10 +122,10 @@
|
|
|
122
122
|
],
|
|
123
123
|
"dependencies": {
|
|
124
124
|
"@matter/main": "0.16.10",
|
|
125
|
-
"@matterbridge/dgram": "3.7.1-dev-
|
|
126
|
-
"@matterbridge/thread": "3.7.1-dev-
|
|
127
|
-
"@matterbridge/types": "3.7.1-dev-
|
|
128
|
-
"@matterbridge/utils": "3.7.1-dev-
|
|
125
|
+
"@matterbridge/dgram": "3.7.1-dev-20260326-40eef29",
|
|
126
|
+
"@matterbridge/thread": "3.7.1-dev-20260326-40eef29",
|
|
127
|
+
"@matterbridge/types": "3.7.1-dev-20260326-40eef29",
|
|
128
|
+
"@matterbridge/utils": "3.7.1-dev-20260326-40eef29",
|
|
129
129
|
"express": "5.2.1",
|
|
130
130
|
"multer": "2.1.1",
|
|
131
131
|
"node-ansi-logger": "3.2.0",
|