Twitch App Access Tokens can do MORE!

In the beginning App Access Tokens could just load public data, that didn’t need any oAuth scopes, from Twitch API. And so any given developers project had to maintain a user access token(s) for users of their application.

Then extensions were introduced, bundled in Extensions are Subscriber support, allowing extensions and their EBS to read a given viewers subscription status to a channel, outside of the Extension JS Helper function to check the status in client side code. That then leaves how does an Extension EBS (aka Backend Service) perform the same call (since you should never trust the client) to perform the same check.

And so Extension generated App Access Tokens, could call Get Broadcaster Subscriptions. The first endpoint that is scoped that an app access token could load on behalf of a user.

Since then various aspects of Twitch Developer Tools have come and gone, we lost WebSub, and we lost PubSub, and we lost both (over time) in favour of EventSub. There is a bunch of other stuff that happened but the important part here is that with the loss of PubSub, Eventsub achieved “featured parity” with PubSub and a number of other things introduced Chat over Eventsub.

EventSub

An Extensions ability to grant the ability for an app access token to call subscriber information is baked into the “install an extension” work flow.

An extension that offers the Subscription and Chat permissions (chat here has it's own endpoint using JWT), but subscriptions grant the power for app access tokens to be able to read.

But EventSub introduced the dual (or even triple) legged auth approach.

First a user will need to perform a user oAuth loop, either Implicit, Authorization Code, or DCF (Device Code Flow). This then creates a link between the user and the ClientID along with the scopes requested.

Combine that with the recently added Get Authorization By User endpoint to check what scopes your ClientID has to a given user, you can check permissions before trying to create EventSub Subscriptions for Webhooks or Conduits.

So for eventsub the pattern is:

  • Check the conditions and scopes needed for the EventSub Type/Topic
  • For the user’s in the condition get that user to user authenticate with the needed scopes
  • Generate an active app access token (if you don’t have one already)
  • Create the Subscription

As an aside you can use “user.authorization.grant” to easily monitor for new user authentications and create subscriptions as a follow up, if say your user token gen is air gapped from your Eventsub handler. This is a pattern I use on the IGDB Extension to monitor for authentications and create subscriptions to stream.online/offline and channel.update (which yes are public topics but become cost 0 _if_ the user is authenticate to the Client ID, and I use the user.authorization.revoke to unsubscribe since I only want to keep cost 0 subscriptions), becuase the IGDB Extension config page launches an implicit flow since I don’t need the user token ever I just need to auth so creating one with a refresh token is overkill, but I digress….

So that briefly is how EventSub dual legged auth operates.

Chat Over EventSub

Then along came Chat over Eventsub and a bunch of separate topics essentially providing IRC chat as a already JSON-ifed consumable feed.

This also came with Conduits and the ability for chat bots to scale to as many channels as needed, because the broadcaster auth’s the chatbot to be present on their channel. It is similar to how adding Discord Bot to a Discord Server works from a broadcaster point of view.

But chat over eventsub specified two user ID’s in the condition. (which yes can be the same ID but we’ll skip that for simplicity).

So first a developer would

  • Create a Client ID (generally owned by their own Twitch profile but the other of a Client ID is irrelevant, it’s always whom auths to the client that is important)
  • Create a Twitch account with the username of the bot they want to use
  • User Authenticate the bot to the Client ID with the relevant scopes
  • Then authentication any number of broadcasters to the Client ID
  • Now you can rejoin all channels with one API request (update shard), great for restarts or outages, as restart the websocket, call update shard, and then the bot is back in all rooms, no more slow roll rejoins or rate limit joins/etc.

So that is briefly the triple legged auth of chat over eventsub.

So What about App Access Tokens on the API?

Send Chat Message was introduced at the same time as chat over eventsub and is the first “proper” endpoint that worked with App Access Tokens following prior auth from the user(s) involved, (the broadcaster_id and sender_id)

This meant that for “general” operations a chat bot still needed to load and maintain either the broadcasters user token for Start Commercial for example, or the bots own token for Banning a User from chat.

So a bot would be juggling and maintaining up to three tokens,

  • A broadcasters token for broadcaster based operations
  • A bot token for doing moderation actions (for example), or sending chat
  • An app access token for subscription management (or sending chat)

But, now, as of yesterday! For the most part that is no more!

Well Spill the Beans then!

TWENTY ONE API endpoints now support this pattern of using an App Access Token after performing prior user(s) authentication(s). Look for the Beta tag under the Authorization section of each endpoint.

Here is the list:

Anything marked in bold with a * has additional scope requirements compared to the user token version, please check the docs, but I suspect your bot will likely already have obtained the scopes.

So now a chat bot broadly doesn’t need to maintain or even keep on file a user access token for itself or the broadcaster, as most general operations a bot is gonna do will work using the App Access Token. This also comes with the bonus that a user access code flow token is only good for 4 hours but an app access token is good for much longer. (Remember to utilise the Validate Token Endpoint to check time remaining on a token)

I’ll note that “Get Clips Download” supports this pattern, but that API was introduced with the pattern.

But a Sadness

However, the obvious missing endpoint that doesn’t support this pattern is Send Chat Announcement which I hope is supported soon, I think that’s the only one I need to token juggle for at the moment.

Summary

Thats it! Go forth an App Access Token! And hopefully more of the API will support this pattern over time!

If you need further help please join as over on the Twitch Dev Discord or Support Forums!

Bonus Thought

Cronjobs that need to do something on multiple users/broadcasters, the script starts, loads the token, does the job and just switches the user ID no token switchin’!

OBS 32.0.0 the one that will be pain (sorta)

Well OBS 32.0.0 is out, and it’s gonna be pain, but in a good way, wait is pain ever a good thing.

It’s similar pain to the OBS 28 to anything after 28 move but PAIN all the same.

Along with all the new shiny features, like FiniteSingularity’s Plugin Manager, theres a bunch of changes under the hood! And the “Streamer Important” of these changes are what this blog post is gonna blog post about!

You can go read the full change log, over on the Releases Page, new shiny features at the top!

OBS 32's Plugin Manager

Look at this AWESOME Plugin manager. (OBS on line 2? Well thats Logitech’s Plugin apparently… go figure)

So What Broke

So since I have a finger in a LOT of pies, not just on Twitch but other realms, all within Streaming space, I’ll pull OBS Beta’s as they come and start testing. (Curse past me for setting my Mac OBS to always grab the betas… but whatever)

So, I pulled down OBS 32.0.0 beta 1 and it crashed on Launch. Here is the GitHub report yeah in the OP I was blaming EVERYONE and their dog (not Finite’s Dog). Took some better knowing peeps to trace it down and at the time I was busy and couldn’t do my own tracing. (Was too busy OMGWUT-ing to “upstairs” with “please don’t let customers go to 32.0.0 just yet)

Beta 2 fixed the crash on launch issue. But the problem here was the Streamdeck Plugin calling something that was removed from OBS in 32.0.0, since under the hood changes are dropping support for some legacy/older plugin hooks (or so I believe), and the SD plugin was invoking that and the fault wasn’t “safe caught” leading to OBS to crash instead of skip plugin loading.

Elgato was informed and so they chimed in with a “we are on it“, praise be to Videophile.

OBS 32.0.0 is General Release

So OBS 32.0.0 is now “released to the masses”

Heres what you need to do step by step, these steps are valid for OBS 31.x.x but shouldn’t be different for Older OBS, if on OBS Portable HAVE FUN! But you should know what you are doing here if you are running portable anyway…

  1. “File” -> “Show Settings Folder”
  2. Take a copy of the folder “basic” your scenes and profiles are in here and you should routinely back this up. (sure this doesn’t backup media assets (images/videos) that your scenes use thats up to you to sort but will preserve file paths and the like)
  3. Close OBS
  4. Next to update Streamdeck, first check that Streamdeck itself is “recent” should be running at least Software Version 7.0.0. Hit the “Cog” -> “General” -> “Check for Updates”. (At time of writing V7.0.1 is available) this step may also do the next step for you.
  5. If Streamdeck has updated it’ll restart, next Hit the “Cog” -> “Plugins” -> Scroll to OBS Studio, “Check for Update”, anything newer than 2.2.8 and you should be good. (Current version is 2.2.9.9), side note: most plugins have auto update, but some plugins you installed before that was a thing won’t so check those manually if you want.
  6. Now Open OBS and let OBS update to 32.0.0 (do “Help” -> “Check for Updates” if not auto prompted)
  7. Away you go!
Streamdeck showing running version 7.0.0 - Build 22005
OBS Studio plugin for Streamdeck showing "outdated" 2.2.8

Other Plugins

After Restarting OBS into 32.0.0 you’ll want to check for and update any other plugins you use/consider important before you go live for the first time.

Notably ratwithacompiler has put out an update for the Closed Captions Plugin, that a lot of people use (and the one I recommend to people for adding CC’s to your streams), during the OBS 32.0.0 beta phases, to fix a similar fix to what Elgato has needed to do.

But everyone installs different plugins, and currently Plugin Management is manual and not all plugins with hint you need to update. (Why “currently” you say? Well the new Plugin Manager is going to be looking at hinting for updates I understand! YAY)

Aitum Multistream will for example, but I don’t think they have any updates at time of writing. Since it works fine under OBS 32.0.0, I just know it has a visible thang for “update available”.

Post Amble

Just doing my OBS updates, nice alert 😀 I know which ones I need to go and do now, but if you have other plugins make sure to check manually in case they didn’t get reported as not loading!

The End of Twitch PubSub

Finally Twitch EventSub is at feature parity with third party supported/documented PubSub, that the end of PubSub has been announced.

In a forum post over on Twitch Developers it has been announced the schedule for the shutdown of Twitch PubSub for access by third party developers.

The final death date is April 14th 2025.

Which I guess means it’s time for me to complete my own migration from PubSub to EventSub. Which at least is JUST ABOUT planned….

At least for 90% of what I need it’s ALL USERNOTICE as an EventSub topic, which is this case is channel.chat.notification and then whatever I choose to go with for consuming cheering.

So then for anything else I’ll need that doesn’t exist, either from it not existing in the first place OR a undocumented topic I might be using, off to uservoice I’ll go!

Off hand I think there is only one thing but we’ll see when I work on and complete my migration! The only other barrier is gonna be authentication but I have authentication or can get it for everything I need.

It is worth noting that Extension PubSub is unaffected, as that for the most part is a completely different system that just shares the same name.

Topic Migration notes

Edit: I guess I should add some notes about migration

PubSubEventSub
channel-bits-events-v1.<channel_id>channel.cheer or channel.chat.message
channel-bits-events-v2.<channel_id>channel.cheer or channel.chat.message
channel-bits-badge-unlocks.<channel_id>channel.chat.message – the bits_badge_tier
channel-points-channel-v1.<channel_id>channel.channel_points_custom_reward.add
channel.channel_points_automatic_reward_redemption.add
channel-subscribe-events-v1.<channel_id>channel.chat.notification is what I will use for my use case of overlay/on stream alerts. Other type segregated topics exist

This topic also includes raids and a bunch of other event types
automod-queue.<moderator_id>.<channel_id>automod.message.hold and automod.message.update
chat_moderator_actions.<user_id>.<channel_id>channel.moderate
low-trust-users.<channel_id>.<suspicious_user_id>channel.suspicious_user.update and channel.suspicious_user.message
user-moderation-notifications.<current_user_id>.<channel_id>channel.chat.user_message_hold
whispers.<user_id>user.whisper.message

This Post was updated on February 2nd for additional migration paths and adding a link to the new PubSub Migration guide

The First shutdown window is February 10th at 7pm UTC for an hour!

DEATH TO KRAKEN

Todays the day!

Twitch’s Kraken API gets turned off in checks watch just over three hours!

I know I’m just about ready, but if you are not, and you already missed the shutdown test windows, heres some notes!

There is a v5 Migration guide – https://dev.twitch.tv/docs/api/migration

DO NOT FORGET this also effects the Extension API endpoints (such as Send PubSub message and Send Extension Chat message), this doesn’t affect the Extension JS Helper!

For most server to server operations you can use a Client Credentials Access Token

Most people will likely be using Ajax calls in website front ends and are now confused how to migrate, there are two routes for this problem. You either ask the user to login with Twitch to provide a token to use (this is how my GitHub examples work) but not very user friendly, you don’t want to prompt the user to login to your website JUST to show them if your stream is live or not. The second route is your frontend needs to call your server and the server uses a Client Credentials token to call the API with.

Alternatively if you are doing something “simple” such as “check my stream is live” then you should consider moving from API calls to EventSub instead. Then when a channel changes Title or stream status, Twitch will tell you in (near) real time. Collect and store that data in a database, and now your website doesn’t need to call the Twitch API each time someone loads your website.

For further help migrating or to join us for the DEATH TO KRAKEN party (bring your own confetti) join us on the Twitch Dev Third Party Developers Discord you can find a link on the Twitch Dev Support Page

For reference here is the Dev Forums link with the shutdown window timings: https://discuss.dev.twitch.tv/t/legacy-twitch-api-v5-i-e-kraken-shutdown-reminder-february-28-2022/36589 the shutdown originally announced back in July.

If a feature is missing or theres something you need thats not in Helix, don’t forget to file a UserVoice. I know a lot of people need/want Clip Offsets so make your voice heard with UserVoices and votes on existing issues. See also my mega UserVoice sheet. And checkout the GitHub for known issues.

Twitch Extensions Part 6 – Dev Environment Updates – Content Security Policy!

In part 5 we wrote about a suitable testing platform for building your extensions on, essentially we create a static content server, that mimics the Twitch CDN for testing with.

Twitch Announced on the Forums that they are revising the CSP (Content Security Policy) that extensions use to protect and control what can be loaded. I wrote about this in the previous blog post.

I’m currently waiting on a response from Twitch (via the forums) about any other changes to the CSP, but for now, you can test the changes today!

What Even is CSP

First lets do a quick explanation of what CSP, CSP is Content Security Policy, a browser technology to help control what a given Website can load and what browser functions are allowed.

The HTTP Content-Security-Policy response header allows web site administrators to control resources the user agent is allowed to load for a given page. With a few exceptions, policies mostly involve specifying server origins and script endpoints. This helps guard against cross-site scripting attacks (Cross-site_scripting).

https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy

You can read more about CSP and the various things it can do over on the MDN Web Docs. There is a lot more that can and can’t be done with CSP more than just controlling what content can be loaded from where, but for Twitch Extensions we only need to consider the parts of the Policy that affect Twitch Extensions.

Twitch Extension CSP Policy

Twitch is requiring Extension developer to declare the Connect, Img, and Media domains, which in the policy are connect-src, img-src and media-src. You can declare this in the Developer Console for a version of your extension, under the Capabilities tab.

The New Extension Dashboard fields
The new Extension Dashboard fields

Now, the items you enter here only apply when you are using Hosted Test (or release), since Hosted Test will use Twitch’s CDN, and thus Twitch’s Server which can load and use the relevant fields, but in localtesting (aka not the CDN) we need to set this up ourselves.

Local Testing a CSP

If you have been following this series, then you already have a Node/Express server that will run a static output for you. We can easily add CSP headers to this server using a module called Helmet, generally speaking it’s wise to consider adding helmet (or CSP Headers in general) to any website you run to protect your users, but I digress!

So, how to set this up for Testing with.

Normally I’d say, on server start call the API to get the current extension settings from the console, however, the API at this time has not been updated to include the new fields, I raised a UserVoice requesting the new fields be added to the endpoints. And you can upvote that here.

So for now, we’ll need to populate the CSP for Helmet manually.

Configuring Helmet for CSP

The first thing I did was look at a released extension to see what the current CSP is, which I then split out into a object for configuring Helmet with. Then I looked at what the rig needs, and then looked at what you need to add to correctly simulate a CSP.

The base CSP for a Twitch Extension is, here twitch.client_id is loaded from an external config file, and represents the location that Hosted Test and Release use to host your files. Which I’ll touch on later.

/*
Current base CSP rules subject to change

See:
https://discuss.dev.twitch.tv/t/new-extensions-policy-for-content-security-policy-csp-directives-and-timeline-for-enforcement/33695/2

This example is based off a live extension
*/

let contentSecurityPolicy = {
    directives: {
        defaultSrc: [
            "'self'",
            `https://${twitch.client_id}.ext-twitch.tv`
        ],
        connectSrc: [
            "'self'",
            `https://${twitch.client_id}.ext-twitch.tv`,
            'https://extension-files.twitch.tv',
            'https://www.google-analytics.com',
            'https://stats.g.doubleclick.net'
        ],
        fontSrc:    [
            "'self'",
            `https://${twitch.client_id}.ext-twitch.tv`,
            'https://fonts.googleapis.com',
            'https://fonts.gstatic.com'
        ],
        imgSrc:     [
            "'self'",
            'data:',
            'blob:'
        ],
        mediaSrc:   [
            "'self'",
            'data:',
            'blob:'
        ],
        scriptSrc:  [
            "'self'",
            `https://${twitch.client_id}.ext-twitch.tv`,
            'https://extension-files.twitch.tv',
            'https://www.google-analytics.com',
            'https://stats.g.doubleclick.net'
        ],
        styleSrc:   [
            "'self'",
            "'unsafe-inline'",
            `https://${twitch.client_id}.ext-twitch.tv`,
            'https://fonts.googleapis.com'
        ],

        frameAncestors: [
            'https://supervisor.ext-twitch.tv',
            'https://extension-files.twitch.tv',
            'https://*.twitch.tv',
            'https://*.twitch.tech',
            'https://localhost.twitch.tv:*',
            'https://localhost.twitch.tech:*',
            'http://localhost.rig.twitch.tv:*'
        ]
    }
}

const helmet = require('helmet');
/*
You can use Security Headers to test your server, if this server is web accessible
https://securityheaders.com/
It'll test that your CSP is valid.
Best testing done with an extension, on Twitch or in the rig!
*/

console.log('Going to use the following CSP', contentSecurityPolicy);

app.use(helmet({
    contentSecurityPolicy
}));

This I add after app.listen and before anything else! It does need to go before your app.use for express.static

This will configure your test server to use the base/default CSP. And will log it out the full CSP to the console when you start the server.

The Extension Developer Rig

So the next step is how to enable your test server to work in the Twitch Extension Developer Rig. I don’t often use the rig, but it’s handy for spot testing views and mobile when I don’t have my phone handy (or the Extension has not been iOS allow listed yet!)

The Extension Rig is built in Electron, which means it will include calls to file and in testing it spot calls some other things.

For the rig I add the following rules, which I append to the default CSP using a Config Switch.

/*
should we enable the Rig?

The rig being an electron app, will call some other things
As well as having a file:// based parent
*/
if (csp_options.enable_rig) {
    let rig_sources = {
        connectSrc: [
            'wss://pubsub-edge.twitch.tv'
        ],
        frameAncestors: [
            'http://localhost:*',
            'file://*',
            'filesystem:'
        ]
    }

    // append these to the CSP
    for (let sourceType in rig_sources) {
        for (let x=0;x<rig_sources[sourceType].length;x++) {
            contentSecurityPolicy.directives[sourceType].push(rig_sources[sourceType][x]);
        }
    }
}

Nothing to silly there, but important if you are testing in the rig. Only enable this in your server when rig testing not testing on the Twitch website, as it’s overly permissive and might catch you out later.

My Sources

The final thing to do is to setup your sources, now this gets a little weird, as a valid CSP rule can omit the schema of the URL (see note).

For this example/setup we are adding the content domains to all three CSP directives. Using this example you can adjust and modify this as granularly as you want.

/*
Did we configure places that we can/may load media from
And yes we are just gonna glob them to all three groups
For example purposes
*/
csp_options.content_domains.forEach(domain => {
    contentSecurityPolicy.directives.imgSrc.push(domain);
    contentSecurityPolicy.directives.mediaSrc.push(domain);
    contentSecurityPolicy.directives.connectSrc.push(domain);
});

Note: In testing browsers will not enable/allow WSS if you declare a schema-less domain of www.example.com. So if you want WSS you need to declare it explicitly, for this I declare wss://www.example.com and https://www.example.com in the rule (not the lack of a trailing /).

I configure these schema+domains in an external configuration file for the server. Here is an example config.json:

{
    "listen": 8050,

    "csp_options": {
        "enable_rig": true,
        "report_uri": false,
        "ebs_domain": "myebs.com",
        "content_domains": [
            "https://mywebsite.com",
            "wss://mywebsite.com"
        ]
    },

    "twitch": {
        "client_id": "abcdefg"
    }
}

EBS?

If your extension utilizes an EBS you’ll need to declare that and add it to your connect-src, however if you also load images from your EBS you can skip this step.

I generally put my images and assets on a seperate server to my EBS, but for test purposes, this server example adds the EBS domain to all three declarations, for both schemas:

/*
Did we configure an EBS to call
*/
if (csp_options.ebs_domain) {
    console.log('Appending EBS Domain');
    let ebs_rules = {
        imgSrc: [
            'https://' + csp_options.ebs_domain,
            'wss://' + csp_options.ebs_domain
        ],
        mediaSrc: [
            'https://' + csp_options.ebs_domain,
            'wss://' + csp_options.ebs_domain
        ],
        connectSrc: [
            'https://' + csp_options.ebs_domain,
            'wss://' + csp_options.ebs_domain
        ]
    }

    for (let sourceType in ebs_rules) {
        for (let x=0;x<ebs_rules[sourceType].length;x++) {
            contentSecurityPolicy.directives[sourceType].push(ebs_rules[sourceType][x]);
        }
    }
}

Full Example!

I put all of this together as a full example over on my GitHub. See Part 6 of the repository. This provides a “rig” as described in Part 5 but with the additional CSP Fields included.

To set this up do as follows

  • Download the Example from GitHub
  • Copy config_sample.json to config.json
  • Populate the twitch->client_id with your Extension Client ID
  • Revise the listen port if needed
  • Configure Your CSP options as needed, add the content domains as needed. And you EBS domain as needed.
  • If you load content from your EBS domain, set the ebs_domain to false, to avoid a duplicate declaration of a domain, or do not include your ebs_domain in your content_domains

Once you have setup the server, you can the test your Rig via Security Headers which will test that your CSP is valid, however this only works if your Test Server is accessible from the internet! Which if you follow Part 5’s note will be for SSL testing purposes! And will only test that your CSP looks correct, not that it functions as intended!

Then you can move on to testing your Extension and check that your CSP works as intended, then you do not have to move to hosted test and back to test changes to your CSP!

If/when the API is updated to return the new fields, I’ll add a part 6.5 (probably) which will use the API to get the details instead. Sods law you’ll add a domain to your Test Rig/Server, and then forget to add the same domain to your Capabilities tab!

DEADLINE

Twitch will begin to Enforce the new CSP policy on January 25th.

Twitch closes for the holidays between Friday, 12/17/21 – Monday, 1/3/22. Twitch requests that Developers submit their extensions for review no later than Wednesday, 15th of December at 3PM PST.

Upcoming Winter Break

Attention, developers! Please note that the review team will be observing a winter holiday break from Friday, 12/17/21 – Monday, 1/3/22 and will not be performing Organization, Game, Chatbot Verification, or Extension reviews during this period of time. If you need a review completed prior to the holiday break, please submit your review request by no later than Wednesday, 12/15 at 3PM PST. Thank you for your understanding & happy holidays!

From the Twitch Developers Console

ONE MORE THING: Report URI

Well what about the report_uri, that you saw in the config.json example?

Well CSP provides a method to report CSP errors to a defined HTTPS POST endpoint. So whenever a CSP error occurs it can be reported to that HTTPS URL, very handy to help debug issues.

So if you configure your report_uri to be the same URL as your Extension Test rig, but with /csp/ on the end, so if your rig is at https://mytestrig.com/ then your CSP Report URI is https://mytestrig.com/csp/

You can capture and log these reports, for Express you will need to use the following code snippet, please note that a JSON payload is posted but using an alternative content-type, so you need to tell express.json to trigger on that content-type of application/csp-report

/*
This will capture any CSP Report and dump log it to console
*/
app.post('/csp/', express.json({
    type: 'application/csp-report'
}), (req,res) => {
    console.log(req.body);

    res.send('Ok');
});

The report-uri documentation over on MDN includes a PHP example, if that is your cup of tea!

An Update! Easier Development!

Added a small update to this post for easier testing with, first I took the entire CSP component and seperated it into a NPM module for easier usage and configuration.

The module, `twitchextensioncsp` can be found over on NPM and on GitHub and essentially just wraps Helmet for you and passes in the CSP Configuration with much less copy/paste between extensions if you are working on multiples.

And the Example simple server (remove the “build” system) is at Part 6.5 on the GitHub Repository

For “ease” of use heres an “quick” static Express Server implementing the module, it will do the following:

  • Create an Express Server on port 8050
  • Invoke twitchextensioncsp
  • Enable the Extension CSP to support the Twitch Extensions Rig
  • Add Img and Media and connect example domains
  • Static mount the build directory onto extension so your testing base URI is http://localhost:8050/extension/ swap as needed depending on your SSL solution
const express = require('express');
const app = express();

app.listen(8050, function () {
    console.log('booted express on 8050');
});

const twitchextensioncsp = require('twitchextensioncsp');
app.use(twitchextensioncsp({
    clientID: 'abcdefg123456'
    enableRig: true,
    imgSrc: [
        'https://images.example.com'
    ],
    mediaSrc: [
        'https://videos.example.com'
    ],
    connectSrc: [
        'https://api.example.com'
    ]
}));

app.use('/extension/', express.static(__dirname + '/build/'));

If you refer to the README for twitchextensioncsp there are a handful of quick start examples for the CSP setup. As you do need to explicitly declare the Twitch CDN and Twitch API if you wish to use those in your Extension frontend!