2020-06-25 16:05:36 -07:00
/ * *
* Copyright ( c ) Microsoft Corporation .
*
* Licensed under the Apache License , Version 2.0 ( the "License" ) ;
* you may not use this file except in compliance with the License .
* You may obtain a copy of the License at
*
* http : //www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing , software
* distributed under the License is distributed on an "AS IS" BASIS ,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND , either express or implied .
* See the License for the specific language governing permissions and
* limitations under the License .
* /
import { URLSearchParams } from 'url' ;
2022-09-20 18:41:51 -07:00
import type * as channels from '@protocol/channels' ;
2020-06-25 16:05:36 -07:00
import { ChannelOwner } from './channelOwner' ;
import { Frame } from './frame' ;
2022-07-01 12:49:43 -07:00
import { Worker } from './worker' ;
2022-04-06 13:57:14 -08:00
import type { Headers , RemoteAddr , SecurityDetails , WaitForEventOptions } from './types' ;
2021-02-11 06:36:15 -08:00
import fs from 'fs' ;
2022-04-18 19:20:49 -08:00
import { mime } from '../utilsBundle' ;
2024-05-22 08:54:19 -07:00
import { assert , isString , headersObjectToArray , isRegExp , rewriteErrorMessage } from '../utils' ;
2023-07-23 21:00:07 -07:00
import { ManualPromise , LongStandingScope } from '../utils/manualPromise' ;
2020-10-26 22:20:43 -07:00
import { Events } from './events' ;
2022-04-06 13:57:14 -08:00
import type { Page } from './page' ;
2020-11-02 14:09:58 -08:00
import { Waiter } from './waiter' ;
2022-04-06 13:57:14 -08:00
import type * as api from '../../types/types' ;
import type { HeadersArray , URLMatch } from '../common/types' ;
2023-01-13 13:50:38 -08:00
import { urlMatches } from '../utils/network' ;
2022-04-07 19:18:22 -08:00
import { MultiMap } from '../utils/multimap' ;
2021-11-05 16:27:49 +01:00
import { APIResponse } from './fetch' ;
2022-12-13 14:01:39 -08:00
import type { Serializable } from '../../types/structs' ;
2023-08-21 16:48:51 -07:00
import type { BrowserContext } from './browserContext' ;
2024-05-22 08:54:19 -07:00
import { isTargetClosedError } from './errors' ;
2020-06-25 16:05:36 -07:00
export type NetworkCookie = {
name : string ,
value : string ,
domain : string ,
path : string ,
expires : number ,
httpOnly : boolean ,
secure : boolean ,
sameSite : 'Strict' | 'Lax' | 'None'
} ;
export type SetNetworkCookieParam = {
name : string ,
value : string ,
url? : string ,
domain? : string ,
path? : string ,
expires? : number ,
httpOnly? : boolean ,
secure? : boolean ,
sameSite ? : 'Strict' | 'Lax' | 'None'
} ;
2024-03-26 08:12:26 -07:00
export type ClearNetworkCookieOptions = {
name? : string | RegExp ,
domain? : string | RegExp ,
path? : string | RegExp ,
2024-03-02 17:27:01 +02:00
} ;
2022-12-13 14:01:39 -08:00
type SerializedFallbackOverrides = {
url? : string ;
method? : string ;
headers? : Headers ;
postDataBuffer? : Buffer ;
} ;
2022-06-13 16:56:16 -08:00
type FallbackOverrides = {
url? : string ;
method? : string ;
headers? : Headers ;
2022-12-13 14:01:39 -08:00
postData? : string | Buffer | Serializable ;
2022-06-13 16:56:16 -08:00
} ;
2021-11-17 15:26:01 -08:00
export class Request extends ChannelOwner < channels.RequestChannel > implements api . Request {
2020-06-25 16:05:36 -07:00
private _redirectedFrom : Request | null = null ;
private _redirectedTo : Request | null = null ;
2020-06-26 11:51:47 -07:00
_failureText : string | null = null ;
2021-09-11 13:27:00 -07:00
private _provisionalHeaders : RawHeaders ;
private _actualHeadersPromise : Promise < RawHeaders > | undefined ;
2020-10-21 23:25:57 -07:00
_timing : ResourceTiming ;
2022-12-13 14:01:39 -08:00
private _fallbackOverrides : SerializedFallbackOverrides = { } ;
2020-06-25 16:05:36 -07:00
2020-08-24 17:05:16 -07:00
static from ( request : channels.RequestChannel ) : Request {
2020-07-01 18:36:09 -07:00
return ( request as any ) . _object ;
2020-06-25 16:05:36 -07:00
}
2020-08-24 17:05:16 -07:00
static fromNullable ( request : channels.RequestChannel | undefined ) : Request | null {
2020-06-25 16:05:36 -07:00
return request ? Request . from ( request ) : null ;
}
2020-08-24 17:05:16 -07:00
constructor ( parent : ChannelOwner , type : string , guid : string , initializer : channels.RequestInitializer ) {
2020-07-10 18:00:10 -07:00
super ( parent , type , guid , initializer ) ;
2020-06-26 12:28:27 -07:00
this . _redirectedFrom = Request . fromNullable ( initializer . redirectedFrom ) ;
2020-06-25 16:05:36 -07:00
if ( this . _redirectedFrom )
this . _redirectedFrom . _redirectedTo = this ;
2021-09-11 13:27:00 -07:00
this . _provisionalHeaders = new RawHeaders ( initializer . headers ) ;
2020-10-21 23:25:57 -07:00
this . _timing = {
startTime : 0 ,
domainLookupStart : - 1 ,
domainLookupEnd : - 1 ,
connectStart : - 1 ,
secureConnectionStart : - 1 ,
connectEnd : - 1 ,
requestStart : - 1 ,
responseStart : - 1 ,
responseEnd : - 1 ,
} ;
2020-06-25 16:05:36 -07:00
}
url ( ) : string {
2022-06-13 16:56:16 -08:00
return this . _fallbackOverrides . url || this . _initializer . url ;
2020-06-25 16:05:36 -07:00
}
resourceType ( ) : string {
2020-06-26 12:28:27 -07:00
return this . _initializer . resourceType ;
2020-06-25 16:05:36 -07:00
}
method ( ) : string {
2022-06-13 16:56:16 -08:00
return this . _fallbackOverrides . method || this . _initializer . method ;
2020-06-25 16:05:36 -07:00
}
postData ( ) : string | null {
2024-05-20 16:36:57 -07:00
return ( this . _fallbackOverrides . postDataBuffer || this . _initializer . postData ) ? . toString ( 'utf-8' ) || null ;
2020-07-22 15:59:37 -07:00
}
postDataBuffer ( ) : Buffer | null {
2024-05-20 16:36:57 -07:00
return this . _fallbackOverrides . postDataBuffer || this . _initializer . postData || null ;
2020-06-25 16:05:36 -07:00
}
postDataJSON ( ) : Object | null {
2020-07-22 15:59:37 -07:00
const postData = this . postData ( ) ;
if ( ! postData )
2020-06-25 16:05:36 -07:00
return null ;
const contentType = this . headers ( ) [ 'content-type' ] ;
2024-03-14 16:48:34 +01:00
if ( contentType ? . includes ( 'application/x-www-form-urlencoded' ) ) {
2020-06-25 16:05:36 -07:00
const entries : Record < string , string > = { } ;
2020-07-22 15:59:37 -07:00
const parsed = new URLSearchParams ( postData ) ;
2020-06-25 16:05:36 -07:00
for ( const [ k , v ] of parsed . entries ( ) )
entries [ k ] = v ;
return entries ;
}
2021-03-05 21:25:14 -08:00
try {
return JSON . parse ( postData ) ;
} catch ( e ) {
throw new Error ( 'POST data is not a valid JSON object: ' + postData ) ;
}
2020-06-25 16:05:36 -07:00
}
2021-09-01 18:28:20 -07:00
/ * *
* @deprecated
* /
2020-07-29 17:26:59 -07:00
headers ( ) : Headers {
2022-06-13 16:56:16 -08:00
if ( this . _fallbackOverrides . headers )
return RawHeaders . _fromHeadersObjectLossy ( this . _fallbackOverrides . headers ) . headers ( ) ;
2022-06-13 18:06:01 -07:00
return this . _provisionalHeaders . headers ( ) ;
2021-09-07 13:27:53 -04:00
}
2023-12-07 20:13:35 -08:00
async _actualHeaders ( ) : Promise < RawHeaders > {
2022-06-13 16:56:16 -08:00
if ( this . _fallbackOverrides . headers )
2023-12-07 20:13:35 -08:00
return RawHeaders . _fromHeadersObjectLossy ( this . _fallbackOverrides . headers ) ;
2022-06-13 16:56:16 -08:00
2021-09-11 13:27:00 -07:00
if ( ! this . _actualHeadersPromise ) {
2021-11-19 16:28:11 -08:00
this . _actualHeadersPromise = this . _wrapApiCall ( async ( ) = > {
return new RawHeaders ( ( await this . _channel . rawRequestHeaders ( ) ) . headers ) ;
2021-09-07 13:27:53 -04:00
} ) ;
}
2023-12-07 20:13:35 -08:00
return await this . _actualHeadersPromise ;
2020-06-25 16:05:36 -07:00
}
2021-09-07 13:27:53 -04:00
async allHeaders ( ) : Promise < Headers > {
2021-09-11 13:27:00 -07:00
return ( await this . _actualHeaders ( ) ) . headers ( ) ;
2021-09-07 13:27:53 -04:00
}
2021-09-11 13:27:00 -07:00
async headersArray ( ) : Promise < HeadersArray > {
return ( await this . _actualHeaders ( ) ) . headersArray ( ) ;
}
async headerValue ( name : string ) : Promise < string | null > {
return ( await this . _actualHeaders ( ) ) . get ( name ) ;
2021-09-01 18:28:20 -07:00
}
2020-06-25 16:05:36 -07:00
async response ( ) : Promise < Response | null > {
2021-11-19 16:28:11 -08:00
return Response . fromNullable ( ( await this . _channel . response ( ) ) . response ) ;
2020-06-25 16:05:36 -07:00
}
2021-10-15 14:22:49 -08:00
async _internalResponse ( ) : Promise < Response | null > {
2023-12-07 20:13:35 -08:00
return await this . _wrapApiCall ( async ( ) = > {
2021-11-19 16:28:11 -08:00
return Response . fromNullable ( ( await this . _channel . response ( ) ) . response ) ;
2021-11-18 22:30:09 -08:00
} , true ) ;
2021-10-15 14:22:49 -08:00
}
2020-06-25 16:05:36 -07:00
frame ( ) : Frame {
2022-07-01 12:49:43 -07:00
if ( ! this . _initializer . frame ) {
assert ( this . serviceWorker ( ) ) ;
throw new Error ( 'Service Worker requests do not have an associated frame.' ) ;
}
2023-08-22 14:06:21 -07:00
const frame = Frame . from ( this . _initializer . frame ) ;
if ( ! frame . _page ) {
throw new Error ( [
'Frame for this navigation request is not available, because the request' ,
'was issued before the frame is created. You can check whether the request' ,
'is a navigation request by calling isNavigationRequest() method.' ,
] . join ( '\n' ) ) ;
}
return frame ;
2020-06-25 16:05:36 -07:00
}
2023-12-04 16:19:05 -08:00
_safePage ( ) : Page | null {
return Frame . fromNullable ( this . _initializer . frame ) ? . _page || null ;
}
2022-07-01 12:49:43 -07:00
serviceWorker ( ) : Worker | null {
return this . _initializer . serviceWorker ? Worker . from ( this . _initializer . serviceWorker ) : null ;
}
2020-06-25 16:05:36 -07:00
isNavigationRequest ( ) : boolean {
2020-06-26 12:28:27 -07:00
return this . _initializer . isNavigationRequest ;
2020-06-25 16:05:36 -07:00
}
redirectedFrom ( ) : Request | null {
return this . _redirectedFrom ;
}
redirectedTo ( ) : Request | null {
return this . _redirectedTo ;
}
failure ( ) : { errorText : string ; } | null {
if ( this . _failureText === null )
return null ;
return {
errorText : this._failureText
} ;
}
2020-07-15 18:48:19 -07:00
2020-10-21 23:25:57 -07:00
timing ( ) : ResourceTiming {
return this . _timing ;
}
2021-09-02 10:39:57 -07:00
async sizes ( ) : Promise < RequestSizes > {
const response = await this . response ( ) ;
if ( ! response )
throw new Error ( 'Unable to fetch sizes for failed request' ) ;
2021-11-19 16:28:11 -08:00
return ( await response . _channel . sizes ( ) ) . sizes ;
2021-08-27 22:53:57 +02:00
}
2022-09-26 17:12:47 -07:00
_setResponseEndTiming ( responseEndTiming : number ) {
this . _timing . responseEnd = responseEndTiming ;
if ( this . _timing . responseStart === - 1 )
this . _timing . responseStart = responseEndTiming ;
}
2020-07-15 18:48:19 -07:00
_finalRequest ( ) : Request {
return this . _redirectedTo ? this . _redirectedTo . _finalRequest ( ) : this ;
}
2020-06-25 16:05:36 -07:00
2022-06-13 16:56:16 -08:00
_applyFallbackOverrides ( overrides : FallbackOverrides ) {
2022-12-15 11:57:51 -08:00
if ( overrides . url )
this . _fallbackOverrides . url = overrides . url ;
if ( overrides . method )
this . _fallbackOverrides . method = overrides . method ;
if ( overrides . headers )
this . _fallbackOverrides . headers = overrides . headers ;
if ( isString ( overrides . postData ) )
this . _fallbackOverrides . postDataBuffer = Buffer . from ( overrides . postData , 'utf-8' ) ;
else if ( overrides . postData instanceof Buffer )
this . _fallbackOverrides . postDataBuffer = overrides . postData ;
else if ( overrides . postData )
this . _fallbackOverrides . postDataBuffer = Buffer . from ( JSON . stringify ( overrides . postData ) , 'utf-8' ) ;
2022-06-13 16:56:16 -08:00
}
_fallbackOverridesForContinue() {
return this . _fallbackOverrides ;
}
2023-01-27 10:42:43 -08:00
2023-07-23 21:00:07 -07:00
_targetClosedScope ( ) : LongStandingScope {
2023-12-04 16:19:05 -08:00
return this . serviceWorker ( ) ? . _closedScope || this . _safePage ( ) ? . _closedOrCrashedScope || new LongStandingScope ( ) ;
2023-01-27 10:42:43 -08:00
}
2022-06-13 16:56:16 -08:00
}
2022-06-10 08:06:39 -08:00
2021-11-17 15:26:01 -08:00
export class Route extends ChannelOwner < channels.RouteChannel > implements api . Route {
2022-06-13 11:30:51 -08:00
private _handlingPromise : ManualPromise < boolean > | null = null ;
2023-08-21 16:48:51 -07:00
_context ! : BrowserContext ;
2023-12-05 10:26:17 -08:00
_didThrow : boolean = false ;
2022-06-10 08:06:39 -08:00
2020-08-24 17:05:16 -07:00
static from ( route : channels.RouteChannel ) : Route {
2020-07-01 18:36:09 -07:00
return ( route as any ) . _object ;
2020-06-26 11:51:47 -07:00
}
2020-08-24 17:05:16 -07:00
constructor ( parent : ChannelOwner , type : string , guid : string , initializer : channels.RouteInitializer ) {
2020-07-10 18:00:10 -07:00
super ( parent , type , guid , initializer ) ;
2020-06-25 16:05:36 -07:00
}
request ( ) : Request {
2020-06-26 12:28:27 -07:00
return Request . from ( this . _initializer . request ) ;
2020-06-25 16:05:36 -07:00
}
2023-12-07 20:13:35 -08:00
private async _raceWithTargetClose ( promise : Promise < any > ) : Promise < void > {
2021-11-08 15:13:15 -08:00
// When page closes or crashes, we catch any potential rejects from this Route.
// Note that page could be missing when routing popup's initial request that
// does not have a Page initialized just yet.
2023-12-07 20:13:35 -08:00
return await this . request ( ) . _targetClosedScope ( ) . safeRace ( promise ) ;
2021-11-08 15:13:15 -08:00
}
2023-12-07 20:13:35 -08:00
async _startHandling ( ) : Promise < boolean > {
2022-06-13 11:30:51 -08:00
this . _handlingPromise = new ManualPromise ( ) ;
2023-12-07 20:13:35 -08:00
return await this . _handlingPromise ;
2022-06-13 11:30:51 -08:00
}
2022-06-13 16:56:16 -08:00
async fallback ( options : FallbackOverrides = { } ) {
2022-06-13 11:30:51 -08:00
this . _checkNotHandled ( ) ;
2022-06-13 16:56:16 -08:00
this . request ( ) . _applyFallbackOverrides ( options ) ;
2022-06-13 11:30:51 -08:00
this . _reportHandled ( false ) ;
2022-06-10 08:06:39 -08:00
}
2020-07-30 11:14:41 -07:00
async abort ( errorCode? : string ) {
2023-12-05 10:26:17 -08:00
await this . _handleRoute ( async ( ) = > {
await this . _raceWithTargetClose ( this . _channel . abort ( { requestUrl : this.request ( ) . _initializer . url , errorCode } ) ) ;
} ) ;
2022-06-17 21:17:30 -07:00
}
2022-06-21 11:01:01 -07:00
async _redirectNavigationRequest ( url : string ) {
2023-12-05 10:26:17 -08:00
await this . _handleRoute ( async ( ) = > {
await this . _raceWithTargetClose ( this . _channel . redirectNavigationRequest ( { url } ) ) ;
} ) ;
2020-06-25 16:05:36 -07:00
}
2023-04-21 01:41:33 +10:00
async fetch ( options : FallbackOverrides & { maxRedirects? : number , timeout? : number } = { } ) : Promise < APIResponse > {
2022-11-30 17:26:19 -08:00
return await this . _wrapApiCall ( async ( ) = > {
2023-12-07 20:13:35 -08:00
return await this . _context . request . _innerFetch ( { request : this.request ( ) , data : options.postData , . . . options } ) ;
2022-11-30 17:26:19 -08:00
} ) ;
}
async fulfill ( options : { response? : api.APIResponse , status? : number , headers? : Headers , contentType? : string , body? : string | Buffer , json? : any , path? : string } = { } ) {
2023-12-05 10:26:17 -08:00
await this . _handleRoute ( async ( ) = > {
await this . _wrapApiCall ( async ( ) = > {
await this . _innerFulfill ( options ) ;
} ) ;
} ) ;
}
private async _handleRoute ( callback : ( ) = > Promise < void > ) {
2022-06-13 11:30:51 -08:00
this . _checkNotHandled ( ) ;
2023-12-05 10:26:17 -08:00
try {
await callback ( ) ;
2022-06-15 08:41:46 -07:00
this . _reportHandled ( true ) ;
2023-12-05 10:26:17 -08:00
} catch ( e ) {
this . _didThrow = true ;
throw e ;
}
2022-06-10 08:06:39 -08:00
}
2022-11-30 17:26:19 -08:00
private async _innerFulfill ( options : { response? : api.APIResponse , status? : number , headers? : Headers , contentType? : string , body? : string | Buffer , json? : any , path? : string } = { } ) : Promise < void > {
2021-11-19 16:28:11 -08:00
let fetchResponseUid ;
2022-06-20 15:19:54 -07:00
let { status : statusOption , headers : headersOption , body } = options ;
2022-06-08 20:29:03 -07:00
2022-11-30 17:26:19 -08:00
if ( options . json !== undefined ) {
assert ( options . body === undefined , 'Can specify either body or json parameters' ) ;
body = JSON . stringify ( options . json ) ;
}
2022-06-14 15:07:22 -07:00
if ( options . response instanceof APIResponse ) {
2022-06-08 20:29:03 -07:00
statusOption ? ? = options . response . status ( ) ;
headersOption ? ? = options . response . headers ( ) ;
2022-06-14 15:07:22 -07:00
if ( body === undefined && options . path === undefined ) {
2022-02-10 12:05:04 -08:00
if ( options . response . _request . _connection === this . _connection )
fetchResponseUid = ( options . response as APIResponse ) . _fetchUid ( ) ;
else
body = await options . response . body ( ) ;
}
2021-11-19 16:28:11 -08:00
}
let isBase64 = false ;
let length = 0 ;
if ( options . path ) {
const buffer = await fs . promises . readFile ( options . path ) ;
body = buffer . toString ( 'base64' ) ;
isBase64 = true ;
length = buffer . length ;
} else if ( isString ( body ) ) {
isBase64 = false ;
length = Buffer . byteLength ( body ) ;
} else if ( body ) {
length = body . length ;
body = body . toString ( 'base64' ) ;
isBase64 = true ;
}
const headers : Headers = { } ;
for ( const header of Object . keys ( headersOption || { } ) )
headers [ header . toLowerCase ( ) ] = String ( headersOption ! [ header ] ) ;
2022-06-20 15:19:54 -07:00
if ( options . contentType )
headers [ 'content-type' ] = String ( options . contentType ) ;
2022-11-30 17:26:19 -08:00
else if ( options . json )
headers [ 'content-type' ] = 'application/json' ;
2021-11-19 16:28:11 -08:00
else if ( options . path )
headers [ 'content-type' ] = mime . getType ( options . path ) || 'application/octet-stream' ;
if ( length && ! ( 'content-length' in headers ) )
headers [ 'content-length' ] = String ( length ) ;
2022-07-01 12:49:43 -07:00
await this . _raceWithTargetClose ( this . _channel . fulfill ( {
2023-03-23 14:57:03 -07:00
requestUrl : this.request ( ) . _initializer . url ,
2021-11-19 16:28:11 -08:00
status : statusOption || 200 ,
headers : headersObjectToArray ( headers ) ,
body ,
isBase64 ,
fetchResponseUid
} ) ) ;
2020-06-25 16:05:36 -07:00
}
2022-06-13 16:56:16 -08:00
async continue ( options : FallbackOverrides = { } ) {
2023-12-05 10:26:17 -08:00
await this . _handleRoute ( async ( ) = > {
this . request ( ) . _applyFallbackOverrides ( options ) ;
await this . _innerContinue ( ) ;
} ) ;
2022-06-13 11:30:51 -08:00
}
_checkNotHandled() {
if ( ! this . _handlingPromise )
2022-06-10 08:06:39 -08:00
throw new Error ( 'Route is already handled!' ) ;
2021-06-18 11:04:48 -07:00
}
2022-06-13 11:30:51 -08:00
_reportHandled ( done : boolean ) {
const chain = this . _handlingPromise ! ;
this . _handlingPromise = null ;
chain . resolve ( done ) ;
2021-10-15 14:22:49 -08:00
}
2022-06-13 16:56:16 -08:00
async _innerContinue ( internal = false ) {
const options = this . request ( ) . _fallbackOverridesForContinue ( ) ;
2021-11-19 16:28:11 -08:00
return await this . _wrapApiCall ( async ( ) = > {
2022-07-01 12:49:43 -07:00
await this . _raceWithTargetClose ( this . _channel . continue ( {
2023-03-23 14:57:03 -07:00
requestUrl : this.request ( ) . _initializer . url ,
2021-01-22 06:49:59 -08:00
url : options.url ,
method : options.method ,
headers : options.headers ? headersObjectToArray ( options . headers ) : undefined ,
2022-12-13 14:01:39 -08:00
postData : options.postDataBuffer ,
2023-06-06 16:55:53 -07:00
isFallback : internal ,
2021-11-08 15:13:15 -08:00
} ) ) ;
2022-06-13 11:30:51 -08:00
} , ! ! internal ) ;
2021-06-18 11:04:48 -07:00
}
2020-06-25 16:05:36 -07:00
}
2023-01-10 18:07:17 +01:00
export type RouteHandlerCallback = ( route : Route , request : Request ) = > Promise < any > | void ;
2020-06-25 16:05:36 -07:00
2020-10-21 23:25:57 -07:00
export type ResourceTiming = {
startTime : number ;
domainLookupStart : number ;
domainLookupEnd : number ;
connectStart : number ;
secureConnectionStart : number ;
connectEnd : number ;
requestStart : number ;
responseStart : number ;
responseEnd : number ;
2021-08-27 22:53:57 +02:00
} ;
export type RequestSizes = {
requestBodySize : number ;
requestHeadersSize : number ;
responseBodySize : number ;
responseHeadersSize : number ;
2020-10-21 23:25:57 -07:00
} ;
2021-11-17 15:26:01 -08:00
export class Response extends ChannelOwner < channels.ResponseChannel > implements api . Response {
2021-09-11 13:27:00 -07:00
private _provisionalHeaders : RawHeaders ;
private _actualHeadersPromise : Promise < RawHeaders > | undefined ;
2020-10-21 23:25:57 -07:00
private _request : Request ;
2023-03-06 08:50:03 -08:00
readonly _finishedPromise = new ManualPromise < null > ( ) ;
2020-07-15 13:21:21 -07:00
2020-08-24 17:05:16 -07:00
static from ( response : channels.ResponseChannel ) : Response {
2020-07-01 18:36:09 -07:00
return ( response as any ) . _object ;
2020-06-25 16:05:36 -07:00
}
2020-08-24 17:05:16 -07:00
static fromNullable ( response : channels.ResponseChannel | undefined ) : Response | null {
2020-06-25 16:05:36 -07:00
return response ? Response . from ( response ) : null ;
}
2020-08-24 17:05:16 -07:00
constructor ( parent : ChannelOwner , type : string , guid : string , initializer : channels.ResponseInitializer ) {
2020-07-10 18:00:10 -07:00
super ( parent , type , guid , initializer ) ;
2021-09-11 13:27:00 -07:00
this . _provisionalHeaders = new RawHeaders ( initializer . headers ) ;
2020-10-21 23:25:57 -07:00
this . _request = Request . from ( this . _initializer . request ) ;
Object . assign ( this . _request . _timing , this . _initializer . timing ) ;
2020-06-25 16:05:36 -07:00
}
url ( ) : string {
2020-06-26 12:28:27 -07:00
return this . _initializer . url ;
2020-06-25 16:05:36 -07:00
}
ok ( ) : boolean {
2021-11-09 23:11:42 +01:00
// Status 0 is for file:// URLs
2020-06-26 12:28:27 -07:00
return this . _initializer . status === 0 || ( this . _initializer . status >= 200 && this . _initializer . status <= 299 ) ;
2020-06-25 16:05:36 -07:00
}
status ( ) : number {
2020-06-26 12:28:27 -07:00
return this . _initializer . status ;
2020-06-25 16:05:36 -07:00
}
statusText ( ) : string {
2020-06-26 12:28:27 -07:00
return this . _initializer . statusText ;
2020-06-25 16:05:36 -07:00
}
2022-06-08 17:34:19 -04:00
fromServiceWorker ( ) : boolean {
return this . _initializer . fromServiceWorker ;
}
2021-09-01 18:28:20 -07:00
/ * *
* @deprecated
* /
2020-07-29 17:26:59 -07:00
headers ( ) : Headers {
2021-09-11 13:27:00 -07:00
return this . _provisionalHeaders . headers ( ) ;
2020-06-25 16:05:36 -07:00
}
2021-09-11 13:27:00 -07:00
async _actualHeaders ( ) : Promise < RawHeaders > {
if ( ! this . _actualHeadersPromise ) {
2021-11-19 16:28:11 -08:00
this . _actualHeadersPromise = ( async ( ) = > {
return new RawHeaders ( ( await this . _channel . rawResponseHeaders ( ) ) . headers ) ;
} ) ( ) ;
2021-09-07 13:27:53 -04:00
}
2023-12-07 20:13:35 -08:00
return await this . _actualHeadersPromise ;
2021-09-01 18:28:20 -07:00
}
2021-09-07 13:27:53 -04:00
async allHeaders ( ) : Promise < Headers > {
2021-09-11 13:27:00 -07:00
return ( await this . _actualHeaders ( ) ) . headers ( ) ;
2021-09-07 13:27:53 -04:00
}
2021-09-11 13:27:00 -07:00
async headersArray ( ) : Promise < HeadersArray > {
return ( await this . _actualHeaders ( ) ) . headersArray ( ) . slice ( ) ;
}
async headerValue ( name : string ) : Promise < string | null > {
return ( await this . _actualHeaders ( ) ) . get ( name ) ;
}
async headerValues ( name : string ) : Promise < string [ ] > {
return ( await this . _actualHeaders ( ) ) . getAll ( name ) ;
2021-09-07 13:27:53 -04:00
}
2021-08-30 20:43:40 -07:00
async finished ( ) : Promise < null > {
2023-12-07 20:13:35 -08:00
return await this . request ( ) . _targetClosedScope ( ) . race ( this . _finishedPromise ) ;
2020-06-25 16:05:36 -07:00
}
async body ( ) : Promise < Buffer > {
2022-07-05 08:58:34 -07:00
return ( await this . _channel . body ( ) ) . binary ;
2020-06-25 16:05:36 -07:00
}
async text ( ) : Promise < string > {
const content = await this . body ( ) ;
return content . toString ( 'utf8' ) ;
}
async json ( ) : Promise < object > {
const content = await this . text ( ) ;
return JSON . parse ( content ) ;
}
request ( ) : Request {
2020-10-21 23:25:57 -07:00
return this . _request ;
2020-06-25 16:05:36 -07:00
}
frame ( ) : Frame {
2020-10-21 23:25:57 -07:00
return this . _request . frame ( ) ;
2020-06-25 16:05:36 -07:00
}
2021-06-17 13:04:55 -07:00
async serverAddr ( ) : Promise < RemoteAddr | null > {
2021-11-19 16:28:11 -08:00
return ( await this . _channel . serverAddr ( ) ) . value || null ;
2021-06-17 13:04:55 -07:00
}
async securityDetails ( ) : Promise < SecurityDetails | null > {
2021-11-19 16:28:11 -08:00
return ( await this . _channel . securityDetails ( ) ) . value || null ;
2021-06-17 13:04:55 -07:00
}
2020-06-25 16:05:36 -07:00
}
2020-08-18 15:38:29 -07:00
2021-11-17 15:26:01 -08:00
export class WebSocket extends ChannelOwner < channels.WebSocketChannel > implements api . WebSocket {
2020-11-02 14:09:58 -08:00
private _page : Page ;
private _isClosed : boolean ;
2020-10-26 22:20:43 -07:00
static from ( webSocket : channels.WebSocketChannel ) : WebSocket {
return ( webSocket as any ) . _object ;
}
constructor ( parent : ChannelOwner , type : string , guid : string , initializer : channels.WebSocketInitializer ) {
super ( parent , type , guid , initializer ) ;
2020-11-02 14:09:58 -08:00
this . _isClosed = false ;
this . _page = parent as Page ;
2022-07-05 08:58:34 -07:00
this . _channel . on ( 'frameSent' , event = > {
2021-06-28 21:23:29 +02:00
if ( event . opcode === 1 )
this . emit ( Events . WebSocket . FrameSent , { payload : event.data } ) ;
else if ( event . opcode === 2 )
this . emit ( Events . WebSocket . FrameSent , { payload : Buffer.from ( event . data , 'base64' ) } ) ;
2020-10-26 22:20:43 -07:00
} ) ;
2022-07-05 08:58:34 -07:00
this . _channel . on ( 'frameReceived' , event = > {
2021-06-28 21:23:29 +02:00
if ( event . opcode === 1 )
this . emit ( Events . WebSocket . FrameReceived , { payload : event.data } ) ;
else if ( event . opcode === 2 )
this . emit ( Events . WebSocket . FrameReceived , { payload : Buffer.from ( event . data , 'base64' ) } ) ;
2020-10-26 22:20:43 -07:00
} ) ;
2020-11-19 12:09:42 -08:00
this . _channel . on ( 'socketError' , ( { error } ) = > this . emit ( Events . WebSocket . Error , error ) ) ;
2020-11-02 14:09:58 -08:00
this . _channel . on ( 'close' , ( ) = > {
this . _isClosed = true ;
2021-01-22 09:58:31 -08:00
this . emit ( Events . WebSocket . Close , this ) ;
2020-11-02 14:09:58 -08:00
} ) ;
2020-10-26 22:20:43 -07:00
}
url ( ) : string {
return this . _initializer . url ;
}
2020-11-02 14:09:58 -08:00
isClosed ( ) : boolean {
return this . _isClosed ;
}
async waitForEvent ( event : string , optionsOrPredicate : WaitForEventOptions = { } ) : Promise < any > {
2023-12-07 20:13:35 -08:00
return await this . _wrapApiCall ( async ( ) = > {
2021-06-28 13:27:38 -07:00
const timeout = this . _page . _timeoutSettings . timeout ( typeof optionsOrPredicate === 'function' ? { } : optionsOrPredicate ) ;
const predicate = typeof optionsOrPredicate === 'function' ? optionsOrPredicate : optionsOrPredicate.predicate ;
2021-12-13 13:32:53 -08:00
const waiter = Waiter . createForEvent ( this , event ) ;
2021-12-06 15:42:57 -08:00
waiter . rejectOnTimeout ( timeout , ` Timeout ${ timeout } ms exceeded while waiting for event " ${ event } " ` ) ;
2021-06-28 13:27:38 -07:00
if ( event !== Events . WebSocket . Error )
waiter . rejectOnEvent ( this , Events . WebSocket . Error , new Error ( 'Socket error' ) ) ;
if ( event !== Events . WebSocket . Close )
waiter . rejectOnEvent ( this , Events . WebSocket . Close , new Error ( 'Socket closed' ) ) ;
2023-10-17 15:35:41 -07:00
waiter . rejectOnEvent ( this . _page , Events . Page . Close , ( ) = > this . _page . _closeErrorWithReason ( ) ) ;
2021-06-28 13:27:38 -07:00
const result = await waiter . waitForEvent ( this , event , predicate as any ) ;
waiter . dispose ( ) ;
return result ;
} ) ;
2020-11-02 14:09:58 -08:00
}
2020-10-26 22:20:43 -07:00
}
2020-08-18 15:38:29 -07:00
export function validateHeaders ( headers : Headers ) {
for ( const key of Object . keys ( headers ) ) {
const value = headers [ key ] ;
2020-08-22 07:07:13 -07:00
if ( ! Object . is ( value , undefined ) && ! isString ( value ) )
2020-08-18 15:38:29 -07:00
throw new Error ( ` Expected value of header " ${ key } " to be String, but " ${ typeof value } " is found. ` ) ;
}
}
2021-08-24 20:45:50 +02:00
2023-02-18 11:41:24 -08:00
export class RouteHandler {
private handledCount = 0 ;
private readonly _baseURL : string | undefined ;
private readonly _times : number ;
readonly url : URLMatch ;
readonly handler : RouteHandlerCallback ;
2023-12-15 09:00:12 -08:00
private _ignoreException : boolean = false ;
private _activeInvocations : Set < { complete : Promise < void > , route : Route } > = new Set ( ) ;
2023-01-25 14:11:53 -08:00
2023-12-15 09:00:12 -08:00
constructor ( baseURL : string | undefined , url : URLMatch , handler : RouteHandlerCallback , times : number = Number . MAX_SAFE_INTEGER ) {
2023-01-25 14:11:53 -08:00
this . _baseURL = baseURL ;
2023-02-18 11:41:24 -08:00
this . _times = times ;
this . url = url ;
this . handler = handler ;
2023-01-25 14:11:53 -08:00
}
2023-02-18 11:41:24 -08:00
static prepareInterceptionPatterns ( handlers : RouteHandler [ ] ) {
2023-01-27 10:43:19 -08:00
const patterns : channels.BrowserContextSetNetworkInterceptionPatternsParams [ 'patterns' ] = [ ] ;
let all = false ;
2023-02-18 11:41:24 -08:00
for ( const handler of handlers ) {
2023-01-27 10:43:19 -08:00
if ( isString ( handler . url ) )
patterns . push ( { glob : handler.url } ) ;
else if ( isRegExp ( handler . url ) )
patterns . push ( { regexSource : handler.url.source , regexFlags : handler.url.flags } ) ;
else
all = true ;
}
2023-02-18 11:41:24 -08:00
if ( all )
return [ { glob : '**/*' } ] ;
return patterns ;
2021-08-24 20:45:50 +02:00
}
public matches ( requestURL : string ) : boolean {
return urlMatches ( this . _baseURL , requestURL , this . url ) ;
}
2022-08-25 11:58:41 -07:00
public async handle ( route : Route ) : Promise < boolean > {
2023-12-15 09:00:12 -08:00
const handlerInvocation = { complete : new ManualPromise ( ) , route } ;
this . _activeInvocations . add ( handlerInvocation ) ;
2023-12-04 16:19:05 -08:00
try {
return await this . _handleInternal ( route ) ;
} catch ( e ) {
// If the handler was stopped (without waiting for completion), we ignore all exceptions.
2023-12-15 09:00:12 -08:00
if ( this . _ignoreException )
2023-12-04 16:19:05 -08:00
return false ;
2024-05-22 08:54:19 -07:00
if ( isTargetClosedError ( e ) ) {
// We are failing in the handler because the target close closed.
// Give user a hint!
rewriteErrorMessage ( e , ` " ${ e . message } " while running route callback. \ nConsider awaiting \` await page.unrouteAll({ behavior: 'ignoreErrors' }) \` \ nbefore the end of the test to ignore remaining routes in flight. ` ) ;
}
2023-12-04 16:19:05 -08:00
throw e ;
} finally {
handlerInvocation . complete . resolve ( ) ;
2023-12-15 09:00:12 -08:00
this . _activeInvocations . delete ( handlerInvocation ) ;
2023-12-04 16:19:05 -08:00
}
}
2023-12-15 09:00:12 -08:00
async stop ( behavior : 'wait' | 'ignoreErrors' ) {
2023-12-04 16:19:05 -08:00
// When a handler is manually unrouted or its page/context is closed we either
// - wait for the current handler invocations to finish
// - or do not wait, if the user opted out of it, but swallow all exceptions
// that happen after the unroute/close.
2023-12-15 09:00:12 -08:00
if ( behavior === 'ignoreErrors' ) {
this . _ignoreException = true ;
2023-12-05 10:26:17 -08:00
} else {
const promises = [ ] ;
2023-12-15 09:00:12 -08:00
for ( const activation of this . _activeInvocations ) {
if ( ! activation . route . _didThrow )
2023-12-05 10:26:17 -08:00
promises . push ( activation . complete ) ;
}
await Promise . all ( promises ) ;
}
2023-12-04 16:19:05 -08:00
}
private async _handleInternal ( route : Route ) : Promise < boolean > {
2022-01-22 22:19:32 +01:00
++ this . handledCount ;
2022-06-13 11:30:51 -08:00
const handledPromise = route . _startHandling ( ) ;
2022-06-10 14:26:45 -07:00
// Extract handler into a variable to avoid [RouteHandler.handler] in the stack.
const handler = this . handler ;
2022-06-13 11:30:51 -08:00
const [ handled ] = await Promise . all ( [
handledPromise ,
2022-08-25 11:58:41 -07:00
handler ( route , route . request ( ) ) ,
2022-06-13 11:30:51 -08:00
] ) ;
return handled ;
2022-01-22 22:19:32 +01:00
}
2022-05-21 21:55:46 -07:00
public willExpire ( ) : boolean {
return this . handledCount + 1 >= this . _times ;
2021-08-24 20:45:50 +02:00
}
}
2021-09-11 13:27:00 -07:00
export class RawHeaders {
private _headersArray : HeadersArray ;
private _headersMap = new MultiMap < string , string > ( ) ;
2022-06-13 16:56:16 -08:00
static _fromHeadersObjectLossy ( headers : Headers ) : RawHeaders {
const headersArray : HeadersArray = Object . entries ( headers ) . map ( ( [ name , value ] ) = > ( {
name , value
} ) ) . filter ( header = > header . value !== undefined ) ;
return new RawHeaders ( headersArray ) ;
}
2021-09-11 13:27:00 -07:00
constructor ( headers : HeadersArray ) {
this . _headersArray = headers ;
for ( const header of headers )
this . _headersMap . set ( header . name . toLowerCase ( ) , header . value ) ;
}
get ( name : string ) : string | null {
const values = this . getAll ( name ) ;
if ( ! values || ! values . length )
return null ;
return values . join ( name . toLowerCase ( ) === 'set-cookie' ? '\n' : ', ' ) ;
}
getAll ( name : string ) : string [ ] {
return [ . . . this . _headersMap . get ( name . toLowerCase ( ) ) ] ;
}
headers ( ) : Headers {
const result : Headers = { } ;
for ( const name of this . _headersMap . keys ( ) )
result [ name ] = this . get ( name ) ! ;
return result ;
}
headersArray ( ) : HeadersArray {
return this . _headersArray ;
}
}