in Web

Fetch and Render GitHub Markdown without CORS

I wanted to embed the contents of my GitHub project on another website, but the path to get there wasn’t straightforward. Here are the roadblocks I hit, and how I got around them.

Skip to the end if you just want the final solution.

Idea 1: Render in an iframe

I’d love to just create an iframe to show everything in my GitHub project on another website. An iframe seemed like a beautiful solution, but….

<iframe src="https://github.com/handstandsam/ShoppingApp"></iframe>

Roadblock: GitHub blocks iframes for its content.

Idea 2: Fetch the HTML, and Render it Manually

I thought I could just scrape the content from the website for my GitHub project and then render it on my site. However, I couldn’t pull arbitrary content from another web host due to CORS.

Note: If I had a server I could do this because I wouldn’t have CORS issues, but I was trying to do this completely in a frontend web page without a server.

Roadblock: CORS Browser Security Policies

Idea 3: Use the GitHub API to fetch the README File

GitHub has an awesome API that we can use to access the contents of a repository! I can’t use it to get the rendering of the entire project page, but I can access individual files like my README.md.

https://api.github.com/repos/handstandsam/ShoppingApp/contents/README.md

This allows me to pull down the contents of the file. The problem is that I can’t do that just in the frontend browser itself due to CORS.

Are you sensing a theme? Doing things in a browser is hard, but it helps make us safer on the web, so I can’t argue with that.

Roadblock: CORS Browser Security Policies

Idea 4: Use JSONP with the GitHub API

JSONP (JSON with Padding) is a workaround for CORS. You basically load an arbitrary bit of JavaScript from a 3rd party site, and have it call an arbitrary function that you know the name of.

Well, GitHub has support for JSONP! We will load JavaScript into our page from https://api.github.com/repos/handstandsam/ShoppingApp/contents/README.md?callback=myCallback and when the loading is done, it will invoke myCallback(results) assuming that the remote server has support for JSONP.

This exposes us to so many security vulnerabilities so PLEASE only do this with trusted sites. They could arbitrarily execute code within the context of your website and you wouldn’t know.

Note: JSONP uses the same functionality of loading in 3rd party javascript to do things like Analytics tracking, or fancy animations with JS libraries like BootStrap JS. It’s just programatically creating a <script> tag.

Roadblock: The response to the API contains Base64 encoded content.

Idea 5: Decode Base64 File Contents and Show README

I was able to define my callback for the GitHub API and then render the text into a pre (preformatted text) element. I did this by Base64 Decoding the GitHub API response’s “content” field, and then programmatically creating a pre element and setting its textContent.

function myCallback(response) {
    // Decode the Base64 Encoded Content
    let decodedContent = atob(response.data.content);

    // Create a "pre" HTML tag and render the content
    let pre = document.createElement("pre");
    pre.textContent = decodedContent;
    document.getElementsByTagName('body')[0].append(pre);
}

Roadblock: The contents weren’t formatted, just plain markdown.

Idea 6: Use a JS Library to Render the Markdown

There is a JavaScript library for everything. In this case I found markedjs/marked. I just give it a string of Markdown, and it’ll give me back the rendered HTML.

<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
function githubMarkdownCallback(response) {
    // Decode the Base64 Encoded Content
    let rawMarkdown = atob(response.data.content);

    // Use the markedjs library to transform markdown -> html
    let markdownHtml = marked.parse(rawMarkdown)

    // Add the new div to the body of the html page
    let div = document.createElement("div");
    div.innerHTML = markdownHtml;
    document.getElementsByTagName('body')[0].append(div);
}

FINAL SOLUTION!

Fetch my project’s README.md contents from GitHub’s public API using JSONP and use markedjs to render the Markdown into HTML.

<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<script type="text/javascript">
function myCallback(response) {
    // Decode the Base64 Encoded Content
    let rawMarkdown = atob(response.data.content);

    // Use the markedjs library to transform markdown -> html
    let markdownHtml = marked.parse(rawMarkdown)

    // Add the new div to the body of the html page
    let div = document.createElement("div");
    div.innerHTML = markdownHtml;
    document.getElementsByTagName('body')[0].append(div);
}

let script = document.createElement('script');
script.src = 'https://api.github.com/repos/handstandsam/ShoppingApp/contents/README.md?callback=myCallback';
document.getElementsByTagName('head')[0].appendChild(script);
</script>