npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@nestjs-twurple/eventsub-http

v0.1.1

Published

A NestJS wrapper for @twurple/eventsub-http package

Downloads

29

Readme

NestJS Twurple EventSub HTTP Listener

A NestJS wrapper for @twurple/eventsub-http package.

This module can be used alone or in combination with other @nestjs-twurple modules.

[!IMPORTANT] These packages require twurple version 7.0 or higher.

[!WARNING] Twurple EventSub HTTP module does NOT support Fastify platform because underlying @twurple/eventsub-http applies Express-like middleware to handle requests. To make it work, your app must be based on Express platform.

Table of Contents

Installation

This module can be used in combination with @nestjs-twurple/auth and @nestjs-twurple/api modules. Install them if necessary.

yarn:

yarn add @nestjs-twurple/eventsub-http @twurple/auth @twurple/api @twurple/eventsub-http

npm:

npm i @nestjs-twurple/eventsub-http @twurple/auth @twurple/api @twurple/eventsub-http

Usage

For basic information, check out the general documentation at the root of the repository @nestjs-twurple.

Also take a look at official @twurple/eventsub-http reference and guides: Setting up an EventSub listener.

Import and Registration

The module must be register either with register or registerAsync static methods.

To create an EventSub HTTP listener, you must provide TwurpleEventSubHttpOptions. Some options below are directly extended from the EventSubMiddlewareConfig interface provided by @twurple/eventsub-http package, so the example below may become outdated at some point.

interface TwurpleEventSubHttpOptions {
	applyHandlersOnModuleInit?: boolean;
	// The options below directly extended from EventSubMiddlewareConfig
	apiClient: ApiClient;
	hostName: string;
	secret: string;
	legacySecrets?: boolean;
	strictHostCheck?: boolean;
	pathPrefix?: string;
	usePathPrefixInHandlers?: boolean;
	logger?: Partial<LoggerOptions>;
}

The best way to register the module is to use it with @nestjs-twurple/auth and @nestjs-twurple/api packages:

import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { TWURPLE_AUTH_PROVIDER, TwurpleAuthModule } from '@nestjs-twurple/auth';
import { TWURPLE_API_CLIENT, TwurpleApiModule } from '@nestjs-twurple/api';
import { TwurpleEventSubHttpModule } from '@nestjs-twurple/eventsub-http';
import { AuthProvider } from '@twurple/auth';
import { ApiClient } from '@twurple/api';

@Module({
	imports: [
		ConfigModule.forRoot({ isGlobal: true }),
		TwurpleAuthModule.registerAsync({
			isGlobal: true,
			inject: [ConfigService],
			useFactory: (configService: ConfigService) => {
				return {
					type: 'refreshing',
					clientId: configService.get('TWITCH_CLIENT_ID'),
					clientSecret: configService.get('TWITCH_CLIENT_SECRET'),
					onRefresh: async (userId, token) => {
						// Handle token refresh
					}
				};
			}
		}),
		TwurpleApiModule.registerAsync({
			isGlobal: true,
			inject: [TWURPLE_AUTH_PROVIDER],
			useFactory: (authProvider: AuthProvider) => {
				return { authProvider };
			}
		}),
		TwurpleEventSubHttpModule.registerAsync({
			isGlobal: true,
			inject: [ConfigService, TWURPLE_API_CLIENT],
			useFactory: (configService: ConfigService, apiClient: ApiClient) => {
				return {
					// `true` is the default value here
					applyHandlersOnModuleInit: true,
					// Access ApiClient instance from the TwurpleApiModule
					apiClient,
					secret: configService.get('TWITCH_EVENTSUB_SECRET'),
					hostName: configService.get('HOST_NAME'),
					pathPrefix: configService.get<string>('TWITCH_EVENTSUB_PATH'),
					legacySecrets: false
				};
			}
		})
	]
})
export class AppModule {}

Using the EventSubMiddleware

The module internally creates an EventSubMiddleware instance. You can inject it anywhere you need it using the @InjectEventSubHttpListener() decorator. For example, you can create TwitchEventSubService provider where you can listen to EventSub events and manage subscriptions:

import { Injectable } from '@nestjs/common';
import { EventSubMiddleware } from '@twurple/eventsub-http';
import { InjectEventSubHttpListener } from '@nestjs-twurple/eventsub-http';

@Injectable()
export class TwitchEventSubService {
	constructor(@InjectEventSubHttpListener() private readonly _eventSubListener: EventSubMiddleware) {}
}

Important Notes

Applying Express App

EventSubMiddleware requires you to pass the Express app so that it can register the handlers. By default, the TwurpleEventSubHttpModule will automatically get the underlying Express app and pass it to the apply() method. If you want to apply manually, you should pass applyHandlersOnModuleInit: false in the module options. Then you can access the underlying Express app using HttpAdapterHost from @nestjs/core:

