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,171 @@
|
|
|
1
|
+
const chalk = require('chalk');
|
|
2
|
+
|
|
3
|
+
class Logger {
|
|
4
|
+
constructor(bot, options = {}) {
|
|
5
|
+
this.bot = bot;
|
|
6
|
+
this.options = {
|
|
7
|
+
showTimestamp: true,
|
|
8
|
+
showMethodName: true,
|
|
9
|
+
colors: true,
|
|
10
|
+
...options
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
this.chalk = this.options.colors ? chalk : new chalk.Instance({ level: 0 });
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
getMessageText(levelColor, message) {
|
|
17
|
+
if (!this.options.colors) return message;
|
|
18
|
+
|
|
19
|
+
switch (levelColor) {
|
|
20
|
+
case 'green':
|
|
21
|
+
return this.chalk.green(message);
|
|
22
|
+
case 'red':
|
|
23
|
+
return this.chalk.red(message);
|
|
24
|
+
case 'yellow':
|
|
25
|
+
return this.chalk.yellow(message);
|
|
26
|
+
case 'blue':
|
|
27
|
+
return this.chalk.blue(message);
|
|
28
|
+
case 'magenta':
|
|
29
|
+
return this.chalk.magenta(message);
|
|
30
|
+
default:
|
|
31
|
+
return message;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
_formatMessage(level, method, message = '', data = null) {
|
|
36
|
+
const parts = [];
|
|
37
|
+
|
|
38
|
+
let header = '┌─ ';
|
|
39
|
+
|
|
40
|
+
if (this.options.showTimestamp) {
|
|
41
|
+
header += this.chalk.magenta(`[${new Date().toISOString()}] `);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
let levelColor;
|
|
45
|
+
switch (level) {
|
|
46
|
+
case 'SUCCESS':
|
|
47
|
+
header += this.chalk.green('[SUCCESS]');
|
|
48
|
+
levelColor = 'green';
|
|
49
|
+
break;
|
|
50
|
+
case 'ERROR':
|
|
51
|
+
header += this.chalk.red('[ERROR]');
|
|
52
|
+
levelColor = 'red';
|
|
53
|
+
break;
|
|
54
|
+
case 'WARN':
|
|
55
|
+
header += this.chalk.yellow('[WARN]');
|
|
56
|
+
levelColor = 'yellow';
|
|
57
|
+
break;
|
|
58
|
+
case 'INFO':
|
|
59
|
+
header += this.chalk.blue('[INFO]');
|
|
60
|
+
levelColor = 'blue';
|
|
61
|
+
break;
|
|
62
|
+
case 'DEBUG':
|
|
63
|
+
header += this.chalk.magenta('[DEBUG]');
|
|
64
|
+
levelColor = 'magenta';
|
|
65
|
+
break;
|
|
66
|
+
default:
|
|
67
|
+
header += `[${level}]`;
|
|
68
|
+
levelColor = 'default';
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (this.options.showMethodName && method) {
|
|
72
|
+
header += ' ' + this.chalk.white('─') + ' ' + this.chalk.cyan(`[${method}]`);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
parts.push(header);
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
if (data) {
|
|
79
|
+
const dataStr = typeof data === 'object' ? JSON.stringify(data) : String(data);
|
|
80
|
+
parts.push('├─ ' + this.chalk.yellow('Data:') + ' ' + this.chalk.gray(dataStr));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
const connector = data ? '└─ ' : '└─ ';
|
|
85
|
+
const coloredMessage = this.getMessageText(levelColor, String(message));
|
|
86
|
+
parts.push(connector + this.chalk.yellow('Message:') + ' ' + coloredMessage);
|
|
87
|
+
|
|
88
|
+
return parts.join('\n');
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
_formatErrorMessage(error, method, data = null) {
|
|
92
|
+
const parts = [];
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
let header = '┌─ ';
|
|
96
|
+
|
|
97
|
+
if (this.options.showTimestamp) {
|
|
98
|
+
header += this.chalk.magenta(`[${new Date().toISOString()}] `);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
header += this.chalk.red('[ERROR]');
|
|
102
|
+
|
|
103
|
+
if (this.options.showMethodName && method) {
|
|
104
|
+
header += ' ' + this.chalk.white('─') + ' ' + this.chalk.cyan(`[${method}]`);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
parts.push(header);
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
if (data) {
|
|
111
|
+
const dataStr = typeof data === 'object' ? JSON.stringify(data) : String(data);
|
|
112
|
+
parts.push('├─ ' + this.chalk.yellow('Data:') + ' ' + this.chalk.gray(dataStr));
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
const errorMsg = error.message || String(error);
|
|
117
|
+
const messageLines = errorMsg.split('\n');
|
|
118
|
+
const hasPreviousLines = parts.length > 1;
|
|
119
|
+
|
|
120
|
+
parts.push('├─ ' + this.chalk.yellow('Message:') + ' ' + this.chalk.red(messageLines[0]));
|
|
121
|
+
|
|
122
|
+
for (let i = 1; i < messageLines.length; i++) {
|
|
123
|
+
const prefix = hasPreviousLines ? '│ ' : ' ';
|
|
124
|
+
parts.push(prefix + ' ' + this.chalk.red(messageLines[i]));
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
const stack = error.stack || '';
|
|
129
|
+
const callerLine = stack.split('\n')
|
|
130
|
+
.find(line => line.includes('Highrise.<anonymous>') || line.includes(method))
|
|
131
|
+
?.trim() || 'Unknown location';
|
|
132
|
+
|
|
133
|
+
const location = callerLine.match(/\((.*):(\d+):(\d+)\)/)?.[0] || callerLine;
|
|
134
|
+
parts.push('└─ ' + this.chalk.yellow('Caller:') + ' ' + this.chalk.gray(location));
|
|
135
|
+
|
|
136
|
+
return parts.join('\n');
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
log(level, method, message, data = null) {
|
|
140
|
+
const formatted = this._formatMessage(level, method, message, data);
|
|
141
|
+
console.log(this.options.colors ? formatted : this._stripColors(formatted));
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
success(method, message, data = null) {
|
|
145
|
+
this.log('SUCCESS', method, message, data);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
error(method, message, data = null, errorInstance = null) {
|
|
149
|
+
const error = errorInstance instanceof Error ? errorInstance : new Error(String(message));
|
|
150
|
+
const formatted = this._formatErrorMessage(error, method, data);
|
|
151
|
+
console.log(this.options.colors ? formatted : this._stripColors(formatted));
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
warn(method, message, data = null) {
|
|
155
|
+
this.log('WARN', method, message, data);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
info(method, message, data = null) {
|
|
159
|
+
this.log('INFO', method, message, data);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
debug(method, message, data = null) {
|
|
163
|
+
this.log('DEBUG', method, message, data);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
_stripColors(text) {
|
|
167
|
+
return text.replace(/\x1b\[\d+m/g, '');
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
module.exports = { Logger };
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
const WebSocketConstants = require('highrise-core/src/constants/WebSocketConstants');
|
|
2
|
+
|
|
3
|
+
class MetricsManager {
|
|
4
|
+
constructor() {
|
|
5
|
+
this.startTime = Date.now();
|
|
6
|
+
this.messagesReceived = 0;
|
|
7
|
+
this.messagesSent = 0;
|
|
8
|
+
this.eventsProcessed = 0;
|
|
9
|
+
this.errors = 0;
|
|
10
|
+
this.reconnects = 0;
|
|
11
|
+
this.cacheStats = {
|
|
12
|
+
lastUpdate: 0,
|
|
13
|
+
activeUsers: 0,
|
|
14
|
+
inactiveUsers: 0,
|
|
15
|
+
memoryUsage: 0,
|
|
16
|
+
changesProcessed: 0
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
increment(metric, value = 1) {
|
|
21
|
+
if (this[metric] !== undefined) {
|
|
22
|
+
this[metric] += value;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
updateCacheStats(stats) {
|
|
27
|
+
this.cacheStats = {
|
|
28
|
+
...this.cacheStats,
|
|
29
|
+
...stats,
|
|
30
|
+
lastUpdate: Date.now()
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
getUptime() {
|
|
35
|
+
const uptime = Date.now() - this.startTime;
|
|
36
|
+
|
|
37
|
+
const weeks = Math.floor(uptime / (7 * 24 * 60 * 60 * 1000));
|
|
38
|
+
const days = Math.floor((uptime % (7 * 24 * 60 * 60 * 1000)) / (24 * 60 * 60 * 1000));
|
|
39
|
+
const hours = Math.floor((uptime % (24 * 60 * 60 * 1000)) / (60 * 60 * 1000));
|
|
40
|
+
const minutes = Math.floor((uptime % (60 * 60 * 1000)) / (60 * 1000));
|
|
41
|
+
const seconds = Math.floor((uptime % (60 * 1000)) / 1000);
|
|
42
|
+
|
|
43
|
+
const parts = [];
|
|
44
|
+
if (weeks > 0) parts.push(`${weeks}w`);
|
|
45
|
+
if (days > 0) parts.push(`${days}d`);
|
|
46
|
+
if (hours > 0) parts.push(`${hours}h`);
|
|
47
|
+
if (minutes > 0) parts.push(`${minutes}m`);
|
|
48
|
+
if (seconds > 0 || parts.length === 0) parts.push(`${seconds}s`);
|
|
49
|
+
|
|
50
|
+
return parts.join(' ');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
formatBytes(bytes) {
|
|
54
|
+
if (bytes === 0) return '0 B';
|
|
55
|
+
const k = 1024;
|
|
56
|
+
const sizes = ['B', 'KB', 'MB', 'GB'];
|
|
57
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
58
|
+
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
getSnapshot() {
|
|
62
|
+
return {
|
|
63
|
+
uptime: this.getUptime(),
|
|
64
|
+
messagesReceived: this.messagesReceived,
|
|
65
|
+
messagesSent: this.messagesSent,
|
|
66
|
+
eventsProcessed: this.eventsProcessed,
|
|
67
|
+
errors: this.errors,
|
|
68
|
+
reconnects: this.reconnects,
|
|
69
|
+
cacheStats: { ...this.cacheStats }
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
reset() {
|
|
74
|
+
this.startTime = Date.now();
|
|
75
|
+
this.messagesReceived = 0;
|
|
76
|
+
this.messagesSent = 0;
|
|
77
|
+
this.eventsProcessed = 0;
|
|
78
|
+
this.errors = 0;
|
|
79
|
+
this.reconnects = 0;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
module.exports = MetricsManager;
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
const WebSocketConstants = require('highrise-core/src/constants/WebSocketConstants');
|
|
2
|
+
const EventEmitter = require('events');
|
|
3
|
+
const WebSocket = require('ws');
|
|
4
|
+
|
|
5
|
+
class ConnectionManager extends EventEmitter {
|
|
6
|
+
constructor(server, logger, options = {}) {
|
|
7
|
+
super();
|
|
8
|
+
|
|
9
|
+
this.server = server;
|
|
10
|
+
this.logger = logger;
|
|
11
|
+
this.ws = null;
|
|
12
|
+
|
|
13
|
+
this.autoReconnect = options.autoReconnect ?? true;
|
|
14
|
+
this.reconnectDelay = options.reconnectDelay ?? WebSocketConstants.DEFAULT_RECONNECT_DELAY;
|
|
15
|
+
|
|
16
|
+
this.reconnectAttempts = 0;
|
|
17
|
+
this.reconnectTimeout = null;
|
|
18
|
+
this.connected = false;
|
|
19
|
+
this.reconnecting = false;
|
|
20
|
+
this.isManualDisconnect = false;
|
|
21
|
+
|
|
22
|
+
this.connectionAuth = null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
connect(token, roomId, events) {
|
|
26
|
+
this.connectionAuth = { token, roomId, events };
|
|
27
|
+
this.isManualDisconnect = false;
|
|
28
|
+
this._establishConnection();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
_establishConnection() {
|
|
32
|
+
if (!this.connectionAuth) {
|
|
33
|
+
throw new Error('No connection authentication available');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
this._clearReconnectTimeout();
|
|
37
|
+
this.reconnecting = true;
|
|
38
|
+
|
|
39
|
+
const { token, roomId, events } = this.connectionAuth;
|
|
40
|
+
const eventsParam = events.join(',');
|
|
41
|
+
const endpoint = `${WebSocketConstants.BASE_WS_URL}?events=${eventsParam}`;
|
|
42
|
+
|
|
43
|
+
if (this.ws) {
|
|
44
|
+
this._cleanupWebSocket();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
this.ws = new WebSocket(endpoint, {
|
|
48
|
+
headers: {
|
|
49
|
+
[WebSocketConstants.HEADERS.API_TOKEN]: token,
|
|
50
|
+
[WebSocketConstants.HEADERS.ROOM_ID]: roomId
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
this._setupWebSocketHandlers();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
_cleanupWebSocket() {
|
|
58
|
+
if (this.ws) {
|
|
59
|
+
this.ws.removeAllListeners();
|
|
60
|
+
|
|
61
|
+
if (this.ws.readyState !== WebSocket.CLOSED &&
|
|
62
|
+
this.ws.readyState !== WebSocket.CLOSING) {
|
|
63
|
+
this.ws.close();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
this.ws = null;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
_setupWebSocketHandlers() {
|
|
71
|
+
this.ws.on('open', () => this._handleOpen());
|
|
72
|
+
this.ws.on('message', (data) => this.emit('message', data));
|
|
73
|
+
this.ws.on('close', (code, reason) => this._handleClose(code, reason));
|
|
74
|
+
this.ws.on('error', (error) => this._handleError(error));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
_handleOpen() {
|
|
78
|
+
if (!this.connected) {
|
|
79
|
+
this.connected = true;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (!this.reconnecting) {
|
|
83
|
+
this.reconnectAttempts = 0;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
this.reconnecting = false;
|
|
87
|
+
this._clearReconnectTimeout();
|
|
88
|
+
|
|
89
|
+
this.emit('connected');
|
|
90
|
+
this.logger.success('ConnectionManager', 'WebSocket connection established');
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
_handleClose(code, reason) {
|
|
94
|
+
if (this.connected) {
|
|
95
|
+
this.connected = false;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const reasonStr = reason.toString();
|
|
99
|
+
this.emit('disconnected', { code, reason: reasonStr });
|
|
100
|
+
this.logger.warn('ConnectionManager', `Connection closed`, {
|
|
101
|
+
code,
|
|
102
|
+
reason: reasonStr,
|
|
103
|
+
reconnectAttempts: this.reconnectAttempts,
|
|
104
|
+
autoReconnect: this.autoReconnect,
|
|
105
|
+
reconnecting: this.reconnecting
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
const wasReconnecting = this.reconnecting;
|
|
109
|
+
this.reconnecting = false;
|
|
110
|
+
|
|
111
|
+
if (this.autoReconnect && !this.isManualDisconnect && !wasReconnecting) {
|
|
112
|
+
const shouldReconnect = this._shouldReconnect(code, reasonStr);
|
|
113
|
+
|
|
114
|
+
if (shouldReconnect) {
|
|
115
|
+
this._scheduleReconnect();
|
|
116
|
+
} else {
|
|
117
|
+
this.logger.warn('ConnectionManager', 'Not reconnecting due to close code or reason', {
|
|
118
|
+
code,
|
|
119
|
+
reason: reasonStr
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
_shouldReconnect(code, reason) {
|
|
126
|
+
if (code === 1000 && reason.includes('Manual disconnect')) {
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (code === 4001 || code === 4003 || code === 4004) {
|
|
131
|
+
this.logger.error('ConnectionManager', 'Authentication failed, not reconnecting', { code, reason });
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (code === 4009 || code === 4029) {
|
|
136
|
+
this.logger.error('ConnectionManager', 'Rate limited or banned, not reconnecting', { code, reason });
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return true;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
_handleError(error) {
|
|
144
|
+
this.emit('error', error);
|
|
145
|
+
this.logger.error('ConnectionManager', 'WebSocket error', {
|
|
146
|
+
error: error.message,
|
|
147
|
+
reconnectAttempts: this.reconnectAttempts,
|
|
148
|
+
reconnecting: this.reconnecting
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
_clearReconnectTimeout() {
|
|
153
|
+
if (this.reconnectTimeout) {
|
|
154
|
+
clearTimeout(this.reconnectTimeout);
|
|
155
|
+
this.reconnectTimeout = null;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
_scheduleReconnect() {
|
|
160
|
+
if (this.reconnecting) {
|
|
161
|
+
this.logger.debug('ConnectionManager', 'Already reconnecting, skipping duplicate');
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const maxAttempts = WebSocketConstants.MAX_RECONNECT_ATTEMPTS || 10;
|
|
166
|
+
if (this.reconnectAttempts >= maxAttempts) {
|
|
167
|
+
this.logger.error('ConnectionManager', 'Max reconnection attempts reached', {
|
|
168
|
+
attempts: this.reconnectAttempts,
|
|
169
|
+
maxAttempts
|
|
170
|
+
});
|
|
171
|
+
this.emit('reconnectFailed', {
|
|
172
|
+
attempts: this.reconnectAttempts,
|
|
173
|
+
maxAttempts
|
|
174
|
+
});
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
this._clearReconnectTimeout();
|
|
179
|
+
|
|
180
|
+
const delay = Math.min(
|
|
181
|
+
this.reconnectDelay * Math.pow(WebSocketConstants.RECONNECT_BACKOFF_FACTOR, this.reconnectAttempts),
|
|
182
|
+
WebSocketConstants.MAX_RECONNECT_DELAY
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
this.reconnectAttempts++;
|
|
186
|
+
this.reconnecting = true;
|
|
187
|
+
|
|
188
|
+
this.server.metrics.increment('reconnects');
|
|
189
|
+
|
|
190
|
+
this.logger.info('ConnectionManager', `Scheduling reconnect...`, {
|
|
191
|
+
attempt: this.reconnectAttempts,
|
|
192
|
+
delay: `${delay}ms (${Math.round(delay / 1000)}s)`,
|
|
193
|
+
maxDelay: `${WebSocketConstants.MAX_RECONNECT_DELAY}ms`
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
this.reconnectTimeout = setTimeout(() => {
|
|
197
|
+
if (this.connectionAuth && !this.isManualDisconnect) {
|
|
198
|
+
this.logger.info('ConnectionManager', `Attempting reconnection (attempt ${this.reconnectAttempts})...`);
|
|
199
|
+
this._establishConnection();
|
|
200
|
+
} else {
|
|
201
|
+
this.reconnecting = false;
|
|
202
|
+
}
|
|
203
|
+
}, delay);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
disconnect(code = WebSocketConstants.ERROR_CODES.NORMAL_CLOSURE, reason = 'Manual disconnect') {
|
|
207
|
+
this.autoReconnect = false;
|
|
208
|
+
this.isManualDisconnect = true;
|
|
209
|
+
this.reconnecting = false;
|
|
210
|
+
|
|
211
|
+
this._clearReconnectTimeout();
|
|
212
|
+
|
|
213
|
+
if (this.ws && this.ws.readyState !== WebSocket.CLOSED && this.ws.readyState !== WebSocket.CLOSING) {
|
|
214
|
+
this.ws.close(code, reason);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
this.connected = false;
|
|
218
|
+
this.emit('disconnected', { code, reason, manual: true });
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
send(data) {
|
|
222
|
+
if (!this.isConnected() || !this.ws) {
|
|
223
|
+
throw new Error('WebSocket is not connected');
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const payload = typeof data === 'string' ? data : JSON.stringify(data);
|
|
227
|
+
this.ws.send(payload);
|
|
228
|
+
|
|
229
|
+
this.server.metrics.increment('messagesSent');
|
|
230
|
+
|
|
231
|
+
return true;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
isConnected() {
|
|
235
|
+
return this.ws && this.ws.readyState === WebSocket.OPEN;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
getWebSocket() {
|
|
239
|
+
return this.ws;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
getReconnectStats() {
|
|
243
|
+
return {
|
|
244
|
+
attempts: this.reconnectAttempts,
|
|
245
|
+
reconnecting: this.reconnecting,
|
|
246
|
+
autoReconnect: this.autoReconnect,
|
|
247
|
+
isManualDisconnect: this.isManualDisconnect
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
cleanup() {
|
|
252
|
+
this.disconnect();
|
|
253
|
+
this.removeAllListeners();
|
|
254
|
+
this.connectionAuth = null;
|
|
255
|
+
this._cleanupWebSocket();
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
module.exports = ConnectionManager;
|