Skip to content

Commit f5afb65

Browse files
authored
Adds user agent header for requests to Green Web Foundation APIs
2 parents 0246400 + c88a8ff commit f5afb65

File tree

13 files changed

+201
-62
lines changed

13 files changed

+201
-62
lines changed

.all-contributorsrc

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,8 +132,17 @@
132132
"bug",
133133
"code"
134134
]
135+
},
136+
{
137+
"login": "sfishel18",
138+
"name": "Simon Fishel",
139+
"avatar_url": "https://avatars.githubusercontent.com/u/294695?v=4",
140+
"profile": "https://github.com/sfishel18",
141+
"contributions": [
142+
"code"
143+
]
135144
}
136145
],
137146
"contributorsPerLine": 7,
138147
"linkToUsage": false
139-
}
148+
}

.esbuild.browser.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1+
const esbuildCommon = require("./.esbuild.common");
2+
13
require('esbuild').buildSync({
4+
...esbuildCommon,
25
entryPoints: ['src/index.js'],
36
outdir: 'dist/iife',
47
globalName: 'co2',

.esbuild.common.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
const CO2JS_VERSION = require("./package.json").version;
2+
3+
module.exports = {
4+
define: {
5+
"process.env.CO2JS_VERSION": JSON.stringify(CO2JS_VERSION),
6+
},
7+
};

.esbuild.esm.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ const esbuild = require('esbuild')
33
// For this build however we need to filter out some extra files
44
// that are used for nodejs, but not in browsers, so we use the
55
// library directly instead of using `esbuild-plugin-glob` as a plugin
6-
const glob = require('tiny-glob');
6+
const glob = require('tiny-glob')
7+
const esbuildCommon = require('./.esbuild.common')
78

89
async function main() {
910
const results = await glob('src/**/!(*.test.js|test-constants.js|!(*.js))')
@@ -12,6 +13,7 @@ async function main() {
1213
const justBrowserCompatibleFiles = results.filter(filepath => !filepath.endsWith('node.js'))
1314

1415
esbuild.build({
16+
...esbuildCommon,
1517
entryPoints: justBrowserCompatibleFiles,
1618
bundle: false,
1719
minify: false,

.esbuild.node.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
const { globPlugin } = require('esbuild-plugin-glob');
2+
const esbuildCommon = require('./.esbuild.common');
23

34
function main() {
45
require('esbuild').build({
6+
...esbuildCommon,
57
entryPoints: ['src/**/!(*.test.js|test-constants.js|!(*.js))'],
68
bundle: false,
79
minify: false,

__mocks__/https.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { getApiRequestHeaders } from "../src/helpers/index.js";
2+
const https = jest.createMockFromModule("https");
3+
import { Stream } from "stream";
4+
5+
const stream = new Stream();
6+
7+
https.get.mockImplementation((url, options, callback) => {
8+
url, { headers: getApiRequestHeaders("TestRunner") }, callback(stream);
9+
if (url.includes("greencheckmulti")) {
10+
stream.emit(
11+
"data",
12+
Buffer.from(
13+
`{"google.com": {"url":"google.com","hosted_by":"Google Inc.","hosted_by_website":"https://www.google.com","partner":null,"green":true}}`
14+
)
15+
);
16+
} else {
17+
stream.emit(
18+
"data",
19+
Buffer.from(
20+
`{"url":"google.com","hosted_by":"Google Inc.","hosted_by_website":"https://www.google.com","partner":null,"green":true}`
21+
)
22+
);
23+
}
24+
25+
stream.emit("end");
26+
});
27+
28+
module.exports = https;

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@
2929
"intensity-data:average": "node data/functions/generate_average_co2.js",
3030
"intensity-data:marginal": "node data/functions/generate_marginal_co2.js",
3131
"intensity-data": "npm run intensity-data:average && npm run intensity-data:marginal && npm run format-data",
32-
"format-data": "cd data && prettier --write '**/*.{js,json}'"
32+
"format-data": "cd data && prettier --write '**/*.{js,json}'",
33+
"version": "npm run build"
3334
},
3435
"keywords": [
3536
"sustainability",
@@ -75,4 +76,4 @@
7576
"type": "git",
7677
"url": "https://github.com/thegreenwebfoundation/co2.js.git"
7778
}
78-
}
79+
}

src/helpers/index.js

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,4 +172,14 @@ function parseOptions(options) {
172172
return adjustments;
173173
}
174174

175-
export { formatNumber, parseOptions };
175+
/**
176+
* Returns an object containing all the HTTP headers to use when making a request to the Green Web Foundation API.
177+
* @param {string} comment - Optional. The app, site, or organisation that is making the request.
178+
*
179+
* @returns {import('http').OutgoingHttpHeaders}
180+
*/
181+
function getApiRequestHeaders(comment = "") {
182+
return { "User-Agent": `co2js/${process.env.CO2JS_VERSION} ${comment}` };
183+
}
184+
185+
export { formatNumber, parseOptions, getApiRequestHeaders };

src/hosting-api.js

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,34 @@
11
"use strict";
22

3+
import { getApiRequestHeaders } from "./helpers/index.js";
4+
35
/**
46
* Check if a string or array of domains has been provided
57
* @param {string|array} domain - The domain to check, or an array of domains to be checked.
8+
* @param {string} userAgentIdentifier - Optional. The app, site, or organisation that is making the request.
69
*/
710

8-
function check(domain) {
11+
function check(domain, userAgentIdentifier) {
912
// is it a single domain or an array of them?
1013
if (typeof domain === "string") {
11-
return checkAgainstAPI(domain);
14+
return checkAgainstAPI(domain, userAgentIdentifier);
1215
} else {
13-
return checkDomainsAgainstAPI(domain);
16+
return checkDomainsAgainstAPI(domain, userAgentIdentifier);
1417
}
1518
}
1619

1720
/**
1821
* Check if a domain is hosted by a green web host by querying the Green Web Foundation API.
1922
* @param {string} domain - The domain to check.
23+
* @param {string} userAgentIdentifier - Optional. The app, site, or organisation that is making the request.
2024
* @returns {boolean} - A boolean indicating whether the domain is hosted by a green web host.
2125
*/
22-
async function checkAgainstAPI(domain) {
26+
async function checkAgainstAPI(domain, userAgentIdentifier) {
2327
const req = await fetch(
24-
`https://api.thegreenwebfoundation.org/greencheck/${domain}`
28+
`https://api.thegreenwebfoundation.org/greencheck/${domain}`,
29+
{
30+
headers: getApiRequestHeaders(userAgentIdentifier),
31+
}
2532
);
2633
const res = await req.json();
2734
return res.green;
@@ -30,15 +37,18 @@ async function checkAgainstAPI(domain) {
3037
/**
3138
* Check if an array of domains is hosted by a green web host by querying the Green Web Foundation API.
3239
* @param {array} domains - An array of domains to check.
40+
* @param {string} userAgentIdentifier - Optional. The app, site, or organisation that is making the request.
3341
* @returns {array} - An array of domains that are hosted by a green web host.
3442
*/
3543

36-
async function checkDomainsAgainstAPI(domains) {
44+
async function checkDomainsAgainstAPI(domains, userAgentIdentifier) {
3745
try {
3846
const apiPath = "https://api.thegreenwebfoundation.org/v2/greencheckmulti";
3947
const domainsString = JSON.stringify(domains);
4048

41-
const req = await fetch(`${apiPath}/${domainsString}`);
49+
const req = await fetch(`${apiPath}/${domainsString}`, {
50+
headers: getApiRequestHeaders(userAgentIdentifier),
51+
});
4252

4353
const allGreenCheckResults = await req.json();
4454

src/hosting-api.test.js

Lines changed: 58 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,72 @@
11
"use strict";
22

3-
import hosting from "./hosting-node.js";
4-
import nock from "nock";
3+
import hosting from "./hosting-api.js";
54
/* eslint-disable jest/no-disabled-tests */
5+
6+
process.env.CO2JS_VERSION = "1.2.34";
7+
const requestHeaderComment = "TestRunner";
8+
9+
global.fetch = jest.fn(() =>
10+
Promise.resolve({
11+
json: () => Promise.resolve({ green: true }),
12+
})
13+
);
14+
615
describe("hostingAPI", () => {
16+
beforeEach(() => {
17+
fetch.mockClear();
18+
});
719
describe("checking a single domain with #check", () => {
8-
it.skip("using the API", async () => {
9-
const scope = nock("https://api.thegreenwebfoundation.org/")
10-
.get("/greencheck/google.com")
11-
.reply(200, {
12-
url: "google.com",
13-
green: true,
14-
});
20+
it("using the API", async () => {
1521
const res = await hosting.check("google.com");
22+
expect(fetch).toHaveBeenCalledTimes(1);
23+
expect(fetch).toHaveBeenLastCalledWith(
24+
expect.any(String),
25+
expect.objectContaining({
26+
headers: { "User-Agent": "co2js/1.2.34 " },
27+
})
28+
);
29+
expect(res).toEqual(true);
30+
});
31+
it("sets the correct user agent header", async () => {
32+
const res = await hosting.check("google.com", requestHeaderComment);
33+
34+
expect(fetch).toHaveBeenCalledTimes(1);
35+
expect(fetch).toHaveBeenLastCalledWith(
36+
expect.any(String),
37+
expect.objectContaining({
38+
headers: { "User-Agent": "co2js/1.2.34 TestRunner" },
39+
})
40+
);
1641
expect(res).toEqual(true);
1742
});
1843
});
1944
describe("implicitly checking multiple domains with #check", () => {
20-
it.skip("using the API", async () => {
21-
const scope = nock("https://api.thegreenwebfoundation.org/")
22-
.get("/v2/greencheckmulti/[%22google.com%22,%22kochindustries.com%22]")
23-
.reply(200, {
24-
"google.com": {
25-
url: "google.com",
26-
green: true,
27-
},
28-
"kochindustries.com": {
29-
url: "kochindustries.com",
30-
green: null,
31-
},
32-
});
45+
it("using the API", async () => {
46+
fetch.mockImplementation(() =>
47+
Promise.resolve({
48+
json: () =>
49+
Promise.resolve({
50+
"google.com": { url: "google.com", green: true },
51+
}),
52+
})
53+
);
3354
const res = await hosting.check(["google.com", "kochindustries.com"]);
55+
expect(fetch).toHaveBeenCalledTimes(1);
56+
expect(res).toContain("google.com");
57+
});
58+
it("sets the correct user agent header", async () => {
59+
const res = await hosting.check(
60+
["google.com", "kochindustries.com"],
61+
requestHeaderComment
62+
);
63+
expect(fetch).toHaveBeenCalledTimes(1);
64+
expect(fetch).toHaveBeenLastCalledWith(
65+
expect.any(String),
66+
expect.objectContaining({
67+
headers: { "User-Agent": "co2js/1.2.34 TestRunner" },
68+
})
69+
);
3470
expect(res).toContain("google.com");
3571
});
3672
});

0 commit comments

Comments
 (0)