vantiv.io 1.0.0

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.
Files changed (48) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +864 -0
  3. package/index.js +13 -0
  4. package/package.json +28 -0
  5. package/src/classes/Actions/Awaiter.js +202 -0
  6. package/src/classes/Actions/Channel.js +73 -0
  7. package/src/classes/Actions/Direct.js +263 -0
  8. package/src/classes/Actions/Inventory.js +156 -0
  9. package/src/classes/Actions/Music.js +278 -0
  10. package/src/classes/Actions/Player.js +377 -0
  11. package/src/classes/Actions/Public.js +66 -0
  12. package/src/classes/Actions/Room.js +333 -0
  13. package/src/classes/Actions/Utils.js +29 -0
  14. package/src/classes/Actions/lib/AudioStreaming.js +447 -0
  15. package/src/classes/Caches/MovementCache.js +357 -0
  16. package/src/classes/Handlers/AxiosErrorHandler.js +68 -0
  17. package/src/classes/Handlers/ErrorHandler.js +65 -0
  18. package/src/classes/Handlers/EventHandlers.js +259 -0
  19. package/src/classes/Handlers/WebSocketHandlers.js +54 -0
  20. package/src/classes/Managers/ChannelManager.js +303 -0
  21. package/src/classes/Managers/DanceFloorManagers.js +509 -0
  22. package/src/classes/Managers/Helpers/CleanupManager.js +130 -0
  23. package/src/classes/Managers/Helpers/LoggerManager.js +171 -0
  24. package/src/classes/Managers/Helpers/MetricsManager.js +83 -0
  25. package/src/classes/Managers/Networking/ConnectionManager.js +259 -0
  26. package/src/classes/Managers/Networking/CooldownManager.js +516 -0
  27. package/src/classes/Managers/Networking/EventsManager.js +64 -0
  28. package/src/classes/Managers/Networking/KeepAliveManager.js +109 -0
  29. package/src/classes/Managers/Networking/MessageHandler.js +110 -0
  30. package/src/classes/Managers/Networking/Request.js +329 -0
  31. package/src/classes/Managers/PermissionManager.js +288 -0
  32. package/src/classes/WebApi/Category/Grab.js +98 -0
  33. package/src/classes/WebApi/Category/Item.js +347 -0
  34. package/src/classes/WebApi/Category/Post.js +154 -0
  35. package/src/classes/WebApi/Category/Room.js +137 -0
  36. package/src/classes/WebApi/Category/User.js +88 -0
  37. package/src/classes/WebApi/webapi.js +52 -0
  38. package/src/constants/TypesConstants.js +89 -0
  39. package/src/constants/WebSocketConstants.js +80 -0
  40. package/src/core/Highrise.js +123 -0
  41. package/src/core/HighriseWebsocket.js +228 -0
  42. package/src/utils/ConvertSvgToPng.js +51 -0
  43. package/src/utils/ModelPool.js +160 -0
  44. package/src/utils/Models.js +128 -0
  45. package/src/utils/versionCheck.js +27 -0
  46. package/src/validators/ConfigValidator.js +205 -0
  47. package/src/validators/ConnectionValidator.js +65 -0
  48. package/typings/index.d.ts +3820 -0
