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
39 changes: 24 additions & 15 deletions docs/javascripts/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,15 +40,19 @@ document.addEventListener('DOMContentLoaded', () => {
});

/* GitHub widget: live stars/forks from the authoritative GitHub API, with
* ungh.cc (a CDN-cached proxy) as a fallback and a 1-hour localStorage
* cache. Falls back silently if every source is unreachable. */
* ungh.cc (a CDN-cached proxy) as a fallback. Uses a stale-while-revalidate
* localStorage cache: a cached value paints instantly, then the count is
* revalidated in the background (at most once every few minutes, to stay
* under GitHub's rate limit) and repainted only if it changed — so a stale
* cache self-corrects on the next visit instead of freezing. Falls back
* silently if every source is unreachable. */
(function () {
const targets = document.querySelectorAll('[data-gh-stat]');
if (targets.length === 0) return;

const REPO = 'RayforceDB/rayforce';
const CACHE_KEY = 'gh-stats:' + REPO;
const CACHE_TTL = 60 * 60 * 1000; /* 1 hour */
const REVALIDATE_AFTER = 5 * 60 * 1000; /* skip the background refetch if the cache is younger than this */

function fmt(n) {
if (typeof n !== 'number' || isNaN(n)) return '—';
Expand Down Expand Up @@ -84,23 +88,30 @@ document.addEventListener('DOMContentLoaded', () => {

function readCache() {
try {
const raw = localStorage.getItem(CACHE_KEY);
if (!raw) return null;
const v = JSON.parse(raw);
if (!v || (Date.now() - v.t) > CACHE_TTL) return null;
return v;
const v = JSON.parse(localStorage.getItem(CACHE_KEY));
return (v && typeof v.stars === 'number' && typeof v.forks === 'number') ? v : null;
} catch (e) { return null; }
}

function writeCache(stars, forks) {
try { localStorage.setItem(CACHE_KEY, JSON.stringify({ t: Date.now(), stars, forks })); } catch (e) {}
}

/* Persist the latest counts, and repaint only when they actually changed —
* avoids re-animating an identical value the cache already showed. */
function update(stars, forks) {
const changed = !cached || cached.stars !== stars || cached.forks !== forks;
writeCache(stars, forks);
if (changed) paint(stars, forks);
}

/* Paint the cached value instantly (may be slightly stale), then revalidate. */
const cached = readCache();
if (cached) { paint(cached.stars, cached.forks); return; }
if (cached) paint(cached.stars, cached.forks);
if (cached && (Date.now() - cached.t) < REVALIDATE_AFTER) return; /* fresh enough; skip the network */

/* GitHub's own API is authoritative and fresh; the per-visitor 60/hour
* unauthenticated limit is ample for a single cached request per hour.
* unauthenticated limit is ample for one request every few minutes.
* ungh.cc is a CDN-cached proxy that can lag GitHub by ~a day, so it is
* used only as a fallback when the direct API is unreachable. */
fetch('https://api.github.com/repos/' + REPO, { headers: { Accept: 'application/vnd.github+json' } })
Expand All @@ -109,8 +120,7 @@ document.addEventListener('DOMContentLoaded', () => {
const stars = d && d.stargazers_count;
const forks = d && d.forks_count;
if (typeof stars !== 'number' || typeof forks !== 'number') throw new Error('gh shape');
writeCache(stars, forks);
paint(stars, forks);
update(stars, forks);
})
.catch(() => {
return fetch('https://ungh.cc/repos/' + REPO)
Expand All @@ -119,9 +129,8 @@ document.addEventListener('DOMContentLoaded', () => {
const stars = d && d.repo && d.repo.stars;
const forks = d && d.repo && d.repo.forks;
if (typeof stars !== 'number' || typeof forks !== 'number') throw new Error('ungh shape');
writeCache(stars, forks);
paint(stars, forks);
update(stars, forks);
});
})
.catch(() => { /* leave em-dash placeholders */ });
.catch(() => { /* leave whatever is painted (cache or em-dash placeholders) */ });
})();
43 changes: 25 additions & 18 deletions website/script.js
Original file line number Diff line number Diff line change
Expand Up @@ -270,16 +270,19 @@ document.addEventListener('DOMContentLoaded', () => {
});

/* GitHub widget: fetch live stars/forks from the authoritative GitHub API,
* with ungh.cc (a CDN-cached proxy) as a fallback and a 1-hour localStorage
* cache so repeat visits don't refetch. Falls back silently if every source
* is unreachable. */
* with ungh.cc (a CDN-cached proxy) as a fallback. Uses a stale-while-
* revalidate localStorage cache: a cached value paints instantly, then the
* count is revalidated in the background (at most once every few minutes, to
* stay under GitHub's rate limit) and repainted only if it changed — so a
* stale cache self-corrects on the next visit instead of freezing. Falls back
* silently if every source is unreachable. */
(function () {
const targets = document.querySelectorAll('[data-gh-stat]');
if (targets.length === 0) return;

const REPO = 'RayforceDB/rayforce';
const CACHE_KEY = 'gh-stats:' + REPO;
const CACHE_TTL = 60 * 60 * 1000; /* 1 hour */
const REVALIDATE_AFTER = 5 * 60 * 1000; /* skip the background refetch if the cache is younger than this */

function fmt(n) {
if (typeof n !== 'number' || isNaN(n)) return '—';
Expand Down Expand Up @@ -315,33 +318,38 @@ document.addEventListener('DOMContentLoaded', () => {

function readCache() {
try {
const raw = localStorage.getItem(CACHE_KEY);
if (!raw) return null;
const v = JSON.parse(raw);
if (!v || (Date.now() - v.t) > CACHE_TTL) return null;
return v;
const v = JSON.parse(localStorage.getItem(CACHE_KEY));
return (v && typeof v.stars === 'number' && typeof v.forks === 'number') ? v : null;
} catch (e) { return null; }
}

function writeCache(stars, forks) {
try { localStorage.setItem(CACHE_KEY, JSON.stringify({ t: Date.now(), stars, forks })); } catch (e) {}
}

/* If we have a fresh cached value, paint it instantly without a network roundtrip. */
/* Persist the latest counts, and repaint only when they actually changed —
* avoids re-animating an identical value the cache already showed. */
function update(stars, forks) {
const changed = !cached || cached.stars !== stars || cached.forks !== forks;
writeCache(stars, forks);
if (changed) paint(stars, forks);
}

/* Paint the cached value instantly (may be slightly stale), then revalidate. */
const cached = readCache();
if (cached) { paint(cached.stars, cached.forks); return; }
if (cached) paint(cached.stars, cached.forks);
if (cached && (Date.now() - cached.t) < REVALIDATE_AFTER) return; /* fresh enough; skip the network */

/* Primary: the authoritative GitHub API. The per-visitor 60/hour
* unauthenticated limit is ample for a single cached request per hour,
* and it is always fresh. Response shape: { stargazers_count, forks_count } */
* unauthenticated limit is ample for one request every few minutes, and it
* is always fresh. Response shape: { stargazers_count, forks_count } */
fetch('https://api.github.com/repos/' + REPO, { headers: { Accept: 'application/vnd.github+json' } })
.then(r => r.ok ? r.json() : Promise.reject(new Error('gh ' + r.status)))
.then(d => {
const stars = d && d.stargazers_count;
const forks = d && d.forks_count;
if (typeof stars !== 'number' || typeof forks !== 'number') throw new Error('gh shape');
writeCache(stars, forks);
paint(stars, forks);
update(stars, forks);
})
.catch(() => {
/* Fallback: ungh.cc (CDN-cached proxy, can lag GitHub by ~a day, but
Expand All @@ -352,11 +360,10 @@ document.addEventListener('DOMContentLoaded', () => {
const stars = d && d.repo && d.repo.stars;
const forks = d && d.repo && d.repo.forks;
if (typeof stars !== 'number' || typeof forks !== 'number') throw new Error('ungh shape');
writeCache(stars, forks);
paint(stars, forks);
update(stars, forks);
});
})
.catch(() => {
/* Leave the static "—" placeholders. Users still get a working link. */
/* Leave whatever is painted (cache or the static "—" placeholders). */
});
})();
Loading