VoiceUDPClient.js (4159B)
1 'use strict'; 2 3 const udp = require('dgram'); 4 const EventEmitter = require('events'); 5 const { Error } = require('../../../errors'); 6 const { VoiceOPCodes } = require('../../../util/Constants'); 7 8 /** 9 * Represents a UDP client for a Voice Connection. 10 * @extends {EventEmitter} 11 * @private 12 */ 13 class VoiceConnectionUDPClient extends EventEmitter { 14 constructor(voiceConnection) { 15 super(); 16 17 /** 18 * The voice connection that this UDP client serves 19 * @type {VoiceConnection} 20 */ 21 this.voiceConnection = voiceConnection; 22 23 /** 24 * The UDP socket 25 * @type {?Socket} 26 */ 27 this.socket = null; 28 29 /** 30 * The address of the Discord voice server 31 * @type {?string} 32 */ 33 this.discordAddress = null; 34 35 /** 36 * The local IP address 37 * @type {?string} 38 */ 39 this.localAddress = null; 40 41 /** 42 * The local port 43 * @type {?string} 44 */ 45 this.localPort = null; 46 47 this.voiceConnection.on('closing', this.shutdown.bind(this)); 48 } 49 50 shutdown() { 51 this.emit('debug', `[UDP] shutdown requested`); 52 if (this.socket) { 53 this.socket.removeAllListeners('message'); 54 try { 55 this.socket.close(); 56 } finally { 57 this.socket = null; 58 } 59 } 60 } 61 62 /** 63 * The port of the Discord voice server 64 * @type {number} 65 * @readonly 66 */ 67 get discordPort() { 68 return this.voiceConnection.authentication.port; 69 } 70 71 /** 72 * Send a packet to the UDP client. 73 * @param {Object} packet The packet to send 74 * @returns {Promise<Object>} 75 */ 76 send(packet) { 77 return new Promise((resolve, reject) => { 78 if (!this.socket) throw new Error('UDP_SEND_FAIL'); 79 if (!this.discordAddress || !this.discordPort) throw new Error('UDP_ADDRESS_MALFORMED'); 80 this.socket.send(packet, 0, packet.length, this.discordPort, this.discordAddress, error => { 81 if (error) { 82 this.emit('debug', `[UDP] >> ERROR: ${error}`); 83 reject(error); 84 } else { 85 resolve(packet); 86 } 87 }); 88 }); 89 } 90 91 async createUDPSocket(address) { 92 this.discordAddress = address; 93 const socket = (this.socket = udp.createSocket('udp4')); 94 socket.on('error', e => { 95 this.emit('debug', `[UDP] Error: ${e}`); 96 this.emit('error', e); 97 }); 98 socket.on('close', () => { 99 this.emit('debug', '[UDP] socket closed'); 100 }); 101 this.emit('debug', `[UDP] created socket`); 102 socket.once('message', message => { 103 this.emit('debug', `[UDP] message: [${[...message]}] (${message})`); 104 // Stop if the sockets have been deleted because the connection has been closed already 105 if (!this.voiceConnection.sockets.ws) return; 106 107 const packet = parseLocalPacket(message); 108 if (packet.error) { 109 this.emit('debug', `[UDP] ERROR: ${packet.error}`); 110 this.emit('error', packet.error); 111 return; 112 } 113 114 this.localAddress = packet.address; 115 this.localPort = packet.port; 116 117 this.voiceConnection.sockets.ws.sendPacket({ 118 op: VoiceOPCodes.SELECT_PROTOCOL, 119 d: { 120 protocol: 'udp', 121 data: { 122 address: packet.address, 123 port: packet.port, 124 mode: this.voiceConnection.authentication.mode, 125 }, 126 }, 127 }); 128 129 this.emit('debug', `[UDP] << ${JSON.stringify(packet)}`); 130 131 socket.on('message', buffer => this.voiceConnection.receiver.packets.push(buffer)); 132 }); 133 134 const blankMessage = Buffer.alloc(70); 135 blankMessage.writeUIntBE(this.voiceConnection.authentication.ssrc, 0, 4); 136 this.emit('debug', `Sending IP discovery packet: [${[...blankMessage]}]`); 137 await this.send(blankMessage); 138 this.emit('debug', `Successfully sent IP discovery packet`); 139 } 140 } 141 142 function parseLocalPacket(message) { 143 try { 144 const packet = Buffer.from(message); 145 let address = ''; 146 for (let i = 4; i < packet.indexOf(0, i); i++) address += String.fromCharCode(packet[i]); 147 const port = parseInt(packet.readUIntLE(packet.length - 2, 2).toString(10), 10); 148 return { address, port }; 149 } catch (error) { 150 return { error }; 151 } 152 } 153 154 module.exports = VoiceConnectionUDPClient;