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.
- package/LICENSE +21 -0
- package/README.md +864 -0
- package/index.js +13 -0
- package/package.json +28 -0
- package/src/classes/Actions/Awaiter.js +202 -0
- package/src/classes/Actions/Channel.js +73 -0
- package/src/classes/Actions/Direct.js +263 -0
- package/src/classes/Actions/Inventory.js +156 -0
- package/src/classes/Actions/Music.js +278 -0
- package/src/classes/Actions/Player.js +377 -0
- package/src/classes/Actions/Public.js +66 -0
- package/src/classes/Actions/Room.js +333 -0
- package/src/classes/Actions/Utils.js +29 -0
- package/src/classes/Actions/lib/AudioStreaming.js +447 -0
- package/src/classes/Caches/MovementCache.js +357 -0
- package/src/classes/Handlers/AxiosErrorHandler.js +68 -0
- package/src/classes/Handlers/ErrorHandler.js +65 -0
- package/src/classes/Handlers/EventHandlers.js +259 -0
- package/src/classes/Handlers/WebSocketHandlers.js +54 -0
- package/src/classes/Managers/ChannelManager.js +303 -0
- package/src/classes/Managers/DanceFloorManagers.js +509 -0
- package/src/classes/Managers/Helpers/CleanupManager.js +130 -0
- package/src/classes/Managers/Helpers/LoggerManager.js +171 -0
- package/src/classes/Managers/Helpers/MetricsManager.js +83 -0
- package/src/classes/Managers/Networking/ConnectionManager.js +259 -0
- package/src/classes/Managers/Networking/CooldownManager.js +516 -0
- package/src/classes/Managers/Networking/EventsManager.js +64 -0
- package/src/classes/Managers/Networking/KeepAliveManager.js +109 -0
- package/src/classes/Managers/Networking/MessageHandler.js +110 -0
- package/src/classes/Managers/Networking/Request.js +329 -0
- package/src/classes/Managers/PermissionManager.js +288 -0
- package/src/classes/WebApi/Category/Grab.js +98 -0
- package/src/classes/WebApi/Category/Item.js +347 -0
- package/src/classes/WebApi/Category/Post.js +154 -0
- package/src/classes/WebApi/Category/Room.js +137 -0
- package/src/classes/WebApi/Category/User.js +88 -0
- package/src/classes/WebApi/webapi.js +52 -0
- package/src/constants/TypesConstants.js +89 -0
- package/src/constants/WebSocketConstants.js +80 -0
- package/src/core/Highrise.js +123 -0
- package/src/core/HighriseWebsocket.js +228 -0
- package/src/utils/ConvertSvgToPng.js +51 -0
- package/src/utils/ModelPool.js +160 -0
- package/src/utils/Models.js +128 -0
- package/src/utils/versionCheck.js +27 -0
- package/src/validators/ConfigValidator.js +205 -0
- package/src/validators/ConnectionValidator.js +65 -0
- 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;
|