Skip to content
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .esbuild.browser.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
const esbuildCommon = require("./.esbuild.common");

require('esbuild').buildSync({
...esbuildCommon,
entryPoints: ['src/index.js'],
outdir: 'dist/iife',
globalName: 'co2',
Expand Down
7 changes: 7 additions & 0 deletions .esbuild.common.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
const CO2JS_VERSION = require("./package.json").version;

module.exports = {
define: {
"process.env.CO2JS_VERSION": JSON.stringify(CO2JS_VERSION),
},
};
4 changes: 3 additions & 1 deletion .esbuild.esm.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ const esbuild = require('esbuild')
// For this build however we need to filter out some extra files
// that are used for nodejs, but not in browsers, so we use the
// library directly instead of using `esbuild-plugin-glob` as a plugin
const glob = require('tiny-glob');
const glob = require('tiny-glob')
const esbuildCommon = require('./.esbuild.common')

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

esbuild.build({
...esbuildCommon,
entryPoints: justBrowserCompatibleFiles,
bundle: false,
minify: false,
Expand Down
2 changes: 2 additions & 0 deletions .esbuild.node.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
const { globPlugin } = require('esbuild-plugin-glob');
const esbuildCommon = require('./.esbuild.common');

function main() {
require('esbuild').build({
...esbuildCommon,
entryPoints: ['src/**/!(*.test.js|test-constants.js|!(*.js))'],
bundle: false,
minify: false,
Expand Down
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@
"intensity-data:average": "node data/functions/generate_average_co2.js",
"intensity-data:marginal": "node data/functions/generate_marginal_co2.js",
"intensity-data": "npm run intensity-data:average && npm run intensity-data:marginal && npm run format-data",
"format-data": "cd data && prettier --write '**/*.{js,json}'"
"format-data": "cd data && prettier --write '**/*.{js,json}'",
"version": "npm run build"
},
"keywords": [
"sustainability",
Expand Down Expand Up @@ -75,4 +76,4 @@
"type": "git",
"url": "https://github.com/thegreenwebfoundation/co2.js.git"
}
}
}
12 changes: 11 additions & 1 deletion src/helpers/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -172,4 +172,14 @@ function parseOptions(options) {
return adjustments;
}

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

export { formatNumber, parseOptions, getApiRequestHeaders };
23 changes: 16 additions & 7 deletions src/hosting-api.js
Original file line number Diff line number Diff line change
@@ -1,27 +1,33 @@
"use strict";

import { getApiRequestHeaders } from "./helpers/index.js";

/**
* Check if a string or array of domains has been provided
* @param {string|array} domain - The domain to check, or an array of domains to be checked.
*/

function check(domain) {
function check(domain, comment) {
// is it a single domain or an array of them?
if (typeof domain === "string") {
return checkAgainstAPI(domain);
return checkAgainstAPI(domain, comment);
} else {
return checkDomainsAgainstAPI(domain);
return checkDomainsAgainstAPI(domain, comment);
}
}

/**
* Check if a domain is hosted by a green web host by querying the Green Web Foundation API.
* @param {string} domain - The domain to check.
* @param {string} comment - Optional. The app, site, or organisation that is making the request.
* @returns {boolean} - A boolean indicating whether the domain is hosted by a green web host.
*/
async function checkAgainstAPI(domain) {
async function checkAgainstAPI(domain, comment) {
const req = await fetch(
`https://api.thegreenwebfoundation.org/greencheck/${domain}`
`https://api.thegreenwebfoundation.org/greencheck/${domain}`,
{
headers: getApiRequestHeaders(comment),
}
);
const res = await req.json();
return res.green;
Expand All @@ -30,15 +36,18 @@ async function checkAgainstAPI(domain) {
/**
* Check if an array of domains is hosted by a green web host by querying the Green Web Foundation API.
* @param {array} domains - An array of domains to check.
* @param {string} comment - Optional. The app, site, or organisation that is making the request.
* @returns {array} - An array of domains that are hosted by a green web host.
*/

async function checkDomainsAgainstAPI(domains) {
async function checkDomainsAgainstAPI(domains, comment) {
try {
const apiPath = "https://api.thegreenwebfoundation.org/v2/greencheckmulti";
const domainsString = JSON.stringify(domains);

const req = await fetch(`${apiPath}/${domainsString}`);
const req = await fetch(`${apiPath}/${domainsString}`, {
headers: getApiRequestHeaders(comment),
});

const allGreenCheckResults = await req.json();

Expand Down
44 changes: 24 additions & 20 deletions src/hosting-api.test.js
Original file line number Diff line number Diff line change
@@ -1,35 +1,39 @@
"use strict";

import hosting from "./hosting-node.js";
import nock from "nock";
import https from "https";
/* eslint-disable jest/no-disabled-tests */

process.env.CO2JS_VERSION = "1.2.34";
const requestHeaderComment = "TestRunner";

describe("hostingAPI", () => {
let httpsGetSpy;
beforeEach(() => {
httpsGetSpy = jest.spyOn(https, "get");
});
afterEach(() => {
jest.restoreAllMocks();
});
describe("checking a single domain with #check", () => {
it.skip("using the API", async () => {
const scope = nock("https://api.thegreenwebfoundation.org/")
.get("/greencheck/google.com")
.reply(200, {
url: "google.com",
green: true,
});
const res = await hosting.check("google.com");
const res = await hosting.check("google.com", null, requestHeaderComment);
expect(res).toEqual(true);
});
it("sets the correct user agent header", async () => {
const res = await hosting.check("google.com", null, requestHeaderComment);
expect(httpsGetSpy).toHaveBeenCalledTimes(1);
expect(httpsGetSpy).toHaveBeenLastCalledWith(
expect.any(String),
expect.objectContaining({
headers: { "User-Agent": "co2js/1.2.34 TestRunner" },
}),
expect.any(Function)
);
});
});
describe("implicitly checking multiple domains with #check", () => {
it.skip("using the API", async () => {
const scope = nock("https://api.thegreenwebfoundation.org/")
.get("/v2/greencheckmulti/[%22google.com%22,%22kochindustries.com%22]")
.reply(200, {
"google.com": {
url: "google.com",
green: true,
},
"kochindustries.com": {
url: "kochindustries.com",
green: null,
},
});
const res = await hosting.check(["google.com", "kochindustries.com"]);
expect(res).toContain("google.com");
});
Expand Down
57 changes: 35 additions & 22 deletions src/hosting-node.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,33 +11,39 @@ This lets us keep the total library small, and dependencies minimal.
import https from "https";

import hostingJSON from "./hosting-json.node.js";
import { getApiRequestHeaders } from "./helpers/index.js";

/**
* Accept a url and perform an http request, returning the body
* for parsing as JSON.
*
* @param {string} url
* @param {string} comment - Optional. The app, site, or organisation that is making the request.
* @return {string}
*/
async function getBody(url) {
async function getBody(url, comment) {
return new Promise(function (resolve, reject) {
// Do async job
const req = https.get(url, function (res) {
if (res.statusCode < 200 || res.statusCode >= 300) {
return reject(
new Error(
`Could not get info from: ${url}. Status Code: ${res.statusCode}`
)
);
}
const data = [];
const req = https.get(
url,
{ headers: getApiRequestHeaders(comment) },
function (res) {
if (res.statusCode < 200 || res.statusCode >= 300) {
return reject(
new Error(
`Could not get info from: ${url}. Status Code: ${res.statusCode}`
)
);
}
const data = [];

res.on("data", (chunk) => {
data.push(chunk);
});
res.on("data", (chunk) => {
data.push(chunk);
});

res.on("end", () => resolve(Buffer.concat(data).toString()));
});
res.on("end", () => resolve(Buffer.concat(data).toString()));
}
);
req.end();
});
}
Expand All @@ -46,46 +52,53 @@ async function getBody(url) {
* Check if a domain is hosted by a green web host.
* @param {string|array} domain - The domain to check, or an array of domains to be checked.
* @param {object} db - Optional. A database object to use for lookups.
* @param {string} comment - Optional. The app, site, or organisation that is making the request.
* @returns {boolean|array} - A boolean if a string was provided, or an array of booleans if an array of domains was provided.
*/

function check(domain, db) {
function check(domain, db, comment) {
if (db) {
return hostingJSON.check(domain, db);
}

// is it a single domain or an array of them?
if (typeof domain === "string") {
return checkAgainstAPI(domain);
return checkAgainstAPI(domain, comment);
} else {
return checkDomainsAgainstAPI(domain);
return checkDomainsAgainstAPI(domain, comment);
}
}

/**
* Check if a domain is hosted by a green web host by querying the Green Web Foundation API.
* @param {string} domain - The domain to check.
* @param {string} comment - Optional. The app, site, or organisation that is making the request.
* @returns {boolean} - A boolean indicating whether the domain is hosted by a green web host.
*/
async function checkAgainstAPI(domain) {
async function checkAgainstAPI(domain, comment) {
const res = JSON.parse(
await getBody(`https://api.thegreenwebfoundation.org/greencheck/${domain}`)
await getBody(
`https://api.thegreenwebfoundation.org/greencheck/${domain}`,
comment
)
);
return res.green;
}

/**
* Check if an array of domains is hosted by a green web host by querying the Green Web Foundation API.
* @param {array} domains - An array of domains to check.
* @param {string} comment - Optional. The app, site, or organisation that is making the request.
* @returns {array} - An array of domains that are hosted by a green web host.
*/
async function checkDomainsAgainstAPI(domains) {
async function checkDomainsAgainstAPI(domains, comment) {
try {
const allGreenCheckResults = JSON.parse(
await getBody(
`https://api.thegreenwebfoundation.org/v2/greencheckmulti/${JSON.stringify(
domains
)}`
)}`,
comment
)
);
return hostingJSON.greenDomainsFromResults(allGreenCheckResults);
Expand Down
4 changes: 2 additions & 2 deletions src/hosting.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import hostingAPI from "./hosting-api.js";
* @param {string|array} domain - The domain to check, or an array of domains to be checked.
* @returns {boolean|array} - A boolean if a string was provided, or an array of booleans if an array of domains was provided.
*/
function check(domain) {
return hostingAPI.check(domain);
function check(domain, comment) {
return hostingAPI.check(domain, comment);
}

export default {
Expand Down
20 changes: 20 additions & 0 deletions src/hosting.test.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
"use strict";

import fs from "fs";
import https from "https";
import path from "path";

import pagexray from "pagexray";

import hosting from "./hosting-node.js";

process.env.CO2JS_VERSION = "1.2.34";
const requestHeaderComment = "TestRunner";

const jsonPath = path.resolve(
__dirname,
"..",
Expand All @@ -17,13 +21,18 @@ const jsonPath = path.resolve(

describe("hosting", () => {
let har;
let httpsGetSpy;
beforeEach(() => {
har = JSON.parse(
fs.readFileSync(
path.resolve(__dirname, "../data/fixtures/tgwf.har"),
"utf8"
)
);
httpsGetSpy = jest.spyOn(https, "get");
});
afterEach(() => {
jest.restoreAllMocks();
});
describe("checking all domains on a page object with #checkPage", () => {
it("returns a list of green domains, when passed a page object", async () => {
Expand Down Expand Up @@ -57,6 +66,17 @@ describe("hosting", () => {
const res = await hosting.check("google.com");
expect(res).toEqual(true);
});
it("sets the correct user agent header", async () => {
await hosting.check("google.com", null, requestHeaderComment);
expect(httpsGetSpy).toHaveBeenCalledTimes(1);
expect(httpsGetSpy).toHaveBeenLastCalledWith(
expect.any(String),
expect.objectContaining({
headers: { "User-Agent": "co2js/1.2.34 TestRunner" },
}),
expect.any(Function)
);
});
});
describe("checking multiple domains with #check", () => {
it("Use the API", async () => {
Expand Down