Bodoque
Bodoque

Reputation: 9

How do I make this Request API work with CORS?

Thanks for reading. I'm making a Spoiler Blocker Chrome Extension, and as you can imagine that's been quite a journey... Right now I have everything sorted out, except the last part. Because of CORS Policy, I have to use a middleman (Cloudflare worker) to access the Razor API from the extension (returns data about entities). I found an open source code from managing requests with Cors activated for the Cloudflare worker, but it's been days, and I can't get it to send requests as I want it to. I tried predetermining the url to analyze and other stuff, but it just doesn't work.

´´´

The API: https://www.textrazor.com/docs/rest

fetch('https://secret.workers.dev/?https://api.textrazor.com', {
            method: 'POST',
            body: JSON.stringify({
                'extractors': 'entities,sentences',
                'text': txt
            }),
            headers: {
                'x-cors-headers': JSON.stringify({
                    'Content-Type': 'application/x-www-form-urlencoded',
                    'X-TextRazor-Key': 'secret',
                    'Accept-Encoding': 'gzip'
                })
            }

Cloudflare worker code:


 addEventListener("fetch", async event=>{
    event.respondWith((async function() {
        isOPTIONS = (event.request.method == "OPTIONS");
        var origin_url = new URL(event.request.url); ```

        function fix(myHeaders) {
            //            myHeaders.set("Access-Control-Allow-Origin", "*");
            myHeaders.set("Access-Control-Allow-Origin", event.request.headers.get("Origin"));
            if (isOPTIONS) {
                myHeaders.set("Access-Control-Allow-Methods", event.request.headers.get("access-control-request-method"));
                acrh = event.request.headers.get("access-control-request-headers");
                //myHeaders.set("Access-Control-Allow-Credentials", "true");

                if (acrh) {
                    myHeaders.set("Access-Control-Allow-Headers", acrh);
                }

                myHeaders.delete("X-Content-Type-Options");
            }
            return myHeaders;
        }
        var fetch_url = decodeURIComponent(decodeURIComponent(origin_url.search.substr(1)));

        var orig = event.request.headers.get("Origin");
        
        var body = event.request;
        
        var remIp = event.request.headers.get("CF-Connecting-IP");

        if ((!isListed(fetch_url, blacklist)) && (isListed(orig, whitelist))) {

            xheaders = event.request.headers.get("x-cors-headers");

            if (xheaders != null) {
                try {
                    xheaders = JSON.parse(xheaders);
                } catch (e) {}
            }

            if (origin_url.search.startsWith("?")) {
                recv_headers = {};
                for (var pair of event.request.headers.entries()) {
                    if ((pair[0].match("^origin") == null) && (pair[0].match("eferer") == null) && (pair[0].match("^cf-") == null) && (pair[0].match("^x-forw") == null) && (pair[0].match("^x-cors-headers") == null)) {
                        recv_headers[pair[0]] = pair[1];
                    }
                }
            
                if (xheaders != null) {
                    Object.entries(xheaders).forEach((c)=>recv_headers[c[0]] = c[1]);
                }

                var myHeaders = new Headers();
                myHeaders.append('x-textrazor-key', 'secret');
                myHeaders.append('Content-Type', 'application/x-www-form-urlencoded');

                var myBody = {
                    extractors: 'entities,sentences',
                    url: 'https://en.wikipedia.org/wiki/Marty_McFly'
                }
                
                newreq = new Request("https://api.textrazor.com", {

                    method: 'POST',
                    headers: myHeaders,
                    body: myBody

                })

                var response = await fetch(fetch_url,newreq);
                var myHeaders = new Headers(response.headers);
                cors_headers = [];
                allh = {};
                for (var pair of response.headers.entries()) {
                    cors_headers.push(pair[0]);
                    allh[pair[0]] = pair[1];
                }
                cors_headers.push("cors-received-headers");
                myHeaders = fix(myHeaders);

                myHeaders.set("Access-Control-Expose-Headers", cors_headers.join(","));

                myHeaders.set("cors-received-headers", JSON.stringify(allh));

                if (isOPTIONS) {
                    var body = null;
                } else {
                    var body = await response.arrayBuffer();
                }

                var init = {
                    headers: myHeaders,
                    status: (isOPTIONS ? 200 : response.status),
                    statusText: (isOPTIONS ? "OK" : response.statusText)
                };
                return new Response(body,init);

            } else {
                var myHeaders = new Headers();
                myHeaders = fix(myHeaders);

                if (typeof event.request.cf != "undefined") {
                    if (typeof event.request.cf.country != "undefined") {
                        country = event.request.cf.country;
                    } else
                        country = false;

                    if (typeof event.request.cf.colo != "undefined") {
                        colo = event.request.cf.colo;
                    } else
                        colo = false;
                } else {
                    country = false;
                    colo = false;
                }

                return new Response(
                    "CLOUDFLARE-CORS-ANYWHERE\n\n" + 
                    "Source:\nhttps://github.com/Zibri/cloudflare-cors-anywhere\n\n" + 
                    "Usage:\n" + origin_url.origin + "/?uri\n\n" +
            "Donate:\nhttps://paypal.me/Zibri/5\n\n" +
                    "Limits: 100,000 requests/day\n" + 
                    "          1,000 requests/10 minutes\n\n" + 
                    (orig != null ? "Origin: " + orig + "\n" : "") + 
                    "Ip: " + remIp + "\n" + 
                    (country ? "Country: " + country + "\n" : "") + 
                    (colo ? "Datacenter: " + colo + "\n" : "") + "\n" + 
                    ((xheaders != null) ? "\nx-cors-headers: " + JSON.stringify(xheaders) : ""),
                    {status: 200, headers: myHeaders}
                );
            }
        } else {

            return new Response(
                "Create your own cors proxy</br>\n" + 
                "<a href='https://github.com/Zibri/cloudflare-cors-anywhere'>https://github.com/Zibri/cloudflare-cors-anywhere</a></br>\n" +
                "\nDonate</br>\n" +
                "<a href='https://paypal.me/Zibri/5'>https://paypal.me/Zibri/5</a>\n",
                {
                    status: 403,
                    statusText: 'Forbidden',
                    headers: {
                        "Content-Type": "text/html"
                    }
                });
        }
    }
    )());
    });

Error: "Please specify a TextRazor request."

Thanks in advance!

EDIT:

So, I tried doing the background thingy. But... it doesn't work background.js after receiving the message from content.js

chrome.runtime.onMessage.addListener(function (msg, sender, value, request, sendResponse) {

if (msg.from == "content") {  //get content scripts tab id

contentTabId = sender.tab.id;

var myHeaders = new Headers();
myHeaders.append("x-textrazor-key", "secret");
myHeaders.append("Content-Type", "application/x-www-form-urlencoded");

var urlencoded = new URLSearchParams();
urlencoded.append("text", request.txt);
urlencoded.append("extractors", "entities,entailments");

fetch("https://api.textrazor.com/", {

  method: 'POST',
  headers: myHeaders,
  body: urlencoded,
  redirect: 'follow'

}).then((response) => sendResponse(response.json()));

return true;  // Will respond asynchronously.

}

content.js after receiving the first message from background (that orders it to look up the text from the page the user is on)

chrome.runtime.sendMessage({
            contentScriptQuery: "querySpoiler", txt: txt, messsage: { from: "content" }, responseCallback: function (response) {
                request = response.json();

When looking onto it in DevTools, runtime.sendMessage arguments and caller return an error: TypeError: 'caller', 'callee', and 'arguments' properties may not be accessed on strict mode functions or the arguments objects for calls to them at Function.r (:1:83) at chrome-extension://gbbiidobmakfdibhklcfnadmobpekifb/content.js:61:15

Thanks again!

Upvotes: 0

Views: 917

Answers (1)

Neea
Neea

Reputation: 1424

To make cross-domain requests in an extension, list all hosts you need to access in the extension manifest.

  • In manifest v3 the key is "host_permissions" ​(in manifest v2 it is "permissions")
  • The value will be an array of 1 or more match patterns

The example in original post shows the target host is https://api.textrazor.com. The extension manifest should include the following permission. After adding the permission, and within the extension context, it will be allowed to make requests to the specified host without needing a proxy server.

{
  "host_permissions": [ 
    "https://api.textrazor.com/*"
  ]
}

The * represents a wildcard and means: "match any path that starts with this pattern". You can change the match pattern based on your requirements to include different schemes, subdomains, paths, etc. and add more entries to the value to include more hosts in general. See: https://developer.chrome.com/docs/extensions/mv3/match_patterns for more details and examples of how to define match patterns.

After that, you can perform the API requests, example:

var myHeaders = new Headers();
myHeaders.append("x-textrazor-key", "API_KEY_HERE");
myHeaders.append("Content-Type", "application/x-www-form-urlencoded");

var urlencoded = new URLSearchParams();
urlencoded.append("text", "Spain's stricken Bankia expects to sell off its vast portfolio of industrial holdings that includes a stake in the parent company of British Airways and Iberia.");
urlencoded.append("extractors", "entities,entailments");


fetch("https://api.textrazor.com/", {
  method: 'POST',
  headers: myHeaders,
  body: urlencoded,
  redirect: 'follow'
})
  .then(response => response.text())
  .then(result => console.log(result))
  .catch(error => console.log('error', error));

Upvotes: 1

Related Questions