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,357 @@
|
|
|
1
|
+
class MovementCache {
|
|
2
|
+
constructor() {
|
|
3
|
+
this._activeCache = new Map();
|
|
4
|
+
this._inactiveCache = new Map();
|
|
5
|
+
this._usernameToId = new Map();
|
|
6
|
+
this._changes = new Map();
|
|
7
|
+
this._inactiveThreshold = 30 * 1000;
|
|
8
|
+
this._cleanupInterval = setInterval(() => this._cleanInactive(), 30 * 1000);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
_cleanInactive() {
|
|
12
|
+
const now = Date.now();
|
|
13
|
+
|
|
14
|
+
for (const [userId, data] of this._inactiveCache) {
|
|
15
|
+
if (now - data.lastSeen > this._inactiveThreshold) {
|
|
16
|
+
this._inactiveCache.delete(userId);
|
|
17
|
+
this._changes.delete(userId);
|
|
18
|
+
this._usernameToId.delete(data.username);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
for (const [userId, data] of this._activeCache) {
|
|
23
|
+
if (now - data.lastSeen > this._inactiveThreshold) {
|
|
24
|
+
this._inactiveCache.set(userId, data);
|
|
25
|
+
this._activeCache.delete(userId);
|
|
26
|
+
this._changes.delete(userId);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
_toUint8Array(position) {
|
|
32
|
+
const buffer = new Uint8Array(4);
|
|
33
|
+
buffer[0] = Math.round(position.x);
|
|
34
|
+
buffer[1] = Math.round(position.y);
|
|
35
|
+
buffer[2] = Math.round(position.z);
|
|
36
|
+
buffer[3] = this._facingToByte(position.facing);
|
|
37
|
+
return buffer;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
_facingToByte(facing) {
|
|
41
|
+
const map = { 'FrontRight': 0, 'FrontLeft': 1, 'BackRight': 2, 'BackLeft': 3 };
|
|
42
|
+
return map[facing] || 0;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
_byteToFacing(byte) {
|
|
46
|
+
const map = ['FrontRight', 'FrontLeft', 'BackRight', 'BackLeft'];
|
|
47
|
+
return map[byte] || 'FrontRight';
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
_fromUint8Array(buffer) {
|
|
51
|
+
return {
|
|
52
|
+
x: buffer[0],
|
|
53
|
+
y: buffer[1],
|
|
54
|
+
z: buffer[2],
|
|
55
|
+
facing: this._byteToFacing(buffer[3])
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
_hasPositionChanged(userId, newPosition) {
|
|
60
|
+
const oldData = this._activeCache.get(userId);
|
|
61
|
+
if (!oldData) return true;
|
|
62
|
+
|
|
63
|
+
const oldPos = this._fromUint8Array(oldData.position);
|
|
64
|
+
return oldPos.x !== newPosition.x || oldPos.y !== newPosition.y || oldPos.z !== newPosition.z || oldPos.facing !== newPosition.facing;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
_update(userId, username, position, anchor = null) {
|
|
68
|
+
this._usernameToId.set(username, userId);
|
|
69
|
+
|
|
70
|
+
const hasChanged = this._hasPositionChanged(userId, position);
|
|
71
|
+
|
|
72
|
+
if (hasChanged) {
|
|
73
|
+
const cachedData = {
|
|
74
|
+
position: this._toUint8Array(position),
|
|
75
|
+
anchor: anchor ? { entity_id: anchor.entity_id, anchor_ix: anchor.anchor_ix } : null,
|
|
76
|
+
lastSeen: Date.now(),
|
|
77
|
+
username: username
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
this._activeCache.set(userId, cachedData);
|
|
81
|
+
this._changes.set(userId, cachedData);
|
|
82
|
+
this._inactiveCache.delete(userId);
|
|
83
|
+
return true;
|
|
84
|
+
} else {
|
|
85
|
+
const existing = this._activeCache.get(userId) || this._inactiveCache.get(userId);
|
|
86
|
+
if (existing) {
|
|
87
|
+
existing.lastSeen = Date.now();
|
|
88
|
+
} else {
|
|
89
|
+
this._inactiveCache.set(userId, {
|
|
90
|
+
position: this._toUint8Array(position),
|
|
91
|
+
anchor,
|
|
92
|
+
lastSeen: Date.now(),
|
|
93
|
+
username: username
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
_getById(userId) {
|
|
101
|
+
let data = this._activeCache.get(userId) || this._inactiveCache.get(userId);
|
|
102
|
+
if (data) {
|
|
103
|
+
return {
|
|
104
|
+
position: this._fromUint8Array(data.position),
|
|
105
|
+
anchor: data.anchor,
|
|
106
|
+
lastSeen: data.lastSeen,
|
|
107
|
+
username: data.username
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
getPlayersInSquare(center, sideLength) {
|
|
114
|
+
const halfSide = sideLength / 2;
|
|
115
|
+
|
|
116
|
+
return this.getPlayersInRectangle({
|
|
117
|
+
c1: { x: center.x - halfSide, z: center.z - halfSide },
|
|
118
|
+
c2: { x: center.x + halfSide, z: center.z - halfSide },
|
|
119
|
+
c3: { x: center.x + halfSide, z: center.z + halfSide },
|
|
120
|
+
c4: { x: center.x - halfSide, z: center.z + halfSide }
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
getPlayersInRectangle({ c1, c2, c3, c4 }) {
|
|
125
|
+
const playersInArea = [];
|
|
126
|
+
|
|
127
|
+
const allUsers = new Map([...this._activeCache, ...this._inactiveCache]);
|
|
128
|
+
|
|
129
|
+
for (const [userId, cachedData] of allUsers) {
|
|
130
|
+
const position = this._fromUint8Array(cachedData.position);
|
|
131
|
+
|
|
132
|
+
if (this._isPointInRectangle(position, { c1, c2, c3, c4 })) {
|
|
133
|
+
playersInArea.push({
|
|
134
|
+
user: {
|
|
135
|
+
id: userId,
|
|
136
|
+
username: cachedData.username
|
|
137
|
+
},
|
|
138
|
+
position: position,
|
|
139
|
+
anchor: cachedData.anchor,
|
|
140
|
+
lastSeen: cachedData.lastSeen
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return playersInArea;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
_isPointInRectangle(point, { c1, c2, c3, c4 }) {
|
|
149
|
+
const corners = [c1, c2, c3, c4];
|
|
150
|
+
|
|
151
|
+
let inside = false;
|
|
152
|
+
for (let i = 0, j = corners.length - 1; i < corners.length; j = i++) {
|
|
153
|
+
const xi = corners[i].x, zi = corners[i].z;
|
|
154
|
+
const xj = corners[j].x, zj = corners[j].z;
|
|
155
|
+
|
|
156
|
+
const intersect = ((zi > point.z) !== (zj > point.z)) &&
|
|
157
|
+
(point.x < (xj - xi) * (point.z - zi) / (zj - zi) + xi);
|
|
158
|
+
|
|
159
|
+
if (intersect) inside = !inside;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return inside;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
getPlayersInCircle(center, radius) {
|
|
166
|
+
const playersInArea = [];
|
|
167
|
+
const radiusSquared = radius * radius;
|
|
168
|
+
const allUsers = this._activeCache.size + this._inactiveCache.size > 0 ?
|
|
169
|
+
new Map([...this._activeCache, ...this._inactiveCache]) : new Map();
|
|
170
|
+
|
|
171
|
+
if (allUsers.size === 0) return playersInArea;
|
|
172
|
+
|
|
173
|
+
for (const [userId, cachedData] of allUsers) {
|
|
174
|
+
const pos = this._fromUint8Array(cachedData.position);
|
|
175
|
+
const dx = pos.x - center.x;
|
|
176
|
+
const dz = pos.z - center.z;
|
|
177
|
+
const distSq = dx * dx + dz * dz;
|
|
178
|
+
|
|
179
|
+
if (distSq <= radiusSquared) {
|
|
180
|
+
const distance = Math.sqrt(distSq);
|
|
181
|
+
const roundedDistance = distance % 1 === 0 ? distance : Math.round(distance * 100) / 100;
|
|
182
|
+
|
|
183
|
+
playersInArea.push({
|
|
184
|
+
user: { id: userId, username: cachedData.username },
|
|
185
|
+
position: pos,
|
|
186
|
+
anchor: cachedData.anchor,
|
|
187
|
+
lastSeen: cachedData.lastSeen,
|
|
188
|
+
distance: roundedDistance
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (playersInArea.length > 1) {
|
|
194
|
+
playersInArea.sort((a, b) => a.distance - b.distance);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return playersInArea;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
getPlayersInVerticalRange(minY, maxY) {
|
|
201
|
+
const playersInRange = [];
|
|
202
|
+
const allUsers = new Map([...this._activeCache, ...this._inactiveCache]);
|
|
203
|
+
|
|
204
|
+
for (const [userId, cachedData] of allUsers) {
|
|
205
|
+
const position = this._fromUint8Array(cachedData.position);
|
|
206
|
+
|
|
207
|
+
if (position.y >= minY && position.y <= maxY) {
|
|
208
|
+
playersInRange.push({
|
|
209
|
+
user: {
|
|
210
|
+
id: userId,
|
|
211
|
+
username: cachedData.username
|
|
212
|
+
},
|
|
213
|
+
position: position,
|
|
214
|
+
anchor: cachedData.anchor,
|
|
215
|
+
lastSeen: cachedData.lastSeen
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return playersInRange;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
getClosestPlayer(point) {
|
|
224
|
+
const allPlayers = this.getPlayersByDistance(point, Infinity);
|
|
225
|
+
return allPlayers.length > 0 ? allPlayers[0] : null;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
getPlayersByDistance(point, maxDistance = Infinity) {
|
|
229
|
+
if (this._activeCache.size === 0 && this._inactiveCache.size === 0) {
|
|
230
|
+
return [];
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const playersWithDistance = [];
|
|
234
|
+
const maxDistanceSq = maxDistance * maxDistance;
|
|
235
|
+
|
|
236
|
+
// Pre-calculate for performance
|
|
237
|
+
const pointX = point.x;
|
|
238
|
+
const pointZ = point.z;
|
|
239
|
+
|
|
240
|
+
// Check active cache first (most likely to be relevant)
|
|
241
|
+
for (const [userId, cachedData] of this._activeCache) {
|
|
242
|
+
const pos = this._fromUint8Array(cachedData.position);
|
|
243
|
+
const dx = pos.x - pointX;
|
|
244
|
+
const dz = pos.z - pointZ;
|
|
245
|
+
const distSq = dx * dx + dz * dz;
|
|
246
|
+
|
|
247
|
+
if (distSq <= maxDistanceSq) {
|
|
248
|
+
playersWithDistance.push({
|
|
249
|
+
user: { id: userId, username: cachedData.username },
|
|
250
|
+
position: pos,
|
|
251
|
+
anchor: cachedData.anchor,
|
|
252
|
+
lastSeen: cachedData.lastSeen,
|
|
253
|
+
distance: Math.sqrt(distSq)
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
for (const [userId, cachedData] of this._inactiveCache) {
|
|
259
|
+
const pos = this._fromUint8Array(cachedData.position);
|
|
260
|
+
const dx = pos.x - pointX;
|
|
261
|
+
const dz = pos.z - pointZ;
|
|
262
|
+
const distSq = dx * dx + dz * dz;
|
|
263
|
+
|
|
264
|
+
if (distSq <= maxDistanceSq) {
|
|
265
|
+
playersWithDistance.push({
|
|
266
|
+
user: { id: userId, username: cachedData.username },
|
|
267
|
+
position: pos,
|
|
268
|
+
anchor: cachedData.anchor,
|
|
269
|
+
lastSeen: cachedData.lastSeen,
|
|
270
|
+
distance: Math.sqrt(distSq)
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if (playersWithDistance.length > 1) {
|
|
276
|
+
playersWithDistance.sort((a, b) => a.distance - b.distance);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
return playersWithDistance;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
isPlayerInSquare(userId, center, sideLength) {
|
|
283
|
+
const player = this._getById(userId);
|
|
284
|
+
if (!player) return false;
|
|
285
|
+
|
|
286
|
+
const halfSide = sideLength / 2;
|
|
287
|
+
const corners = {
|
|
288
|
+
c1: { x: center.x - halfSide, z: center.z - halfSide },
|
|
289
|
+
c2: { x: center.x + halfSide, z: center.z - halfSide },
|
|
290
|
+
c3: { x: center.x + halfSide, z: center.z + halfSide },
|
|
291
|
+
c4: { x: center.x - halfSide, z: center.z + halfSide }
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
return this._isPointInRectangle(player.position, corners);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
isPlayerInRectangle(userId, corners) {
|
|
298
|
+
const player = this._getById(userId);
|
|
299
|
+
if (!player) return false;
|
|
300
|
+
|
|
301
|
+
return this._isPointInRectangle(player.position, corners);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
getPlayerCountInSquare(center, sideLength) {
|
|
305
|
+
return this.getPlayersInSquare(center, sideLength).length;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
getPlayerCountInRectangle(corners) {
|
|
309
|
+
return this.getPlayersInRectangle(corners).length;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
_getByUsername(username) {
|
|
313
|
+
const userId = this._usernameToId.get(username);
|
|
314
|
+
return userId ? this._getById(userId) : null;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
get(identifier) {
|
|
318
|
+
const byId = this._getById(identifier);
|
|
319
|
+
if (byId) return byId;
|
|
320
|
+
return this._getByUsername(identifier);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
_remove(userId) {
|
|
324
|
+
const data = this._activeCache.get(userId) || this._inactiveCache.get(userId);
|
|
325
|
+
if (data) {
|
|
326
|
+
this._usernameToId.delete(data.username);
|
|
327
|
+
}
|
|
328
|
+
this._activeCache.delete(userId);
|
|
329
|
+
this._inactiveCache.delete(userId);
|
|
330
|
+
this._changes.delete(userId);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
getStats() {
|
|
334
|
+
const activeUsers = this._activeCache.size;
|
|
335
|
+
const inactiveUsers = this._inactiveCache.size;
|
|
336
|
+
const memoryUsage = (activeUsers * 120) + (inactiveUsers * 80);
|
|
337
|
+
|
|
338
|
+
return {
|
|
339
|
+
activeUsers,
|
|
340
|
+
inactiveUsers,
|
|
341
|
+
memoryUsage,
|
|
342
|
+
changesProcessed: this._changes.size,
|
|
343
|
+
totalUsers: activeUsers + inactiveUsers
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
destroy() {
|
|
348
|
+
clearInterval(this._cleanupInterval);
|
|
349
|
+
this._activeCache.clear();
|
|
350
|
+
this._inactiveCache.clear();
|
|
351
|
+
this._usernameToId.clear();
|
|
352
|
+
this._changes.clear();
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
module.exports = { MovementCache }
|
|
357
|
+
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
class AxiosErrorHandler {
|
|
2
|
+
static handle(error, logger, method, context = {}) {
|
|
3
|
+
const errorInfo = {
|
|
4
|
+
...context,
|
|
5
|
+
code: error.code,
|
|
6
|
+
message: error.message
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
if (error.response) {
|
|
10
|
+
errorInfo.status = error.response.status
|
|
11
|
+
errorInfo.statusText = error.response.statusText
|
|
12
|
+
errorInfo.data = error.response.data
|
|
13
|
+
|
|
14
|
+
switch (error.response.status) {
|
|
15
|
+
case 400:
|
|
16
|
+
logger.warn(method, `Bad Request`, errorInfo)
|
|
17
|
+
break
|
|
18
|
+
case 401:
|
|
19
|
+
logger.warn(method, `Unauthorized`, errorInfo)
|
|
20
|
+
break
|
|
21
|
+
case 403:
|
|
22
|
+
logger.warn(method, `Forbidden`, errorInfo)
|
|
23
|
+
break
|
|
24
|
+
case 404:
|
|
25
|
+
logger.warn(method, `Not Found`, errorInfo)
|
|
26
|
+
break
|
|
27
|
+
case 429:
|
|
28
|
+
logger.warn(method, `Rate Limited`, errorInfo)
|
|
29
|
+
break
|
|
30
|
+
case 500:
|
|
31
|
+
logger.error(method, `Server Error`, errorInfo)
|
|
32
|
+
break
|
|
33
|
+
case 502:
|
|
34
|
+
logger.error(method, `Bad Gateway`, errorInfo)
|
|
35
|
+
break
|
|
36
|
+
case 503:
|
|
37
|
+
logger.error(method, `Service Unavailable`, errorInfo)
|
|
38
|
+
break
|
|
39
|
+
default:
|
|
40
|
+
logger.error(method, `HTTP ${error.response.status}`, errorInfo)
|
|
41
|
+
}
|
|
42
|
+
} else if (error.request) {
|
|
43
|
+
logger.error(method, `Network Error - No response received`, errorInfo)
|
|
44
|
+
} else if (error.code === 'ECONNABORTED') {
|
|
45
|
+
logger.error(method, `Request Timeout`, errorInfo)
|
|
46
|
+
} else if (error.code === 'ENOTFOUND') {
|
|
47
|
+
logger.error(method, `DNS Lookup Failed`, errorInfo)
|
|
48
|
+
} else {
|
|
49
|
+
logger.error(method, `Unexpected Error`, errorInfo)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return null
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
static isNotFound(error) {
|
|
56
|
+
return error.response && error.response.status === 404
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
static isRateLimited(error) {
|
|
60
|
+
return error.response && error.response.status === 429
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
static isServerError(error) {
|
|
64
|
+
return error.response && error.response.status >= 500
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
module.exports = { AxiosErrorHandler }
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
class ErrorHandler {
|
|
2
|
+
static isConnectionError(error) {
|
|
3
|
+
const connectionErrors = [
|
|
4
|
+
'ECONNREFUSED', 'ECONNRESET', 'EPIPE', 'ETIMEDOUT',
|
|
5
|
+
'ENOTFOUND', 'EHOSTUNREACH', 'ENETUNREACH'
|
|
6
|
+
];
|
|
7
|
+
|
|
8
|
+
return connectionErrors.includes(error.code);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
static isWebSocketError(error) {
|
|
12
|
+
const wsErrors = [
|
|
13
|
+
'is not connected',
|
|
14
|
+
'WebSocket is not open',
|
|
15
|
+
'readyState',
|
|
16
|
+
'WebSocket'
|
|
17
|
+
];
|
|
18
|
+
|
|
19
|
+
return wsErrors.some(msg => error.message.includes(msg));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
static isRateLimitError(error) {
|
|
23
|
+
return error.response?.status === 429 ||
|
|
24
|
+
error.message?.includes('rate limit') ||
|
|
25
|
+
error.message?.includes('too many requests');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
static createError(error, context = {}) {
|
|
29
|
+
const enhancedError = new Error(error.message || error);
|
|
30
|
+
|
|
31
|
+
enhancedError.timestamp = Date.now();
|
|
32
|
+
enhancedError.context = context;
|
|
33
|
+
enhancedError.originalError = error;
|
|
34
|
+
enhancedError.code = error.code;
|
|
35
|
+
|
|
36
|
+
if (error.response) {
|
|
37
|
+
enhancedError.status = error.response.status;
|
|
38
|
+
enhancedError.statusText = error.response.statusText;
|
|
39
|
+
enhancedError.data = error.response.data;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return enhancedError;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
static shouldRetry(error, attempt, maxRetries) {
|
|
46
|
+
if (attempt >= maxRetries) return false;
|
|
47
|
+
|
|
48
|
+
// Don't retry these errors
|
|
49
|
+
if (error.code === 'ENOTFOUND') return false;
|
|
50
|
+
if (error.response?.status >= 400 && error.response?.status < 500) return false;
|
|
51
|
+
|
|
52
|
+
// Retry these errors
|
|
53
|
+
if (this.isConnectionError(error)) return true;
|
|
54
|
+
if (this.isRateLimitError(error)) return true;
|
|
55
|
+
if (error.response?.status >= 500) return true;
|
|
56
|
+
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
static getRetryDelay(attempt, baseDelay = 100) {
|
|
61
|
+
return Math.min(baseDelay * Math.pow(2, attempt), 5000);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
module.exports = ErrorHandler;
|