/**
 * Copyright 2020 Google Inc. All rights reserved.
 *
 * 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 { ProtocolMapping } from 'devtools-protocol/types/protocol-mapping.js' ; import { EventEmitter } from './EventEmitter.js' ; import { Frame } from './FrameManager.js' ; import { HTTPResponse } from './HTTPResponse.js' ; import { assert } from './assert.js' ; import { helper , debugError } from './helper.js' ; import { Protocol } from 'devtools-protocol' ; import { ProtocolError } from './Errors.js' ; public export interface ContinueRequestOverrides { /**
     * If set, the request URL will change. This is not a redirect.
     */ url ? : string ; method ? : string ; postData ? : string ; headers ? : Record < string , string > ; } public export interface InterceptResolutionState { action : InterceptResolutionAction ; priority ? : number ; } public export interface ResponseForRequest { status : number ; /**
     * Optional response headers. All values are converted to strings.
     */ headers : Record < string , unknown > ; contentType : string ; body : string | Buffer ; } public export type ResourceType = Lowercase < Protocol . Network . ResourceType > ; public export const DEFAULT_INTERCEPT_RESOLUTION_PRIORITY = 0 ; interface CDPSession extends EventEmitter { send < T extends keyof ProtocolMapping . Commands > ( method , ... paramArgs ) : Promise < ProtocolMapping . Commands [ T ] [ 'returnType' ] > ; } remarks public export class HTTPRequest { internal _requestId : string ; internal _interceptionId : string ; internal _failureText = null ; internal _response : HTTPResponse | null = null ; internal _fromMemoryCache = false ; internal _redirectChain : HTTPRequest [ ] ; private _client : CDPSession ; private _isNavigationRequest : boolean ; private _allowInterception : boolean ; private _interceptionHandled = false ; private _url : string ; private _resourceType : ResourceType ; private _method : string ; private _postData ? : string ; private _headers : Record < string , string > = { } ; private _frame : Frame ; private _continueRequestOverrides : ContinueRequestOverrides ; private _responseForRequest : Partial < ResponseForRequest > ; private _abortErrorReason : Protocol . Network . ErrorReason ; private _interceptResolutionState : InterceptResolutionState ; private _interceptHandlers : Array < ( ) => void | PromiseLike < any > > ; private _initiator : Protocol . Network . Initiator ; internal constructor ( client , frame , interceptionId , allowInterception , event , redirectChain ) { this . _client = client ; this . _requestId = event . requestId ; this . _isNavigationRequest = event . requestId === event . loaderId && event . type === 'Document' ; this . _interceptionId = interceptionId ; this . _allowInterception = allowInterception ; this . _url = event . request . url ; this . _resourceType = event . type . toLowerCase ( ) as ResourceType ; this . _method = event . request . method ; this . _postData = event . request . postData ; this . _frame = frame ; this . _redirectChain = redirectChain ; this . _continueRequestOverrides = { } ; this . _interceptResolutionState = { action : InterceptResolutionAction . None } ; this . _interceptHandlers = [ ] ; this . _initiator = event . initiator ; for ( const key of Object . keys ( event . request . headers ) ) this . _headers [ key . toLowerCase ( ) ] = event . request . headers [ key ] ; } returns url ( ) { return this . _url ; } returns continueRequestOverrides ( ) { assert ( this . _allowInterception , 'Request Interception is not enabled!' ) ; return this . _continueRequestOverrides ; } returns responseForRequest ( ) { assert ( this . _allowInterception , 'Request Interception is not enabled!' ) ; return this . _responseForRequest ; } returns abortErrorReason ( ) { assert ( this . _allowInterception , 'Request Interception is not enabled!' ) ; return this . _abortErrorReason ; } returns interceptResolutionState ( ) { if ( ! this . _allowInterception ) return { action : InterceptResolutionAction . Disabled } ; if ( this . _interceptionHandled ) return { action : InterceptResolutionAction . AlreadyHandled } ; return { ... this . _interceptResolutionState } ; } returns isInterceptResolutionHandled ( ) { return this . _interceptionHandled ; } /**
     * Adds an async request handler to the processing queue.
     * Deferred handlers are not guaranteed to execute in any particular order,
     * but they are guarnateed to resolve before the request interception
     * is finalized.
     */ enqueueInterceptAction ( pendingHandler ) { this . _interceptHandlers . push ( pendingHandler ) ; } /**
     * Awaits pending interception handlers and then decides how to fulfill
     * the request interception.
     */ async finalizeInterceptions ( ) { await this . _interceptHandlers . reduce ( ( promiseChain , interceptAction ) => promiseChain . then ( interceptAction ) , Promise . resolve ( ) ) ; const { action } = this . interceptResolutionState ( ) ; switch ( action ) { case 'abort' : return this . _abort ( this . _abortErrorReason ) ; case 'respond' : return this . _respond ( this . _responseForRequest ) ; case 'continue' : return this . _continue ( this . _continueRequestOverrides ) ; } } /**
     * Contains the request's resource type as it was perceived by the rendering
     * engine.
     */ resourceType ( ) { return this . _resourceType ; } returns method ( ) { return this . _method ; } returns postData ( ) { return this . _postData ; } returns headers ( ) { return this . _headers ; } returns response ( ) { return this . _response ; } returns frame ( ) { return this . _frame ; } returns isNavigationRequest ( ) { return this . _isNavigationRequest ; } returns initiator ( ) { return this . _initiator ; } remarks returns redirectChain ( ) { return this . _redirectChain . slice ( ) ; } remarks example returns failure ( ) { if ( ! this . _failureText ) return null ; return { errorText : this . _failureText , } ; } remarks To use this, request
     * interception should be enabled with  Page . setRequestInterception .
     *
     * Exception is immediately thrown if the request interception is not enabled.
     *
     *  example param overrides param priority async continue ( overrides = { } , priority ? ) { if ( this . _url . startsWith ( 'data:' ) ) return ; assert ( this . _allowInterception , 'Request Interception is not enabled!' ) ; assert ( ! this . _interceptionHandled , 'Request is already handled!' ) ; if ( priority === undefined ) { return this . _continue ( overrides ) ; } this . _continueRequestOverrides = overrides ; if ( priority > this . _interceptResolutionState . priority || this . _interceptResolutionState . priority === undefined ) { this . _interceptResolutionState = { action : InterceptResolutionAction . Continue , priority , } ; return ; } if ( priority === this . _interceptResolutionState . priority ) { if ( this . _interceptResolutionState . action === 'abort' || this . _interceptResolutionState . action === 'respond' ) { return ; } this . _interceptResolutionState . action = InterceptResolutionAction . Continue ; } return ; } private async _continue ( overrides = { } ) { const { url , method , postData , headers } = overrides ; this . _interceptionHandled = true ; const postDataBinaryBase64 = postData ? Buffer . from ( postData ) . toString ( 'base64' ) : undefined ; await this . _client . send ( 'Fetch.continueRequest' , { requestId : this . _interceptionId , url , method , postData : postDataBinaryBase64 , headers : headers ? headersArray ( headers ) : undefined , } ) . catch ( ( error ) => { this . _interceptionHandled = false ; return handleError ( error ) ; } ) ; } remarks To use this, request
     * interception should be enabled with  Page . setRequestInterception .
     *
     * Exception is immediately thrown if the request interception is not enabled.
     *
     *  example param response param priority async respond ( response , priority ? ) { if ( this . _url . startsWith ( 'data:' ) ) return ; assert ( this . _allowInterception , 'Request Interception is not enabled!' ) ; assert ( ! this . _interceptionHandled , 'Request is already handled!' ) ; if ( priority === undefined ) { return this . _respond ( response ) ; } this . _responseForRequest = response ; if ( priority > this . _interceptResolutionState . priority || this . _interceptResolutionState . priority === undefined ) { this . _interceptResolutionState = { action : InterceptResolutionAction . Respond , priority , } ; return ; } if ( priority === this . _interceptResolutionState . priority ) { if ( this . _interceptResolutionState . action === 'abort' ) { return ; } this . _interceptResolutionState . action = InterceptResolutionAction . Respond ; } } private async _respond ( response ) { this . _interceptionHandled = true ; const responseBody = response . body && helper . isString ( response . body ) ? Buffer . from ( response . body ) : ( response . body as Buffer ) || null ; const responseHeaders = { } ; if ( response . headers ) { for ( const header of Object . keys ( response . headers ) ) responseHeaders [ header . toLowerCase ( ) ] = String ( response . headers [ header ] ) ; } if ( response . contentType ) responseHeaders [ 'content-type' ] = response . contentType ; if ( responseBody && ! ( 'content-length' in responseHeaders ) ) responseHeaders [ 'content-length' ] = String ( Buffer . byteLength ( responseBody ) ) ; await this . _client . send ( 'Fetch.fulfillRequest' , { requestId : this . _interceptionId , responseCode : response . status || 200 , responsePhrase : STATUS_TEXTS [ response . status || 200 ] , responseHeaders : headersArray ( responseHeaders ) , body : responseBody ? responseBody . toString ( 'base64' ) : undefined , } ) . catch ( ( error ) => { this . _interceptionHandled = false ; return handleError ( error ) ; } ) ; } remarks To use this, request interception should be enabled with
     *  Page . setRequestInterception . If it is not enabled, this method will
     * throw an exception immediately.
     *
     *  param errorCode param priority async abort ( errorCode = 'failed' , priority ? ) { if ( this . _url . startsWith ( 'data:' ) ) return ; const errorReason = errorReasons [ errorCode ] ; assert ( errorReason , 'Unknown error code: ' + errorCode ) ; assert ( this . _allowInterception , 'Request Interception is not enabled!' ) ; assert ( ! this . _interceptionHandled , 'Request is already handled!' ) ; if ( priority === undefined ) { return this . _abort ( errorReason ) ; } this . _abortErrorReason = errorReason ; if ( priority >= this . _interceptResolutionState . priority || this . _interceptResolutionState . priority === undefined ) { this . _interceptResolutionState = { action : InterceptResolutionAction . Abort , priority , } ; return ; } } private async _abort ( errorReason ) { this . _interceptionHandled = true ; await this . _client . send ( 'Fetch.failRequest' , { requestId : this . _interceptionId , errorReason , } ) . catch ( handleError ) ; } } public export enum InterceptResolutionAction { Abort = 'abort' , Respond = 'respond' , Continue = 'continue' , Disabled = 'disabled' , None = 'none' , AlreadyHandled = 'already-handled' } public deprecated please use  InterceptResolutionAction instead.
  export type InterceptResolutionStrategy = InterceptResolutionAction ; public export type ErrorCode = 'aborted' | 'accessdenied' | 'addressunreachable' | 'blockedbyclient' | 'blockedbyresponse' | 'connectionaborted' | 'connectionclosed' | 'connectionfailed' | 'connectionrefused' | 'connectionreset' | 'internetdisconnected' | 'namenotresolved' | 'timedout' | 'failed' ; const errorReasons = { aborted : 'Aborted' , accessdenied : 'AccessDenied' , addressunreachable : 'AddressUnreachable' , blockedbyclient : 'BlockedByClient' , blockedbyresponse : 'BlockedByResponse' , connectionaborted : 'ConnectionAborted' , connectionclosed : 'ConnectionClosed' , connectionfailed : 'ConnectionFailed' , connectionrefused : 'ConnectionRefused' , connectionreset : 'ConnectionReset' , internetdisconnected : 'InternetDisconnected' , namenotresolved : 'NameNotResolved' , timedout : 'TimedOut' , failed : 'Failed' , } as const ; public export type ActionResult = 'continue' | 'abort' | 'respond' ; function headersArray ( headers ) { const result = [ ] ; for ( const name in headers ) { if ( ! Object . is ( headers [ name ] , undefined ) ) result . push ( { name , value : headers [ name ] + '' } ) ; } return result ; } async function handleError ( error ) { if ( [ 'Invalid header' ] . includes ( error . originalMessage ) ) { throw error ; } debugError ( error ) ; } const STATUS_TEXTS = { '100' : 'Continue' , '101' : 'Switching Protocols' , '102' : 'Processing' , '103' : 'Early Hints' , '200' : 'OK' , '201' : 'Created' , '202' : 'Accepted' , '203' : 'Non-Authoritative Information' , '204' : 'No Content' , '205' : 'Reset Content' , '206' : 'Partial Content' , '207' : 'Multi-Status' , '208' : 'Already Reported' , '226' : 'IM Used' , '300' : 'Multiple Choices' , '301' : 'Moved Permanently' , '302' : 'Found' , '303' : 'See Other' , '304' : 'Not Modified' , '305' : 'Use Proxy' , '306' : 'Switch Proxy' , '307' : 'Temporary Redirect' , '308' : 'Permanent Redirect' , '400' : 'Bad Request' , '401' : 'Unauthorized' , '402' : 'Payment Required' , '403' : 'Forbidden' , '404' : 'Not Found' , '405' : 'Method Not Allowed' , '406' : 'Not Acceptable' , '407' : 'Proxy Authentication Required' , '408' : 'Request Timeout' , '409' : 'Conflict' , '410' : 'Gone' , '411' : 'Length Required' , '412' : 'Precondition Failed' , '413' : 'Payload Too Large' , '414' : 'URI Too Long' , '415' : 'Unsupported Media Type' , '416' : 'Range Not Satisfiable' , '417' : 'Expectation Failed' , '418' : "I'm a teapot" , '421' : 'Misdirected Request' , '422' : 'Unprocessable Entity' , '423' : 'Locked' , '424' : 'Failed Dependency' , '425' : 'Too Early' , '426' : 'Upgrade Required' , '428' : 'Precondition Required' , '429' : 'Too Many Requests' , '431' : 'Request Header Fields Too Large' , '451' : 'Unavailable For Legal Reasons' , '500' : 'Internal Server Error' , '501' : 'Not Implemented' , '502' : 'Bad Gateway' , '503' : 'Service Unavailable' , '504' : 'Gateway Timeout' , '505' : 'HTTP Version Not Supported' , '506' : 'Variant Also Negotiates' , '507' : 'Insufficient Storage' , '508' : 'Loop Detected' , '510' : 'Not Extended' , '511' : 'Network Authentication Required' , } as const ;