11ty / eleventy-fetch

Utility to cache any remote asset: Image, Video, Web Font, CSS, JSON, etc
https://www.11ty.dev/docs/plugins/fetch/
146 stars 19 forks source link

Passing header x-api-key but getting 403 #35

Closed msm1227 closed 6 months ago

msm1227 commented 1 year ago

I am trying to pass an api key in the headers but am getting back 403.

I can use node-fetch with the code below but am wondering if it is possible to accomplish this with eleventy-fetch

const fetch = require('node-fetch');
const url =
"https://example.com";
const apiKey = "xxxxxxxxxx";

fetch(url, {
    method: 'GET',
    headers: {
        'x-api-key': apiKey
    },
    mode: 'cors',
    cache: 'default',
})
.then(response => response.json())
.then(json => console.log(JSON.parse(JSON.parse(json).data)))
.catch(err => console.log(err));

This is the current code that I have using eleventy-fetch:

const EleventyFetch = require("@11ty/eleventy-fetch");
const url =
"https://example.com";
const apiKey = "xxxxxxxxxx";

async function getTechData() {

  return EleventyFetch(url, {
    method: 'GET',
    headers: {
        'x-api-key': apiKey
    },
    duration: "1d", // save for 1 day
    type: "json"    // we’ll parse JSON for you
  });
  const techData = response
  return techData;
};

module.exports = getTechData;

Thanks!

pdehaan commented 1 year ago

https://www.11ty.dev/docs/plugins/fetch/#fetch-google-fonts-css

let fontCss = await EleventyFetch(url, {
    duration: "1d",
    type: "text",
    fetchOptions: {
        headers: {
            // lol
            "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36"
        }
    }
});

From the looks of that example, you might have to wrap the headers (and possibly method 🤷) in fetchOptions object.

msm1227 commented 1 year ago

@pdehaan that worked! Thanks!

The only other thing now is the parsing. In my node-fetch example I am having to do this:

async function getTechData() {
    const fetch = require('node-fetch');
    const url =
    "https:/example.com";
    const apiKey = "xxxxxxxx";

    const response = await fetch(url, {
        method: 'GET',
        headers: {
            'x-api-key': apiKey
        },
        mode: 'cors',
        cache: 'default',
    });
    const techData = await response.json();
    const techDataParsed = await JSON.parse(JSON.parse(techData).data);
    return techDataParsed;
}

module.exports = getTechData;

Namely the "const techDataParsed = await JSON.parse(JSON.parse(techData).data);" portion to parse the json so that it comes through properly.

If I try the same thing with eleventy fetch I get the following error:

[11ty] Unhandled rejection in promise: (more in DEBUG output)
[11ty] reponse.json is not a function (via TypeError)
[11ty] 
[11ty] Original error stack trace: TypeError: reponse.json is not a function
async function getTechData() {
    const EleventyFetch = require("@11ty/eleventy-fetch");
    const url =
    "https:/example.com";
    const apiKey = "xxxxxxxx";

    const response = await EleventyFetch(url, {
        duration: "30s",
        type: "json",
        fetchOptions: {
            headers: {
                'x-api-key': apiKey
            }
        } 
    });
    const techData = await response.json();
    const techDataParsed = await JSON.parse(JSON.parse(techData).data);
    return techDataParsed;
}

module.exports = getTechData;
pdehaan commented 1 year ago

Does something like this work?

async function getTechData() {
    const EleventyFetch = require("@11ty/eleventy-fetch");
    const url = "https://example.com";
    const apiKey = "xxxxxxxx"; // or ideally move this into an ENV var instead of hardcoding in project.

    return EleventyFetch(url, {
        duration: "30s",
        type: "json",
        fetchOptions: {
            headers: {
                'x-api-key': apiKey
            }
        } 
    });
}

module.exports = getTechData;
pdehaan commented 1 year ago

if I understand EleventyFetch correctly, it should do this magic for you:

    const techData = await response.json();
    const techDataParsed = await JSON.parse(JSON.parse(techData).data);
    return techDataParsed;

Plus, JSON.parse() isn't async, so it shouldn't need the await keyword. Not sure why you need a double JSON.parse(), but hard to tell without knowing what the API is returning and how encoded it is.

msm1227 commented 1 year ago

This is a sample of how the API response is coming through which is why I am having to double parse it:

[{\\"name\\":\\"Rodger Saffold\\",\\"pos\\":\\"N\\/A\\",\\"year\\":2022,\\"}]

Edit : It actually has 3 backslashes but GitHub is escaping them

pdehaan commented 1 year ago

In cases where I have to do post-processing of data, it's sometimes easier to manage the cache manually: https://www.11ty.dev/docs/plugins/fetch/#manually-store-your-own-data-in-the-cache

So maybe it looks more like your node-fetch example, and then you set the AssetCache and check if the cache is valid for the specified time period, etc.


I've used the manual caching approach in weird cases where I scraped some remote HTML, parsed it with Cheerio and then did some .map().reduce() silliness and cached the DOM results. Seemed slightly more performant than just caching the raw HTML and rerunning the Cheerio transforms each time.

pdehaan commented 1 year ago

Otherwise, in your const response = await EleventyFetch(url, {...}) example, try logging response object and see what it looks like. Sounds like it might not need that response.json() step, but might need to return the double JSON.parse()ed value.

msm1227 commented 1 year ago

@pdehaan thank you so much for your help on this! It is now working properly.

For anyone who comes across this in the future, the final code is:

async function getTechData() {
    const EleventyFetch = require("@11ty/eleventy-fetch");
    const url =
    "https:/example.com";
    const apiKey = "xxxxxxxx";

    const response = await EleventyFetch(url, {
        duration: "60s",
        type: "json",
        fetchOptions: {
            headers: {
                'x-api-key': apiKey
            }
        } 
    });
    const techDataParsed = JSON.parse(JSON.parse(response).data);
    return techDataParsed;
}

module.exports = getTechData;

As for the api key, I was hoping to put that in a netlify environment variable. Not sure if that will work here or not since I haven't tried it yet but it's working with my functions