{"id":51890,"date":"2021-02-26T20:21:46","date_gmt":"2021-02-26T20:21:46","guid":{"rendered":"https:\/\/barrycarlyon.co.uk\/wordpress\/?p=51890"},"modified":"2021-02-28T21:05:46","modified_gmt":"2021-02-28T21:05:46","slug":"twitch-extensions-part-3","status":"publish","type":"post","link":"https:\/\/barrycarlyon.co.uk\/wordpress\/2021\/02\/26\/twitch-extensions-part-3\/","title":{"rendered":"Twitch Extensions: Part 3 \u2013 The Architecture of an Extension"},"content":{"rendered":"<p>In <a href=\"\/wordpress\/2021\/02\/19\/twitch-extensions-part-2\/\">Part 2<\/a> we spoke a fair bit about <i>&#8220;the path and file name of the HTML file you wish to load, it is a relative path to the Testing Base URI\/final upload URL&#8221;<\/i>, and this week we will cover why I mentioned that every time!<\/p>\n<p>So, this week The Architecture of a Twitch Extension!<\/p>\n<figure style=\"width: 450px\" class=\"wp-caption aligncenter\"><a href=\"https:\/\/en.wikipedia.org\/wiki\/Architecture\" target=\"_blank\"><img decoding=\"async\" src=\"https:\/\/upload.wikimedia.org\/wikipedia\/commons\/thumb\/c\/c6\/All_Gizah_Pyramids-3.jpg\/1920px-All_Gizah_Pyramids-3.jpg\" width=\"450px\" alt=\"An example of Architecture from Wikipedia, The Pyramids at Gizah - &copy; CC BY-SA 2.0\" class=\"size-large\" \/><\/a><figcaption class=\"wp-caption-text\">An example of Architecture from Wikipedia, The Pyramids at Gizah &#8211; &copy; CC BY-SA 2.0<\/figcaption><\/figure>\n<h3>Basic Architecture<\/h3>\n<p>We have covered before that <i>essentially<\/i> a Twitch Extension is a &#8220;Website&#8221; that is iFrame&#8217;ed onto the Twitch Page, into an integration slot, but what does that actually look like?<\/p>\n<figure style=\"width: 450px\" class=\"wp-caption aligncenter\"><a href=\"https:\/\/dev.twitch.tv\/docs\/extensions\/required-technical-background\"><img data-recalc-dims=\"1\" decoding=\"async\" src=\"https:\/\/i0.wp.com\/media.forgecdn.net\/attachments\/215\/369\/extensionsguide-architecture.png?w=450&#038;ssl=1\"  alt=\"The Twitch Architecture Overview\" class=\"size-large\" \/><\/a><figcaption class=\"wp-caption-text\">The Twitch Architecture Overview &#8211; According to Twitch<\/figcaption><\/figure>\n<p>This is the &#8220;stack&#8221; that represents how an Extension is loaded, to illustrate this, we&#8217;ll use <a href=\"https:\/\/dashboard.twitch.tv\/extensions\/q6gmlap07mpxekhpspevz2sq5xjth7\" target=\"_blank\">FlightSimTrack<\/a> installed to the <a href=\"https:\/\/www.twitch.tv\/flightsimtrack\" target=\"_blank\">FlightSimTrack Twitch channel<\/a><\/p>\n<ul>\n<li>First we have the Twitch Channel page &#8211; the &#8220;Browser&#8221; above<\/li>\n<li>Then the Twitch Extension &#8220;supervisor&#8221;, this basically handles any handshaking between the outer Twitch and the inner Extension, we can&#8217;t cover more about it as it&#8217;s not documented anywhere, and that is my theory on what it does! It is mentioned in passing in the <a href=\"https:\/\/dev.twitch.tv\/docs\/extensions#ext-twitchtv\" target=\"_blank\">documentation<\/a><\/li>\n<li>Then the Extension itself &#8211; the &#8220;iFrame&#8221; above<\/li>\n<\/ul>\n<h4>Relative path? WHY?<\/h4>\n<p>So why &#8220;relative path&#8221;? Well here is what the URL to the Panel looks like for FlightSimTrack<\/p>\n<p><code>https:\/\/q6gmlap07mpxekhpspevz2sq5xjth7.ext-twitch.tv\/q6gmlap07mpxekhpspevz2sq5xjth7\/ 0.0.2\/78753d6eeea69840398d8e46ff018e3b\/panel\/index.html?anchor=panel&language=en&locale=en-US&mode=viewer&state=released&platform=web<\/code><\/p>\n<p>The first thing we&#8217;ll notice is that the <code>index.html<\/code> is in a sub folder of the domain. And <strong>NOW<\/strong> you know why we said <i>&#8220;the path and file name of the HTML file you wish to load, it is a relative path to the Testing Base URI\/final upload URL&#8221;<\/i> <strong>every<\/strong>, <strong>single<\/strong>, <strong>time<\/strong>. And it&#8217;s a common hiccup that Extension Developers run into, a number of people come into the Developer Discord needing extension help, and they either made a mistake packaging the Zip for upload (we&#8217;ll cover Upload procedure in a future post), or the developer has used an &#8220;absolute&#8221; link to a JS\/CSS file (starting <code>\/style.css<\/code> or <code>https:\/\/example.com\/style.css<\/code> for example, instead of <i>just<\/i> <code>style.css<\/code>).<\/p>\n<p>But lets break down this URL, into it&#8217;s parts<\/p>\n<h4>URL breakdown<\/h4>\n<p><code>https:\/\/extClientID.ext-twitch.tv\/extClientID\/Version\/md5OfZip\/path_to_file_for_integration_point.html?querystring<\/code><\/p>\n<p>We&#8217;ll cover the Query string parameters more in a future post, but you can read about them in the <a href=\"https:\/\/dev.twitch.tv\/docs\/extensions\/reference#client-query-parameters\" target=\"_blank\">Extension Documentation Reference<\/a><\/p>\n<p>The most important one to be aware of right now is <code>anchor<\/code> which tells you which integration slot this is. In this example it&#8217;s a <code>panel<\/code>, and FlightSimTrack&#8217;s panel is configured to load <code>panel\/index.html<\/code> for this integration slot.<\/p>\n<ul>\n<li><code>extClientID<\/code> is obvious, it&#8217;s the ClientID of your Extension<\/li>\n<li>Extensions live on the domain <code>extClientID.ext-twitch.tv<\/code>, why a different domain for each extension and separate to Twitch? This prevents any issues with Cookies from the &#8220;main&#8221; Twitch Website, so there won&#8217;t be any session hijacking or other crazy things, you can read a little more on that in the <a href=\"https:\/\/dev.twitch.tv\/docs\/extensions#ext-twitchtv\" target=\"_blank\">documentation<\/a><\/li>\n<li>Version &#8211; the version of the extension that these files are for<\/li>\n<li>md5 of the zip file that was uploaded, during testing you might go to Hosted test\/local test (two Stages of the Extension Lifecycle that we&#8217;ll cover next week) a few times, so the md5 will change, this is a easy &#8220;cache defeat&#8221; when you are Hosted testing<\/li>\n<li>Final part is the path and html file you specified in the console to load for this integration slot<\/li>\n<\/ul>\n<figure id=\"attachment_51891\" aria-describedby=\"caption-attachment-51891\" style=\"width: 586px\" class=\"wp-caption aligncenter\"><a href=\"https:\/\/i0.wp.com\/barrycarlyon.co.uk\/wordpress\/wp-content\/uploads\/2021\/02\/extensions_part_3_flightsimtrack_asset_hosting.png?ssl=1\" rel=\"attachment wp-att-51891\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" src=\"https:\/\/i0.wp.com\/barrycarlyon.co.uk\/wordpress\/wp-content\/uploads\/2021\/02\/extensions_part_3_flightsimtrack_asset_hosting.png?resize=586%2C385&#038;ssl=1\" alt=\"Asset Hosting for FlightSimTrack\" width=\"586\" height=\"385\" class=\"size-large wp-image-51891\" srcset=\"https:\/\/i0.wp.com\/barrycarlyon.co.uk\/wordpress\/wp-content\/uploads\/2021\/02\/extensions_part_3_flightsimtrack_asset_hosting.png?resize=1024%2C672&amp;ssl=1 1024w, https:\/\/i0.wp.com\/barrycarlyon.co.uk\/wordpress\/wp-content\/uploads\/2021\/02\/extensions_part_3_flightsimtrack_asset_hosting.png?resize=300%2C197&amp;ssl=1 300w, https:\/\/i0.wp.com\/barrycarlyon.co.uk\/wordpress\/wp-content\/uploads\/2021\/02\/extensions_part_3_flightsimtrack_asset_hosting.png?resize=768%2C504&amp;ssl=1 768w, https:\/\/i0.wp.com\/barrycarlyon.co.uk\/wordpress\/wp-content\/uploads\/2021\/02\/extensions_part_3_flightsimtrack_asset_hosting.png?w=1778&amp;ssl=1 1778w, https:\/\/i0.wp.com\/barrycarlyon.co.uk\/wordpress\/wp-content\/uploads\/2021\/02\/extensions_part_3_flightsimtrack_asset_hosting.png?w=1680&amp;ssl=1 1680w\" sizes=\"auto, (max-width: 586px) 85vw, 586px\" \/><\/a><figcaption id=\"caption-attachment-51891\" class=\"wp-caption-text\">Asset Hosting for FlightSimTrack<\/figcaption><\/figure>\n<p>The URL structure is the same for <i>every<\/i> integration point, and all files are considered &#8220;public&#8221;, which is something to consider when building in things for Channel Moderators to use.<\/p>\n<p>That about covers everything for the frontend<\/p>\n<h3>Well, what about the Backend?<\/h3>\n<p>Oh you want the backend to do you?<\/p>\n<figure style=\"width: 450px\" class=\"wp-caption aligncenter\"><a href=\"https:\/\/dev.twitch.tv\/docs\/extensions\/required-technical-background\"><img data-recalc-dims=\"1\" decoding=\"async\" src=\"https:\/\/i0.wp.com\/media.forgecdn.net\/attachments\/215\/369\/extensionsguide-architecture.png?w=450&#038;ssl=1\"  alt=\"The Twitch Architecture Overview\" class=\"size-large\" \/><\/a><figcaption class=\"wp-caption-text\">The Twitch Architecture Overview &#8211; According to Twitch again!<\/figcaption><\/figure>\n<p>First of all it&#8217;s important to note than a Backend, also referred to as <strong>EBS<\/strong> or <strong>Extension Backend Service<\/strong>, is entirely optional, depending on your Extension, what\/how it functions or what it does, it might not even need an EBS, either from calling Public API&#8217;s or from it being completely self contained.<\/p>\n<h4>The SSL Requirement<\/h4>\n<p>But for those Extensions that need an EBS, there is <i>essentially<\/i> one rule, <strong>it must serves it&#8217;s contents over SSL<\/strong>, this isn&#8217;t just good for Security, but is required as the Twitch Website is over SSL and browsers will reject Mixed Content (loading non SSL content from SSL).<\/p>\n<p>So<\/p>\n<ul>\n<li>Loading from the EBS? Needs to be https:\/\/urltoyourebs\/<\/li>\n<li>Loading from a Public API? Needs to be https:\/\/someapi\/<\/li>\n<li>Loading from a Websocket? Needs to be wss:\/\/somesocket\/<\/li>\n<li>Loading an image from a CDN? Needs to be https:\/\/somecdn\/image.png<\/li>\n<\/ul>\n<p>To illustrate some examples:<\/p>\n<ul>\n<li>FlightSimTrack viewer, nothing at all (images are on the Twitch Extensions CDN and map tiles come from map tile vendors over SSL)<\/li>\n<li>FlightSimTrack config, EBS lives at <code>https:\/\/twitch.extensions.barrycarlyon.co.uk\/<\/code><\/li>\n<li>CohhCarnage Panel Extension, uses an API at <code>https:\/\/extensions.cohhilition.com\/<\/code> and a socket at <code>wss:\/\/extensions.cohhilition.com\/<\/code><\/li>\n<li>Dropped Frames video Extension, no API or socket at all, but images from a CDN, usually Twitter\/Twitch avatars directly from Twitter\/Twitch over HTTPs, it receives from Twitch PubSub only.<\/li>\n<\/ul>\n<p>You can see that they <i>all<\/i> all work over endpoints\/routes protected by SSL.<\/p>\n<p>We&#8217;ll cover how to build a &#8220;custom&#8221; EBS, and verifying identity in a future post, this week it&#8217;s just &#8220;Architecture&#8221;, the long and short of it, is you are basically just building a Custom API to interact between your front end and backend. A common way I describe Extensions, is as follows<\/p>\n<\/figure>\n<blockquote><p>Extensions are a website, where the front end is on a different server to the backend, and you can&#8217;t do server side rendering<\/blockquote><figcaption>&#8211; Barry Carlyon on &#8220;Well what is an Extension anyway!&#8221;<\/figcaption><\/figure>\n<h4>Languages<\/h4>\n<p>Whilst a Twitch extension is &#8220;limited&#8221; to HTML and JavaScript, your EBS\/Backend API can be in any language you want. But when it comes to examples, samples and boilerplates you&#8217;ll generally find them in JavaScript (via NodeJS) or Go, (since Twitch is a &#8220;Go House&#8221;). We&#8217;ll be using NodeJS in this series, merely because it&#8217;s potentially the easiest for people to understand and convert knowledge between different languages.<\/p>\n<p>If you want to jump ahead, you can check out the <a href=\"https:\/\/dev.twitch.tv\/docs\/extensions\/rig\" target=\"_blank\">Developer Rig<\/a>, which will provide you access to a number of Extension Examples, which are also available on the <a href=\"https:\/\/github.com\/twitchdev\/?q=extensions\" target=\"_blank\">TwitchDev GitHub<\/a>, or my own <a href=\"https:\/\/github.com\/BarryCarlyon\/twitch_profile_extension\" target=\"_blank\">Twitch Profile Extension<\/a> (which demonstrates how to call the Twitch API via an &#8220;EBS&#8221; proxy), these examples are designed to highlight a specific function of Twitch Extensions, rather than a &#8220;practical&#8221; example.<\/p>\n<h4>Real Certificates only<\/h4>\n<p>Naturally this needs to use &#8220;real&#8221; SSL Certificates, as apposed to self signed, which is &#8220;bearable&#8221; for testing with. Generally you&#8217;ll find Extension Developers will whip out <a href=\"https:\/\/letsencrypt.org\/\">LetsEncrypt<\/a> as it&#8217;s free and easy to setup, and straightforward to setup a testing system.<\/p>\n<h3>Summary<\/h3>\n<p>So that covers the Architecture of an Extension<\/p>\n<p>Not really sure what else to add, here is a photo of my cat, Sprite.<\/p>\n<figure id=\"attachment_51913\" aria-describedby=\"caption-attachment-51913\" style=\"width: 500px\" class=\"wp-caption aligncenter\"><a href=\"https:\/\/i0.wp.com\/barrycarlyon.co.uk\/wordpress\/wp-content\/uploads\/2021\/02\/IMG_8726.png?ssl=1\" rel=\"attachment wp-att-51913\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" src=\"https:\/\/i0.wp.com\/barrycarlyon.co.uk\/wordpress\/wp-content\/uploads\/2021\/02\/IMG_8726.png?resize=500%2C667&#038;ssl=1\" alt=\"Sprite has invaded this Blog Post\" width=\"500\" height=\"667\" class=\"size-full wp-image-51913\" srcset=\"https:\/\/i0.wp.com\/barrycarlyon.co.uk\/wordpress\/wp-content\/uploads\/2021\/02\/IMG_8726.png?w=500&amp;ssl=1 500w, https:\/\/i0.wp.com\/barrycarlyon.co.uk\/wordpress\/wp-content\/uploads\/2021\/02\/IMG_8726.png?resize=225%2C300&amp;ssl=1 225w\" sizes=\"auto, (max-width: 500px) 85vw, 500px\" \/><\/a><figcaption id=\"caption-attachment-51913\" class=\"wp-caption-text\">Sprite has invaded this Blog Post<\/figcaption><\/figure>\n<h4>What about next week?<\/h4>\n<p>Next week we will be looking at the Developer Rig and how\/when to use it and setting up a Dev environment suitable for developing a Twitch Extension!<\/p>\n<h3>BUT MOTHER I CRAVE VIOLENCE<\/h3>\n<p>Well, until I write the next part if you want to read more about the Developer Side of Extensions, you can pop a visit over the to the <a href=\"https:\/\/dev.twitch.tv\/docs\/extensions\" target=\"_blank\">Documentation<\/a> or take a look at Twitch&#8217;s <a href=\"https:\/\/dev.twitch.tv\/extensions\" target=\"_blank\">Introductory Page<\/a> and you can always join us on the &#8220;TwitchDev Discord Server&#8221;, visit the <a href=\"https:\/\/dev.twitch.tv\/support\" target=\"_blank\">Developer Support Page<\/a> for the current invite link!<\/p>\n<p><strong>Why you think you are good enough to even write blog posts on Extensions?<\/strong> I made a <a href=\"https:\/\/twitch.extensions.barrycarlyon.co.uk\/\" target=\"_blank\">one or two of them Extensions of various types.<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>In Part 2 we spoke a fair bit about &#8220;the path and file name of the HTML file you wish to load, it is a relative path to the Testing Base URI\/final upload URL&#8221;, and this week we will cover why I mentioned that every time! So, this week The Architecture of a Twitch Extension! &hellip; <a href=\"https:\/\/barrycarlyon.co.uk\/wordpress\/2021\/02\/26\/twitch-extensions-part-3\/\" class=\"more-link\">Continue reading<span class=\"screen-reader-text\"> &#8220;Twitch Extensions: Part 3 \u2013 The Architecture of an Extension&#8221;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_publicize_message":"","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":true,"jetpack_social_options":{"image_generator_settings":{"template":"highway","default_image_id":0,"font":"","enabled":false},"version":2}},"categories":[88,351,358,45,312],"tags":[356,352,357,313,326],"class_list":["post-51890","post","type-post","status-publish","format-standard","hentry","category-code-geekery","category-extensions","category-extensions-series","category-geekery","category-twitch","tag-architecture","tag-extensions","tag-hosting","tag-twitch","tag-work"],"jetpack_publicize_connections":[],"jetpack_featured_media_url":"","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/barrycarlyon.co.uk\/wordpress\/wp-json\/wp\/v2\/posts\/51890","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/barrycarlyon.co.uk\/wordpress\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/barrycarlyon.co.uk\/wordpress\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/barrycarlyon.co.uk\/wordpress\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/barrycarlyon.co.uk\/wordpress\/wp-json\/wp\/v2\/comments?post=51890"}],"version-history":[{"count":34,"href":"https:\/\/barrycarlyon.co.uk\/wordpress\/wp-json\/wp\/v2\/posts\/51890\/revisions"}],"predecessor-version":[{"id":51933,"href":"https:\/\/barrycarlyon.co.uk\/wordpress\/wp-json\/wp\/v2\/posts\/51890\/revisions\/51933"}],"wp:attachment":[{"href":"https:\/\/barrycarlyon.co.uk\/wordpress\/wp-json\/wp\/v2\/media?parent=51890"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/barrycarlyon.co.uk\/wordpress\/wp-json\/wp\/v2\/categories?post=51890"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/barrycarlyon.co.uk\/wordpress\/wp-json\/wp\/v2\/tags?post=51890"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}