detritus-client-socket
Version:
A TypeScript NodeJS library to interact with Discord's Gateway
875 lines (874 loc) • 33.3 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Socket = void 0;
const os = require("os");
const url_1 = require("url");
const detritus_utils_1 = require("detritus-utils");
const basesocket_1 = require("./basesocket");
const bucket_1 = require("./bucket");
const decompressor_1 = require("./decompressor");
const errors_1 = require("./errors");
const media_1 = require("./media");
const constants_1 = require("./constants");
const Dependencies = {
Erlpack: null,
};
try {
Dependencies.Erlpack = require('erlpack');
}
catch (e) { }
const IdentifyProperties = Object.freeze({
$browser: process.version.replace(/^v/, (process.release.name || 'node') + '/'),
$device: `Detritus v${constants_1.Package.VERSION}`,
$os: `${os.type()} ${os.release()}; ${os.arch()}`,
});
const defaultOptions = Object.freeze({
autoReconnect: true,
compress: true,
encoding: (Dependencies.Erlpack) ? constants_1.EncodingTypes.ETF : constants_1.EncodingTypes.JSON,
guildSubscriptions: true,
intents: constants_1.GATEWAY_INTENTS_ALL_UNPRIVILEGED,
largeThreshold: 250,
presence: null,
reconnectDelay: constants_1.DEFAULT_SHARD_LAUNCH_DELAY,
reconnectMax: 5,
shardCount: constants_1.DEFAULT_SHARD_COUNT,
shardId: 0,
});
const defaultPresence = Object.freeze({
afk: false,
since: null,
status: constants_1.GatewayPresenceStatuses.ONLINE,
});
class Socket extends detritus_utils_1.EventSpewer {
constructor(token, options = {}) {
super();
this.state = constants_1.SocketStates.CLOSED;
this._heartbeat = {
ack: false,
lastAck: null,
lastSent: null,
interval: new detritus_utils_1.Timers.Interval(),
intervalTime: null,
};
this.discordTrace = [];
this.identifyProperties = Object.assign({}, IdentifyProperties);
this.intents = constants_1.GATEWAY_INTENTS_ALL_UNPRIVILEGED;
this.killed = false;
this.mediaGateways = new detritus_utils_1.BaseCollection();
this.presence = null;
this.reconnects = 0;
this.resuming = false;
this.sequence = 0;
this.sessionId = null;
this.socket = null;
this.url = null;
this.userId = null;
options = Object.assign({
disabledEvents: [],
}, defaultOptions, options);
if (typeof (options.compress) === 'boolean') {
if (options.compress) {
options.compress = decompressor_1.Decompressor.supported().shift();
}
else {
options.compress = constants_1.CompressTypes.NONE;
}
}
this.autoReconnect = !!options.autoReconnect;
this.compress = options.compress.toLowerCase();
this.encoding = options.encoding.toLowerCase();
this.disabledEvents = options.disabledEvents;
this.guildSubscriptions = !!options.guildSubscriptions;
this.largeThreshold = options.largeThreshold;
this.reconnectDelay = options.reconnectDelay;
this.reconnectMax = options.reconnectMax;
this.shardCount = options.shardCount;
this.shardId = options.shardId;
this.token = token;
this.onIdentifyCheck = options.onIdentifyCheck || this.onIdentifyCheck;
if (options.presence) {
this.presence = Object.assign({}, defaultPresence, options.presence);
}
Object.assign(this.identifyProperties, options.identifyProperties);
if (!constants_1.COMPRESS_TYPES.includes(this.compress)) {
throw new Error(`Compress type must be of: '${constants_1.COMPRESS_TYPES.join(', ')}'`);
}
if (this.compress === constants_1.CompressTypes.PAYLOAD) {
throw new Error(`Compress type '${this.compress}' is currently not supported.`);
}
if (this.shardCount <= this.shardId) {
throw new Error('Shard count cannot be less than or equal to the Shard Id!');
}
if (!Object.values(constants_1.EncodingTypes).includes(this.encoding)) {
throw new Error(`Invalid Encoding Type, valid: ${JSON.stringify(Object.values(constants_1.EncodingTypes))}`);
}
if (this.encoding === constants_1.EncodingTypes.ETF && !Dependencies.Erlpack) {
throw new Error('Install `Erlpack` to use ETF encoding.');
}
this.bucket = new bucket_1.Bucket(120, 60 * 1000);
this.decompressor = null;
switch (this.compress) {
case constants_1.CompressTypes.ZLIB:
{
if (!decompressor_1.Decompressor.supported().includes(this.compress)) {
throw new Error(`Missing modules for ${this.compress} Compress Type`);
}
this.decompressor = new decompressor_1.Decompressor({ type: this.compress });
this.decompressor.on('data', (data) => {
this.handle(data, true);
}).on('error', (error) => {
this.disconnect(constants_1.SocketInternalCloseCodes.INVALID_DATA);
this.emit(constants_1.SocketEvents.WARN, error);
});
}
;
}
if (options.intents !== undefined) {
this.intents = 0;
if (options.intents === 'ALL') {
this.intents = constants_1.GATEWAY_INTENTS_ALL;
}
else if (options.intents === 'ALL_UNPRIVILEGED') {
this.intents = constants_1.GATEWAY_INTENTS_ALL_UNPRIVILEGED;
}
else {
const intents = (Array.isArray(options.intents)) ? options.intents : [options.intents];
for (let intent of intents) {
if (typeof (intent) === 'string') {
intent = intent.toUpperCase();
if (intent in constants_1.GatewayIntents) {
this.intents |= constants_1.GatewayIntents[intent];
}
}
else if (typeof (intent) === 'number') {
this.intents |= intent;
}
else {
throw new Error(`Invalid intent received: ${intent}`);
}
}
}
}
Object.defineProperties(this, {
_heartbeat: { enumerable: false, writable: false },
identifyProperties: { enumerable: false },
killed: { configurable: true },
state: { configurable: true, writable: false },
token: { enumerable: false, writable: false },
});
}
get closed() {
return !!this.socket && this.socket.closed;
}
get closing() {
return !!this.socket && this.socket.closing;
}
get connected() {
return !!this.socket && this.socket.connected;
}
get connecting() {
return !!this.socket && this.socket.connecting;
}
setState(value) {
if (value in constants_1.SocketStates && value !== this.state) {
Object.defineProperty(this, 'state', { value });
this.emit(constants_1.SocketEvents.STATE, { state: value });
}
}
makePresence(options = {}) {
options = this.presence = Object.assign({}, defaultPresence, this.presence, options);
const data = {
activities: [],
afk: !!options.afk,
since: options.since || null,
status: options.status || defaultPresence.status,
};
const activities = [...(options.activities || [])];
if (options.activity) {
activities.unshift(options.activity);
}
if (options.game) {
activities.unshift(options.game);
}
if (activities.length) {
for (let activity of activities) {
const raw = {
application_id: activity.applicationId,
assets: undefined,
details: activity.details,
emoji: undefined,
flags: activity.flags,
metadata: activity.metadata,
name: activity.name,
party: undefined,
platform: activity.platform,
secrets: undefined,
session_id: activity.sessionId,
state: activity.state,
sync_id: activity.syncId,
timestamps: undefined,
type: activity.type,
url: activity.url,
};
if (activity.assets) {
raw.assets = {
large_image: activity.assets.largeImage,
large_text: activity.assets.largeText,
small_image: activity.assets.smallImage,
small_text: activity.assets.smallText,
};
}
if (activity.emoji) {
raw.emoji = {
animated: activity.emoji.animated,
id: activity.emoji.id,
name: activity.emoji.name,
};
}
if (activity.party) {
raw.party = {
id: activity.party.id,
size: activity.party.size,
};
}
if (activity.secrets) {
raw.secrets = {
join: activity.secrets.join,
match: activity.secrets.match,
spectate: activity.secrets.spectate,
};
}
if (activity.timestamps) {
raw.timestamps = {
end: activity.timestamps.end,
start: activity.timestamps.start,
};
}
data.activities.push(raw);
}
}
return data;
}
getIdentifyData() {
const data = {
/* payload compression, rather use transport compression, using the get params overrides this */
compress: (this.compress === constants_1.CompressTypes.PAYLOAD),
guild_subscriptions: this.guildSubscriptions,
intents: this.intents,
large_threshold: this.largeThreshold,
properties: this.identifyProperties,
token: this.token,
};
if (constants_1.DEFAULT_SHARD_COUNT < this.shardCount) {
data.shard = [this.shardId, this.shardCount];
}
if (this.presence) {
data.presence = this.makePresence();
}
return data;
}
getResumeData() {
return {
seq: this.sequence || null,
session_id: this.sessionId,
token: this.token,
};
}
cleanup(code, reason) {
this.bucket.clear();
this.bucket.lock();
if (this.decompressor) {
this.decompressor.reset();
}
// un-resumable events
// 1000
// un-resumable and kill socket
// 4004 Authentication Failed
// 4010 Invalid Shard Sent
// 4011 Sharding Required
// 4012 Invalid Gateway Version
// 4013 Invalid Intents Sent
if (code !== undefined) {
code = parseInt(code);
switch (code) {
case constants_1.SocketCloseCodes.NORMAL:
{
this.sequence = 0;
this.sessionId = null;
}
;
break;
case constants_1.SocketGatewayCloseCodes.AUTHENTICATION_FAILED:
case constants_1.SocketGatewayCloseCodes.INVALID_SHARD:
case constants_1.SocketGatewayCloseCodes.SHARDING_REQUIRED:
case constants_1.SocketGatewayCloseCodes.INVALID_VERSION:
case constants_1.SocketGatewayCloseCodes.INVALID_INTENTS:
case constants_1.SocketGatewayCloseCodes.DISALLOWED_INTENTS:
{
this.kill(new errors_1.SocketKillError(code, reason));
}
;
break;
}
}
this._heartbeat.interval.stop();
this._heartbeat.ack = false;
this._heartbeat.lastAck = null;
this._heartbeat.lastSent = null;
this._heartbeat.intervalTime = null;
}
connect(url) {
if (this.killed) {
return;
}
if (this.connected) {
this.disconnect();
}
if (!url) {
url = this.url;
}
if (!url) {
throw new Error('Socket requires a url to connect to.');
}
this.url = new url_1.URL('', url);
this.url.searchParams.set('encoding', this.encoding);
this.url.searchParams.set('v', String(constants_1.ApiVersions.GATEWAY));
this.url.pathname = this.url.pathname || '/';
switch (this.compress) {
case constants_1.CompressTypes.ZLIB:
{
this.url.searchParams.set('compress', this.compress);
}
;
break;
}
try {
this.socket = new basesocket_1.BaseSocket(this.url.href);
this.socket.on(constants_1.SocketEventsBase.CLOSE, this.onClose.bind(this, this.socket));
this.socket.on(constants_1.SocketEventsBase.ERROR, this.onError.bind(this, this.socket));
this.socket.on(constants_1.SocketEventsBase.MESSAGE, this.onMessage.bind(this, this.socket));
this.socket.on(constants_1.SocketEventsBase.OPEN, this.onOpen.bind(this, this.socket));
}
catch (error) {
this.socket = null;
if (this.autoReconnect && !this.killed) {
if (this.reconnectMax < this.reconnects) {
this.kill(new Error(`Tried reconnecting more than ${this.reconnectMax} times.`));
}
else {
this.emit(constants_1.SocketEvents.RECONNECTING);
setTimeout(() => {
this.connect(url);
this.reconnects++;
}, this.reconnectDelay);
}
}
}
this.setState(constants_1.SocketStates.CONNECTING);
this.emit(constants_1.SocketEvents.SOCKET, this.socket);
}
decode(data, uncompressed = false) {
try {
if (data instanceof ArrayBuffer) {
data = Buffer.from(new Uint8Array(data));
}
else if (Array.isArray(data)) {
data = Buffer.concat(data);
}
if (!uncompressed) {
if (this.decompressor) {
this.decompressor.feed(data);
return null;
}
}
switch (this.encoding) {
case constants_1.EncodingTypes.ETF: return Dependencies.Erlpack.unpack(data);
case constants_1.EncodingTypes.JSON: return JSON.parse(data);
}
}
catch (error) {
this.emit(constants_1.SocketEvents.WARN, error);
}
}
disconnect(code = constants_1.SocketCloseCodes.NORMAL, reason) {
this.cleanup(code, reason);
if (this.socket) {
if (!reason && (code in constants_1.SocketInternalCloseReasons)) {
reason = constants_1.SocketInternalCloseReasons[code];
}
this.socket.close(code, reason);
this.socket = null;
}
this.resuming = false;
}
handle(data, uncompressed = false) {
const packet = this.decode(data, uncompressed);
if (!packet) {
return;
}
if (packet.s !== null) {
this.sequence = packet.s;
}
switch (packet.op) {
case constants_1.GatewayOpCodes.HEARTBEAT:
{
this.heartbeat();
}
;
break;
case constants_1.GatewayOpCodes.HEARTBEAT_ACK:
{
this._heartbeat.ack = true;
this._heartbeat.lastAck = Date.now();
}
;
break;
case constants_1.GatewayOpCodes.HELLO:
{
const data = packet.d;
this.setHeartbeat(data);
if (this.sessionId) {
this.resume();
}
else {
this.identifyTry();
}
}
;
break;
case constants_1.GatewayOpCodes.INVALID_SESSION:
{
const shouldResume = packet.d;
setTimeout(() => {
if (shouldResume) {
this.resume();
}
else {
this.sequence = 0;
this.sessionId = null;
this.identifyTry();
}
}, Math.floor(Math.random() * 5 + 1) * 1000);
}
;
break;
case constants_1.GatewayOpCodes.RECONNECT:
{
this.disconnect(constants_1.SocketInternalCloseCodes.RECONNECTING);
this.connect();
}
;
break;
case constants_1.GatewayOpCodes.DISPATCH:
{
this.handleDispatch(packet.t, packet.d);
}
;
break;
}
setImmediate(() => {
this.emit(constants_1.SocketEvents.PACKET, packet);
});
}
handleDispatch(name, data) {
switch (name) {
case constants_1.GatewayDispatchEvents.READY:
{
this.bucket.unlock();
this.reconnects = 0;
this.discordTrace = data['_trace'];
this.sessionId = data['session_id'];
this.userId = data['user']['id'];
this.setState(constants_1.SocketStates.READY);
this.emit(constants_1.SocketEvents.READY);
}
;
break;
case constants_1.GatewayDispatchEvents.RESUMED:
{
this.reconnects = 0;
this.resuming = false;
this.bucket.unlock();
this.setState(constants_1.SocketStates.READY);
this.emit(constants_1.SocketEvents.READY);
}
;
break;
case constants_1.GatewayDispatchEvents.GUILD_DELETE:
{
const serverId = data['id'];
if (this.mediaGateways.has(serverId)) {
const mGateway = this.mediaGateways.get(serverId);
if (data['unavailable']) {
mGateway.kill(new Error('The guild this voice was connected to became unavailable'));
}
else {
mGateway.kill(new Error('Left the guild this voice was connected to'));
}
}
}
;
break;
case constants_1.GatewayDispatchEvents.VOICE_SERVER_UPDATE:
{
const serverId = (data['guild_id'] || data['channel_id']);
if (this.mediaGateways.has(serverId)) {
const gateway = this.mediaGateways.get(serverId);
gateway.setEndpoint(data['endpoint']);
gateway.setToken(data['token']);
}
}
;
break;
case constants_1.GatewayDispatchEvents.VOICE_STATE_UPDATE:
{
const userId = data['user_id'];
if (userId !== this.userId) {
// not our voice state update
return;
}
const serverId = (data['guild_id'] || data['channel_id']);
if (this.mediaGateways.has(serverId)) {
const gateway = this.mediaGateways.get(serverId);
if (!data['channel_id']) {
gateway.kill();
}
else if (gateway.sessionId !== data['session_id']) {
gateway.kill(new Error('Connected to this server from a different session'));
}
else {
gateway.setChannelId(data['channel_id']);
gateway.resolvePromises();
}
}
}
;
break;
}
}
kill(error) {
if (!this.killed) {
Object.defineProperty(this, 'killed', { value: true });
this.disconnect(constants_1.SocketCloseCodes.NORMAL);
for (let [serverId, socket] of this.mediaGateways) {
socket.kill(error);
}
this.emit(constants_1.SocketEvents.KILLED, { error });
this.removeAllListeners();
}
}
onClose(target, code, reason) {
if (code === undefined) {
code = constants_1.SocketInternalCloseCodes.CONNECTION_ERROR;
}
if (!reason && (code in constants_1.SocketInternalCloseReasons)) {
reason = constants_1.SocketInternalCloseReasons[code];
}
this.emit(constants_1.SocketEvents.CLOSE, { code, reason });
if (!this.socket || this.socket === target) {
this.setState(constants_1.SocketStates.CLOSED);
this.disconnect(code, reason);
if (this.autoReconnect && !this.killed) {
if (this.reconnectMax < this.reconnects) {
this.kill(new Error(`Tried reconnecting more than ${this.reconnectMax} times.`));
}
else {
this.emit(constants_1.SocketEvents.RECONNECTING);
setTimeout(() => {
this.connect();
this.reconnects++;
}, this.reconnectDelay);
}
}
}
}
onError(target, error) {
this.emit(constants_1.SocketEvents.WARN, error);
}
onMessage(target, data) {
if (this.socket === target) {
this.handle(data);
}
else {
target.close(constants_1.SocketInternalCloseCodes.OTHER_SOCKET_MESSAGE);
}
}
onOpen(target) {
this.emit(constants_1.SocketEvents.OPEN, target);
if (this.socket === target) {
this.setState(constants_1.SocketStates.OPEN);
}
else {
target.close(constants_1.SocketInternalCloseCodes.OTHER_SOCKET_OPEN);
}
}
async ping(timeout) {
if (!this.connected || !this.socket) {
throw new Error('Socket is still initializing!');
}
return this.socket.ping(timeout);
}
send(op, d, callback, direct = false) {
const packet = { op, d };
let data;
try {
switch (this.encoding) {
case constants_1.EncodingTypes.JSON:
data = JSON.stringify(packet);
break;
case constants_1.EncodingTypes.ETF:
data = Dependencies.Erlpack.pack(packet);
break;
default:
{
throw new errors_1.DroppedPacketError(packet, `Invalid encoding: ${this.encoding}`);
}
;
}
}
catch (error) {
this.emit(constants_1.SocketEvents.WARN, error);
}
if (data !== undefined) {
if (direct) {
if (this.connected && this.socket) {
this.socket.send(data, callback);
}
else {
this.emit(constants_1.SocketEvents.WARN, new errors_1.DroppedPacketError(packet, 'Socket isn\'t connected'));
}
}
else {
const throttled = () => {
if (this.bucket.locked || !this.connected) {
if (!this.bucket.locked) {
this.bucket.lock();
}
this.bucket.add(throttled, true);
}
else {
try {
this.socket.send(data, callback);
}
catch (error) {
this.emit(constants_1.SocketEvents.WARN, error);
}
}
};
this.bucket.add(throttled);
}
}
}
heartbeat(fromInterval = false) {
if (fromInterval && !this._heartbeat.ack) {
this.disconnect(constants_1.SocketInternalCloseCodes.HEARTBEAT_ACK);
this.connect();
}
else {
this._heartbeat.ack = false;
this._heartbeat.lastSent = Date.now();
const sequence = (this.sequence) ? this.sequence : null;
this.send(constants_1.GatewayOpCodes.HEARTBEAT, sequence, undefined, true);
}
}
setHeartbeat(data) {
if (data) {
this.heartbeat();
this._heartbeat.ack = true;
this._heartbeat.lastAck = Date.now();
this._heartbeat.intervalTime = data['heartbeat_interval'];
this._heartbeat.interval.start(this._heartbeat.intervalTime, () => {
this.heartbeat(true);
});
this.discordTrace = data._trace;
}
}
identify() {
if (this.state !== constants_1.SocketStates.OPEN) {
return;
}
const data = this.getIdentifyData();
this.send(constants_1.GatewayOpCodes.IDENTIFY, data, () => {
this.setState(constants_1.SocketStates.IDENTIFYING);
}, true);
}
async identifyTry() {
if (!this.onIdentifyCheck || (await Promise.resolve(this.onIdentifyCheck()))) {
this.identify();
}
}
resume() {
this.resuming = true;
const data = this.getResumeData();
this.send(constants_1.GatewayOpCodes.RESUME, data, () => {
this.setState(constants_1.SocketStates.RESUMING);
}, true);
}
/* user callable function */
callConnect(channelId, callback) {
this.send(constants_1.GatewayOpCodes.CALL_CONNECT, {
channel_id: channelId,
}, callback);
}
guildStreamCreate(guildId, options, callback) {
this.send(constants_1.GatewayOpCodes.STREAM_CREATE, {
channel_id: options.channelId,
guild_id: guildId,
preferred_region: options.preferredRegion,
type: 'guild',
}, callback);
}
lobbyConnect(lobbyId, lobbySecret, callback) {
this.send(constants_1.GatewayOpCodes.LOBBY_CONNECT, {
lobby_id: lobbyId,
lobby_secret: lobbySecret,
}, callback);
}
lobbyDisconnect(lobbyId, callback) {
this.send(constants_1.GatewayOpCodes.LOBBY_DISCONNECT, {
lobby_id: lobbyId,
}, callback);
}
lobbyVoiceStatesUpdate(voiceStates, callback) {
this.send(constants_1.GatewayOpCodes.LOBBY_VOICE_STATES_UPDATE, voiceStates.map((voiceState) => {
return {
lobby_id: voiceState.lobbyId,
self_deaf: voiceState.selfDeaf,
self_mute: voiceState.selfMute,
};
}), callback);
}
requestGuildMembers(guildIds, options, callback) {
this.send(constants_1.GatewayOpCodes.REQUEST_GUILD_MEMBERS, {
guild_id: guildIds,
limit: options.limit,
nonce: options.nonce,
presences: options.presences,
query: options.query,
user_ids: options.userIds,
}, callback);
}
requestApplicationCommands(guildId, options) {
this.send(constants_1.GatewayOpCodes.REQUEST_APPLICATION_COMMANDS, {
application_id: options.applicationId,
guild_id: guildId,
limit: options.limit,
nonce: options.nonce,
offset: options.offset,
query: options.query,
});
}
setPresence(options = {}, callback) {
const data = this.makePresence(options);
this.send(constants_1.GatewayOpCodes.PRESENCE_UPDATE, data, callback);
}
streamDelete(streamKey, callback) {
this.send(constants_1.GatewayOpCodes.STREAM_DELETE, {
stream_key: streamKey,
}, callback);
}
streamPing(streamKey, callback) {
this.send(constants_1.GatewayOpCodes.STREAM_PING, {
stream_key: streamKey,
}, callback);
}
streamSetPaused(streamKey, paused, callback) {
this.send(constants_1.GatewayOpCodes.STREAM_SET_PAUSED, {
stream_key: streamKey,
paused,
}, callback);
}
streamWatch(streamKey, callback) {
this.send(constants_1.GatewayOpCodes.STREAM_WATCH, {
stream_key: streamKey,
}, callback);
}
syncGuild(guildIds, callback) {
this.send(constants_1.GatewayOpCodes.SYNC_GUILD, guildIds, callback);
}
updateGuildSubscriptions(guildId, options = {}, callback) {
this.send(constants_1.GatewayOpCodes.GUILD_SUBSCRIPTIONS, {
activities: options.activities,
channels: options.channels,
guild_id: guildId,
members: options.members,
typing: options.typing,
}, callback);
}
voiceServerPing(callback) {
this.send(constants_1.GatewayOpCodes.VOICE_SERVER_PING, null, callback);
}
voiceStateUpdate(guildId = null, channelId = null, options = {}, callback) {
this.send(constants_1.GatewayOpCodes.VOICE_STATE_UPDATE, {
channel_id: channelId,
guild_id: guildId,
preferred_region: options.preferredRegion,
self_deaf: options.selfDeaf,
self_mute: options.selfMute,
self_video: options.selfVideo,
}, callback);
}
async voiceConnect(guildId, channelId, options = {
receive: true,
timeout: constants_1.DEFAULT_VOICE_TIMEOUT,
}) {
if (!guildId && !channelId) {
throw new Error('A Guild Id or a Channel Id is required.');
}
if (options.timeout === undefined) {
options.timeout = constants_1.DEFAULT_VOICE_TIMEOUT;
}
if (options.selfVideo && options.video === undefined) {
options.video = options.selfVideo;
}
const serverId = (guildId || channelId);
let gateway;
if (this.mediaGateways.has(serverId)) {
gateway = this.mediaGateways.get(serverId);
if (!channelId) {
gateway.kill();
return null;
}
if (channelId === gateway.channelId) {
return gateway;
}
}
else {
if (!channelId) {
this.voiceStateUpdate(guildId, channelId, options);
return null;
}
gateway = new media_1.Socket(this, {
channelId,
forceMode: options.forceMode,
receive: options.receive,
serverId,
userId: this.userId,
video: options.video,
});
this.mediaGateways.set(serverId, gateway);
}
const timeout = new detritus_utils_1.Timers.Timeout();
if (options.timeout) {
timeout.start(options.timeout, () => {
gateway.kill(new Error(`Voice Gateway took longer than ${options.timeout}ms.`));
});
}
return new Promise((resolve, reject) => {
gateway.promises.add({ resolve, reject });
this.voiceStateUpdate(guildId, channelId, options);
}).then(() => {
timeout.stop();
return (channelId) ? gateway : null;
}).catch((error) => {
timeout.stop();
throw error;
});
}
on(event, listener) {
super.on(event, listener);
return this;
}
}
exports.Socket = Socket;