diff --git a/index.ts b/index.ts index a3f404e..3bf49a2 100644 --- a/index.ts +++ b/index.ts @@ -1,6 +1,84 @@ import "dotenv/config"; +const AUTH_KEY = Bun.env.TBA_AUTH_KEY; +if (!AUTH_KEY) { + process.exit(); +} + +const authHeaders = new Headers({ + "X-TBA-Auth-Key": AUTH_KEY, +}); + const BASE_URL = "https://www.thebluealliance.com/api/v3"; -console.log("Hello via Bun!"); +interface CacheEntry { + etag: string; + data: string; + staleTime: number; +} + +const tbaCache: { [name: string]: CacheEntry } = {}; + +interface TBAResponse { + data: string; + status: number; +} + +function updateCache( + endpoint: string, + etag: string | null, + data: string, + maxAge: number, +) { + if (etag) { + tbaCache[endpoint] = { etag, data, staleTime: Date.now() + maxAge }; + } +} + +function fetchCache(endpoint: string) { + return tbaCache[endpoint]; +} + +async function getTBAEndpoint(endpoint: string): Promise { + const time = Date.now(); + console.log(tbaCache[endpoint]); + if (tbaCache[endpoint] && tbaCache[endpoint].staleTime > time) { + console.log("Cache hit... " + endpoint); + return { data: fetchCache(endpoint).data, status: 200 }; + } + + console.log("Fetching... " + endpoint); + const headers = new Headers(authHeaders); + const response = await fetch(BASE_URL + endpoint, { headers }); + + let data; + if (response.status === 200) { + data = await response.text(); + const maxAge = parseInt( + response.headers + .get("cache-control") + ?.split(",") + .filter((d) => d.includes("max-age"))[0] + .trim() + .split("=")[1] as string, + ); + updateCache(endpoint, response.headers.get("ETag"), data, maxAge); + } else if (response.status === 304) { + // Safety: a 304 response indicates out cache is valid based on ETag + data = fetchCache(endpoint).data as string; + + console.log("Unchanged... " + endpoint); + } else { + data = ""; + } + + return { + data, + status: response.status, + }; +} + +const response = await getTBAEndpoint("/team/frc4043/events/simple"); + +console.log(response);