UNPKG

node-insim

Version:

An InSim library for NodeJS with TypeScript support

342 lines (341 loc) 15.2 kB
"use strict"; var __extends = (this && this.__extends) || (function () { var extendStatics = function (d, b) { extendStatics = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; return extendStatics(d, b); }; return function (d, b) { if (typeof b !== "function" && b !== null) throw new TypeError("Class extends value " + String(b) + " is not a constructor or null"); extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; })(); 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 __generator = (this && this.__generator) || function (thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype); return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while (g && (g = 0, op[0] && (_ = 0)), _) try { if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; case 5: _.label++; y = op[1]; op = [0]; continue; case 7: op = _.ops.pop(); _.trys.pop(); continue; default: if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } if (t[2]) _.ops.pop(); _.trys.pop(); continue; } op = body.call(thisArg, _); } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.InSim = void 0; var crypto_1 = __importDefault(require("crypto")); var lodash_defaults_1 = __importDefault(require("lodash.defaults")); var tiny_typed_emitter_1 = require("tiny-typed-emitter"); var unicode_to_lfs_1 = __importDefault(require("unicode-to-lfs")); var errors_1 = require("./errors"); var lfspack_1 = require("./lfspack"); var log_1 = require("./log"); var packets_1 = require("./packets"); var protocols_1 = require("./protocols"); var log = log_1.log.extend('insim'); var InSim = /** @class */ (function (_super) { __extends(InSim, _super); function InSim(id) { var _this = _super.call(this) || this; _this._options = defaultInSimOptions; /** The main connection to InSim (TCP or UDP) */ _this.connection = null; _this.sizeMultiplier = 4; /** * Connect to a server via InSim. */ _this.connect = function (options) { if (_this.connection !== null) { log('Cannot connect - already connected'); return; } _this._connect(options); }; /** * Connect to InSim Relay. * * After you are connected you can request a host list, so you can see * which hosts you can connect to. * Then you can send a packet to the Relay to select a host. After that * the Relay will send you all insim data from that host. * * Some hosts require a spectator password in order to be selectable. * * You do not need to specify a spectator password if you use a valid administrator password. * * If you connect with an administrator password, you can send just about every * regular InSim packet there is available in LFS, just like as if you were connected * to the host directly. * * Regular insim packets that a relay client can send to host: * * For anyone * TINY_VER * TINY_PING * TINY_SCP * TINY_SST * TINY_GTH * TINY_ISM * TINY_NCN * TINY_NPL * TINY_RES * TINY_REO * TINY_RST * TINY_AXI * * Admin only * TINY_VTC * ISP_MST * ISP_MSX * ISP_MSL * ISP_MTC * ISP_SCH * ISP_BFN * ISP_BTN * * The relay will also accept, but not forward * TINY_NONE // for relay-connection maintenance */ _this.connectRelay = function () { _this._connect({ Host: 'isrelay.lfs.net', Port: 47474, Protocol: 'TCP', }, true); }; _this._connect = function (options, isRelay) { if (isRelay === void 0) { isRelay = false; } _this._options = (0, lodash_defaults_1.default)(options, defaultInSimOptions); log("Connecting to ".concat(_this._options.Host, ":").concat(_this._options.Port, " using ").concat(_this._options.Protocol, "...")); _this.sizeMultiplier = isRelay ? 1 : 4; _this.connection = _this._options.Protocol === 'TCP' ? new protocols_1.TCP(_this._options.Host, _this._options.Port, _this.sizeMultiplier) : new protocols_1.UDP({ host: _this._options.Host, port: _this._options.Port, packetSizeMultiplier: _this.sizeMultiplier, socketInitialisationMode: 'connect', }); _this.connection.connect(); _this.connection.on('connect', function () { if (!isRelay) { _this.send(new packets_1.IS_ISI({ Flags: _this._options.Flags, Prefix: _this._options.Prefix, Admin: _this._options.Admin, UDPPort: _this._options.UDPPort, ReqI: _this._options.ReqI, Interval: _this._options.Interval, IName: _this._options.IName, InSimVer: InSim.INSIM_VERSION, })); } _this.emit('connect', _this); }); _this.connection.on('disconnect', function () { _this.emit('disconnect', _this); }); _this.connection.on('error', function (error) { throw new errors_1.InSimError("".concat(_this._options.Protocol, " connection error: ").concat(error.message)); }); _this.connection.on('data', function (data) { return _this.handlePacket(data); }); }; _this.disconnect = function () { if (_this.connection !== null) { log('Disconnecting...'); _this.connection.disconnect(); } }; _this.send = function (packet) { if (_this.connection === null) { log('Cannot send a packet - not connected'); return; } packet.SIZE_MULTIPLIER = _this.sizeMultiplier; log('Send packet:', packets_1.PacketType[packet.Type], packet); var data = packet.pack(); _this.connection.send(data); }; /** * Send a message or command to LFS * * If the message starts with a slash (`/`), it will be treated as a command. * Otherwise, it will be treated as a message. * * The maximum length of the message is {@link MSX_MSG_MAX_LENGTH} characters. */ _this.sendMessage = function (message) { log('Send message:', message); if (message.startsWith(InSim.COMMAND_PREFIX)) { return _this.send(new packets_1.IS_MST({ Msg: message, })); } var encodedMessageLength = (0, unicode_to_lfs_1.default)(message).length; if (encodedMessageLength >= packets_1.MST_MSG_MAX_LENGTH) { return _this.send(new packets_1.IS_MSX({ Msg: message, })); } return _this.send(new packets_1.IS_MST({ Msg: message, })); }; /** * Send a message which will appear on the local computer only. * * The maximum length of the message is {@link MSL_MSG_MAX_LENGTH} characters. */ _this.sendLocalMessage = function (message, sound) { if (sound === void 0) { sound = packets_1.MessageSound.SND_SILENT; } log('Send local message:', message); return _this.send(new packets_1.IS_MSL({ Msg: message, Sound: sound, })); }; /** Send a message to a specific connection */ _this.sendMessageToConnection = function (ucid, message, sound) { if (sound === void 0) { sound = packets_1.MessageSound.SND_SILENT; } log('Send message to connection:', ucid, message); _this.send(new packets_1.IS_MTC({ UCID: ucid, Text: message, Sound: sound, })); }; /** Send a message to a specific player */ _this.sendMessageToPlayer = function (plid, message, sound) { if (sound === void 0) { sound = packets_1.MessageSound.SND_SILENT; } log('Send message to player:', plid, message); _this.send(new packets_1.IS_MTC({ PLID: plid, Text: message, Sound: sound, })); }; _this.sendAwait = function (packet, packetTypeToAwait, filterPacketData) { if (filterPacketData === void 0) { filterPacketData = function () { return true; }; } return new Promise(function (resolve, reject) { if (_this.connection === null) { log('Cannot send a packet with await - not connected'); reject(); return; } _this.send(packet); log('Await packet:', packets_1.PacketType[packetTypeToAwait]); var packetListener = function (receivedPacket) { if (receivedPacket.ReqI === packet.ReqI && filterPacketData(receivedPacket)) { resolve(receivedPacket); } }; _this.once(packetTypeToAwait, packetListener); _this.on('disconnect', function () { _this.removeListener(packetTypeToAwait, packetListener); reject(); }); }); }; _this.handlePacket = function (data) { return __awaiter(_this, void 0, void 0, function () { var header, packetType, packetTypeString, PacketClass, packetInstance; return __generator(this, function (_a) { header = (0, lfspack_1.unpack)('<BB', data.buffer); if (!header) { log("Incomplete packet header received: ".concat(data.join())); return [2 /*return*/]; } packetType = header[1]; packetTypeString = packets_1.PacketType[packetType]; if (packetTypeString === undefined) { log("Unknown packet type received: ".concat(packetType)); return [2 /*return*/]; } PacketClass = packets_1.packetTypeToClass[packetType]; if (PacketClass === undefined) { log("Packet handler not found for ".concat(packetTypeString)); return [2 /*return*/]; } packetInstance = new PacketClass(); packetInstance.SIZE_MULTIPLIER = this.sizeMultiplier; this.emit(packetType, packetInstance.unpack(data), this); return [2 /*return*/]; }); }); }; _this.handleKeepAlive = function (packet) { if (packet.SubT === packets_1.TinyType.TINY_NONE) { _this.send(new packets_1.IS_TINY({ SubT: packets_1.TinyType.TINY_NONE, })); } }; _this.id = id !== null && id !== void 0 ? id : crypto_1.default.randomUUID(); _this.on('connect', function () { return log("Connected to ".concat(_this._options.Host, ":").concat(_this._options.Port)); }); _this.on('disconnect', function () { _this.connection = null; log("Disconnected from ".concat(_this._options.Host, ":").concat(_this._options.Port)); }); _this.on(packets_1.PacketType.ISP_TINY, _this.handleKeepAlive); return _this; } Object.defineProperty(InSim.prototype, "options", { get: function () { return this._options; }, enumerable: false, configurable: true }); /** Currently supported InSim version */ InSim.INSIM_VERSION = 9; InSim.COMMAND_PREFIX = '/'; return InSim; }(tiny_typed_emitter_1.TypedEmitter)); exports.InSim = InSim; InSim.defaultMaxListeners = 255; var defaultInSimOptions = { Host: '127.0.0.1', Port: 29999, Protocol: 'TCP', ReqI: 0, UDPPort: 0, Flags: 0, Prefix: '', Interval: 0, Admin: '', IName: '', };