{"id":386,"date":"2022-10-08T15:15:19","date_gmt":"2022-10-08T15:15:19","guid":{"rendered":"https:\/\/johnlradford.io\/blog\/?p=386"},"modified":"2022-10-08T15:15:21","modified_gmt":"2022-10-08T15:15:21","slug":"roku-tv-remote-xhr-website-cors-proxy","status":"publish","type":"post","link":"https:\/\/johnlradford.io\/blog\/2022\/10\/08\/roku-tv-remote-xhr-website-cors-proxy\/","title":{"rendered":"Roku TV Remote XHR Website + CORS Proxy"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">I&#8217;ve recently made a fun little webpage that doubles as a remote control for <a href=\"https:\/\/www.amazon.com\/dp\/B08DHDS4T3?psc=1&amp;ref=ppx_yo2ov_dt_b_product_details\">my new Roku TV<\/a>. It works by sending <a href=\"https:\/\/developer.roku.com\/docs\/developer-program\/debugging\/external-control-api.md\" data-type=\"URL\" data-id=\"https:\/\/developer.roku.com\/docs\/developer-program\/debugging\/external-control-api.md\">External Control Protocol (ECP)<\/a> messages to the TV&#8217;s onboard API via a secure<a href=\"https:\/\/rapidapi.com\/guides\/cors-proxy-apis\" data-type=\"URL\" data-id=\"https:\/\/rapidapi.com\/guides\/cors-proxy-apis\"> CORS Proxy <\/a>I&#8217;ve setup on a raspberry pi on my LAN.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"461\" height=\"1024\" src=\"https:\/\/johnlradford.io\/blog\/wp-content\/uploads\/2022\/10\/remote-461x1024.png\" alt=\"\" class=\"wp-image-387\" srcset=\"https:\/\/johnlradford.io\/blog\/wp-content\/uploads\/2022\/10\/remote-461x1024.png 461w, https:\/\/johnlradford.io\/blog\/wp-content\/uploads\/2022\/10\/remote-135x300.png 135w, https:\/\/johnlradford.io\/blog\/wp-content\/uploads\/2022\/10\/remote-768x1707.png 768w, https:\/\/johnlradford.io\/blog\/wp-content\/uploads\/2022\/10\/remote-691x1536.png 691w, https:\/\/johnlradford.io\/blog\/wp-content\/uploads\/2022\/10\/remote.png 864w\" sizes=\"auto, (max-width: 461px) 100vw, 461px\" \/><figcaption>Roku TV Remote Webpage<\/figcaption><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Overview \/ How It Works<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">So the remote webpage lives on one of my sites, https:\/\/intra.example.com\/tv_remote. Which is an internet accessible page. But I keep it password protected and use it as a collection of online resources for myself and some friends. You can find the source of the tv remote page on my github, linked below.<br><br><strong><a href=\"https:\/\/github.com\/BlueSquare23\/Learning_HTML\/blob\/master\/Examples\/roku_remote\/index.html\">Roku TV Remote WebPage Source<\/a><\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The roku remote page is just HTML and Javascript. The way it works is, the JS embedded on the page sends XHR POST request&#8217;s to the secure CORS proxy which then relays those ECP commands to the API hosted on the TV itself. The reason for the CORS proxy is, <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTTP\/CORS\">CORS<\/a>. AKA browsers will NOT allow JS on any random web page to make POST requests to some resource on someones LAN unless a few pre-requisites are met. <\/p>\n\n\n\n<ol class=\"wp-block-list\"><li>The HTTP requests must be secured via SSL (encrypted).<\/li><li>The endpoint must explicitly allow requests from the origin.<br><\/li><\/ol>\n\n\n\n<h3 class=\"wp-block-heading\">Diagram<\/h3>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"529\" height=\"432\" src=\"https:\/\/johnlradford.io\/blog\/wp-content\/uploads\/2022\/10\/remote_diagram-1.png\" alt=\"\" class=\"wp-image-389\" srcset=\"https:\/\/johnlradford.io\/blog\/wp-content\/uploads\/2022\/10\/remote_diagram-1.png 529w, https:\/\/johnlradford.io\/blog\/wp-content\/uploads\/2022\/10\/remote_diagram-1-300x245.png 300w\" sizes=\"auto, (max-width: 529px) 100vw, 529px\" \/><figcaption>Overview Diagram<\/figcaption><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">TV Setup<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">To make this work you have to first <a href=\"https:\/\/developer.roku.com\/docs\/developer-program\/getting-started\/developer-setup.md\">enable developer mode by entering the konami code on the TV&#8217;s real remote<\/a>. After setting up developer mode and entering a password the TV&#8217;s ECP API will be online.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Router \/ LAN Setup<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">On your router \/ local DNS server you&#8217;ll want to configure a static IP entry for your raspberry pi and your TV. From there you&#8217;ll want to assign a DNS hostname to your pi so it can have a domain name on your LAN. I&#8217;ve named my pi &#8220;webpi&#8221;.  <\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Setting Up Nginx As A Secure Reverse Proxy for CORS On A Raspberry Pi<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><a href=\"https:\/\/www.nginx.com\/\" data-type=\"URL\" data-id=\"https:\/\/www.nginx.com\/\">Nginx<\/a> is great for if you just need a quick and simple LAN-side <a href=\"https:\/\/www.cloudflare.com\/learning\/cdn\/glossary\/reverse-proxy\/\">reverse proxy<\/a>. <br><br>On a blank raspberry pi you can install Nginx with the commands below.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">sudo apt update &amp;&amp; sudo apt upgrade -y\nsudo apt install nginx<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">From there you can configure Nginx to work as a reverse proxy by creating a new virtual host conf. I named my virtual host conf after the name of my raspberrypi (webpi). You can name yours after whatever you call your pi on your home network. <br><br>The main conf files for nginx hosts go here, \/etc\/nginx\/sites-available. If you&#8217;re not familiar with using vim as your text editor, use pico.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">vim \/etc\/nginx\/sites-available\/webpi.conf<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">In that file put the following.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">server {\n  listen 80;\n  listen [::]:80;\n  server_name webpi.example.com;\n  \n  root \/var\/www\/html;\n  index pihole\/index.php index.php index.html index.htm;\n  \n  charset UTF-8;\n  \n  if ($scheme = http) {\n      return 301 https:\/\/$server_name$request_uri;\n  }\n  \n}\n\nserver {\n  listen 443 ssl http2;\n  listen [::]:443 ssl http2;\n  \n  root \/var\/www\/html;\n  server_name webpi.example.com;\n  autoindex off;\n  \n  index pihole\/index.php index.php index.html index.htm;\n  \n  charset UTF-8;\n  \n  if ($scheme = http) {\n      return 301 https:\/\/$server_name$request_uri;\n  }\n  \n  location \/ {\n      expires max;\n      try_files $uri $uri\/ =404;\n  }\n\n  location \/tv_proxy {\n      if ($request_method = OPTIONS ) {\n          add_header Content-Length 0;\n          add_header Content-Type text\/plain;\n          add_header Access-Control-Allow-Origin $http_origin;\n          add_header Access-Control-Allow-Private-Network true;\n          return 200;\n      }\n\n      proxy_pass http:\/\/YOUR_TVs_LAN_IP:8060\/;\n      proxy_set_header Origin http:\/\/intra.example.com\/;\n      proxy_hide_header Access-Control-Allow-Origin;\n      add_header Access-Control-Allow-Origin $http_origin;\n  }\n  \n  location ~ \/\\.ht {\n      deny all;\n  }\n  \n  ssl_certificate \/etc\/letsencrypt\/live\/webpi.example.com\/fullchain.pem;\n  ssl_certificate_key \/etc\/letsencrypt\/live\/webpi.example.com\/privkey.pem;\n  \n}<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This setup requires that you get an SSL certificate for the name webpi.example.com (or whatever the name is in your case). I&#8217;m using Let&#8217;s Encrypt to get these certificates. Let&#8217;s Encrypt requires that you verify domain ownership before they&#8217;ll issues a certificate for your domain. There are a few ways to do this. However, for this purpose I would advise using <a href=\"https:\/\/letsencrypt.org\/docs\/challenge-types\/\">Let&#8217;s Encrypts  DNS challenge<\/a>, as the raspberry pi in this case should<mark style=\"background-color:rgba(0, 0, 0, 0);color:#f40000\" class=\"has-inline-color\"><strong>NOT<\/strong><\/mark>have ports forwarded \/ be WAN accessible.<br><br>The tool used to install Let&#8217;s Encrypt certs is called certbot. You can install certbot on the pi with the following command.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">sudo apt install certbot<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">From there you can run the certbot in manual mode to do a DNS challenge.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">sudo certbot certonly --manual \\\n        --preferred-challenges=dns\n        --register-unsafely-without-email\n        -d webpi.example.com<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">That will then spit out a string of random text that must be entered as a TXT record within your domain&#8217;s DNS. Once the DNS TXT record has been added and has propagated across the global internet, (could take hours depending on your domain&#8217;s DNS TTL value), you should be able to hit enter and get a message like the following.<br><\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">IMPORTANT NOTES:\n - Congratulations! Your certificate and chain have been saved at:\n   \/etc\/letsencrypt\/live\/webpi.example.com\/fullchain.pem\n   Your key file has been saved at:\n   \/etc\/letsencrypt\/live\/webpi.example.com\/privkey.pem\n...<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">From there you should be able to start \/ enable Nginx.<br><\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">sudo systemctl enable nginx\nsudo systemctl start nginx<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">And then if you visit webpi.example.com in a browser from a device on your LAN you&#8217;ll see the XML output of the TV&#8217;s API.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Setting Up The Webpage<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Lastly, on either an internet accessible site or on a site on your LAN you&#8217;ll want to add a folder for roku_remote and then copy in the <a href=\"https:\/\/github.com\/BlueSquare23\/Learning_HTML\/blob\/master\/Examples\/roku_remote\/index.html\">code from my github<\/a>. When you initially visit the page you&#8217;ll be greeted with a message explaining you have to enter a proxy address. After entering the name of the CORS proxy server (webpi in my case), the site will then work as a fully functioning TV remote for a Roku TV.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">In Conclusion<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Although modern browsers try to dicourage it for security reasons it is possible to use Javascript to communicate with devices on your LAN as long as there is a secure CORS proxy setup in between. This project is a fun little useful example of that in practice on my LAN. Thanks for reading!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>I&#8217;ve recently made a fun little webpage that doubles as a remote control for my new Roku TV. It works by sending External Control Protocol (ECP) messages to the TV&#8217;s<span class=\"more-button\"><a href=\"https:\/\/johnlradford.io\/blog\/2022\/10\/08\/roku-tv-remote-xhr-website-cors-proxy\/\" class=\"more-link\">Continue reading<span class=\"screen-reader-text\">Roku TV Remote XHR Website + CORS Proxy<\/span><\/a><\/span><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-386","post","type-post","status-publish","format-standard","hentry","category-uncategorized"],"_links":{"self":[{"href":"https:\/\/johnlradford.io\/blog\/wp-json\/wp\/v2\/posts\/386","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/johnlradford.io\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/johnlradford.io\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/johnlradford.io\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/johnlradford.io\/blog\/wp-json\/wp\/v2\/comments?post=386"}],"version-history":[{"count":1,"href":"https:\/\/johnlradford.io\/blog\/wp-json\/wp\/v2\/posts\/386\/revisions"}],"predecessor-version":[{"id":390,"href":"https:\/\/johnlradford.io\/blog\/wp-json\/wp\/v2\/posts\/386\/revisions\/390"}],"wp:attachment":[{"href":"https:\/\/johnlradford.io\/blog\/wp-json\/wp\/v2\/media?parent=386"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/johnlradford.io\/blog\/wp-json\/wp\/v2\/categories?post=386"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/johnlradford.io\/blog\/wp-json\/wp\/v2\/tags?post=386"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}