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,88 @@
1
+ const convertAvatarToPNG = require("../../../utils/ConvertSvgToPng");
2
+
3
+ class UserCategory {
4
+ constructor(webapi, AxiosError) {
5
+ this.webapi = webapi;
6
+ this.logger = webapi.logger;
7
+ this.base = webapi.base;
8
+ this.AxiosErrorHandler = AxiosError
9
+ }
10
+
11
+ async get(identifier) {
12
+ const method = 'bot.webapi.user.get'
13
+
14
+ try {
15
+ if (!identifier || typeof identifier !== 'string') {
16
+ throw new TypeError('identifier must be a non-empty string');
17
+ }
18
+
19
+ const response = await this.base.get(`/users/${identifier}`);
20
+ if (response.status === 200 && response.data?.user) {
21
+ const { outfit, ...userData } = response.data.user;
22
+ return this._formatUserData(userData);
23
+ }
24
+ return null;
25
+ } catch (error) {
26
+ if (error instanceof TypeError) {
27
+ this.logger.error(method, `TypeError: ${error.message}`, { identifier }, error);
28
+ } else {
29
+ return this.AxiosErrorHandler.handle(error, this.logger, method, { identifier });
30
+ }
31
+ }
32
+ }
33
+
34
+ async getUserAvatar(identifier, imagePath = 'avatar.png') {
35
+ const method = `bot.webapi.user.getUserAvatar`
36
+ try {
37
+ if (!identifier || typeof identifier !== 'string') {
38
+ throw new TypeError('identifier must be a non-empty string');
39
+ }
40
+
41
+ if (imagePath && !imagePath.endsWith('.png')) {
42
+ throw new TypeError(`imagePath extension must be .png`);
43
+ }
44
+
45
+ const UserData = await this.get(identifier);
46
+
47
+ if (!UserData || !UserData.svg) {
48
+ throw new Error('User not found or no avatar available');
49
+ }
50
+
51
+ const svg = UserData.svg;
52
+
53
+ const status = await convertAvatarToPNG(svg, imagePath);
54
+ return status;
55
+
56
+ } catch (error) {
57
+ if (error instanceof TypeError) {
58
+ this.logger.error(method, `TypeError: ${error.message}`, { identifier }, error);
59
+ } else {
60
+ this.logger.error(method, `Failed to get user avatar: ${error.message}`, { identifier }, error);
61
+ }
62
+ return null;
63
+ }
64
+ }
65
+
66
+ _formatUserData(userData) {
67
+ return {
68
+ id: userData.user_id,
69
+ username: userData.username,
70
+ bio: userData.bio || '',
71
+ joinedAt: this.webapi._formatDate(userData.created_at),
72
+ lastOnline: this.webapi._formatDate(userData.last_online),
73
+ followers: userData.follower_count || 0,
74
+ following: userData.following_count || 0,
75
+ friends: userData.friend_count || 0,
76
+ currentRoom: userData.current_room || '',
77
+ country: userData.country || '',
78
+ crew: userData.crew || '',
79
+ voiceEnabled: userData.voice_enabled || false,
80
+ discordConnected: userData.discord_connected || false,
81
+ avatar: userData.profile_photo || '',
82
+ icon: userData.photo_id || '',
83
+ svg: userData.avatar_svg || ''
84
+ };
85
+ }
86
+ }
87
+
88
+ module.exports = UserCategory
@@ -0,0 +1,52 @@
1
+ const axios = require('axios')
2
+ const UserAgent = require('user-agents')
3
+ const { AxiosErrorHandler } = require('../Handlers/AxiosErrorHandler')
4
+ const UserCategory = require('./Category/User')
5
+ const RoomCategory = require('./Category/Room')
6
+ const PostCategory = require('./Category/Post')
7
+ const ItemCategory = require('./Category/Item')
8
+ const GrabCategory = require('./Category/Grab')
9
+
10
+ class WebApi {
11
+ constructor(bot) {
12
+ this.bot = bot
13
+ this.logger = bot.utils.logger
14
+ this.base = axios.create({
15
+ baseURL: 'https://webapi.highrise.game',
16
+ timeout: 30000,
17
+ headers: {
18
+ 'Content-Type': 'application/json',
19
+ 'User-Agent': new UserAgent().toString()
20
+ }
21
+ });
22
+
23
+ this.user = new UserCategory(this, AxiosErrorHandler);
24
+ this.room = new RoomCategory(this, AxiosErrorHandler);
25
+ this.post = new PostCategory(this, AxiosErrorHandler);
26
+ this.item = new ItemCategory(this, AxiosErrorHandler);
27
+ this.grab = new GrabCategory(this, AxiosErrorHandler);
28
+ }
29
+
30
+ _formatDate(dateString) {
31
+ if (!dateString) return null;
32
+ try {
33
+ const date = new Date(dateString);
34
+ return isNaN(date.getTime()) ? null : date.toISOString();
35
+ } catch (error) {
36
+ return null;
37
+ }
38
+ }
39
+
40
+ _formatInventory(inventory) {
41
+ if (!inventory || !Array.isArray(inventory.items)) return null;
42
+ return {
43
+ items: inventory.items.map(item => ({
44
+ itemId: item.item_id,
45
+ activePalette: item.active_palette || null,
46
+ accountBound: item.account_bound || false
47
+ }))
48
+ };
49
+ }
50
+ }
51
+
52
+ module.exports = { WebApi };
@@ -0,0 +1,89 @@
1
+ const reactions = ['clap', 'heart', 'thumbs', 'wave', 'wink'];
2
+ const gold_bars = [1, 5, 10, 50, 100, 500, 1000, 5000, 10000]
3
+ const payment_methods = ["bot_wallet_only", "bot_wallet_priority", "user_wallet_only"]
4
+ const default_skin = [
5
+ {
6
+ type: 'clothing',
7
+ amount: 1,
8
+ id: 'hair_front-n_malenew05',
9
+ account_bound: false,
10
+ active_palette: 1
11
+ },
12
+ {
13
+ type: 'clothing',
14
+ amount: 1,
15
+ id: 'hair_back-n_malenew05',
16
+ account_bound: false,
17
+ active_palette: 1
18
+ },
19
+ {
20
+ type: 'clothing',
21
+ amount: 1,
22
+ id: 'body-flesh',
23
+ account_bound: false,
24
+ active_palette: 27
25
+ },
26
+ {
27
+ type: 'clothing',
28
+ amount: 1,
29
+ id: 'eye-n_basic2018malesquaresleepy',
30
+ account_bound: false,
31
+ active_palette: 7
32
+ },
33
+ {
34
+ type: 'clothing',
35
+ amount: 1,
36
+ id: 'eyebrow-n_basic2018newbrows07',
37
+ account_bound: false,
38
+ active_palette: 0
39
+ },
40
+ {
41
+ type: 'clothing',
42
+ amount: 1,
43
+ id: 'nose-n_basic2018newnose05',
44
+ account_bound: false,
45
+ active_palette: 0
46
+ },
47
+ {
48
+ type: 'clothing',
49
+ amount: 1,
50
+ id: 'mouth-basic2018chippermouth',
51
+ account_bound: false,
52
+ active_palette: -1
53
+ },
54
+ {
55
+ type: 'clothing',
56
+ amount: 1,
57
+ id: 'freckle-n_basic2018freckle04',
58
+ account_bound: false,
59
+ active_palette: 0
60
+ },
61
+ {
62
+ type: 'clothing',
63
+ amount: 1,
64
+ id: 'shirt-n_room32019denimjackethoodie',
65
+ account_bound: false,
66
+ active_palette: 0
67
+ },
68
+ {
69
+ type: 'clothing',
70
+ amount: 1,
71
+ id: 'pants-n_starteritems2019cuffedjeanswhite',
72
+ account_bound: false,
73
+ active_palette: 0
74
+ },
75
+ {
76
+ type: 'clothing',
77
+ amount: 1,
78
+ id: 'shoes-n_room32019socksneakersgrey',
79
+ account_bound: false,
80
+ active_palette: 0
81
+ }
82
+ ]
83
+
84
+ module.exports = {
85
+ reactions,
86
+ gold_bars,
87
+ default_skin,
88
+ payment_methods
89
+ }
@@ -0,0 +1,80 @@
1
+ const WebSocketConstants = {
2
+ // Connection
3
+ KEEPALIVE_INTERVAL: 15000,
4
+ MAX_RECONNECT_DELAY: 300000, // 5 minutes
5
+ RECONNECT_BACKOFF_FACTOR: 1.5,
6
+ DEFAULT_RECONNECT_DELAY: 5000,
7
+ DEFAULT_AUTO_RECONNECT: true,
8
+ MAX_RECONNECT_ATTEMPTS: 10,
9
+ RECONNECT_BACKOFF_FACTOR: 1.5,
10
+
11
+ // Logger Options
12
+ DEFAULT_LOGGER_OPTIONS: {
13
+ SHOW_TIMESTAMP: true,
14
+ SHOW_METHODS_NAME: true,
15
+ SHOW_COLORS: true
16
+ },
17
+
18
+ // Metrics
19
+ METRICS_UPDATE_INTERVAL: 5000,
20
+ INACTIVE_USER_THRESHOLD: 30000, // 30 seconds
21
+
22
+ // Request/Response
23
+ DEFAULT_TIMEOUT: 10000,
24
+ DEFAULT_MAX_RETRIES: 2,
25
+ DEFAULT_RETRY_DELAY: 100,
26
+
27
+ // Validation
28
+ TOKEN_LENGTH: 64,
29
+ ROOM_ID_LENGTH: 24,
30
+
31
+ // Endpoints
32
+ BASE_WS_URL: 'wss://highrise.game/web/botapi',
33
+
34
+ // Headers
35
+ HEADERS: {
36
+ API_TOKEN: 'api-token',
37
+ ROOM_ID: 'room-id',
38
+ CONTENT_TYPE: 'Content-Type',
39
+ USER_AGENT: 'User-Agent'
40
+ },
41
+
42
+ // Events
43
+ DEFAULT_EVENTS: [
44
+ 'SessionMetadata', 'ChatEvent', 'WhisperEvent',
45
+ 'UserMovedEvent', 'UserJoinedEvent', 'UserLeftEvent',
46
+ 'MessageEvent', 'TipReactionEvent', 'RoomModeratedEvent'
47
+ ],
48
+
49
+ // Error Codes
50
+ ERROR_CODES: {
51
+ NORMAL_CLOSURE: 1000,
52
+ GOING_AWAY: 1001,
53
+ PROTOCOL_ERROR: 1002,
54
+ UNSUPPORTED_DATA: 1003,
55
+ NO_STATUS_RECEIVED: 1005,
56
+ ABNORMAL_CLOSURE: 1006,
57
+ INVALID_FRAME_PAYLOAD_DATA: 1007,
58
+ POLICY_VIOLATION: 1008,
59
+ MESSAGE_TOO_BIG: 1009,
60
+ MISSING_EXTENSION: 1010,
61
+ INTERNAL_ERROR: 1011,
62
+ SERVICE_RESTART: 1012,
63
+ TRY_AGAIN_LATER: 1013,
64
+ BAD_GATEWAY: 1014,
65
+ TLS_HANDSHAKE_FAILED: 1015
66
+ },
67
+
68
+ // Closing Codes
69
+ CLOSE_CODES: {
70
+ NORMAL: 1000,
71
+ AUTH_FAILED: 4001,
72
+ INVALID_TOKEN: 4003,
73
+ INVALID_ROOM: 4004,
74
+ RATE_LIMITED: 4009,
75
+ BANNED: 4029,
76
+ SERVER_ERROR: 5000
77
+ }
78
+ };
79
+
80
+ module.exports = WebSocketConstants;
@@ -0,0 +1,123 @@
1
+ const HighriseWebsocket = require('highrise-core/src/core/HighriseWebsocket');
2
+ const { ConfigValidator } = require('highrise-core/src/validators/ConfigValidator');
3
+ const { checkVersion } = require('highrise-core/src/utils/versionCheck');
4
+ const WebSocketConstants = require('highrise-core/src/constants/WebSocketConstants')
5
+ const Utils = require('highrise-core/src/classes/Actions/Utils');
6
+
7
+ class Highrise extends HighriseWebsocket {
8
+ constructor(events, userOptions = {}) {
9
+ const userLoggerOptions = userOptions.LoggerOptions || {};
10
+
11
+ const options = {
12
+ LoggerOptions: {
13
+ showTimestamp: userLoggerOptions.showTimestamp !== undefined
14
+ ? userLoggerOptions.showTimestamp
15
+ : WebSocketConstants.DEFAULT_LOGGER_OPTIONS.SHOW_TIMESTAMP,
16
+
17
+ showMethodName: userLoggerOptions.showMethodName !== undefined
18
+ ? userLoggerOptions.showMethodName
19
+ : WebSocketConstants.DEFAULT_LOGGER_OPTIONS.SHOW_METHODS_NAME,
20
+
21
+ colors: userLoggerOptions.colors !== undefined
22
+ ? userLoggerOptions.colors
23
+ : WebSocketConstants.DEFAULT_LOGGER_OPTIONS.SHOW_COLORS
24
+ },
25
+
26
+ autoReconnect: userOptions.autoReconnect !== undefined
27
+ ? userOptions.autoReconnect
28
+ : WebSocketConstants.DEFAULT_AUTO_RECONNECT,
29
+
30
+ reconnectDelay: userOptions.reconnectDelay !== undefined
31
+ ? userOptions.reconnectDelay
32
+ : WebSocketConstants.DEFAULT_RECONNECT_DELAY,
33
+
34
+ customRoles: Array.isArray(userOptions.customRoles)
35
+ ? userOptions.customRoles
36
+ : [],
37
+
38
+ music: {
39
+ enabled: userOptions.music.enabled || false,
40
+ }
41
+ };
42
+
43
+ const eventsValidation = ConfigValidator.validateEvents(events);
44
+ const optionsValidation = ConfigValidator.validateHighriseOptions(options);
45
+
46
+ if (!eventsValidation.success) {
47
+ throw new Error(`Events validation failed: ${eventsValidation.errors.join(', ')}`);
48
+ }
49
+
50
+ if (!optionsValidation.success) {
51
+ throw new Error(`Options validation failed: ${optionsValidation.errors.join(', ')}`);
52
+ }
53
+
54
+ super(eventsValidation.validatedEvents, optionsValidation.validatedConfig);
55
+
56
+ this.utils = new Utils(this, {
57
+ ...options.LoggerOptions,
58
+ customRoles: options.customRoles
59
+ });
60
+
61
+ this._setupPublicAPI();
62
+
63
+ if (options.music.enabled) {
64
+ try {
65
+ const MusicClass = require('../classes/Actions/Music');
66
+ this.music = new MusicClass(this, options.music)
67
+ } catch (error) {
68
+ console.error('Failed to initialize MusicClass:', error);
69
+ this.music = null;
70
+ }
71
+ } else {
72
+ this.music = null;
73
+ }
74
+
75
+ checkVersion(this.utils.logger);
76
+ }
77
+
78
+ _setupPublicAPI() {
79
+ const { MessageClass } = require('../classes/Actions/Public');
80
+ const { WhisperClass } = require('../classes/Actions/Public');
81
+ const { DirectClass } = require('../classes/Actions/Direct');
82
+ const { PlayerClass } = require('../classes/Actions/Player');
83
+ const { RoomClass } = require('../classes/Actions/Room');
84
+ const { ChannelClass } = require('../classes/Actions/Channel');
85
+ const { InventoryClass } = require('../classes/Actions/Inventory');
86
+ const { WebApi } = require('../classes/WebApi/webapi');
87
+
88
+ this.message = new MessageClass(this);
89
+ this.whisper = new WhisperClass(this);
90
+ this.direct = new DirectClass(this);
91
+ this.player = new PlayerClass(this);
92
+ this.room = new RoomClass(this);
93
+ this.inventory = new InventoryClass(this);
94
+ this.webapi = new WebApi(this);
95
+ this.channel = new ChannelClass(this);
96
+
97
+ this.cache = {
98
+ position: this.movementCache
99
+ };
100
+ }
101
+
102
+ login(token, roomId) {
103
+ const credentialsValidation = ConfigValidator.validateCredentials(token, roomId);
104
+ if (!credentialsValidation.success) {
105
+ throw new Error(`Login validation failed: ${credentialsValidation.errors.join(', ')}`);
106
+ }
107
+
108
+ this.connect(token, roomId);
109
+ }
110
+
111
+ disconnect() {
112
+ if (this.music) {
113
+ this.music.destroy();
114
+ }
115
+
116
+ this.utils.DanceFloor.destroy();
117
+ this.channel.clear();
118
+ this.utils.permissions.clearAll()
119
+ super.disconnect(1000, 'Graceful shutdown');
120
+ }
121
+ }
122
+
123
+ module.exports = Highrise;
@@ -0,0 +1,228 @@
1
+ const EventEmitter = require('events');
2
+
3
+ const ConnectionManager = require('highrise-core/src/classes/Managers/Networking/ConnectionManager');
4
+ const KeepAliveManager = require('highrise-core/src/classes/Managers/Networking/KeepAliveManager');
5
+ const MessageHandler = require('highrise-core/src/classes/Managers/Networking/MessageHandler');
6
+ const EventsManager = require('highrise-core/src/classes/Managers/Networking/EventsManager');
7
+ const { FireAndForgetSender, RequestResponseSender } = require('highrise-core/src/classes/Managers/Networking/Request');
8
+
9
+ const MetricsManager = require('highrise-core/src/classes/Managers/Helpers/MetricsManager');
10
+ const CleanupManager = require('highrise-core/src/classes/Managers/Helpers/CleanupManager');
11
+
12
+ const ChannelManager = require('highrise-core/src/classes/Managers/ChannelManager');
13
+ const ConnectionValidator = require('highrise-core/src/validators/ConnectionValidator');
14
+ const WebSocketHandlers = require('highrise-core/src/classes/Handlers/WebSocketHandlers');
15
+
16
+ const { DirectClass } = require('highrise-core/src/classes/Actions/Direct');
17
+ const { MovementCache } = require('highrise-core/src/classes/Caches/MovementCache');
18
+ const AwaitClass = require('highrise-core/src/classes/Actions/Awaiter');
19
+ const { Logger } = require('highrise-core/src/classes/Managers/Helpers/LoggerManager');
20
+
21
+ class HighriseWebsocket extends EventEmitter {
22
+ constructor(events, options = {}) {
23
+ super();
24
+
25
+ this.events = ConnectionValidator.validateEvents(events);
26
+ this.options = ConnectionValidator.validateOptions(options);
27
+
28
+ this._initializeProperties();
29
+ this._setupManagers();
30
+ this._setupErrorHandlers();
31
+ }
32
+
33
+ _initializeProperties() {
34
+ this._connected = false;
35
+ this._info = {
36
+ user: { id: null, username: null },
37
+ room: { id: null, name: null, owner_id: null }
38
+ };
39
+ }
40
+
41
+ _setupManagers() {
42
+ this.logger = new Logger(this, this.options.LoggerOptions || {});
43
+
44
+ this.await = new AwaitClass(this);
45
+ this.metrics = new MetricsManager();
46
+ this.cleanupManager = new CleanupManager();
47
+ this.eventsManager = new EventsManager(this)
48
+ this.connectionManager = new ConnectionManager(this, this.logger, this.options);
49
+ this.keepAliveManager = new KeepAliveManager(this.connectionManager, this.logger);
50
+ this._fireAndForget = new FireAndForgetSender(this);
51
+ this._requestResponse = new RequestResponseSender(this);
52
+ this.messageHandler = new MessageHandler(this);
53
+ this._channelManager = new ChannelManager(this);
54
+
55
+ this._direct = new DirectClass(this);
56
+ this._movementCache = new MovementCache();
57
+
58
+ this.cleanupManager.registerResource(this._movementCache);
59
+ this.cleanupManager.registerResource(this._fireAndForget);
60
+ this.cleanupManager.registerResource(this._requestResponse);
61
+ this.cleanupManager.registerResource(this._channelManager);
62
+
63
+ const metricsInterval = setInterval(() => this._updateMetrics(), 5000);
64
+ this.cleanupManager.registerInterval(metricsInterval);
65
+
66
+ this._setupEventListeners();
67
+ }
68
+
69
+ _setupEventListeners() {
70
+ this.cleanupManager.registerListener(
71
+ this.connectionManager,
72
+ 'connected',
73
+ this._handleConnected.bind(this)
74
+ );
75
+
76
+ this.cleanupManager.registerListener(
77
+ this.connectionManager,
78
+ 'disconnected',
79
+ this._handleDisconnected.bind(this)
80
+ );
81
+
82
+ this.cleanupManager.registerListener(
83
+ this.connectionManager,
84
+ 'message',
85
+ (data) => this.messageHandler.handle(data)
86
+ );
87
+
88
+ this.cleanupManager.registerListener(
89
+ this.connectionManager,
90
+ 'error',
91
+ (error) => this.emit('error', error)
92
+ );
93
+ }
94
+
95
+ _setupErrorHandlers() {
96
+ this.webSocketHandlers = new WebSocketHandlers(this);
97
+ this.processHandlers = this.webSocketHandlers.setupErrorHandlers();
98
+ this.cleanupManager.registerResource({
99
+ cleanup: () => this.webSocketHandlers.removeErrorHandlers(this.processHandlers)
100
+ });
101
+ }
102
+
103
+ _handleConnected() {
104
+ this._connected = true;
105
+ this.keepAliveManager.start();
106
+ this.emit('connected');
107
+ }
108
+
109
+ _handleDisconnected(details) {
110
+ this._connected = false;
111
+ this.keepAliveManager.stop();
112
+ this.emit('disconnected', details);
113
+ }
114
+
115
+ connect(token, roomId) {
116
+ try {
117
+ ConnectionValidator.validateCredentials(token, roomId);
118
+
119
+ this._info.room.id = roomId;
120
+ this.connectionManager.connect(token, roomId, this.events);
121
+
122
+ } catch (error) {
123
+ this.logger.error('HighriseWebsocket.connect', `Connection failed ${error.message}`, {
124
+ tokenLength: token?.length,
125
+ roomIdLength: roomId?.length
126
+ }, error);
127
+
128
+ this.emit('error', error);
129
+ throw error;
130
+ }
131
+ }
132
+
133
+ disconnect(code = 1000, reason = 'Manual disconnect') {
134
+ this.logger.info('HighriseWebsocket.disconnect', 'Disconnecting...');
135
+
136
+ this.connectionManager.disconnect(code, reason);
137
+ this.cleanupManager.cleanup();
138
+
139
+ this._connected = false;
140
+ this.logger.success('HighriseWebsocket.disconnect', 'Disconnected successfully');
141
+ this.emit('disconnected', { code, reason, manual: true });
142
+ }
143
+
144
+ send(data) {
145
+ return this.connectionManager.send(data);
146
+ }
147
+
148
+ isConnected() {
149
+ return this.connectionManager.isConnected();
150
+ }
151
+
152
+ getMetrics() {
153
+ const cacheStats = this._movementCache.getStats();
154
+ const metrics = this.metrics.getSnapshot();
155
+
156
+ return {
157
+ ...metrics,
158
+ connected: this.isConnected(),
159
+ room: this._info.room.name || 'Unknown',
160
+ cache: {
161
+ users: cacheStats.totalUsers,
162
+ memory: this.metrics.formatBytes(cacheStats.memoryUsage),
163
+ active: cacheStats.activeUsers,
164
+ changes: cacheStats.changesProcessed
165
+ },
166
+ pendingReq: {
167
+ fireForget: this._fireAndForget.getPendingCount(),
168
+ reqRes: this._requestResponse.getPendingCount()
169
+ },
170
+ keepAlive: this.keepAliveManager.getStats(),
171
+ cleanup: this.cleanupManager.getStats()
172
+ };
173
+ }
174
+
175
+ configureSenders(config = {}) {
176
+ if (config.defaultTimeout !== undefined) {
177
+ this._requestResponse.setConfig({ defaultTimeout: config.defaultTimeout });
178
+ }
179
+
180
+ if (config.maxRetries !== undefined) {
181
+ this._requestResponse.setConfig({ maxRetries: config.maxRetries });
182
+ this._fireAndForget.setRetryConfig({ maxRetries: config.maxRetries });
183
+ }
184
+
185
+ if (config.retryDelay !== undefined) {
186
+ this._requestResponse.setConfig({ retryDelay: config.retryDelay });
187
+ this._fireAndForget.setRetryConfig({ retryDelay: config.retryDelay });
188
+ }
189
+
190
+ this.logger.info('HighriseWebsocket.configureSenders', 'Sender configuration updated', config);
191
+ }
192
+
193
+ getSenderStats() {
194
+ return {
195
+ fireAndForget: { pending: this._fireAndForget.getPendingCount() },
196
+ requestResponse: { pending: this._requestResponse.getPendingCount() }
197
+ };
198
+ }
199
+
200
+ _updateMetrics() {
201
+ const cacheStats = this._movementCache.getStats();
202
+ this.metrics.updateCacheStats(cacheStats);
203
+ }
204
+
205
+ get info() {
206
+ return { ...this._info };
207
+ }
208
+
209
+ get movementCache() {
210
+ return this._movementCache;
211
+ }
212
+
213
+ get fireAndForget() {
214
+ return this._fireAndForget;
215
+ }
216
+
217
+ get requestResponse() {
218
+ return this._requestResponse;
219
+ }
220
+
221
+ destroy() {
222
+ this.disconnect();
223
+ this.removeAllListeners();
224
+ this.cleanupManager.cleanup();
225
+ }
226
+ }
227
+
228
+ module.exports = HighriseWebsocket;