proxy backend
This commit is contained in:
168
backend/src/services/discoveryService.ts
Normal file
168
backend/src/services/discoveryService.ts
Normal file
@@ -0,0 +1,168 @@
|
||||
import Bonjour from 'bonjour-service';
|
||||
import { WledClient } from '../wled/client';
|
||||
|
||||
export interface DiscoveredDevice {
|
||||
name: string;
|
||||
ipAddress: string;
|
||||
port: number;
|
||||
host: string;
|
||||
type: string;
|
||||
}
|
||||
|
||||
export class DiscoveryService {
|
||||
private bonjour: Bonjour;
|
||||
|
||||
constructor() {
|
||||
this.bonjour = new Bonjour();
|
||||
}
|
||||
|
||||
/**
|
||||
* Discover WLED devices on the network using mDNS
|
||||
* @param timeout - How long to search for devices (in milliseconds)
|
||||
* @returns Array of discovered devices
|
||||
*/
|
||||
async discoverDevices(timeout: number = 5000): Promise<DiscoveredDevice[]> {
|
||||
return new Promise((resolve) => {
|
||||
const discoveredDevices: DiscoveredDevice[] = [];
|
||||
const seenAddresses = new Set<string>();
|
||||
|
||||
// Search for WLED devices using mDNS service discovery
|
||||
// WLED devices advertise themselves as _http._tcp
|
||||
const browser = this.bonjour.find({ type: 'http' });
|
||||
|
||||
browser.on('up', (service) => {
|
||||
// WLED devices typically have "wled" in their name or txt records
|
||||
const isWled =
|
||||
service.name?.toLowerCase().includes('wled') ||
|
||||
service.txt?.ver?.startsWith('0.') || // WLED version format
|
||||
service.txt?.name !== undefined;
|
||||
|
||||
if (isWled && service.addresses && service.addresses.length > 0) {
|
||||
// Use the first IPv4 address
|
||||
const ipv4Address = service.addresses.find((addr: string) =>
|
||||
addr.includes('.') && !addr.startsWith('169.254')
|
||||
);
|
||||
|
||||
if (ipv4Address && !seenAddresses.has(ipv4Address)) {
|
||||
seenAddresses.add(ipv4Address);
|
||||
|
||||
discoveredDevices.push({
|
||||
name: service.name || service.host || 'Unknown WLED Device',
|
||||
ipAddress: ipv4Address,
|
||||
port: service.port || 80,
|
||||
host: service.host || ipv4Address,
|
||||
type: service.type || 'http',
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Stop searching after timeout
|
||||
setTimeout(() => {
|
||||
browser.stop();
|
||||
resolve(discoveredDevices);
|
||||
}, timeout);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Scan a range of IP addresses for WLED devices
|
||||
* This is a fallback method when mDNS doesn't work
|
||||
* @param baseIp - Base IP address (e.g., "192.168.1")
|
||||
* @param startRange - Start of range (e.g., 1)
|
||||
* @param endRange - End of range (e.g., 254)
|
||||
* @returns Array of discovered devices
|
||||
*/
|
||||
async scanIpRange(
|
||||
baseIp: string,
|
||||
startRange: number = 1,
|
||||
endRange: number = 254
|
||||
): Promise<DiscoveredDevice[]> {
|
||||
const discoveredDevices: DiscoveredDevice[] = [];
|
||||
const promises: Promise<void>[] = [];
|
||||
|
||||
for (let i = startRange; i <= endRange; i++) {
|
||||
const ipAddress = `${baseIp}.${i}`;
|
||||
|
||||
promises.push(
|
||||
(async () => {
|
||||
try {
|
||||
const client = new WledClient(ipAddress, 80);
|
||||
const info = await Promise.race([
|
||||
client.getInfo(),
|
||||
new Promise((_, reject) =>
|
||||
setTimeout(() => reject(new Error('Timeout')), 2000)
|
||||
),
|
||||
]) as any;
|
||||
|
||||
if (info && info.name) {
|
||||
discoveredDevices.push({
|
||||
name: info.name,
|
||||
ipAddress,
|
||||
port: 80,
|
||||
host: ipAddress,
|
||||
type: 'wled',
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
// Device not found or not responding, ignore
|
||||
}
|
||||
})()
|
||||
);
|
||||
|
||||
// Process in batches of 10 to avoid overwhelming the network
|
||||
if (promises.length >= 10) {
|
||||
await Promise.all(promises);
|
||||
promises.length = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for remaining promises
|
||||
if (promises.length > 0) {
|
||||
await Promise.all(promises);
|
||||
}
|
||||
|
||||
return discoveredDevices;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify a device at a specific IP address is a WLED device
|
||||
* @param ipAddress - IP address to check
|
||||
* @param port - Port number (default: 80)
|
||||
* @returns Device info if it's a WLED device, null otherwise
|
||||
*/
|
||||
async verifyDevice(
|
||||
ipAddress: string,
|
||||
port: number = 80
|
||||
): Promise<DiscoveredDevice | null> {
|
||||
try {
|
||||
const client = new WledClient(ipAddress, port);
|
||||
const info = await Promise.race([
|
||||
client.getInfo(),
|
||||
new Promise((_, reject) =>
|
||||
setTimeout(() => reject(new Error('Timeout')), 3000)
|
||||
),
|
||||
]) as any;
|
||||
|
||||
if (info && info.name) {
|
||||
return {
|
||||
name: info.name,
|
||||
ipAddress,
|
||||
port,
|
||||
host: ipAddress,
|
||||
type: 'wled',
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
} catch (err) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.bonjour.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
export const discoveryService = new DiscoveryService();
|
||||
Reference in New Issue
Block a user