UNPKG

node-insim

Version:

An InSim library for NodeJS with TypeScript support

209 lines (208 loc) 8.23 kB
import { __awaiter } from "tslib"; import crypto from 'crypto'; import defaults from 'lodash.defaults'; import { TypedEmitter } from 'tiny-typed-emitter'; import unicodeToLfs from 'unicode-to-lfs'; import { unpack } from './lfspack'; import { log as baseLog } from './log'; import { IS_ISI, IS_MSL, IS_MST, IS_MSX, IS_MTC, IS_TINY, MessageSound, MST_MSG_MAX_LENGTH, PacketType, packetTypeToClass, TinyType, } from './packets'; import { TCP, UDP } from './protocols'; const log = baseLog.extend('insim'); export class InSim extends TypedEmitter { constructor(id) { super(); 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 = (options) => { if (this.connection !== null) { log('Cannot connect - already connected'); return; } this._options = defaults(options, defaultInSimOptions); log(`Connecting to ${this._options.Host}:${this._options.Port} using ${this._options.Protocol}...`); this.sizeMultiplier = 4; this.connection = this._options.Protocol === 'TCP' ? new TCP(this._options.Host, this._options.Port, this.sizeMultiplier) : new UDP({ host: this._options.Host, port: this._options.Port, packetSizeMultiplier: this.sizeMultiplier, socketInitialisationMode: 'connect', }); this.connection.connect(); this.connection.on('connect', () => { this.send(new 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', () => { this.emit('disconnect', this); }); this.connection.on('error', (error) => { this.emit('error', error); }); this.connection.on('data', (data) => this.handlePacket(data)); }; this.disconnect = () => { if (this.connection !== null) { log('Disconnecting...'); this.connection.disconnect(); } }; this.send = (packet) => { if (this.connection === null) { log('Cannot send a packet - not connected'); return; } packet.SIZE_MULTIPLIER = this.sizeMultiplier; log('Send packet:', PacketType[packet.Type], packet); const 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 = (message) => { log('Send message:', message); if (message.startsWith(InSim.COMMAND_PREFIX)) { return this.send(new IS_MST({ Msg: message, })); } const encodedMessageLength = unicodeToLfs(message).length; if (encodedMessageLength >= MST_MSG_MAX_LENGTH) { return this.send(new IS_MSX({ Msg: message, })); } return this.send(new 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 = (message, sound = MessageSound.SND_SILENT) => { log('Send local message:', message); return this.send(new IS_MSL({ Msg: message, Sound: sound, })); }; /** Send a message to a specific connection */ this.sendMessageToConnection = (ucid, message, sound = MessageSound.SND_SILENT) => { log('Send message to connection:', ucid, message); this.send(new IS_MTC({ UCID: ucid, Text: message, Sound: sound, })); }; /** Send a message to a specific player */ this.sendMessageToPlayer = (plid, message, sound = MessageSound.SND_SILENT) => { log('Send message to player:', plid, message); this.send(new IS_MTC({ PLID: plid, Text: message, Sound: sound, })); }; this.sendAwait = (packet, packetTypeToAwait, filterPacketData = () => true) => { return new Promise((resolve, reject) => { if (this.connection === null) { log('Cannot send a packet with await - not connected'); reject(); return; } this.send(packet); log('Await packet:', PacketType[packetTypeToAwait]); const packetListener = (receivedPacket) => { if (receivedPacket.ReqI === packet.ReqI && filterPacketData(receivedPacket)) { resolve(receivedPacket); } }; this.once(packetTypeToAwait, packetListener); this.on('disconnect', () => { this.removeListener(packetTypeToAwait, packetListener); reject(); }); }); }; this.handlePacket = (data) => __awaiter(this, void 0, void 0, function* () { const header = unpack('<BB', data.buffer); if (!header) { log(`Incomplete packet header received: ${data.join()}`); return; } const packetType = header[1]; const packetTypeString = PacketType[packetType]; if (packetTypeString === undefined) { log(`Unknown packet type received: ${packetType}`); return; } const PacketClass = packetTypeToClass[packetType]; if (PacketClass === undefined) { log(`Packet handler not found for ${packetTypeString}`); return; } const packetInstance = new PacketClass(); packetInstance.SIZE_MULTIPLIER = this.sizeMultiplier; this.emit(packetType, packetInstance.unpack(data), this); }); this.handleKeepAlive = (packet) => { if (packet.SubT === TinyType.TINY_NONE) { this.send(new IS_TINY({ SubT: TinyType.TINY_NONE, })); } }; this.id = id !== null && id !== void 0 ? id : crypto.randomUUID(); this.on('connect', () => log(`Connected to ${this._options.Host}:${this._options.Port}`)); this.on('disconnect', () => { this.connection = null; log(`Disconnected from ${this._options.Host}:${this._options.Port}`); }); this.on(PacketType.ISP_TINY, this.handleKeepAlive); } get options() { return this._options; } } /** Currently supported InSim version */ InSim.INSIM_VERSION = 10; InSim.COMMAND_PREFIX = '/'; InSim.defaultMaxListeners = 255; const defaultInSimOptions = { Host: '127.0.0.1', Port: 29999, Protocol: 'TCP', ReqI: 0, UDPPort: 0, Flags: 0, Prefix: '', Interval: 0, Admin: '', IName: '', };