UNPKG

lifxlan

Version:

TypeScript library for controlling LIFX products over LAN

121 lines 4.6 kB
import { NO_TARGET, PORT } from './constants/index.js'; import { convertSerialNumberToTarget, PromiseWithResolvers } from './utils/index.js'; import { AbortError, ValidationError } from './errors.js'; export function Device(config) { if (!config.address) { throw new ValidationError('address', config.address, 'is required'); } if (config.port !== undefined && (config.port < 1 || config.port > 65535)) { throw new ValidationError('port', config.port, 'must be between 1 and 65535'); } if (config.sequence !== undefined && (config.sequence < 0 || config.sequence > 254)) { throw new ValidationError('sequence', config.sequence, 'must be between 0 and 254'); } const device = config; device.port ??= PORT; device.target ??= config.serialNumber ? convertSerialNumberToTarget(config.serialNumber) : NO_TARGET; device.sequence = config.sequence ?? 0; return device; } export function Devices(options = {}) { const defaultTimeoutMs = options.defaultTimeoutMs ?? 3000; const onAdded = options.onAdded; const onChanged = options.onChanged; const onRemoved = options.onRemoved; const knownDevices = new Map(); const deviceResolvers = new Map(); function registerDevice(serialNumber, port, address, target) { const existingDevice = knownDevices.get(serialNumber); if (existingDevice) { if (port !== existingDevice.port || address !== existingDevice.address) { existingDevice.port = port; existingDevice.address = address; if (onChanged) { onChanged(existingDevice); } } return existingDevice; } const device = Device(target ? { serialNumber, port, address, target, } : { serialNumber, port, address, }); knownDevices.set(serialNumber, device); if (onAdded) { onAdded(device); } return device; } return { get registered() { return knownDevices; }, register(serialNumber, port, address, target) { const device = registerDevice(serialNumber, port, address, target); const resolvers = deviceResolvers.get(serialNumber); if (resolvers) { deviceResolvers.delete(serialNumber); resolvers.forEach((resolver) => { resolver(device); }); } return device; }, remove(serialNumber) { const device = knownDevices.get(serialNumber); const removed = knownDevices.delete(serialNumber); if (device && onRemoved) { onRemoved(device); } return removed; }, get(serialNumber, signal) { const knownDevice = knownDevices.get(serialNumber); if (knownDevice) { return Promise.resolve(knownDevice); } const { resolve, reject, promise } = PromiseWithResolvers(); function onAbort(errOrEvent) { const resolvers = deviceResolvers.get(serialNumber); if (resolvers) { if (resolvers.size > 1) { resolvers.delete(resolve); } else { deviceResolvers.delete(serialNumber); } } reject(errOrEvent instanceof Error ? errOrEvent : new AbortError('device lookup')); } let timeout; if (signal) { signal.addEventListener('abort', onAbort, { once: true }); } else { timeout = setTimeout(() => onAbort(new Error('Timeout')), defaultTimeoutMs); } const resolver = (device) => { if (signal) { signal.removeEventListener('abort', onAbort); } else if (timeout) { clearTimeout(timeout); } resolve(device); }; const resolvers = deviceResolvers.get(serialNumber); if (!resolvers) { deviceResolvers.set(serialNumber, new Set([resolver])); } else { resolvers.add(resolver); } return promise; }, [Symbol.iterator]() { return knownDevices.values(); }, }; } //# sourceMappingURL=devices.js.map