@@ -44,6 +44,55 @@ const PATH_TMP_DIRECTORY = join(__dirname, '../tmp')
4444const URL_DOWNLOAD = `https://raw.githubusercontent.com/${ repo } /`
4545
4646const queue = new JobQueue ( )
47+ const RATE_LIMIT_HEADER_REMAINING = 'X-GitHub-RateLimit-Remaining'
48+ const RATE_LIMIT_HEADER_RESET = 'X-GitHub-RateLimit-Reset'
49+
50+ /**
51+ * Extract rate limit metadata from a source object.
52+ * @param {{ rateLimitRemaining?: number, rateLimitReset?: number }|null|undefined } source
53+ * @returns {{ rateLimitRemaining?: number, rateLimitReset?: number }|null }
54+ */
55+ function extractRateLimitMeta ( source ) {
56+ const remaining = ( typeof source ?. rateLimitRemaining === 'number' ) ? source . rateLimitRemaining : undefined
57+ const reset = ( typeof source ?. rateLimitReset === 'number' ) ? source . rateLimitReset : undefined
58+
59+ if ( remaining === undefined && reset === undefined ) {
60+ return null
61+ }
62+
63+ return { rateLimitRemaining : remaining , rateLimitReset : reset }
64+ }
65+
66+ /**
67+ * Attach rate limit metadata to a result object without mutating the original.
68+ * @template T
69+ * @param {T } target
70+ * @param {{ rateLimitRemaining?: number, rateLimitReset?: number }|null } meta
71+ * @returns {T }
72+ */
73+ function applyRateLimitMeta ( target , meta ) {
74+ if ( ! target || ! meta ) {
75+ return target
76+ }
77+
78+ const needsRemaining = meta . rateLimitRemaining !== undefined && target . rateLimitRemaining === undefined
79+ const needsReset = meta . rateLimitReset !== undefined && target . rateLimitReset === undefined
80+
81+ if ( ! needsRemaining && ! needsReset ) {
82+ return target
83+ }
84+
85+ const result = { ...target }
86+
87+ if ( needsRemaining ) {
88+ result . rateLimitRemaining = meta . rateLimitRemaining
89+ }
90+ if ( needsReset ) {
91+ result . rateLimitReset = meta . rateLimitReset
92+ }
93+
94+ return result
95+ }
4796
4897/**
4998 * Tries to look for a remote tsconfig file if the branch/ref is of newer date typically 2019+.
@@ -111,19 +160,21 @@ async function handlerDefault (req, res) {
111160 // Serve a file depending on the request URL.
112161 // Try to serve a static file.
113162 let result = await serveStaticFile ( branch , url )
163+ const rateLimitMeta = extractRateLimitMeta ( result )
114164
115165 if ( result ?. status !== 200 ) {
116166 // Try to build the file
117- result = await serveBuildFile ( branch , url , useGitDownloader )
167+ const buildResult = await serveBuildFile ( branch , url , useGitDownloader )
168+ result = applyRateLimitMeta ( buildResult , rateLimitMeta )
118169 }
119170
120171 // await updateBranchAccess(join(PATH_TMP_DIRECTORY, branch))
121172
122173 if ( ! result ) {
123- result = {
124- status : 404 ,
174+ result = applyRateLimitMeta ( {
175+ status : 200 ,
125176 body : 'Not Found'
126- }
177+ } , rateLimitMeta )
127178 }
128179
129180 if ( ! res . headersSent ) {
@@ -221,6 +272,8 @@ async function handlerUpdate (req, res) {
221272 * @param {import('express').Request } request Express request object.
222273 */
223274async function respondToClient ( result , response , request ) {
275+ const rateLimitRemaining = ( typeof result ?. rateLimitRemaining === 'number' ) ? result . rateLimitRemaining : undefined
276+ const rateLimitReset = ( typeof result ?. rateLimitReset === 'number' ) ? result . rateLimitReset : undefined
224277 const { body, file, status } = result
225278 // Make sure connection is not lost before attemting a response.
226279 if ( request . connectionAborted || response . headersSent ) {
@@ -239,6 +292,13 @@ async function respondToClient (result, response, request) {
239292 response . header ( 'Cache-Control' , 'max-age=3600' )
240293 response . header ( 'CDN-Cache-Control' , 'max-age=3600' )
241294
295+ if ( rateLimitRemaining !== undefined ) {
296+ response . header ( RATE_LIMIT_HEADER_REMAINING , String ( rateLimitRemaining ) )
297+ }
298+ if ( rateLimitReset !== undefined ) {
299+ response . header ( RATE_LIMIT_HEADER_RESET , String ( rateLimitReset ) )
300+ }
301+
242302 if ( file ) {
243303 await new Promise ( ( resolve , reject ) => {
244304 response . sendFile ( file , ( err ) => {
@@ -313,7 +373,7 @@ async function serveBuildFile (branch, requestURL, useGitDownloader = true) {
313373 return maybeResponse
314374 }
315375
316- return { status : 404 , body : 'could not find' }
376+ return { status : 200 , body : 'could not find' }
317377 }
318378
319379 const buildProject = isMastersQuery
@@ -378,7 +438,7 @@ ${error.message}`)
378438 ) . then ( ( ) => {
379439 return { status : 200 , file : pathOutputFile }
380440 } ) . catch ( ( ) => {
381- return { status : 404 , body : 'Server busy, try again in a few minutes' }
441+ return { status : 200 , body : 'Server busy, try again in a few minutes' }
382442 } )
383443 }
384444
@@ -476,6 +536,7 @@ ${error.message}`)
476536 */
477537async function serveStaticFile ( branch , requestURL ) {
478538 const file = getFile ( branch , 'classic' , requestURL )
539+ let rateLimitMeta = null
479540
480541 // Respond with not found if the interpreter can not find a filename.
481542 if ( file === false ) {
@@ -488,11 +549,15 @@ async function serveStaticFile (branch, requestURL) {
488549 if ( ! existsSync ( fileLocation ) ) {
489550 const urlFile = `${ URL_DOWNLOAD } ${ branch } /${ file } `
490551 const download = await downloadFile ( urlFile , fileLocation )
552+ const downloadMeta = extractRateLimitMeta ( download )
553+ if ( downloadMeta ) {
554+ rateLimitMeta = downloadMeta
555+ }
491556 if ( download . success ) {
492- return { status : 200 , file : fileLocation }
557+ return applyRateLimitMeta ( { status : 200 , file : fileLocation } , rateLimitMeta )
493558 }
494559 } else {
495- return { status : 200 , file : fileLocation }
560+ return applyRateLimitMeta ( { status : 200 , file : fileLocation } , rateLimitMeta )
496561 }
497562 }
498563
@@ -502,18 +567,25 @@ async function serveStaticFile (branch, requestURL) {
502567 if ( ! exists ( pathFile ) ) {
503568 const urlFile = `${ URL_DOWNLOAD } ${ branch } /js/${ file } `
504569 const download = await downloadFile ( urlFile , pathFile )
570+ const downloadMeta = extractRateLimitMeta ( download )
571+ if ( downloadMeta ) {
572+ rateLimitMeta = downloadMeta
573+ }
505574 if ( download . statusCode !== 200 ) {
506575 // we don't always know if it is a static file before we have tried to download it.
507576 // check if this branch contains TypeScript config (we then need to compile it).
508577 if ( file . split ( '/' ) . length <= 1 || await shouldDownloadTypeScriptFolders ( URL_DOWNLOAD , branch ) ) {
509- return serveBuildFile ( branch , requestURL )
578+ const buildResult = await serveBuildFile ( branch , requestURL )
579+ return applyRateLimitMeta ( buildResult , rateLimitMeta )
510580 }
511- return response . missingFile
581+ return applyRateLimitMeta ( { ... response . missingFile } , rateLimitMeta )
512582 }
583+
584+ return applyRateLimitMeta ( { status : 200 , file : pathFile } , rateLimitMeta )
513585 }
514586
515587 // Return path to file location in the cache.
516- return { status : 200 , file : pathFile }
588+ return applyRateLimitMeta ( { status : 200 , file : pathFile } , rateLimitMeta )
517589}
518590
519591function printTreeChildren ( children , level = 1 , carry ) {
@@ -545,7 +617,7 @@ async function handlerFS (req, res) {
545617 return respondToClient ( { status : 200 , body : `<pre>${ textTree } </pre>` } , res , req )
546618 }
547619 }
548- return respondToClient ( { status : 404 , body : 'no output folder found for this commit' } , res , req )
620+ return respondToClient ( { status : 200 , body : 'no output folder found for this commit' } , res , req )
549621 }
550622
551623 return respondToClient ( response . missingFile , res , req )
0 commit comments