Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
9 changes: 5 additions & 4 deletions apps/site/next-data/generators/supportersData.mjs
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import { OPENCOLLECTIVE_MEMBERS_URL } from '#site/next.constants.mjs';
import { fetchWithRetry } from '#site/util/fetch';

/**
* Fetches supporters data from Open Collective API, filters active backers,
* and maps it to the Supporters type.
*
* @returns {Promise<Array<import('#site/types/supporters')>>} Array of supporters
* @returns {Promise<Array<import('#site/types/supporters').OpenCollectiveSupporter>>} Array of supporters
*/
async function fetchOpenCollectiveData() {
const endpoint = 'https://opencollective.com/nodejs/members/all.json';

const response = await fetch(endpoint);
const response = await fetchWithRetry(OPENCOLLECTIVE_MEMBERS_URL);

const payload = await response.json();

Expand Down
28 changes: 10 additions & 18 deletions apps/site/next-data/generators/vulnerabilities.mjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { VULNERABILITIES_URL } from '#site/next.constants.mjs';
import { fetchWithRetry } from '#site/util/fetch';

const RANGE_REGEX = /([<>]=?)\s*(\d+)(?:\.(\d+))?/;
const V0_REGEX = /^0\.\d+(\.x)?$/;
const VER_REGEX = /^\d+\.x$/;

/**
* Fetches vulnerability data from the Node.js Security Working Group repository,
Expand All @@ -9,7 +12,7 @@ const RANGE_REGEX = /([<>]=?)\s*(\d+)(?:\.(\d+))?/;
* @returns {Promise<import('#site/types/vulnerabilities').GroupedVulnerabilities>} Grouped vulnerabilities
*/
export default async function generateVulnerabilityData() {
const response = await fetch(VULNERABILITIES_URL);
const response = await fetchWithRetry(VULNERABILITIES_URL);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's check if the response status is ok (200) before parsing the body. Otherwise, throw error too.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@araujogui I kinda agree, but, this should be done in another PR, not this one, IMO. Since Aviv did not change anything related to this. Don't you agree? 👀

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO if there's a 404 that produces invalid JSON, it'll throw anyway.

That's not a fetch failure, that's a JSON parsing error.


/** @type {Array<import('#site/types/vulnerabilities').RawVulnerability>} */
const data = Object.values(await response.json());
Expand All @@ -26,14 +29,14 @@ export default async function generateVulnerabilityData() {
// Helper function to process version patterns
const processVersion = (version, vulnerability) => {
// Handle 0.X versions (pre-semver)
if (/^0\.\d+(\.x)?$/.test(version)) {
if (V0_REGEX.test(version)) {
addToGroup('0', vulnerability);

return;
}

// Handle simple major.x patterns (e.g., 12.x)
if (/^\d+\.x$/.test(version)) {
if (VER_REGEX.test(version)) {
const majorVersion = version.split('.')[0];

addToGroup(majorVersion, vulnerability);
Expand Down Expand Up @@ -67,25 +70,14 @@ export default async function generateVulnerabilityData() {
}
};

for (const vulnerability of Object.values(data)) {
const parsedVulnerability = {
cve: vulnerability.cve,
url: vulnerability.ref,
vulnerable: vulnerability.vulnerable,
patched: vulnerability.patched,
description: vulnerability.description,
overview: vulnerability.overview,
affectedEnvironments: vulnerability.affectedEnvironments,
severity: vulnerability.severity,
};
for (const { ref, ...vulnerability } of Object.values(data)) {
vulnerability.url = ref;

// Process all potential versions from the vulnerable field
const versions = parsedVulnerability.vulnerable
.split(' || ')
.filter(Boolean);
const versions = vulnerability.vulnerable.split(' || ').filter(Boolean);

for (const version of versions) {
processVersion(version, parsedVulnerability);
processVersion(version, vulnerability);
}
}

Expand Down
3 changes: 2 additions & 1 deletion apps/site/next.calendar.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
BASE_CALENDAR_URL,
SHARED_CALENDAR_KEY,
} from './next.calendar.constants.mjs';
import { fetchWithRetry } from './util/fetch';

/**
*
Expand Down Expand Up @@ -33,7 +34,7 @@ export const getCalendarEvents = async (calendarId = '', maxResults = 20) => {
calendarQueryUrl.searchParams.append(key, value)
);

return fetch(calendarQueryUrl)
return fetchWithRetry(calendarQueryUrl)
.then(response => response.json())
.then(calendar => calendar.items ?? []);
};
6 changes: 6 additions & 0 deletions apps/site/next.constants.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -213,3 +213,9 @@ export const EOL_VERSION_IDENTIFIER = 'End-of-life';
*/
export const VULNERABILITIES_URL =
'https://raw.githubusercontent.com/nodejs/security-wg/main/vuln/core/index.json';

/**
* The location of the OpenCollective data
*/
export const OPENCOLLECTIVE_MEMBERS_URL =
'https://opencollective.com/nodejs/members/all.json';
1 change: 1 addition & 0 deletions apps/site/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@ export * from './download';
export * from './userAgent';
export * from './vulnerabilities';
export * from './page';
export * from './supporters';
9 changes: 9 additions & 0 deletions apps/site/types/supporters.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export type Supporter<T extends string> = {
name: string;
image: string;
url: string;
profile: string;
source: T;
};

export type OpenCollectiveSupporter = Supporter<'opencollective'>;
34 changes: 34 additions & 0 deletions apps/site/util/fetch.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { setTimeout } from 'node:timers/promises';

type RetryOptions = RequestInit & {
maxRetry?: number;
delay?: number;
};

type FetchError = {
cause: {
code: string;
};
};

export const fetchWithRetry = async (
url: string,
{ maxRetry = 3, delay = 100, ...options }: RetryOptions = {}
) => {
for (let i = 1; i <= maxRetry; i++) {
try {
return fetch(url, options);
} catch (e) {
console.debug(
`fetch of ${url} failed at ${Date.now()}, attempt ${i}/${maxRetry}`,
e
);

if (i === maxRetry || (e as FetchError).cause.code !== 'ETIMEDOUT') {
throw e;
}

await setTimeout(delay * i);
}
}
};
Loading