@tf2pickup-org/mumble-client
Version:
A simple bot for managing mumble servers
137 lines • 4.28 kB
JavaScript
import { Subject } from 'rxjs';
import { packetForType, packetType } from './packet-type-registry.js';
import { UDPTunnel } from '@tf2pickup-org/mumble-protocol';
import { readVarint } from './read-varint.js';
export class MumbleSocket {
socket;
_packet = new Subject();
_audioPacket = new Subject();
buffers = [];
length = 0;
readers = [];
constructor(socket) {
this.socket = socket;
this.socket.on('data', (data) => {
this.receiveData(data);
});
this.readPrefix();
}
get packet() {
return this._packet.asObservable();
}
get audioPacket() {
return this._audioPacket.asObservable();
}
read(length, callback) {
this.readers.push({ length, callback });
if (this.readers.length === 1) {
this.flushReaders();
}
}
async send(message, payload) {
const typeNumber = packetType(message);
if (typeNumber === undefined) {
throw new Error(`unknown message type (${message.typeName})`);
}
const encoded = message.toBinary(payload);
const prefix = Buffer.alloc(6);
prefix.writeUint16BE(typeNumber, 0);
prefix.writeUint32BE(encoded.length, 2);
await this.write(Buffer.concat([prefix, encoded]));
}
write(buffer) {
return new Promise((resolve, reject) => {
if (this.socket.writable) {
this.socket.write(buffer, err => {
if (err) {
reject(err);
}
else {
resolve();
}
});
}
else {
reject(new Error('socket not writable'));
}
});
}
end() {
this.socket.end();
}
receiveData(data) {
this.buffers.push(data);
this.length += data.length;
this.flushReaders();
}
flushReaders() {
if (this.readers.length === 0) {
return;
}
const reader = this.readers[0];
if (this.length < reader.length) {
return;
}
const buffer = Buffer.alloc(reader.length);
let written = 0;
while (written < reader.length) {
const received = this.buffers[0];
const remaining = reader.length - written;
if (received.length <= remaining) {
received.copy(buffer, written);
written += received.length;
this.buffers.splice(0, 1);
this.length -= received.length;
}
else {
received.copy(buffer, written, 0, remaining);
written += remaining;
this.buffers[0] = received.subarray(remaining);
this.length -= remaining;
}
}
this.readers.splice(0, 1);
reader.callback(buffer);
}
readPrefix() {
this.read(6, prefix => {
const type = prefix.readUint16BE(0);
const length = prefix.readUint32BE(2);
this.readPacket(type, length);
});
}
readPacket(type, length) {
this.read(length, data => {
const message = packetForType(type);
if (message) {
switch (message.typeName) {
case UDPTunnel.typeName:
this.decodeAudio(data);
break;
default:
this._packet.next({
type,
typeName: message.typeName,
payload: message.fromBinary(data),
});
}
}
else {
console.error(`Unrecognized packet type (${type})`);
}
this.readPrefix();
});
}
decodeAudio(packet) {
if (packet.length < 1) {
return;
}
const target = 0b000111111 & packet[0];
if (target !== 0) {
return;
}
const { value } = readVarint(packet.subarray(1));
this._audioPacket.next({ source: value });
}
}
//# sourceMappingURL=mumble-socket.js.map