Skip to content

Commit f8fe1ff

Browse files
authored
Fix menu settings not re-initializing after page load, improve viewer count streaming (#16)
1 parent 69fcfed commit f8fe1ff

File tree

5 files changed

+177
-113
lines changed

5 files changed

+177
-113
lines changed

netlify.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
[dev]
22
framework = "astro"
3-
targetPort = 4321
3+
targetPort = 4321

netlify/edge-functions/visitors.ts

Lines changed: 86 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ import { getStore } from "@netlify/blobs";
22
import type { Config, Context } from "@netlify/edge-functions";
33

44
const VISITORS_KEY = "visitor_counts";
5-
const CACHE_TTL = 6; // Fathom limits API calls to 10 times per minute
6-
const MAX_HISTORY_POINTS = 10;
5+
const CACHE_TTL = 15;
6+
const MAX_HISTORY_POINTS = 8;
77
const SITE_ID = "EPXKTQED";
88

99
interface VisitorData {
@@ -43,7 +43,7 @@ function createDefaultVisitorData(index?: number): VisitorData | VisitorData[] {
4343
function createJsonResponse<T>(
4444
data: T,
4545
status = 200,
46-
cacheControl = "public, max-age=6",
46+
cacheControl = "public, max-age=15",
4747
): Response {
4848
return new Response(JSON.stringify(data), {
4949
status,
@@ -194,7 +194,86 @@ function createVisitorResponse(
194194
};
195195
}
196196

197-
export default async (_request: Request, _context: Context) => {
197+
/**
198+
* Handles Server-Sent Events (SSE) for real-time visitor updates
199+
*/
200+
async function handleSSE(request: Request): Promise<Response> {
201+
const headers = new Headers({
202+
"Content-Type": "text/event-stream",
203+
"Cache-Control": "no-cache",
204+
Connection: "keep-alive",
205+
});
206+
207+
const stream = new TransformStream();
208+
const writer = stream.writable.getWriter();
209+
const store = getStore("visitors-cache");
210+
211+
const sendStats = async () => {
212+
try {
213+
const visitorData = await getVisitorData(store);
214+
const recentResponse = getRecentData(visitorData);
215+
216+
if (recentResponse) {
217+
writer.write(
218+
new TextEncoder().encode(
219+
`data: ${JSON.stringify(recentResponse)}\n\n`,
220+
),
221+
);
222+
return;
223+
}
224+
225+
// If no recent data, fetch from Fathom API
226+
try {
227+
const total = await fetchFathomData();
228+
const updatedData = await updateVisitorData(store, visitorData, total);
229+
const response = createVisitorResponse(updatedData, total);
230+
231+
writer.write(
232+
new TextEncoder().encode(`data: ${JSON.stringify(response)}\n\n`),
233+
);
234+
} catch (error) {
235+
// If Fathom errors, just send existing data
236+
const response = {
237+
total: visitorData[visitorData.length - 1]?.count ?? 0,
238+
history: visitorData.map((data) => data.count),
239+
};
240+
241+
writer.write(
242+
new TextEncoder().encode(`data: ${JSON.stringify(response)}\n\n`),
243+
);
244+
}
245+
} catch (error) {
246+
console.error("Error in SSE handler:", error);
247+
writer.write(
248+
new TextEncoder().encode(
249+
`data: ${JSON.stringify({ total: 0, history: Array(MAX_HISTORY_POINTS).fill(0) })}\n\n`,
250+
),
251+
);
252+
}
253+
};
254+
255+
// Send initial data
256+
await sendStats();
257+
258+
// Update every 15 seconds
259+
const timer = setInterval(sendStats, 15000);
260+
261+
request.signal.addEventListener("abort", () => {
262+
clearInterval(timer);
263+
writer.close();
264+
});
265+
266+
return new Response(stream.readable, { headers });
267+
}
268+
269+
export default async (request: Request, _context: Context) => {
270+
// Check if this is an SSE request
271+
const acceptHeader = request.headers.get("accept");
272+
if (acceptHeader?.includes("text/event-stream")) {
273+
return handleSSE(request);
274+
}
275+
276+
// Regular JSON request handling
198277
try {
199278
const store = getStore("visitors-cache");
200279
const visitorData = await getVisitorData(store);
@@ -212,10 +291,8 @@ export default async (_request: Request, _context: Context) => {
212291
const response = createVisitorResponse(updatedData, total);
213292
return createJsonResponse(response);
214293
} catch (error: unknown) {
215-
console.error("Error fetching Fathom data:", error);
216-
const errorMessage =
217-
error instanceof Error ? error.message : "Failed to fetch visitor data";
218-
return createErrorResponse(errorMessage);
294+
// If Fathom errors we have probably exceeded the rate limit, just return existing
295+
return createJsonResponse(visitorData);
219296
}
220297
} catch (error: unknown) {
221298
console.error("Internal server error:", error);
@@ -228,6 +305,6 @@ export const config: Config = {
228305
rateLimit: {
229306
action: "rate_limit",
230307
aggregateBy: "ip",
231-
windowSize: 60,
308+
windowSize: 100,
232309
},
233310
};

0 commit comments

Comments
 (0)