UNPKG

@twurple/eventsub-ws

Version:

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

162 lines (160 loc) 6.1 kB
import { __decorate } from "tslib"; import { HellFreezesOverError, } from '@twurple/api'; import { rtfm } from '@twurple/common'; import { EventSubBase, } from '@twurple/eventsub-base'; import { EventSubWsSocket } from "./EventSubWsSocket.mjs"; /** * A WebSocket listener for the Twitch EventSub event distribution mechanism. * * @beta * @hideProtected * @inheritDoc * * @meta category main */ let EventSubWsListener = class EventSubWsListener extends EventSubBase { /** * Creates a new EventSub WebSocket listener. * * @param config * * @expandParams */ constructor(config) { var _a; super(config); this._sockets = new Map(); this._accepting = false; /** * Fires when a user socket has established a connection with the EventSub server. * * @param userId The ID of the user. */ this.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. */ this.onUserSocketDisconnect = this.registerEvent(); this._initialUrl = this._apiClient._mockServerPort ? `ws://127.0.0.1:${this._apiClient._mockServerPort}/ws` : (_a = config.url) !== null && _a !== void 0 ? _a : '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(); } /** @private */ async _getCliTestCommandForSubscription(subscription) { if (!this._apiClient._mockServerPort) { throw new Error(`You must use the mock server from the Twitch CLI to be able to test WebSocket events. To do so, specify the \`mockServerPort\` option in your \`ApiClient\`.`); } 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) { var _a; 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 (_a = socket === null || socket === void 0 ? void 0 : socket.readyToSubscribe) !== null && _a !== void 0 ? _a : 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 === null || socket === void 0 ? void 0 : 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 };