@@ -414,10 +414,61 @@ app.post('/scrape', async (req: Request, res: Response) => {
414414 page = await requestContext . newPage ( ) ;
415415
416416 if ( headers ) {
417- // Remove the user-agent key before calling setExtraHTTPHeaders since
418- // we already forwarded it to the context-level userAgent option.
417+ // A Cookie header passed through setExtraHTTPHeaders is sent on the first
418+ // request but DROPPED on any redirect hop (the browser regenerates the
419+ // redirected request from its cookie jar, which is empty). Authenticated
420+ // sites that 302 (e.g. to /signin when the session looks absent) then
421+ // land on the login page. Seed the cookie jar instead so Chromium re-sends
422+ // it on every request, including redirects — matching what a raw HTTP
423+ // client does.
424+ const cookieHeader = Object . entries ( headers ) . find ( ( [ k ] ) => k . toLowerCase ( ) === 'cookie' ) ?. [ 1 ] ;
425+ if ( cookieHeader ) {
426+ // Scope cookies to the registrable domain (e.g. ".example.com"), not
427+ // host-only. Authenticated pages often 302 across sibling subdomains
428+ // (example.com -> app.example.com); a host-only cookie set for the
429+ // original host would not be sent to the redirect target, leaving the
430+ // request unauthenticated. The Cookie header carries no domain info, so
431+ // we apply the eTLD+1 — broad enough to follow the redirect, and these
432+ // are first-party cookies being returned to their own origin anyway.
433+ let cookieDomain : string | undefined ;
434+ try {
435+ const host = new URL ( url ) . hostname ;
436+ const labels = host . split ( '.' ) ;
437+ cookieDomain = labels . length > 2 ? labels . slice ( - 2 ) . join ( '.' ) : host ;
438+ } catch {
439+ cookieDomain = undefined ;
440+ }
441+ type SeedCookie = { name : string ; value : string ; url ?: string ; domain ?: string ; path ?: string } ;
442+ const cookies = cookieHeader
443+ . split ( ';' )
444+ . map ( pair => pair . trim ( ) )
445+ . filter ( Boolean )
446+ . map ( ( pair ) : SeedCookie | null => {
447+ const eq = pair . indexOf ( '=' ) ;
448+ if ( eq === - 1 ) return null ;
449+ const name = pair . slice ( 0 , eq ) . trim ( ) ;
450+ const value = pair . slice ( eq + 1 ) . trim ( ) ;
451+ return cookieDomain
452+ ? { name, value, domain : `.${ cookieDomain } ` , path : '/' }
453+ : { name, value, url } ;
454+ } )
455+ . filter ( ( c ) : c is SeedCookie => c !== null ) ;
456+ if ( cookies . length > 0 ) {
457+ try {
458+ await requestContext . addCookies ( cookies ) ;
459+ } catch ( error ) {
460+ console . warn ( 'Failed to seed cookies from Cookie header:' , error ) ;
461+ }
462+ }
463+ }
464+
465+ // Remove user-agent (already applied at the context level) and cookie
466+ // (now seeded into the jar) before forwarding the rest verbatim.
419467 const filteredHeaders = Object . fromEntries (
420- Object . entries ( headers ) . filter ( ( [ k ] ) => k . toLowerCase ( ) !== 'user-agent' )
468+ Object . entries ( headers ) . filter ( ( [ k ] ) => {
469+ const lower = k . toLowerCase ( ) ;
470+ return lower !== 'user-agent' && lower !== 'cookie' ;
471+ } )
421472 ) ;
422473 if ( Object . keys ( filteredHeaders ) . length > 0 ) {
423474 await page . setExtraHTTPHeaders ( filteredHeaders ) ;
0 commit comments