To Be A Dev

How To: Make A Github Proxy With Dark Mode

In this article I'll be showing you how you can make a proxy that serves Github in dark mode. We'll be creating a serverless function that serves as the proxy and deploying to Now. We'll use Now's configuration file to route all traffic through a single endpoint: index.js.

What's A Proxy?

TLDR;

Proxy's serve as a layer between you and the rest of the internet by providing things like extra security, privacy, access to blocked resources, etc.

There's also a longer read that dives deeper into what a proxy is and what it can do. Along with the potential risks associated with using one.

What's A Serverless Function?

TLDR;

Serverless functions can be broken down simply as a software design patter where applications are hosted by a third-party service, which eliminates the need for a developer to manage a server with hardware and/or software. In other words, you push your code and tell the service how to handle it.

The Twilio docs have a really good explanation diving into greater detail about serverless functions.

Let's Get Started

This is going to be pretty short. All we really need to get this working is 2 files: index.js and dark.css. We need a CSS file to send along to enable dark mode. I'm using this Github-Dark style, but you could always find another one that works best for you.

Now that we have our dark style CSS for Github at the root of out project, we can start working on our JavaScript. First thing we need is a way to async-fetch pages from Github, I like to use the node-fetch.

npm i node-fetch

And let's set the groundwork:

const fetch = require('node-fetch');
module.exports = async (req, res) => {
const html = (await (await fetch(`https://github.com${req.url}`)).text())
res.end(html);
};

Awesome! Now we can navigate Github through our proxy. I'm using Now to deploy this as a function. This requires some extra setup through the now.json configuration file to make sure all of the routes hit out index.js, and also that Now is building and serving our assets correctly:

now.json

{
"version": 2,
"routes": [
{ "src": "/dark\\.css", "dest": "dark.css" },
{ "src": "/.*", "dest": "index.js" }
],
"builds": [
{ "src": "index.js", "use": "@now/node" },
{ "src": "dark.css", "use": "@now/static" }
]
}

Once we deploy we should be able to navigate Github through our proxy:

Github Proxy

From this point, we're presented with two issues: 1. Any link that points to https://github.com/... will throw us to Github instead of continue through the proxy 2. We're not applying the dark mode styling

We're already converting our HTML tp text, so let's replace any github.com links with our host URL:

const fetch = require('node-fetch');
module.exports = async (req, res) => {
res.setHeader('Cache-Control', 's-maxage=3, stale-while-revalidate');
const html = (await (await fetch(`https://github.com${req.url}`)).text())
.replace(/(href=.)https?:\/\/github.com/g, `$1//${req.headers.host}`);
res.end(html);
};

If you're having trouble understanding the '$1' in the second argument of .replace(), have a look at the documentation on MDN 😀

Great! If we take a look at each new page that we're fetching, we should see that some links are being changed to provide the host url:

developer console

Onto the last issue of adding the dark mode styles to the site. You might think that it makes sense to just take the contents of the file, and put it in a style tag somewhere in the html that we're grabbing on each fetch. While that is an option, we can make that a little cleaner:

const fetch = require('node-fetch');
module.exports = async (req, res) => {
res.setHeader('Cache-Control', 's-maxage=3, stale-while-revalidate');
const html = (await (await fetch(`https://github.com${req.url}`)).text())
.replace(/(href=.)https?:\/\/github.com/g, `$1//${req.headers.host}`)
.replace(
'</head>',
'<link media="all" href="/dark.css" rel="stylesheet" /></head>'
);
res.end(html);
};

Just add a link tag with the relative path to the asset 🥳 Because we're serving the content from our function, we can also pass along files that exist from the same source - i.e. our dark.css. After we publish all of those changes, we can now freely browse Github in dark mode through our proxy 🎉

dark mode github

Limitations

This is a very rudimentary version of a proxy. It doesn't provide anymore than a good way to apply a theme to a site and make sure some link traffic still gets routed through the proxy. In fact, it actually strips away a lot of things from Github. For instance, you can't log in, so you can't do any actions tied to an account. This is because we're not sending any information along to authenticate ourselves. In the future, I may try to figure out a way to make that happen.

In the meantime, we CAN add some caching to make performance a little bit better when navigating:

const fetch = require('node-fetch');
module.exports = async (req, res) => {
res.setHeader('Cache-Control', 's-maxage=3, stale-while-revalidate');
const html = (await (await fetch(`https://github.com${req.url}`)).text())
.replace(/(href=.)https?:\/\/github.com/g, `$1//${req.headers.host}`)
.replace(
'</head>',
'<link media="all" href="/dark.css" rel="stylesheet" /></head>'
);
res.end(html);
};

The rest, for now, is up to you. Happy coding! 💙

Something You Should Know

A proxy can be a bit of a double edged sword when it comes to privacy and security. Organizations have and to this day use proxys to monitor employee activity amongst other things. Because a proxy's main feature is processing the traffic that passes through it, it can do a lot of things with that data. You should take care with the actions you take when using a proxy.

What I'm specifically doing here is doing checks and routing traffic through a single endpoint that transforms the <head> and some links to achieve proper routing and the dark theme for Github. I could write something that modifies inputs on the login page to do whatever I want. I'm not doing that, and my deployment is public so that can be confirmed. The lesson is that if you're not absolutely sure what a third party proxy is doing, you shouldn't be doing any actions that could cause self-harm.

Devin Metivier

Personal blog by Devin Metivier

I like to share what I learn