@@ -78,6 +78,34 @@ async function get(
7878 }
7979}
8080
81+ /** Like get(), but reads only the first chunk — for sniffing big artifacts
82+ * (e.g. OpenAPI specs) without downloading or parsing the whole body. */
83+ async function peek (
84+ fetchImpl : FetchLike ,
85+ url : string ,
86+ init ?: RequestInit ,
87+ maxBytes = 4096 ,
88+ ) : Promise < { res : Response ; head : string } | null > {
89+ const ctrl = new AbortController ( ) ;
90+ const t = setTimeout ( ( ) => ctrl . abort ( ) , TIMEOUT_MS ) ;
91+ try {
92+ const headers = { "user-agent" : "integrations.sh-detector/0.1 (+https://integrations.sh)" , ...( init ?. headers as Record < string , string > | undefined ) } ;
93+ const res = await fetchImpl ( url , { redirect : "follow" , ...init , headers, signal : ctrl . signal } ) ;
94+ if ( ! res . body ) {
95+ const text = await res . text ( ) ;
96+ return { res, head : text . slice ( 0 , maxBytes ) } ;
97+ }
98+ const reader = res . body . getReader ( ) ;
99+ const { value } = await reader . read ( ) ;
100+ reader . cancel ( ) . catch ( ( ) => { } ) ;
101+ return { res, head : value ? new TextDecoder ( ) . decode ( value ) . slice ( 0 , maxBytes ) : "" } ;
102+ } catch {
103+ return null ;
104+ } finally {
105+ clearTimeout ( t ) ;
106+ }
107+ }
108+
81109/** Parse JSON only when it actually looks like JSON (guards SPA/HTML fallbacks). */
82110function asJson ( text : string , contentType : string | null ) : any | null {
83111 const trimmed = text . trimStart ( ) ;
@@ -149,14 +177,17 @@ async function checkLlmsTxt(fetchImpl: FetchLike, domain: string): Promise<boole
149177 */
150178async function checkApiSchema ( fetchImpl : FetchLike , domain : string ) {
151179 const paths = [ "/api/schema/" , "/openapi.json" , "/swagger.json" , "/api/openapi.json" , "/v1/openapi.json" ] ;
152- // Probe all candidate paths concurrently; keep the first (by order) that is a spec .
180+ // Probe concurrently; sniff only the head (no body download, no parse) .
153181 const results = await Promise . all ( paths . map ( async ( p ) => {
154- const hit = await get ( fetchImpl , `https://${ domain } ${ p } ` ) ;
182+ const url = `https://${ domain } ${ p } ` ;
183+ const hit = await peek ( fetchImpl , url ) ;
155184 if ( ! hit || ! hit . res . ok ) return undefined ;
156185 const ct = hit . res . headers . get ( "content-type" ) ?? "" ;
157- const doc = asJson ( hit . text , ct ) ;
158- const isOpenapi = / o p e n a p i / i. test ( ct ) || Boolean ( doc && ( doc . openapi || doc . swagger ) ) ;
159- return isOpenapi ? { url : `https://${ domain } ${ p } ` , format : "openapi" as const , version : doc ?. openapi ?? doc ?. swagger } : undefined ;
186+ if ( / t e x t \/ h t m l / i. test ( ct ) ) return undefined ; // SPA fallback
187+ const isOpenapi = / o p e n a p i | s w a g g e r / i. test ( ct ) || / [ " ' ] ? (?: o p e n a p i | s w a g g e r ) [ " ' ] ? \s * : / . test ( hit . head ) ;
188+ if ( ! isOpenapi ) return undefined ;
189+ const version = / [ " ' ] ? (?: o p e n a p i | s w a g g e r ) [ " ' ] ? \s * : \s * [ " ' ] ( [ ^ " ' ] + ) [ " ' ] / . exec ( hit . head ) ?. [ 1 ] ;
190+ return { url, format : "openapi" as const , version } ;
160191 } ) ) ;
161192 return results . find ( Boolean ) ;
162193}
0 commit comments