dt-common-device
Version:
A secure and robust device management library for IoT applications
392 lines (391 loc) • 20.6 kB
JavaScript
"use strict";
var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
var _, done = false;
for (var i = decorators.length - 1; i >= 0; i--) {
var context = {};
for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
for (var p in contextIn.access) context.access[p] = contextIn.access[p];
context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
if (kind === "accessor") {
if (result === void 0) continue;
if (result === null || typeof result !== "object") throw new TypeError("Object expected");
if (_ = accept(result.get)) descriptor.get = _;
if (_ = accept(result.set)) descriptor.set = _;
if (_ = accept(result.init)) initializers.unshift(_);
}
else if (_ = accept(result)) {
if (kind === "field") initializers.unshift(_);
else descriptor[key] = _;
}
}
if (target) Object.defineProperty(target, contextIn.name, descriptor);
done = true;
};
var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
var useValue = arguments.length > 2;
for (var i = 0; i < initializers.length; i++) {
value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
}
return useValue ? value : void 0;
};
var __setFunctionName = (this && this.__setFunctionName) || function (f, name, prefix) {
if (typeof name === "symbol") name = name.description ? "[".concat(name.description, "]") : "";
return Object.defineProperty(f, "name", { configurable: true, value: prefix ? "".concat(prefix, " ", name) : name });
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.LocalDeviceService = void 0;
const EventHandler_1 = require("../../../events/EventHandler");
const lodash_1 = require("lodash");
const Device_repository_1 = require("../repository/Device.repository");
const typedi_1 = __importDefault(require("typedi"));
const Alert_service_1 = require("../../../alerts/Alert.service");
const Issue_service_1 = require("../../../issues/Issue.service");
const config_1 = require("../../../config/config");
const typedi_2 = require("typedi");
let LocalDeviceService = (() => {
let _classDecorators = [(0, typedi_2.Service)()];
let _classDescriptor;
let _classExtraInitializers = [];
let _classThis;
var LocalDeviceService = _classThis = class {
constructor() {
// Use dependency injection instead of creating new instances
this.eventHandler = typedi_1.default.get(EventHandler_1.EventHandler);
this.deviceRepository = typedi_1.default.get(Device_repository_1.DeviceRepository);
this.alertService = typedi_1.default.get(Alert_service_1.AlertService);
this.issueService = typedi_1.default.get(Issue_service_1.IssueService);
this.logger = (0, config_1.getConfig)().LOGGER;
}
async createDevice(body) {
const device = await this.deviceRepository.createDevice(body);
await this.eventHandler.onDeviceCreate(device);
return device;
}
async getDevice(deviceId, withHubDetails = false) {
if (!deviceId) {
throw new Error("Device ID is required");
}
return await this.deviceRepository.getDevice(deviceId, withHubDetails);
}
async getDevices(deviceIds, withHubDetails = false) {
if (!deviceIds.length) {
throw new Error("At least one device ID is required");
}
return await this.deviceRepository.getDevices(deviceIds, withHubDetails);
}
async getPropertyDevices(propertyId, selectDeviceId = false, type, withHubDetails = false) {
if (!propertyId) {
throw new Error("Property ID is required");
}
return await this.deviceRepository.getPropertyDevices(propertyId, selectDeviceId, type, withHubDetails);
}
async getPropertyDeviceIds(propertyId, selectDeviceId = false, type) {
if (!propertyId) {
throw new Error("Property ID is required");
}
return await this.deviceRepository.getPropertyDeviceIds(propertyId, selectDeviceId, type);
}
async updateDevice(deviceId, body, auditBody) {
if (!deviceId) {
throw new Error("Device ID is required");
}
await this.deviceRepository.updateDevice(deviceId, body);
return await this.eventHandler.onDeviceUpdate(deviceId, body, auditBody);
}
async updateDevices(query, updateData) {
if (!query || !updateData) {
throw new Error("Query and update data are required");
}
return await this.deviceRepository.updateDevices(query, updateData);
}
async deleteDevice(deviceId, auditBody) {
if (!deviceId) {
throw new Error("Device ID is required");
}
await this.deviceRepository.deleteDevice(deviceId);
return await this.eventHandler.onDeviceDelete(deviceId, auditBody);
}
async getState(deviceId) {
if (!deviceId) {
throw new Error("Device ID is required");
}
return await this.deviceRepository.getState(deviceId);
}
async setState(deviceId, newState, auditProperties) {
if (!deviceId || !newState) {
throw new Error("Device ID and new state are required");
}
// If old state and new state are different
const oldState = (await this.getState(deviceId))?.state ?? {};
const changedKeys = Object.keys(newState).filter((key) => !(0, lodash_1.isEqual)(oldState[key], newState[key]));
if (changedKeys.length > 0) {
await this.deviceRepository.setState(deviceId, newState);
return await this.eventHandler.onStateChange(deviceId, newState, auditProperties);
}
}
async getStatus(deviceId) {
if (!deviceId) {
throw new Error("Device ID is required");
}
return await this.deviceRepository.getStatus(deviceId);
}
async setStatus(deviceId, newStatus, source, auditBody, reason) {
if (!deviceId || !newStatus) {
throw new Error("Device ID and new status are required");
}
const device = await this.getDevice(deviceId);
const oldStatus = device.status;
const currentTime = new Date().toISOString();
// Determine if the new status is ONLINE or OFFLINE
const isNewStatusOnline = newStatus.liveStatus === "ONLINE";
const isNewStatusOffline = newStatus.liveStatus === "OFFLINE";
if (isNewStatusOffline) {
// New Status = OFFLINE
await this.handleOfflineStatus(device, oldStatus, newStatus, source, auditBody, reason, currentTime);
}
else if (isNewStatusOnline) {
// New Status = ONLINE
await this.handleOnlineStatus(device, oldStatus, newStatus, source, auditBody, reason, currentTime);
}
else {
// For any other status, just update normally
await this.deviceRepository.setStatus(deviceId, newStatus);
await this.eventHandler.onStatusChange(deviceId, newStatus, auditBody);
}
}
async setStatusMany(query, newStatus, source, auditBody) {
if (!query || !newStatus) {
throw new Error("Query and new status are required");
}
newStatus.lastUpdated = new Date().toISOString();
await this.deviceRepository.setStatusMany(query, newStatus);
await this.eventHandler.onStatusChangeMany(query, newStatus, auditBody);
}
async handleOfflineStatus(device, oldStatus, newStatus, source, auditBody, reason, currentTime) {
const isExistingStatusOnline = oldStatus?.online === true;
const isExistingStatusOffline = oldStatus?.online === false;
if (isExistingStatusOnline) {
// Existing status is Online
newStatus.online = true;
newStatus.liveStatus = "OFFLINE";
newStatus.lastUpdated = currentTime;
newStatus.error = {
type: "offline",
message: reason || "Device went offline",
default: {},
};
await this.deviceRepository.setStatus(device.deviceId, newStatus);
await this.eventHandler.onStatusChange(device.deviceId, newStatus, auditBody);
}
else if (isExistingStatusOffline) {
// Existing status is Offline
const timeLapsed = oldStatus?.lastUpdated
? Date.now() - new Date(oldStatus.lastUpdated).getTime()
: 0;
const baselineTime = await this.getDeviceBaseline(device.deviceId);
if (timeLapsed > baselineTime) {
// When the time lapsed is higher than the baseline time
newStatus.online = false;
newStatus.liveStatus = "OFFLINE";
newStatus.lastUpdated = currentTime;
newStatus.error = {
type: "offline",
message: reason || "Device has been offline for longer than baseline",
default: {},
};
await this.deviceRepository.setStatus(device.deviceId, newStatus);
await this.eventHandler.onStatusChange(device.deviceId, newStatus, auditBody);
// Raise alert (OPERATIONAL only)
await this.alertService.raiseDeviceOfflineAlert(device, source, reason);
// Raise issue when the device goes offline if longer than the baseline (OPERATIONAL only)
await this.issueService.createDeviceOfflineIssue(device, source, reason);
}
}
}
async handleOnlineStatus(device, oldStatus, newStatus, source, auditBody, reason, currentTime) {
const isExistingStatusOnline = oldStatus?.online === true;
const isExistingStatusOffline = oldStatus?.online === false;
if (isExistingStatusOnline) {
// Existing status is Online
if (oldStatus?.liveStatus === "ONLINE") {
// liveStatus = Online → Do nothing
return;
}
else if (oldStatus?.liveStatus === "OFFLINE") {
// liveStatus = Offline → Follow the step #2 (same as existing status is Offline)
newStatus.online = true;
newStatus.liveStatus = "ONLINE";
newStatus.lastUpdated = currentTime;
newStatus.error = {}; // Clear the error
await this.deviceRepository.setStatus(device.deviceId, newStatus);
await this.eventHandler.onStatusChange(device.deviceId, newStatus, auditBody);
//TODO: ALERT NEEDED?
// Raise alert
await this.alertService.raiseDeviceOnlineAlert(device, source, reason);
}
}
else if (isExistingStatusOffline) {
// Existing status is Offline
newStatus.online = true;
newStatus.liveStatus = "ONLINE";
newStatus.lastUpdated = currentTime;
newStatus.error = undefined; // Clear the error
await this.deviceRepository.setStatus(device.deviceId, newStatus);
await this.eventHandler.onStatusChange(device.deviceId, newStatus, auditBody);
//TODO: ALERT NEEDED?
// Raise alert
await this.alertService.raiseDeviceOnlineAlert(device, source, reason);
}
}
async getDeviceBaseline(deviceId) {
// TODO: Implement device baseline retrieval
// This should return the baseline time in milliseconds for the specific device
// For now, returning a default value of 5 minutes (300000ms)
// In a real implementation, this would fetch from device configuration or settings
return 3600000; // 1 hour in milliseconds
}
async getBatteryLevel(deviceId) {
if (!deviceId) {
throw new Error("Device ID is required");
}
return await this.deviceRepository.getBatteryLevel(deviceId);
}
async setBatteryLevel(deviceId, batteryLevel, source, auditBody) {
if (!deviceId || batteryLevel === undefined || batteryLevel === null) {
throw new Error("Device ID and battery level are required");
}
// Get device information
const device = await this.getDevice(deviceId);
// Fetch the old battery level state
const oldBatteryLevel = device.state?.batteryPercentage;
// Check if battery level has changed
const isDifferent = !(0, lodash_1.isEqual)(oldBatteryLevel?.value, batteryLevel);
if (isDifferent) {
// Check if current time is greater than or equal to last updated time (8-hour logic)
const shouldUpdate = this.shouldUpdateBatteryLevel(oldBatteryLevel?.lastUpdated);
if (shouldUpdate) {
// Save the battery level in the device
await this.deviceRepository.setBatteryLevel(deviceId, batteryLevel);
await this.eventHandler.onBatteryLevelChange(deviceId, batteryLevel, auditBody);
// Get property threshold
const propertyThreshold = await this.getPropertyBatteryThreshold(device.propertyId);
// Check if battery level is below threshold
if (batteryLevel < propertyThreshold) {
// Raise alert
await this.alertService.raiseDeviceBatteryAlert(device, batteryLevel, propertyThreshold, source);
// Raise issue when the level is below threshold
await this.issueService.createDeviceBatteryIssue(device, batteryLevel, propertyThreshold, source);
}
}
}
}
shouldUpdateBatteryLevel(lastUpdated) {
if (!lastUpdated) {
return true; // No previous update, so we should update
}
const lastUpdateTime = new Date(lastUpdated).getTime();
const currentTime = Date.now();
const eightHoursInMs = 8 * 60 * 60 * 1000; // 8 hours in milliseconds
// Return true if current time is greater than or equal to last updated time + 8 hours
return currentTime >= lastUpdateTime + eightHoursInMs;
}
async getPropertyBatteryThreshold(propertyId) {
// TODO: Implement property battery threshold retrieval
// This should return the battery threshold for the specific property
// For now, returning a default value of 20%
// In a real implementation, this would fetch from property configuration or settings
return 20; // 20% default threshold
}
async checkForDeviceMalfunctions(device, source, reason) {
// TODO: Implement device malfunction detection logic
// This should check for:
// - Lock jammed
// - Device not accepting codes
// - Other malfunction indicators
// For now, we'll check if the reason indicates a malfunction
const malfunctionIndicators = [
"jammed",
"not accepting codes",
"malfunction",
"error",
"failure",
];
const hasMalfunction = malfunctionIndicators.some((indicator) => reason?.toLowerCase().includes(indicator));
if (hasMalfunction) {
// Raise alert for device malfunction (READINESS + OPERATIONAL)
await this.alertService.raiseDeviceIssueAlert(device, "Device Malfunction Detected", source, reason);
// Raise issue for device malfunction (READINESS + OPERATIONAL)
await this.issueService.createDeviceMalfunctionIssue(device, "Device Malfunction", source, reason);
}
}
async getMetaData(deviceId) {
if (!deviceId) {
throw new Error("Device ID is required");
}
return await this.deviceRepository.getMetaData(deviceId);
}
async setMetaData(deviceId, metaData, auditBody) {
if (!deviceId || !metaData) {
throw new Error("Device ID and meta data are required");
}
await this.eventHandler.onDeviceMetaChange(deviceId, metaData, auditBody);
return await this.deviceRepository.setMetaData(deviceId, metaData);
}
async getDevicesByZone(zoneId) {
if (!zoneId) {
throw new Error("Zone ID is required");
}
return await this.deviceRepository.getDevicesByZone(zoneId);
}
async getDevicesByAccessGroup(accessGroupId) {
if (!accessGroupId) {
throw new Error("Access Group ID is required");
}
return await this.deviceRepository.getDevicesByAccessGroup(accessGroupId);
}
async querySelect(query, fields) {
if (!query || Object.keys(query).length === 0) {
throw new Error("Query is required");
}
return await this.deviceRepository.querySelect(query, fields);
}
async queryCount(query) {
if (!query || Object.keys(query).length === 0) {
throw new Error("Query is required");
}
const count = await this.deviceRepository.queryCount(query);
return count;
}
async deleteDevices(query) {
if (!query || Object.keys(query).length === 0) {
throw new Error("Query is required");
}
return await this.deviceRepository.deleteDevices(query);
}
async queryDevices(query) {
if (!query || Object.keys(query).length === 0) {
throw new Error("Query is required");
}
const res = await this.deviceRepository.queryDevices(query);
return res;
}
};
__setFunctionName(_classThis, "LocalDeviceService");
(() => {
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0;
__esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
LocalDeviceService = _classThis = _classDescriptor.value;
if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
__runInitializers(_classThis, _classExtraInitializers);
})();
return LocalDeviceService = _classThis;
})();
exports.LocalDeviceService = LocalDeviceService;