mirror of
https://github.com/vyme-fr/MercuryCloud_Dashboard.git
synced 2026-01-19 00:57:23 +01:00
165 lines
5.4 KiB
JavaScript
165 lines
5.4 KiB
JavaScript
const { EventEmitter } = require('events');
|
|
const fetch = require('node-fetch');
|
|
const caseConv = require('../../util/caseConv');
|
|
|
|
class NodeStatus extends EventEmitter {
|
|
headers = {
|
|
'Content-Type': 'application/json',
|
|
'Accept': 'application/json',
|
|
'User-Agent': 'NodeStatus PteroJS v1.0.3'
|
|
}
|
|
#interval = null;
|
|
#connected = new Set();
|
|
|
|
/**
|
|
* @param {StatusOptions} options
|
|
*/
|
|
constructor(options) {
|
|
super();
|
|
|
|
Object.assign(this, options);
|
|
if (!/https?\:\/\/(?:localhost\:\d{4}|[\w\.\-]{3,256})/gi.test(this.domain))
|
|
throw new SyntaxError(
|
|
"Domain URL must start with 'http://' or 'https://' and "+
|
|
'must be bound to a port if using localhost.'
|
|
);
|
|
|
|
this.headers['Authorization'] = 'Bearer '+ options.auth;
|
|
this.nextInterval ||= 5;
|
|
this.retryLimit ||= 0;
|
|
|
|
/** @type {null | (id: number) => void} */
|
|
this.onConnect = null;
|
|
|
|
/** @type {null | (d: object) => void} */
|
|
this.onInterval = null;
|
|
|
|
/** @type {null | (id: number) => void} */
|
|
this.onDisconnect = null;
|
|
|
|
this.ping = -1;
|
|
this.current = 0;
|
|
this.readyAt = 0;
|
|
|
|
if (this.nodes.some(i => typeof i !== 'number'))
|
|
throw new TypeError('[NS] Node IDs must be numbers only.');
|
|
|
|
if (this.callInterval < 10_000 || this.callInterval > 43_200_000)
|
|
throw new RangeError('[NS] Call interval must be between 10 seconds and 12 hours.');
|
|
|
|
if (this.nextInterval >= this.callInterval)
|
|
throw new RangeError('[NS] Next interval must be less than the call interval.');
|
|
}
|
|
|
|
#debug(message) { this.emit('debug', '[NS] '+ message) }
|
|
|
|
async connect() {
|
|
if (this.readyAt) throw new Error('Process already running.');
|
|
this.#debug('Starting connection to API');
|
|
await this.#ping();
|
|
await this.#handleNext();
|
|
|
|
this.#interval = setInterval(() => this.#handleNext(), this.callInterval).unref();
|
|
this.readyAt = Date.now();
|
|
process.on('SIGINT', _ => this.close());
|
|
process.on('SIGTERM', _ => this.close());
|
|
}
|
|
|
|
async #ping() {
|
|
const start = Date.now();
|
|
const res = await fetch(`${this.domain}/api/application`, {
|
|
method: 'GET', headers: this.headers
|
|
});
|
|
|
|
if (res.status === 401)
|
|
return this.close(
|
|
'[NS:401] Invalid API credentials. Contact your panel administrator.',
|
|
true
|
|
);
|
|
|
|
if (res.status === 403) return this.close('[NS:403] Missing access.', true);
|
|
|
|
this.ping = Date.now() - start;
|
|
const data = await res.json().catch(()=>{});
|
|
if (data?.errors?.length) return;
|
|
return this.close('[NS:404] Application API is unavailable.', true);
|
|
}
|
|
|
|
async #handleNext() {
|
|
for (let i=0; i<this.nodes.length; i++) {
|
|
await this.#request(this.nodes[i]);
|
|
if (this.nodes[i+1]) {
|
|
await new Promise(res => setTimeout(res, this.nextInterval).unref());
|
|
}
|
|
}
|
|
}
|
|
|
|
async #request(id) {
|
|
this.#debug(`Fetching: /api/application/nodes/${id}`);
|
|
const res = await fetch(
|
|
`${this.domain}/api/application/nodes/${id}`, {
|
|
method: 'GET', headers: this.headers
|
|
});
|
|
|
|
if (!res.ok) {
|
|
if (res.status === 401)
|
|
return this.close(
|
|
'[NS:401] Invalid API credentials. Contact your panel administrator.',
|
|
true
|
|
);
|
|
|
|
if (res.status === 403) return this.close('[NS:403] Missing access.', true);
|
|
if (res.status === 404) {
|
|
if (this.#connected.has(id)) {
|
|
this.emit('disconnect', id);
|
|
if (this.onDisconnect) this.onDisconnect(id);
|
|
this.#connected.delete(id);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (this.current > this.retryLimit)
|
|
return this.close('[NS] Maximum retry limit exceeded.');
|
|
|
|
this.current++;
|
|
this.#debug('Attempting retry fetch');
|
|
this.#request(id);
|
|
return;
|
|
}
|
|
|
|
let { attributes } = await res.json();
|
|
attributes = caseConv.camelCase(attributes);
|
|
if (!this.#connected.has(id)) {
|
|
this.#connected.add(id);
|
|
this.emit('connect', id);
|
|
if (this.onConnect !== null) this.onConnect(id);
|
|
}
|
|
|
|
this.emit('interval', attributes);
|
|
if (this.onInterval !== null) this.onInterval(attributes);
|
|
}
|
|
|
|
close(message = 'None', error = false) {
|
|
if (!this.readyAt) return;
|
|
|
|
this.#debug('Closing connection');
|
|
if (this.#interval) clearInterval(this.#interval);
|
|
|
|
this.removeAllListeners();
|
|
this.#connected.clear();
|
|
if (error && message) throw new Error(message);
|
|
}
|
|
}
|
|
|
|
module.exports = NodeStatus;
|
|
|
|
/**
|
|
* @typedef {object} StatusOptions
|
|
* @property {string} domain The domain for the API.
|
|
* @property {string} auth The API key authorization.
|
|
* @property {number[]} nodes An array of node IDs to listen for.
|
|
* @property {number} callInterval The interval to wait between API calls (between 10-6000 seconds).
|
|
* @property {?number} nextInterval The interval to wait between processing checks. Must be less than the callInterval.
|
|
* @property {?number} retryLimit The amount of times to retry fetching the API.
|
|
*/
|