lavacord
Version:
A simple by design lavalink wrapper made for any discord library.
420 lines (417 loc) • 15.5 kB
JavaScript
'use strict';
var Rest_cjs = require('./Rest.cjs');
var cloudstorm = require('cloudstorm');
var index_cjs = require('../index.cjs');
var __defProp = Object.defineProperty;
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
var LavalinkNode = class {
/**
* Creates a new LavalinkNode instance.
*
* @summary Initializes a new Lavalink node connection
* @param manager - The {@link Manager} instance that controls this node
* @param options - Configuration options for this Lavalink node as defined in {@link LavalinkNodeOptions}
*/
constructor(manager, options) {
this.manager = manager;
this.id = options.id;
if (options.host) Object.defineProperty(this, "host", { value: options.host });
if (options.port) Object.defineProperty(this, "port", { value: options.port });
if (options.password) Object.defineProperty(this, "password", { value: options.password });
if (options.secure !== void 0) Object.defineProperty(this, "secure", { value: options.secure });
if (options.reconnectInterval) this.reconnectInterval = options.reconnectInterval;
if (options.sessionId) this.sessionId = options.sessionId;
if (options.resuming !== void 0) this.resuming = options.resuming;
if (options.resumeTimeout) this.resumeTimeout = options.resumeTimeout;
if (options.state) this.state = options.state;
}
static {
__name(this, "LavalinkNode");
}
/**
* The identifier for this Lavalink node. Used to distinguish between multiple nodes.
*
* @summary Unique identifier for the node
* @remarks
* This is a required property that must be unique across all nodes in your application.
* It's used for identifying this node in logs and when selecting nodes for new players.
*/
id;
/**
* The hostname or IP address of the Lavalink server.
*
* @summary Server hostname or IP address
* @remarks
* This can be a domain name, IPv4, or IPv6 address pointing to your Lavalink server.
*/
host = "localhost";
/**
* The port number that the Lavalink server is listening on.
*
* @summary Server port number
* @remarks
* This should match the port configured in your Lavalink server's application.yml.
*/
port = 2333;
/**
* The time in milliseconds between reconnection attempts if the connection fails.
*
* @summary Reconnection delay in milliseconds
* @remarks
* Lower values will attempt reconnections more quickly, but might
* cause excessive connection attempts during prolonged server outages.
*/
reconnectInterval = 1e4;
/**
* The password used for authorization with the Lavalink server.
*
* @summary Authorization password for the Lavalink server
* @remarks
* This password must match the one configured in your Lavalink server's application.yml.
* It's used in the Authorization header when establishing the WebSocket connection.
*/
password = "youshallnotpass";
/**
* Whether to use secure connections (HTTPS/WSS) instead of HTTP/WS.
*
* @summary Secure connection flag for SSL/TLS
* @remarks
* When true, WebSocket connections will use WSS and REST requests will use HTTPS.
* This is required when connecting to Lavalink servers behind SSL/TLS.
*/
secure = false;
/**
* The WebSocket instance used for communication with the Lavalink server.
*
* @summary Active WebSocket connection to the Lavalink server
* @remarks
* When not connected to Lavalink, this property will be null.
* You can check the {@link connected} property to determine connection status.
*/
ws = null;
/**
* The statistics received from the Lavalink server.
*
* @summary Server resource usage and player statistics
* @remarks
* Contains information about system resource usage, player counts, and audio frame statistics.
* This is updated whenever the Lavalink server sends a stats update (typically every minute).
* You can use these stats to implement node selection strategies in your application.
*/
stats = {
players: 0,
playingPlayers: 0,
uptime: 0,
memory: {
used: 0,
free: 0,
allocated: 0,
reservable: 0
},
cpu: {
cores: 0,
systemLoad: 0,
lavalinkLoad: 0
},
frameStats: {
sent: 0,
nulled: 0,
deficit: 0
}
};
/**
* Whether this node should attempt to resume the session when reconnecting.
*
* @summary Session resumption flag
* @remarks
* When true, the node will try to resume the previous session after a disconnect,
* preserving player states and connections. This helps maintain playback during
* brief disconnections or node restarts.
*/
resuming = false;
/**
* The timeout in seconds after which a disconnected session can no longer be resumed.
*
* @summary Maximum session resumption timeout in seconds
* @remarks
* This value is sent to the Lavalink server when configuring session resuming.
* After this many seconds of disconnection, the session will be fully closed
* and cannot be resumed.
*/
resumeTimeout = 60;
/**
* Custom data that can be attached to the node instance.
*
* @summary Custom application data storage
* @remarks
* Not used internally by Lavacord, available for application-specific needs.
* You can use this property to store any data relevant to your implementation,
* such as region information, feature flags, or custom metrics.
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
state;
/**
* The unique session identifier provided by Lavalink on successful connection.
*
* @summary Lavalink session identifier
* @remarks
* This ID is used for resuming sessions and in certain REST API calls.
* It's automatically assigned when connecting to the Lavalink server.
*/
sessionId;
/**
* The version of the Lavalink protocol this node is using.
*
* @summary Lavalink protocol version
* @remarks
* This is set automatically when connecting to the Lavalink server.
* It indicates which version of the Lavalink protocol this node supports.
* The default value is "4", which corresponds to the latest stable version.
*
* @defaultValue "4"
*/
version = "4";
/**
* Timeout reference used for the reconnection mechanism.
* This holds the NodeJS.Timeout instance used to schedule reconnection attempts.
*/
_reconnect;
/**
* Current reconnection attempt count for exponential backoff.
*/
_reconnectAttempts = 0;
/**
* Tracks whether the session has been updated with the Lavalink server.
* Used internally to avoid redundant session update requests.
*/
_sessionUpdated = false;
/**
* Establishes a connection to the Lavalink server.
*
* This method creates a new WebSocket connection to the configured Lavalink server.
* If the node is already connected, it will close the existing connection first.
* The method sets up event listeners for the WebSocket to handle messages, errors,
* and connection state changes.
*
* Note: This method is primarily used internally by the {@link Manager} class.
* Users typically should not call this method directly as the Manager handles
* node connections automatically.
*
* @returns A promise that resolves when connected or rejects if connection fails
* @throws {Error} If the connection fails due to network issues, authentication problems, or other errors
*/
async connect() {
this.version = await Rest_cjs.Rest.version(this).then((str) => str.split(".")[0]).catch(() => "4");
const headers = {
Authorization: this.password,
"User-Id": this.manager.userId,
"Client-Name": `Lavacord/${index_cjs.VERSION}`
};
if (this.sessionId && this.resuming) headers["Session-Id"] = this.sessionId;
if (!this.ws) {
this.ws = new cloudstorm.BetterWs(this.socketURL, { headers, encoding: "json", connectThrottle: 0 });
this.ws.on("ws_receive", (data) => this.onMessage(data)).on("error", (e) => this.onError(e)).on("ws_close", (code, reason) => this.onClose(code, reason));
}
if (["connecting", "upgrading"].includes(this.ws.sm.currentStateName)) {
await cloudstorm.eventSwitch(this.ws, { ws_open: /* @__PURE__ */ __name(() => void 0, "ws_open") });
return this.ws;
}
if (["connected", "half_close"].includes(this.ws.sm.currentStateName)) await this.ws.close(1e3, "reconnecting");
this.ws.connect();
await cloudstorm.eventSwitch(this.ws, {
ws_open: /* @__PURE__ */ __name(() => {
this.onOpen();
}, "ws_open"),
error: /* @__PURE__ */ __name((e) => {
throw e;
}, "error"),
ws_close: /* @__PURE__ */ __name((code, reason) => {
throw new Error(`WebSocket closed during connection: ${code} ${reason.toString()}`);
}, "ws_close")
});
return this.ws;
}
/**
* Gracefully closes the connection to the Lavalink server.
*
* This method closes the WebSocket connection with a normal closure code (1000)
* and a reason of "destroy", indicating an intentional disconnection rather
* than an error condition.
*
* Note: This method is primarily used internally by the {@link Manager} class.
* Users typically should not call this method directly as the Manager handles
* node disconnections automatically.
*
* @returns void
*/
destroy() {
if (!this.connected) return;
this.ws.close(1e3, "destroy");
}
/**
* Indicates whether this node is currently connected to the Lavalink server.
*
* @summary Connection status check
* @remarks
* Checks if the {@link ws} instance exists and if its ready state is 1.
* This property is useful for verifying connection status before attempting operations
* or implementing node selection strategies.
*
* @returns `true` if connected, `false` otherwise
*/
get connected() {
if (!this.ws) return false;
return this.ws.sm.currentStateName === "connected";
}
/**
* Gets the WebSocket URL for connecting to the Lavalink server.
*
* @summary WebSocket connection URL
* @remarks
* Returns either a secure (wss://) or insecure (ws://) WebSocket URL
* based on the {@link secure} property configuration.
*
* @returns The complete WebSocket URL including protocol, host, port, and path
*/
get socketURL() {
const protocol = this.secure ? "wss" : "ws";
return `${protocol}://${this.host}:${this.port}/v${this.version}/websocket`;
}
/**
* Gets the REST API base URL for the Lavalink server.
*
* @summary REST API base URL
* @remarks
* Returns either a secure (https://) or insecure (http://) REST URL
* based on the {@link secure} property configuration.
*
* @returns The complete REST API base URL including protocol, host, port, and path
*/
get restURL() {
const protocol = this.secure ? "https" : "http";
return `${protocol}://${this.host}:${this.port}`;
}
/**
* Handles the WebSocket 'open' event when a connection is established.
*/
onOpen() {
if (this._reconnect) clearTimeout(this._reconnect);
this._reconnectAttempts = 0;
this.manager.emit("ready", this);
}
/**
* Processes incoming WebSocket messages from the Lavalink server.
* @param msg - The raw data received from the WebSocket
*/
onMessage(msg) {
switch (msg.op) {
case "ready":
if (msg.sessionId) this.sessionId = msg.sessionId;
if (!this._sessionUpdated) {
this._sessionUpdated = true;
Rest_cjs.Rest.updateSession(this).catch((e) => this.manager.emit("error", e, this));
}
break;
case "stats": {
const { op, ...stats } = msg;
this.stats = stats;
break;
}
case "event":
this._handleEvent(msg);
break;
case "playerUpdate": {
const player = this.manager.players.get(msg.guildId);
if (!player) break;
player.state = msg.state;
if (player.listenerCount("state")) player.emit("state", msg.state);
if (this.manager.listenerCount("playerState")) this.manager.emit("playerState", player, msg.state);
break;
}
}
this.manager.emit("raw", msg, this);
}
/**
* Handles WebSocket errors.
* @param error - The error received from the WebSocket
*/
onError(error) {
if (!error) return;
this.manager.emit("error", error, this);
this.destroy();
}
/**
* Handles WebSocket closure.
*
* @param code - The WebSocket close code (see Lavalink API for code meanings)
* @param reason - The reason why the WebSocket was closed
*/
onClose(code, reason) {
this._sessionUpdated = false;
this.manager.emit("disconnect", code, reason, this);
switch (code) {
case 1e3:
if (reason.toString() === "destroy") return clearTimeout(this._reconnect);
break;
case 4001:
case 4002:
case 4003:
case 4004:
case 4005:
this.manager.emit("error", new Error(`Lavalink authentication error: ${code} ${reason}`), this);
return;
}
this.reconnect();
}
/**
* Initiates a reconnection attempt after a delay with exponential backoff.
*/
reconnect() {
this._reconnectAttempts++;
const delay = Math.min(this.reconnectInterval * Math.pow(2, this._reconnectAttempts - 1), 6e4);
this._reconnect = setTimeout(async () => {
this.manager.emit("reconnecting", this);
await this.connect().catch(() => void 0);
}, delay);
}
_handleEvent(data) {
const player = this.manager.players.get(data.guildId);
if (!player) return;
switch (data.type) {
case "TrackStartEvent":
player.track = data.track;
player.timestamp = Date.now();
if (player.listenerCount("trackStart")) player.emit("trackStart", data);
if (this.manager.listenerCount("playerTrackStart")) this.manager.emit("playerTrackStart", player, data);
break;
case "TrackEndEvent":
if (data.reason !== "replaced") {
player.track = null;
player.timestamp = null;
}
if (player.listenerCount("trackEnd")) player.emit("trackEnd", data);
if (this.manager.listenerCount("playerTrackEnd")) this.manager.emit("playerTrackEnd", player, data);
break;
case "TrackExceptionEvent":
if (player.listenerCount("trackException")) player.emit("trackException", data);
if (this.manager.listenerCount("playerTrackException")) this.manager.emit("playerTrackException", player, data);
break;
case "TrackStuckEvent":
if (player.listenerCount("trackStuck")) player.emit("trackStuck", data);
if (this.manager.listenerCount("playerTrackStuck")) this.manager.emit("playerTrackStuck", player, data);
break;
case "WebSocketClosedEvent":
player.track = null;
player.timestamp = null;
if (player.listenerCount("webSocketClosed")) player.emit("webSocketClosed", data);
if (this.manager.listenerCount("playerWebSocketClosed")) this.manager.emit("playerWebSocketClosed", player, data);
break;
default:
this.manager.emit("warn", `Unexpected event type: ${data.type}`);
break;
}
}
};
exports.LavalinkNode = LavalinkNode;
//# sourceMappingURL=LavalinkNode.cjs.map
//# sourceMappingURL=LavalinkNode.cjs.map