jspteroapi
Version:
A pterodactyl v1 api using undici
159 lines (158 loc) • 5.75 kB
JavaScript
"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;