UNPKG

@twurple/eventsub-ws

Version:

Listen to events on Twitch via their EventSub API using WebSockets.

170 lines (168 loc) 6.06 kB
import { __decorate } from "tslib"; import { HellFreezesOverError, } from '@twurple/api'; import { getMockApiPort, rtfm } from '@twurple/common'; import { EventSubBase, } from '@twurple/eventsub-base'; import { EventSubWsSocket } from './EventSubWsSocket.js'; /** * A WebSocket listener for the Twitch EventSub event distribution mechanism. * * @beta * @hideProtected * @inheritDoc * * @meta category main */ let EventSubWsListener = class EventSubWsListener extends EventSubBase { _sockets = new Map(); _initialUrl; _accepting = false; _loggerOptions; _mockApiPort; /** * Fires when a user socket has established a connection with the EventSub server. * * @param userId The ID of the user. */ onUserSocketConnect = this.registerEvent(); /** * Fires when a user socket has disconnected from the EventSub server. * * @param userId The ID of the user. * @param error The error that caused the disconnection, or `undefined` for a clean disconnect. */ onUserSocketDisconnect = this.registerEvent(); /** * Creates a new EventSub WebSocket listener. * * @param config * * @expandParams */ constructor(config) { super(config); this._mockApiPort = getMockApiPort(); this._initialUrl = this._mockApiPort ? `ws://127.0.0.1:${this._mockApiPort}/ws` : config.url ?? 'wss://eventsub.wss.twitch.tv/ws'; this._loggerOptions = config.logger; } /** * Starts the WebSocket listener. */ start() { this._accepting = true; const userSocketsToCreate = new Set([...this._subscriptions.values()].map(sub => sub.authUserId)); for (const userId of userSocketsToCreate) { this._createSocketForUser(userId); } } /** * Stops the WebSocket listener. */ stop() { this._accepting = false; for (const socket of this._sockets.values()) { socket.stop(); } this._sockets.clear(); } /** * Whether the WebSocket listener is active. */ get isActive() { return this._accepting; } /** @private */ async _getCliTestCommandForSubscription(subscription) { if (!this._mockApiPort) { throw new Error(`You must use the mock server from the Twitch CLI to be able to test WebSocket events. To do so, set the \`TWURPLE_MOCK_API_PORT\` environment variable to the port the mock server runs on (usually 8080).`); } const { authUserId } = subscription; if (!authUserId) { throw new Error('Can not test a WebSocket subscription for a topic without user authentication'); } if (!subscription._twitchId) { throw new Error('Subscription must be registered with the mock server before being able to use this method'); } const socket = this._sockets.get(authUserId); if (!socket) { throw new HellFreezesOverError(`Can not get appropriate socket for user ${authUserId}`); } if (!socket.sessionId) { throw new HellFreezesOverError(`Socket for user ${authUserId} does not have a session ID yet`); } return `twitch event trigger ${subscription._cliName} -T websocket --session ${socket.sessionId} -u ${subscription._twitchId}`; } /** @private */ _isReadyToSubscribe(subscription) { const { authUserId } = subscription; if (!authUserId) { throw new Error('Can not create a WebSocket subscription for a topic without user authentication'); } const socket = this._sockets.get(authUserId); return socket?.readyToSubscribe ?? false; } /** @private */ async _getTransportOptionsForSubscription(subscription) { const { authUserId } = subscription; if (!authUserId) { throw new Error('Can not create a WebSocket subscription for a topic without user authentication'); } const socket = this._sockets.get(authUserId); if (!socket?.sessionId) { throw new HellFreezesOverError(`Socket for user ${authUserId} is not connected or does not have a session ID yet`); } return { method: 'websocket', // eslint-disable-next-line @typescript-eslint/naming-convention session_id: socket.sessionId, }; } /** @private */ _getSubscriptionsForUser(userId) { return [...this._subscriptions.values()].filter(sub => sub.authUserId === userId); } /** @private */ _handleSubscriptionRevoke(subscription, status) { this.emit(this.onRevoke, subscription, status); } /** @internal */ _notifySocketConnect(socket) { this.emit(this.onUserSocketConnect, socket.userId); } /** @internal */ _notifySocketDisconnect(socket, error) { this.emit(this.onUserSocketDisconnect, socket.userId, error); } _genericSubscribe(clazz, handler, client, ...params) { const subscription = super._genericSubscribe(clazz, handler, client, ...params); const { authUserId } = subscription; if (!authUserId) { throw new HellFreezesOverError('WS subscription created without user ID'); } if (!this._accepting) { return subscription; } if (this._sockets.has(authUserId)) { this._sockets.get(authUserId).start(); } else { this._createSocketForUser(authUserId); } return subscription; } _findTwitchSubscriptionToContinue() { return undefined; } /** @internal */ _createSocketForUser(authUserId) { const socket = new EventSubWsSocket(this, authUserId, this._initialUrl, this._loggerOptions); this._sockets.set(authUserId, socket); socket.start(); } }; EventSubWsListener = __decorate([ rtfm('eventsub-ws', 'EventSubWsListener') ], EventSubWsListener); export { EventSubWsListener };