dt-common-device 14.0.1 → 14.0.2
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/dist/Integrations/twilio/twilio.service.d.ts +1 -2
- package/dist/Integrations/twilio/twilio.service.js +16 -20
- package/dist/alerts/alert.types.d.ts +5 -1
- package/dist/alerts/alert.types.js +4 -0
- package/dist/audit/AuditUtils.js +13 -0
- package/dist/audit/IAuditProperties.d.ts +7 -1
- package/dist/audit/IAuditProperties.js +1 -0
- package/dist/audit/PushAudit.d.ts +17 -1
- package/dist/audit/PushAudit.js +51 -9
- package/dist/config/config.d.ts +2 -1
- package/dist/config/config.js +14 -38
- package/dist/config/config.types.d.ts +3 -3
- package/dist/config/constants.js +1 -0
- package/dist/constants/ConnectionProviders.d.ts +4 -0
- package/dist/constants/ConnectionProviders.js +4 -0
- package/dist/constants/Event.d.ts +22 -0
- package/dist/constants/Event.js +22 -0
- package/dist/copilotQueue/utils/queueManager.js +2 -35
- package/dist/cronicle/Cronicle.service.d.ts +3 -3
- package/dist/cronicle/Cronicle.service.js +4 -3
- package/dist/cronicle/ICronicle.interface.d.ts +67 -0
- package/dist/db/db.js +32 -28
- package/dist/emails/emailService.js +23 -41
- package/dist/entities/admin/Admin.repository.d.ts +11 -2
- package/dist/entities/admin/Admin.repository.js +73 -2
- package/dist/entities/admin/Admin.service.d.ts +2 -1
- package/dist/entities/admin/Admin.service.js +12 -1
- package/dist/entities/admin/IAdmin.d.ts +40 -0
- package/dist/entities/connection/Connection.repository.js +12 -4
- package/dist/entities/connection/Connection.service.d.ts +2 -1
- package/dist/entities/connection/IConnection.d.ts +4 -1
- package/dist/entities/connection/IConnection.js +3 -0
- package/dist/entities/device/cloud/interfaces/IRawDevice.d.ts +3 -1
- package/dist/entities/device/cloud/interfaces/IRawDevice.js +2 -0
- package/dist/entities/device/local/interfaces/IDevice.d.ts +6 -0
- package/dist/entities/device/local/repository/Device.repository.d.ts +7 -0
- package/dist/entities/device/local/repository/Device.repository.js +11 -0
- package/dist/entities/device/local/services/Device.service.d.ts +8 -0
- package/dist/entities/device/local/services/Device.service.js +21 -0
- package/dist/entities/pms/IPms.d.ts +2 -1
- package/dist/entities/pms/IPms.js +1 -0
- package/dist/entities/pms/pms.service.js +22 -11
- package/dist/entities/property/Property.repository.d.ts +1 -0
- package/dist/entities/property/Property.repository.js +5 -0
- package/dist/entities/property/Property.service.d.ts +1 -0
- package/dist/entities/property/Property.service.js +6 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2 -0
- package/dist/issues/issue.types.d.ts +9 -2
- package/dist/issues/issue.types.js +8 -0
- package/dist/queue/entities/HybridHttpQueue.d.ts +0 -1
- package/dist/queue/entities/HybridHttpQueue.js +3 -5
- package/dist/queue/utils/queueUtils.d.ts +2 -2
- package/dist/queue/utils/queueUtils.js +16 -25
- package/dist/queue/utils/rateLimit.utils.js +19 -1
- package/dist/utils/http.utils.d.ts +3 -1
- package/dist/utils/http.utils.js +8 -0
- package/dist/webhookQueue/services/WebhookQueueService.js +3 -36
- package/package.json +3 -2
|
@@ -148,19 +148,30 @@ let PmsService = (() => {
|
|
|
148
148
|
if (!scheduleAccessGroups || scheduleAccessGroups.length === 0) {
|
|
149
149
|
return false;
|
|
150
150
|
}
|
|
151
|
-
|
|
152
|
-
//
|
|
153
|
-
|
|
151
|
+
// Split reservations often have multiple rows per schedule with the *same* startTime
|
|
152
|
+
// across different access groups. Sorting *all* rows globally made the last two entries
|
|
153
|
+
// always different access groups. Here we only consider rows for this accessGroupId,
|
|
154
|
+
// ordered by startTime, and require the latest segment to follow the previous one in time.
|
|
155
|
+
const rowsForThisAccessGroup = scheduleAccessGroups
|
|
156
|
+
.filter((row) => row.accessGroupId === accessGroupId)
|
|
157
|
+
.sort((a, b) => new Date(a.startTime).getTime() - new Date(b.startTime).getTime());
|
|
158
|
+
if (rowsForThisAccessGroup.length < 2) {
|
|
154
159
|
return false;
|
|
155
160
|
}
|
|
156
|
-
const
|
|
157
|
-
const
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
);
|
|
161
|
+
const previousSegment = rowsForThisAccessGroup.at(-2);
|
|
162
|
+
const currentSegment = rowsForThisAccessGroup.at(-1);
|
|
163
|
+
if (!previousSegment.endTime || !currentSegment.startTime) {
|
|
164
|
+
return false;
|
|
165
|
+
}
|
|
166
|
+
const previousStart = new Date(previousSegment.startTime).getTime();
|
|
167
|
+
const previousEnd = new Date(previousSegment.endTime).getTime();
|
|
168
|
+
const currentStart = new Date(currentSegment.startTime).getTime();
|
|
169
|
+
// Same startTime as previous row (duplicate import, extra map row, etc.) — still reuse one code.
|
|
170
|
+
if (currentStart === previousStart) {
|
|
171
|
+
return true;
|
|
172
|
+
}
|
|
173
|
+
// Consecutive nights: current segment starts at or after previous segment ends.
|
|
174
|
+
return currentStart >= previousEnd;
|
|
164
175
|
}
|
|
165
176
|
};
|
|
166
177
|
__setFunctionName(_classThis, "PmsService");
|
|
@@ -4,5 +4,6 @@ export declare class PropertyRepository {
|
|
|
4
4
|
constructor();
|
|
5
5
|
getPropertyPreferences(propertyId: string, keys?: string[]): Promise<IPropertySettings | null>;
|
|
6
6
|
getProperty(propertyId: string): Promise<IProperty | null>;
|
|
7
|
+
getOrganizationByPropertyId(propertyId: string): Promise<any | null>;
|
|
7
8
|
getAllProperties(): Promise<any[]>;
|
|
8
9
|
}
|
|
@@ -41,6 +41,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
41
41
|
exports.PropertyRepository = void 0;
|
|
42
42
|
const db_1 = require("../../db");
|
|
43
43
|
const typedi_1 = require("typedi");
|
|
44
|
+
const utils_1 = require("../../utils");
|
|
44
45
|
let PropertyRepository = (() => {
|
|
45
46
|
let _classDecorators = [(0, typedi_1.Service)()];
|
|
46
47
|
let _classDescriptor;
|
|
@@ -84,6 +85,10 @@ let PropertyRepository = (() => {
|
|
|
84
85
|
}
|
|
85
86
|
return null;
|
|
86
87
|
}
|
|
88
|
+
async getOrganizationByPropertyId(propertyId) {
|
|
89
|
+
const organization = await (0, utils_1.getAdminServiceAxiosInstance)().get(`/properties/${propertyId}/organizations`);
|
|
90
|
+
return organization.data.data;
|
|
91
|
+
}
|
|
87
92
|
async getAllProperties() {
|
|
88
93
|
try {
|
|
89
94
|
//Retrieve all the properties ids from the database where isDeleted is false
|
|
@@ -5,4 +5,5 @@ export declare class LocalPropertyService {
|
|
|
5
5
|
getProperty(propertyId: string): Promise<import("./IProperty").IProperty | null>;
|
|
6
6
|
getPropertyTimeZone(propertyId: string): Promise<string>;
|
|
7
7
|
getAllProperties(): Promise<any[]>;
|
|
8
|
+
getOrganizationByPropertyId(propertyId: string): Promise<any>;
|
|
8
9
|
}
|
|
@@ -110,6 +110,12 @@ let LocalPropertyService = (() => {
|
|
|
110
110
|
const properties = await this.propertyRepository.getAllProperties();
|
|
111
111
|
return properties;
|
|
112
112
|
}
|
|
113
|
+
async getOrganizationByPropertyId(propertyId) {
|
|
114
|
+
if (!propertyId) {
|
|
115
|
+
throw new Error("Property ID is required");
|
|
116
|
+
}
|
|
117
|
+
return await this.propertyRepository.getOrganizationByPropertyId(propertyId);
|
|
118
|
+
}
|
|
113
119
|
};
|
|
114
120
|
__setFunctionName(_classThis, "LocalPropertyService");
|
|
115
121
|
(() => {
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -75,3 +75,5 @@ __exportStar(require("./Integrations"), exports);
|
|
|
75
75
|
__exportStar(require("./Integrations/twilio"), exports);
|
|
76
76
|
// Export Email Service
|
|
77
77
|
__exportStar(require("./emails/emailService"), exports);
|
|
78
|
+
// Export Notification Service
|
|
79
|
+
__exportStar(require("./entities/notification"), exports);
|
|
@@ -31,6 +31,7 @@ export declare enum EntitySubType {
|
|
|
31
31
|
ON_PREM_SERVER = "ON_PREM_SERVER",
|
|
32
32
|
CLOUDBEDS = "CLOUDBEDS",
|
|
33
33
|
STAYNTOUCH = "STAYNTOUCH",
|
|
34
|
+
INFOR = "INFOR",
|
|
34
35
|
HOTELKEY = "HOTELKEY",
|
|
35
36
|
YANOLJA = "YANOLJA",
|
|
36
37
|
SKYTOUCH = "SKYTOUCH",
|
|
@@ -49,7 +50,9 @@ export declare enum EntitySubType {
|
|
|
49
50
|
SALTOSPACE = "SALTOSPACE",
|
|
50
51
|
SCHLAGE = "SCHLAGE",
|
|
51
52
|
LOCKLY = "LOCKLY",
|
|
52
|
-
SIFELY = "SIFELY"
|
|
53
|
+
SIFELY = "SIFELY",
|
|
54
|
+
GEOCODING = "GEOCODING",
|
|
55
|
+
ULTRALOCK = "ULTRALOCK"
|
|
53
56
|
}
|
|
54
57
|
export declare enum IssueStatus {
|
|
55
58
|
PENDING = "PENDING",
|
|
@@ -85,7 +88,9 @@ export declare enum IssueType {
|
|
|
85
88
|
LOW_GUEST_CODES = "LOW_GUEST_CODES",
|
|
86
89
|
PMS_CODE_NOT_DELIVERED = "PMS_CODE_NOT_DELIVERED",
|
|
87
90
|
SCHEDULE_CODE_NOT_ASSIGNED = "SCHEDULE_CODE_NOT_ASSIGNED",
|
|
88
|
-
MISSING_ACCESS_GROUP = "MISSING_ACCESS_GROUP"
|
|
91
|
+
MISSING_ACCESS_GROUP = "MISSING_ACCESS_GROUP",
|
|
92
|
+
GEOCODING_FAILED = "GEOCODING_FAILED",
|
|
93
|
+
CLOUD_PROVIDER_EVENTS_ISSUE = "CLOUD_PROVIDER_EVENTS_ISSUE"
|
|
89
94
|
}
|
|
90
95
|
export declare const IssueDescriptions: {
|
|
91
96
|
BATTERY_LOW: string;
|
|
@@ -101,6 +106,8 @@ export declare const IssueDescriptions: {
|
|
|
101
106
|
PMS_CODE_NOT_DELIVERED: string;
|
|
102
107
|
SCHEDULE_CODE_NOT_ASSIGNED: string;
|
|
103
108
|
MISSING_ACCESS_GROUP: string;
|
|
109
|
+
GEOCODING_FAILED: string;
|
|
110
|
+
CLOUD_PROVIDER_EVENTS_ISSUE: string;
|
|
104
111
|
};
|
|
105
112
|
export interface IssueDocument {
|
|
106
113
|
id: string;
|
|
@@ -40,6 +40,7 @@ var EntitySubType;
|
|
|
40
40
|
// PMS PROVIDERS
|
|
41
41
|
EntitySubType["CLOUDBEDS"] = "CLOUDBEDS";
|
|
42
42
|
EntitySubType["STAYNTOUCH"] = "STAYNTOUCH";
|
|
43
|
+
EntitySubType["INFOR"] = "INFOR";
|
|
43
44
|
EntitySubType["HOTELKEY"] = "HOTELKEY";
|
|
44
45
|
EntitySubType["YANOLJA"] = "YANOLJA";
|
|
45
46
|
EntitySubType["SKYTOUCH"] = "SKYTOUCH";
|
|
@@ -60,6 +61,9 @@ var EntitySubType;
|
|
|
60
61
|
EntitySubType["SCHLAGE"] = "SCHLAGE";
|
|
61
62
|
EntitySubType["LOCKLY"] = "LOCKLY";
|
|
62
63
|
EntitySubType["SIFELY"] = "SIFELY";
|
|
64
|
+
// OTHER
|
|
65
|
+
EntitySubType["GEOCODING"] = "GEOCODING";
|
|
66
|
+
EntitySubType["ULTRALOCK"] = "ULTRALOCK";
|
|
63
67
|
})(EntitySubType || (exports.EntitySubType = EntitySubType = {}));
|
|
64
68
|
var IssueStatus;
|
|
65
69
|
(function (IssueStatus) {
|
|
@@ -92,6 +96,8 @@ var IssueType;
|
|
|
92
96
|
IssueType["PMS_CODE_NOT_DELIVERED"] = "PMS_CODE_NOT_DELIVERED";
|
|
93
97
|
IssueType["SCHEDULE_CODE_NOT_ASSIGNED"] = "SCHEDULE_CODE_NOT_ASSIGNED";
|
|
94
98
|
IssueType["MISSING_ACCESS_GROUP"] = "MISSING_ACCESS_GROUP";
|
|
99
|
+
IssueType["GEOCODING_FAILED"] = "GEOCODING_FAILED";
|
|
100
|
+
IssueType["CLOUD_PROVIDER_EVENTS_ISSUE"] = "CLOUD_PROVIDER_EVENTS_ISSUE";
|
|
95
101
|
})(IssueType || (exports.IssueType = IssueType = {}));
|
|
96
102
|
exports.IssueDescriptions = {
|
|
97
103
|
[IssueType.BATTERY_LOW]: "The issue is raised when the battery level is lower than the threshold.",
|
|
@@ -107,4 +113,6 @@ exports.IssueDescriptions = {
|
|
|
107
113
|
[IssueType.PMS_CODE_NOT_DELIVERED]: "The issue is raised when the code is not delivered to the PMS system.",
|
|
108
114
|
[IssueType.SCHEDULE_CODE_NOT_ASSIGNED]: "The issue is raised when the code is not assigned to a schedule.",
|
|
109
115
|
[IssueType.MISSING_ACCESS_GROUP]: "The issue is raised when the access group is missing from devicethread but present in PMS system.",
|
|
116
|
+
[IssueType.GEOCODING_FAILED]: "The issue is raised when the system fails to retrieve latitude and longitude from the provided property address.",
|
|
117
|
+
[IssueType.CLOUD_PROVIDER_EVENTS_ISSUE]: "The issue is raised when the system fails to automatically resolve the event subscription for the cloud provider (Lock or PMS)",
|
|
110
118
|
};
|
|
@@ -54,7 +54,6 @@ let HybridHttpQueue = (() => {
|
|
|
54
54
|
// BullMQ queues and workers for persistent storage
|
|
55
55
|
this.queues = new Map();
|
|
56
56
|
this.workers = new Map();
|
|
57
|
-
this.jobResults = new Map();
|
|
58
57
|
this.rateLimitConfigs = rateLimit_utils_1.RateLimitUtils.initializeRateLimitConfigs();
|
|
59
58
|
}
|
|
60
59
|
async addToQueue(connectionId, provider, url, method, options) {
|
|
@@ -89,7 +88,7 @@ let HybridHttpQueue = (() => {
|
|
|
89
88
|
console.log(`[${new Date().toISOString()}] [HybridHttpQueue] Job added to queue - JobId: ${jobId}, QueueKey: ${queueKey}`);
|
|
90
89
|
// Initialize worker if not exists
|
|
91
90
|
console.log(`[${new Date().toISOString()}] [HybridHttpQueue] Initializing worker for queue: ${queueKey}`);
|
|
92
|
-
queueUtils_1.QueueUtils.getOrCreateWorker(queueKey, this.workers, this.processHttpRequest.bind(this)
|
|
91
|
+
queueUtils_1.QueueUtils.getOrCreateWorker(queueKey, this.workers, this.processHttpRequest.bind(this));
|
|
93
92
|
// Verify worker was created
|
|
94
93
|
if (this.workers.has(queueKey)) {
|
|
95
94
|
console.log(`[${new Date().toISOString()}] [HybridHttpQueue] Worker confirmed for queue: ${queueKey}`);
|
|
@@ -100,7 +99,7 @@ let HybridHttpQueue = (() => {
|
|
|
100
99
|
(0, config_1.getConfig)().LOGGER.info(`Request queued: ${method} ${url} -> ${provider} [${connectionId}]. Job ID: ${jobId}, Delay: ${delay}ms`);
|
|
101
100
|
// Wait for job completion and return result
|
|
102
101
|
// Simple: delay + windowMs + HTTP buffer timeout
|
|
103
|
-
return queueUtils_1.QueueUtils.waitForJobCompletion(jobId, queueKey, delay, windowMs);
|
|
102
|
+
return queueUtils_1.QueueUtils.waitForJobCompletion(jobId, queueKey, delay, windowMs, this.queues);
|
|
104
103
|
}
|
|
105
104
|
async processHttpRequest(job) {
|
|
106
105
|
// Log immediately when worker picks up the job
|
|
@@ -148,7 +147,7 @@ let HybridHttpQueue = (() => {
|
|
|
148
147
|
return this.handleRequest(url, method, httpCallOption);
|
|
149
148
|
}
|
|
150
149
|
async handleRequest(url, method, options) {
|
|
151
|
-
const { connectionId, provider
|
|
150
|
+
const { connectionId, provider } = jobUtils_1.JobUtils.extractConnectionDetails(options);
|
|
152
151
|
// Check rate limit first
|
|
153
152
|
const allowed = await rateLimit_utils_1.RateLimitUtils.isRateLimitAllowed(connectionId, provider, this.rateLimitConfigs);
|
|
154
153
|
if (allowed) {
|
|
@@ -170,7 +169,6 @@ let HybridHttpQueue = (() => {
|
|
|
170
169
|
]);
|
|
171
170
|
this.workers.clear();
|
|
172
171
|
this.queues.clear();
|
|
173
|
-
this.jobResults.clear();
|
|
174
172
|
(0, config_1.getConfig)().LOGGER.info("HTTP queues shutdown complete");
|
|
175
173
|
}
|
|
176
174
|
};
|
|
@@ -4,9 +4,9 @@ export declare class QueueUtils {
|
|
|
4
4
|
static getQueueKey(microservice: string, connectionId: string, provider: string): string;
|
|
5
5
|
static getRequestQueueKey(connectionId: string, provider: string): string;
|
|
6
6
|
static getOrCreateQueue(queueKey: string, queues: Map<string, any>): any;
|
|
7
|
-
static getOrCreateWorker(queueKey: string, workers: Map<string, any>, processFunction: (job: any) => Promise<any
|
|
7
|
+
static getOrCreateWorker(queueKey: string, workers: Map<string, any>, processFunction: (job: any) => Promise<any>): void;
|
|
8
8
|
static waitForRateLimitExpiry(connectionId: string, provider: string, rateLimitConfigs: Map<string, IRateLimitConfig>): Promise<void>;
|
|
9
9
|
static executeHttpRequest(url: string, method: string, options: HttpCallOption, connectionId: string, provider: string): Promise<any>;
|
|
10
10
|
static addJobToQueue(queueKey: string, jobData: any, delay: number, queues: Map<string, any>): Promise<string>;
|
|
11
|
-
static waitForJobCompletion(jobId: string, queueKey: string, jobDelay
|
|
11
|
+
static waitForJobCompletion(jobId: string, queueKey: string, jobDelay: number | undefined, windowMs: number | undefined, queues: Map<string, any>): Promise<any>;
|
|
12
12
|
}
|
|
@@ -24,7 +24,7 @@ class QueueUtils {
|
|
|
24
24
|
}))
|
|
25
25
|
.get(queueKey));
|
|
26
26
|
}
|
|
27
|
-
static getOrCreateWorker(queueKey, workers, processFunction
|
|
27
|
+
static getOrCreateWorker(queueKey, workers, processFunction) {
|
|
28
28
|
if (workers.has(queueKey)) {
|
|
29
29
|
const existingWorker = workers.get(queueKey);
|
|
30
30
|
console.log(`[${new Date().toISOString()}] [getOrCreateWorker] Worker already exists for queue: ${queueKey}, checking if it's running...`);
|
|
@@ -35,6 +35,8 @@ class QueueUtils {
|
|
|
35
35
|
console.log(`[${new Date().toISOString()}] [getOrCreateWorker] Existing worker running status: ${isRunning}`);
|
|
36
36
|
if (!isRunning) {
|
|
37
37
|
console.log(`[${new Date().toISOString()}] [getOrCreateWorker] Worker not running, closing and recreating...`);
|
|
38
|
+
// Stale worker detected — surface at WARN level with count context
|
|
39
|
+
(0, config_1.getConfig)().LOGGER.warn(`Stale worker for queue ${queueKey} (not running), recreating. Active workers before recreate: ${workers.size}`);
|
|
38
40
|
try {
|
|
39
41
|
existingWorker.close();
|
|
40
42
|
}
|
|
@@ -95,24 +97,11 @@ class QueueUtils {
|
|
|
95
97
|
console.log(`[${new Date().toISOString()}] [Worker Event] Job completed - JobId: ${job.id}, QueueKey: ${queueKey}, ReturnValueType: ${typeof job.returnvalue}, HasReturnValue: ${job.returnvalue !== undefined}`);
|
|
96
98
|
(0, config_1.getConfig)().LOGGER.info(`HTTP request completed: ${job.id} [${queueKey}]`);
|
|
97
99
|
const result = job.returnvalue;
|
|
98
|
-
const jobResult = {
|
|
99
|
-
result,
|
|
100
|
-
resolved: true,
|
|
101
|
-
timestamp: Date.now(),
|
|
102
|
-
};
|
|
103
100
|
console.log(`[${new Date().toISOString()}] [Worker Event] Storing job result - JobId: ${job.id}, ResultType: ${typeof result}, Resolved: true`);
|
|
104
|
-
jobResults.set(job.id, jobResult);
|
|
105
|
-
console.log(`[${new Date().toISOString()}] [Worker Event] Job result stored in jobResults - JobId: ${job.id}, StoredResult: ${JSON.stringify(jobResult)}`);
|
|
106
101
|
});
|
|
107
102
|
worker.on("failed", (job, err) => {
|
|
108
103
|
console.log(`[${new Date().toISOString()}] [Worker Event] Job failed - JobId: ${job?.id}, QueueKey: ${queueKey}, Error: ${err.message}`);
|
|
109
104
|
(0, config_1.getConfig)().LOGGER.error(`HTTP request failed: ${job?.id} [${queueKey}], Error: ${err.message}`);
|
|
110
|
-
jobResults.set(job.id, {
|
|
111
|
-
error: err.message,
|
|
112
|
-
resolved: true,
|
|
113
|
-
timestamp: Date.now(),
|
|
114
|
-
});
|
|
115
|
-
console.log(`[${new Date().toISOString()}] [Worker Event] Job error stored in jobResults - JobId: ${job?.id}`);
|
|
116
105
|
});
|
|
117
106
|
worker.on("error", (err) => {
|
|
118
107
|
console.log(`[${new Date().toISOString()}] [Worker Event] Worker error - QueueKey: ${queueKey}, Error: ${err.message}, Stack: ${err.stack}`);
|
|
@@ -129,7 +118,8 @@ class QueueUtils {
|
|
|
129
118
|
});
|
|
130
119
|
workers.set(queueKey, worker);
|
|
131
120
|
console.log(`[${new Date().toISOString()}] [getOrCreateWorker] Worker initialized and stored for queue: ${queueKey}`);
|
|
132
|
-
|
|
121
|
+
// Log total active workers — a growing count here signals a connection-proliferation leak
|
|
122
|
+
(0, config_1.getConfig)().LOGGER.info(`Worker initialized for queue: ${queueKey} (total active workers: ${workers.size})`);
|
|
133
123
|
}
|
|
134
124
|
static async waitForRateLimitExpiry(connectionId, provider, rateLimitConfigs) {
|
|
135
125
|
const key = `rate_limit:${provider}:${connectionId}`;
|
|
@@ -215,22 +205,25 @@ class QueueUtils {
|
|
|
215
205
|
queue.getActiveCount(),
|
|
216
206
|
]);
|
|
217
207
|
console.log(`[${new Date().toISOString()}] [addJobToQueue] Queue state - QueueKey: ${queueKey}, Waiting: ${waiting}, Delayed: ${delayed}, Active: ${active}`);
|
|
208
|
+
// Warn via structured logger when backlog is building — correlates with heap stats
|
|
209
|
+
if (waiting + delayed > 1) {
|
|
210
|
+
(0, config_1.getConfig)().LOGGER.warn(`Queue backlog [${queueKey}]: waiting=${waiting} delayed=${delayed} active=${active}`);
|
|
211
|
+
}
|
|
218
212
|
}
|
|
219
213
|
catch (e) {
|
|
220
214
|
// Ignore errors getting queue state
|
|
221
215
|
}
|
|
222
216
|
return job.id;
|
|
223
217
|
}
|
|
224
|
-
static async waitForJobCompletion(jobId, queueKey, jobDelay = 0, windowMs = 60000) {
|
|
218
|
+
static async waitForJobCompletion(jobId, queueKey, jobDelay = 0, windowMs = 60000, queues) {
|
|
225
219
|
// Simple: delay (wait) + windowMs (execute) + HTTP timeout buffer
|
|
226
220
|
const httpTimeout = 60000;
|
|
227
221
|
const totalTimeout = jobDelay + windowMs + httpTimeout;
|
|
228
222
|
console.log(`[${new Date().toISOString()}] [waitForJobCompletion] Waiting for job ${jobId} - delay: ${jobDelay}ms, timeout: ${totalTimeout}ms`);
|
|
229
223
|
const startTime = Date.now();
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
});
|
|
224
|
+
// Reuse the existing queue instance — never create a new Queue per call
|
|
225
|
+
// (each new Queue duplicates the Redis connection, causing a connection leak)
|
|
226
|
+
const queue = this.getOrCreateQueue(queueKey, queues);
|
|
234
227
|
return new Promise(async (resolve, reject) => {
|
|
235
228
|
const checkInterval = setInterval(async () => {
|
|
236
229
|
try {
|
|
@@ -239,7 +232,6 @@ class QueueUtils {
|
|
|
239
232
|
const elapsed = Date.now() - startTime;
|
|
240
233
|
if (elapsed > totalTimeout) {
|
|
241
234
|
clearInterval(checkInterval);
|
|
242
|
-
await queue.close();
|
|
243
235
|
return reject(new Error(`Job ${jobId} not found and timeout exceeded`));
|
|
244
236
|
}
|
|
245
237
|
return; // Job not found yet, keep checking
|
|
@@ -249,26 +241,25 @@ class QueueUtils {
|
|
|
249
241
|
console.log(`[${new Date().toISOString()}] [waitForJobCompletion] Job ${jobId} completed`);
|
|
250
242
|
clearInterval(checkInterval);
|
|
251
243
|
const result = job.returnvalue;
|
|
252
|
-
await queue.close();
|
|
253
244
|
return resolve(result);
|
|
254
245
|
}
|
|
255
246
|
if (state === "failed") {
|
|
256
247
|
console.log(`[${new Date().toISOString()}] [waitForJobCompletion] Job ${jobId} failed: ${job.failedReason}`);
|
|
257
248
|
clearInterval(checkInterval);
|
|
258
|
-
await queue.close();
|
|
259
249
|
return reject(new Error(job.failedReason || "Job failed"));
|
|
260
250
|
}
|
|
261
251
|
}
|
|
262
252
|
catch (error) {
|
|
263
253
|
clearInterval(checkInterval);
|
|
264
|
-
await queue.close();
|
|
265
254
|
reject(error);
|
|
266
255
|
}
|
|
267
256
|
}, 500); // Check every 500ms
|
|
268
257
|
// Timeout
|
|
269
258
|
setTimeout(() => {
|
|
270
259
|
clearInterval(checkInterval);
|
|
271
|
-
|
|
260
|
+
// Log via structured logger before rejecting so the timeout is visible in
|
|
261
|
+
// CloudWatch alongside the periodic heap stats — correlates queue stalls with memory pressure.
|
|
262
|
+
(0, config_1.getConfig)().LOGGER.error(`Job ${jobId} timed out after ${totalTimeout}ms [${queueKey}] (jobDelay=${jobDelay}ms)`);
|
|
272
263
|
reject(new Error(`Request timeout: Maximum wait time exceeded (${totalTimeout}ms). Job delay was ${jobDelay}ms.`));
|
|
273
264
|
}, totalTimeout);
|
|
274
265
|
});
|
|
@@ -51,6 +51,12 @@ class RateLimitUtils {
|
|
|
51
51
|
provider: constants_1.CONNECTION_PROVIDERS.STAYNTOUCH,
|
|
52
52
|
maxTimeoutWindowMs: 120000,
|
|
53
53
|
});
|
|
54
|
+
configs.set(constants_1.CONNECTION_PROVIDERS.INFOR, {
|
|
55
|
+
maxRequests: 10,
|
|
56
|
+
windowMs: 10000, // 10 seconds
|
|
57
|
+
provider: constants_1.CONNECTION_PROVIDERS.INFOR,
|
|
58
|
+
maxTimeoutWindowMs: 120000,
|
|
59
|
+
});
|
|
54
60
|
configs.set(constants_1.CONNECTION_PROVIDERS.HOTELKEY, {
|
|
55
61
|
maxRequests: 10,
|
|
56
62
|
windowMs: 10000, // 10 seconds
|
|
@@ -76,7 +82,7 @@ class RateLimitUtils {
|
|
|
76
82
|
maxTimeoutWindowMs: 120000,
|
|
77
83
|
});
|
|
78
84
|
configs.set(constants_1.CONNECTION_PROVIDERS.SMARTTHINGS, {
|
|
79
|
-
maxRequests:
|
|
85
|
+
maxRequests: 25,
|
|
80
86
|
windowMs: 60000, // 1 minute
|
|
81
87
|
provider: constants_1.CONNECTION_PROVIDERS.SMARTTHINGS,
|
|
82
88
|
maxTimeoutWindowMs: 120000,
|
|
@@ -141,6 +147,18 @@ class RateLimitUtils {
|
|
|
141
147
|
provider: constants_1.CONNECTION_PROVIDERS.SIFELY,
|
|
142
148
|
maxTimeoutWindowMs: 120000,
|
|
143
149
|
});
|
|
150
|
+
configs.set(constants_1.CONNECTION_PROVIDERS.DUSAW, {
|
|
151
|
+
maxRequests: 10,
|
|
152
|
+
windowMs: 60000, // 1 minute
|
|
153
|
+
provider: constants_1.CONNECTION_PROVIDERS.DUSAW,
|
|
154
|
+
maxTimeoutWindowMs: 120000,
|
|
155
|
+
});
|
|
156
|
+
configs.set(constants_1.CONNECTION_PROVIDERS.ULTRALOCK, {
|
|
157
|
+
maxRequests: 60,
|
|
158
|
+
windowMs: 60000, // 1 minute — adjust once UltraLock API docs confirm their limit
|
|
159
|
+
provider: constants_1.CONNECTION_PROVIDERS.ULTRALOCK,
|
|
160
|
+
maxTimeoutWindowMs: 120000,
|
|
161
|
+
});
|
|
144
162
|
return configs;
|
|
145
163
|
}
|
|
146
164
|
static async isRateLimitAllowed(connectionId, provider, rateLimitConfigs) {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import axios from "axios";
|
|
1
2
|
/**
|
|
2
3
|
* Validates if a URL is properly formatted and accessible
|
|
3
4
|
*/
|
|
@@ -5,10 +6,11 @@ export declare function validateServiceUrl(url: string): boolean;
|
|
|
5
6
|
/**
|
|
6
7
|
* Creates a properly configured axios instance with error handling
|
|
7
8
|
*/
|
|
8
|
-
export declare function createAxiosInstance(baseURL?: string):
|
|
9
|
+
export declare function createAxiosInstance(baseURL?: string): axios.AxiosInstance;
|
|
9
10
|
export declare function getDeviceServiceAxiosInstance(): any;
|
|
10
11
|
export declare function getAdminServiceAxiosInstance(): any;
|
|
11
12
|
export declare function getMonitoringServiceAxiosInstance(): any;
|
|
13
|
+
export declare function getNotificationServiceAxiosInstance(): any;
|
|
12
14
|
/**
|
|
13
15
|
* Retry function for failed HTTP requests
|
|
14
16
|
*/
|
package/dist/utils/http.utils.js
CHANGED
|
@@ -8,6 +8,7 @@ exports.createAxiosInstance = createAxiosInstance;
|
|
|
8
8
|
exports.getDeviceServiceAxiosInstance = getDeviceServiceAxiosInstance;
|
|
9
9
|
exports.getAdminServiceAxiosInstance = getAdminServiceAxiosInstance;
|
|
10
10
|
exports.getMonitoringServiceAxiosInstance = getMonitoringServiceAxiosInstance;
|
|
11
|
+
exports.getNotificationServiceAxiosInstance = getNotificationServiceAxiosInstance;
|
|
11
12
|
exports.retryRequest = retryRequest;
|
|
12
13
|
const config_1 = require("../config/config");
|
|
13
14
|
const axios_1 = __importDefault(require("axios"));
|
|
@@ -105,6 +106,13 @@ function getMonitoringServiceAxiosInstance() {
|
|
|
105
106
|
}
|
|
106
107
|
return deviceMonitoringServiceAxiosInstance;
|
|
107
108
|
}
|
|
109
|
+
let notificationServiceAxiosInstance = null;
|
|
110
|
+
function getNotificationServiceAxiosInstance() {
|
|
111
|
+
if (!notificationServiceAxiosInstance) {
|
|
112
|
+
notificationServiceAxiosInstance = createAxiosInstance((0, config_1.getNotificationServiceUrl)());
|
|
113
|
+
}
|
|
114
|
+
return notificationServiceAxiosInstance;
|
|
115
|
+
}
|
|
108
116
|
/**
|
|
109
117
|
* Retry function for failed HTTP requests
|
|
110
118
|
*/
|
|
@@ -1,20 +1,4 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
2
|
var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
|
|
19
3
|
function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
|
|
20
4
|
var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
|
|
@@ -49,23 +33,6 @@ var __runInitializers = (this && this.__runInitializers) || function (thisArg, i
|
|
|
49
33
|
}
|
|
50
34
|
return useValue ? value : void 0;
|
|
51
35
|
};
|
|
52
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
53
|
-
var ownKeys = function(o) {
|
|
54
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
55
|
-
var ar = [];
|
|
56
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
57
|
-
return ar;
|
|
58
|
-
};
|
|
59
|
-
return ownKeys(o);
|
|
60
|
-
};
|
|
61
|
-
return function (mod) {
|
|
62
|
-
if (mod && mod.__esModule) return mod;
|
|
63
|
-
var result = {};
|
|
64
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
65
|
-
__setModuleDefault(result, mod);
|
|
66
|
-
return result;
|
|
67
|
-
};
|
|
68
|
-
})();
|
|
69
36
|
var __setFunctionName = (this && this.__setFunctionName) || function (f, name, prefix) {
|
|
70
37
|
if (typeof name === "symbol") name = name.description ? "[".concat(name.description, "]") : "";
|
|
71
38
|
return Object.defineProperty(f, "name", { configurable: true, value: prefix ? "".concat(prefix, " ", name) : name });
|
|
@@ -144,7 +111,7 @@ let WebhookQueueService = (() => {
|
|
|
144
111
|
try {
|
|
145
112
|
// Create queue instance if not exists locally
|
|
146
113
|
if (!this.webhookQueues.has(queueName)) {
|
|
147
|
-
const { Queue } = await
|
|
114
|
+
const { Queue } = await import("bullmq");
|
|
148
115
|
const queue = new Queue(queueName, {
|
|
149
116
|
connection: (0, redis_1.getRedisClient)(),
|
|
150
117
|
});
|
|
@@ -219,7 +186,7 @@ let WebhookQueueService = (() => {
|
|
|
219
186
|
try {
|
|
220
187
|
// Create queue instance if not exists locally
|
|
221
188
|
if (!this.webhookQueues.has(queueName)) {
|
|
222
|
-
const { Queue } = await
|
|
189
|
+
const { Queue } = await import("bullmq");
|
|
223
190
|
const queue = new Queue(queueName, {
|
|
224
191
|
connection: (0, redis_1.getRedisClient)(),
|
|
225
192
|
});
|
|
@@ -245,7 +212,7 @@ let WebhookQueueService = (() => {
|
|
|
245
212
|
if (queues.has(queueKey)) {
|
|
246
213
|
return queues.get(queueKey);
|
|
247
214
|
}
|
|
248
|
-
const { Queue } = await
|
|
215
|
+
const { Queue } = await import("bullmq");
|
|
249
216
|
const queue = new Queue(queueKey, {
|
|
250
217
|
connection: (0, redis_1.getRedisClient)(),
|
|
251
218
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dt-common-device",
|
|
3
|
-
"version": "14.0.
|
|
3
|
+
"version": "14.0.2",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"types": "dist/index.d.ts",
|
|
6
6
|
"files": [
|
|
@@ -43,10 +43,11 @@
|
|
|
43
43
|
"typescript": "^5.8.3"
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
|
-
"@aws-sdk/client-s3": "
|
|
46
|
+
"@aws-sdk/client-s3": "3.1015.0",
|
|
47
47
|
"@aws-sdk/client-ses": "3.1003.0",
|
|
48
48
|
"axios": "1.10.0",
|
|
49
49
|
"bullmq": "5.56.4",
|
|
50
|
+
"class-validator": "0.15.1",
|
|
50
51
|
"dt-audit-library": "1.7.1",
|
|
51
52
|
"dt-pub-sub": "^1.0.0",
|
|
52
53
|
"ioredis": "5.6.1",
|