UNPKG

dt-common-device

Version:

A secure and robust device management library for IoT applications

392 lines (391 loc) 20.6 kB
"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;