dt-common-device 1.0.8 → 1.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.js +44 -0
- package/README.md +1 -1
- package/SECURITY.md +67 -0
- package/dist/device/local/handler/EventHandler.d.ts +7 -0
- package/dist/device/local/handler/EventHandler.js +44 -0
- package/dist/device/local/interfaces/IDevice.d.ts +2 -1
- package/dist/device/local/interfaces/IHub.d.ts +1 -1
- package/dist/device/local/services/Alert.service.d.ts +7 -0
- package/dist/device/local/services/Alert.service.js +11 -0
- package/dist/device/local/services/Device.service.d.ts +9 -4
- package/dist/device/local/services/Device.service.js +64 -23
- package/dist/device/local/services/Hub.service.d.ts +4 -4
- package/dist/index.d.ts +1 -1
- package/package.json +33 -8
- package/src/device/local/handler/EventHandler.ts +52 -0
- package/src/device/local/interfaces/IDevice.ts +2 -1
- package/src/device/local/interfaces/IHub.ts +1 -1
- package/src/device/local/services/Alert.service.ts +8 -0
- package/src/device/local/services/Device.service.ts +81 -34
- package/src/device/local/services/Hub.service.ts +4 -4
- package/src/index.ts +1 -4
package/.eslintrc.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
parser: "@typescript-eslint/parser",
|
|
3
|
+
extends: ["eslint:recommended", "@typescript-eslint/recommended"],
|
|
4
|
+
plugins: ["@typescript-eslint"],
|
|
5
|
+
env: {
|
|
6
|
+
node: true,
|
|
7
|
+
es2020: true,
|
|
8
|
+
},
|
|
9
|
+
parserOptions: {
|
|
10
|
+
ecmaVersion: 2020,
|
|
11
|
+
sourceType: "module",
|
|
12
|
+
},
|
|
13
|
+
rules: {
|
|
14
|
+
// Security rules
|
|
15
|
+
"no-eval": "error",
|
|
16
|
+
"no-implied-eval": "error",
|
|
17
|
+
"no-new-func": "error",
|
|
18
|
+
"no-script-url": "error",
|
|
19
|
+
"no-unsafe-finally": "error",
|
|
20
|
+
|
|
21
|
+
// TypeScript security
|
|
22
|
+
"@typescript-eslint/no-explicit-any": "warn",
|
|
23
|
+
"@typescript-eslint/no-unsafe-assignment": "warn",
|
|
24
|
+
"@typescript-eslint/no-unsafe-call": "warn",
|
|
25
|
+
"@typescript-eslint/no-unsafe-member-access": "warn",
|
|
26
|
+
"@typescript-eslint/no-unsafe-return": "warn",
|
|
27
|
+
|
|
28
|
+
// Code quality
|
|
29
|
+
"@typescript-eslint/explicit-function-return-type": "warn",
|
|
30
|
+
"@typescript-eslint/no-unused-vars": "error",
|
|
31
|
+
"@typescript-eslint/prefer-const": "error",
|
|
32
|
+
"@typescript-eslint/no-var-requires": "error",
|
|
33
|
+
|
|
34
|
+
// Error handling
|
|
35
|
+
"no-console": "warn",
|
|
36
|
+
"no-throw-literal": "error",
|
|
37
|
+
|
|
38
|
+
// Best practices
|
|
39
|
+
"prefer-const": "error",
|
|
40
|
+
"no-var": "error",
|
|
41
|
+
"object-shorthand": "error",
|
|
42
|
+
},
|
|
43
|
+
ignorePatterns: ["dist/", "node_modules/", "*.js"],
|
|
44
|
+
};
|
package/README.md
CHANGED
|
@@ -167,7 +167,7 @@ await deviceHubService.deleteAllHubs(hubIds);
|
|
|
167
167
|
All types and interfaces are available as named exports:
|
|
168
168
|
|
|
169
169
|
```ts
|
|
170
|
-
import { IDevice,
|
|
170
|
+
import { IDevice, IHub, IConnection } from "dt-common-device";
|
|
171
171
|
```
|
|
172
172
|
|
|
173
173
|
---
|
package/SECURITY.md
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# Security Policy
|
|
2
|
+
|
|
3
|
+
## Supported Versions
|
|
4
|
+
|
|
5
|
+
| Version | Supported |
|
|
6
|
+
| ------- | ------------------ |
|
|
7
|
+
| 1.0.x | :white_check_mark: |
|
|
8
|
+
|
|
9
|
+
## Reporting a Vulnerability
|
|
10
|
+
|
|
11
|
+
If you discover a security vulnerability within dt-common-device, please send an email to security@devicethread.com. All security vulnerabilities will be promptly addressed.
|
|
12
|
+
|
|
13
|
+
## Security Features
|
|
14
|
+
|
|
15
|
+
### Input Validation
|
|
16
|
+
|
|
17
|
+
- All user inputs are validated and sanitized
|
|
18
|
+
- Device IDs and Property IDs are validated for format and length
|
|
19
|
+
- Metadata is sanitized to prevent injection attacks
|
|
20
|
+
- URL validation for all external service endpoints
|
|
21
|
+
|
|
22
|
+
### Error Handling
|
|
23
|
+
|
|
24
|
+
- Custom error classes for different types of failures
|
|
25
|
+
- No sensitive information leaked in error messages
|
|
26
|
+
- Proper HTTP status code mapping
|
|
27
|
+
- Graceful degradation for network failures
|
|
28
|
+
|
|
29
|
+
### Type Safety
|
|
30
|
+
|
|
31
|
+
- Full TypeScript support with strict typing
|
|
32
|
+
- No `any` types in public APIs
|
|
33
|
+
- Interface validation for all data structures
|
|
34
|
+
|
|
35
|
+
### Network Security
|
|
36
|
+
|
|
37
|
+
- HTTPS enforcement for all external calls
|
|
38
|
+
- Request timeout handling
|
|
39
|
+
- Retry logic for transient failures
|
|
40
|
+
- Proper error classification for different HTTP status codes
|
|
41
|
+
|
|
42
|
+
## Best Practices
|
|
43
|
+
|
|
44
|
+
1. **Always validate inputs** before passing to library methods
|
|
45
|
+
2. **Handle errors appropriately** using the provided error classes
|
|
46
|
+
3. **Use HTTPS** for all external service URLs
|
|
47
|
+
4. **Keep dependencies updated** by running `npm audit` regularly
|
|
48
|
+
5. **Monitor for security updates** in dependent packages
|
|
49
|
+
|
|
50
|
+
## Dependencies
|
|
51
|
+
|
|
52
|
+
This library uses the following security-focused dependencies:
|
|
53
|
+
|
|
54
|
+
- `axios` - HTTP client with built-in security features
|
|
55
|
+
- `lodash` - Utility library with security patches
|
|
56
|
+
- Custom validation utilities for input sanitization
|
|
57
|
+
|
|
58
|
+
## Vulnerability Prevention
|
|
59
|
+
|
|
60
|
+
- Regular security audits with `npm audit`
|
|
61
|
+
- Automated vulnerability scanning in CI/CD
|
|
62
|
+
- Input validation and sanitization
|
|
63
|
+
- Type-safe interfaces
|
|
64
|
+
- Comprehensive error handling
|
|
65
|
+
- No eval() or dynamic code execution
|
|
66
|
+
- No direct file system access
|
|
67
|
+
- No command injection vulnerabilities
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export declare class EventHandler {
|
|
2
|
+
private readonly source;
|
|
3
|
+
constructor();
|
|
4
|
+
onStateChange(deviceId: string, state: any): Promise<void>;
|
|
5
|
+
onStatusChange(deviceId: string, status: any): Promise<void>;
|
|
6
|
+
onBatteryLevelChange(deviceId: string, batteryLevel: number): Promise<void>;
|
|
7
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.EventHandler = void 0;
|
|
4
|
+
const dt_pub_sub_1 = require("dt-pub-sub");
|
|
5
|
+
const dt_audit_library_1 = require("dt-audit-library");
|
|
6
|
+
class EventHandler {
|
|
7
|
+
constructor() {
|
|
8
|
+
this.source = "dt-common-device";
|
|
9
|
+
}
|
|
10
|
+
async onStateChange(deviceId, state) {
|
|
11
|
+
await dt_pub_sub_1.eventDispatcher.publishEvent("device.state.set", { deviceId, state }, this.source);
|
|
12
|
+
const payload = {
|
|
13
|
+
eventType: "device.state.set",
|
|
14
|
+
properties: {
|
|
15
|
+
deviceId,
|
|
16
|
+
state,
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
await (0, dt_audit_library_1.publishAudit)(payload);
|
|
20
|
+
}
|
|
21
|
+
async onStatusChange(deviceId, status) {
|
|
22
|
+
await dt_pub_sub_1.eventDispatcher.publishEvent("device.status.set", { deviceId, status }, this.source);
|
|
23
|
+
const payload = {
|
|
24
|
+
eventType: "device.status.set",
|
|
25
|
+
properties: {
|
|
26
|
+
deviceId,
|
|
27
|
+
status,
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
await (0, dt_audit_library_1.publishAudit)(payload);
|
|
31
|
+
}
|
|
32
|
+
async onBatteryLevelChange(deviceId, batteryLevel) {
|
|
33
|
+
await dt_pub_sub_1.eventDispatcher.publishEvent("device.battery.set", { deviceId, batteryLevel }, this.source);
|
|
34
|
+
const payload = {
|
|
35
|
+
eventType: "device.battery.set",
|
|
36
|
+
properties: {
|
|
37
|
+
deviceId,
|
|
38
|
+
batteryLevel,
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
await (0, dt_audit_library_1.publishAudit)(payload);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
exports.EventHandler = EventHandler;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export interface
|
|
1
|
+
export interface IDevice {
|
|
2
2
|
deviceId: string;
|
|
3
3
|
propertyId: string;
|
|
4
4
|
name: string;
|
|
@@ -43,4 +43,5 @@ export interface IDeviceCreateParams {
|
|
|
43
43
|
metaData?: object;
|
|
44
44
|
createdAt?: Date;
|
|
45
45
|
updatedAt?: Date;
|
|
46
|
+
hubDeviceDetails?: IDevice[];
|
|
46
47
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AlertService = void 0;
|
|
4
|
+
class AlertService {
|
|
5
|
+
constructor() { }
|
|
6
|
+
raiseReadinessAlert() { }
|
|
7
|
+
raiseOperationsAlert() { }
|
|
8
|
+
raiseSecurityAlert() { }
|
|
9
|
+
raiseEnergyAlert() { }
|
|
10
|
+
}
|
|
11
|
+
exports.AlertService = AlertService;
|
|
@@ -1,18 +1,23 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { IDevice } from "../interfaces";
|
|
2
2
|
export declare class DeviceService {
|
|
3
3
|
private readonly baseUrl;
|
|
4
4
|
private readonly source;
|
|
5
|
+
private readonly eventHandler;
|
|
5
6
|
constructor();
|
|
6
|
-
createDevice(body:
|
|
7
|
-
getDevice(deviceId: string): Promise<
|
|
8
|
-
|
|
7
|
+
createDevice(body: IDevice): Promise<void>;
|
|
8
|
+
getDevice(deviceId: string, withHubDetails?: boolean): Promise<IDevice>;
|
|
9
|
+
getDevices(deviceIds: string[], withHubDetails?: boolean): Promise<IDevice[]>;
|
|
10
|
+
getPropertyDevices(propertyId: string, withHubDetails?: boolean): Promise<IDevice[]>;
|
|
9
11
|
updateDevice(deviceId: string, body: any): Promise<any>;
|
|
10
12
|
deleteDevice(deviceId: string): Promise<any>;
|
|
11
13
|
getState(deviceId: string): Promise<import("axios").AxiosResponse<any, any>>;
|
|
14
|
+
setState(deviceId: string, state: any): Promise<void>;
|
|
12
15
|
getStatus(deviceId: string): Promise<import("axios").AxiosResponse<any, any>>;
|
|
13
16
|
setStatus(deviceId: string, status: any): Promise<void>;
|
|
14
17
|
getBatteryLevel(deviceId: string): Promise<import("axios").AxiosResponse<any, any>>;
|
|
15
18
|
setBatteryLevel(deviceId: string, batteryLevel: number): Promise<void>;
|
|
16
19
|
getMetaData(deviceId: string): Promise<import("axios").AxiosResponse<any, any>>;
|
|
17
20
|
setMetaData(deviceId: string, metaData: Record<string, any>): Promise<void>;
|
|
21
|
+
getDeviceByZone(zoneId: string): Promise<void>;
|
|
22
|
+
getDevicesByAccessGroup(accessGroupId: string): Promise<void>;
|
|
18
23
|
}
|
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
// src/device/local/services/Device.service.ts
|
|
3
2
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
4
|
};
|
|
6
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
6
|
exports.DeviceService = void 0;
|
|
7
|
+
//TODO: Import Redis
|
|
8
8
|
const axios_1 = __importDefault(require("axios"));
|
|
9
9
|
const config_1 = require("../../../config/config");
|
|
10
10
|
const dt_pub_sub_1 = require("dt-pub-sub");
|
|
11
11
|
const dt_audit_library_1 = require("dt-audit-library");
|
|
12
|
+
const EventHandler_1 = require("../handler/EventHandler");
|
|
13
|
+
const lodash_1 = require("lodash");
|
|
12
14
|
class DeviceService {
|
|
13
15
|
constructor() {
|
|
14
16
|
this.source = "dt-common-device";
|
|
@@ -19,6 +21,7 @@ class DeviceService {
|
|
|
19
21
|
this.baseUrl = DEVICE_SERVICE;
|
|
20
22
|
(0, config_1.checkAwsEnv)();
|
|
21
23
|
(0, config_1.ensureAuditInitialized)();
|
|
24
|
+
this.eventHandler = new EventHandler_1.EventHandler();
|
|
22
25
|
}
|
|
23
26
|
async createDevice(body) {
|
|
24
27
|
await dt_pub_sub_1.eventDispatcher.publishEvent("device.created", body, this.source);
|
|
@@ -30,12 +33,17 @@ class DeviceService {
|
|
|
30
33
|
};
|
|
31
34
|
await (0, dt_audit_library_1.publishAudit)(payload);
|
|
32
35
|
}
|
|
33
|
-
async getDevice(deviceId) {
|
|
34
|
-
return await axios_1.default.get(`${this.baseUrl}/devices/${deviceId}`);
|
|
36
|
+
async getDevice(deviceId, withHubDetails = false) {
|
|
37
|
+
return await axios_1.default.get(`${this.baseUrl}/devices/${deviceId}?withHubDetails=${withHubDetails} `);
|
|
35
38
|
}
|
|
36
|
-
async
|
|
39
|
+
async getDevices(deviceIds, withHubDetails = false) {
|
|
37
40
|
return await axios_1.default.get(`${this.baseUrl}/devices`, {
|
|
38
|
-
params: {
|
|
41
|
+
params: { deviceIds, withHubDetails },
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
async getPropertyDevices(propertyId, withHubDetails = false) {
|
|
45
|
+
return await axios_1.default.get(`${this.baseUrl}/devices`, {
|
|
46
|
+
params: { propertyId, withHubDetails },
|
|
39
47
|
});
|
|
40
48
|
}
|
|
41
49
|
async updateDevice(deviceId, body) {
|
|
@@ -62,33 +70,40 @@ class DeviceService {
|
|
|
62
70
|
async getState(deviceId) {
|
|
63
71
|
return await axios_1.default.get(`${this.baseUrl}/devices/${deviceId}/state`);
|
|
64
72
|
}
|
|
73
|
+
async setState(deviceId, state) {
|
|
74
|
+
// If old status and new status are different
|
|
75
|
+
const oldState = await this.getState(deviceId);
|
|
76
|
+
if (!(0, lodash_1.isEqual)(oldState, state)) {
|
|
77
|
+
this.eventHandler.onStateChange(deviceId, state);
|
|
78
|
+
}
|
|
79
|
+
// If offline, check the time difference between the last offline status and current time
|
|
80
|
+
// Compare it with the baseline profile time of the device. If the difference is higher, then raise an operational alert
|
|
81
|
+
//deviceAlertService.raiseOperationalAlert();
|
|
82
|
+
}
|
|
65
83
|
async getStatus(deviceId) {
|
|
66
84
|
return await axios_1.default.get(`${this.baseUrl}/devices/${deviceId}/status`);
|
|
67
85
|
}
|
|
68
86
|
async setStatus(deviceId, status) {
|
|
69
|
-
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
await (0, dt_audit_library_1.publishAudit)(payload);
|
|
87
|
+
// If old status and new status are different
|
|
88
|
+
const oldStatus = await this.getStatus(deviceId);
|
|
89
|
+
if (!(0, lodash_1.isEqual)(oldStatus, status)) {
|
|
90
|
+
this.eventHandler.onStatusChange(deviceId, status);
|
|
91
|
+
}
|
|
92
|
+
// If offline, check the time difference between the last offline status and current time
|
|
93
|
+
// Compare it with the baseline profile time of the device. If the difference is higher, then raise an operational alert
|
|
94
|
+
//deviceAlertService.raiseOperationalAlert();
|
|
78
95
|
}
|
|
79
96
|
async getBatteryLevel(deviceId) {
|
|
80
97
|
return await axios_1.default.get(`${this.baseUrl}/devices/${deviceId}/battery-level`);
|
|
81
98
|
}
|
|
82
99
|
async setBatteryLevel(deviceId, batteryLevel) {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
};
|
|
91
|
-
await (0, dt_audit_library_1.publishAudit)(payload);
|
|
100
|
+
// If old status and new status are different
|
|
101
|
+
// and the last battery level received time more than 8 hours {
|
|
102
|
+
this.eventHandler.onBatteryLevelChange(deviceId, batteryLevel);
|
|
103
|
+
// if(batteryLevel < propertySettingBatteryThreshold) {
|
|
104
|
+
// deviceAlertService.raiseEnergyAlert({});
|
|
105
|
+
// }
|
|
106
|
+
// }
|
|
92
107
|
}
|
|
93
108
|
async getMetaData(deviceId) {
|
|
94
109
|
return await axios_1.default.get(`${this.baseUrl}/devices/${deviceId}/metaData`);
|
|
@@ -104,5 +119,31 @@ class DeviceService {
|
|
|
104
119
|
};
|
|
105
120
|
await (0, dt_audit_library_1.publishAudit)(payload);
|
|
106
121
|
}
|
|
122
|
+
async getDeviceByZone(zoneId) {
|
|
123
|
+
try {
|
|
124
|
+
//Step 1: Check if zone data available in redis
|
|
125
|
+
//Step 2: If not available, fetch uaing smart-cloud-node zone api
|
|
126
|
+
//Step 3: Store the zone data in redis
|
|
127
|
+
//Step 4: Return the zone data
|
|
128
|
+
//Step 5: If zone data is available in redis, return the zone data
|
|
129
|
+
}
|
|
130
|
+
catch (error) {
|
|
131
|
+
console.log(error);
|
|
132
|
+
throw new Error("Failed to get device by zone");
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
async getDevicesByAccessGroup(accessGroupId) {
|
|
136
|
+
try {
|
|
137
|
+
//Step 1: Check if access group data available in redis
|
|
138
|
+
//Step 2: If not available, fetch using smart-cloud-node access-group api
|
|
139
|
+
//Step 3: Store the access group data in redis
|
|
140
|
+
//Step 4: Return the access group data
|
|
141
|
+
//Step 5: If access group data is available in redis, return the access group data
|
|
142
|
+
}
|
|
143
|
+
catch (error) {
|
|
144
|
+
console.log(error);
|
|
145
|
+
throw new Error("Failed to get devices by access group");
|
|
146
|
+
}
|
|
147
|
+
}
|
|
107
148
|
}
|
|
108
149
|
exports.DeviceService = DeviceService;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { IHub } from "../interfaces";
|
|
2
2
|
export declare class HubService {
|
|
3
3
|
private readonly baseUrl;
|
|
4
4
|
constructor();
|
|
5
|
-
addHub(body:
|
|
6
|
-
getHubs(hubIds: string[]): Promise<
|
|
7
|
-
getHub(hubId: string): Promise<
|
|
5
|
+
addHub(body: IHub): Promise<any>;
|
|
6
|
+
getHubs(hubIds: string[]): Promise<IHub[]>;
|
|
7
|
+
getHub(hubId: string): Promise<IHub>;
|
|
8
8
|
updateHub(hubId: string, body: any): Promise<any>;
|
|
9
9
|
getStatus(hubId: string): Promise<any>;
|
|
10
10
|
deleteHub(hubId: string): Promise<any>;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { CloudDeviceService, CloudHubService, CloudConnectionService, } from "./device/cloud/services";
|
|
2
|
-
export { LocalDeviceService, LocalHubService
|
|
2
|
+
export { LocalDeviceService, LocalHubService } from "./device/local/services";
|
|
3
3
|
export * as cloudInterfaces from "./device/cloud/interfaces";
|
|
4
4
|
export * from "./device/cloud/types";
|
|
5
5
|
export * as localInterfaces from "./device/local/interfaces";
|
package/package.json
CHANGED
|
@@ -1,23 +1,48 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dt-common-device",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.9",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"types": "dist/index.d.ts",
|
|
6
6
|
"scripts": {
|
|
7
|
-
"
|
|
8
|
-
"
|
|
7
|
+
"build": "tsc",
|
|
8
|
+
"security:audit": "npm audit --audit-level=moderate",
|
|
9
|
+
"security:fix": "npm audit fix",
|
|
10
|
+
"security:check": "npm audit && npm outdated",
|
|
11
|
+
"type-check": "tsc --noEmit"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"device",
|
|
15
|
+
"iot",
|
|
16
|
+
"smart-devices",
|
|
17
|
+
"device-management"
|
|
18
|
+
],
|
|
19
|
+
"author": "DeviceThread Team",
|
|
20
|
+
"license": "MIT",
|
|
21
|
+
"description": "A secure and robust device management library for IoT applications",
|
|
22
|
+
"repository": {
|
|
23
|
+
"type": "git",
|
|
24
|
+
"url": "https://github.com/devicethread/dt-common-device.git"
|
|
25
|
+
},
|
|
26
|
+
"engines": {
|
|
27
|
+
"node": ">=16.0.0",
|
|
28
|
+
"npm": ">=8.0.0"
|
|
9
29
|
},
|
|
10
|
-
"keywords": [],
|
|
11
|
-
"author": "",
|
|
12
|
-
"license": "ISC",
|
|
13
|
-
"description": "",
|
|
14
30
|
"devDependencies": {
|
|
31
|
+
"@types/lodash": "^4.17.19",
|
|
32
|
+
"@types/node": "^20.0.0",
|
|
15
33
|
"ts-node": "^10.9.2",
|
|
16
34
|
"typescript": "^5.8.3"
|
|
17
35
|
},
|
|
18
36
|
"dependencies": {
|
|
19
37
|
"axios": "^1.10.0",
|
|
20
38
|
"dt-audit-library": "^1.0.3",
|
|
21
|
-
"dt-pub-sub": "^1.0.0"
|
|
39
|
+
"dt-pub-sub": "^1.0.0",
|
|
40
|
+
"lodash": "^4.17.21"
|
|
41
|
+
},
|
|
42
|
+
"peerDependencies": {
|
|
43
|
+
"axios": ">=1.0.0"
|
|
44
|
+
},
|
|
45
|
+
"overrides": {
|
|
46
|
+
"axios": "^1.10.0"
|
|
22
47
|
}
|
|
23
48
|
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { eventDispatcher } from "dt-pub-sub";
|
|
2
|
+
import { publishAudit } from "dt-audit-library";
|
|
3
|
+
|
|
4
|
+
export class EventHandler {
|
|
5
|
+
private readonly source = "dt-common-device";
|
|
6
|
+
constructor() {}
|
|
7
|
+
async onStateChange(deviceId: string, state: any) {
|
|
8
|
+
await eventDispatcher.publishEvent(
|
|
9
|
+
"device.state.set",
|
|
10
|
+
{ deviceId, state },
|
|
11
|
+
this.source
|
|
12
|
+
);
|
|
13
|
+
const payload = {
|
|
14
|
+
eventType: "device.state.set",
|
|
15
|
+
properties: {
|
|
16
|
+
deviceId,
|
|
17
|
+
state,
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
await publishAudit(payload);
|
|
21
|
+
}
|
|
22
|
+
async onStatusChange(deviceId: string, status: any) {
|
|
23
|
+
await eventDispatcher.publishEvent(
|
|
24
|
+
"device.status.set",
|
|
25
|
+
{ deviceId, status },
|
|
26
|
+
this.source
|
|
27
|
+
);
|
|
28
|
+
const payload = {
|
|
29
|
+
eventType: "device.status.set",
|
|
30
|
+
properties: {
|
|
31
|
+
deviceId,
|
|
32
|
+
status,
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
await publishAudit(payload);
|
|
36
|
+
}
|
|
37
|
+
async onBatteryLevelChange(deviceId: string, batteryLevel: number) {
|
|
38
|
+
await eventDispatcher.publishEvent(
|
|
39
|
+
"device.battery.set",
|
|
40
|
+
{ deviceId, batteryLevel },
|
|
41
|
+
this.source
|
|
42
|
+
);
|
|
43
|
+
const payload = {
|
|
44
|
+
eventType: "device.battery.set",
|
|
45
|
+
properties: {
|
|
46
|
+
deviceId,
|
|
47
|
+
batteryLevel,
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
await publishAudit(payload);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Interfaces for DeviceService methods
|
|
2
2
|
|
|
3
|
-
export interface
|
|
3
|
+
export interface IDevice {
|
|
4
4
|
deviceId: string;
|
|
5
5
|
propertyId: string;
|
|
6
6
|
name: string;
|
|
@@ -45,4 +45,5 @@ export interface IDeviceCreateParams {
|
|
|
45
45
|
metaData?: object;
|
|
46
46
|
createdAt?: Date;
|
|
47
47
|
updatedAt?: Date;
|
|
48
|
+
hubDeviceDetails?: IDevice[];
|
|
48
49
|
}
|
|
@@ -1,18 +1,20 @@
|
|
|
1
|
-
//
|
|
2
|
-
|
|
1
|
+
//TODO: Import Redis
|
|
3
2
|
import axios from "axios";
|
|
4
3
|
import {
|
|
5
4
|
getConfig,
|
|
6
5
|
checkAwsEnv,
|
|
7
6
|
ensureAuditInitialized,
|
|
8
7
|
} from "../../../config/config";
|
|
9
|
-
import {
|
|
8
|
+
import { IDevice } from "../interfaces";
|
|
10
9
|
import { eventDispatcher } from "dt-pub-sub";
|
|
11
10
|
import { publishAudit } from "dt-audit-library";
|
|
11
|
+
import { EventHandler } from "../handler/EventHandler";
|
|
12
|
+
import { isEqual } from "lodash";
|
|
12
13
|
|
|
13
14
|
export class DeviceService {
|
|
14
15
|
private readonly baseUrl: string;
|
|
15
16
|
private readonly source = "dt-common-device";
|
|
17
|
+
private readonly eventHandler: EventHandler;
|
|
16
18
|
|
|
17
19
|
constructor() {
|
|
18
20
|
const { DEVICE_SERVICE } = getConfig();
|
|
@@ -24,9 +26,10 @@ export class DeviceService {
|
|
|
24
26
|
this.baseUrl = DEVICE_SERVICE;
|
|
25
27
|
checkAwsEnv();
|
|
26
28
|
ensureAuditInitialized();
|
|
29
|
+
this.eventHandler = new EventHandler();
|
|
27
30
|
}
|
|
28
31
|
|
|
29
|
-
async createDevice(body:
|
|
32
|
+
async createDevice(body: IDevice): Promise<void> {
|
|
30
33
|
await eventDispatcher.publishEvent("device.created", body, this.source);
|
|
31
34
|
const payload = {
|
|
32
35
|
eventType: "device.created",
|
|
@@ -37,13 +40,30 @@ export class DeviceService {
|
|
|
37
40
|
await publishAudit(payload);
|
|
38
41
|
}
|
|
39
42
|
|
|
40
|
-
async getDevice(
|
|
41
|
-
|
|
43
|
+
async getDevice(
|
|
44
|
+
deviceId: string,
|
|
45
|
+
withHubDetails: boolean = false
|
|
46
|
+
): Promise<IDevice> {
|
|
47
|
+
return await axios.get(
|
|
48
|
+
`${this.baseUrl}/devices/${deviceId}?withHubDetails=${withHubDetails} `
|
|
49
|
+
);
|
|
42
50
|
}
|
|
43
51
|
|
|
44
|
-
async
|
|
52
|
+
async getDevices(
|
|
53
|
+
deviceIds: string[],
|
|
54
|
+
withHubDetails: boolean = false
|
|
55
|
+
): Promise<IDevice[]> {
|
|
45
56
|
return await axios.get(`${this.baseUrl}/devices`, {
|
|
46
|
-
params: {
|
|
57
|
+
params: { deviceIds, withHubDetails },
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async getPropertyDevices(
|
|
62
|
+
propertyId: string,
|
|
63
|
+
withHubDetails: boolean = false
|
|
64
|
+
): Promise<IDevice[]> {
|
|
65
|
+
return await axios.get(`${this.baseUrl}/devices`, {
|
|
66
|
+
params: { propertyId, withHubDetails },
|
|
47
67
|
});
|
|
48
68
|
}
|
|
49
69
|
|
|
@@ -82,43 +102,44 @@ export class DeviceService {
|
|
|
82
102
|
return await axios.get(`${this.baseUrl}/devices/${deviceId}/state`);
|
|
83
103
|
}
|
|
84
104
|
|
|
105
|
+
async setState(deviceId: string, state: any) {
|
|
106
|
+
// If old status and new status are different
|
|
107
|
+
const oldState = await this.getState(deviceId);
|
|
108
|
+
if (!isEqual(oldState, state)) {
|
|
109
|
+
this.eventHandler.onStateChange(deviceId, state);
|
|
110
|
+
}
|
|
111
|
+
// If offline, check the time difference between the last offline status and current time
|
|
112
|
+
// Compare it with the baseline profile time of the device. If the difference is higher, then raise an operational alert
|
|
113
|
+
//deviceAlertService.raiseOperationalAlert();
|
|
114
|
+
}
|
|
115
|
+
|
|
85
116
|
async getStatus(deviceId: string) {
|
|
86
117
|
return await axios.get(`${this.baseUrl}/devices/${deviceId}/status`);
|
|
87
118
|
}
|
|
88
119
|
|
|
89
120
|
async setStatus(deviceId: string, status: any) {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
this.
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
deviceId,
|
|
99
|
-
status,
|
|
100
|
-
},
|
|
101
|
-
};
|
|
102
|
-
await publishAudit(payload);
|
|
121
|
+
// If old status and new status are different
|
|
122
|
+
const oldStatus = await this.getStatus(deviceId);
|
|
123
|
+
if (!isEqual(oldStatus, status)) {
|
|
124
|
+
this.eventHandler.onStatusChange(deviceId, status);
|
|
125
|
+
}
|
|
126
|
+
// If offline, check the time difference between the last offline status and current time
|
|
127
|
+
// Compare it with the baseline profile time of the device. If the difference is higher, then raise an operational alert
|
|
128
|
+
//deviceAlertService.raiseOperationalAlert();
|
|
103
129
|
}
|
|
104
130
|
|
|
105
131
|
async getBatteryLevel(deviceId: string) {
|
|
106
132
|
return await axios.get(`${this.baseUrl}/devices/${deviceId}/battery-level`);
|
|
107
133
|
}
|
|
108
134
|
async setBatteryLevel(deviceId: string, batteryLevel: number) {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
)
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
deviceId,
|
|
118
|
-
batteryLevel,
|
|
119
|
-
},
|
|
120
|
-
};
|
|
121
|
-
await publishAudit(payload);
|
|
135
|
+
// If old status and new status are different
|
|
136
|
+
|
|
137
|
+
// and the last battery level received time more than 8 hours {
|
|
138
|
+
this.eventHandler.onBatteryLevelChange(deviceId, batteryLevel);
|
|
139
|
+
// if(batteryLevel < propertySettingBatteryThreshold) {
|
|
140
|
+
// deviceAlertService.raiseEnergyAlert({});
|
|
141
|
+
// }
|
|
142
|
+
// }
|
|
122
143
|
}
|
|
123
144
|
async getMetaData(deviceId: string) {
|
|
124
145
|
return await axios.get(`${this.baseUrl}/devices/${deviceId}/metaData`);
|
|
@@ -138,4 +159,30 @@ export class DeviceService {
|
|
|
138
159
|
};
|
|
139
160
|
await publishAudit(payload);
|
|
140
161
|
}
|
|
162
|
+
|
|
163
|
+
async getDeviceByZone(zoneId: string) {
|
|
164
|
+
try {
|
|
165
|
+
//Step 1: Check if zone data available in redis
|
|
166
|
+
//Step 2: If not available, fetch uaing smart-cloud-node zone api
|
|
167
|
+
//Step 3: Store the zone data in redis
|
|
168
|
+
//Step 4: Return the zone data
|
|
169
|
+
//Step 5: If zone data is available in redis, return the zone data
|
|
170
|
+
} catch (error) {
|
|
171
|
+
console.log(error);
|
|
172
|
+
throw new Error("Failed to get device by zone");
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
async getDevicesByAccessGroup(accessGroupId: string) {
|
|
177
|
+
try {
|
|
178
|
+
//Step 1: Check if access group data available in redis
|
|
179
|
+
//Step 2: If not available, fetch using smart-cloud-node access-group api
|
|
180
|
+
//Step 3: Store the access group data in redis
|
|
181
|
+
//Step 4: Return the access group data
|
|
182
|
+
//Step 5: If access group data is available in redis, return the access group data
|
|
183
|
+
} catch (error) {
|
|
184
|
+
console.log(error);
|
|
185
|
+
throw new Error("Failed to get devices by access group");
|
|
186
|
+
}
|
|
187
|
+
}
|
|
141
188
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import axios from "axios";
|
|
2
2
|
import { getConfig } from "../../../config/config";
|
|
3
|
-
import {
|
|
3
|
+
import { IHub } from "../interfaces";
|
|
4
4
|
|
|
5
5
|
export class HubService {
|
|
6
6
|
private readonly baseUrl: string;
|
|
@@ -15,18 +15,18 @@ export class HubService {
|
|
|
15
15
|
this.baseUrl = DEVICE_SERVICE;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
async addHub(body:
|
|
18
|
+
async addHub(body: IHub): Promise<any> {
|
|
19
19
|
return await axios.post(`${this.baseUrl}/devices/hubs`, body);
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
//get hubs takes an array of hub ids as query params
|
|
23
|
-
async getHubs(hubIds: string[]): Promise<
|
|
23
|
+
async getHubs(hubIds: string[]): Promise<IHub[]> {
|
|
24
24
|
const query = hubIds && hubIds.length ? `?ids=${hubIds.join(",")}` : "";
|
|
25
25
|
return await axios.get(`${this.baseUrl}/devices/hubs${query}`);
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
//get hub takes a hub id in params
|
|
29
|
-
async getHub(hubId: string): Promise<
|
|
29
|
+
async getHub(hubId: string): Promise<IHub> {
|
|
30
30
|
return await axios.get(`${this.baseUrl}/devices/hubs/${hubId}`);
|
|
31
31
|
}
|
|
32
32
|
|
package/src/index.ts
CHANGED
|
@@ -6,10 +6,7 @@ export {
|
|
|
6
6
|
CloudHubService,
|
|
7
7
|
CloudConnectionService,
|
|
8
8
|
} from "./device/cloud/services";
|
|
9
|
-
export {
|
|
10
|
-
LocalDeviceService,
|
|
11
|
-
LocalHubService,
|
|
12
|
-
} from "./device/local/services";
|
|
9
|
+
export { LocalDeviceService, LocalHubService } from "./device/local/services";
|
|
13
10
|
|
|
14
11
|
export * as cloudInterfaces from "./device/cloud/interfaces";
|
|
15
12
|
export * from "./device/cloud/types";
|