network.js 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", {
  3. value: true
  4. });
  5. exports.WebSocket = exports.RouteHandler = exports.Route = exports.Response = exports.Request = exports.RawHeaders = void 0;
  6. exports.validateHeaders = validateHeaders;
  7. var _url = require("url");
  8. var _channelOwner = require("./channelOwner");
  9. var _frame = require("./frame");
  10. var _worker = require("./worker");
  11. var _fs = _interopRequireDefault(require("fs"));
  12. var _utilsBundle = require("../utilsBundle");
  13. var _utils = require("../utils");
  14. var _manualPromise = require("../utils/manualPromise");
  15. var _events = require("./events");
  16. var _waiter = require("./waiter");
  17. var _network = require("../utils/network");
  18. var _multimap = require("../utils/multimap");
  19. var _fetch = require("./fetch");
  20. function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  21. /**
  22. * Copyright (c) Microsoft Corporation.
  23. *
  24. * Licensed under the Apache License, Version 2.0 (the "License");
  25. * you may not use this file except in compliance with the License.
  26. * You may obtain a copy of the License at
  27. *
  28. * http://www.apache.org/licenses/LICENSE-2.0
  29. *
  30. * Unless required by applicable law or agreed to in writing, software
  31. * distributed under the License is distributed on an "AS IS" BASIS,
  32. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  33. * See the License for the specific language governing permissions and
  34. * limitations under the License.
  35. */
  36. class Request extends _channelOwner.ChannelOwner {
  37. static from(request) {
  38. return request._object;
  39. }
  40. static fromNullable(request) {
  41. return request ? Request.from(request) : null;
  42. }
  43. constructor(parent, type, guid, initializer) {
  44. super(parent, type, guid, initializer);
  45. this._redirectedFrom = null;
  46. this._redirectedTo = null;
  47. this._failureText = null;
  48. this._provisionalHeaders = void 0;
  49. this._actualHeadersPromise = void 0;
  50. this._timing = void 0;
  51. this._fallbackOverrides = {};
  52. this._redirectedFrom = Request.fromNullable(initializer.redirectedFrom);
  53. if (this._redirectedFrom) this._redirectedFrom._redirectedTo = this;
  54. this._provisionalHeaders = new RawHeaders(initializer.headers);
  55. this._fallbackOverrides.postDataBuffer = initializer.postData;
  56. this._timing = {
  57. startTime: 0,
  58. domainLookupStart: -1,
  59. domainLookupEnd: -1,
  60. connectStart: -1,
  61. secureConnectionStart: -1,
  62. connectEnd: -1,
  63. requestStart: -1,
  64. responseStart: -1,
  65. responseEnd: -1
  66. };
  67. }
  68. url() {
  69. return this._fallbackOverrides.url || this._initializer.url;
  70. }
  71. resourceType() {
  72. return this._initializer.resourceType;
  73. }
  74. method() {
  75. return this._fallbackOverrides.method || this._initializer.method;
  76. }
  77. postData() {
  78. var _this$_fallbackOverri;
  79. return ((_this$_fallbackOverri = this._fallbackOverrides.postDataBuffer) === null || _this$_fallbackOverri === void 0 ? void 0 : _this$_fallbackOverri.toString('utf-8')) || null;
  80. }
  81. postDataBuffer() {
  82. return this._fallbackOverrides.postDataBuffer || null;
  83. }
  84. postDataJSON() {
  85. const postData = this.postData();
  86. if (!postData) return null;
  87. const contentType = this.headers()['content-type'];
  88. if (contentType === 'application/x-www-form-urlencoded') {
  89. const entries = {};
  90. const parsed = new _url.URLSearchParams(postData);
  91. for (const [k, v] of parsed.entries()) entries[k] = v;
  92. return entries;
  93. }
  94. try {
  95. return JSON.parse(postData);
  96. } catch (e) {
  97. throw new Error('POST data is not a valid JSON object: ' + postData);
  98. }
  99. }
  100. /**
  101. * @deprecated
  102. */
  103. headers() {
  104. if (this._fallbackOverrides.headers) return RawHeaders._fromHeadersObjectLossy(this._fallbackOverrides.headers).headers();
  105. return this._provisionalHeaders.headers();
  106. }
  107. async _actualHeaders() {
  108. if (this._fallbackOverrides.headers) return RawHeaders._fromHeadersObjectLossy(this._fallbackOverrides.headers);
  109. if (!this._actualHeadersPromise) {
  110. this._actualHeadersPromise = this._wrapApiCall(async () => {
  111. return new RawHeaders((await this._channel.rawRequestHeaders()).headers);
  112. });
  113. }
  114. return await this._actualHeadersPromise;
  115. }
  116. async allHeaders() {
  117. return (await this._actualHeaders()).headers();
  118. }
  119. async headersArray() {
  120. return (await this._actualHeaders()).headersArray();
  121. }
  122. async headerValue(name) {
  123. return (await this._actualHeaders()).get(name);
  124. }
  125. async response() {
  126. return Response.fromNullable((await this._channel.response()).response);
  127. }
  128. async _internalResponse() {
  129. return await this._wrapApiCall(async () => {
  130. return Response.fromNullable((await this._channel.response()).response);
  131. }, true);
  132. }
  133. frame() {
  134. if (!this._initializer.frame) {
  135. (0, _utils.assert)(this.serviceWorker());
  136. throw new Error('Service Worker requests do not have an associated frame.');
  137. }
  138. const frame = _frame.Frame.from(this._initializer.frame);
  139. if (!frame._page) {
  140. throw new Error(['Frame for this navigation request is not available, because the request', 'was issued before the frame is created. You can check whether the request', 'is a navigation request by calling isNavigationRequest() method.'].join('\n'));
  141. }
  142. return frame;
  143. }
  144. _safePage() {
  145. var _Frame$fromNullable;
  146. return ((_Frame$fromNullable = _frame.Frame.fromNullable(this._initializer.frame)) === null || _Frame$fromNullable === void 0 ? void 0 : _Frame$fromNullable._page) || null;
  147. }
  148. serviceWorker() {
  149. return this._initializer.serviceWorker ? _worker.Worker.from(this._initializer.serviceWorker) : null;
  150. }
  151. isNavigationRequest() {
  152. return this._initializer.isNavigationRequest;
  153. }
  154. redirectedFrom() {
  155. return this._redirectedFrom;
  156. }
  157. redirectedTo() {
  158. return this._redirectedTo;
  159. }
  160. failure() {
  161. if (this._failureText === null) return null;
  162. return {
  163. errorText: this._failureText
  164. };
  165. }
  166. timing() {
  167. return this._timing;
  168. }
  169. async sizes() {
  170. const response = await this.response();
  171. if (!response) throw new Error('Unable to fetch sizes for failed request');
  172. return (await response._channel.sizes()).sizes;
  173. }
  174. _setResponseEndTiming(responseEndTiming) {
  175. this._timing.responseEnd = responseEndTiming;
  176. if (this._timing.responseStart === -1) this._timing.responseStart = responseEndTiming;
  177. }
  178. _finalRequest() {
  179. return this._redirectedTo ? this._redirectedTo._finalRequest() : this;
  180. }
  181. _applyFallbackOverrides(overrides) {
  182. if (overrides.url) this._fallbackOverrides.url = overrides.url;
  183. if (overrides.method) this._fallbackOverrides.method = overrides.method;
  184. if (overrides.headers) this._fallbackOverrides.headers = overrides.headers;
  185. if ((0, _utils.isString)(overrides.postData)) this._fallbackOverrides.postDataBuffer = Buffer.from(overrides.postData, 'utf-8');else if (overrides.postData instanceof Buffer) this._fallbackOverrides.postDataBuffer = overrides.postData;else if (overrides.postData) this._fallbackOverrides.postDataBuffer = Buffer.from(JSON.stringify(overrides.postData), 'utf-8');
  186. }
  187. _fallbackOverridesForContinue() {
  188. return this._fallbackOverrides;
  189. }
  190. _targetClosedScope() {
  191. var _this$serviceWorker, _this$_safePage;
  192. return ((_this$serviceWorker = this.serviceWorker()) === null || _this$serviceWorker === void 0 ? void 0 : _this$serviceWorker._closedScope) || ((_this$_safePage = this._safePage()) === null || _this$_safePage === void 0 ? void 0 : _this$_safePage._closedOrCrashedScope) || new _manualPromise.LongStandingScope();
  193. }
  194. }
  195. exports.Request = Request;
  196. class Route extends _channelOwner.ChannelOwner {
  197. static from(route) {
  198. return route._object;
  199. }
  200. constructor(parent, type, guid, initializer) {
  201. super(parent, type, guid, initializer);
  202. this._handlingPromise = null;
  203. this._context = void 0;
  204. this._didThrow = false;
  205. }
  206. request() {
  207. return Request.from(this._initializer.request);
  208. }
  209. async _raceWithTargetClose(promise) {
  210. // When page closes or crashes, we catch any potential rejects from this Route.
  211. // Note that page could be missing when routing popup's initial request that
  212. // does not have a Page initialized just yet.
  213. return await this.request()._targetClosedScope().safeRace(promise);
  214. }
  215. async _startHandling() {
  216. this._handlingPromise = new _manualPromise.ManualPromise();
  217. return await this._handlingPromise;
  218. }
  219. async fallback(options = {}) {
  220. this._checkNotHandled();
  221. this.request()._applyFallbackOverrides(options);
  222. this._reportHandled(false);
  223. }
  224. async abort(errorCode) {
  225. await this._handleRoute(async () => {
  226. await this._raceWithTargetClose(this._channel.abort({
  227. requestUrl: this.request()._initializer.url,
  228. errorCode
  229. }));
  230. });
  231. }
  232. async _redirectNavigationRequest(url) {
  233. await this._handleRoute(async () => {
  234. await this._raceWithTargetClose(this._channel.redirectNavigationRequest({
  235. url
  236. }));
  237. });
  238. }
  239. async fetch(options = {}) {
  240. return await this._wrapApiCall(async () => {
  241. return await this._context.request._innerFetch({
  242. request: this.request(),
  243. data: options.postData,
  244. ...options
  245. });
  246. });
  247. }
  248. async fulfill(options = {}) {
  249. await this._handleRoute(async () => {
  250. await this._wrapApiCall(async () => {
  251. await this._innerFulfill(options);
  252. });
  253. });
  254. }
  255. async _handleRoute(callback) {
  256. this._checkNotHandled();
  257. try {
  258. await callback();
  259. this._reportHandled(true);
  260. } catch (e) {
  261. this._didThrow = true;
  262. throw e;
  263. }
  264. }
  265. async _innerFulfill(options = {}) {
  266. let fetchResponseUid;
  267. let {
  268. status: statusOption,
  269. headers: headersOption,
  270. body
  271. } = options;
  272. if (options.json !== undefined) {
  273. (0, _utils.assert)(options.body === undefined, 'Can specify either body or json parameters');
  274. body = JSON.stringify(options.json);
  275. }
  276. if (options.response instanceof _fetch.APIResponse) {
  277. var _statusOption, _headersOption;
  278. (_statusOption = statusOption) !== null && _statusOption !== void 0 ? _statusOption : statusOption = options.response.status();
  279. (_headersOption = headersOption) !== null && _headersOption !== void 0 ? _headersOption : headersOption = options.response.headers();
  280. if (body === undefined && options.path === undefined) {
  281. if (options.response._request._connection === this._connection) fetchResponseUid = options.response._fetchUid();else body = await options.response.body();
  282. }
  283. }
  284. let isBase64 = false;
  285. let length = 0;
  286. if (options.path) {
  287. const buffer = await _fs.default.promises.readFile(options.path);
  288. body = buffer.toString('base64');
  289. isBase64 = true;
  290. length = buffer.length;
  291. } else if ((0, _utils.isString)(body)) {
  292. isBase64 = false;
  293. length = Buffer.byteLength(body);
  294. } else if (body) {
  295. length = body.length;
  296. body = body.toString('base64');
  297. isBase64 = true;
  298. }
  299. const headers = {};
  300. for (const header of Object.keys(headersOption || {})) headers[header.toLowerCase()] = String(headersOption[header]);
  301. if (options.contentType) headers['content-type'] = String(options.contentType);else if (options.json) headers['content-type'] = 'application/json';else if (options.path) headers['content-type'] = _utilsBundle.mime.getType(options.path) || 'application/octet-stream';
  302. if (length && !('content-length' in headers)) headers['content-length'] = String(length);
  303. await this._raceWithTargetClose(this._channel.fulfill({
  304. requestUrl: this.request()._initializer.url,
  305. status: statusOption || 200,
  306. headers: (0, _utils.headersObjectToArray)(headers),
  307. body,
  308. isBase64,
  309. fetchResponseUid
  310. }));
  311. }
  312. async continue(options = {}) {
  313. await this._handleRoute(async () => {
  314. this.request()._applyFallbackOverrides(options);
  315. await this._innerContinue();
  316. });
  317. }
  318. _checkNotHandled() {
  319. if (!this._handlingPromise) throw new Error('Route is already handled!');
  320. }
  321. _reportHandled(done) {
  322. const chain = this._handlingPromise;
  323. this._handlingPromise = null;
  324. chain.resolve(done);
  325. }
  326. async _innerContinue(internal = false) {
  327. const options = this.request()._fallbackOverridesForContinue();
  328. return await this._wrapApiCall(async () => {
  329. await this._raceWithTargetClose(this._channel.continue({
  330. requestUrl: this.request()._initializer.url,
  331. url: options.url,
  332. method: options.method,
  333. headers: options.headers ? (0, _utils.headersObjectToArray)(options.headers) : undefined,
  334. postData: options.postDataBuffer,
  335. isFallback: internal
  336. }));
  337. }, !!internal);
  338. }
  339. }
  340. exports.Route = Route;
  341. class Response extends _channelOwner.ChannelOwner {
  342. static from(response) {
  343. return response._object;
  344. }
  345. static fromNullable(response) {
  346. return response ? Response.from(response) : null;
  347. }
  348. constructor(parent, type, guid, initializer) {
  349. super(parent, type, guid, initializer);
  350. this._provisionalHeaders = void 0;
  351. this._actualHeadersPromise = void 0;
  352. this._request = void 0;
  353. this._finishedPromise = new _manualPromise.ManualPromise();
  354. this._provisionalHeaders = new RawHeaders(initializer.headers);
  355. this._request = Request.from(this._initializer.request);
  356. Object.assign(this._request._timing, this._initializer.timing);
  357. }
  358. url() {
  359. return this._initializer.url;
  360. }
  361. ok() {
  362. // Status 0 is for file:// URLs
  363. return this._initializer.status === 0 || this._initializer.status >= 200 && this._initializer.status <= 299;
  364. }
  365. status() {
  366. return this._initializer.status;
  367. }
  368. statusText() {
  369. return this._initializer.statusText;
  370. }
  371. fromServiceWorker() {
  372. return this._initializer.fromServiceWorker;
  373. }
  374. /**
  375. * @deprecated
  376. */
  377. headers() {
  378. return this._provisionalHeaders.headers();
  379. }
  380. async _actualHeaders() {
  381. if (!this._actualHeadersPromise) {
  382. this._actualHeadersPromise = (async () => {
  383. return new RawHeaders((await this._channel.rawResponseHeaders()).headers);
  384. })();
  385. }
  386. return await this._actualHeadersPromise;
  387. }
  388. async allHeaders() {
  389. return (await this._actualHeaders()).headers();
  390. }
  391. async headersArray() {
  392. return (await this._actualHeaders()).headersArray().slice();
  393. }
  394. async headerValue(name) {
  395. return (await this._actualHeaders()).get(name);
  396. }
  397. async headerValues(name) {
  398. return (await this._actualHeaders()).getAll(name);
  399. }
  400. async finished() {
  401. return await this.request()._targetClosedScope().race(this._finishedPromise);
  402. }
  403. async body() {
  404. return (await this._channel.body()).binary;
  405. }
  406. async text() {
  407. const content = await this.body();
  408. return content.toString('utf8');
  409. }
  410. async json() {
  411. const content = await this.text();
  412. return JSON.parse(content);
  413. }
  414. request() {
  415. return this._request;
  416. }
  417. frame() {
  418. return this._request.frame();
  419. }
  420. async serverAddr() {
  421. return (await this._channel.serverAddr()).value || null;
  422. }
  423. async securityDetails() {
  424. return (await this._channel.securityDetails()).value || null;
  425. }
  426. }
  427. exports.Response = Response;
  428. class WebSocket extends _channelOwner.ChannelOwner {
  429. static from(webSocket) {
  430. return webSocket._object;
  431. }
  432. constructor(parent, type, guid, initializer) {
  433. super(parent, type, guid, initializer);
  434. this._page = void 0;
  435. this._isClosed = void 0;
  436. this._isClosed = false;
  437. this._page = parent;
  438. this._channel.on('frameSent', event => {
  439. if (event.opcode === 1) this.emit(_events.Events.WebSocket.FrameSent, {
  440. payload: event.data
  441. });else if (event.opcode === 2) this.emit(_events.Events.WebSocket.FrameSent, {
  442. payload: Buffer.from(event.data, 'base64')
  443. });
  444. });
  445. this._channel.on('frameReceived', event => {
  446. if (event.opcode === 1) this.emit(_events.Events.WebSocket.FrameReceived, {
  447. payload: event.data
  448. });else if (event.opcode === 2) this.emit(_events.Events.WebSocket.FrameReceived, {
  449. payload: Buffer.from(event.data, 'base64')
  450. });
  451. });
  452. this._channel.on('socketError', ({
  453. error
  454. }) => this.emit(_events.Events.WebSocket.Error, error));
  455. this._channel.on('close', () => {
  456. this._isClosed = true;
  457. this.emit(_events.Events.WebSocket.Close, this);
  458. });
  459. }
  460. url() {
  461. return this._initializer.url;
  462. }
  463. isClosed() {
  464. return this._isClosed;
  465. }
  466. async waitForEvent(event, optionsOrPredicate = {}) {
  467. return await this._wrapApiCall(async () => {
  468. const timeout = this._page._timeoutSettings.timeout(typeof optionsOrPredicate === 'function' ? {} : optionsOrPredicate);
  469. const predicate = typeof optionsOrPredicate === 'function' ? optionsOrPredicate : optionsOrPredicate.predicate;
  470. const waiter = _waiter.Waiter.createForEvent(this, event);
  471. waiter.rejectOnTimeout(timeout, `Timeout ${timeout}ms exceeded while waiting for event "${event}"`);
  472. if (event !== _events.Events.WebSocket.Error) waiter.rejectOnEvent(this, _events.Events.WebSocket.Error, new Error('Socket error'));
  473. if (event !== _events.Events.WebSocket.Close) waiter.rejectOnEvent(this, _events.Events.WebSocket.Close, new Error('Socket closed'));
  474. waiter.rejectOnEvent(this._page, _events.Events.Page.Close, () => this._page._closeErrorWithReason());
  475. const result = await waiter.waitForEvent(this, event, predicate);
  476. waiter.dispose();
  477. return result;
  478. });
  479. }
  480. }
  481. exports.WebSocket = WebSocket;
  482. function validateHeaders(headers) {
  483. for (const key of Object.keys(headers)) {
  484. const value = headers[key];
  485. if (!Object.is(value, undefined) && !(0, _utils.isString)(value)) throw new Error(`Expected value of header "${key}" to be String, but "${typeof value}" is found.`);
  486. }
  487. }
  488. class RouteHandler {
  489. constructor(baseURL, url, handler, times = Number.MAX_SAFE_INTEGER) {
  490. this.handledCount = 0;
  491. this._baseURL = void 0;
  492. this._times = void 0;
  493. this.url = void 0;
  494. this.handler = void 0;
  495. this._ignoreException = false;
  496. this._activeInvocations = new Set();
  497. this._baseURL = baseURL;
  498. this._times = times;
  499. this.url = url;
  500. this.handler = handler;
  501. }
  502. static prepareInterceptionPatterns(handlers) {
  503. const patterns = [];
  504. let all = false;
  505. for (const handler of handlers) {
  506. if ((0, _utils.isString)(handler.url)) patterns.push({
  507. glob: handler.url
  508. });else if ((0, _utils.isRegExp)(handler.url)) patterns.push({
  509. regexSource: handler.url.source,
  510. regexFlags: handler.url.flags
  511. });else all = true;
  512. }
  513. if (all) return [{
  514. glob: '**/*'
  515. }];
  516. return patterns;
  517. }
  518. matches(requestURL) {
  519. return (0, _network.urlMatches)(this._baseURL, requestURL, this.url);
  520. }
  521. async handle(route) {
  522. const handlerInvocation = {
  523. complete: new _manualPromise.ManualPromise(),
  524. route
  525. };
  526. this._activeInvocations.add(handlerInvocation);
  527. try {
  528. return await this._handleInternal(route);
  529. } catch (e) {
  530. // If the handler was stopped (without waiting for completion), we ignore all exceptions.
  531. if (this._ignoreException) return false;
  532. throw e;
  533. } finally {
  534. handlerInvocation.complete.resolve();
  535. this._activeInvocations.delete(handlerInvocation);
  536. }
  537. }
  538. async stop(behavior) {
  539. // When a handler is manually unrouted or its page/context is closed we either
  540. // - wait for the current handler invocations to finish
  541. // - or do not wait, if the user opted out of it, but swallow all exceptions
  542. // that happen after the unroute/close.
  543. if (behavior === 'ignoreErrors') {
  544. this._ignoreException = true;
  545. } else {
  546. const promises = [];
  547. for (const activation of this._activeInvocations) {
  548. if (!activation.route._didThrow) promises.push(activation.complete);
  549. }
  550. await Promise.all(promises);
  551. }
  552. }
  553. async _handleInternal(route) {
  554. ++this.handledCount;
  555. const handledPromise = route._startHandling();
  556. // Extract handler into a variable to avoid [RouteHandler.handler] in the stack.
  557. const handler = this.handler;
  558. const [handled] = await Promise.all([handledPromise, handler(route, route.request())]);
  559. return handled;
  560. }
  561. willExpire() {
  562. return this.handledCount + 1 >= this._times;
  563. }
  564. }
  565. exports.RouteHandler = RouteHandler;
  566. class RawHeaders {
  567. static _fromHeadersObjectLossy(headers) {
  568. const headersArray = Object.entries(headers).map(([name, value]) => ({
  569. name,
  570. value
  571. })).filter(header => header.value !== undefined);
  572. return new RawHeaders(headersArray);
  573. }
  574. constructor(headers) {
  575. this._headersArray = void 0;
  576. this._headersMap = new _multimap.MultiMap();
  577. this._headersArray = headers;
  578. for (const header of headers) this._headersMap.set(header.name.toLowerCase(), header.value);
  579. }
  580. get(name) {
  581. const values = this.getAll(name);
  582. if (!values || !values.length) return null;
  583. return values.join(name.toLowerCase() === 'set-cookie' ? '\n' : ', ');
  584. }
  585. getAll(name) {
  586. return [...this._headersMap.get(name.toLowerCase())];
  587. }
  588. headers() {
  589. const result = {};
  590. for (const name of this._headersMap.keys()) result[name] = this.get(name);
  591. return result;
  592. }
  593. headersArray() {
  594. return this._headersArray;
  595. }
  596. }
  597. exports.RawHeaders = RawHeaders;