matterbridge
Version:
Matterbridge plugin manager for Matter
264 lines • 10.6 kB
JavaScript
/**
* @description This file contains the network functions.
* @file network.ts
* @author Luca Liguori
* @created 2024-02-17
* @version 1.0.1
* @license Apache-2.0
*
* Copyright 2024, 2025, 2026 Luca Liguori.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Node.js modules
import os from 'node:os';
// AnsiLogger module
import { AnsiLogger, BLUE, CYAN, nf } from 'node-ansi-logger';
/**
* Retrieves the IPv4 address of the first non-internal network interface.
*
* @returns {string | undefined} The IPv4 address of the selected network interface, or undefined if not found.
*/
export function getIpv4InterfaceAddress() {
let ipv4Address;
const networkInterfaces = os.networkInterfaces();
// console.log('Available Network Interfaces:', networkInterfaces);
for (const interfaceDetails of Object.values(networkInterfaces)) {
if (!interfaceDetails) {
break;
}
for (const detail of interfaceDetails) {
if (detail.family === 'IPv4' && !detail.internal && ipv4Address === undefined) {
ipv4Address = detail.address;
}
}
if (ipv4Address !== undefined) {
break;
}
}
// console.log('Selected Network Interfaces:', ipv4Address);
return ipv4Address;
}
/**
* Retrieves the IPv6 address of the first non-internal network interface.
*
* @returns {string | undefined} The IPv4 address of the selected network interface, or undefined if not found.
*/
export function getIpv6InterfaceAddress() {
let ipv6Address;
const networkInterfaces = os.networkInterfaces();
// console.log('Available Network Interfaces:', networkInterfaces);
for (const interfaceDetails of Object.values(networkInterfaces)) {
if (!interfaceDetails) {
break;
}
for (const detail of interfaceDetails) {
if (detail.family === 'IPv6' && !detail.internal && ipv6Address === undefined) {
ipv6Address = detail.address;
}
}
if (ipv6Address !== undefined) {
break;
}
}
// console.log('Selected Network Interfaces:', ipv6Address);
return ipv6Address;
}
/**
* Retrieves the mac address of the first non-internal network interface.
*
* @returns {string | undefined} The mac address, or undefined if not found.
*/
export function getMacAddress() {
let macAddress;
const networkInterfaces = os.networkInterfaces();
// console.log('Available Network Interfaces:', networkInterfaces);
for (const interfaceDetails of Object.values(networkInterfaces)) {
if (!interfaceDetails) {
break;
}
for (const detail of interfaceDetails) {
if (!detail.internal && macAddress === undefined) {
macAddress = detail.mac;
}
}
if (macAddress !== undefined) {
break;
}
}
return macAddress;
}
/**
* Logs the available network interfaces and their details.
*
* @returns {void}
*/
export function logInterfaces() {
const log = new AnsiLogger({ logName: 'MatterbridgeUtils', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: "info" /* LogLevel.INFO */ });
log.logLevel = "info" /* LogLevel.INFO */;
log.logName = 'LogInterfaces';
log.info('Available Network Interfaces:');
const availableAddresses = Object.entries(os.networkInterfaces());
for (const [ifaceName, ifaces] of availableAddresses) {
if (ifaces && ifaces.length > 0) {
log.info(`Network interface ${BLUE}${ifaceName}${nf}:`);
ifaces.forEach((iface) => {
log.info(`- ${CYAN}${iface.family}${nf} address ${CYAN}${iface.address}${nf} netmask ${CYAN}${iface.netmask}${nf} mac ${CYAN}${iface.mac}${nf}` +
`${iface.scopeid ? ` scopeid ${CYAN}${iface.scopeid}${nf}` : ''}${iface.cidr ? ` cidr ${CYAN}${iface.cidr}${nf}` : ''} ${CYAN}${iface.internal ? 'internal' : 'external'}${nf}`);
});
}
}
}
/**
* Resolves the given hostname to an IP address.
*
* @param {string} hostname - The hostname to resolve.
* @param {0 | 4 | 6} [family] - The address family to use (0 for any, 4 for IPv4, 6 for IPv6). Default is 4.
* @returns {Promise<string | null>} - A promise that resolves to the IP address or null if not found.
*
* @remarks
* This function uses DNS lookup to resolve the hostname, which can take some time to complete.
*/
export async function resolveHostname(hostname, family = 4) {
const dns = await import('node:dns');
try {
const addresses = await dns.promises.lookup(hostname.toLowerCase() /* + '.local'*/, { family });
return addresses.address;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
}
catch (error) {
return null;
}
}
/**
* Retrieves the version of an npm package from the npm registry.
*
* @param {string} packageName - The name of the npm package.
* @param {string} [tag] - The tag of the package version to retrieve (default is 'latest').
* @param {number} [timeout] - The timeout duration in milliseconds (default is 10000ms).
* @returns {Promise<string>} A promise that resolves to the version string of the package.
* @throws {Error} If the request fails or the tag is not found.
*/
export async function getNpmPackageVersion(packageName, tag = 'latest', timeout = 10000) {
const https = await import('node:https');
return new Promise((resolve, reject) => {
const url = `https://registry.npmjs.org/${packageName}`;
const controller = new AbortController();
const timeoutId = setTimeout(() => {
controller.abort();
reject(new Error(`Request timed out after ${timeout / 1000} seconds`));
}, timeout);
const req = https.get(url, { signal: controller.signal }, (res) => {
let data = '';
if (res.statusCode !== 200) {
clearTimeout(timeoutId);
res.resume(); // Discard response data to close the socket properly
req.destroy(); // Forcefully close the request
reject(new Error(`Failed to fetch data. Status code: ${res.statusCode}`));
return;
}
res.on('data', (chunk) => {
data += chunk;
});
res.on('end', () => {
clearTimeout(timeoutId);
try {
const jsonData = JSON.parse(data);
// console.log(`Package ${packageName} tag ${tag}`, jsonData);
const version = jsonData['dist-tags']?.[tag];
if (version) {
resolve(version);
}
else {
reject(new Error(`Tag "${tag}" not found for package "${packageName}"`));
}
}
catch (error) {
reject(new Error(`Failed to parse response JSON: ${error instanceof Error ? error.message : error}`));
}
});
});
req.on('error', (error) => {
clearTimeout(timeoutId);
reject(new Error(`Request failed: ${error instanceof Error ? error.message : error}`));
});
});
}
/**
* Retrieves a file from the public directory of the Matterbridge GitHub repository.
*
* @param {string} branch - The branch from which to fetch the file.
* @param {string} file - The file path to fetch.
* @param {number} [timeout] - The timeout duration in milliseconds (default is 10000ms).
* @returns {Promise<Record<string, boolean | string | number>>} A promise that resolves to the parsed JSON object from the file.
* @throws {Error} If the request fails or the JSON parsing fails.
*/
export async function getGitHubUpdate(branch, file, timeout = 10000) {
const https = await import('node:https');
return new Promise((resolve, reject) => {
const url = `https://raw.githubusercontent.com/Luligu/matterbridge/${branch}/public/${file}`;
const controller = new AbortController();
// istanbul ignore next
const timeoutId = setTimeout(() => {
controller.abort();
reject(new Error(`Request timed out after ${timeout / 1000} seconds`));
}, timeout);
const req = https.get(url, { signal: controller.signal }, (res) => {
let data = '';
if (res.statusCode !== 200) {
clearTimeout(timeoutId);
res.resume(); // Discard response data to close the socket properly
req.destroy(); // Forcefully close the request
reject(new Error(`Failed to fetch data. Status code: ${res.statusCode}`));
return;
}
res.on('data', (chunk) => {
data += chunk;
});
res.on('end', () => {
clearTimeout(timeoutId);
try {
const jsonData = JSON.parse(data);
resolve(jsonData);
}
catch (error) {
reject(new Error(`Failed to parse response JSON: ${error instanceof Error ? error.message : error}`));
}
});
});
// istanbul ignore next
req.on('error', (error) => {
clearTimeout(timeoutId);
reject(new Error(`Request failed: ${error instanceof Error ? error.message : error}`));
});
});
}
/**
* Retrieves the path to the global Node.js modules directory.
*
* @returns {Promise<string>} A promise that resolves to the path of the global Node.js modules directory.
*/
export async function getGlobalNodeModules() {
const { exec } = await import('node:child_process');
return new Promise((resolve, reject) => {
exec('npm root -g', (error, stdout) => {
if (error) {
reject(error);
}
else {
resolve(stdout.trim());
}
});
});
}
//# sourceMappingURL=network.js.map