import { Injectable } from '@nestjs/common';
import { HttpAdapterHost } from '@nestjs/core';
import { InjectEventSubHttpListener } from '@nestjs-twurple/eventsub-http';
import { EventSubMiddleware } from '@twurple/eventsub-http';

@Injectable()
export class TwitchEventSubService {
	constructor(
		private readonly _httpAdapterHost: HttpAdapterHost,
		@InjectEventSubHttpListener() private readonly _eventSubListener: EventSubMiddleware
	) {
		// Get Express app from HTTP adapter
		const app = this._httpAdapterHost.httpAdapter.getInstance();

		// Pass the app to the #apply() method
		this._eventSubListener.apply(app);
	}
}

Making the Listener Ready to Subscribe

Finally, before subscribing to events, you need to mark listener as ready for subscribing to events by calling markAsReady() method. Note that this method must be called after NestJS started listening for connections. In other words, you must call this method after await app.listen() in your boostrap function.

You can do this directly in the bootstrap function (usually main.ts file):

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { TwitchEventSubService } from './twitch-eventsub';

async function bootstrap() {
	const app = await NestFactory.create(AppModule);
	await app.listen(3000);

	// This is a custom provider that we created specially
	// for handling EventSub events and manage subscription
	const twitchEventSubService = app.get(TwitchEventSubService);
	await twitchEventSubService.start();
}

bootstrap();
import { Injectable } from '@nestjs/common';
import { InjectEventSubHttpListener } from '@nestjs-twurple/eventsub-http';
import { EventSubMiddleware } from '@twurple/eventsub-http';

@Injectable()
export class TwitchEventSubService {
	constructor(@InjectEventSubHttpListener() private readonly _eventSubListener: EventSubMiddleware) {}

	// We call this method after app start
	async start(): Promise<void> {
		// Mark EventSub as ready
		await this._eventSubListener.markAsReady();

		// You probably want also inject a service into this provider that manages
		// users to get the data that is required for subscriptions, such their user ID
		const userId = '<USER_ID>';
		const onlineSubscription = this._eventSubListener.onStreamOnline(userId, evt => {
			console.log(`${evt.broadcasterDisplayName} just went live!`);
		});
	}
}

An even better way is to use events:

import { NestFactory } from '@nestjs/core';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { AppModule } from './app.module';

async function bootstrap() {
	const app = await NestFactory.create(AppModule);
	await app.listen(3000);

	// Get event emitter from the DI container
	const eventEmitter = app.get(EventEmitter2);
	// Emit start event
	// You'd better use constants to avoid misspelling
	eventEmitter.emit(APP_START_EVENT);
}

bootstrap();
import { Injectable } from '@nestjs/common';
import { OnEvent } from '@nestjs/event-emitter';
import { InjectEventSubHttpListener } from '@nestjs-twurple/eventsub-http';
import { EventSubMiddleware } from '@twurple/eventsub-http';

@Injectable()
export class TwitchEventSubService {
	constructor(@InjectEventSubHttpListener() private readonly _eventSubListener: EventSubMiddleware) {}

	// Handle app start event
	@OnEvent(APP_START_EVENT)
	private async _onAppStart(): Promise<void> {
		await this.start();
	}

	async start(): Promise<void> {
		// Mark EventSub as ready
		await this._eventSubListener.markAsReady();

		// You can inject a service that manages users to get the data
		// that is required for subscriptions, such as their IDs
		const userId = '<USER_ID>';
		const onlineSubscription = this._eventSubListener.onStreamOnline(userId, evt => {
			console.log(`${evt.broadcasterDisplayName} just went live!`);
		});
	}
}

You probably also want to store created subscription in a map/object/array to be able to stop them at any time:

await onlineSubscription.stop();

[!WARNING] Twurple's EventSub event handler expects non-consumed body. You should disable global body parser middleware.

async function bootstrap() {
	const app = await NestFactory.create(AppModule, { bodyParser: false });
	await app.listen(3000);
}

Dumb workaround:

import { ConfigService } from '@nestjs/config';
import { json } from 'body-parser';

async function bootstrap() {
	const app = await NestFactory.create(AppModule, { bodyParser: false });

	// Apply body parser for all routes except for EventSub path
	// It must be the same path as you specified in TwurpleEventSubHttpModule options
	app.use((req: Request, res: Response, next: NextFunction) => {
		if (req.path.includes('twitch/eventsub/webhooks/callback')) {
			next();
		} else {
			json()(req, res, next);
		}
	});

	// ----- OR ------ //

	// You can get config service if you set the path using an env variable
	const configService = app.get(ConfigService);
	const eventSubPath = configService.get('TWITCH_EVENTSUB_PATH');

	app.use((req: Request, res: Response, next: NextFunction) => {
		if (req.path.includes(eventSubPath)) {
			next();
		} else {
			json()(req, res, next);
		}
	});

	await app.listen(3000);
}