@@ -0,0 +1,516 @@
1
+
2
+ class CooldownManager {
3
+ constructor() {
4
+ this.cooldowns = new Map();
5
+ this.globalCooldowns = new Map();
6
+ this.groups = new Map();
7
+ this.stats = {
8
+ totalChecks: 0,
9
+ blockedActions: 0,
10
+ successfulActions: 0,
11
+ averageCooldownTime: 0
12
+ };
13
+ }
14
+
15
+ /**
16
+ * Set a cooldown for a specific key
17
+ * @param {string} key - Unique identifier (e.g., 'user:command' or 'userId:action')
18
+ * @param {number} durationMs - Cooldown duration in milliseconds
19
+ * @param {Object} [metadata] - Optional metadata stored with cooldown
20
+ * @returns {boolean} - Returns true if cooldown was set, false if already on cooldown
21
+ */
22
+ set(key, durationMs, metadata = {}) {
23
+ const now = Date.now();
24
+ const expiresAt = now + durationMs;
25
+
26
+
27
+ if (this.check(key)) {
28
+ this.stats.blockedActions++;
29
+ return false;
30
+ }
31
+
32
+ this.cooldowns.set(key, {
33
+ expiresAt,
34
+ duration: durationMs,
35
+ metadata: {
36
+ ...metadata,
37
+ setAt: now,
38
+ key
39
+ }
40
+ });
41
+
42
+ this.stats.successfulActions++;
43
+ return true;
44
+ }
45
+
46
+ /**
47
+ * Check if a key is on cooldown
48
+ * @param {string} key - Key to check
49
+ * @param {boolean} [autoCleanup=true] - Automatically remove expired cooldowns
50
+ * @returns {Object|null} - Cooldown info if active, null if not on cooldown
51
+ */
52
+ check(key, autoCleanup = true) {
53
+ this.stats.totalChecks++;
54
+
55
+ if (autoCleanup) {
56
+ this._cleanupExpired();
57
+ }
58
+
59
+ const cooldown = this.cooldowns.get(key);
60
+ if (!cooldown) return null;
61
+
62
+ const now = Date.now();
63
+ if (now >= cooldown.expiresAt) {
64
+ this.cooldowns.delete(key);
65
+ return null;
66
+ }
67
+
68
+ return {
69
+ ...cooldown,
70
+ remainingMs: cooldown.expiresAt - now,
71
+ remainingSeconds: Math.ceil((cooldown.expiresAt - now) / 1000),
72
+ progress: 1 - ((cooldown.expiresAt - now) / cooldown.duration)
73
+ };
74
+ }
75
+
76
+ /**
77
+ * Get remaining time for a cooldown
78
+ * @param {string} key - Key to check
79
+ * @returns {number} - Remaining milliseconds (0 if no cooldown)
80
+ */
81
+ getRemaining(key) {
82
+ const cooldown = this.check(key);
83
+ return cooldown ? cooldown.remainingMs : 0;
84
+ }
85
+
86
+ /**
87
+ * Attempt to execute if not on cooldown, otherwise return remaining time
88
+ * @param {string} key - Key to check
89
+ * @param {number} durationMs - Cooldown duration if executed
90
+ * @param {Function} [callback] - Optional callback to execute if not on cooldown
91
+ * @returns {Object} - Result object
92
+ */
93
+ attempt(key, durationMs, callback = null) {
94
+ const cooldown = this.check(key);
95
+
96
+ if (cooldown) {
97
+ return {
98
+ success: false,
99
+ onCooldown: true,
100
+ remainingMs: cooldown.remainingMs,
101
+ remainingSeconds: cooldown.remainingSeconds,
102
+ data: cooldown.metadata
103
+ };
104
+ }
105
+
106
+
107
+ this.set(key, durationMs);
108
+
109
+
110
+ let result = null;
111
+ let error = null;
112
+
113
+ if (callback) {
114
+ try {
115
+ result = callback();
116
+ } catch (err) {
117
+ error = err;
118
+ }
119
+ }
120
+
121
+ return {
122
+ success: true,
123
+ onCooldown: false,
124
+ executed: callback !== null,
125
+ result,
126
+ error,
127
+ cooldownSet: true,
128
+ durationMs
129
+ };
130
+ }
131
+
132
+ /**
133
+ * Remove a cooldown
134
+ * @param {string} key - Key to remove
135
+ * @returns {boolean} - True if removed, false if not found
136
+ */
137
+ remove(key) {
138
+ return this.cooldowns.delete(key);
139
+ }
140
+
141
+ /**
142
+ * Clear all cooldowns (or by prefix)
143
+ * @param {string} [prefix] - Optional prefix to filter keys
144
+ * @returns {number} - Number of cooldowns cleared
145
+ */
146
+ clear(prefix = null) {
147
+ if (!prefix) {
148
+ const count = this.cooldowns.size;
149
+ this.cooldowns.clear();
150
+ return count;
151
+ }
152
+
153
+ let count = 0;
154
+ for (const key of this.cooldowns.keys()) {
155
+ if (key.startsWith(prefix)) {
156
+ this.cooldowns.delete(key);
157
+ count++;
158
+ }
159
+ }
160
+ return count;
161
+ }
162
+
163
+ /**
164
+ * Set a global cooldown (affects all users)
165
+ * @param {string} action - Action identifier
166
+ * @param {number} durationMs - Cooldown duration
167
+ * @returns {boolean} - True if set, false if already active
168
+ */
169
+ setGlobal(action, durationMs) {
170
+ const now = Date.now();
171
+
172
+ if (this.checkGlobal(action)) {
173
+ return false;
174
+ }
175
+
176
+ this.globalCooldowns.set(action, {
177
+ expiresAt: now + durationMs,
178
+ duration: durationMs,
179
+ setAt: now
180
+ });
181
+
182
+ return true;
183
+ }
184
+
185
+ /**
186
+ * Check global cooldown
187
+ * @param {string} action - Action identifier
188
+ * @returns {Object|null} - Cooldown info if active
189
+ */
190
+ checkGlobal(action) {
191
+ const cooldown = this.globalCooldowns.get(action);
192
+ if (!cooldown) return null;
193
+
194
+ const now = Date.now();
195
+ if (now >= cooldown.expiresAt) {
196
+ this.globalCooldowns.delete(action);
197
+ return null;
198
+ }
199
+
200
+ return {
201
+ ...cooldown,
202
+ remainingMs: cooldown.expiresAt - now
203
+ };
204
+ }
205
+
206
+ /**
207
+ * Create a cooldown group
208
+ * @param {string} groupName - Group identifier
209
+ * @param {Object} config - Group configuration
210
+ */
211
+ createGroup(groupName, config = {}) {
212
+ this.groups.set(groupName, {
213
+ config: {
214
+ defaultDuration: config.defaultDuration || 5000,
215
+ maxConcurrent: config.maxConcurrent || 1,
216
+ perUserLimit: config.perUserLimit || null,
217
+ ...config
218
+ },
219
+ active: new Map(),
220
+ usage: []
221
+ });
222
+ }
223
+
224
+ /**
225
+ * Check group cooldown with rate limiting
226
+ * @param {string} groupName - Group identifier
227
+ * @param {string} userId - User identifier
228
+ * @returns {Object} - Result object
229
+ */
230
+ checkGroup(groupName, userId = null) {
231
+ const group = this.groups.get(groupName);
232
+ if (!group) {
233
+ throw new Error(`Group '${groupName}' not found`);
234
+ }
235
+
236
+ const now = Date.now();
237
+ const { maxConcurrent, perUserLimit, defaultDuration } = group.config;
238
+
239
+
240
+ if (group.active.size >= maxConcurrent) {
241
+ return {
242
+ allowed: false,
243
+ reason: 'max_concurrent',
244
+ message: `Maximum ${maxConcurrent} concurrent actions allowed`
245
+ };
246
+ }
247
+
248
+
249
+ if (userId && perUserLimit) {
250
+ const userCount = Array.from(group.active.values())
251
+ .filter(entry => entry.userId === userId).length;
252
+
253
+ if (userCount >= perUserLimit) {
254
+ return {
255
+ allowed: false,
256
+ reason: 'user_limit',
257
+ message: `User limit of ${perUserLimit} actions reached`
258
+ };
259
+ }
260
+ }
261
+
262
+
263
+ if (userId) {
264
+ const userKey = `${groupName}:user:${userId}`;
265
+ const cooldown = this.check(userKey, false);
266
+
267
+ if (cooldown) {
268
+ return {
269
+ allowed: false,
270
+ reason: 'user_cooldown',
271
+ remainingMs: cooldown.remainingMs,
272
+ message: `Please wait ${cooldown.remainingSeconds}s`
273
+ };
274
+ }
275
+ }
276
+
277
+ return {
278
+ allowed: true,
279
+ duration: defaultDuration
280
+ };
281
+ }
282
+
283
+ /**
284
+ * Start a group action
285
+ * @param {string} groupName - Group identifier
286
+ * @param {string} actionId - Unique action identifier
287
+ * @param {string} [userId] - Optional user identifier
288
+ * @returns {Object} - Result object
289
+ */
290
+ startGroupAction(groupName, actionId, userId = null) {
291
+ const group = this.groups.get(groupName);
292
+ if (!group) {
293
+ throw new Error(`Group '${groupName}' not found`);
294
+ }
295
+
296
+ const check = this.checkGroup(groupName, userId);
297
+ if (!check.allowed) {
298
+ return check;
299
+ }
300
+
301
+ const now = Date.now();
302
+
303
+
304
+ if (userId) {
305
+ const userKey = `${groupName}:user:${userId}`;
306
+ this.set(userKey, group.config.defaultDuration, { group: groupName, userId });
307
+ }
308
+
309
+
310
+ group.active.set(actionId, {
311
+ startedAt: now,
312
+ userId,
313
+ actionId
314
+ });
315
+
316
+
317
+ group.usage.push({
318
+ actionId,
319
+ userId,
320
+ startedAt: now,
321
+ group: groupName
322
+ });
323
+
324
+
325
+ if (group.usage.length > 1000) {
326
+ group.usage = group.usage.slice(-500);
327
+ }
328
+
329
+ return {
330
+ success: true,
331
+ actionId,
332
+ startedAt: now,
333
+ duration: group.config.defaultDuration
334
+ };
335
+ }
336
+
337
+ /**
338
+ * End a group action
339
+ * @param {string} groupName - Group identifier
340
+ * @param {string} actionId - Action identifier
341
+ * @returns {boolean} - True if ended
342
+ */
343
+ endGroupAction(groupName, actionId) {
344
+ const group = this.groups.get(groupName);
345
+ if (!group) return false;
346
+
347
+ return group.active.delete(actionId);
348
+ }
349
+
350
+ /**
351
+ * Get formatted time string
352
+ * @param {number} ms - Milliseconds
353
+ * @returns {string} - Formatted time (e.g., "1m 30s")
354
+ */
355
+ static formatTime(ms) {
356
+ if (ms < 1000) return `${ms}ms`;
357
+
358
+ const seconds = Math.floor(ms / 1000);
359
+ const minutes = Math.floor(seconds / 60);
360
+ const hours = Math.floor(minutes / 60);
361
+ const days = Math.floor(hours / 24);
362
+
363
+ const parts = [];
364
+
365
+ if (days > 0) parts.push(`${days}d`);
366
+ if (hours % 24 > 0) parts.push(`${hours % 24}h`);
367
+ if (minutes % 60 > 0) parts.push(`${minutes % 60}m`);
368
+ if (seconds % 60 > 0) parts.push(`${seconds % 60}s`);
369
+
370
+ return parts.join(' ') || '0s';
371
+ }
372
+
373
+ /**
374
+ * Get user's cooldowns
375
+ * @param {string} userId - User identifier
376
+ * @returns {Array} - Array of active cooldowns
377
+ */
378
+ getUserCooldowns(userId) {
379
+ const userCooldowns = [];
380
+
381
+ for (const [key, cooldown] of this.cooldowns) {
382
+ if (key.includes(`:${userId}:`) || key.endsWith(`:${userId}`)) {
383
+ const remaining = cooldown.expiresAt - Date.now();
384
+ if (remaining > 0) {
385
+ userCooldowns.push({
386
+ key,
387
+ remainingMs: remaining,
388
+ expiresAt: cooldown.expiresAt,
389
+ metadata: cooldown.metadata
390
+ });
391
+ }
392
+ }
393
+ }
394
+
395
+ return userCooldowns;
396
+ }
397
+
398
+ /**
399
+ * Get statistics
400
+ * @returns {Object} - Statistics object
401
+ */
402
+ getStats() {
403
+ const now = Date.now();
404
+ const activeCooldowns = Array.from(this.cooldowns.values())
405
+ .filter(c => c.expiresAt > now).length;
406
+
407
+ const activeGlobals = Array.from(this.globalCooldowns.values())
408
+ .filter(c => c.expiresAt > now).length;
409
+
410
+ let groupStats = {};
411
+ for (const [name, group] of this.groups) {
412
+ groupStats[name] = {
413
+ active: group.active.size,
414
+ totalUsage: group.usage.length,
415
+ config: group.config
416
+ };
417
+ }
418
+
419
+ return {
420
+ ...this.stats,
421
+ activeCooldowns,
422
+ activeGlobals,
423
+ groups: groupStats,
424
+ memoryUsage: this._estimateMemoryUsage()
425
+ };
426
+ }
427
+
428
+ /**
429
+ * Estimate memory usage
430
+ * @private
431
+ */
432
+ _estimateMemoryUsage() {
433
+
434
+ const cooldownBytes = this.cooldowns.size * 100;
435
+ const globalBytes = this.globalCooldowns.size * 80;
436
+ const groupBytes = Array.from(this.groups.values())
437
+ .reduce((sum, group) => sum + (group.active.size * 120) + (group.usage.length * 200), 0);
438
+
439
+ return {
440
+ cooldowns: `${Math.round(cooldownBytes / 1024)}KB`,
441
+ globals: `${Math.round(globalBytes / 1024)}KB`,
442
+ groups: `${Math.round(groupBytes / 1024)}KB`,
443
+ total: `${Math.round((cooldownBytes + globalBytes + groupBytes) / 1024)}KB`
444
+ };
445
+ }
446
+
447
+ /**
448
+ * Cleanup expired cooldowns
449
+ * @private
450
+ */
451
+ _cleanupExpired() {
452
+ const now = Date.now();
453
+ let cleaned = 0;
454
+
455
+ for (const [key, cooldown] of this.cooldowns) {
456
+ if (now >= cooldown.expiresAt) {
457
+ this.cooldowns.delete(key);
458
+ cleaned++;
459
+ }
460
+ }
461
+
462
+ for (const [action, cooldown] of this.globalCooldowns) {
463
+ if (now >= cooldown.expiresAt) {
464
+ this.globalCooldowns.delete(action);
465
+ }
466
+ }
467
+
468
+ return cleaned;
469
+ }
470
+
471
+ /**
472
+ * Auto-cleanup every minute
473
+ */
474
+ startAutoCleanup(intervalMs = 60000) {
475
+ if (this._cleanupInterval) {
476
+ clearInterval(this._cleanupInterval);
477
+ }
478
+
479
+ this._cleanupInterval = setInterval(() => {
480
+ const cleaned = this._cleanupExpired();
481
+ if (cleaned > 0) {
482
+
483
+ }
484
+ }, intervalMs);
485
+
486
+ return this._cleanupInterval;
487
+ }
488
+
489
+ /**
490
+ * Stop auto-cleanup
491
+ */
492
+ stopAutoCleanup() {
493
+ if (this._cleanupInterval) {
494
+ clearInterval(this._cleanupInterval);
495
+ this._cleanupInterval = null;
496
+ }
497
+ }
498
+
499
+ /**
500
+ * Destroy all resources
501
+ */
502
+ destroy() {
503
+ this.stopAutoCleanup();
504
+ this.cooldowns.clear();
505
+ this.globalCooldowns.clear();
506
+ this.groups.clear();
507
+ this.stats = {
508
+ totalChecks: 0,
509
+ blockedActions: 0,
510
+ successfulActions: 0,
511
+ averageCooldownTime: 0
512
+ };
513
+ }
514
+ }
515
+
516
+ module.exports = CooldownManager;
@@ -0,0 +1,64 @@
1
+ const {
2
+ UserLeftHandler,
3
+ VoiceEventHandler,
4
+ UserJoinedHandler,
5
+ TipReactionHandler,
6
+ DirectEventHandler,
7
+ MessageEventHandler,
8
+ UserMovementHandler,
9
+ RoomModerateHandler,
10
+ HiddenChannelHandler,
11
+ SessionMetadataHandler,
12
+ } = require('highrise-core/src/classes/Handlers/EventHandlers');
13
+
14
+ class EventsManager {
15
+ constructor(server) {
16
+ this.server = server;
17
+ this._handlers = new Map();
18
+ this._registerDefaultHandlers();
19
+ }
20
+
21
+ _registerDefaultHandlers() {
22
+ this._handlers.set('VoiceEvent', VoiceEventHandler);
23
+ this._handlers.set('UserLeftEvent', UserLeftHandler);
24
+ this._handlers.set('ChatEvent', MessageEventHandler);
25
+ this._handlers.set('MessageEvent', DirectEventHandler);
26
+ this._handlers.set('WhisperEvent', MessageEventHandler);
27
+ this._handlers.set('ChannelEvent', HiddenChannelHandler);
28
+ this._handlers.set('UserJoinedEvent', UserJoinedHandler);
29
+ this._handlers.set('UserMovedEvent', UserMovementHandler);
30
+ this._handlers.set('TipReactionEvent', TipReactionHandler);
31
+ this._handlers.set('SessionMetadata', SessionMetadataHandler);
32
+ this._handlers.set('RoomModeratedEvent', RoomModerateHandler);
33
+ }
34
+
35
+ registerHandler(eventType, handler) {
36
+ this._handlers.set(eventType, handler);
37
+ }
38
+
39
+ unregisterHandler(eventType) {
40
+ this._handlers.delete(eventType);
41
+ }
42
+
43
+ handleEvent(event) {
44
+ const eventType = event._type;
45
+ const handler = this._handlers.get(eventType);
46
+
47
+ if (handler) {
48
+ handler(this.server, event);
49
+ } else {
50
+ this.server.emit('unhandledEvent', eventType, event);
51
+ }
52
+ }
53
+
54
+ clearHandlers(eventType = null) {
55
+ if (eventType) {
56
+ this._handlers.delete(eventType);
57
+ } else {
58
+ this._handlers.clear();
59
+ this._registerDefaultHandlers();
60
+ }
61
+ }
62
+ }
63
+
64
+ module.exports = EventsManager;
@@ -0,0 +1,109 @@
1
+ const WebSocketConstants = require('highrise-core/src/constants/WebSocketConstants');
2
+ const crypto = require('crypto');
3
+
4
+ class KeepAliveManager {
5
+ constructor(connectionManager, logger) {
6
+ this.connectionManager = connectionManager;
7
+ this.logger = logger;
8
+
9
+ this.interval = null;
10
+ this.lastKeepaliveSent = 0;
11
+ this.lastPongReceived = 0;
12
+ this.consecutiveNoPong = 0;
13
+ this.maxConsecutiveNoPong = 3;
14
+ this.firstKeepaliveSent = false;
15
+ }
16
+
17
+ start() {
18
+ this.stop();
19
+
20
+ this.firstKeepaliveSent = false;
21
+
22
+ this._sendKeepAlive();
23
+
24
+ this.interval = setInterval(() => {
25
+ this._sendKeepAlive();
26
+ }, WebSocketConstants.KEEPALIVE_INTERVAL);
27
+
28
+ this.logger.info('KeepAliveManager', 'Keep-alive started');
29
+ }
30
+
31
+ stop() {
32
+ if (this.interval) {
33
+ clearInterval(this.interval);
34
+ this.interval = null;
35
+ }
36
+ this.consecutiveNoPong = 0;
37
+ this.firstKeepaliveSent = false;
38
+ }
39
+
40
+ _sendKeepAlive() {
41
+ if (!this.connectionManager.isConnected()) {
42
+ this.logger.debug('KeepAliveManager', 'Skipping keepalive');
43
+ return;
44
+ }
45
+
46
+ try {
47
+ const keepalive = {
48
+ _type: 'KeepaliveRequest',
49
+ rid: crypto.randomUUID()
50
+ };
51
+
52
+ this.connectionManager.send(keepalive);
53
+ this.lastKeepaliveSent = Date.now();
54
+
55
+ if (!this.firstKeepaliveSent) {
56
+ this.firstKeepaliveSent = true;
57
+ this.lastPongReceived = Date.now();
58
+ return;
59
+ }
60
+
61
+ } catch (error) {
62
+ this.logger.error('KeepAliveManager', 'Failed to send keepalive', {
63
+ error: error.message
64
+ });
65
+ }
66
+ }
67
+
68
+ _forceReconnect() {
69
+ if (this.connectionManager.isConnected()) {
70
+ this.connectionManager.disconnect(
71
+ WebSocketConstants.ERROR_CODES.ABNORMAL_CLOSURE,
72
+ 'Keepalive timeout'
73
+ );
74
+ }
75
+ this.consecutiveNoPong = 0;
76
+ }
77
+
78
+ handlePong() {
79
+ this.lastPongReceived = Date.now();
80
+ this.consecutiveNoPong = 0;
81
+
82
+ const latency = this.lastPongReceived - this.lastKeepaliveSent;
83
+ if (latency > 10000) {
84
+ this.logger.warn('KeepAliveManager', `High latency: ${latency}ms`);
85
+ }
86
+ }
87
+
88
+ getStats() {
89
+ const timeSinceLastPong = Date.now() - this.lastPongReceived;
90
+ const timeSinceLastKeepalive = Date.now() - this.lastKeepaliveSent;
91
+
92
+ return {
93
+ isActive: this.interval !== null,
94
+ lastKeepaliveSent: this.lastKeepaliveSent,
95
+ lastPongReceived: this.lastPongReceived,
96
+ timeSinceLastPong: timeSinceLastPong,
97
+ timeSinceLastKeepalive: timeSinceLastKeepalive,
98
+ consecutiveNoPong: this.consecutiveNoPong,
99
+ healthStatus: this.firstKeepaliveSent ? (timeSinceLastPong < 45000 ? 'healthy' : 'degraded') : 'initializing'
100
+ };
101
+ }
102
+
103
+ forceKeepAlive() {
104
+ this._sendKeepAlive();
105
+ this.logger.info('KeepAliveManager', 'Manual keepalive sent');
106
+ }
107
+ }
108
+
109
+ module.exports = KeepAliveManager;