Skip to content
Merged
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
13 changes: 8 additions & 5 deletions src/routes/errors.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import * as Sentry from '@sentry/cloudflare';
import type { Hono } from 'hono';

import cache from '../utils/cache.ts';
import notFound from '../utils/notFound.ts';
import respond from '../utils/respond.ts';
import event from '../utils/event.ts';
import respond, { notFound, withCache } from '../utils/respond.ts';

import type { ErrorResponse } from './errors.schema.ts';

Expand Down Expand Up @@ -48,11 +47,15 @@ export default (app: Hono) => {
// Handle errors
app.onError((err, ctx) => {
// Log the error
console.error(err.stack);
const sentry = Sentry.captureException(err);
event('unhandled-error', {
ctx,
level: 'error',
data: { message: err.message, stack: err.stack, sentry },
});

// Never cache this
cache(ctx, -1);
withCache(ctx, -1);

// Send the error response
ctx.status(500);
Expand Down
8 changes: 4 additions & 4 deletions src/routes/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { env } from 'cloudflare:workers';
import type { Context, Hono } from 'hono';

import cache from '../utils/cache.ts';
import { withCache } from '../utils/respond.ts';

/**
* Handle GET / requests.
Expand All @@ -11,7 +11,7 @@ import cache from '../utils/cache.ts';
const handleGet = (ctx: Context) => {
// Set a 355 day (same as CDN) life on this response
// This is also immutable
cache(ctx, 355 * 24 * 60 * 60, true);
withCache(ctx, 355 * 24 * 60 * 60, true);

// Redirect to the API docs
return ctx.redirect('https://cdnjs.com/api', 301);
Expand All @@ -24,7 +24,7 @@ const handleGet = (ctx: Context) => {
*/
const handleGetHealth = (ctx: Context) => {
// Don't cache health, ensure its always live
cache(ctx, -1);
withCache(ctx, -1);

// If we have a known release, include a header for it
if (env.SENTRY_RELEASE) {
Expand All @@ -43,7 +43,7 @@ const handleGetHealth = (ctx: Context) => {
const handleGetRobotsTxt = (ctx: Context) => {
// Set a 355 day (same as CDN) life on this response
// This is also immutable
cache(ctx, 355 * 24 * 60 * 60, true);
withCache(ctx, 355 * 24 * 60 * 60, true);

// Disallow all robots
return ctx.text('User-agent: *\nDisallow: /');
Expand Down
5 changes: 2 additions & 3 deletions src/routes/libraries.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import type { Context, Hono } from 'hono';

import { libraries } from '../utils/algolia.ts';
import cache from '../utils/cache.ts';
import filter from '../utils/filter.ts';
import { queryArray, queryCheck } from '../utils/query.ts';
import respond from '../utils/respond.ts';
import respond, { withCache } from '../utils/respond.ts';

import type { LibrariesResponse } from './libraries.schema.ts';

Expand Down Expand Up @@ -44,7 +43,7 @@ const handleGetLibraries = async (ctx: Context) => {
const trimmed = limit ? response.slice(0, limit) : response;

// Set a 6 hour life on this response
cache(ctx, 6 * 60 * 60);
withCache(ctx, 6 * 60 * 60);

// Send the response
return respond(ctx, {
Expand Down
2 changes: 1 addition & 1 deletion src/routes/library.schema.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as z from 'zod';

import { librarySchema } from '../utils/kvMetadata.schema';
import { librarySchema } from '../utils/metadata.schema';

export const libraryVersionResponseSchema = z
.object({
Expand Down
54 changes: 45 additions & 9 deletions src/routes/library.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,69 @@
import type { Context, Hono } from 'hono';

import cache from '../utils/cache.ts';
import files from '../utils/files.ts';
import filter from '../utils/filter.ts';
import {
library,
libraryVersion,
libraryVersionSri,
libraryVersions,
} from '../utils/kvMetadata.ts';
import notFound from '../utils/notFound.ts';
} from '../utils/metadata.ts';
import { queryCheck } from '../utils/query.ts';
import respond from '../utils/respond.ts';
import sriForVersion from '../utils/sriForVersion.ts';
import respond, { notFound, withCache } from '../utils/respond.ts';

import type {
LibraryResponse,
LibraryVersionResponse,
} from './library.schema.ts';

const extensions = Object.keys(files);
/**
* Create a map of file names to SRI hashes, based on library files and SRI data.
*
* @param library Name of the library.
* @param version Version of the library.
* @param files Names of the files for this version of the library.
* @param sriData SRI data for the library version.
*/
const sriForVersion = (
library: string,
version: string,
files: string[],
sriData: Record<string, string>,
) => {
// Build the SRI object
const sri: Record<string, string> = {};
for (const file of files) {
const fullFile = `${library}/${version}/${file}`;

// If we have an SRI entry for this, add it
if (sriData && sriData[fullFile]) {
sri[file] = sriData[fullFile];
continue;
}

// If we don't have an SRI entry, but expect one, error!
// if (file.endsWith('.js') || file.endsWith('.css')) {
// Sentry.withScope(scope => {
// scope.setTag('library', library);
// scope.setTag('library.version', version);
// scope.setTag('library.file', file);
// scope.setTag('library.file.full', fullFile);
// Sentry.captureException(new Error('Missing SRI entry'));
// });
// }
}

// Done
return sri;
};

/**
* Check if a file is whitelisted for cdnjs, based on its extension.
*
* @param file Filename to check.
*/
const whitelisted = (file: string) =>
extensions.includes(file.split('.').slice(-1)[0] || '');
Object.keys(files).includes(file.split('.').slice(-1)[0] || '');

/**
* Handle GET /libraries/:library/:version requests.
Expand Down Expand Up @@ -85,7 +121,7 @@ const handleGetLibraryVersion = async (ctx: Context) => {

// Set a 355 day (same as CDN) life on this response
// This is also immutable as a version will never change
cache(ctx, 355 * 24 * 60 * 60, true);
withCache(ctx, 355 * 24 * 60 * 60, true);

// Send the response
return respond(ctx, response);
Expand Down Expand Up @@ -196,7 +232,7 @@ const handleGetLibrary = async (ctx: Context) => {
}

// Set a 6 hour life on this response
cache(ctx, 6 * 60 * 60);
withCache(ctx, 6 * 60 * 60);

// Send the response
return respond(ctx, response);
Expand Down
7 changes: 3 additions & 4 deletions src/routes/stats.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import type { Context, Hono } from 'hono';

import cache from '../utils/cache.ts';
import filter from '../utils/filter.ts';
import { libraries } from '../utils/kvMetadata.ts';
import { libraries } from '../utils/metadata.ts';
import { queryCheck } from '../utils/query.ts';
import respond from '../utils/respond.ts';
import respond, { withCache } from '../utils/respond.ts';

import type { StatsResponse } from './stats.schema.ts';

Expand All @@ -23,7 +22,7 @@ const handleGetStats = async (ctx: Context) => {
);

// Set a 6 hour life on this response
cache(ctx, 6 * 60 * 60);
withCache(ctx, 6 * 60 * 60);

// Send the response
return respond(ctx, response);
Expand Down
5 changes: 2 additions & 3 deletions src/routes/whitelist.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import type { Context, Hono } from 'hono';

import cache from '../utils/cache.ts';
import files from '../utils/files.ts';
import filter from '../utils/filter.ts';
import { queryCheck } from '../utils/query.ts';
import respond from '../utils/respond.ts';
import respond, { withCache } from '../utils/respond.ts';

import type { WhitelistResponse } from './whitelist.schema.ts';

Expand All @@ -24,7 +23,7 @@ const handleGetWhitelist = (ctx: Context) => {
);

// Set a 6 hour life on this response
cache(ctx, 6 * 60 * 60);
withCache(ctx, 6 * 60 * 60);

// Send the response
return respond(ctx, response);
Expand Down
16 changes: 10 additions & 6 deletions src/utils/algolia.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { env, waitUntil } from 'cloudflare:workers';
import * as z from 'zod';

import { type Library, librarySchema } from './algolia.schema.ts';
import event from './event.ts';

/**
* Convert an ArrayBuffer to a hex string.
Expand Down Expand Up @@ -106,17 +107,20 @@ export const libraries = async (
if (parsed.success) {
hits.push(parsed.data);
} else {
console.warn(
'Found bad entry in Algolia data',
parsed.error.issues,
hit,
);
Sentry.withScope((scope) => {
scope.setExtra('issues', parsed.error.issues);
scope.setExtra('hit', hit);
Sentry.captureException(
const sentry = Sentry.captureException(
new Error('Bad entry in Algolia data'),
);
event('bad-algolia-entry', {
level: 'warn',
data: {
issues: parsed.error.issues,
hit,
sentry,
},
});
});
}
});
Expand Down
26 changes: 0 additions & 26 deletions src/utils/cache.ts

This file was deleted.

25 changes: 17 additions & 8 deletions src/utils/event.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,28 @@ import { routePath } from 'hono/route';
* Log an event for Workers Observability, including the route and user agent for context.
*
* @param name Name of the event to log.
* @param ctx Request context.
* @param data Additional data to include in the log.
* @param opts Options for the event.
* @param opts.ctx Request context for the event.
* @param opts.level Log level for the event.
* @param opts.data Additional data for the event.
*/
const event = (
name: string,
ctx: Context,
data: Record<string, unknown> = {},
{
ctx,
level = 'log',
data = {},
}: {
ctx?: Context;
level?: 'log' | 'debug' | 'info' | 'warn' | 'error';
data?: Record<string, unknown>;
} = {},
) => {
const route = routePath(ctx);
const ua = ctx.req.header('User-Agent') || '';
console.log(
const route = ctx ? routePath(ctx) : 'unknown';
const ua = (ctx && ctx.req.header('User-Agent')) || 'unknown';
console[level](
`event=${JSON.stringify(name)} route=${JSON.stringify(route)}`,
{ event: { name, route, ua, data } },
{ event: { name, route, ua, level, data } },
);
};

Expand Down
File renamed without changes.
File renamed without changes.
20 changes: 10 additions & 10 deletions src/utils/kvMetadata.ts → src/utils/metadata.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,30 @@
import { env } from 'cloudflare:workers';

import fetchJson from './fetchJson.ts';
import fetch from './fetch.ts';
import {
librariesSchema,
librarySchema,
libraryVersionSchema,
libraryVersionSriSchema,
libraryVersionsSchema,
} from './kvMetadata.schema.ts';
} from './metadata.schema.ts';
import sortVersions from './sort.ts';

const kvBase = env.METADATA_BASE || 'https://metadata.speedcdnjs.com';
const base = env.METADATA_BASE || 'https://metadata.speedcdnjs.com';

/**
* Get a list of libraries.
*/
export const libraries = () =>
fetchJson(`${kvBase}/packages`).then(librariesSchema.parse);
fetch(`${base}/packages`).then(librariesSchema.parse);

/**
* Get the metadata for a library.
*
* @param name Name of the library to fetch.
*/
export const library = (name: string) =>
fetchJson(`${kvBase}/packages/${encodeURIComponent(name)}`).then(
fetch(`${base}/packages/${encodeURIComponent(name)}`).then(
librarySchema.parse,
);

Expand All @@ -34,7 +34,7 @@ export const library = (name: string) =>
* @param name Name of the library to fetch.
*/
export const libraryVersions = (name: string) =>
fetchJson(`${kvBase}/packages/${encodeURIComponent(name)}/versions`)
fetch(`${base}/packages/${encodeURIComponent(name)}/versions`)
.then(libraryVersionsSchema.parse)
.then(sortVersions);

Expand All @@ -45,8 +45,8 @@ export const libraryVersions = (name: string) =>
* @param version Version of the library to fetch.
*/
export const libraryVersion = (name: string, version: string) =>
fetchJson(
`${kvBase}/packages/${encodeURIComponent(name)}/versions/${encodeURIComponent(version)}`,
fetch(
`${base}/packages/${encodeURIComponent(name)}/versions/${encodeURIComponent(version)}`,
).then(libraryVersionSchema.parse);

/**
Expand All @@ -56,6 +56,6 @@ export const libraryVersion = (name: string, version: string) =>
* @param version Version of the library to fetch.
*/
export const libraryVersionSri = (name: string, version: string) =>
fetchJson(
`${kvBase}/packages/${encodeURIComponent(name)}/sris/${encodeURIComponent(version)}`,
fetch(
`${base}/packages/${encodeURIComponent(name)}/sris/${encodeURIComponent(version)}`,
).then(libraryVersionSriSchema.parse);
Loading