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,110 @@
|
|
|
1
|
+
class MessageHandler {
|
|
2
|
+
constructor(server) {
|
|
3
|
+
this.server = server;
|
|
4
|
+
this.logger = server.logger;
|
|
5
|
+
this.metrics = server.metrics;
|
|
6
|
+
this.eventsManager = server.eventsManager;
|
|
7
|
+
|
|
8
|
+
this.fireAndForget = server._fireAndForget;
|
|
9
|
+
this.requestResponse = server._requestResponse;
|
|
10
|
+
this.keepAliveManager = server.keepAliveManager;
|
|
11
|
+
this.await = server.await;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
handle(data) {
|
|
15
|
+
this.metrics.increment('messagesReceived');
|
|
16
|
+
|
|
17
|
+
setImmediate(() => {
|
|
18
|
+
try {
|
|
19
|
+
const message = JSON.parse(data.toString());
|
|
20
|
+
if (this.validateMessageStructure(message)) {
|
|
21
|
+
this._processMessage(message);
|
|
22
|
+
} else {
|
|
23
|
+
this.metrics.increment('invalidMessages');
|
|
24
|
+
this.logger.warn('MessageHandler', 'Invalid message structure', {
|
|
25
|
+
type: message._type,
|
|
26
|
+
preview: data.toString().substring(0, 50)
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
} catch (error) {
|
|
30
|
+
this.metrics.increment('errors');
|
|
31
|
+
this.logger.error('MessageHandler', 'Failed to parse WebSocket message', {
|
|
32
|
+
error: error.message,
|
|
33
|
+
dataLength: data.length,
|
|
34
|
+
dataPreview: data.toString().substring(0, 100)
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
_processMessage(message) {
|
|
41
|
+
if (message._type === 'KeepaliveResponse') {
|
|
42
|
+
this.keepAliveManager.handlePong();
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (this.fireAndForget && this.fireAndForget._responseHandler(message)) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (this.requestResponse && this.requestResponse._responseHandler(message)) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
this._processAwaitEvents(message);
|
|
56
|
+
|
|
57
|
+
this.metrics.increment('eventsProcessed');
|
|
58
|
+
this.eventsManager.handleEvent(message);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
_processAwaitEvents(message) {
|
|
62
|
+
switch (message._type) {
|
|
63
|
+
case 'ChatEvent':
|
|
64
|
+
if (!message.whisper) {
|
|
65
|
+
this.await._processEvent('chat', message.user, message.message);
|
|
66
|
+
} else {
|
|
67
|
+
this.await._processEvent('whisper', message.user, message.message);
|
|
68
|
+
}
|
|
69
|
+
break;
|
|
70
|
+
|
|
71
|
+
case 'TipReactionEvent':
|
|
72
|
+
this.await._processEvent('tip',
|
|
73
|
+
message.sender,
|
|
74
|
+
message.receiver,
|
|
75
|
+
{ type: message.item?.type, amount: message.item?.amount }
|
|
76
|
+
);
|
|
77
|
+
break;
|
|
78
|
+
|
|
79
|
+
case 'UserMovedEvent':
|
|
80
|
+
this.await._processEvent('movement',
|
|
81
|
+
message.user,
|
|
82
|
+
message.position,
|
|
83
|
+
message.position.entity_id ? message.position : null
|
|
84
|
+
);
|
|
85
|
+
break;
|
|
86
|
+
|
|
87
|
+
case 'DirectMessageEvent':
|
|
88
|
+
this.await._processEvent('direct',
|
|
89
|
+
message.user,
|
|
90
|
+
message.message,
|
|
91
|
+
message.conversation
|
|
92
|
+
);
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
validateMessageStructure(message) {
|
|
98
|
+
if (!message || typeof message !== 'object') {
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (!message._type || typeof message._type !== 'string') {
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return true;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
module.exports = MessageHandler;
|
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
const WebSocketConstants = require("highrise-core/src/constants/WebSocketConstants");
|
|
2
|
+
|
|
3
|
+
class FireAndForgetSender {
|
|
4
|
+
constructor(websocket) {
|
|
5
|
+
if (!websocket || typeof websocket.connectionManager.send !== 'function') {
|
|
6
|
+
throw new Error('WebSocket instance with send method is required');
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
this._ws = websocket;
|
|
10
|
+
this._pendingOperations = new Map();
|
|
11
|
+
this._operationId = 0;
|
|
12
|
+
this._maxRetries = 3;
|
|
13
|
+
this._retryDelay = 100;
|
|
14
|
+
|
|
15
|
+
this._responseHandler = this._responseHandler.bind(this);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
_responseHandler(message) {
|
|
19
|
+
try {
|
|
20
|
+
if (message._type === 'Error' && message.rid) {
|
|
21
|
+
const operation = this._pendingOperations.get(message.rid);
|
|
22
|
+
if (operation) {
|
|
23
|
+
this._pendingOperations.delete(message.rid);
|
|
24
|
+
|
|
25
|
+
const error = new Error(message.message);
|
|
26
|
+
error.do_not_reconnect = message.do_not_reconnect || false;
|
|
27
|
+
error.originalData = operation.data;
|
|
28
|
+
|
|
29
|
+
operation.reject(error);
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (message.rid && this._pendingOperations.has(message.rid)) {
|
|
35
|
+
const operation = this._pendingOperations.get(message.rid);
|
|
36
|
+
this._pendingOperations.delete(message.rid);
|
|
37
|
+
operation.resolve({
|
|
38
|
+
success: true,
|
|
39
|
+
operationId: message.rid,
|
|
40
|
+
response: message
|
|
41
|
+
});
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
} catch (error) {
|
|
46
|
+
console.error('Error in response handler:', error);
|
|
47
|
+
}
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async send(data, maxRetries = this._maxRetries) {
|
|
52
|
+
if (!this._ws.isConnected()) {
|
|
53
|
+
throw new Error('WebSocket is not connected');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const operationId = (++this._operationId).toString();
|
|
57
|
+
|
|
58
|
+
return new Promise((resolve, reject) => {
|
|
59
|
+
const operation = {
|
|
60
|
+
id: operationId,
|
|
61
|
+
data: this._deepClone(data),
|
|
62
|
+
attempts: 0,
|
|
63
|
+
maxRetries: Math.min(maxRetries, 10),
|
|
64
|
+
timestamp: Date.now(),
|
|
65
|
+
resolve,
|
|
66
|
+
reject
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
if (typeof operation.data === 'object' && operation.data !== null) {
|
|
70
|
+
operation.data.rid = operationId;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
this._pendingOperations.set(operationId, operation);
|
|
74
|
+
this._safeSend(operation);
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async _safeSend(operation) {
|
|
79
|
+
try {
|
|
80
|
+
operation.attempts++;
|
|
81
|
+
const payload = typeof operation.data === 'string'
|
|
82
|
+
? operation.data
|
|
83
|
+
: JSON.stringify(operation.data);
|
|
84
|
+
|
|
85
|
+
this._ws.send(payload);
|
|
86
|
+
|
|
87
|
+
} catch (error) {
|
|
88
|
+
if (operation.attempts < operation.maxRetries) {
|
|
89
|
+
setTimeout(() => {
|
|
90
|
+
this._safeSend(operation);
|
|
91
|
+
}, this._retryDelay * Math.pow(2, operation.attempts - 1));
|
|
92
|
+
} else {
|
|
93
|
+
this._pendingOperations.delete(operation.id);
|
|
94
|
+
operation.reject(new Error(`Failed after ${operation.attempts} attempts: ${error.message}`));
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
_addTimeoutFallback(operationId, timeoutMs = 5000) {
|
|
100
|
+
return setTimeout(() => {
|
|
101
|
+
if (this._pendingOperations.has(operationId)) {
|
|
102
|
+
const operation = this._pendingOperations.get(operationId);
|
|
103
|
+
this._pendingOperations.delete(operationId);
|
|
104
|
+
|
|
105
|
+
console.warn(`Operation ${operationId} timed out after ${timeoutMs}ms`);
|
|
106
|
+
operation.reject(new Error(`Operation timed out after ${timeoutMs}ms`));
|
|
107
|
+
}
|
|
108
|
+
}, timeoutMs);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
_deepClone(obj) {
|
|
112
|
+
if (obj === null || typeof obj !== 'object') return obj;
|
|
113
|
+
if (obj instanceof Date) return new Date(obj);
|
|
114
|
+
if (Array.isArray(obj)) return obj.map(item => this._deepClone(item));
|
|
115
|
+
|
|
116
|
+
const cloned = {};
|
|
117
|
+
Object.keys(obj).forEach(key => {
|
|
118
|
+
cloned[key] = this._deepClone(obj[key]);
|
|
119
|
+
});
|
|
120
|
+
return cloned;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
getPendingCount() {
|
|
124
|
+
return this._pendingOperations.size;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
cancelAll() {
|
|
128
|
+
this._pendingOperations.clear();
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
setRetryConfig({ maxRetries, retryDelay }) {
|
|
132
|
+
if (maxRetries !== undefined) {
|
|
133
|
+
this._maxRetries = Math.max(0, Math.min(maxRetries, 10));
|
|
134
|
+
}
|
|
135
|
+
if (retryDelay !== undefined) {
|
|
136
|
+
this._retryDelay = Math.max(10, Math.min(retryDelay, 5000));
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
destroy() {
|
|
141
|
+
this.cancelAll();
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
class RequestResponseSender {
|
|
146
|
+
constructor(websocket, options = {}) {
|
|
147
|
+
if (!websocket || typeof websocket.connectionManager.send !== 'function') {
|
|
148
|
+
throw new Error('WebSocket instance with send method is required');
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
this._ws = websocket;
|
|
152
|
+
this._pendingRequests = new Map();
|
|
153
|
+
this._requestId = 0;
|
|
154
|
+
this._defaultTimeout = options.defaultTimeout || WebSocketConstants.DEFAULT_TIMEOUT;
|
|
155
|
+
this._maxRetries = options.maxRetries || WebSocketConstants.DEFAULT_MAX_RETRIES;
|
|
156
|
+
this._retryDelay = options.retryDelay || WebSocketConstants.DEFAULT_RETRY_DELAY;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
_errorHandler(data) {
|
|
160
|
+
try {
|
|
161
|
+
const message = JSON.parse(data.toString());
|
|
162
|
+
|
|
163
|
+
if (message._type === 'Error' && message.rid) {
|
|
164
|
+
const requestId = message.rid;
|
|
165
|
+
|
|
166
|
+
if (this._pendingRequests.has(requestId)) {
|
|
167
|
+
const request = this._pendingRequests.get(requestId);
|
|
168
|
+
this._cleanupRequest(requestId);
|
|
169
|
+
request.resolvers.reject(new Error(message.message));
|
|
170
|
+
return true;
|
|
171
|
+
}
|
|
172
|
+
return false;
|
|
173
|
+
}
|
|
174
|
+
return false
|
|
175
|
+
} catch (error) {
|
|
176
|
+
return false;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
async send(data, timeout = this._defaultTimeout, maxRetries = this._maxRetries) {
|
|
181
|
+
if (!this._ws.isConnected()) {
|
|
182
|
+
throw new Error('WebSocket is not connected');
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const requestId = String(++this._requestId);
|
|
186
|
+
const request = this._createRequest(requestId, data, timeout, maxRetries);
|
|
187
|
+
|
|
188
|
+
try {
|
|
189
|
+
this._pendingRequests.set(requestId, request);
|
|
190
|
+
return await this._safeSend(request);
|
|
191
|
+
} catch (error) {
|
|
192
|
+
this._cleanupRequest(requestId);
|
|
193
|
+
throw error;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
_createRequest(requestId, data, timeout, maxRetries) {
|
|
198
|
+
const requestData = {
|
|
199
|
+
...this._deepClone(data),
|
|
200
|
+
rid: requestId,
|
|
201
|
+
timestamp: Date.now()
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
return {
|
|
205
|
+
id: requestId,
|
|
206
|
+
data: requestData,
|
|
207
|
+
attempts: 0,
|
|
208
|
+
maxRetries: Math.min(maxRetries, 5),
|
|
209
|
+
timeout: Math.min(Math.max(timeout, 1000), 60000),
|
|
210
|
+
resolvers: {
|
|
211
|
+
resolve: null,
|
|
212
|
+
reject: null
|
|
213
|
+
},
|
|
214
|
+
timeoutId: null,
|
|
215
|
+
timestamp: Date.now()
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
async _safeSend(request) {
|
|
220
|
+
return new Promise((resolve, reject) => {
|
|
221
|
+
request.resolvers = { resolve, reject };
|
|
222
|
+
|
|
223
|
+
request.timeoutId = setTimeout(() => {
|
|
224
|
+
if (this._pendingRequests.has(request.id)) {
|
|
225
|
+
this._cleanupRequest(request.id);
|
|
226
|
+
}
|
|
227
|
+
}, request.timeout);
|
|
228
|
+
|
|
229
|
+
this._executeSend(request);
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
_executeSend(request) {
|
|
234
|
+
try {
|
|
235
|
+
request.attempts++;
|
|
236
|
+
|
|
237
|
+
const payload = typeof request.data === 'string'
|
|
238
|
+
? request.data
|
|
239
|
+
: JSON.stringify(request.data);
|
|
240
|
+
|
|
241
|
+
this._ws.send(payload);
|
|
242
|
+
|
|
243
|
+
} catch (error) {
|
|
244
|
+
if (request.attempts < request.maxRetries) {
|
|
245
|
+
const delay = this._retryDelay * Math.pow(2, request.attempts - 1);
|
|
246
|
+
setTimeout(() => this._executeSend(request), delay);
|
|
247
|
+
} else {
|
|
248
|
+
this._cleanupRequest(request.id);
|
|
249
|
+
request.resolvers.reject(new Error(`Failed after ${request.attempts} attempts: ${error.message}`));
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
_responseHandler(message) {
|
|
255
|
+
try {
|
|
256
|
+
|
|
257
|
+
if (message.rid && this._pendingRequests.has(message.rid)) {
|
|
258
|
+
const request = this._pendingRequests.get(message.rid);
|
|
259
|
+
this._cleanupRequest(message.rid);
|
|
260
|
+
request.resolvers.resolve(message);
|
|
261
|
+
return true;
|
|
262
|
+
}
|
|
263
|
+
} catch (error) {
|
|
264
|
+
}
|
|
265
|
+
return false;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
_cleanupRequest(requestId) {
|
|
269
|
+
const request = this._pendingRequests.get(requestId);
|
|
270
|
+
if (request) {
|
|
271
|
+
if (request.timeoutId) {
|
|
272
|
+
clearTimeout(request.timeoutId);
|
|
273
|
+
}
|
|
274
|
+
this._pendingRequests.delete(requestId);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
_deepClone(obj) {
|
|
279
|
+
if (obj === null || typeof obj !== 'object') return obj;
|
|
280
|
+
if (obj instanceof Date) return new Date(obj);
|
|
281
|
+
if (obj instanceof Array) return obj.map(item => this._deepClone(item));
|
|
282
|
+
if (obj instanceof Object) {
|
|
283
|
+
const cloned = {};
|
|
284
|
+
Object.keys(obj).forEach(key => {
|
|
285
|
+
cloned[key] = this._deepClone(obj[key]);
|
|
286
|
+
});
|
|
287
|
+
return cloned;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
getPendingCount() {
|
|
292
|
+
return this._pendingRequests.size;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
cancelRequest(requestId) {
|
|
296
|
+
const request = this._pendingRequests.get(requestId);
|
|
297
|
+
if (request) {
|
|
298
|
+
this._cleanupRequest(requestId);
|
|
299
|
+
request.resolvers.reject('Request cancelled');
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
cancelAll() {
|
|
304
|
+
for (const [requestId, request] of this._pendingRequests) {
|
|
305
|
+
this._cleanupRequest(requestId);
|
|
306
|
+
request.resolvers.reject('All requests cancelled');
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
setConfig({ defaultTimeout, maxRetries, retryDelay }) {
|
|
311
|
+
if (defaultTimeout !== undefined) {
|
|
312
|
+
this._defaultTimeout = Math.min(Math.max(defaultTimeout, 1000), 60000);
|
|
313
|
+
}
|
|
314
|
+
if (maxRetries !== undefined) {
|
|
315
|
+
this._maxRetries = Math.max(0, Math.min(maxRetries, 5));
|
|
316
|
+
}
|
|
317
|
+
if (retryDelay !== undefined) {
|
|
318
|
+
this._retryDelay = Math.max(10, Math.min(retryDelay, 5000));
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
destroy() {
|
|
323
|
+
this.cancelAll()
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
module.exports = {
|
|
327
|
+
FireAndForgetSender,
|
|
328
|
+
RequestResponseSender
|
|
329
|
+
};
|