@bacnet-js/device
Version:
A TypeScript library for implementing BACnet IP devices in Node.js.
87 lines • 3.65 kB
JavaScript
import { ApplicationTag, } from '@bacnet-js/client';
import { getPropertyUID, } from '../../uids.js';
import {} from './types.js';
import { EMPTY_ARRAY } from '../../constants.js';
/**
* Comparator for sorting subscriptions by expiration date in ascending order.
*/
const subscriptionArraySortingFn = (a, b) => {
return a.expiresAt - b.expiresAt;
};
/**
* Comparator for sorting subscriptions data instances by expiration date in ascending order.
*/
const subscriptionDataArraySortingFn = (a, b) => {
return subscriptionArraySortingFn(a.value, b.value);
};
/**
* Like Array.prototype.findIndex, but returns the length of the array if no element satisfies the predicate.
*/
const findIndexOrLength = (arr, predicate) => {
const idx = arr.findIndex(predicate);
return idx === -1 ? arr.length : idx;
};
/**
* Helper class to keep track of active subscriptions.
*/
export class SubscriptionStore {
/** Array of subscriptions sorted by expiration date in ascending order. */
#deviceSubData;
/** Maps each property (through its UID) to the array of subscriptions for
* that property, sorted by expiration date in ascending order. */
#propertySubs;
/** Timeout ID for clearing expired subscriptions. */
#clearTimeoutId;
constructor() {
this.#deviceSubData = [];
this.#propertySubs = new Map();
this.#clearTimeoutId = null;
}
#setClearTimeout() {
if (this.#clearTimeoutId === null && this.#deviceSubData.length > 0) {
this.#clearTimeoutId = setTimeout(this.#onClearTimeout, this.#deviceSubData[0].value.expiresAt - Date.now());
}
}
#onClearTimeout = () => {
const now = Date.now();
this.#deviceSubData = this.#deviceSubData.slice(findIndexOrLength(this.#deviceSubData, sub => sub.value.expiresAt > now));
for (const [propertyUid, propertySubs] of this.#propertySubs.entries()) {
this.#propertySubs.set(propertyUid, propertySubs.slice(findIndexOrLength(propertySubs, item => item.expiresAt > now)));
}
this.#clearTimeoutId = null;
this.#setClearTimeout();
};
add(subscription) {
const propertyUid = getPropertyUID(subscription.object.uid, subscription.property.identifier);
let propertySubscriptions = this.#propertySubs.get(propertyUid);
if (!propertySubscriptions) {
propertySubscriptions = [];
this.#propertySubs.set(propertyUid, propertySubscriptions);
}
const previousSub = propertySubscriptions.find((existingSub) => {
return existingSub.subscriber.address === subscription.subscriber.address
&& existingSub.subscriptionProcessId === subscription.subscriptionProcessId;
});
if (previousSub) {
previousSub.expiresAt = subscription.expiresAt;
}
else {
propertySubscriptions.push(subscription);
this.#deviceSubData.push({ type: ApplicationTag.COV_SUBSCRIPTION, value: subscription });
}
propertySubscriptions.sort(subscriptionArraySortingFn);
this.#deviceSubData.sort(subscriptionDataArraySortingFn);
this.#setClearTimeout();
}
getPropertySubscriptions(propertyUid) {
return this.#propertySubs.get(propertyUid) ?? EMPTY_ARRAY;
}
getDeviceSubscriptionData() {
const now = Date.now();
for (const subscription of this.#deviceSubData) {
subscription.value.timeRemaining = Math.floor((subscription.value.expiresAt - now) / 1000);
}
return this.#deviceSubData;
}
}
//# sourceMappingURL=subscriptionstore.js.map