UNPKG

jspteroapi

Version:

A pterodactyl v1 api using undici

159 lines (158 loc) 5.75 kB
"use strict"; /** @module ClientWebsocket */ Object.defineProperty(exports, "__esModule", { value: true }); exports.WebsocketClient = void 0; const node_events_1 = require("node:events"); const Error_1 = require("../modules/Error"); const Socket_1 = require("../modules/Socket"); const reconnectErrors = [ 'jwt: exp claim is invalid', 'jwt: created too far in past (denylist)' ]; class WebsocketClient extends node_events_1.EventEmitter { getToken; constructor(errorHandler, auth, getToken) { super(); this.getToken = getToken; // Allow 100 listeners for this instance this.setMaxListeners(100); this.updateToken = ((getToken, socket) => { if (this.isUpdating) { return; } this.isUpdating = true; try { getToken().then((data) => socket.setToken(data.token)); } catch (e) { if (e instanceof Error_1.JSPteroAPIError) { if (e.ERRORS[0] === 'This server is currently suspended and the functionality requested is unavailable.') { return this.close(409, 'Suspended'); } return errorHandler(e); } throw e; } }).bind(undefined, this.getToken, this); this.setToken(auth.token).connect(auth.socket); // Set listerners this.on('daemon error', (message) => { console.error(message); }); this.on('token expiring', () => this.updateToken()); this.on('token expired', () => this.updateToken()); this.on('jwt error', (error) => { if (reconnectErrors.find((v) => error.toLowerCase().indexOf(v) >= 0)) { this.updateToken(); } else { throw new Error(error); } }); this.on('transfer status', (status) => { if (status === 'starting' || status === 'success') { return; } // This code forces a reconnection to the websocket which will connect us to the target node instead of the source node // in order to be able to receive transfer logs from the target node. this.close(); this.open(); }); } isUpdating = false; // Timer instance for this socket. timer; // The backoff for the timer, in milliseconds. backoff = 5000; // The socket instance being tracked. socket = null; // The URL being connected to for the socket. url = null; // The authentication token passed along with every request to the Daemon. // By default this token expires every 15 minutes and must therefore be // refreshed at a pretty continuous interval. The socket server will respond // with "token expiring" and "token expired" events when approaching 3 minutes // and 0 minutes to expiry. token = ''; // Connects to the websocket instance and sets the token for the initial request. connect(url) { this.url = url; this.timer && clearTimeout(this.timer); // Close socket if needed if (this.socket?.ws.OPEN) { this.close(); } this.socket = new Socket_1.Socket(this.url, { onmessage: (e) => { try { const { event, args } = JSON.parse(e.data.toString()); args ? this.emit(event, ...args) : this.emit(event); } catch (ex) { console.warn('Failed to parse incoming websocket message.', ex); } }, onopen: () => { // Clear the timers, we managed to connect just fine. this.timer && clearTimeout(this.timer); this.backoff = 5000; this.emit('SOCKET_OPEN'); this.authenticate(); }, onreconnect: () => { this.emit('SOCKET_RECONNECT'); this.authenticate(); }, onclose: () => this.emit('SOCKET_CLOSE'), onerror: (event) => { if (event.message === 'WebSocket was closed before the connection was established') return; throw new Error(event.message); }, onmaximum: () => { return; } }); this.timer = setTimeout(() => { this.backoff = this.backoff + 2500 >= 20000 ? 20000 : this.backoff + 2500; this.socket && this.socket.close(undefined, 'timeout'); clearTimeout(this.timer); // Re-attempt connecting to the socket. this.connect(url); }, this.backoff); return this; } updateToken; // Sets the authentication token to use when sending commands back and forth // between the websocket instance. setToken(token, isUpdate = false) { this.token = token; if (isUpdate) { this.authenticate(); } return this; } authenticate() { if (this.url && this.token) { this.send('auth', this.token); } } close(code, reason) { this.url = null; this.token = ''; this.socket && this.socket.close(code, reason); } open() { this.socket && this.socket.open(); } send(event, payload) { this.socket && this.socket.send(JSON.stringify({ event, args: Array.isArray(payload) ? payload : [payload] })); } } exports.WebsocketClient = WebsocketClient;