From 9df25731073166ca628cf5c72bd309ec38586799 Mon Sep 17 00:00:00 2001 From: Andres Cera Date: Mon, 23 Jun 2025 02:21:49 -0500 Subject: [PATCH 1/3] chore: migrate biome config file to version >2.0 && apply lint/format --- biome.json | 33 +++++++++++++++---- bun.lockb | Bin 59584 -> 59584 bytes package.json | 12 +++++-- server/helpers/exec.ts | 4 +-- server/modules/ingest/srt.ts | 2 +- server/modules/modems/modem-network-scan.ts | 2 +- server/modules/modems/modem-status.ts | 4 +-- server/modules/modems/modem-update-loop.ts | 8 ++--- server/modules/modems/modems.ts | 9 +++-- server/modules/network/dns.ts | 2 +- server/modules/network/gateways.ts | 5 ++- server/modules/network/network-interfaces.ts | 7 ++-- server/modules/remote/remote-relays.ts | 6 ++-- server/modules/remote/remote.ts | 9 ++--- server/modules/streaming/audio.ts | 1 - server/modules/streaming/camlink.ts | 2 +- server/modules/streaming/srtla.ts | 2 +- server/modules/streaming/streaming.ts | 11 +++---- server/modules/system/revisions.ts | 6 ++-- server/modules/system/sensors.ts | 6 ++-- server/modules/system/ssh.ts | 2 +- server/modules/ui/auth.ts | 2 +- server/modules/ui/notifications.ts | 4 +-- server/modules/ui/websocket-server.ts | 12 +++---- server/modules/wifi/wifi-connections.ts | 4 +-- server/modules/wifi/wifi-hotspot.ts | 19 +++++------ server/modules/wifi/wifi-interfaces.ts | 19 +++++------ server/modules/wifi/wifi.ts | 15 ++++----- server/package.json | 4 ++- ui/src/main.ts | 2 +- 30 files changed, 116 insertions(+), 98 deletions(-) diff --git a/biome.json b/biome.json index 403142b..4f8296d 100644 --- a/biome.json +++ b/biome.json @@ -1,13 +1,32 @@ { "files": { - "ignore": [ - "ui/node_modules/**/*", - "ui/vendor/**/*", - "ui/dist/**/*", - "config.json", - "gsm_operator_cache.json" + "includes": [ + "./package.json", + "**/server/**/*", + "**/ui/**/*", + "**/*.json", + "!**/ui/node_modules/**/*", + "!**/ui/vendor/**/*", + "!**/ui/dist/**/*", + "!**/config.json", + "!**/gsm_operator_cache.json" ], - "include": ["server/**/*", "ui/**/*", "*.json"], "ignoreUnknown": true + }, + "linter": { + "rules": { + "style": { + "noParameterAssign": "error", + "useAsConstAssertion": "error", + "useDefaultParameterLast": "error", + "useEnumInitializers": "error", + "useSelfClosingElements": "error", + "useSingleVarDeclarator": "error", + "noUnusedTemplateLiteral": "error", + "useNumberNamespace": "error", + "noInferrableTypes": "error", + "noUselessElse": "error" + } + } } } diff --git a/bun.lockb b/bun.lockb index c12f58471658b731f535db3277cb9138984a4d61..d8605a9b680ba4b3a12a31198e25e62c42f096f6 100755 GIT binary patch delta 35 rcmX?bk@>(y<_!XZlc(_LOb*~;ncTp|vDrhAgK@Gmm%-)+A%|-K=&B3v delta 31 ncmX?bk@>(y<_!XZlP?GuOm5)f*z6(5!8ln!$Y67Wki#_q!zc?E diff --git a/package.json b/package.json index ed7fdde..7f4fda8 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,10 @@ { "name": "@belaui/root", "type": "module", - "workspaces": ["server", "ui"], + "workspaces": [ + "server", + "ui" + ], "dependencies": {}, "devDependencies": { "@types/bun": "^1.2.4", @@ -11,7 +14,7 @@ }, "scripts": { "check": "bun run --bun --filter '*' check && run-p check:*", - "check:biome": "bun x --bun @biomejs/biome check", + "check:biome": "bun x --bun @biomejs/biome check .", "dev": "bun run --bun --filter '*' dev", "fix": "bun run --bun --filter '*' fix", "lint": "bun run --bun --filter '*' lint", @@ -20,5 +23,8 @@ "build:copy-ui": "mkdir -p ./dist/public && rm -rf ./dist/public/* && cp -r ./ui/dist/* ./dist/public/", "build:copy-server": "mkdir -p ./dist && cp ./server/belaUI ./dist/" }, - "trustedDependencies": ["@biomejs/biome", "esbuild"] + "trustedDependencies": [ + "@biomejs/biome", + "esbuild" + ] } diff --git a/server/helpers/exec.ts b/server/helpers/exec.ts index bfdd5ff..0e9a684 100644 --- a/server/helpers/exec.ts +++ b/server/helpers/exec.ts @@ -12,7 +12,7 @@ export async function execPNR(cmd: string) { try { const res = await execP(cmd); return { stdout: res.stdout, stderr: res.stderr, code: 0 }; - } catch (err) { + } catch (_err) { return { stdout: "", stderr: "", code: 1 }; } } @@ -21,7 +21,7 @@ export function checkExecPathSafe(path: string) { try { fs.accessSync(path, fs.constants.R_OK); return true; - } catch (err) { + } catch (_err) { logger.error( `\n\n${path} not found, double check the settings in setup.json`, ); diff --git a/server/modules/ingest/srt.ts b/server/modules/ingest/srt.ts index 847b6ff..2a1f32f 100644 --- a/server/modules/ingest/srt.ts +++ b/server/modules/ingest/srt.ts @@ -24,7 +24,7 @@ function runSLT() { try { const stats = JSON.parse(data.toString("utf8")); ingestStats = `${Math.round(stats.recv.mbitRate * 1024)} Kbps, ${Math.round(stats.link.rtt)} ms RTT`; - } catch (err) {} + } catch (_err) {} }); proc.stderr.on("data", (data) => { diff --git a/server/modules/modems/modem-network-scan.ts b/server/modules/modems/modem-network-scan.ts index fc25777..8738b45 100644 --- a/server/modules/modems/modem-network-scan.ts +++ b/server/modules/modems/modem-network-scan.ts @@ -22,10 +22,10 @@ import { broadcastMsg } from "../ui/websocket-server.ts"; import { mmNetworkScan } from "./mmcli.ts"; import { type AvailableNetwork, - type Modem, getAvailableNetworksForModem, getModem, getModemIds, + type Modem, } from "./modems-state.ts"; function modemBuildAvailableNetworksMessage(id: number) { diff --git a/server/modules/modems/modem-status.ts b/server/modules/modems/modem-status.ts index 72fb21a..41aca3f 100644 --- a/server/modules/modems/modem-status.ts +++ b/server/modules/modems/modem-status.ts @@ -23,11 +23,11 @@ import { broadcastMsg } from "../ui/websocket-server.ts"; import type { ModemId } from "./mmcli.ts"; import { type AvailableNetwork, - type Modem, - type ModemConfig, getAvailableNetworksForModem, getModem, getModemIds, + type Modem, + type ModemConfig, } from "./modems-state.ts"; type ModemsResponseModemStatus = { diff --git a/server/modules/modems/modem-update-loop.ts b/server/modules/modems/modem-update-loop.ts index 7ee3b08..359bd3f 100644 --- a/server/modules/modems/modem-update-loop.ts +++ b/server/modules/modems/modem-update-loop.ts @@ -22,8 +22,8 @@ import { type NetworkManagerConnection, type NetworkManagerConnectionModemConfig, nmConnAdd, - nmConnGetFields, nmConnect, + nmConnGetFields, } from "../network/network-manager.ts"; import { setup } from "../setup.ts"; @@ -32,20 +32,20 @@ import { getGsmConnections, resetGsmConnections } from "./gsm-connections.ts"; import { type ModemId, type ModemInfo, - type SimInfo, mmConvertAccessTech, mmConvertNetworkType, mmConvertNetworkTypes, mmGetModem, mmGetSim, mmList, + type SimInfo, } from "./mmcli.ts"; import { broadcastModems } from "./modem-status.ts"; import { - type Modem, - type ModemConfig, getModem, getModems, + type Modem, + type ModemConfig, removeModem, setModem, } from "./modems-state.ts"; diff --git a/server/modules/modems/modems.ts b/server/modules/modems/modems.ts index 1c2d70d..bab5f66 100644 --- a/server/modules/modems/modems.ts +++ b/server/modules/modems/modems.ts @@ -35,7 +35,7 @@ import { mmSetNetworkTypes } from "./mmcli.ts"; import { modemNetworkScan } from "./modem-network-scan.ts"; import { broadcastModems } from "./modem-status.ts"; import { sanitizeModemConfigForNetworkManager } from "./modem-update-loop.ts"; -import { type ModemConfig, getModem } from "./modems-state.ts"; +import { getModem, type ModemConfig } from "./modems-state.ts"; type ModemConfigMessage = { config: { @@ -71,7 +71,7 @@ async function updateModemConnection( } async function handleModemConfig( - conn: WebSocket, + _conn: WebSocket, msg: ModemConfigMessage["config"], ) { if (!msg.device) { @@ -186,7 +186,10 @@ async function handleModemConfig( broadcastModems({ [msg.device]: true }); } -async function handleModemScan(conn: WebSocket, msg: ModemScanMessage["scan"]) { +async function handleModemScan( + _conn: WebSocket, + msg: ModemScanMessage["scan"], +) { const modemId = Number.parseInt(msg.device, 10); if (!msg || !getModem(modemId)) return; diff --git a/server/modules/network/dns.ts b/server/modules/network/dns.ts index df4b56e..23d148b 100644 --- a/server/modules/network/dns.ts +++ b/server/modules/network/dns.ts @@ -117,7 +117,7 @@ let dnsCache: Record = {}; const dnsResults: Record = {}; try { dnsCache = JSON.parse(fs.readFileSync(DNS_CACHE_FILE, "utf8")); -} catch (err) { +} catch (_err) { logger.warn( "Failed to load the persistent DNS cache, starting with an empty cache", ); diff --git a/server/modules/network/gateways.ts b/server/modules/network/gateways.ts index 48699f9..cf0455f 100644 --- a/server/modules/network/gateways.ts +++ b/server/modules/network/gateways.ts @@ -16,9 +16,8 @@ */ import { execP } from "../../helpers/exec.ts"; -import { getms } from "../../helpers/time.ts"; - import { logger } from "../../helpers/logger.ts"; +import { getms } from "../../helpers/time.ts"; import { notificationBroadcast, notificationRemove, @@ -41,7 +40,7 @@ async function clear_default_gws() { while (true) { await execP("ip route del default"); } - } catch (err) { + } catch (_err) { return; } } diff --git a/server/modules/network/network-interfaces.ts b/server/modules/network/network-interfaces.ts index 8f0c679..aef9b13 100644 --- a/server/modules/network/network-interfaces.ts +++ b/server/modules/network/network-interfaces.ts @@ -17,15 +17,12 @@ /* Network interface list */ import { exec } from "node:child_process"; - +import { EventEmitter } from "node:events"; import type WebSocket from "ws"; -import { getms } from "../../helpers/time.ts"; - import { logger } from "../../helpers/logger.ts"; import { ACTIVE_TO } from "../../helpers/shared.ts"; - -import { EventEmitter } from "node:events"; +import { getms } from "../../helpers/time.ts"; import { notificationBroadcast, notificationRemove, diff --git a/server/modules/remote/remote-relays.ts b/server/modules/remote/remote-relays.ts index f3da912..cd61a29 100644 --- a/server/modules/remote/remote-relays.ts +++ b/server/modules/remote/remote-relays.ts @@ -93,7 +93,7 @@ try { relaysCache = JSON.parse( fs.readFileSync(RELAYS_CACHE_FILE, "utf8"), ) as RelayCache; -} catch (err) { +} catch (_err) { logger.warn("Failed to load the relays cache, starting with an empty cache"); } @@ -135,7 +135,7 @@ export function buildRelaysMsg() { export async function updateCachedRelays(relays: RelayCache | undefined) { try { assert.deepStrictEqual(relays, relaysCache); - } catch (err) { + } catch (_err) { logger.debug("updated the relays cache", relays); relaysCache = relays; await writeTextFile(RELAYS_CACHE_FILE, JSON.stringify(relays)); @@ -182,7 +182,7 @@ function validateRemoteRelays(msg: ValidateRemoteRelaysMessage["relays"]) { if (Object.keys(out.servers).length < 1) return; return out; - } catch (err) { + } catch (_err) { return undefined; } } diff --git a/server/modules/remote/remote.ts b/server/modules/remote/remote.ts index 80239a8..d68c455 100644 --- a/server/modules/remote/remote.ts +++ b/server/modules/remote/remote.ts @@ -52,16 +52,17 @@ import { broadcastMsgLocal, deleteSocketSenderId, getLastActive, + handleMessage, + type Message, markConnectionActive, setSocketSenderId, } from "../ui/websocket-server.ts"; -import { type Message, handleMessage } from "../ui/websocket-server.ts"; import { - type ValidateRemoteRelaysMessage, buildRelaysMsg, handleRemoteRelays, updateCachedRelays, + type ValidateRemoteRelaysMessage, } from "./remote-relays.ts"; type RemoteAuthEncoderMessage = { @@ -78,7 +79,7 @@ const remoteEndpointPath = setup.remote_endpoint_path ?? "/ws/remote"; const remoteTimeout = 5000; const remoteConnectTimeout = 10000; -let remoteWs: WebSocket | undefined = undefined; +let remoteWs: WebSocket | undefined; let remoteStatusHandled = false; export function getRemoteWebSocket() { @@ -194,7 +195,7 @@ async function remoteConnect() { queueUpdateGw(); logger.warn(`remote: DNS lookup failed, using cached address ${host}`); } - } catch (err) { + } catch (_err) { return remoteRetry(); } diff --git a/server/modules/streaming/audio.ts b/server/modules/streaming/audio.ts index daf2f2f..c6bbf7c 100644 --- a/server/modules/streaming/audio.ts +++ b/server/modules/streaming/audio.ts @@ -29,7 +29,6 @@ import { setup } from "../setup.ts"; import { notificationBroadcast } from "../ui/notifications.ts"; import { broadcastMsg } from "../ui/websocket-server.ts"; -import { resolveSrtla } from "./srtla.ts"; import { startError } from "./streaming.ts"; const deviceDir = setup.sound_device_dir ?? "/sys/class/sound"; diff --git a/server/modules/streaming/camlink.ts b/server/modules/streaming/camlink.ts index 5b1ae4d..ee9d37e 100644 --- a/server/modules/streaming/camlink.ts +++ b/server/modules/streaming/camlink.ts @@ -55,7 +55,7 @@ export async function checkCamlinkUsb2() { if (!version.match("3.00")) { foundUsb2 = true; } - } catch (err) {} + } catch (_err) {} } if (foundUsb2) { diff --git a/server/modules/streaming/srtla.ts b/server/modules/streaming/srtla.ts index 57d3f08..72431ce 100644 --- a/server/modules/streaming/srtla.ts +++ b/server/modules/streaming/srtla.ts @@ -39,7 +39,7 @@ export async function resolveSrtla(addr: string, conn: WebSocket) { const res = await dnsCacheResolve(addr, "a"); addrs = res.addrs; fromCache = res.fromCache; - } catch (err) { + } catch (_err) { const senderId = getSocketSenderId(conn) ?? "unknown sender"; startError(conn, `failed to resolve SRTLA addr ${addr}`, senderId); queueUpdateGw(); diff --git a/server/modules/streaming/streaming.ts b/server/modules/streaming/streaming.ts index 5e661e7..f78c563 100644 --- a/server/modules/streaming/streaming.ts +++ b/server/modules/streaming/streaming.ts @@ -21,6 +21,10 @@ import type WebSocket from "ws"; import { validateInteger, validatePortNo } from "../../helpers/number.ts"; import { getConfig, saveConfig } from "../config.ts"; +import { + convertManualToRemoteRelay, + getRelays, +} from "../remote/remote-relays.ts"; import { notificationSend } from "../ui/notifications.ts"; import type { StatusResponseMessage } from "../ui/status.ts"; import { @@ -30,17 +34,12 @@ import { getSocketSenderId, setSocketSenderId, } from "../ui/websocket-server.ts"; - import { - convertManualToRemoteRelay, - getRelays, -} from "../remote/remote-relays.ts"; -import { - DEFAULT_AUDIO_ID, abortAsrcRetry, asrcProbe, asrcScheduleRetry, audioCodecs, + DEFAULT_AUDIO_ID, getAudioDevices, isAsrcRetryScheduled, pipelineSetAsrc, diff --git a/server/modules/system/revisions.ts b/server/modules/system/revisions.ts index d2fd136..ecb482f 100644 --- a/server/modules/system/revisions.ts +++ b/server/modules/system/revisions.ts @@ -28,7 +28,7 @@ const revisions: Record = {}; function readRevision(cmd: string) { try { return execSync(cmd).toString().trim(); - } catch (err) { + } catch (_err) { return "unknown revision"; } } @@ -36,7 +36,7 @@ function readRevision(cmd: string) { export function initRevisions() { try { revisions.belaUI = fs.readFileSync("revision", "utf8"); - } catch (err) { + } catch (_err) { revisions.belaUI = readRevision("git rev-parse --short HEAD"); } @@ -51,7 +51,7 @@ export function initRevisions() { revisions["BELABOX image"] = fs .readFileSync("/etc/belabox_img_version", "utf8") .trim(); - } catch (err) {} + } catch (_err) {} logger.debug("Revisions", revisions); } diff --git a/server/modules/system/sensors.ts b/server/modules/system/sensors.ts index 169f421..0a99075 100644 --- a/server/modules/system/sensors.ts +++ b/server/modules/system/sensors.ts @@ -50,7 +50,7 @@ function updateSensorThermal(id: number, name: string) { ); const socTemp = Number.parseInt(socTempStr, 10) / 1000.0; sensors[name] = `${socTemp.toFixed(1)} °C`; - } catch (err) {} + } catch (_err) {} } function updateSensorsJetson() { @@ -61,7 +61,7 @@ function updateSensorsJetson() { ); const socVoltage = Number.parseInt(socVoltageStr, 10) / 1000.0; sensors["SoC voltage"] = `${socVoltage.toFixed(3)} V`; - } catch (err) {} + } catch (_err) {} try { const socCurrentStr = fs.readFileSync( @@ -70,7 +70,7 @@ function updateSensorsJetson() { ); const socCurrent = Number.parseInt(socCurrentStr, 10) / 1000.0; sensors["SoC current"] = `${socCurrent.toFixed(3)} A`; - } catch (err) {} + } catch (_err) {} updateSensorThermal(0, "SoC temperature"); } diff --git a/server/modules/system/ssh.ts b/server/modules/system/ssh.ts index a36b66d..cf6d29d 100644 --- a/server/modules/system/ssh.ts +++ b/server/modules/system/ssh.ts @@ -129,7 +129,7 @@ export function resetSshPassword(conn: WebSocket) { const password = crypto .randomBytes(24) .toString("base64") - .replace(/[+\/=]/g, "") + .replace(/[+/=]/g, "") .substring(0, 20); const cmd = `printf "${password}\n${password}" | passwd ${setup.ssh_user}`; exec(cmd, (err) => { diff --git a/server/modules/ui/auth.ts b/server/modules/ui/auth.ts index 01a66fd..52079a0 100644 --- a/server/modules/ui/auth.ts +++ b/server/modules/ui/auth.ts @@ -49,7 +49,7 @@ const tempTokens: Record = {}; let persistentTokens: Record; try { persistentTokens = JSON.parse(fs.readFileSync(AUTH_TOKENS_FILE, "utf8")); -} catch (err) { +} catch (_err) { persistentTokens = {}; } diff --git a/server/modules/ui/notifications.ts b/server/modules/ui/notifications.ts index c3f1289..a63d6ca 100644 --- a/server/modules/ui/notifications.ts +++ b/server/modules/ui/notifications.ts @@ -29,10 +29,8 @@ */ import type WebSocket from "ws"; - -import { getms } from "../../helpers/time.ts"; - import { logger } from "../../helpers/logger.ts"; +import { getms } from "../../helpers/time.ts"; import { broadcastMsg, buildMsg, diff --git a/server/modules/ui/websocket-server.ts b/server/modules/ui/websocket-server.ts index ccda51a..17770dd 100644 --- a/server/modules/ui/websocket-server.ts +++ b/server/modules/ui/websocket-server.ts @@ -19,24 +19,22 @@ import { spawnSync } from "node:child_process"; import type WebSocket from "ws"; import { WebSocketServer } from "ws"; - +import { logger } from "../../helpers/logger.ts"; import { getms } from "../../helpers/time.ts"; import { extractMessage } from "../../helpers/types.ts"; - -import { logger } from "../../helpers/logger.ts"; -import { type ModemsMessage, handleModems } from "../modems/modems.ts"; +import { handleModems, type ModemsMessage } from "../modems/modems.ts"; import { - type NetworkInterfaceMessage, handleNetif, + type NetworkInterfaceMessage, } from "../network/network-interfaces.ts"; import { getRemoteWebSocket, setRemoteKey } from "../remote/remote.ts"; import { type BitrateParams, setBitrate } from "../streaming/encoder.ts"; -import { type StartMessage, getIsStreaming } from "../streaming/streaming.ts"; +import { getIsStreaming, type StartMessage } from "../streaming/streaming.ts"; import { start, stop } from "../streaming/streamloop.ts"; import { getLog } from "../system/logs.ts"; import { isUpdating, startSoftwareUpdate } from "../system/software-updates.ts"; import { resetSshPassword, startStopSsh } from "../system/ssh.ts"; -import { type WifiMessage, handleWifi } from "../wifi/wifi.ts"; +import { handleWifi, type WifiMessage } from "../wifi/wifi.ts"; import { type AuthMessage, getPasswordHash, diff --git a/server/modules/wifi/wifi-connections.ts b/server/modules/wifi/wifi-connections.ts index 38907bb..cce5502 100644 --- a/server/modules/wifi/wifi-connections.ts +++ b/server/modules/wifi/wifi-connections.ts @@ -17,13 +17,13 @@ import { type MacAddress, + nmcliParseSep, nmRescan, nmScanResults, - nmcliParseSep, } from "../network/network-manager.ts"; +import { wifiBroadcastState } from "./wifi.ts"; import { wifiDeviceListGetMacAddress } from "./wifi-device-list.ts"; import type { WifiInterface } from "./wifi-interfaces.ts"; -import { wifiBroadcastState } from "./wifi.ts"; const wifiInterfacesByMacAddress: Record = {}; diff --git a/server/modules/wifi/wifi-hotspot.ts b/server/modules/wifi/wifi-hotspot.ts index 767fee3..f967adb 100644 --- a/server/modules/wifi/wifi-hotspot.ts +++ b/server/modules/wifi/wifi-hotspot.ts @@ -21,21 +21,21 @@ import type WebSocket from "ws"; import { logger } from "../../helpers/logger.ts"; import { getms } from "../../helpers/time.ts"; import { - type WifiChannel, - channelFromNM, - isWifiChannelName, - wifiChannels, -} from "./wifi-channels.ts"; - -import { + nmConnect, nmConnGetFields, nmConnSetFields, nmConnSetWifiMacAddress, - nmConnect, nmDisconnect, nmHotspot, } from "../network/network-manager.ts"; import { buildMsg, getSocketSenderId } from "../ui/websocket-server.ts"; +import { wifiBroadcastState, wifiUpdateSavedConns } from "./wifi.ts"; +import { + channelFromNM, + isWifiChannelName, + type WifiChannel, + wifiChannels, +} from "./wifi-channels.ts"; import { getWifiInterfaceByMacAddress, getWifiInterfacesByMacAddress, @@ -43,11 +43,10 @@ import { } from "./wifi-connections.ts"; import { type BaseWifiInterface, - type WifiInterface, getMacAddressForWifiInterface, + type WifiInterface, wifiUpdateDevices, } from "./wifi-interfaces.ts"; -import { wifiBroadcastState, wifiUpdateSavedConns } from "./wifi.ts"; export type WifiHotspotMessage = { hotspot: { diff --git a/server/modules/wifi/wifi-interfaces.ts b/server/modules/wifi/wifi-interfaces.ts index e719582..e132d9c 100644 --- a/server/modules/wifi/wifi-interfaces.ts +++ b/server/modules/wifi/wifi-interfaces.ts @@ -20,19 +20,23 @@ import { getms } from "../../helpers/time.ts"; import { updateMoblinkRelayInterfaces } from "../network/moblink-relay.ts"; import { - NETIF_ERR_HOTSPOT, getNetworkInterfaces, + NETIF_ERR_HOTSPOT, setNetifHotspot, triggerNetworkInterfacesChange, } from "../network/network-interfaces.ts"; import { type ConnectionUUID, type MacAddress, + nmcliParseSep, nmDeviceProp, nmDevices, - nmcliParseSep, } from "../network/network-manager.ts"; - +import { + type WifiNetwork, + wifiBroadcastState, + wifiUpdateSavedConns, +} from "./wifi.ts"; import { addWifiInterface, getWifiInterfaceByMacAddress, @@ -46,15 +50,10 @@ import { wifiDeviceListGetMacAddress, } from "./wifi-device-list.ts"; import { + isHotspot, type WifiHotspot, type WifiInterfaceWithHotspot, - isHotspot, } from "./wifi-hotspot.ts"; -import { - type WifiNetwork, - wifiBroadcastState, - wifiUpdateSavedConns, -} from "./wifi.ts"; export type SSID = string; export type WifiInterfaceId = number; @@ -147,7 +146,7 @@ export async function wifiUpdateDevices() { "GENERAL.VENDOR,GENERAL.PRODUCT,WIFI-PROPERTIES.AP,WIFI-PROPERTIES.5GHZ,WIFI-PROPERTIES.2GHZ", )) as [string, string, string, string, string]; const vendor = prop[0].replace("Corporation", "").trim(); - const pb = prop[1].match(/[\[(](.+)[\])]/); + const pb = prop[1].match(/[[(](.+)[\])]/); const product = pb ? pb[1] : prop[1]; const newInterface = { diff --git a/server/modules/wifi/wifi.ts b/server/modules/wifi/wifi.ts index 0d74e10..4436c1f 100644 --- a/server/modules/wifi/wifi.ts +++ b/server/modules/wifi/wifi.ts @@ -23,23 +23,22 @@ import type WebSocket from "ws"; import { logger } from "../../helpers/logger.ts"; import { extractMessage } from "../../helpers/types.ts"; -import { getWifiChannelMap } from "./wifi-channels.ts"; - import { type ConnectionUUID, nmConnDelete, + nmConnect, nmConnGetFields, nmConnSetWifiMacAddress, - nmConnect, nmConnsGet, - nmDisconnect, nmcliParseSep, + nmDisconnect, } from "../network/network-manager.ts"; import { broadcastMsg, buildMsg, getSocketSenderId, } from "../ui/websocket-server.ts"; +import { getWifiChannelMap } from "./wifi-channels.ts"; import { getWifiInterfaceByMacAddress, getWifiInterfacesByMacAddress, @@ -48,21 +47,21 @@ import { wifiUpdateScanResult, } from "./wifi-connections.ts"; import { - type WifiHotspot, - type WifiHotspotMessage, canHotspot, handleHotspotConn, isHotspot, + type WifiHotspot, + type WifiHotspotMessage, wifiHotspotConfig, wifiHotspotStart, wifiHotspotStop, } from "./wifi-hotspot.ts"; import { type BaseWifiInterface, - type SSID, - type WifiInterfaceId, getMacAddressForWifiInterface, getWifiIdToMacAddress, + type SSID, + type WifiInterfaceId, } from "./wifi-interfaces.ts"; type WifiConnectMessage = { diff --git a/server/package.json b/server/package.json index ecd04e1..967c8cc 100644 --- a/server/package.json +++ b/server/package.json @@ -28,5 +28,7 @@ "format": "bun x --bun @biomejs/biome format --write .", "lint": "bun x --bun @biomejs/biome lint --write ." }, - "trustedDependencies": ["@biomejs/biome"] + "trustedDependencies": [ + "@biomejs/biome" + ] } diff --git a/ui/src/main.ts b/ui/src/main.ts index 2bf1036..d22e024 100644 --- a/ui/src/main.ts +++ b/ui/src/main.ts @@ -30,8 +30,8 @@ import "bootstrap/js/dist/modal.js"; import "bootstrap/js/dist/collapse.js"; import "bootstrap/js/dist/tooltip.js"; -import { initRemoteRelays } from "./modules/remote/remote-relays.ts"; import { initRemote } from "./modules/remote/remote.ts"; +import { initRemoteRelays } from "./modules/remote/remote-relays.ts"; import { initPipelines } from "./modules/streaming/pipelines.ts"; import { initStreamingUi } from "./modules/streaming/streaming.ts"; import { initSoftwareUpdate } from "./modules/system/software-update.ts"; From 0bd49fbfe0ebd525ba2abbc0200008b770164dd9 Mon Sep 17 00:00:00 2001 From: Andres Cera Date: Mon, 23 Jun 2025 01:25:45 -0500 Subject: [PATCH 2/3] add more descriptive rtmp/srt stats types for better readability --- server/modules/ingest/rtmp.ts | 79 ++++++++++++++++++++------------ server/modules/ingest/srt.ts | 86 +++++++++++++++++++++++++---------- 2 files changed, 113 insertions(+), 52 deletions(-) diff --git a/server/modules/ingest/rtmp.ts b/server/modules/ingest/rtmp.ts index 26fbdf2..31b0ac4 100644 --- a/server/modules/ingest/rtmp.ts +++ b/server/modules/ingest/rtmp.ts @@ -1,48 +1,69 @@ import { parseStringPromise as parseXmlStringPromise } from "xml2js"; - import { httpGet } from "../network/internet"; -/* Monitor the RTMP server */ -let rtmpIngestStats: Record = {}; -let prevRtmpBytesIn: Record = {}; -async function updateRtmpStats() { - const statsReq = await httpGet({ +// Define specific types for the RTMP statistics +type StreamBandwidth = `${number} Kbps`; +type StreamName = `RTMP ingest - ${string}`; + +// Define more specific record types +type RtmpStats = Record; +type BytesCounter = Record; + +// Track RTMP server stats +let currentStreamBandwidths: RtmpStats = {}; +let previousBytesCounted: BytesCounter = {}; + +async function updateRtmpStats(): Promise { + const serverResponse = await httpGet({ host: "localhost", port: 1936, }); - if (statsReq.code !== 200) return; - - const newStats: Record = {}; - const bytesIn: Record = {}; - - let stats = await parseXmlStringPromise(statsReq.body); - stats = stats.rtmp.server[0].application[0].live[0]; - - if (stats.stream) { - for (const s of stats.stream) { - const name = `RTMP ingest - ${s.name[0]}`; - bytesIn[name] = Number.parseInt(s.bytes_in[0]); - const bw = Math.round( - ((bytesIn[name] - (prevRtmpBytesIn[name] || 0)) * 8) / 1024, - ); - newStats[name] = `${bw} Kbps`; + + // Exit if request failed + if (serverResponse.code !== 200) return; + + const newStreamBandwidths: RtmpStats = {}; + const currentBytesCounted: BytesCounter = {}; + + // Parse XML response and navigate to live streams data + const xmlData = await parseXmlStringPromise(serverResponse.body); + const liveStreamData = xmlData.rtmp.server[0].application[0].live[0]; + + // Process each active stream if any exist + if (liveStreamData.stream) { + for (const stream of liveStreamData.stream) { + // Create standardized stream name + const streamName = `RTMP ingest - ${stream.name[0]}` as StreamName; + + // Count total bytes received for this stream + currentBytesCounted[streamName] = Number.parseInt(stream.bytes_in[0]); + + // Calculate bandwidth based on difference since last check + const previousBytes = previousBytesCounted[streamName] || 0; + const bytesDifference = currentBytesCounted[streamName] - previousBytes; + const bandwidthKbps = Math.round((bytesDifference * 8) / 1024); + + // Store formatted bandwidth value + newStreamBandwidths[streamName] = + `${bandwidthKbps} Kbps` as StreamBandwidth; } } - rtmpIngestStats = newStats; - prevRtmpBytesIn = bytesIn; + // Update global state with new values + currentStreamBandwidths = newStreamBandwidths; + previousBytesCounted = currentBytesCounted; } -export function initRTMPIngestStats() { +export function initRTMPIngestStats(): void { setInterval(async () => { try { await updateRtmpStats(); - } catch (err) { - console.log(err); + } catch (error) { + console.log(error); } }, 1000); } -export function getRTMPIngestStats() { - return rtmpIngestStats; +export function getRTMPIngestStats(): RtmpStats { + return currentStreamBandwidths; } diff --git a/server/modules/ingest/srt.ts b/server/modules/ingest/srt.ts index 2a1f32f..49a881c 100644 --- a/server/modules/ingest/srt.ts +++ b/server/modules/ingest/srt.ts @@ -1,16 +1,36 @@ import { spawn } from "node:child_process"; -/* Use srt-live-transmit to convert from SRT to UDP (usable by udpsrc in gstreamer), with stats */ -let ingestStats: string | null = null; -function runSLT() { - const proc = spawn( +// Define types for better clarity +type ConnectionStats = `${number} Kbps, ${number} ms RTT` | "" | null; +interface SrtStatsData { + recv: { + mbitRate: number; + }; + link: { + rtt: number; + }; +} + +/** + * Manages SRT to UDP conversion using srt-live-transmit + * Collects and formats connection statistics + */ +let currentConnectionStats: ConnectionStats = null; + +/** + * Starts the SRT Live Transmit process to convert SRT stream to UDP + * Collects and processes statistics about the connection + */ +function startSrtTransmitter(): void { + // Launch the srt-live-transmit process with appropriate parameters + const transmitProcess = spawn( "srt-live-transmit", [ "-st:yes", "-stats-report-frequency:500", "-statspf:json", - "srt://:4000", - "udp://127.0.0.1:4001", + "srt://:4000", // SRT input on port 4000 + "udp://127.0.0.1:4001", // UDP output on localhost:4001 ], { detached: true, @@ -18,30 +38,50 @@ function runSLT() { }, ); - let hasInConn = false; - proc.stdout.on("data", (data) => { - if (!hasInConn) return; + let hasActiveConnection = false; + + // Process statistics output from stdout + transmitProcess.stdout.on("data", (data) => { + if (!hasActiveConnection) return; + try { - const stats = JSON.parse(data.toString("utf8")); - ingestStats = `${Math.round(stats.recv.mbitRate * 1024)} Kbps, ${Math.round(stats.link.rtt)} ms RTT`; - } catch (_err) {} + // Parse JSON stats and format connection information + const statsData: SrtStatsData = JSON.parse(data.toString("utf8")); + const bitrate = Math.round(statsData.recv.mbitRate * 1024); + const roundTripTime = Math.round(statsData.link.rtt); + + currentConnectionStats = `${bitrate} Kbps, ${roundTripTime} ms RTT`; + } catch (_err) { + // Silently handle parsing errors + } }); - proc.stderr.on("data", (data) => { - const datStr = data.toString("utf8"); - if (datStr.match("SRT source disconnected")) { - ingestStats = ""; - hasInConn = false; - } else if (datStr.match("Accepted SRT source connection")) { - hasInConn = true; + // Monitor connection status from stderr + transmitProcess.stderr.on("data", (data) => { + const logMessage = data.toString("utf8"); + + if (logMessage.match("SRT source disconnected")) { + // Handle disconnection + currentConnectionStats = ""; + hasActiveConnection = false; + } else if (logMessage.match("Accepted SRT source connection")) { + // Handle the new connection + hasActiveConnection = true; } }); } -export function initSRTIngest() { - runSLT(); +/** + * Initialize the SRT ingest system + */ +export function initSRTIngest(): void { + startSrtTransmitter(); } -export function getSRTIngestStats() { - return ingestStats; +/** + * Get the current SRT connection statistics + * @returns Current connection stats: bitrate and round-trip time, or empty if disconnected + */ +export function getSRTIngestStats(): ConnectionStats { + return currentConnectionStats; } From c36f53506c77da10907e34e2db3d9b8f54f7370f Mon Sep 17 00:00:00 2001 From: Andres Cera Date: Tue, 24 Jun 2025 23:42:42 -0500 Subject: [PATCH 3/3] build(deps): dependencies were upgrade to latest compatible version --- bun.lockb | Bin 59584 -> 60536 bytes package.json | 8 ++++---- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/bun.lockb b/bun.lockb index d8605a9b680ba4b3a12a31198e25e62c42f096f6..16d805ea64c3348b4b3016bcbe7194afe9836bae 100755 GIT binary patch delta 10831 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 iqd%kdGuG{D1%5}T}45Y9t>;4PAJkfjr delta 10403 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
uxFrls)FW-){sMq~QGT?f~n z!qPB(V=#SbW-*Mefr}rjrD0>t;s9Dc2Ga-bF1T6pH8TH2p(7GvlKaGB$^ls(QY#!<^S3#lh)Nju&w4xy~^7%p&U zz$K7of`y7F!o~?^aTuKfr<(*DCz{11Dwzly!Tko@aMDkLeUo9|B(pezE`ST20{bSL z#gWu78TNs@4sJAsO@Vz=Vc!(9IEJo)i_d_4Q_Z58mQRI!;O>GOM?*4T-!#~lVHPLQ zEpTZT*f-5APNI#|U>~@L;HJXGK(`vGac5=&{D`uvzSdaGhy9KE&U!`E(K@9I&e>Co5ei31g<_?OEEcSF`pLY zSi}OlhS*AxxfZdImLo2rPZ1Z>kXaV7gw`M~rCW&0Xn3AQET@f#E9e`U+E&}hnP<+$P50EZUJLLz*1ltupICO_(hM;F8B@53*aLFIDq`t$gj5i`da{$ z09K$Fr~nFqDuCb9Gk|G=U=+q9F#+HcgE7EZzzpz<+CktDa2PlOv{8?(6XeIJec<*3 zF9AGL8-QH^&%qvGE3gfC5!epA4D0}Q0xdu*z?0bw>;~8?x&wUlVgu#?79bNy2c`o& z;WK#RvXRIEf&o3y6(|Gpfr)@0z-I@uxOWK8HnT^-e+3~3;GyESco-!>1bA3^7;cNGb6+2y2JQwo?GNYx?iy1b6O2)#JV@N&3jp^u2w<1vfCOMDFa#I|uxlp+CLjqI0SpIHc>FEE zcwibZ65z_l0T}>$;jMA~&IN;J`coqBO9eRn9D ze%$FFsC?>IKDnXrU}J<4bGb~SSvz$`<(q%jxqGTDH|~Z)AqonpUAYBdne_g+>~Al8 z3P~i@?i?x!Dmt)JCuOMU?VVAv$~XOacZzMi#TyFjIT8*Cz+mEp|T`l#AA&7h{ZrCFHy(Jy7}7-KFkwLHTdLlX`P^q6Zs? z;A1$I@6iP+-^fd644QuCigRBmL>i-cFpp_y+ny%zdkSvROE0)mVv8=I8GDTf`gbwL zcWTzhe{t~@O~Ya^_EEx@RMnyv@6+;@Xi4QpXIs)G4>xje)k!_vXy0C)6y-+At-3(v zlEZ@Z54Kkwh<<~o2<>5Z)7+@8H7ZcK{qXKxRp#)1=VGA{jRAyL4RfPcp`cucIDPrc z*?Vk9m&!H3YAkPe^4hD5Rqjaye>5nke@^h9F-I|&Uo@fIgrF<8ozAvD+Rqao%L3e$ zpq#zwN?*~^n|q_A-d^|NH_hLt3si17%&zG>EZNO7UT$}Y5qr`)Z#uM37pq)v zxRlv?am-(~ejzK!j;maRaPiIcop&qcAS4(wwyDC0e%hy#aB<=5gE}d(Yg^R*K#|#r z2XwK@wT25xAHH{X+2*4#Boc#-7L|(;<2Ig+`^@#(1Cq!qfDKvFm0mialU8-5lLrc= zRlXE-FiP6uOH&VSaLK}!;^*Pfaj0uVwFn}lQ!MEm%Q!LPn~k*n#2cD zrys5S)*^OF7HVkxQC)y?pQ0_qIXmL!(_K3i{Akfpy%$_0YI2>0cK4 zcPjLvNyqeJJe3{O2PpSR7Mr)0-EMJB@6@WIt;h7ziXQaJu_)tD!Xy}h8b&_4r_0Vzq+BNcWY<5Qu#0Z}nL1wCFGkUZ(~GI+70tA~N?S>|t*)WmYRj*!vf09I z)p>I(E36e&wGB4hy34DmD5)%|$g9gQ22&7TotIyhS7Z$@wN+Jy7v|aOY{k~{^6;8D zRdv?Nssd}ce94ZMo?k&;XH9Lt`t<;jMx0Nj_)7s~zVazuJd;f0&psl{x!=&h_mgO1 zyN5WKX0@ky3^vvkJ(*nBP;Iphnrovy?Q2MlLdmyXsO)`LXZ#@#Zi{(!u1HZ=eJSZi gm$u)%7VXL6wzBs}`MHMPv|AW)Q+~6$etYx319GHbYXATM diff --git a/package.json b/package.json index 7f4fda8..bfef084 100644 --- a/package.json +++ b/package.json @@ -7,10 +7,10 @@ ], "dependencies": {}, "devDependencies": { - "@types/bun": "^1.2.4", - "globals": "^15.15.0", - "npm-run-all2": "^7.0.2", - "typescript": "^5.8.2" + "@types/bun": "^1.2.17", + "globals": "^16.2.0", + "npm-run-all2": "^8.0.4", + "typescript": "^5.8.3" }, "scripts": { "check": "bun run --bun --filter '*' check && run-p check:*",