homebridge-homeconnect
Version:
A Homebridge plugin that connects Home Connect appliances to Apple HomeKit
110 lines • 3.91 kB
JavaScript
// Homebridge plugin for Home Connect home appliances
// Copyright © 2019-2025 Alexander Thoukydides
import { setImmediate as setImmediateP } from 'timers/promises';
// Count of serialised operation objects
let serialisedCounter = 0;
// Coalesce and serialise operations that should run exclusively
export class Serialised {
log;
operation;
defaultValue;
options;
// Operation currently in progress
activePromise;
// Pending operation
pendingValue;
pendingPromise;
// Count of operations performed
counter = 1;
// Initialise a serialised operation manager
constructor(log, operation, defaultValue, options = {}) {
this.log = log;
this.operation = operation;
this.defaultValue = defaultValue;
this.options = options;
// Ensure that the operation has a name
++serialisedCounter;
this.options.name ??= serialisedCounter.toString();
// Set the initial value for the pending operation
this.pendingValue = defaultValue;
}
// Trigger the operation
trigger(value) {
this.updatePendingValue(value);
this.pendingPromise ??= this.startPending();
return this.pendingPromise;
}
// Wait for any active operation to complete and start a new one
async startPending() {
// Wait for any active operation to complete
try {
await this.activePromise;
}
catch { /* empty */ }
await setImmediateP();
// Start the pending operation and reset ready for the next
this.activePromise = this.startActive(this.pendingValue);
this.resetPendingValue();
this.pendingPromise = undefined;
// Wait for the active operation to complete
try {
return await this.activePromise;
}
finally {
this.activePromise = undefined;
}
}
// Start a new operation
async startActive(value) {
const logPrefix = this.logPrefix;
++this.counter;
try {
this.logVerbose(`${logPrefix} starting: ${JSON.stringify(value)}`);
const result = await this.operation(value);
this.logVerbose(`${logPrefix} successful`);
return result;
}
catch (err) {
const message = err instanceof Error ? err.message : String(err);
this.logVerbose(`${logPrefix} failed: ${message}`);
throw err;
}
}
// Merge a new value with the pending value
updatePendingValue(value) {
let description;
if (typeof value === 'object') {
// Objects are merged, with new properties overwriting old ones
const newValue = Object.assign({}, this.pendingValue, value);
description = `${JSON.stringify(this.pendingValue)} + ${JSON.stringify(value)} = ${JSON.stringify(newValue)}`;
this.pendingValue = newValue;
}
else if (value !== undefined) {
// Other types just use the latest value
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
description = `${value} (was ${this.pendingValue})`;
this.pendingValue = value;
}
else {
// No value provided so keep the existing value
description = '(no value)';
}
this.logVerbose(`${this.logPrefix} coalescing: ${description}`);
}
// Reset the pending value
resetPendingValue() {
if (this.options.reset) {
this.pendingValue = this.defaultValue;
}
}
// Prefix for log messages
get logPrefix() {
return `Serialised request #${this.options.name}.${this.counter}`;
}
// Verbose logging
logVerbose(message) {
if (this.options.verbose)
this.log.debug(message);
}
}
//# sourceMappingURL=serialised.js.map