@twurple/eventsub-ws
Advanced tools
Comparing version 5.3.0-pre.2 to 5.3.0-pre.3
@@ -5,2 +5,13 @@ import type { HelixEventSubWebSocketTransportOptions } from '@twurple/api'; | ||
/** | ||
* Configuration for an EventSub WebSocket listener. | ||
*/ | ||
export interface EventSubWsConfig extends EventSubBaseConfig { | ||
/** | ||
* The URL to connect to initially. | ||
* | ||
* Can be used to connect to a test server, for example one created by the Twitch CLI. | ||
*/ | ||
url?: string; | ||
} | ||
/** | ||
* A WebSocket listener for the Twitch EventSub event distribution mechanism. | ||
@@ -18,2 +29,5 @@ * | ||
private _welcomeCallback?; | ||
private readonly _initialUrl; | ||
private _connecting; | ||
private _reconnectInProgress; | ||
/** | ||
@@ -26,3 +40,3 @@ * Creates a new EventSub HTTP listener. | ||
*/ | ||
constructor(config: EventSubBaseConfig); | ||
constructor(config: EventSubWsConfig); | ||
start(): Promise<void>; | ||
@@ -36,3 +50,5 @@ stop(): Promise<void>; | ||
private _connectTo; | ||
private _disconnect; | ||
private _reconnect; | ||
} | ||
//# sourceMappingURL=EventSubWsListener.d.ts.map |
@@ -7,2 +7,3 @@ "use strict"; | ||
const shared_utils_1 = require("@d-fischer/shared-utils"); | ||
const api_1 = require("@twurple/api"); | ||
const auth_1 = require("@twurple/auth"); | ||
@@ -29,2 +30,3 @@ const common_1 = require("@twurple/common"); | ||
constructor(config) { | ||
var _a; | ||
if (config.apiClient._authProvider.tokenType !== 'user') { | ||
@@ -34,2 +36,5 @@ throw new auth_1.InvalidTokenTypeError('EventSub over WebSockets requires user access tokens to work.'); | ||
super(config); | ||
this._initialUrl = (_a = config.url) !== null && _a !== void 0 ? _a : 'wss://eventsub-beta.wss.twitch.tv/ws'; | ||
this._connecting = false; | ||
this._reconnectInProgress = false; | ||
} | ||
@@ -54,3 +59,3 @@ async start() { | ||
if (!this._sessionId) { | ||
throw new Error('Listener is not connected or does not have a session ID yet (this is a bug, please report it)'); | ||
throw new api_1.HellFreezesOverError('Listener is not connected or does not have a session ID yet'); | ||
} | ||
@@ -63,3 +68,3 @@ return { | ||
async _connect() { | ||
await this._connectTo('wss://eventsub-beta.wss.twitch.tv/ws'); | ||
await this._connectTo(this._initialUrl); | ||
} | ||
@@ -71,64 +76,126 @@ async _connectTo(url) { | ||
} | ||
this._connection = new connection_1.WebSocketConnection({ url }); | ||
this._connection.onConnect(() => { | ||
// TODO reset backoff | ||
}); | ||
this._connection.onDisconnect(async (manually) => { | ||
this._readyToSubscribe = false; | ||
this._connection = undefined; | ||
if (!manually) { | ||
await this._connect(); // TODO backoff failures, re-register subscriptions on successful reconnect (at welcome?) | ||
} | ||
}); | ||
this._connection.onReceive(async (data) => { | ||
var _a, _b; | ||
const { metadata, payload } = JSON.parse(data); | ||
switch (metadata.message_type) { | ||
case 'session_welcome': { | ||
this._sessionId = payload.session.id; | ||
this._readyToSubscribe = true; | ||
(_a = this._welcomeCallback) === null || _a === void 0 ? void 0 : _a.call(this); | ||
this._welcomeCallback = undefined; | ||
// TODO subscribe to all resting subscription objects | ||
break; | ||
this._connecting = true; | ||
const retryTimerGenerator = (0, shared_utils_1.fibWithLimit)(120); | ||
while (true) { | ||
const newConnection = (this._connection = new connection_1.WebSocketConnection({ url }, { logger: this._logger })); | ||
newConnection.onDisconnect(async (manually, reason) => { | ||
this._readyToSubscribe = false; | ||
if (manually) { | ||
if (this._reconnectInProgress) { | ||
this._logger.debug('Reconnect: old connection cleaned up'); | ||
} | ||
else { | ||
this._logger.info('Disconnected'); | ||
} | ||
void newConnection.disconnect(); | ||
if (this._connection === newConnection) { | ||
this._connection = undefined; | ||
} | ||
} | ||
case 'session_keepalive': { | ||
// TODO reset keepalive timeout | ||
break; | ||
else { | ||
if (reason) { | ||
this._logger.warn(`Disconnected unexpectedly: ${reason.message}; trying to reconnect`); | ||
} | ||
else { | ||
this._logger.warn('Disconnected unexpectedly; trying to reconnect'); | ||
} | ||
if (!this._connecting) { | ||
void this._reconnect(); | ||
} | ||
} | ||
case 'notification': { | ||
const id = payload.subscription.id; | ||
const subscription = this._getCorrectSubscriptionByTwitchId(id); | ||
if (!subscription) { | ||
this._logger.error(`Notification from unknown event received: ${id}`); | ||
}); | ||
this._connection.onReceive(async (data) => { | ||
var _a; | ||
this._logger.debug(`Received data: ${data.trim()}`); | ||
const { metadata, payload } = JSON.parse(data); | ||
switch (metadata.message_type) { | ||
case 'session_welcome': { | ||
this._logger.info(this._reconnectInProgress | ||
? 'Reconnect: new connection established' | ||
: 'Connection established'); | ||
this._sessionId = payload.session.id; | ||
this._readyToSubscribe = true; | ||
const welcomeCallbackPromise = (_a = this._welcomeCallback) === null || _a === void 0 ? void 0 : _a.call(this); | ||
this._welcomeCallback = undefined; | ||
await welcomeCallbackPromise; | ||
this._reconnectInProgress = false; | ||
// TODO subscribe to all resting subscription objects | ||
break; | ||
} | ||
subscription._handleData(payload.event); | ||
break; | ||
} | ||
case 'reconnect': { | ||
await ((_b = this._connection) === null || _b === void 0 ? void 0 : _b.disconnect()); | ||
await this._connectTo(payload.session.reconnect_url); | ||
break; | ||
} | ||
case 'revocation': { | ||
const id = payload.subscription.id; | ||
const subscription = this._getCorrectSubscriptionByTwitchId(id); | ||
if (!subscription) { | ||
this._logger.error(`Revocation from unknown event received: ${id}`); | ||
case 'session_keepalive': { | ||
// TODO implement keepalive timeout | ||
break; | ||
} | ||
this._dropSubscription(subscription.id); | ||
this._dropTwitchSubscription(subscription.id); | ||
this.emit(this.onRevoke, subscription); | ||
this._logger.debug(`Subscription revoked by Twitch for event: ${id}`); | ||
break; | ||
case 'session_reconnect': { | ||
this._logger.info('Reconnect message received; initiating new connection'); | ||
this._reconnectInProgress = true; | ||
const oldConnection = this._connection; | ||
this._connection = undefined; | ||
this._welcomeCallback = async () => await oldConnection.disconnect(); | ||
await this._connectTo(payload.session.reconnect_url); | ||
break; | ||
} | ||
case 'notification': { | ||
const id = payload.subscription.id; | ||
const subscription = this._getCorrectSubscriptionByTwitchId(id); | ||
if (!subscription) { | ||
this._logger.error(`Notification from unknown event received: ${id}`); | ||
break; | ||
} | ||
subscription._handleData(payload.event); | ||
break; | ||
} | ||
case 'revocation': { | ||
const id = payload.subscription.id; | ||
const subscription = this._getCorrectSubscriptionByTwitchId(id); | ||
if (!subscription) { | ||
this._logger.error(`Revocation from unknown event received: ${id}`); | ||
break; | ||
} | ||
this._dropSubscription(subscription.id); | ||
this._dropTwitchSubscription(subscription.id); | ||
this.emit(this.onRevoke, subscription); | ||
this._logger.debug(`Subscription revoked by Twitch for event: ${id}`); | ||
break; | ||
} | ||
default: { | ||
this._logger.warn(`Unknown message type encountered: ${metadata.message_type}`); | ||
} | ||
} | ||
default: { | ||
this._logger.warn(`Unknown message type encountered: ${metadata.message_type}`); | ||
}); | ||
try { | ||
await this._connection.connect(); | ||
this._connecting = false; | ||
return; | ||
} | ||
catch (e) { | ||
if (!this._connecting) { | ||
return; | ||
} | ||
this._logger.debug(`Connection error caught: ${e.message}`); | ||
const secs = retryTimerGenerator.next().value; | ||
if (secs !== 0) { | ||
this._logger.info(`Retrying in ${secs} seconds`); | ||
} | ||
await (0, shared_utils_1.delay)(secs * 1000); | ||
this._logger.info('Trying to reconnect'); | ||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition | ||
if (!this._connecting) { | ||
return; | ||
} | ||
} | ||
}); | ||
await this._connection.connect(); | ||
} | ||
} | ||
async _disconnect() { | ||
this._connecting = false; | ||
if (this._connection) { | ||
const lastConnection = this._connection; | ||
this._connection = undefined; | ||
await lastConnection.disconnect(); | ||
} | ||
} | ||
async _reconnect() { | ||
void this._disconnect().catch((e) => this._logger.error(`Error while disconnecting for the reconnect: ${e.message}`)); | ||
await this._connect(); | ||
} | ||
}; | ||
@@ -135,0 +202,0 @@ tslib_1.__decorate([ |
{ | ||
"name": "@twurple/eventsub-ws", | ||
"version": "5.3.0-pre.2", | ||
"version": "5.3.0-pre.3", | ||
"publishConfig": { | ||
@@ -38,14 +38,14 @@ "access": "public" | ||
"@d-fischer/logger": "^4.0.0", | ||
"@d-fischer/shared-utils": "^3.2.0", | ||
"@d-fischer/shared-utils": "^3.4.0", | ||
"@d-fischer/typed-event-emitter": "^3.3.0", | ||
"@twurple/auth": "5.3.0-pre.2", | ||
"@twurple/common": "5.3.0-pre.2", | ||
"@twurple/eventsub-base": "5.3.0-pre.2", | ||
"@twurple/auth": "5.3.0-pre.3", | ||
"@twurple/common": "5.3.0-pre.3", | ||
"@twurple/eventsub-base": "5.3.0-pre.3", | ||
"tslib": "^2.0.3" | ||
}, | ||
"devDependencies": { | ||
"@twurple/api": "5.3.0-pre.2" | ||
"@twurple/api": "5.3.0-pre.3" | ||
}, | ||
"peerDependencies": { | ||
"@twurple/api": "5.3.0-pre.2" | ||
"@twurple/api": "5.3.0-pre.3" | ||
}, | ||
@@ -52,0 +52,0 @@ "files": [ |
@@ -1,7 +0,24 @@ | ||
# Twurple - EventSub base | ||
# Twurple - EventSub WebSocket listener | ||
A base package for the HTTP and WS EventSub listeners. Don't use this directly. | ||
[![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/twurple/twurple/blob/main/LICENSE) | ||
[![npm version](https://img.shields.io/npm/v/@twurple/eventsub-ws.svg?style=flat)](https://www.npmjs.com/package/@twurple/eventsub-ws) | ||
![PRs welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg) | ||
Listen to events on Twitch via their EventSub API using WebSockets. | ||
## Installation | ||
yarn add @twurple/api @twurple/eventsub-ws | ||
or using npm: | ||
npm install @twurple/api @twurple/eventsub-ws | ||
## Documentation | ||
A good place to start with this library is the [documentation](https://twurple.js.org) | ||
which also includes a complete reference of all classes and interfaces, as well as changes and deprecations between major versions. | ||
## If you're getting stuck... | ||
You can join the [Twitch API Libraries Discord Server](https://discord.gg/b9ZqMfz) and ask in `#twurple` for support. |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
22820
469
25
+ Added@twurple/api@5.3.0-pre.3(transitive)
+ Added@twurple/api-call@5.3.0-pre.3(transitive)
+ Added@twurple/auth@5.3.0-pre.3(transitive)
+ Added@twurple/common@5.3.0-pre.3(transitive)
+ Added@twurple/eventsub-base@5.3.0-pre.3(transitive)
- Removed@twurple/api@5.3.0-pre.2(transitive)
- Removed@twurple/api-call@5.3.0-pre.2(transitive)
- Removed@twurple/auth@5.3.0-pre.2(transitive)
- Removed@twurple/common@5.3.0-pre.2(transitive)
- Removed@twurple/eventsub-base@5.3.0-pre.2(transitive)
Updated@twurple/auth@5.3.0-pre.3
Updated@twurple/common@5.3.0-pre.3