Ctrl K
ComunidadGuías…

lib/github-star-count.js

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
// github-star-count.js - Cached public star count for the fixed Clay repository.
//
// Server-wide and unauthenticated toward GitHub: one fixed public metadata
// URL, no credentials, no redirects, no user data. A fresh value is reused for
// an hour, concurrent callers share one request, failures back off, and the
// last successful count survives a failed refresh. Unknown stays null; a count
// is never invented.

var https = require("https");

var REPO = "chadbyte/clay";
var HOST = "api.github.com";
var PATH = "/repos/" + REPO;
var TTL_MS = 60 * 60 * 1000;
var TIMEOUT_MS = 5000;
var BACKOFF_BASE_MS = 60 * 1000;
var MAX_BODY_BYTES = 256 * 1024;

// Resolves { status, body } or rejects. Never follows redirects. `timeoutMs`
// is a wall-clock deadline for the whole exchange (DNS, TLS, headers, body),
// not socket inactivity, so a slow trickle cannot pin the request. `get` is
// injectable for tests.
function fetchRepoMetadata(timeoutMs, get) {
  get = get || https.get;
  return new Promise(function (resolve, reject) {
    var settled = false;
    var req = null;
    var deadline = null;
    function finish(fn, value) {
      if (settled) return;
      settled = true;
      clearTimeout(deadline);
      fn(value);
    }
    function fail(err) {
      finish(reject, err);
      if (req) req.destroy();
    }
    deadline = setTimeout(function () { fail(new Error("timeout")); }, timeoutMs);
    try {
      req = get({
        host: HOST,
        path: PATH,
        method: "GET",
        headers: {
          "User-Agent": "clay-server",
          "Accept": "application/vnd.github+json",
          "X-GitHub-Api-Version": "2022-11-28",
        },
      }, function (res) {
        var chunks = [];
        var size = 0;
        res.on("data", function (chunk) {
          size += chunk.length;
          if (size > MAX_BODY_BYTES) {
            fail(new Error("response too large"));
            if (res.destroy) res.destroy();
            return;
          }
          chunks.push(chunk);
        });
        res.on("end", function () {
          finish(resolve, { status: res.statusCode, body: Buffer.concat(chunks).toString("utf8") });
        });
        res.on("error", fail);
        res.on("aborted", function () { fail(new Error("aborted")); });
      });
    } catch (err) {
      fail(err);
      return;
    }
    req.on("error", fail);
    if (settled) req.destroy();
  });
}

function parseStars(response) {
  if (!response || response.status !== 200) return null;
  var data;
  try { data = JSON.parse(response.body); } catch (e) { return null; }
  var n = data && data.stargazers_count;
  if (typeof n !== "number" || !isFinite(n) || n < 0 || Math.floor(n) !== n) return null;
  return n;
}

// opts (all optional, for tests): fetch(timeoutMs) -> Promise<{status, body}>,
// now() -> ms, ttlMs, timeoutMs, backoffBaseMs.
function createGithubStarCount(opts) {
  opts = opts || {};
  var fetchFn = opts.fetch || fetchRepoMetadata;
  var now = opts.now || Date.now;
  var ttlMs = opts.ttlMs || TTL_MS;
  var timeoutMs = opts.timeoutMs || TIMEOUT_MS;
  var backoffBaseMs = opts.backoffBaseMs || BACKOFF_BASE_MS;

  var stars = null;
  var fetchedAt = null;
  var failures = 0;
  var retryAt = 0;
  var inflight = null;

  function snapshot() {
    return { repo: REPO, stars: stars, fetchedAt: fetchedAt };
  }

  function refresh() {
    var started;
    try {
      started = Promise.resolve(fetchFn(timeoutMs));
    } catch (e) {
      started = Promise.reject(e);
    }
    return started.then(function (response) {
      var parsed = parseStars(response);
      if (parsed === null) throw new Error("unusable response");
      stars = parsed;
      fetchedAt = now();
      failures = 0;
      retryAt = 0;
    }).catch(function () {
      failures++;
      retryAt = now() + Math.min(backoffBaseMs * Math.pow(2, failures - 1), ttlMs);
    }).then(function () {
      inflight = null;
      return snapshot();
    });
  }

  function get() {
    if (stars !== null && fetchedAt !== null && now() - fetchedAt < ttlMs) return Promise.resolve(snapshot());
    if (inflight) return inflight;
    if (now() < retryAt) return Promise.resolve(snapshot());
    inflight = refresh();
    return inflight;
  }

  return { get: get };
}

module.exports = {
  createGithubStarCount: createGithubStarCount,
  parseStars: parseStars,
  fetchRepoMetadata: fetchRepoMetadata,
  MAX_BODY_BYTES: MAX_BODY_BYTES,
  REPO: REPO,
  TTL_MS: TTL_MS,
};