"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.BidiNetworkManager = void 0;
exports.bidiBytesValueToString = bidiBytesValueToString;
var _eventsHelper = require("../../utils/eventsHelper");
var network = _interopRequireWildcard(require("../network"));
var bidi = _interopRequireWildcard(require("./third_party/bidiProtocol"));
var _cookieStore = require("../cookieStore");
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
/**
 * 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.
 */

class BidiNetworkManager {
  constructor(bidiSession, page, onNavigationResponseStarted) {
    this._session = void 0;
    this._requests = void 0;
    this._page = void 0;
    this._eventListeners = void 0;
    this._onNavigationResponseStarted = void 0;
    this._userRequestInterceptionEnabled = false;
    this._protocolRequestInterceptionEnabled = false;
    this._credentials = void 0;
    this._intercepId = void 0;
    this._session = bidiSession;
    this._requests = new Map();
    this._page = page;
    this._onNavigationResponseStarted = onNavigationResponseStarted;
    this._eventListeners = [_eventsHelper.eventsHelper.addEventListener(bidiSession, 'network.beforeRequestSent', this._onBeforeRequestSent.bind(this)), _eventsHelper.eventsHelper.addEventListener(bidiSession, 'network.responseStarted', this._onResponseStarted.bind(this)), _eventsHelper.eventsHelper.addEventListener(bidiSession, 'network.responseCompleted', this._onResponseCompleted.bind(this)), _eventsHelper.eventsHelper.addEventListener(bidiSession, 'network.fetchError', this._onFetchError.bind(this)), _eventsHelper.eventsHelper.addEventListener(bidiSession, 'network.authRequired', this._onAuthRequired.bind(this))];
  }
  dispose() {
    _eventsHelper.eventsHelper.removeEventListeners(this._eventListeners);
  }
  _onBeforeRequestSent(param) {
    if (param.request.url.startsWith('data:')) return;
    const redirectedFrom = param.redirectCount ? this._requests.get(param.request.request) || null : null;
    const frame = redirectedFrom ? redirectedFrom.request.frame() : param.context ? this._page._frameManager.frame(param.context) : null;
    if (!frame) return;
    if (redirectedFrom) this._requests.delete(redirectedFrom._id);
    let route;
    if (param.intercepts) {
      // We do not support intercepting redirects.
      if (redirectedFrom) {
        var _redirectedFrom$_orig;
        this._session.sendMayFail('network.continueRequest', {
          request: param.request.request,
          ...(((_redirectedFrom$_orig = redirectedFrom._originalRequestRoute) === null || _redirectedFrom$_orig === void 0 ? void 0 : _redirectedFrom$_orig._alreadyContinuedHeaders) || {})
        });
      } else {
        route = new BidiRouteImpl(this._session, param.request.request);
      }
    }
    const request = new BidiRequest(frame, redirectedFrom, param, route);
    this._requests.set(request._id, request);
    this._page._frameManager.requestStarted(request.request, route);
  }
  _onResponseStarted(params) {
    const request = this._requests.get(params.request.request);
    if (!request) return;
    const getResponseBody = async () => {
      throw new Error(`Response body is not available for requests in Bidi`);
    };
    const timings = params.request.timings;
    const startTime = timings.requestTime;
    function relativeToStart(time) {
      if (!time) return -1;
      return (time - startTime) / 1000;
    }
    const timing = {
      startTime: startTime / 1000,
      requestStart: relativeToStart(timings.requestStart),
      responseStart: relativeToStart(timings.responseStart),
      domainLookupStart: relativeToStart(timings.dnsStart),
      domainLookupEnd: relativeToStart(timings.dnsEnd),
      connectStart: relativeToStart(timings.connectStart),
      secureConnectionStart: relativeToStart(timings.tlsStart),
      connectEnd: relativeToStart(timings.connectEnd)
    };
    const response = new network.Response(request.request, params.response.status, params.response.statusText, fromBidiHeaders(params.response.headers), timing, getResponseBody, false);
    response._serverAddrFinished();
    response._securityDetailsFinished();
    // "raw" headers are the same as "provisional" headers in Bidi.
    response.setRawResponseHeaders(null);
    response.setResponseHeadersSize(params.response.headersSize);
    this._page._frameManager.requestReceivedResponse(response);
    if (params.navigation) this._onNavigationResponseStarted(params);
  }
  _onResponseCompleted(params) {
    const request = this._requests.get(params.request.request);
    if (!request) return;
    const response = request.request._existingResponse();
    // TODO: body size is the encoded size
    response.setTransferSize(params.response.bodySize);
    response.setEncodedBodySize(params.response.bodySize);

    // Keep redirected requests in the map for future reference as redirectedFrom.
    const isRedirected = response.status() >= 300 && response.status() <= 399;
    const responseEndTime = params.request.timings.responseEnd / 1000 - response.timing().startTime;
    if (isRedirected) {
      response._requestFinished(responseEndTime);
    } else {
      this._requests.delete(request._id);
      response._requestFinished(responseEndTime);
    }
    response._setHttpVersion(params.response.protocol);
    this._page._frameManager.reportRequestFinished(request.request, response);
  }
  _onFetchError(params) {
    const request = this._requests.get(params.request.request);
    if (!request) return;
    this._requests.delete(request._id);
    const response = request.request._existingResponse();
    if (response) {
      response.setTransferSize(null);
      response.setEncodedBodySize(null);
      response._requestFinished(-1);
    }
    request.request._setFailureText(params.errorText);
    // TODO: support canceled flag
    this._page._frameManager.requestFailed(request.request, params.errorText === 'NS_BINDING_ABORTED');
  }
  _onAuthRequired(params) {
    var _params$response$auth;
    const isBasic = (_params$response$auth = params.response.authChallenges) === null || _params$response$auth === void 0 ? void 0 : _params$response$auth.some(challenge => challenge.scheme.startsWith('Basic'));
    const credentials = this._page._browserContext._options.httpCredentials;
    if (isBasic && credentials) {
      this._session.sendMayFail('network.continueWithAuth', {
        request: params.request.request,
        action: 'provideCredentials',
        credentials: {
          type: 'password',
          username: credentials.username,
          password: credentials.password
        }
      });
    } else {
      this._session.sendMayFail('network.continueWithAuth', {
        request: params.request.request,
        action: 'default'
      });
    }
  }
  async setRequestInterception(value) {
    this._userRequestInterceptionEnabled = value;
    await this._updateProtocolRequestInterception();
  }
  async setCredentials(credentials) {
    this._credentials = credentials;
    await this._updateProtocolRequestInterception();
  }
  async _updateProtocolRequestInterception(initial) {
    const enabled = this._userRequestInterceptionEnabled || !!this._credentials;
    if (enabled === this._protocolRequestInterceptionEnabled) return;
    this._protocolRequestInterceptionEnabled = enabled;
    if (initial && !enabled) return;
    const cachePromise = this._session.send('network.setCacheBehavior', {
      cacheBehavior: enabled ? 'bypass' : 'default'
    });
    let interceptPromise = Promise.resolve(undefined);
    if (enabled) {
      interceptPromise = this._session.send('network.addIntercept', {
        phases: [bidi.Network.InterceptPhase.AuthRequired, bidi.Network.InterceptPhase.BeforeRequestSent],
        urlPatterns: [{
          type: 'pattern'
        }]
        // urlPatterns: [{ type: 'string', pattern: '*' }],
      }).then(r => {
        this._intercepId = r.intercept;
      });
    } else if (this._intercepId) {
      interceptPromise = this._session.send('network.removeIntercept', {
        intercept: this._intercepId
      });
      this._intercepId = undefined;
    }
    await Promise.all([cachePromise, interceptPromise]);
  }
}
exports.BidiNetworkManager = BidiNetworkManager;
class BidiRequest {
  constructor(frame, redirectedFrom, payload, route) {
    var _payload$navigation;
    this.request = void 0;
    this._id = void 0;
    this._redirectedTo = void 0;
    // Only first request in the chain can be intercepted, so this will
    // store the first and only Route in the chain (if any).
    this._originalRequestRoute = void 0;
    this._id = payload.request.request;
    if (redirectedFrom) redirectedFrom._redirectedTo = this;
    // TODO: missing in the spec?
    const postDataBuffer = null;
    this.request = new network.Request(frame._page._browserContext, frame, null, redirectedFrom ? redirectedFrom.request : null, (_payload$navigation = payload.navigation) !== null && _payload$navigation !== void 0 ? _payload$navigation : undefined, payload.request.url, 'other', payload.request.method, postDataBuffer, fromBidiHeaders(payload.request.headers));
    // "raw" headers are the same as "provisional" headers in Bidi.
    this.request.setRawRequestHeaders(null);
    this.request._setBodySize(payload.request.bodySize || 0);
    this._originalRequestRoute = route !== null && route !== void 0 ? route : redirectedFrom === null || redirectedFrom === void 0 ? void 0 : redirectedFrom._originalRequestRoute;
    route === null || route === void 0 || route._setRequest(this.request);
  }
  _finalRequest() {
    let request = this;
    while (request._redirectedTo) request = request._redirectedTo;
    return request;
  }
}
class BidiRouteImpl {
  constructor(session, requestId) {
    this._requestId = void 0;
    this._session = void 0;
    this._request = void 0;
    this._alreadyContinuedHeaders = void 0;
    this._session = session;
    this._requestId = requestId;
  }
  _setRequest(request) {
    this._request = request;
  }
  async continue(overrides) {
    // Firefox does not update content-length header.
    let headers = overrides.headers || this._request.headers();
    if (overrides.postData && headers) {
      headers = headers.map(header => {
        if (header.name.toLowerCase() === 'content-length') return {
          name: header.name,
          value: overrides.postData.byteLength.toString()
        };
        return header;
      });
    }
    this._alreadyContinuedHeaders = headers;
    await this._session.sendMayFail('network.continueRequest', {
      request: this._requestId,
      url: overrides.url,
      method: overrides.method,
      ...toBidiRequestHeaders(this._alreadyContinuedHeaders),
      body: overrides.postData ? {
        type: 'base64',
        value: Buffer.from(overrides.postData).toString('base64')
      } : undefined
    });
  }
  async fulfill(response) {
    const base64body = response.isBase64 ? response.body : Buffer.from(response.body).toString('base64');
    await this._session.sendMayFail('network.provideResponse', {
      request: this._requestId,
      statusCode: response.status,
      reasonPhrase: network.statusText(response.status),
      ...toBidiResponseHeaders(response.headers),
      body: {
        type: 'base64',
        value: base64body
      }
    });
  }
  async abort(errorCode) {
    await this._session.sendMayFail('network.failRequest', {
      request: this._requestId
    });
  }
}
function fromBidiHeaders(bidiHeaders) {
  const result = [];
  for (const {
    name,
    value
  } of bidiHeaders) result.push({
    name,
    value: bidiBytesValueToString(value)
  });
  return result;
}
function toBidiRequestHeaders(allHeaders) {
  const bidiHeaders = toBidiHeaders(allHeaders);
  const cookies = bidiHeaders.filter(h => h.name.toLowerCase() === 'cookie');
  const headers = bidiHeaders.filter(h => h.name.toLowerCase() !== 'cookie');
  return {
    cookies,
    headers
  };
}
function toBidiResponseHeaders(headers) {
  const setCookieHeaders = headers.filter(h => h.name.toLowerCase() === 'set-cookie');
  const otherHeaders = headers.filter(h => h.name.toLowerCase() !== 'set-cookie');
  const rawCookies = setCookieHeaders.map(h => (0, _cookieStore.parseRawCookie)(h.value));
  const cookies = rawCookies.filter(Boolean).map(c => {
    return {
      ...c,
      value: {
        type: 'string',
        value: c.value
      },
      sameSite: toBidiSameSite(c.sameSite)
    };
  });
  return {
    cookies,
    headers: toBidiHeaders(otherHeaders)
  };
}
function toBidiHeaders(headers) {
  return headers.map(({
    name,
    value
  }) => ({
    name,
    value: {
      type: 'string',
      value
    }
  }));
}
function bidiBytesValueToString(value) {
  if (value.type === 'string') return value.value;
  if (value.type === 'base64') return Buffer.from(value.type, 'base64').toString('binary');
  return 'unknown value type: ' + value.type;
}
function toBidiSameSite(sameSite) {
  if (!sameSite) return undefined;
  if (sameSite === 'Strict') return bidi.Network.SameSite.Strict;
  if (sameSite === 'Lax') return bidi.Network.SameSite.Lax;
  return bidi.Network.SameSite.None;
}