angulartypescriptgithubgithub-pagesgithub-api

GitHub REST API CORS error when trying to download public repository git archive


I'm trying to build an Angular application hosted on my GitHub pages with a custom verified domain.
This is my code to call GitHub API and get the zip archive of a (public) repository of mine:

  public getTemplate() {
    return this.http.get(
      'https://api.github.com/repos/crystal-nest/cobweb-mod-template/zipball/1.20.4',
      {
        headers: {
          'X-GitHub-Api-Version': '2022-11-28'
        }
      }
    );
  }

I know the URL is correct because if I paste it in my browser the download starts.
However, when the code gets executed I get the following error:

Access to XMLHttpRequest at 'https://codeload.github.com/Crystal-Nest/cobweb-mod-template/legacy.zip/refs/heads/1.20.4' (redirected from 'https://api.github.com/repos/crystal-nest/cobweb-mod-template/zipball/1.20.4') from origin 'https://crystalnest.it' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.

I then tried with JSONP requests:

return this.http.jsonp('https://api.github.com/repos/crystal-nest/cobweb-mod-template/zipball/1.20.4', 'callback');

But this time the error I received was:

{
    "headers": {
        "normalizedNames": {},
        "lazyUpdate": null,
        "headers": {}
    },
    "status": 0,
    "statusText": "JSONP Error",
    "url": "https://api.github.com/repos/crystal-nest/cobweb-mod-template/zipball/1.20.4?callback=ng_jsonp_callback_0",
    "ok": false,
    "name": "HttpErrorResponse",
    "message": "Http failure response for https://api.github.com/repos/crystal-nest/cobweb-mod-template/zipball/1.20.4?callback=ng_jsonp_callback_0: 0 JSONP Error",
    "error": {
        "message": "JSONP injected script did not invoke callback.",
        "stack": "Error: JSONP injected script did not invoke callback.\n    stacktrace omitted for the sake of brevity"
    }
}

My code can be found here, and here there's a live example.

Side note: even if the request worked and I was able to get the file as an 'arraybuffer' (the data I need to process the file with JSZip), it's a pain that the zip name and its root directory have a code appended at the end (that I can't understand what it is and thus I can't predict what it's going to be). Ideally, I'd like to use this URL https://github.com/crystal-nest/cobweb-mod-template/archive/refs/heads/1.20.4.zip because it gives me a name easy to predict and thus easy to process.


Solution

  • As pointed out on GitHub community forum here, the only current way to do it is by employing a proxy.
    In my case, I simply used this free and public proxy.

    Update

    As pointed out, that proxy is not free anymore.
    I ended up using a custom reverse proxy hosted for free on a Cloudflare worker.

    This is the code:

    /**
     * Reverse proxy for GitHub repository zip archive downloads.
     */
    export default {
      async fetch(request, env, ctx) {
        if (new URL(request.url).hostname == 'crystalnest.it') {
          const {user, repo, branch} = await request.json();
          return new Response(
            await (await fetch(`https://codeload.github.com/${user}/${repo}/zip/refs/heads/${branch}`)).arrayBuffer(),
            {
              headers: {
                "Access-Control-Allow-Headers": request.headers.get("Access-Control-Request-Headers"),
                "Access-Control-Allow-Methods": "GET,HEAD,POST,OPTIONS",
                "Access-Control-Allow-Origin": "*",
                "Access-Control-Max-Age": "300",
                "content-type": "application/zip",
              }
            }
          );
        }
        return new Response(`Forbidden request URL: ${request.url}`, {status: 403, statusText: 'Forbidden request URL.'});
      },
    };
    

    This worker is then accessible from a custom route based on my domain.