"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.DisconnectReasons = exports.Events = exports.Messages = exports.Socket = void 0;
const events_1 = __importDefault(require("events"));
const ws_1 = require("ws");
const Utilities_js_1 = require("./Utilities.js");
/**
* Object for sending and receiving messages the raw WebSocket.
* It is not recommended to use this object in your code, as it is used internally.
*
* Accessable from a `Server` object via `Server.socket`.
*
* @class
* @property {string} host - The host WebSocket address. Example: `ws://localhost:5050`
* @property {bool} connected - Will be true if the server is connected to the WebSocket, and false otherwise.
* @property {object} server - The `Server` object this belongs to.
* @property {object} ws - The WebSocket class provided by the `ws` module. Used for actually sending and receiving messages.
* @property {array} acknowledgements - List of acknowledgements received from the server. Used to recognize when the server successfully processes a message.
*/
class Socket extends events_1.default {
/**
* @constructor
* @param {Server} server - The `Server` object this belongs to.
* @param {string} host - Websocket address. Example: `ws://localhost:5050`
*/
constructor(server, host) {
super();
this.host = host;
this.connected = false;
this.server = server;
this.ws = new ws_1.WebSocket(host);
this.lastMessage = Date.now();
this.authhash = "";
this.acknowledgements = [];
this.ws.on("error", (err) => {
/**
* Used internally for when the WebSocket experiences an error.
*
* @event Socket#error
* @param {Error} error - The error that occured.
*/
this.emit("error", err);
});
this.ws.on("open", () => {
this.connected = true;
/**
* Fired when the connection to the server has been established.
*
* @event Socket#ready
*/
this.emit("ready", true);
});
this.ws.on("message", (data) => {
/**
* Fired when a WebSocket packet is received.
*
* @event Socket#raw
* @param {Buffer} data - Raw data sent by the server.
*/
this.emit("raw", data);
var json;
try {
json = JSON.parse(data);
}
catch (err) {
throw err;
}
/**
* Used internally for when a raw event is received from the server.
* It isn't recommended to use this event in your code.
*
* @event Socket#event
* @param {object} json - The message data sent by the server.
*/
this.emit(json.type, json);
this.lastMessage = Date.now();
switch (json.type) {
case "Acknowledgement":
if (!this.acknowledgements.includes(json.message)) {
this.acknowledgements.push(json.message);
}
break;
case "Ping":
this.send("Pong", Date.now());
break;
case "Error":
switch (json.message) {
case "TF2ItemsMissing":
/**
* @throws Will throw an error if you try to use a function that requires TF2Items, and it isn't installed.
*/
throw Error("The TF2Items plugin is missing from the server!");
break;
}
break;
case "Disconnect":
this.server.disconnect();
switch (json.message) {
case "Unauthorized":
/**
* @throws Will throw an error if you are unauthorized to the server.
*/
throw Error("Unauthorized!");
break;
case "TimedOut":
/**
* @throws Will throw an error if you fail to send a message to the server for 5 seconds.
*/
throw Error("Timed out!");
break;
}
break;
}
setInterval(() => {
if (Date.now() - this.lastMessage > 5000) {
this.server.disconnect();
throw Error("Timed out!");
}
}, 1000);
});
}
/**
* Sends a raw message to the WebSocket
* @function
* @param {string} type
* @param {object} message
*/
send(type, message) {
return __awaiter(this, void 0, void 0, function* () {
return new Promise((resolve, reject) => {
var _a;
var ack = (0, Utilities_js_1.UID)();
this.ws.send(JSON.stringify({
type: type,
message: message,
ack: ack,
auth: (_a = this.server.options.get("auth")) !== null && _a !== void 0 ? _a : "none",
timestamp: Date.now()
}));
/* wait until an acknowledgement is received */
let timeout = setInterval(() => {
if (this.acknowledgements.includes(ack)) {
clearInterval(timeout);
resolve(true);
}
}, 1);
});
});
}
/**
* Disconnects from the WebSocket.
* @function
*/
disconnect() {
return __awaiter(this, void 0, void 0, function* () {
this.ws.terminate();
this.connected = false;
this.server.connected = false;
/**
* Fired when the WebSocket disconnects.
*
* @event Socket#disconnect
*/
this.emit("disconnect", true);
});
}
}
exports.Socket = Socket;
/**
* An enum describing the messages that can be sent to a SourceMod.JS server.
*
* @readonly
* @enum {string} Messages
*/
exports.Messages = {
PrintToServer: "PrintToServer",
ServerCommand: "ServerCommand",
FetchPlayer: "FetchPlayer",
FetchPlayers: "FetchPlayers",
KickPlayer: "KickPlayer",
PlayerChat: "PlayerChat",
PlayerChatAll: "PlayerChatAll",
Pong: "Pong",
FetchServer: "FetchServer",
PlayerHint: "PlayerHint",
PlayerHintAll: "PlayerHintAll",
PlayerCenterHint: "PlayerCenterHint",
PlayerCenterHintAll: "PlayerCenterHintAll",
PlaySound: "PlaySound",
PlaySoundAll: "PlaySoundAll",
SetMap: "SetMap",
SetNextMap: "SetNextMap",
TeleportPlayer: "TeleportPlayer",
SlapPlayer: "SlapPlayer",
SetPlayerRendering: "SetPlayerRendering",
TF2_RegeneratePlayer: "TF2_RegeneratePlayer",
TF2_GiveWeapon: "TF2_GiveWeapon",
TF2_ApplyCondition: "TF2_ApplyCondition",
};
/**
* An enum describing the events that can be received from a SourceMod.JS server.
*
* @readonly
* @enum {string} Events
*/
exports.Events = {
PlayerChat: "PlayerChat",
PlayerUpdate: "PlayerUpdate",
PlayerConnect: "PlayerConnect",
PlayerDisconnect: "PlayerDisconnect",
Disconnect: "Disconnect",
Ping: "Ping",
ServerUpdate: "ServerUpdate",
ConVarChanged: "ConVarChanged",
Error: "Error"
};
/**
* An enum describing the reasons for being disconnected from a SourceMod.JS server.
*
* @readonly
* @enum {string} DisconnectReasons
*/
exports.DisconnectReasons = {
Disconnect: "Disconnect",
Unauthorized: "Unauthorized",
TimedOut: "TimedOut",
UnknownError: "UnknownError"
};