lifxlan
Version:
TypeScript library for controlling LIFX products over LAN
121 lines • 4.6 kB
JavaScript
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