1+ const WAKATIME_STATS_URL = 'https://wakatime.com/api/v1/users/current/stats/last_7_days' ;
2+ const REQUEST_TIMEOUT_MS = 8000 ;
3+ const MAX_RETRIES = 2 ;
4+ const INITIAL_BACKOFF_MS = 300 ;
5+
6+ function sleep ( ms ) {
7+ return new Promise ( ( resolve ) => setTimeout ( resolve , ms ) ) ;
8+ }
9+
10+ function shouldRetry ( status ) {
11+ return status === 429 || status >= 500 ;
12+ }
13+
14+ async function fetchWithRetry ( url , options ) {
15+ let lastError ;
16+
17+ for ( let attempt = 0 ; attempt <= MAX_RETRIES ; attempt += 1 ) {
18+ const controller = new AbortController ( ) ;
19+ const timeout = setTimeout ( ( ) => controller . abort ( ) , REQUEST_TIMEOUT_MS ) ;
20+
21+ try {
22+ const response = await fetch ( url , {
23+ ...options ,
24+ signal : controller . signal ,
25+ } ) ;
26+
27+ clearTimeout ( timeout ) ;
28+
29+ if ( ! response . ok && shouldRetry ( response . status ) && attempt < MAX_RETRIES ) {
30+ const backoffMs = INITIAL_BACKOFF_MS * ( 2 ** attempt ) ;
31+ await sleep ( backoffMs ) ;
32+ continue ;
33+ }
34+
35+ return response ;
36+ } catch ( error ) {
37+ clearTimeout ( timeout ) ;
38+ lastError = error ;
39+
40+ if ( attempt >= MAX_RETRIES ) {
41+ throw error ;
42+ }
43+
44+ const backoffMs = INITIAL_BACKOFF_MS * ( 2 ** attempt ) ;
45+ await sleep ( backoffMs ) ;
46+ }
47+ }
48+
49+ throw lastError || new Error ( 'Failed to fetch Wakatime data' ) ;
50+ }
51+
152export default async function handler ( req , res ) {
2- const apiKey = process . env . WAKATIME_API_KEY || process . env . VITE_WAKATIME_API_KEY ;
53+ if ( req . method !== 'GET' ) {
54+ res . setHeader ( 'Allow' , 'GET' ) ;
55+ return res . status ( 405 ) . json ( { error : 'Method Not Allowed' } ) ;
56+ }
57+
58+ const apiKey = process . env . WAKATIME_API_KEY ;
359
460 if ( ! apiKey ) {
5- return res . status ( 500 ) . json ( { error : 'Server Configuration Error: Missing API Key ' } ) ;
61+ return res . status ( 500 ) . json ( { error : 'Server Configuration Error' } ) ;
662 }
763
64+ const authToken = Buffer . from ( `${ apiKey } :` ) . toString ( 'base64' ) ;
65+
866 try {
9- const url = `https://wakatime.com/api/v1/users/current/stats/last_7_days?api_key=${ apiKey } ` ;
10- const response = await fetch ( url ) ;
67+ const response = await fetchWithRetry ( WAKATIME_STATS_URL , {
68+ headers : {
69+ Authorization : `Basic ${ authToken } ` ,
70+ } ,
71+ } ) ;
1172
1273 if ( ! response . ok ) {
13- const errorText = await response . text ( ) ;
14- return res . status ( response . status ) . json ( { error : 'Failed to fetch from Wakatime' , details : errorText } ) ;
74+ return res . status ( response . status ) . json ( { error : 'Failed to fetch from Wakatime' } ) ;
1575 }
1676
1777 const data = await response . json ( ) ;
18-
78+
1979 res . setHeader ( 'Cache-Control' , 's-maxage=3600, stale-while-revalidate' ) ;
2080 return res . status ( 200 ) . json ( data ) ;
2181 } catch ( error ) {
22- return res . status ( 500 ) . json ( { error : 'Internal Server Error' , details : error . message } ) ;
82+ console . error ( 'Wakatime fetch error:' , error ) ;
83+ return res . status ( 502 ) . json ( { error : 'Unable to fetch Wakatime data' } ) ;
2384 }
24- }
85+ }
0 commit comments