@@ -2,8 +2,8 @@ import { getStore } from "@netlify/blobs";
2
2
import type { Config , Context } from "@netlify/edge-functions" ;
3
3
4
4
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 ;
7
7
const SITE_ID = "EPXKTQED" ;
8
8
9
9
interface VisitorData {
@@ -43,7 +43,7 @@ function createDefaultVisitorData(index?: number): VisitorData | VisitorData[] {
43
43
function createJsonResponse < T > (
44
44
data : T ,
45
45
status = 200 ,
46
- cacheControl = "public, max-age=6 " ,
46
+ cacheControl = "public, max-age=15 " ,
47
47
) : Response {
48
48
return new Response ( JSON . stringify ( data ) , {
49
49
status,
@@ -194,7 +194,86 @@ function createVisitorResponse(
194
194
} ;
195
195
}
196
196
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
198
277
try {
199
278
const store = getStore ( "visitors-cache" ) ;
200
279
const visitorData = await getVisitorData ( store ) ;
@@ -212,10 +291,8 @@ export default async (_request: Request, _context: Context) => {
212
291
const response = createVisitorResponse ( updatedData , total ) ;
213
292
return createJsonResponse ( response ) ;
214
293
} 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 ) ;
219
296
}
220
297
} catch ( error : unknown ) {
221
298
console . error ( "Internal server error:" , error ) ;
@@ -228,6 +305,6 @@ export const config: Config = {
228
305
rateLimit : {
229
306
action : "rate_limit" ,
230
307
aggregateBy : "ip" ,
231
- windowSize : 60 ,
308
+ windowSize : 100 ,
232
309
} ,
233
310
} ;
0 commit comments