import "server-only"; import { z } from "zod"; import { connectWifiFromHelper, disconnectWifiFromHelper, getNetworkStatusFromHelper, isNetworkHelperUnavailableError, type NetworkHelperError, scanWifiNetworksFromHelper, } from "@/lib/server/modules/network/helper-client"; import { logServerAction, withServerTiming } from "@/lib/server/logging/logger"; import { getSystemMetricsSnapshot } from "@/lib/server/modules/system/service"; import type { ConnectNetworkRequest, DisconnectNetworkRequest, NetworkServiceErrorCode, NetworkStatus, WifiAccessPoint, } from "@/lib/shared/contracts/network"; type RequestContext = { requestId?: string; }; type NetworkSource = "helper" | "fallback"; type NetworkStatusResult = { data: NetworkStatus; source: NetworkSource; }; type WifiNetworksResult = { data: WifiAccessPoint[]; source: NetworkSource; }; const NETWORK_FALLBACK_CACHE_TTL_MS = 30_000; const FALLBACK_LOG_COOLDOWN_MS = 60_000; let fallbackStatusCache: | { value: NetworkStatus; expiresAt: number; } | null = null; let fallbackWifiNetworksCache: | { value: WifiAccessPoint[]; expiresAt: number; } | null = null; const fallbackLogAtByAction = new Map(); const connectNetworkSchema = z.object({ ssid: z.string().trim().min(1).max(128), password: z.string().min(1).max(128).optional(), }); const disconnectNetworkSchema = z.object({ iface: z.string().trim().min(1).max(64).optional(), }); export class NetworkServiceError extends Error { readonly code: NetworkServiceErrorCode; readonly statusCode: number; constructor( message: string, options?: { code?: NetworkServiceErrorCode; statusCode?: number; cause?: unknown; }, ) { super(message, { cause: options?.cause, }); this.name = "NetworkServiceError"; this.code = options?.code ?? "internal_error"; this.statusCode = options?.statusCode ?? 500; } } function mapFallbackStatusFromMetrics(snapshot: Awaited>) { return { connected: snapshot.wifi.connected, iface: snapshot.wifi.iface, ssid: snapshot.wifi.ssid, ipv4: snapshot.wifi.ipv4, signalPercent: snapshot.wifi.signalPercent, } satisfies NetworkStatus; } function mapFallbackNetworksFromMetrics(snapshot: Awaited>) { return snapshot.wifi.availableNetworks .map((network) => ({ ssid: network.ssid, bssid: null, signalPercent: network.qualityPercent, channel: network.channel, frequencyMhz: null, security: network.security, })) .sort((left, right) => (right.signalPercent ?? 0) - (left.signalPercent ?? 0)); } function readCachedFallbackStatus() { if (!fallbackStatusCache) return null; if (fallbackStatusCache.expiresAt <= Date.now()) { fallbackStatusCache = null; return null; } return fallbackStatusCache.value; } function readCachedFallbackWifiNetworks() { if (!fallbackWifiNetworksCache) return null; if (fallbackWifiNetworksCache.expiresAt <= Date.now()) { fallbackWifiNetworksCache = null; return null; } return fallbackWifiNetworksCache.value; } function writeFallbackCaches(snapshot: Awaited>) { const expiresAt = Date.now() + NETWORK_FALLBACK_CACHE_TTL_MS; fallbackStatusCache = { value: mapFallbackStatusFromMetrics(snapshot), expiresAt, }; fallbackWifiNetworksCache = { value: mapFallbackNetworksFromMetrics(snapshot), expiresAt, }; } function logFallbackNotice(options: { action: string; requestId?: string; message: string; error?: unknown; }) { const now = Date.now(); const lastLoggedAt = fallbackLogAtByAction.get(options.action) ?? 0; if (now - lastLoggedAt < FALLBACK_LOG_COOLDOWN_MS) { return; } fallbackLogAtByAction.set(options.action, now); logServerAction({ level: "warn", layer: "service", action: options.action, status: "info", requestId: options.requestId, message: options.message, error: options.error, }); } function mapKnownServiceError(error: unknown) { if (error instanceof NetworkServiceError) return error; if (error instanceof Error && error.name === "NetworkHelperError") { const helperError = error as NetworkHelperError; return new NetworkServiceError(helperError.message, { code: helperError.code, statusCode: helperError.statusCode, cause: error, }); } return new NetworkServiceError("Network request failed", { code: "internal_error", statusCode: 500, cause: error, }); } function validateConnectInput(input: unknown) { const parsed = connectNetworkSchema.safeParse(input); if (!parsed.success) { throw new NetworkServiceError("Invalid network connect payload", { code: "invalid_request", statusCode: 400, cause: parsed.error, }); } return parsed.data satisfies ConnectNetworkRequest; } function validateDisconnectInput(input: unknown) { const parsed = disconnectNetworkSchema.safeParse(input ?? {}); if (!parsed.success) { throw new NetworkServiceError("Invalid network disconnect payload", { code: "invalid_request", statusCode: 400, cause: parsed.error, }); } return parsed.data satisfies DisconnectNetworkRequest; } export async function getNetworkStatus(context?: RequestContext): Promise { return withServerTiming( { level: "debug", layer: "service", action: "network.status.get", requestId: context?.requestId, }, async () => { try { const data = await getNetworkStatusFromHelper({ requestId: context?.requestId, }); return { data, source: "helper", } satisfies NetworkStatusResult; } catch (error) { if (!isNetworkHelperUnavailableError(error)) { throw mapKnownServiceError(error); } const cached = readCachedFallbackStatus(); if (cached) { logFallbackNotice({ action: "network.status.get.fallback", requestId: context?.requestId, message: "DBus helper unavailable; using in-memory fallback cache", }); return { data: cached, source: "fallback", } satisfies NetworkStatusResult; } logFallbackNotice({ action: "network.status.get.fallback", requestId: context?.requestId, message: "DBus helper unavailable; refreshing fallback cache from system metrics", error, }); const snapshot = await getSystemMetricsSnapshot(); writeFallbackCaches(snapshot); return { data: readCachedFallbackStatus() ?? mapFallbackStatusFromMetrics(snapshot), source: "fallback", } satisfies NetworkStatusResult; } }, ); } export async function getWifiNetworks( context?: RequestContext, ): Promise { return withServerTiming( { level: "debug", layer: "service", action: "network.networks.get", requestId: context?.requestId, }, async () => { try { const data = await scanWifiNetworksFromHelper({ requestId: context?.requestId, }); return { data, source: "helper", } satisfies WifiNetworksResult; } catch (error) { if (!isNetworkHelperUnavailableError(error)) { throw mapKnownServiceError(error); } const cached = readCachedFallbackWifiNetworks(); if (cached) { logFallbackNotice({ action: "network.networks.get.fallback", requestId: context?.requestId, message: "DBus helper unavailable; using in-memory fallback cache", }); return { data: cached, source: "fallback", } satisfies WifiNetworksResult; } logFallbackNotice({ action: "network.networks.get.fallback", requestId: context?.requestId, message: "DBus helper unavailable; refreshing fallback cache from system metrics", error, }); const snapshot = await getSystemMetricsSnapshot(); writeFallbackCaches(snapshot); return { data: readCachedFallbackWifiNetworks() ?? mapFallbackNetworksFromMetrics(snapshot), source: "fallback", } satisfies WifiNetworksResult; } }, ); } export async function connectNetwork( input: unknown, context?: RequestContext, ): Promise { const validated = validateConnectInput(input); return withServerTiming( { layer: "service", action: "network.connect.post", requestId: context?.requestId, meta: { ssid: validated.ssid, }, }, async () => { try { return await connectWifiFromHelper(validated, { requestId: context?.requestId, }); } catch (error) { throw mapKnownServiceError(error); } }, ); } export async function disconnectNetwork( input: unknown, context?: RequestContext, ): Promise { const validated = validateDisconnectInput(input); return withServerTiming( { layer: "service", action: "network.disconnect.post", requestId: context?.requestId, meta: { iface: validated.iface ?? null, }, }, async () => { try { return await disconnectWifiFromHelper(validated, { requestId: context?.requestId, }); } catch (error) { throw mapKnownServiceError(error); } }, ); }