WebSocket.js

const ws = require('ws');
const EventEmitter = require('events').EventEmitter;

/**
 * Creates a new WebSocket client
 * @class WebSocket
 * @extends EventEmitter
 */
class WebSocket extends EventEmitter {
    /**
     * @param {object} options An object with options
     * @param {number} [options.heartbeatInterval] The amount of seconds between each heartbeat. Must be below or equal to 50.
     * @param {array<string>} options.tokens An array of bot tokens.
     * @param {boolean} [options.reconnect] Tells the websocket to reconnect after a disconnect.
     */
    constructor(options) {
        super();

        if (typeof options != 'object') throw new TypeError('options must be an object');
        if ('reconnect' in options && typeof options.reconnect !== 'boolean') throw new TypeError('reconnect must be a boolean when provided');
        if (!Array.isArray(options.tokens) || !options.tokens.every((token) => typeof token === 'string')) throw new TypeError('tokens must be an array of strings');
        if ('heartbeatInterval' in options && typeof options.heartbeatInterval !== 'number') throw new TypeError('heartbeatInterval must be a number when provided');
        if ('heartbeatInterval' in options && options.heartbeatInterval > 50) throw new TypeError('heartbeatInterval must be less than or equal to 50');

        this._options = {
            tokens: options.tokens,
            reconnect: options.reconnect || true,
            heartbeatInterval: options.heartbeatInterval || 30
        };
        this._heartbeat = null;

        this._socket = new ws('wss://gateway.discordbots.group');
        this._socket.on('open', this._onOpen.bind(this));
        this._socket.on('close', this._onClose.bind(this));
        this._socket.on('error', this._onError.bind(this));
        this._socket.on('message', this._onMessage.bind(this));

    }

    /**
     * Called when socket is open
     * @private
     * @extends WebSocket
     */
    _onOpen() {
        this.emit('connected');
        this._send(0, { tokens: this._options.tokens });
        this._heartbeat = setInterval(() => {
            if (this._socket.readyState === this._socket.OPEN) {
                this._sendHeartbeat();
            } else {
                clearInterval(this._heartbeat);
                this._heartbeat = null;
            }
        }, 1000 * this._options.heartbeatInterval);
    }

    /**
     * Called when socket sends message
     * @param data
     * @private
     */
    _onMessage(data) {
        try {
            data = JSON.parse(data);
            if (data.op === 2) {
                this.emit('heartbeatAck');
            } else if (data.op === 3) {
                this.emit('upvote', new BotUpvote(data.data));
            } else if (data.op === 4) {
                this.emit('pageView', new BotView(data.data));
            }
        } catch (e) {
            this.emit('error', e);
        }
    }

    /**
     * Called when socket errors
     * @param error
     * @private
     */
    _onError(...errors) {
        this.emit('error', ...errors);
    }

    /**
     * Called when socket is closed
     * @param code
     * @param message
     * @private
     */
    _onClose(code, message) {
        clearInterval(this._heartbeat);
        this._heartbeat = null;
        this.emit('disconnected', new CloseEvent(code, message));
        if (this._options.reconnect) {
            this.emit('reconnecting');
            delete this._socket;
            this._socket = new ws('wss://gateway.discordbots.group');
            this._socket.on('open', this._onOpen.bind(this));
            this._socket.on('close', this._onClose.bind(this));
            this._socket.on('error', this._onError.bind(this));
            this._socket.on('message', this._onMessage.bind(this));
        }
    }

    /**
     * Send a payload to the websocket
     * @param op
     * @param data
     * @private
     */
    _send(op, data) {
        this._socket.send(JSON.stringify({ op, t: Date.now(), data }));
    }

    /**
     * Send a heartbeat to the server
     * @private
     */
    _sendHeartbeat() {
        this._send(1, {});
    }
}

class CloseEvent {
    constructor(code, message) {
        this.code = code;
        this.message = message;
    }
}

class BotUpvote {
    constructor(data) {
        this.bot = data.bot;
        this.user = data.user;
    }
}

class BotView {
    constructor(data) {
        this.bot = data.bot;
    }
}

module.exports = WebSocket;