Feb 08, 2022

Hero Image

I have published a few libraries on Jitpack.

I like Jitpack for its simplicity. Besides artifact hosting, it offers release badges, and a free, public API to query statistics about published libraries. One such stat is the number of weekly and monthly downloads.

I wanted to display the number of downloads as a badge on one of my library’s README file, but Jitpack does not offer badges for download statistics.

Given the lack of official support, I started looking for alternatives and discovered Shields.io, a platform that offers many different types of badges for free. Unfortunately, it does not have badges for Jitpack download stats either. It does have the ability to build badges from custom API endpoints, as long as the API responds with a fixed predefined schema.


Shields.io Schema

PropertyDescription
schemaVersionRequired. Always the number 1.
labelRequired. The left text, or the empty string to omit the left side of the badge. This can be overridden by the query string.
messageRequired. Can’t be empty. The right text.
colorDefault: lightgrey. The right color. Supports the eight named colors above, as well as hex, rgb, rgba, hsl, hsla and css named colors. This can be overridden by the query string.
cacheSecondsDefault: 300, min 300. Set the HTTP cache lifetime in seconds, which should be respected by the Shields’ CDN and downstream users. Values below 300 will be ignored. This lets you tune performance and traffic vs. responsiveness. The value you specify can be overridden by the user via the query string, but only to a longer value.

Here’s what the response of Jitpack’s download stats API looks like:

{
  "week": 1707,
  "month": 22214
}

To create my badge, I needed to build a service that acts as an adapter between Jitpack and Shield.io.

Flow

A serverless function felt like the natural fit for this task. I could use one of a number of available options:

I decided I wanted to learn something new, so I went with a Cloudflare Worker instead.

Cloudflare offers a generous free tier, but I was surprised to learn about the 50ms CPU time limit on a worker. Even if Jitpack’s API was extremely fast, it would surely take more than 50ms to fetch download stats for an artifact.

After reading some docs, I realized the time limit is for active CPU usage only. Most of my worker’s time would be spent awaiting a fetch() API call, and thankfully that won’t count as active CPU time.

Cloudflare’s quickstart guide was enough to teach me everything I needed to build the worker. Ignoring all the setup with the wrangler CLI, here’s what the worker looks like:

const baseUrl = "https://jitpack.io/api";

addEventListener("fetch", (event) => {
  event.respondWith(handleRequest(event.request));
});

async function handleRequest(request) {
  const url = new URL(request.url);

  const group = url.searchParams.get("group");
  const artifact = url.searchParams.get("artifact");
  const frequency = url.searchParams.get("frequency");

  let res;
  switch (frequency) {
    case "monthly":
      res = await getMonthlyDownloads(group, artifact);
      break;
    case "weekly":
      res = await getWeeklyDownloads(group, artifact);
      break;
    default:
      return new Response(null, { status: 400 });
  }

  return new Response(JSON.stringify(res), {
    headers: { "content-type": "application/json" },
  });
}

async function getMonthlyDownloads(groupId, artifactId) {
  const downloadsUrl = `${baseUrl}/downloads/${groupId}/${artifactId}`;
  const response = await fetch(downloadsUrl);
  const { month: monthlyDownloads } = await response.json();

  const res = {
    schemaVersion: 1,
    label: "Downloads",
    message: `${monthlyDownloads}/month`,
    color: "green",
  };

  return res;
}

async function getWeeklyDownloads(groupId, artifactId) {
  const downloadsUrl = `${baseUrl}/downloads/${groupId}/${artifactId}`;
  const response = await fetch(downloadsUrl);
  const { week: weeklyDownloads } = await response.json();

  const res = {
    schemaVersion: 1,
    label: "Downloads",
    message: `${weeklyDownloads}/week`,
    color: "green",
  };

  return res;
}

I then deployed it with wrangler publish, and then created a custom shield on Shields.io.

The result: Result

I’m pretty happy with how it turned out. This was my first experience with Cloudflare Workers, and it left a positive impression. I might start using it more in the near future.


Question, comments or feedback? Feel free to reach out to me on Twitter @haroldadmin