fix(mdns-scanner): browser par type au lieu d'une recherche globale

bonjour-service exige un `type` dans BrowserConfig ; il n'existe pas
d'option pour écouter "tous les services". Le scanner instancie
maintenant un browser par type mDNS courant en homelab (http, ssh,
smb, airplay, googlecast, homekit, ipp, etc.) et agrège les
annonces par IP via une fonction `ingest` partagée.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-15 14:21:45 +02:00
parent 1273368c9a
commit f7ca7a0929

View File

@@ -1,13 +1,36 @@
import { Bonjour } from 'bonjour-service'; import { Bonjour, type Service } from 'bonjour-service';
import { env } from '@/config/env'; import { env } from '@/config/env';
import type { DiscoveryResult, DiscoveryTarget, Scanner } from '../types'; import type { DiscoveryResult, DiscoveryTarget, Scanner } from '../types';
/** /**
* Découverte via mDNS / DNS-SD (_services._dns-sd._udp.local). * Découverte via mDNS / DNS-SD.
* Écoute pendant `timeout` ms et agrège les annonces reçues. * `bonjour-service` exige un `type` par browser : on en lance un par type
* courant en homelab et on agrège les annonces reçues pendant `timeout` ms.
*/ */
const DEFAULT_TIMEOUT_MS = 4000; const DEFAULT_TIMEOUT_MS = 4000;
// Types mDNS les plus fréquents en homelab (sans le préfixe `_` ni le suffixe `._tcp/._udp`)
const COMMON_SERVICE_TYPES: Array<{ type: string; protocol: 'tcp' | 'udp' }> = [
{ type: 'http', protocol: 'tcp' },
{ type: 'https', protocol: 'tcp' },
{ type: 'ssh', protocol: 'tcp' },
{ type: 'sftp-ssh', protocol: 'tcp' },
{ type: 'smb', protocol: 'tcp' },
{ type: 'nfs', protocol: 'tcp' },
{ type: 'afpovertcp', protocol: 'tcp' },
{ type: 'workstation', protocol: 'tcp' },
{ type: 'device-info', protocol: 'tcp' },
{ type: 'printer', protocol: 'tcp' },
{ type: 'ipp', protocol: 'tcp' },
{ type: 'ipps', protocol: 'tcp' },
{ type: 'airplay', protocol: 'tcp' },
{ type: 'raop', protocol: 'tcp' },
{ type: 'googlecast', protocol: 'tcp' },
{ type: 'homekit', protocol: 'tcp' },
{ type: 'hap', protocol: 'tcp' },
{ type: 'spotify-connect', protocol: 'tcp' },
];
export const mdnsScanner: Scanner = { export const mdnsScanner: Scanner = {
kind: 'mdns', kind: 'mdns',
label: 'mDNS / Bonjour', label: 'mDNS / Bonjour',
@@ -17,11 +40,14 @@ export const mdnsScanner: Scanner = {
const bonjour = new Bonjour(); const bonjour = new Bonjour();
const byIp = new Map<string, DiscoveryResult>(); const byIp = new Map<string, DiscoveryResult>();
const browser = bonjour.find({}); const ingest = (svc: Service) => {
browser.on('up', (svc) => {
const ips = (svc.addresses ?? []).filter((a) => a && !a.includes(':')); // IPv4 const ips = (svc.addresses ?? []).filter((a) => a && !a.includes(':')); // IPv4
for (const ip of ips) { for (const ip of ips) {
const existing = byIp.get(ip) ?? { ipAddress: ip, hostname: svc.host, services: [] }; const existing = byIp.get(ip) ?? {
ipAddress: ip,
hostname: svc.host,
services: [],
};
existing.services!.push({ existing.services!.push({
name: svc.name, name: svc.name,
type: `_${svc.type}._${svc.protocol}.local`, type: `_${svc.type}._${svc.protocol}.local`,
@@ -29,10 +55,16 @@ export const mdnsScanner: Scanner = {
}); });
byIp.set(ip, existing); byIp.set(ip, existing);
} }
};
const browsers = COMMON_SERVICE_TYPES.map(({ type, protocol }) => {
const browser = bonjour.find({ type, protocol });
browser.on('up', ingest);
return browser;
}); });
await new Promise((r) => setTimeout(r, DEFAULT_TIMEOUT_MS)); await new Promise((r) => setTimeout(r, DEFAULT_TIMEOUT_MS));
browser.stop(); for (const b of browsers) b.stop();
bonjour.destroy(); bonjour.destroy();
return [...byIp.values()]; return [...byIp.values()];