mirror of
				https://github.com/microsoft/playwright.git
				synced 2025-06-26 21:40:17 +00:00 
			
		
		
		
	feat(fetch): introduce global fetch request (#8927)
This commit is contained in:
		
							parent
							
								
									5253a7eb54
								
							
						
					
					
						commit
						c58f34fb2e
					
				@ -69,7 +69,7 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel,
 | 
			
		||||
      this._browser = parent;
 | 
			
		||||
    this._isChromium = this._browser?._name === 'chromium';
 | 
			
		||||
    this.tracing = new Tracing(this);
 | 
			
		||||
    this._request = new FetchRequest(this);
 | 
			
		||||
    this._request = FetchRequest.from(initializer.fetchRequest);
 | 
			
		||||
 | 
			
		||||
    this._channel.on('bindingCall', ({binding}) => this._onBinding(BindingCall.from(binding)));
 | 
			
		||||
    this._channel.on('close', () => this._onClose());
 | 
			
		||||
 | 
			
		||||
@ -39,6 +39,7 @@ import { ParsedStackTrace } from '../utils/stackTrace';
 | 
			
		||||
import { Artifact } from './artifact';
 | 
			
		||||
import { EventEmitter } from 'events';
 | 
			
		||||
import { JsonPipe } from './jsonPipe';
 | 
			
		||||
import { FetchRequest } from './fetch';
 | 
			
		||||
 | 
			
		||||
class Root extends ChannelOwner<channels.RootChannel, {}> {
 | 
			
		||||
  constructor(connection: Connection) {
 | 
			
		||||
@ -216,6 +217,9 @@ export class Connection extends EventEmitter {
 | 
			
		||||
      case 'ElementHandle':
 | 
			
		||||
        result = new ElementHandle(parent, type, guid, initializer);
 | 
			
		||||
        break;
 | 
			
		||||
      case 'FetchRequest':
 | 
			
		||||
        result = new FetchRequest(parent, type, guid, initializer);
 | 
			
		||||
        break;
 | 
			
		||||
      case 'Frame':
 | 
			
		||||
        result = new Frame(parent, type, guid, initializer);
 | 
			
		||||
        break;
 | 
			
		||||
 | 
			
		||||
@ -18,7 +18,7 @@ import * as api from '../../types/types';
 | 
			
		||||
import { HeadersArray } from '../common/types';
 | 
			
		||||
import * as channels from '../protocol/channels';
 | 
			
		||||
import { assert, headersObjectToArray, isString, objectToArray } from '../utils/utils';
 | 
			
		||||
import { BrowserContext } from './browserContext';
 | 
			
		||||
import { ChannelOwner } from './channelOwner';
 | 
			
		||||
import * as network from './network';
 | 
			
		||||
import { RawHeaders } from './network';
 | 
			
		||||
import { Headers } from './types';
 | 
			
		||||
@ -32,11 +32,13 @@ export type FetchOptions = {
 | 
			
		||||
  failOnStatusCode?: boolean,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export class FetchRequest implements api.FetchRequest {
 | 
			
		||||
  private _context: BrowserContext;
 | 
			
		||||
export class FetchRequest extends ChannelOwner<channels.FetchRequestChannel, channels.FetchRequestInitializer> implements api.FetchRequest {
 | 
			
		||||
  static from(channel: channels.FetchRequestChannel): FetchRequest {
 | 
			
		||||
    return (channel as any)._object;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  constructor(context: BrowserContext) {
 | 
			
		||||
    this._context = context;
 | 
			
		||||
  constructor(parent: ChannelOwner, type: string, guid: string, initializer: channels.FetchRequestInitializer) {
 | 
			
		||||
    super(parent, type, guid, initializer);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async get(
 | 
			
		||||
@ -69,7 +71,7 @@ export class FetchRequest implements api.FetchRequest {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async fetch(urlOrRequest: string | api.Request, options: FetchOptions = {}): Promise<FetchResponse> {
 | 
			
		||||
    return this._context._wrapApiCall(async (channel: channels.BrowserContextChannel) => {
 | 
			
		||||
    return this._wrapApiCall(async (channel: channels.FetchRequestChannel) => {
 | 
			
		||||
      const request: network.Request | undefined = (urlOrRequest instanceof network.Request) ? urlOrRequest as network.Request : undefined;
 | 
			
		||||
      assert(request || typeof urlOrRequest === 'string', 'First argument must be either URL string or Request');
 | 
			
		||||
      const url = request ? request.url() : urlOrRequest as string;
 | 
			
		||||
@ -93,7 +95,7 @@ export class FetchRequest implements api.FetchRequest {
 | 
			
		||||
      });
 | 
			
		||||
      if (result.error)
 | 
			
		||||
        throw new Error(`Request failed: ${result.error}`);
 | 
			
		||||
      return new FetchResponse(this._context, result.response!);
 | 
			
		||||
      return new FetchResponse(this, result.response!);
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -101,10 +103,10 @@ export class FetchRequest implements api.FetchRequest {
 | 
			
		||||
export class FetchResponse implements api.FetchResponse {
 | 
			
		||||
  private readonly _initializer: channels.FetchResponse;
 | 
			
		||||
  private readonly _headers: RawHeaders;
 | 
			
		||||
  private readonly _context: BrowserContext;
 | 
			
		||||
  private readonly _request: FetchRequest;
 | 
			
		||||
 | 
			
		||||
  constructor(context: BrowserContext, initializer: channels.FetchResponse) {
 | 
			
		||||
    this._context = context;
 | 
			
		||||
  constructor(context: FetchRequest, initializer: channels.FetchResponse) {
 | 
			
		||||
    this._request = context;
 | 
			
		||||
    this._initializer = initializer;
 | 
			
		||||
    this._headers = new RawHeaders(this._initializer.headers);
 | 
			
		||||
  }
 | 
			
		||||
@ -134,7 +136,7 @@ export class FetchResponse implements api.FetchResponse {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async body(): Promise<Buffer> {
 | 
			
		||||
    return this._context._wrapApiCall(async (channel: channels.BrowserContextChannel) => {
 | 
			
		||||
    return this._request._wrapApiCall(async (channel: channels.FetchRequestChannel) => {
 | 
			
		||||
      const result = await channel.fetchResponseBody({ fetchUid: this._fetchUid() });
 | 
			
		||||
      if (!result.binary)
 | 
			
		||||
        throw new Error('Response has been disposed');
 | 
			
		||||
@ -153,7 +155,7 @@ export class FetchResponse implements api.FetchResponse {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async dispose(): Promise<void> {
 | 
			
		||||
    return this._context._wrapApiCall(async (channel: channels.BrowserContextChannel) => {
 | 
			
		||||
    return this._request._wrapApiCall(async (channel: channels.FetchRequestChannel) => {
 | 
			
		||||
      await channel.disposeFetchResponse({ fetchUid: this._fetchUid() });
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -102,7 +102,7 @@ export class Page extends ChannelOwner<channels.PageChannel, channels.PageInitia
 | 
			
		||||
    this.accessibility = new Accessibility(this._channel);
 | 
			
		||||
    this.keyboard = new Keyboard(this);
 | 
			
		||||
    this.mouse = new Mouse(this);
 | 
			
		||||
    this._request = new FetchRequest(this._browserContext);
 | 
			
		||||
    this._request = this._browserContext._request;
 | 
			
		||||
    this.touchscreen = new Touchscreen(this);
 | 
			
		||||
 | 
			
		||||
    this._mainFrame = Frame.from(initializer.mainFrame);
 | 
			
		||||
 | 
			
		||||
@ -24,6 +24,7 @@ import { Android } from './android';
 | 
			
		||||
import { BrowserType } from './browserType';
 | 
			
		||||
import { ChannelOwner } from './channelOwner';
 | 
			
		||||
import { Electron } from './electron';
 | 
			
		||||
import { FetchRequest } from './fetch';
 | 
			
		||||
import { Selectors, SelectorsOwner, sharedSelectors } from './selectors';
 | 
			
		||||
import { Size } from './types';
 | 
			
		||||
const dnsLookupAsync = util.promisify(dns.lookup);
 | 
			
		||||
@ -68,6 +69,12 @@ export class Playwright extends ChannelOwner<channels.PlaywrightChannel, channel
 | 
			
		||||
    this.selectors._addChannel(this._selectorsOwner);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async _newRequest(options?: {}): Promise<FetchRequest> {
 | 
			
		||||
    return await this._wrapApiCall(async (channel: channels.PlaywrightChannel) => {
 | 
			
		||||
      return FetchRequest.from((await channel.newRequest({})).request);
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  _enablePortForwarding(redirectPortForTest?: number) {
 | 
			
		||||
    this._redirectPortForTest = redirectPortForTest;
 | 
			
		||||
    this._channel.on('socksRequested', ({ uid, host, port }) => this._onSocksRequested(uid, host, port));
 | 
			
		||||
 | 
			
		||||
@ -15,12 +15,11 @@
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
import { BrowserContext } from '../server/browserContext';
 | 
			
		||||
import { Dispatcher, DispatcherScope, lookupDispatcher } from './dispatcher';
 | 
			
		||||
import { Dispatcher, DispatcherScope, existingDispatcher, lookupDispatcher } from './dispatcher';
 | 
			
		||||
import { PageDispatcher, BindingCallDispatcher, WorkerDispatcher } from './pageDispatcher';
 | 
			
		||||
import { playwrightFetch } from '../server/fetch';
 | 
			
		||||
import { FrameDispatcher } from './frameDispatcher';
 | 
			
		||||
import * as channels from '../protocol/channels';
 | 
			
		||||
import { RouteDispatcher, RequestDispatcher, ResponseDispatcher } from './networkDispatchers';
 | 
			
		||||
import { RouteDispatcher, RequestDispatcher, ResponseDispatcher, FetchRequestDispatcher } from './networkDispatchers';
 | 
			
		||||
import { CRBrowserContext } from '../server/chromium/crBrowser';
 | 
			
		||||
import { CDPSessionDispatcher } from './cdpSessionDispatcher';
 | 
			
		||||
import { RecorderSupplement } from '../server/supplements/recorderSupplement';
 | 
			
		||||
@ -28,13 +27,15 @@ import { CallMetadata } from '../server/instrumentation';
 | 
			
		||||
import { ArtifactDispatcher } from './artifactDispatcher';
 | 
			
		||||
import { Artifact } from '../server/artifact';
 | 
			
		||||
import { Request, Response } from '../server/network';
 | 
			
		||||
import { arrayToObject, headersArrayToObject } from '../utils/utils';
 | 
			
		||||
 | 
			
		||||
export class BrowserContextDispatcher extends Dispatcher<BrowserContext, channels.BrowserContextInitializer, channels.BrowserContextEvents> implements channels.BrowserContextChannel {
 | 
			
		||||
  private _context: BrowserContext;
 | 
			
		||||
 | 
			
		||||
  constructor(scope: DispatcherScope, context: BrowserContext) {
 | 
			
		||||
    super(scope, context, 'BrowserContext', { isChromium: context._browser.options.isChromium }, true);
 | 
			
		||||
    super(scope, context, 'BrowserContext', {
 | 
			
		||||
      isChromium: context._browser.options.isChromium,
 | 
			
		||||
      fetchRequest: FetchRequestDispatcher.from(scope, context.fetchRequest),
 | 
			
		||||
    }, true);
 | 
			
		||||
    this._context = context;
 | 
			
		||||
    // Note: when launching persistent context, dispatcher is created very late,
 | 
			
		||||
    // so we can already have pages, videos and everything else.
 | 
			
		||||
@ -57,6 +58,10 @@ export class BrowserContextDispatcher extends Dispatcher<BrowserContext, channel
 | 
			
		||||
    context.on(BrowserContext.Events.Close, () => {
 | 
			
		||||
      this._dispatchEvent('close');
 | 
			
		||||
      this._dispose();
 | 
			
		||||
      const fetch = existingDispatcher<FetchRequestDispatcher>(this._context.fetchRequest);
 | 
			
		||||
      // FetchRequestDispatcher is created in the browser rather then context scope but its
 | 
			
		||||
      // lifetime is bound to the context dispatcher, so we manually dispose it here.
 | 
			
		||||
      fetch._disposeDispatcher();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    if (context._browser.options.name === 'chromium') {
 | 
			
		||||
@ -107,38 +112,6 @@ export class BrowserContextDispatcher extends Dispatcher<BrowserContext, channel
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async fetch(params: channels.BrowserContextFetchParams): Promise<channels.BrowserContextFetchResult> {
 | 
			
		||||
    const { fetchResponse, error } = await playwrightFetch(this._context, {
 | 
			
		||||
      url: params.url,
 | 
			
		||||
      params: arrayToObject(params.params),
 | 
			
		||||
      method: params.method,
 | 
			
		||||
      headers: params.headers ? headersArrayToObject(params.headers, false) : undefined,
 | 
			
		||||
      postData: params.postData ? Buffer.from(params.postData, 'base64') : undefined,
 | 
			
		||||
      timeout: params.timeout,
 | 
			
		||||
      failOnStatusCode: params.failOnStatusCode,
 | 
			
		||||
    });
 | 
			
		||||
    let response;
 | 
			
		||||
    if (fetchResponse) {
 | 
			
		||||
      response = {
 | 
			
		||||
        url: fetchResponse.url,
 | 
			
		||||
        status: fetchResponse.status,
 | 
			
		||||
        statusText: fetchResponse.statusText,
 | 
			
		||||
        headers: fetchResponse.headers,
 | 
			
		||||
        fetchUid: fetchResponse.fetchUid
 | 
			
		||||
      };
 | 
			
		||||
    }
 | 
			
		||||
    return { response, error };
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async fetchResponseBody(params: channels.BrowserContextFetchResponseBodyParams): Promise<channels.BrowserContextFetchResponseBodyResult> {
 | 
			
		||||
    const buffer = this._context.fetchResponses.get(params.fetchUid);
 | 
			
		||||
    return { binary: buffer ? buffer.toString('base64') : undefined };
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async disposeFetchResponse(params: channels.BrowserContextDisposeFetchResponseParams): Promise<channels.BrowserContextDisposeFetchResponseResult> {
 | 
			
		||||
    this._context.fetchResponses.delete(params.fetchUid);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async newPage(params: channels.BrowserContextNewPageParams, metadata: CallMetadata): Promise<channels.BrowserContextNewPageResult> {
 | 
			
		||||
    return { page: lookupDispatcher<PageDispatcher>(await this._context.newPage(metadata)) };
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -19,6 +19,8 @@ import * as channels from '../protocol/channels';
 | 
			
		||||
import { Dispatcher, DispatcherScope, lookupNullableDispatcher, existingDispatcher } from './dispatcher';
 | 
			
		||||
import { FrameDispatcher } from './frameDispatcher';
 | 
			
		||||
import { CallMetadata } from '../server/instrumentation';
 | 
			
		||||
import { FetchRequest } from '../server/fetch';
 | 
			
		||||
import { arrayToObject, headersArrayToObject } from '../utils/utils';
 | 
			
		||||
 | 
			
		||||
export class RequestDispatcher extends Dispatcher<Request, channels.RequestInitializer, channels.RequestEvents> implements channels.RequestChannel {
 | 
			
		||||
 | 
			
		||||
@ -156,3 +158,56 @@ export class WebSocketDispatcher extends Dispatcher<WebSocket, channels.WebSocke
 | 
			
		||||
    webSocket.on(WebSocket.Events.Close, () => this._dispatchEvent('close', {}));
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class FetchRequestDispatcher extends Dispatcher<FetchRequest, channels.FetchRequestInitializer, channels.FetchRequestEvents> implements channels.FetchRequestChannel {
 | 
			
		||||
  static from(scope: DispatcherScope, request: FetchRequest): FetchRequestDispatcher {
 | 
			
		||||
    const result = existingDispatcher<FetchRequestDispatcher>(request);
 | 
			
		||||
    return result || new FetchRequestDispatcher(scope, request);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static fromNullable(scope: DispatcherScope, request: FetchRequest | null): FetchRequestDispatcher | undefined {
 | 
			
		||||
    return request ? FetchRequestDispatcher.from(scope, request) : undefined;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private constructor(scope: DispatcherScope, request: FetchRequest) {
 | 
			
		||||
    super(scope, request, 'FetchRequest', {}, true);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async fetch(params: channels.FetchRequestFetchParams, metadata?: channels.Metadata): Promise<channels.FetchRequestFetchResult> {
 | 
			
		||||
    const { fetchResponse, error } = await this._object.fetch({
 | 
			
		||||
      url: params.url,
 | 
			
		||||
      params: arrayToObject(params.params),
 | 
			
		||||
      method: params.method,
 | 
			
		||||
      headers: params.headers ? headersArrayToObject(params.headers, false) : undefined,
 | 
			
		||||
      postData: params.postData ? Buffer.from(params.postData, 'base64') : undefined,
 | 
			
		||||
      timeout: params.timeout,
 | 
			
		||||
      failOnStatusCode: params.failOnStatusCode,
 | 
			
		||||
    });
 | 
			
		||||
    let response;
 | 
			
		||||
    if (fetchResponse) {
 | 
			
		||||
      response = {
 | 
			
		||||
        url: fetchResponse.url,
 | 
			
		||||
        status: fetchResponse.status,
 | 
			
		||||
        statusText: fetchResponse.statusText,
 | 
			
		||||
        headers: fetchResponse.headers,
 | 
			
		||||
        fetchUid: fetchResponse.fetchUid
 | 
			
		||||
      };
 | 
			
		||||
    }
 | 
			
		||||
    return { response, error };
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async fetchResponseBody(params: channels.FetchRequestFetchResponseBodyParams, metadata?: channels.Metadata): Promise<channels.FetchRequestFetchResponseBodyResult> {
 | 
			
		||||
    const buffer = this._object.fetchResponses.get(params.fetchUid);
 | 
			
		||||
    return { binary: buffer ? buffer.toString('base64') : undefined };
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async disposeFetchResponse(params: channels.FetchRequestDisposeFetchResponseParams, metadata?: channels.Metadata): Promise<void> {
 | 
			
		||||
    this._object.fetchResponses.delete(params.fetchUid);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  _disposeDispatcher() {
 | 
			
		||||
    if (!this._disposed)
 | 
			
		||||
      super._dispose();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -26,6 +26,8 @@ import * as types from '../server/types';
 | 
			
		||||
import { SocksConnection, SocksConnectionClient } from '../utils/socksProxy';
 | 
			
		||||
import { createGuid } from '../utils/utils';
 | 
			
		||||
import { debugLogger } from '../utils/debugLogger';
 | 
			
		||||
import { GlobalFetchRequest } from '../server/fetch';
 | 
			
		||||
import { FetchRequestDispatcher } from './networkDispatchers';
 | 
			
		||||
 | 
			
		||||
export class PlaywrightDispatcher extends Dispatcher<Playwright, channels.PlaywrightInitializer, channels.PlaywrightEvents> implements channels.PlaywrightChannel {
 | 
			
		||||
  private _socksProxy: SocksProxy | undefined;
 | 
			
		||||
@ -71,6 +73,11 @@ export class PlaywrightDispatcher extends Dispatcher<Playwright, channels.Playwr
 | 
			
		||||
  async socksEnd(params: channels.PlaywrightSocksEndParams): Promise<void> {
 | 
			
		||||
    this._socksProxy?.sendSocketEnd(params);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async newRequest(params: channels.PlaywrightNewRequestParams, metadata?: channels.Metadata): Promise<channels.PlaywrightNewRequestResult> {
 | 
			
		||||
    const request = new GlobalFetchRequest(this._object);
 | 
			
		||||
    return { request: FetchRequestDispatcher.from(this._scope, request) };
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class SocksProxy implements SocksConnectionClient {
 | 
			
		||||
 | 
			
		||||
@ -150,6 +150,54 @@ export type InterceptedResponse = {
 | 
			
		||||
  headers: NameValue[],
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// ----------- FetchRequest -----------
 | 
			
		||||
export type FetchRequestInitializer = {};
 | 
			
		||||
export interface FetchRequestChannel extends Channel {
 | 
			
		||||
  fetch(params: FetchRequestFetchParams, metadata?: Metadata): Promise<FetchRequestFetchResult>;
 | 
			
		||||
  fetchResponseBody(params: FetchRequestFetchResponseBodyParams, metadata?: Metadata): Promise<FetchRequestFetchResponseBodyResult>;
 | 
			
		||||
  disposeFetchResponse(params: FetchRequestDisposeFetchResponseParams, metadata?: Metadata): Promise<FetchRequestDisposeFetchResponseResult>;
 | 
			
		||||
}
 | 
			
		||||
export type FetchRequestFetchParams = {
 | 
			
		||||
  url: string,
 | 
			
		||||
  params?: NameValue[],
 | 
			
		||||
  method?: string,
 | 
			
		||||
  headers?: NameValue[],
 | 
			
		||||
  postData?: Binary,
 | 
			
		||||
  timeout?: number,
 | 
			
		||||
  failOnStatusCode?: boolean,
 | 
			
		||||
};
 | 
			
		||||
export type FetchRequestFetchOptions = {
 | 
			
		||||
  params?: NameValue[],
 | 
			
		||||
  method?: string,
 | 
			
		||||
  headers?: NameValue[],
 | 
			
		||||
  postData?: Binary,
 | 
			
		||||
  timeout?: number,
 | 
			
		||||
  failOnStatusCode?: boolean,
 | 
			
		||||
};
 | 
			
		||||
export type FetchRequestFetchResult = {
 | 
			
		||||
  response?: FetchResponse,
 | 
			
		||||
  error?: string,
 | 
			
		||||
};
 | 
			
		||||
export type FetchRequestFetchResponseBodyParams = {
 | 
			
		||||
  fetchUid: string,
 | 
			
		||||
};
 | 
			
		||||
export type FetchRequestFetchResponseBodyOptions = {
 | 
			
		||||
 | 
			
		||||
};
 | 
			
		||||
export type FetchRequestFetchResponseBodyResult = {
 | 
			
		||||
  binary?: Binary,
 | 
			
		||||
};
 | 
			
		||||
export type FetchRequestDisposeFetchResponseParams = {
 | 
			
		||||
  fetchUid: string,
 | 
			
		||||
};
 | 
			
		||||
export type FetchRequestDisposeFetchResponseOptions = {
 | 
			
		||||
 | 
			
		||||
};
 | 
			
		||||
export type FetchRequestDisposeFetchResponseResult = void;
 | 
			
		||||
 | 
			
		||||
export interface FetchRequestEvents {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export type FetchResponse = {
 | 
			
		||||
  fetchUid: string,
 | 
			
		||||
  url: string,
 | 
			
		||||
@ -213,6 +261,7 @@ export interface PlaywrightChannel extends Channel {
 | 
			
		||||
  socksData(params: PlaywrightSocksDataParams, metadata?: Metadata): Promise<PlaywrightSocksDataResult>;
 | 
			
		||||
  socksError(params: PlaywrightSocksErrorParams, metadata?: Metadata): Promise<PlaywrightSocksErrorResult>;
 | 
			
		||||
  socksEnd(params: PlaywrightSocksEndParams, metadata?: Metadata): Promise<PlaywrightSocksEndResult>;
 | 
			
		||||
  newRequest(params: PlaywrightNewRequestParams, metadata?: Metadata): Promise<PlaywrightNewRequestResult>;
 | 
			
		||||
}
 | 
			
		||||
export type PlaywrightSocksRequestedEvent = {
 | 
			
		||||
  uid: string,
 | 
			
		||||
@ -266,6 +315,15 @@ export type PlaywrightSocksEndOptions = {
 | 
			
		||||
 | 
			
		||||
};
 | 
			
		||||
export type PlaywrightSocksEndResult = void;
 | 
			
		||||
export type PlaywrightNewRequestParams = {
 | 
			
		||||
  ignoreHTTPSErrors?: boolean,
 | 
			
		||||
};
 | 
			
		||||
export type PlaywrightNewRequestOptions = {
 | 
			
		||||
  ignoreHTTPSErrors?: boolean,
 | 
			
		||||
};
 | 
			
		||||
export type PlaywrightNewRequestResult = {
 | 
			
		||||
  request: FetchRequestChannel,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export interface PlaywrightEvents {
 | 
			
		||||
  'socksRequested': PlaywrightSocksRequestedEvent;
 | 
			
		||||
@ -733,6 +791,7 @@ export interface EventTargetEvents {
 | 
			
		||||
// ----------- BrowserContext -----------
 | 
			
		||||
export type BrowserContextInitializer = {
 | 
			
		||||
  isChromium: boolean,
 | 
			
		||||
  fetchRequest: FetchRequestChannel,
 | 
			
		||||
};
 | 
			
		||||
export interface BrowserContextChannel extends EventTargetChannel {
 | 
			
		||||
  on(event: 'bindingCall', callback: (params: BrowserContextBindingCallEvent) => void): this;
 | 
			
		||||
@ -753,9 +812,6 @@ export interface BrowserContextChannel extends EventTargetChannel {
 | 
			
		||||
  close(params?: BrowserContextCloseParams, metadata?: Metadata): Promise<BrowserContextCloseResult>;
 | 
			
		||||
  cookies(params: BrowserContextCookiesParams, metadata?: Metadata): Promise<BrowserContextCookiesResult>;
 | 
			
		||||
  exposeBinding(params: BrowserContextExposeBindingParams, metadata?: Metadata): Promise<BrowserContextExposeBindingResult>;
 | 
			
		||||
  fetch(params: BrowserContextFetchParams, metadata?: Metadata): Promise<BrowserContextFetchResult>;
 | 
			
		||||
  fetchResponseBody(params: BrowserContextFetchResponseBodyParams, metadata?: Metadata): Promise<BrowserContextFetchResponseBodyResult>;
 | 
			
		||||
  disposeFetchResponse(params: BrowserContextDisposeFetchResponseParams, metadata?: Metadata): Promise<BrowserContextDisposeFetchResponseResult>;
 | 
			
		||||
  grantPermissions(params: BrowserContextGrantPermissionsParams, metadata?: Metadata): Promise<BrowserContextGrantPermissionsResult>;
 | 
			
		||||
  newPage(params?: BrowserContextNewPageParams, metadata?: Metadata): Promise<BrowserContextNewPageResult>;
 | 
			
		||||
  setDefaultNavigationTimeoutNoReply(params: BrowserContextSetDefaultNavigationTimeoutNoReplyParams, metadata?: Metadata): Promise<BrowserContextSetDefaultNavigationTimeoutNoReplyResult>;
 | 
			
		||||
@ -855,43 +911,6 @@ export type BrowserContextExposeBindingOptions = {
 | 
			
		||||
  needsHandle?: boolean,
 | 
			
		||||
};
 | 
			
		||||
export type BrowserContextExposeBindingResult = void;
 | 
			
		||||
export type BrowserContextFetchParams = {
 | 
			
		||||
  url: string,
 | 
			
		||||
  params?: NameValue[],
 | 
			
		||||
  method?: string,
 | 
			
		||||
  headers?: NameValue[],
 | 
			
		||||
  postData?: Binary,
 | 
			
		||||
  timeout?: number,
 | 
			
		||||
  failOnStatusCode?: boolean,
 | 
			
		||||
};
 | 
			
		||||
export type BrowserContextFetchOptions = {
 | 
			
		||||
  params?: NameValue[],
 | 
			
		||||
  method?: string,
 | 
			
		||||
  headers?: NameValue[],
 | 
			
		||||
  postData?: Binary,
 | 
			
		||||
  timeout?: number,
 | 
			
		||||
  failOnStatusCode?: boolean,
 | 
			
		||||
};
 | 
			
		||||
export type BrowserContextFetchResult = {
 | 
			
		||||
  response?: FetchResponse,
 | 
			
		||||
  error?: string,
 | 
			
		||||
};
 | 
			
		||||
export type BrowserContextFetchResponseBodyParams = {
 | 
			
		||||
  fetchUid: string,
 | 
			
		||||
};
 | 
			
		||||
export type BrowserContextFetchResponseBodyOptions = {
 | 
			
		||||
 | 
			
		||||
};
 | 
			
		||||
export type BrowserContextFetchResponseBodyResult = {
 | 
			
		||||
  binary?: Binary,
 | 
			
		||||
};
 | 
			
		||||
export type BrowserContextDisposeFetchResponseParams = {
 | 
			
		||||
  fetchUid: string,
 | 
			
		||||
};
 | 
			
		||||
export type BrowserContextDisposeFetchResponseOptions = {
 | 
			
		||||
 | 
			
		||||
};
 | 
			
		||||
export type BrowserContextDisposeFetchResponseResult = void;
 | 
			
		||||
export type BrowserContextGrantPermissionsParams = {
 | 
			
		||||
  permissions: string[],
 | 
			
		||||
  origin?: string,
 | 
			
		||||
 | 
			
		||||
@ -217,6 +217,39 @@ InterceptedResponse:
 | 
			
		||||
      items: NameValue
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
FetchRequest:
 | 
			
		||||
  type: interface
 | 
			
		||||
 | 
			
		||||
  commands:
 | 
			
		||||
 | 
			
		||||
    fetch:
 | 
			
		||||
      parameters:
 | 
			
		||||
        url: string
 | 
			
		||||
        params:
 | 
			
		||||
          type: array?
 | 
			
		||||
          items: NameValue
 | 
			
		||||
        method: string?
 | 
			
		||||
        headers:
 | 
			
		||||
          type: array?
 | 
			
		||||
          items: NameValue
 | 
			
		||||
        postData: binary?
 | 
			
		||||
        timeout: number?
 | 
			
		||||
        failOnStatusCode: boolean?
 | 
			
		||||
      returns:
 | 
			
		||||
        response: FetchResponse?
 | 
			
		||||
        error: string?
 | 
			
		||||
 | 
			
		||||
    fetchResponseBody:
 | 
			
		||||
      parameters:
 | 
			
		||||
        fetchUid: string
 | 
			
		||||
      returns:
 | 
			
		||||
        binary?: binary
 | 
			
		||||
 | 
			
		||||
    disposeFetchResponse:
 | 
			
		||||
      parameters:
 | 
			
		||||
        fetchUid: string
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
FetchResponse:
 | 
			
		||||
  type: object
 | 
			
		||||
  properties:
 | 
			
		||||
@ -417,6 +450,12 @@ Playwright:
 | 
			
		||||
      parameters:
 | 
			
		||||
        uid: string
 | 
			
		||||
 | 
			
		||||
    newRequest:
 | 
			
		||||
      parameters:
 | 
			
		||||
        ignoreHTTPSErrors: boolean?
 | 
			
		||||
      returns:
 | 
			
		||||
        request: FetchRequest
 | 
			
		||||
 | 
			
		||||
  events:
 | 
			
		||||
    socksRequested:
 | 
			
		||||
      parameters:
 | 
			
		||||
@ -579,6 +618,7 @@ BrowserContext:
 | 
			
		||||
 | 
			
		||||
  initializer:
 | 
			
		||||
    isChromium: boolean
 | 
			
		||||
    fetchRequest: FetchRequest
 | 
			
		||||
 | 
			
		||||
  commands:
 | 
			
		||||
 | 
			
		||||
@ -613,33 +653,6 @@ BrowserContext:
 | 
			
		||||
        name: string
 | 
			
		||||
        needsHandle: boolean?
 | 
			
		||||
 | 
			
		||||
    fetch:
 | 
			
		||||
      parameters:
 | 
			
		||||
        url: string
 | 
			
		||||
        params:
 | 
			
		||||
          type: array?
 | 
			
		||||
          items: NameValue
 | 
			
		||||
        method: string?
 | 
			
		||||
        headers:
 | 
			
		||||
          type: array?
 | 
			
		||||
          items: NameValue
 | 
			
		||||
        postData: binary?
 | 
			
		||||
        timeout: number?
 | 
			
		||||
        failOnStatusCode: boolean?
 | 
			
		||||
      returns:
 | 
			
		||||
        response: FetchResponse?
 | 
			
		||||
        error: string?
 | 
			
		||||
 | 
			
		||||
    fetchResponseBody:
 | 
			
		||||
      parameters:
 | 
			
		||||
        fetchUid: string
 | 
			
		||||
      returns:
 | 
			
		||||
        binary?: binary
 | 
			
		||||
 | 
			
		||||
    disposeFetchResponse:
 | 
			
		||||
      parameters:
 | 
			
		||||
        fetchUid: string
 | 
			
		||||
 | 
			
		||||
    grantPermissions:
 | 
			
		||||
      parameters:
 | 
			
		||||
        permissions:
 | 
			
		||||
 | 
			
		||||
@ -147,6 +147,21 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
 | 
			
		||||
    statusText: tString,
 | 
			
		||||
    headers: tArray(tType('NameValue')),
 | 
			
		||||
  });
 | 
			
		||||
  scheme.FetchRequestFetchParams = tObject({
 | 
			
		||||
    url: tString,
 | 
			
		||||
    params: tOptional(tArray(tType('NameValue'))),
 | 
			
		||||
    method: tOptional(tString),
 | 
			
		||||
    headers: tOptional(tArray(tType('NameValue'))),
 | 
			
		||||
    postData: tOptional(tBinary),
 | 
			
		||||
    timeout: tOptional(tNumber),
 | 
			
		||||
    failOnStatusCode: tOptional(tBoolean),
 | 
			
		||||
  });
 | 
			
		||||
  scheme.FetchRequestFetchResponseBodyParams = tObject({
 | 
			
		||||
    fetchUid: tString,
 | 
			
		||||
  });
 | 
			
		||||
  scheme.FetchRequestDisposeFetchResponseParams = tObject({
 | 
			
		||||
    fetchUid: tString,
 | 
			
		||||
  });
 | 
			
		||||
  scheme.FetchResponse = tObject({
 | 
			
		||||
    fetchUid: tString,
 | 
			
		||||
    url: tString,
 | 
			
		||||
@ -177,6 +192,9 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
 | 
			
		||||
  scheme.PlaywrightSocksEndParams = tObject({
 | 
			
		||||
    uid: tString,
 | 
			
		||||
  });
 | 
			
		||||
  scheme.PlaywrightNewRequestParams = tObject({
 | 
			
		||||
    ignoreHTTPSErrors: tOptional(tBoolean),
 | 
			
		||||
  });
 | 
			
		||||
  scheme.SelectorsRegisterParams = tObject({
 | 
			
		||||
    name: tString,
 | 
			
		||||
    source: tString,
 | 
			
		||||
@ -392,21 +410,6 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
 | 
			
		||||
    name: tString,
 | 
			
		||||
    needsHandle: tOptional(tBoolean),
 | 
			
		||||
  });
 | 
			
		||||
  scheme.BrowserContextFetchParams = tObject({
 | 
			
		||||
    url: tString,
 | 
			
		||||
    params: tOptional(tArray(tType('NameValue'))),
 | 
			
		||||
    method: tOptional(tString),
 | 
			
		||||
    headers: tOptional(tArray(tType('NameValue'))),
 | 
			
		||||
    postData: tOptional(tBinary),
 | 
			
		||||
    timeout: tOptional(tNumber),
 | 
			
		||||
    failOnStatusCode: tOptional(tBoolean),
 | 
			
		||||
  });
 | 
			
		||||
  scheme.BrowserContextFetchResponseBodyParams = tObject({
 | 
			
		||||
    fetchUid: tString,
 | 
			
		||||
  });
 | 
			
		||||
  scheme.BrowserContextDisposeFetchResponseParams = tObject({
 | 
			
		||||
    fetchUid: tString,
 | 
			
		||||
  });
 | 
			
		||||
  scheme.BrowserContextGrantPermissionsParams = tObject({
 | 
			
		||||
    permissions: tArray(tString),
 | 
			
		||||
    origin: tOptional(tString),
 | 
			
		||||
 | 
			
		||||
@ -34,6 +34,7 @@ import { Tracing } from './trace/recorder/tracing';
 | 
			
		||||
import { HarRecorder } from './supplements/har/harRecorder';
 | 
			
		||||
import { RecorderSupplement } from './supplements/recorderSupplement';
 | 
			
		||||
import * as consoleApiSource from '../generated/consoleApiSource';
 | 
			
		||||
import { BrowserContextFetchRequest } from './fetch';
 | 
			
		||||
 | 
			
		||||
export abstract class BrowserContext extends SdkObject {
 | 
			
		||||
  static Events = {
 | 
			
		||||
@ -63,7 +64,7 @@ export abstract class BrowserContext extends SdkObject {
 | 
			
		||||
  private _origins = new Set<string>();
 | 
			
		||||
  readonly _harRecorder: HarRecorder | undefined;
 | 
			
		||||
  readonly tracing: Tracing;
 | 
			
		||||
  readonly fetchResponses: Map<string, Buffer> = new Map();
 | 
			
		||||
  readonly fetchRequest = new BrowserContextFetchRequest(this);
 | 
			
		||||
 | 
			
		||||
  constructor(browser: Browser, options: types.BrowserContextOptions, browserContextId: string | undefined) {
 | 
			
		||||
    super(browser, 'browser-context');
 | 
			
		||||
@ -133,7 +134,7 @@ export abstract class BrowserContext extends SdkObject {
 | 
			
		||||
    this._closedStatus = 'closed';
 | 
			
		||||
    this._deleteAllDownloads();
 | 
			
		||||
    this._downloads.clear();
 | 
			
		||||
    this.fetchResponses.clear();
 | 
			
		||||
    this.fetchRequest.dispose();
 | 
			
		||||
    if (this._isPersistentContext)
 | 
			
		||||
      this._onClosePersistent();
 | 
			
		||||
    this._closePromiseFulfill!(new Error('Context closed'));
 | 
			
		||||
@ -382,12 +383,6 @@ export abstract class BrowserContext extends SdkObject {
 | 
			
		||||
    this.on(BrowserContext.Events.Page, installInPage);
 | 
			
		||||
    return Promise.all(this.pages().map(installInPage));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  storeFetchResponseBody(body: Buffer): string {
 | 
			
		||||
    const uid = createGuid();
 | 
			
		||||
    this.fetchResponses.set(uid, body);
 | 
			
		||||
    return uid;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function assertBrowserContextIsNotOwned(context: BrowserContext) {
 | 
			
		||||
 | 
			
		||||
@ -22,209 +22,303 @@ import * as https from 'https';
 | 
			
		||||
import { BrowserContext } from './browserContext';
 | 
			
		||||
import * as types from './types';
 | 
			
		||||
import { pipeline, Readable, Transform } from 'stream';
 | 
			
		||||
import { monotonicTime } from '../utils/utils';
 | 
			
		||||
import { createGuid, monotonicTime } from '../utils/utils';
 | 
			
		||||
import { SdkObject } from './instrumentation';
 | 
			
		||||
import { Playwright } from './playwright';
 | 
			
		||||
import { HeadersArray, ProxySettings } from './types';
 | 
			
		||||
import { HTTPCredentials } from '../../types/types';
 | 
			
		||||
import { TimeoutSettings } from '../utils/timeoutSettings';
 | 
			
		||||
 | 
			
		||||
export async function playwrightFetch(context: BrowserContext, params: types.FetchOptions): Promise<{fetchResponse?: Omit<types.FetchResponse, 'body'> & { fetchUid: string }, error?: string}> {
 | 
			
		||||
  try {
 | 
			
		||||
    const headers: { [name: string]: string } = {};
 | 
			
		||||
    if (params.headers) {
 | 
			
		||||
      for (const [name, value] of Object.entries(params.headers))
 | 
			
		||||
        headers[name.toLowerCase()] = value;
 | 
			
		||||
    }
 | 
			
		||||
    headers['user-agent'] ??= context._options.userAgent || context._browser.userAgent();
 | 
			
		||||
    headers['accept'] ??= '*/*';
 | 
			
		||||
    headers['accept-encoding'] ??= 'gzip,deflate,br';
 | 
			
		||||
 | 
			
		||||
    if (context._options.extraHTTPHeaders) {
 | 
			
		||||
      for (const {name, value} of context._options.extraHTTPHeaders)
 | 
			
		||||
        headers[name.toLowerCase()] = value;
 | 
			
		||||
    }
 | 
			
		||||
type FetchRequestOptions = {
 | 
			
		||||
  userAgent: string;
 | 
			
		||||
  extraHTTPHeaders?: HeadersArray;
 | 
			
		||||
  httpCredentials?: HTTPCredentials;
 | 
			
		||||
  proxy?: ProxySettings;
 | 
			
		||||
  timeoutSettings: TimeoutSettings;
 | 
			
		||||
  ignoreHTTPSErrors?: boolean;
 | 
			
		||||
  baseURL?: string;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
    const method = params.method?.toUpperCase() || 'GET';
 | 
			
		||||
    const proxy = context._options.proxy || context._browser.options.proxy;
 | 
			
		||||
    let agent;
 | 
			
		||||
    if (proxy) {
 | 
			
		||||
      // TODO: support bypass proxy
 | 
			
		||||
      const proxyOpts = url.parse(proxy.server);
 | 
			
		||||
      if (proxy.username)
 | 
			
		||||
        proxyOpts.auth = `${proxy.username}:${proxy.password || ''}`;
 | 
			
		||||
      agent = new HttpsProxyAgent(proxyOpts);
 | 
			
		||||
    }
 | 
			
		||||
export abstract class FetchRequest extends SdkObject {
 | 
			
		||||
  readonly fetchResponses: Map<string, Buffer> = new Map();
 | 
			
		||||
 | 
			
		||||
    const timeout = context._timeoutSettings.timeout(params);
 | 
			
		||||
    const deadline = monotonicTime() + timeout;
 | 
			
		||||
 | 
			
		||||
    const options: https.RequestOptions & { maxRedirects: number, deadline: number } = {
 | 
			
		||||
      method,
 | 
			
		||||
      headers,
 | 
			
		||||
      agent,
 | 
			
		||||
      maxRedirects: 20,
 | 
			
		||||
      timeout,
 | 
			
		||||
      deadline
 | 
			
		||||
    };
 | 
			
		||||
    // rejectUnauthorized = undefined is treated as true in node 12.
 | 
			
		||||
    if (context._options.ignoreHTTPSErrors)
 | 
			
		||||
      options.rejectUnauthorized = false;
 | 
			
		||||
 | 
			
		||||
    const requestUrl = new URL(params.url, context._options.baseURL);
 | 
			
		||||
    if (params.params) {
 | 
			
		||||
      for (const [name, value] of Object.entries(params.params))
 | 
			
		||||
        requestUrl.searchParams.set(name, value);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const fetchResponse = await sendRequest(context, requestUrl, options, params.postData);
 | 
			
		||||
    const fetchUid = context.storeFetchResponseBody(fetchResponse.body);
 | 
			
		||||
    if (params.failOnStatusCode && (fetchResponse.status < 200 || fetchResponse.status >= 400))
 | 
			
		||||
      return { error: `${fetchResponse.status} ${fetchResponse.statusText}` };
 | 
			
		||||
    return { fetchResponse: { ...fetchResponse, fetchUid } };
 | 
			
		||||
  } catch (e) {
 | 
			
		||||
    return { error: String(e) };
 | 
			
		||||
  constructor(parent: SdkObject) {
 | 
			
		||||
    super(parent, 'fetchRequest');
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function updateCookiesFromHeader(context: BrowserContext, responseUrl: string, setCookie: string[]) {
 | 
			
		||||
  const url = new URL(responseUrl);
 | 
			
		||||
  // https://datatracker.ietf.org/doc/html/rfc6265#section-5.1.4
 | 
			
		||||
  const defaultPath = '/' + url.pathname.substr(1).split('/').slice(0, -1).join('/');
 | 
			
		||||
  const cookies: types.SetNetworkCookieParam[] = [];
 | 
			
		||||
  for (const header of setCookie) {
 | 
			
		||||
    // Decode cookie value?
 | 
			
		||||
    const cookie: types.SetNetworkCookieParam | null = parseCookie(header);
 | 
			
		||||
    if (!cookie)
 | 
			
		||||
      continue;
 | 
			
		||||
    if (!cookie.domain)
 | 
			
		||||
      cookie.domain = url.hostname;
 | 
			
		||||
    if (!canSetCookie(cookie.domain!, url.hostname))
 | 
			
		||||
      continue;
 | 
			
		||||
    // https://datatracker.ietf.org/doc/html/rfc6265#section-5.2.4
 | 
			
		||||
    if (!cookie.path || !cookie.path.startsWith('/'))
 | 
			
		||||
      cookie.path = defaultPath;
 | 
			
		||||
    cookies.push(cookie);
 | 
			
		||||
  dispose() {
 | 
			
		||||
    this.fetchResponses.clear();
 | 
			
		||||
  }
 | 
			
		||||
  if (cookies.length)
 | 
			
		||||
    await context.addCookies(cookies);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function updateRequestCookieHeader(context: BrowserContext, url: URL, options: http.RequestOptions) {
 | 
			
		||||
  if (options.headers!['cookie'] !== undefined)
 | 
			
		||||
    return;
 | 
			
		||||
  const cookies = await context.cookies(url.toString());
 | 
			
		||||
  if (cookies.length) {
 | 
			
		||||
    const valueArray = cookies.map(c => `${c.name}=${c.value}`);
 | 
			
		||||
    options.headers!['cookie'] = valueArray.join('; ');
 | 
			
		||||
  abstract _defaultOptions(): FetchRequestOptions;
 | 
			
		||||
  abstract _addCookies(cookies: types.SetNetworkCookieParam[]): Promise<void>;
 | 
			
		||||
  abstract _cookies(url: string): Promise<types.NetworkCookie[]>;
 | 
			
		||||
 | 
			
		||||
  private _storeResponseBody(body: Buffer): string {
 | 
			
		||||
    const uid = createGuid();
 | 
			
		||||
    this.fetchResponses.set(uid, body);
 | 
			
		||||
    return uid;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function sendRequest(context: BrowserContext, url: URL, options: https.RequestOptions & { maxRedirects: number, deadline: number }, postData?: Buffer): Promise<types.FetchResponse>{
 | 
			
		||||
  await updateRequestCookieHeader(context, url, options);
 | 
			
		||||
  return new Promise<types.FetchResponse>((fulfill, reject) => {
 | 
			
		||||
    const requestConstructor: ((url: URL, options: http.RequestOptions, callback?: (res: http.IncomingMessage) => void) => http.ClientRequest)
 | 
			
		||||
      = (url.protocol === 'https:' ? https : http).request;
 | 
			
		||||
    const request = requestConstructor(url, options, async response => {
 | 
			
		||||
      if (response.headers['set-cookie'])
 | 
			
		||||
        await updateCookiesFromHeader(context, response.url || url.toString(), response.headers['set-cookie']);
 | 
			
		||||
      if (redirectStatus.includes(response.statusCode!)) {
 | 
			
		||||
        if (!options.maxRedirects) {
 | 
			
		||||
          reject(new Error('Max redirect count exceeded'));
 | 
			
		||||
          request.abort();
 | 
			
		||||
          return;
 | 
			
		||||
        }
 | 
			
		||||
        const headers = { ...options.headers };
 | 
			
		||||
        delete headers[`cookie`];
 | 
			
		||||
  async fetch(params: types.FetchOptions): Promise<{fetchResponse?: Omit<types.FetchResponse, 'body'> & { fetchUid: string }, error?: string}> {
 | 
			
		||||
    try {
 | 
			
		||||
      const headers: { [name: string]: string } = {};
 | 
			
		||||
      const defaults = this._defaultOptions();
 | 
			
		||||
      headers['user-agent'] = defaults.userAgent;
 | 
			
		||||
      headers['accept'] = '*/*';
 | 
			
		||||
      headers['accept-encoding'] = 'gzip,deflate,br';
 | 
			
		||||
 | 
			
		||||
        // HTTP-redirect fetch step 13 (https://fetch.spec.whatwg.org/#http-redirect-fetch)
 | 
			
		||||
        const status = response.statusCode!;
 | 
			
		||||
        let method = options.method!;
 | 
			
		||||
        if ((status === 301 || status === 302) && method === 'POST' ||
 | 
			
		||||
             status === 303 && !['GET', 'HEAD'].includes(method)) {
 | 
			
		||||
          method = 'GET';
 | 
			
		||||
          postData = undefined;
 | 
			
		||||
          delete headers[`content-encoding`];
 | 
			
		||||
          delete headers[`content-language`];
 | 
			
		||||
          delete headers[`content-location`];
 | 
			
		||||
          delete headers[`content-type`];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const redirectOptions: http.RequestOptions & { maxRedirects: number, deadline: number } = {
 | 
			
		||||
          method,
 | 
			
		||||
          headers,
 | 
			
		||||
          agent: options.agent,
 | 
			
		||||
          maxRedirects: options.maxRedirects - 1,
 | 
			
		||||
          timeout: options.timeout,
 | 
			
		||||
          deadline: options.deadline
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        // HTTP-redirect fetch step 4: If locationURL is null, then return response.
 | 
			
		||||
        if (response.headers.location) {
 | 
			
		||||
          const locationURL = new URL(response.headers.location, url);
 | 
			
		||||
          fulfill(sendRequest(context, locationURL, redirectOptions, postData));
 | 
			
		||||
          request.abort();
 | 
			
		||||
          return;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      if (response.statusCode === 401 && !options.headers!['authorization']) {
 | 
			
		||||
        const auth = response.headers['www-authenticate'];
 | 
			
		||||
        const credentials = context._options.httpCredentials;
 | 
			
		||||
        if (auth?.trim().startsWith('Basic ') && credentials) {
 | 
			
		||||
          const {username, password} = credentials;
 | 
			
		||||
          const encoded = Buffer.from(`${username || ''}:${password || ''}`).toString('base64');
 | 
			
		||||
          options.headers!['authorization'] = `Basic ${encoded}`;
 | 
			
		||||
          fulfill(sendRequest(context, url, options, postData));
 | 
			
		||||
          request.abort();
 | 
			
		||||
          return;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      response.on('aborted', () => reject(new Error('aborted')));
 | 
			
		||||
 | 
			
		||||
      let body: Readable = response;
 | 
			
		||||
      let transform: Transform | undefined;
 | 
			
		||||
      const encoding = response.headers['content-encoding'];
 | 
			
		||||
      if (encoding === 'gzip' || encoding === 'x-gzip') {
 | 
			
		||||
        transform = zlib.createGunzip({
 | 
			
		||||
          flush: zlib.constants.Z_SYNC_FLUSH,
 | 
			
		||||
          finishFlush: zlib.constants.Z_SYNC_FLUSH
 | 
			
		||||
        });
 | 
			
		||||
      } else if (encoding === 'br') {
 | 
			
		||||
        transform = zlib.createBrotliDecompress();
 | 
			
		||||
      } else if (encoding === 'deflate') {
 | 
			
		||||
        transform = zlib.createInflate();
 | 
			
		||||
      }
 | 
			
		||||
      if (transform) {
 | 
			
		||||
        body = pipeline(response, transform, e => {
 | 
			
		||||
          if (e)
 | 
			
		||||
            reject(new Error(`failed to decompress '${encoding}' encoding: ${e}`));
 | 
			
		||||
        });
 | 
			
		||||
      if (defaults.extraHTTPHeaders) {
 | 
			
		||||
        for (const {name, value} of defaults.extraHTTPHeaders)
 | 
			
		||||
          headers[name.toLowerCase()] = value;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      const chunks: Buffer[] = [];
 | 
			
		||||
      body.on('data', chunk => chunks.push(chunk));
 | 
			
		||||
      body.on('end', () => {
 | 
			
		||||
        const body = Buffer.concat(chunks);
 | 
			
		||||
        fulfill({
 | 
			
		||||
          url: response.url || url.toString(),
 | 
			
		||||
          status: response.statusCode || 0,
 | 
			
		||||
          statusText: response.statusMessage || '',
 | 
			
		||||
          headers: toHeadersArray(response.rawHeaders),
 | 
			
		||||
          body
 | 
			
		||||
        });
 | 
			
		||||
      });
 | 
			
		||||
      body.on('error',reject);
 | 
			
		||||
    });
 | 
			
		||||
    request.on('error', reject);
 | 
			
		||||
    const rejectOnTimeout = () =>  {
 | 
			
		||||
      reject(new Error(`Request timed out after ${options.timeout}ms`));
 | 
			
		||||
      request.abort();
 | 
			
		||||
    };
 | 
			
		||||
    const remaining = options.deadline - monotonicTime();
 | 
			
		||||
    if (remaining <= 0) {
 | 
			
		||||
      rejectOnTimeout();
 | 
			
		||||
      if (params.headers) {
 | 
			
		||||
        for (const [name, value] of Object.entries(params.headers))
 | 
			
		||||
          headers[name.toLowerCase()] = value;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      const method = params.method?.toUpperCase() || 'GET';
 | 
			
		||||
      const proxy = defaults.proxy;
 | 
			
		||||
      let agent;
 | 
			
		||||
      if (proxy) {
 | 
			
		||||
        // TODO: support bypass proxy
 | 
			
		||||
        const proxyOpts = url.parse(proxy.server);
 | 
			
		||||
        if (proxy.username)
 | 
			
		||||
          proxyOpts.auth = `${proxy.username}:${proxy.password || ''}`;
 | 
			
		||||
        agent = new HttpsProxyAgent(proxyOpts);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      const timeout = defaults.timeoutSettings.timeout(params);
 | 
			
		||||
      const deadline = monotonicTime() + timeout;
 | 
			
		||||
 | 
			
		||||
      const options: https.RequestOptions & { maxRedirects: number, deadline: number } = {
 | 
			
		||||
        method,
 | 
			
		||||
        headers,
 | 
			
		||||
        agent,
 | 
			
		||||
        maxRedirects: 20,
 | 
			
		||||
        timeout,
 | 
			
		||||
        deadline
 | 
			
		||||
      };
 | 
			
		||||
      // rejectUnauthorized = undefined is treated as true in node 12.
 | 
			
		||||
      if (defaults.ignoreHTTPSErrors)
 | 
			
		||||
        options.rejectUnauthorized = false;
 | 
			
		||||
 | 
			
		||||
      const requestUrl = new URL(params.url, defaults.baseURL);
 | 
			
		||||
      if (params.params) {
 | 
			
		||||
        for (const [name, value] of Object.entries(params.params))
 | 
			
		||||
          requestUrl.searchParams.set(name, value);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      const fetchResponse = await this._sendRequest(requestUrl, options, params.postData);
 | 
			
		||||
      const fetchUid = this._storeResponseBody(fetchResponse.body);
 | 
			
		||||
      if (params.failOnStatusCode && (fetchResponse.status < 200 || fetchResponse.status >= 400))
 | 
			
		||||
        return { error: `${fetchResponse.status} ${fetchResponse.statusText}` };
 | 
			
		||||
      return { fetchResponse: { ...fetchResponse, fetchUid } };
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      return { error: String(e) };
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private async _updateCookiesFromHeader(responseUrl: string, setCookie: string[]) {
 | 
			
		||||
    const url = new URL(responseUrl);
 | 
			
		||||
    // https://datatracker.ietf.org/doc/html/rfc6265#section-5.1.4
 | 
			
		||||
    const defaultPath = '/' + url.pathname.substr(1).split('/').slice(0, -1).join('/');
 | 
			
		||||
    const cookies: types.SetNetworkCookieParam[] = [];
 | 
			
		||||
    for (const header of setCookie) {
 | 
			
		||||
      // Decode cookie value?
 | 
			
		||||
      const cookie: types.SetNetworkCookieParam | null = parseCookie(header);
 | 
			
		||||
      if (!cookie)
 | 
			
		||||
        continue;
 | 
			
		||||
      if (!cookie.domain)
 | 
			
		||||
        cookie.domain = url.hostname;
 | 
			
		||||
      if (!canSetCookie(cookie.domain!, url.hostname))
 | 
			
		||||
        continue;
 | 
			
		||||
      // https://datatracker.ietf.org/doc/html/rfc6265#section-5.2.4
 | 
			
		||||
      if (!cookie.path || !cookie.path.startsWith('/'))
 | 
			
		||||
        cookie.path = defaultPath;
 | 
			
		||||
      cookies.push(cookie);
 | 
			
		||||
    }
 | 
			
		||||
    if (cookies.length)
 | 
			
		||||
      await this._addCookies(cookies);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private async _updateRequestCookieHeader(url: URL, options: http.RequestOptions) {
 | 
			
		||||
    if (options.headers!['cookie'] !== undefined)
 | 
			
		||||
      return;
 | 
			
		||||
    const cookies = await this._cookies(url.toString());
 | 
			
		||||
    if (cookies.length) {
 | 
			
		||||
      const valueArray = cookies.map(c => `${c.name}=${c.value}`);
 | 
			
		||||
      options.headers!['cookie'] = valueArray.join('; ');
 | 
			
		||||
    }
 | 
			
		||||
    request.setTimeout(remaining, rejectOnTimeout);
 | 
			
		||||
    if (postData)
 | 
			
		||||
      request.write(postData);
 | 
			
		||||
    request.end();
 | 
			
		||||
  });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private async _sendRequest(url: URL, options: https.RequestOptions & { maxRedirects: number, deadline: number }, postData?: Buffer): Promise<types.FetchResponse>{
 | 
			
		||||
    await this._updateRequestCookieHeader(url, options);
 | 
			
		||||
    return new Promise<types.FetchResponse>((fulfill, reject) => {
 | 
			
		||||
      const requestConstructor: ((url: URL, options: http.RequestOptions, callback?: (res: http.IncomingMessage) => void) => http.ClientRequest)
 | 
			
		||||
        = (url.protocol === 'https:' ? https : http).request;
 | 
			
		||||
      const request = requestConstructor(url, options, async response => {
 | 
			
		||||
        if (response.headers['set-cookie'])
 | 
			
		||||
          await this._updateCookiesFromHeader(response.url || url.toString(), response.headers['set-cookie']);
 | 
			
		||||
        if (redirectStatus.includes(response.statusCode!)) {
 | 
			
		||||
          if (!options.maxRedirects) {
 | 
			
		||||
            reject(new Error('Max redirect count exceeded'));
 | 
			
		||||
            request.abort();
 | 
			
		||||
            return;
 | 
			
		||||
          }
 | 
			
		||||
          const headers = { ...options.headers };
 | 
			
		||||
          delete headers[`cookie`];
 | 
			
		||||
 | 
			
		||||
          // HTTP-redirect fetch step 13 (https://fetch.spec.whatwg.org/#http-redirect-fetch)
 | 
			
		||||
          const status = response.statusCode!;
 | 
			
		||||
          let method = options.method!;
 | 
			
		||||
          if ((status === 301 || status === 302) && method === 'POST' ||
 | 
			
		||||
              status === 303 && !['GET', 'HEAD'].includes(method)) {
 | 
			
		||||
            method = 'GET';
 | 
			
		||||
            postData = undefined;
 | 
			
		||||
            delete headers[`content-encoding`];
 | 
			
		||||
            delete headers[`content-language`];
 | 
			
		||||
            delete headers[`content-location`];
 | 
			
		||||
            delete headers[`content-type`];
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          const redirectOptions: http.RequestOptions & { maxRedirects: number, deadline: number } = {
 | 
			
		||||
            method,
 | 
			
		||||
            headers,
 | 
			
		||||
            agent: options.agent,
 | 
			
		||||
            maxRedirects: options.maxRedirects - 1,
 | 
			
		||||
            timeout: options.timeout,
 | 
			
		||||
            deadline: options.deadline
 | 
			
		||||
          };
 | 
			
		||||
 | 
			
		||||
          // HTTP-redirect fetch step 4: If locationURL is null, then return response.
 | 
			
		||||
          if (response.headers.location) {
 | 
			
		||||
            const locationURL = new URL(response.headers.location, url);
 | 
			
		||||
            fulfill(this._sendRequest(locationURL, redirectOptions, postData));
 | 
			
		||||
            request.abort();
 | 
			
		||||
            return;
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
        if (response.statusCode === 401 && !options.headers!['authorization']) {
 | 
			
		||||
          const auth = response.headers['www-authenticate'];
 | 
			
		||||
          const credentials = this._defaultOptions().httpCredentials;
 | 
			
		||||
          if (auth?.trim().startsWith('Basic ') && credentials) {
 | 
			
		||||
            const {username, password} = credentials;
 | 
			
		||||
            const encoded = Buffer.from(`${username || ''}:${password || ''}`).toString('base64');
 | 
			
		||||
            options.headers!['authorization'] = `Basic ${encoded}`;
 | 
			
		||||
            fulfill(this._sendRequest(url, options, postData));
 | 
			
		||||
            request.abort();
 | 
			
		||||
            return;
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
        response.on('aborted', () => reject(new Error('aborted')));
 | 
			
		||||
 | 
			
		||||
        let body: Readable = response;
 | 
			
		||||
        let transform: Transform | undefined;
 | 
			
		||||
        const encoding = response.headers['content-encoding'];
 | 
			
		||||
        if (encoding === 'gzip' || encoding === 'x-gzip') {
 | 
			
		||||
          transform = zlib.createGunzip({
 | 
			
		||||
            flush: zlib.constants.Z_SYNC_FLUSH,
 | 
			
		||||
            finishFlush: zlib.constants.Z_SYNC_FLUSH
 | 
			
		||||
          });
 | 
			
		||||
        } else if (encoding === 'br') {
 | 
			
		||||
          transform = zlib.createBrotliDecompress();
 | 
			
		||||
        } else if (encoding === 'deflate') {
 | 
			
		||||
          transform = zlib.createInflate();
 | 
			
		||||
        }
 | 
			
		||||
        if (transform) {
 | 
			
		||||
          body = pipeline(response, transform, e => {
 | 
			
		||||
            if (e)
 | 
			
		||||
              reject(new Error(`failed to decompress '${encoding}' encoding: ${e}`));
 | 
			
		||||
          });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const chunks: Buffer[] = [];
 | 
			
		||||
        body.on('data', chunk => chunks.push(chunk));
 | 
			
		||||
        body.on('end', () => {
 | 
			
		||||
          const body = Buffer.concat(chunks);
 | 
			
		||||
          fulfill({
 | 
			
		||||
            url: response.url || url.toString(),
 | 
			
		||||
            status: response.statusCode || 0,
 | 
			
		||||
            statusText: response.statusMessage || '',
 | 
			
		||||
            headers: toHeadersArray(response.rawHeaders),
 | 
			
		||||
            body
 | 
			
		||||
          });
 | 
			
		||||
        });
 | 
			
		||||
        body.on('error',reject);
 | 
			
		||||
      });
 | 
			
		||||
      request.on('error', reject);
 | 
			
		||||
      const rejectOnTimeout = () =>  {
 | 
			
		||||
        reject(new Error(`Request timed out after ${options.timeout}ms`));
 | 
			
		||||
        request.abort();
 | 
			
		||||
      };
 | 
			
		||||
      const remaining = options.deadline - monotonicTime();
 | 
			
		||||
      if (remaining <= 0) {
 | 
			
		||||
        rejectOnTimeout();
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
      request.setTimeout(remaining, rejectOnTimeout);
 | 
			
		||||
      if (postData)
 | 
			
		||||
        request.write(postData);
 | 
			
		||||
      request.end();
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class BrowserContextFetchRequest extends FetchRequest {
 | 
			
		||||
  private readonly _context: BrowserContext;
 | 
			
		||||
 | 
			
		||||
  constructor(context: BrowserContext) {
 | 
			
		||||
    super(context);
 | 
			
		||||
    this._context = context;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  _defaultOptions(): FetchRequestOptions {
 | 
			
		||||
    return {
 | 
			
		||||
      userAgent: this._context._options.userAgent || this._context._browser.userAgent(),
 | 
			
		||||
      extraHTTPHeaders: this._context._options.extraHTTPHeaders,
 | 
			
		||||
      httpCredentials: this._context._options.httpCredentials,
 | 
			
		||||
      proxy: this._context._options.proxy || this._context._browser.options.proxy,
 | 
			
		||||
      timeoutSettings: this._context._timeoutSettings,
 | 
			
		||||
      ignoreHTTPSErrors: this._context._options.ignoreHTTPSErrors,
 | 
			
		||||
      baseURL: this._context._options.baseURL,
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async _addCookies(cookies: types.SetNetworkCookieParam[]): Promise<void> {
 | 
			
		||||
    await this._context.addCookies(cookies);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async _cookies(url: string): Promise<types.NetworkCookie[]> {
 | 
			
		||||
    return await this._context.cookies(url);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
export class GlobalFetchRequest extends FetchRequest {
 | 
			
		||||
  constructor(playwright: Playwright) {
 | 
			
		||||
    super(playwright);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  _defaultOptions(): FetchRequestOptions {
 | 
			
		||||
    return {
 | 
			
		||||
      userAgent: '',
 | 
			
		||||
      extraHTTPHeaders: undefined,
 | 
			
		||||
      proxy: undefined,
 | 
			
		||||
      timeoutSettings: new TimeoutSettings(),
 | 
			
		||||
      ignoreHTTPSErrors: false,
 | 
			
		||||
      baseURL: undefined,
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async _addCookies(cookies: types.SetNetworkCookieParam[]): Promise<void> {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async _cookies(url: string): Promise<types.NetworkCookie[]> {
 | 
			
		||||
    return [];
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function toHeadersArray(rawHeaders: string[]): types.HeadersArray {
 | 
			
		||||
 | 
			
		||||
@ -228,7 +228,7 @@ export class Route extends SdkObject {
 | 
			
		||||
    if (body === undefined) {
 | 
			
		||||
      if (overrides.fetchResponseUid) {
 | 
			
		||||
        const context = this._request.frame()._page._browserContext;
 | 
			
		||||
        const buffer = context.fetchResponses.get(overrides.fetchResponseUid);
 | 
			
		||||
        const buffer = context.fetchRequest.fetchResponses.get(overrides.fetchResponseUid);
 | 
			
		||||
        assert(buffer, 'Fetch response has been disposed');
 | 
			
		||||
        body = buffer.toString('utf8');
 | 
			
		||||
        isBase64 = false;
 | 
			
		||||
 | 
			
		||||
@ -40,6 +40,19 @@ it.afterAll(() => {
 | 
			
		||||
  http.globalAgent = prevAgent;
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
it('global get should work', async ({playwright, context, server}) => {
 | 
			
		||||
  const request = await playwright._newRequest();
 | 
			
		||||
  const response = await request.get(server.PREFIX + '/simple.json');
 | 
			
		||||
  expect(response.url()).toBe(server.PREFIX + '/simple.json');
 | 
			
		||||
  expect(response.status()).toBe(200);
 | 
			
		||||
  expect(response.statusText()).toBe('OK');
 | 
			
		||||
  expect(response.ok()).toBeTruthy();
 | 
			
		||||
  expect(response.url()).toBe(server.PREFIX + '/simple.json');
 | 
			
		||||
  expect(response.headers()['content-type']).toBe('application/json; charset=utf-8');
 | 
			
		||||
  expect(response.headersArray()).toContainEqual({ name: 'Content-Type', value: 'application/json; charset=utf-8' });
 | 
			
		||||
  expect(await response.text()).toBe('{"foo": "bar"}\n');
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
it('get should work', async ({context, server}) => {
 | 
			
		||||
  const response = await context._request.get(server.PREFIX + '/simple.json');
 | 
			
		||||
  expect(response.url()).toBe(server.PREFIX + '/simple.json');
 | 
			
		||||
 | 
			
		||||
@ -57,6 +57,7 @@ it('should scope context handles', async ({browserType, browserOptions, server})
 | 
			
		||||
            { _guid: 'request', objects: [] },
 | 
			
		||||
            { _guid: 'response', objects: [] },
 | 
			
		||||
          ]},
 | 
			
		||||
          { _guid: 'fetchRequest', objects: [] }
 | 
			
		||||
        ] },
 | 
			
		||||
      ] },
 | 
			
		||||
      { _guid: 'electron', objects: [] },
 | 
			
		||||
@ -140,7 +141,8 @@ it('should scope browser handles', async ({browserType, browserOptions}) => {
 | 
			
		||||
      { _guid: 'browser-type', objects: [
 | 
			
		||||
        {
 | 
			
		||||
          _guid: 'browser', objects: [
 | 
			
		||||
            { _guid: 'browser-context', objects: [] }
 | 
			
		||||
            { _guid: 'browser-context', objects: [] },
 | 
			
		||||
            { _guid: 'fetchRequest', objects: [] }
 | 
			
		||||
          ]
 | 
			
		||||
        },
 | 
			
		||||
      ]
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										1
									
								
								types/types.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								types/types.d.ts
									
									
									
									
										vendored
									
									
								
							@ -10668,6 +10668,7 @@ export const firefox: BrowserType;
 | 
			
		||||
export const webkit: BrowserType;
 | 
			
		||||
export const _electron: Electron;
 | 
			
		||||
export const _android: Android;
 | 
			
		||||
export const _newRequest: () => Promise<FetchRequest>;
 | 
			
		||||
 | 
			
		||||
// This is required to not export everything by default. See https://github.com/Microsoft/TypeScript/issues/19545#issuecomment-340490459
 | 
			
		||||
export {};
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										1
									
								
								utils/generate_types/overrides.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								utils/generate_types/overrides.d.ts
									
									
									
									
										vendored
									
									
								
							@ -348,6 +348,7 @@ export const firefox: BrowserType;
 | 
			
		||||
export const webkit: BrowserType;
 | 
			
		||||
export const _electron: Electron;
 | 
			
		||||
export const _android: Android;
 | 
			
		||||
export const _newRequest: () => Promise<FetchRequest>;
 | 
			
		||||
 | 
			
		||||
// This is required to not export everything by default. See https://github.com/Microsoft/TypeScript/issues/19545#issuecomment-340490459
 | 
			
		||||
export {};
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user