feat: implement net.fetch (#36733)
This commit is contained in:
		
					parent
					
						
							
								63f94f2359
							
						
					
				
			
			
				commit
				
					
						872d1fe05a
					
				
			
		
					 13 changed files with 969 additions and 539 deletions
				
			
		| 
						 | 
				
			
			@ -23,12 +23,14 @@ following properties:
 | 
			
		|||
    with which the request is associated. Defaults to the empty string. The
 | 
			
		||||
    `session` option supersedes `partition`. Thus if a `session` is explicitly
 | 
			
		||||
    specified, `partition` is ignored.
 | 
			
		||||
  * `credentials` string (optional) - Can be `include` or `omit`. Whether to
 | 
			
		||||
    send [credentials](https://fetch.spec.whatwg.org/#credentials) with this
 | 
			
		||||
  * `credentials` string (optional) - Can be `include`, `omit` or
 | 
			
		||||
    `same-origin`. Whether to send
 | 
			
		||||
    [credentials](https://fetch.spec.whatwg.org/#credentials) with this
 | 
			
		||||
    request. If set to `include`, credentials from the session associated with
 | 
			
		||||
    the request will be used. If set to `omit`, credentials will not be sent
 | 
			
		||||
    with the request (and the `'login'` event will not be triggered in the
 | 
			
		||||
    event of a 401). This matches the behavior of the
 | 
			
		||||
    event of a 401). If set to `same-origin`, `origin` must also be specified.
 | 
			
		||||
    This matches the behavior of the
 | 
			
		||||
    [fetch](https://fetch.spec.whatwg.org/#concept-request-credentials-mode)
 | 
			
		||||
    option of the same name. If this option is not specified, authentication
 | 
			
		||||
    data from the session will be sent, and cookies will not be sent (unless
 | 
			
		||||
| 
						 | 
				
			
			@ -49,6 +51,13 @@ following properties:
 | 
			
		|||
    [`request.followRedirect`](#requestfollowredirect) is invoked synchronously
 | 
			
		||||
    during the [`redirect`](#event-redirect) event.  Defaults to `follow`.
 | 
			
		||||
  * `origin` string (optional) - The origin URL of the request.
 | 
			
		||||
  * `referrerPolicy` string (optional) - can be `""`, `no-referrer`,
 | 
			
		||||
    `no-referrer-when-downgrade`, `origin`, `origin-when-cross-origin`,
 | 
			
		||||
    `unsafe-url`, `same-origin`, `strict-origin`, or
 | 
			
		||||
    `strict-origin-when-cross-origin`. Defaults to
 | 
			
		||||
    `strict-origin-when-cross-origin`.
 | 
			
		||||
  * `cache` string (optional) - can be `default`, `no-store`, `reload`,
 | 
			
		||||
    `no-cache`, `force-cache` or `only-if-cached`.
 | 
			
		||||
 | 
			
		||||
`options` properties such as `protocol`, `host`, `hostname`, `port` and `path`
 | 
			
		||||
strictly follow the Node.js model as described in the
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -63,6 +63,44 @@ Creates a [`ClientRequest`](./client-request.md) instance using the provided
 | 
			
		|||
The `net.request` method would be used to issue both secure and insecure HTTP
 | 
			
		||||
requests according to the specified protocol scheme in the `options` object.
 | 
			
		||||
 | 
			
		||||
### `net.fetch(input[, init])`
 | 
			
		||||
 | 
			
		||||
* `input` string | [Request](https://nodejs.org/api/globals.html#request)
 | 
			
		||||
* `init` [RequestInit](https://developer.mozilla.org/en-US/docs/Web/API/fetch#options) (optional)
 | 
			
		||||
 | 
			
		||||
Returns `Promise<GlobalResponse>` - see [Response](https://developer.mozilla.org/en-US/docs/Web/API/Response).
 | 
			
		||||
 | 
			
		||||
Sends a request, similarly to how `fetch()` works in the renderer, using
 | 
			
		||||
Chrome's network stack. This differs from Node's `fetch()`, which uses
 | 
			
		||||
Node.js's HTTP stack.
 | 
			
		||||
 | 
			
		||||
Example:
 | 
			
		||||
 | 
			
		||||
```js
 | 
			
		||||
async function example () {
 | 
			
		||||
  const response = await net.fetch('https://my.app')
 | 
			
		||||
  if (response.ok) {
 | 
			
		||||
    const body = await response.json()
 | 
			
		||||
    // ... use the result.
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
This method will issue requests from the [default
 | 
			
		||||
session](session.md#sessiondefaultsession). To send a `fetch` request from
 | 
			
		||||
another session, use [ses.fetch()](session.md#sesfetchinput-init).
 | 
			
		||||
 | 
			
		||||
See the MDN documentation for
 | 
			
		||||
[`fetch()`](https://developer.mozilla.org/en-US/docs/Web/API/fetch) for more
 | 
			
		||||
details.
 | 
			
		||||
 | 
			
		||||
Limitations:
 | 
			
		||||
 | 
			
		||||
* `net.fetch()` does not support the `data:` or `blob:` schemes.
 | 
			
		||||
* The value of the `integrity` option is ignored.
 | 
			
		||||
* The `.type` and `.url` values of the returned `Response` object are
 | 
			
		||||
  incorrect.
 | 
			
		||||
 | 
			
		||||
### `net.isOnline()`
 | 
			
		||||
 | 
			
		||||
Returns `boolean` - Whether there is currently internet connection.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -731,6 +731,43 @@ Returns `Promise<void>` - Resolves when all connections are closed.
 | 
			
		|||
 | 
			
		||||
**Note:** It will terminate / fail all requests currently in flight.
 | 
			
		||||
 | 
			
		||||
#### `ses.fetch(input[, init])`
 | 
			
		||||
 | 
			
		||||
* `input` string | [GlobalRequest](https://nodejs.org/api/globals.html#request)
 | 
			
		||||
* `init` [RequestInit](https://developer.mozilla.org/en-US/docs/Web/API/fetch#options) (optional)
 | 
			
		||||
 | 
			
		||||
Returns `Promise<GlobalResponse>` - see [Response](https://developer.mozilla.org/en-US/docs/Web/API/Response).
 | 
			
		||||
 | 
			
		||||
Sends a request, similarly to how `fetch()` works in the renderer, using
 | 
			
		||||
Chrome's network stack. This differs from Node's `fetch()`, which uses
 | 
			
		||||
Node.js's HTTP stack.
 | 
			
		||||
 | 
			
		||||
Example:
 | 
			
		||||
 | 
			
		||||
```js
 | 
			
		||||
async function example () {
 | 
			
		||||
  const response = await net.fetch('https://my.app')
 | 
			
		||||
  if (response.ok) {
 | 
			
		||||
    const body = await response.json()
 | 
			
		||||
    // ... use the result.
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
See also [`net.fetch()`](net.md#netfetchinput-init), a convenience method which
 | 
			
		||||
issues requests from the [default session](#sessiondefaultsession).
 | 
			
		||||
 | 
			
		||||
See the MDN documentation for
 | 
			
		||||
[`fetch()`](https://developer.mozilla.org/en-US/docs/Web/API/fetch) for more
 | 
			
		||||
details.
 | 
			
		||||
 | 
			
		||||
Limitations:
 | 
			
		||||
 | 
			
		||||
* `net.fetch()` does not support the `data:` or `blob:` schemes.
 | 
			
		||||
* The value of the `integrity` option is ignored.
 | 
			
		||||
* The `.type` and `.url` values of the returned `Response` object are
 | 
			
		||||
  incorrect.
 | 
			
		||||
 | 
			
		||||
#### `ses.disableNetworkEmulation()`
 | 
			
		||||
 | 
			
		||||
Disables any network emulation already active for the `session`. Resets to
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -208,6 +208,8 @@ auto_filenames = {
 | 
			
		|||
    "lib/browser/api/message-channel.ts",
 | 
			
		||||
    "lib/browser/api/module-list.ts",
 | 
			
		||||
    "lib/browser/api/native-theme.ts",
 | 
			
		||||
    "lib/browser/api/net-client-request.ts",
 | 
			
		||||
    "lib/browser/api/net-fetch.ts",
 | 
			
		||||
    "lib/browser/api/net-log.ts",
 | 
			
		||||
    "lib/browser/api/net.ts",
 | 
			
		||||
    "lib/browser/api/notification.ts",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										522
									
								
								lib/browser/api/net-client-request.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										522
									
								
								lib/browser/api/net-client-request.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,522 @@
 | 
			
		|||
import * as url from 'url';
 | 
			
		||||
import { Readable, Writable } from 'stream';
 | 
			
		||||
import { app } from 'electron/main';
 | 
			
		||||
import type { ClientRequestConstructorOptions, UploadProgress } from 'electron/main';
 | 
			
		||||
 | 
			
		||||
const {
 | 
			
		||||
  isValidHeaderName,
 | 
			
		||||
  isValidHeaderValue,
 | 
			
		||||
  createURLLoader
 | 
			
		||||
} = process._linkedBinding('electron_browser_net');
 | 
			
		||||
const { Session } = process._linkedBinding('electron_browser_session');
 | 
			
		||||
 | 
			
		||||
const kSupportedProtocols = new Set(['http:', 'https:']);
 | 
			
		||||
 | 
			
		||||
// set of headers that Node.js discards duplicates for
 | 
			
		||||
// see https://nodejs.org/api/http.html#http_message_headers
 | 
			
		||||
const discardableDuplicateHeaders = new Set([
 | 
			
		||||
  'content-type',
 | 
			
		||||
  'content-length',
 | 
			
		||||
  'user-agent',
 | 
			
		||||
  'referer',
 | 
			
		||||
  'host',
 | 
			
		||||
  'authorization',
 | 
			
		||||
  'proxy-authorization',
 | 
			
		||||
  'if-modified-since',
 | 
			
		||||
  'if-unmodified-since',
 | 
			
		||||
  'from',
 | 
			
		||||
  'location',
 | 
			
		||||
  'max-forwards',
 | 
			
		||||
  'retry-after',
 | 
			
		||||
  'etag',
 | 
			
		||||
  'last-modified',
 | 
			
		||||
  'server',
 | 
			
		||||
  'age',
 | 
			
		||||
  'expires'
 | 
			
		||||
]);
 | 
			
		||||
 | 
			
		||||
class IncomingMessage extends Readable {
 | 
			
		||||
  _shouldPush: boolean = false;
 | 
			
		||||
  _data: (Buffer | null)[] = [];
 | 
			
		||||
  _responseHead: NodeJS.ResponseHead;
 | 
			
		||||
  _resume: (() => void) | null = null;
 | 
			
		||||
 | 
			
		||||
  constructor (responseHead: NodeJS.ResponseHead) {
 | 
			
		||||
    super();
 | 
			
		||||
    this._responseHead = responseHead;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  get statusCode () {
 | 
			
		||||
    return this._responseHead.statusCode;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  get statusMessage () {
 | 
			
		||||
    return this._responseHead.statusMessage;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  get headers () {
 | 
			
		||||
    const filteredHeaders: Record<string, string | string[]> = {};
 | 
			
		||||
    const { headers, rawHeaders } = this._responseHead;
 | 
			
		||||
    for (const [name, values] of Object.entries(headers)) {
 | 
			
		||||
      filteredHeaders[name] = discardableDuplicateHeaders.has(name) ? values[0] : values.join(', ');
 | 
			
		||||
    }
 | 
			
		||||
    const cookies = rawHeaders.filter(({ key }) => key.toLowerCase() === 'set-cookie').map(({ value }) => value);
 | 
			
		||||
    // keep set-cookie as an array per Node.js rules
 | 
			
		||||
    // see https://nodejs.org/api/http.html#http_message_headers
 | 
			
		||||
    if (cookies.length) { filteredHeaders['set-cookie'] = cookies; }
 | 
			
		||||
    return filteredHeaders;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  get rawHeaders () {
 | 
			
		||||
    const rawHeadersArr: string[] = [];
 | 
			
		||||
    const { rawHeaders } = this._responseHead;
 | 
			
		||||
    rawHeaders.forEach(header => {
 | 
			
		||||
      rawHeadersArr.push(header.key, header.value);
 | 
			
		||||
    });
 | 
			
		||||
    return rawHeadersArr;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  get httpVersion () {
 | 
			
		||||
    return `${this.httpVersionMajor}.${this.httpVersionMinor}`;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  get httpVersionMajor () {
 | 
			
		||||
    return this._responseHead.httpVersion.major;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  get httpVersionMinor () {
 | 
			
		||||
    return this._responseHead.httpVersion.minor;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  get rawTrailers () {
 | 
			
		||||
    throw new Error('HTTP trailers are not supported');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  get trailers () {
 | 
			
		||||
    throw new Error('HTTP trailers are not supported');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  _storeInternalData (chunk: Buffer | null, resume: (() => void) | null) {
 | 
			
		||||
    // save the network callback for use in _pushInternalData
 | 
			
		||||
    this._resume = resume;
 | 
			
		||||
    this._data.push(chunk);
 | 
			
		||||
    this._pushInternalData();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  _pushInternalData () {
 | 
			
		||||
    while (this._shouldPush && this._data.length > 0) {
 | 
			
		||||
      const chunk = this._data.shift();
 | 
			
		||||
      this._shouldPush = this.push(chunk);
 | 
			
		||||
    }
 | 
			
		||||
    if (this._shouldPush && this._resume) {
 | 
			
		||||
      // Reset the callback, so that a new one is used for each
 | 
			
		||||
      // batch of throttled data. Do this before calling resume to avoid a
 | 
			
		||||
      // potential race-condition
 | 
			
		||||
      const resume = this._resume;
 | 
			
		||||
      this._resume = null;
 | 
			
		||||
 | 
			
		||||
      resume();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  _read () {
 | 
			
		||||
    this._shouldPush = true;
 | 
			
		||||
    this._pushInternalData();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** Writable stream that buffers up everything written to it. */
 | 
			
		||||
class SlurpStream extends Writable {
 | 
			
		||||
  _data: Buffer;
 | 
			
		||||
  constructor () {
 | 
			
		||||
    super();
 | 
			
		||||
    this._data = Buffer.alloc(0);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  _write (chunk: Buffer, encoding: string, callback: () => void) {
 | 
			
		||||
    this._data = Buffer.concat([this._data, chunk]);
 | 
			
		||||
    callback();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  data () { return this._data; }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class ChunkedBodyStream extends Writable {
 | 
			
		||||
  _pendingChunk: Buffer | undefined;
 | 
			
		||||
  _downstream?: NodeJS.DataPipe;
 | 
			
		||||
  _pendingCallback?: (error?: Error) => void;
 | 
			
		||||
  _clientRequest: ClientRequest;
 | 
			
		||||
 | 
			
		||||
  constructor (clientRequest: ClientRequest) {
 | 
			
		||||
    super();
 | 
			
		||||
    this._clientRequest = clientRequest;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  _write (chunk: Buffer, encoding: string, callback: () => void) {
 | 
			
		||||
    if (this._downstream) {
 | 
			
		||||
      this._downstream.write(chunk).then(callback, callback);
 | 
			
		||||
    } else {
 | 
			
		||||
      // the contract of _write is that we won't be called again until we call
 | 
			
		||||
      // the callback, so we're good to just save a single chunk.
 | 
			
		||||
      this._pendingChunk = chunk;
 | 
			
		||||
      this._pendingCallback = callback;
 | 
			
		||||
 | 
			
		||||
      // The first write to a chunked body stream begins the request.
 | 
			
		||||
      this._clientRequest._startRequest();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  _final (callback: () => void) {
 | 
			
		||||
    this._downstream!.done();
 | 
			
		||||
    callback();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  startReading (pipe: NodeJS.DataPipe) {
 | 
			
		||||
    if (this._downstream) {
 | 
			
		||||
      throw new Error('two startReading calls???');
 | 
			
		||||
    }
 | 
			
		||||
    this._downstream = pipe;
 | 
			
		||||
    if (this._pendingChunk) {
 | 
			
		||||
      const doneWriting = (maybeError: Error | void) => {
 | 
			
		||||
        // If the underlying request has been aborted, we honestly don't care about the error
 | 
			
		||||
        // all work should cease as soon as we abort anyway, this error is probably a
 | 
			
		||||
        // "mojo pipe disconnected" error (code=9)
 | 
			
		||||
        if (this._clientRequest._aborted) return;
 | 
			
		||||
 | 
			
		||||
        const cb = this._pendingCallback!;
 | 
			
		||||
        delete this._pendingCallback;
 | 
			
		||||
        delete this._pendingChunk;
 | 
			
		||||
        cb(maybeError || undefined);
 | 
			
		||||
      };
 | 
			
		||||
      this._downstream.write(this._pendingChunk).then(doneWriting, doneWriting);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type RedirectPolicy = 'manual' | 'follow' | 'error';
 | 
			
		||||
 | 
			
		||||
function parseOptions (optionsIn: ClientRequestConstructorOptions | string): NodeJS.CreateURLLoaderOptions & { redirectPolicy: RedirectPolicy, headers: Record<string, { name: string, value: string | string[] }> } {
 | 
			
		||||
  const options: any = typeof optionsIn === 'string' ? url.parse(optionsIn) : { ...optionsIn };
 | 
			
		||||
 | 
			
		||||
  let urlStr: string = options.url;
 | 
			
		||||
 | 
			
		||||
  if (!urlStr) {
 | 
			
		||||
    const urlObj: url.UrlObject = {};
 | 
			
		||||
    const protocol = options.protocol || 'http:';
 | 
			
		||||
    if (!kSupportedProtocols.has(protocol)) {
 | 
			
		||||
      throw new Error('Protocol "' + protocol + '" not supported');
 | 
			
		||||
    }
 | 
			
		||||
    urlObj.protocol = protocol;
 | 
			
		||||
 | 
			
		||||
    if (options.host) {
 | 
			
		||||
      urlObj.host = options.host;
 | 
			
		||||
    } else {
 | 
			
		||||
      if (options.hostname) {
 | 
			
		||||
        urlObj.hostname = options.hostname;
 | 
			
		||||
      } else {
 | 
			
		||||
        urlObj.hostname = 'localhost';
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (options.port) {
 | 
			
		||||
        urlObj.port = options.port;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (options.path && / /.test(options.path)) {
 | 
			
		||||
      // The actual regex is more like /[^A-Za-z0-9\-._~!$&'()*+,;=/:@]/
 | 
			
		||||
      // with an additional rule for ignoring percentage-escaped characters
 | 
			
		||||
      // but that's a) hard to capture in a regular expression that performs
 | 
			
		||||
      // well, and b) possibly too restrictive for real-world usage. That's
 | 
			
		||||
      // why it only scans for spaces because those are guaranteed to create
 | 
			
		||||
      // an invalid request.
 | 
			
		||||
      throw new TypeError('Request path contains unescaped characters');
 | 
			
		||||
    }
 | 
			
		||||
    const pathObj = url.parse(options.path || '/');
 | 
			
		||||
    urlObj.pathname = pathObj.pathname;
 | 
			
		||||
    urlObj.search = pathObj.search;
 | 
			
		||||
    urlObj.hash = pathObj.hash;
 | 
			
		||||
    urlStr = url.format(urlObj);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const redirectPolicy = options.redirect || 'follow';
 | 
			
		||||
  if (!['follow', 'error', 'manual'].includes(redirectPolicy)) {
 | 
			
		||||
    throw new Error('redirect mode should be one of follow, error or manual');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (options.headers != null && typeof options.headers !== 'object') {
 | 
			
		||||
    throw new TypeError('headers must be an object');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const urlLoaderOptions: NodeJS.CreateURLLoaderOptions & { redirectPolicy: RedirectPolicy, headers: Record<string, { name: string, value: string | string[] }> } = {
 | 
			
		||||
    method: (options.method || 'GET').toUpperCase(),
 | 
			
		||||
    url: urlStr,
 | 
			
		||||
    redirectPolicy,
 | 
			
		||||
    headers: {},
 | 
			
		||||
    body: null as any,
 | 
			
		||||
    useSessionCookies: options.useSessionCookies,
 | 
			
		||||
    credentials: options.credentials,
 | 
			
		||||
    origin: options.origin,
 | 
			
		||||
    referrerPolicy: options.referrerPolicy,
 | 
			
		||||
    cache: options.cache
 | 
			
		||||
  };
 | 
			
		||||
  const headers: Record<string, string | string[]> = options.headers || {};
 | 
			
		||||
  for (const [name, value] of Object.entries(headers)) {
 | 
			
		||||
    if (!isValidHeaderName(name)) {
 | 
			
		||||
      throw new Error(`Invalid header name: '${name}'`);
 | 
			
		||||
    }
 | 
			
		||||
    if (!isValidHeaderValue(value.toString())) {
 | 
			
		||||
      throw new Error(`Invalid value for header '${name}': '${value}'`);
 | 
			
		||||
    }
 | 
			
		||||
    const key = name.toLowerCase();
 | 
			
		||||
    urlLoaderOptions.headers[key] = { name, value };
 | 
			
		||||
  }
 | 
			
		||||
  if (options.session) {
 | 
			
		||||
    if (!(options.session instanceof Session)) { throw new TypeError('`session` should be an instance of the Session class'); }
 | 
			
		||||
    urlLoaderOptions.session = options.session;
 | 
			
		||||
  } else if (options.partition) {
 | 
			
		||||
    if (typeof options.partition === 'string') {
 | 
			
		||||
      urlLoaderOptions.partition = options.partition;
 | 
			
		||||
    } else {
 | 
			
		||||
      throw new TypeError('`partition` should be a string');
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  return urlLoaderOptions;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class ClientRequest extends Writable implements Electron.ClientRequest {
 | 
			
		||||
  _started: boolean = false;
 | 
			
		||||
  _firstWrite: boolean = false;
 | 
			
		||||
  _aborted: boolean = false;
 | 
			
		||||
  _chunkedEncoding: boolean | undefined;
 | 
			
		||||
  _body: Writable | undefined;
 | 
			
		||||
  _urlLoaderOptions: NodeJS.CreateURLLoaderOptions & { headers: Record<string, { name: string, value: string | string[] }> };
 | 
			
		||||
  _redirectPolicy: RedirectPolicy;
 | 
			
		||||
  _followRedirectCb?: () => void;
 | 
			
		||||
  _uploadProgress?: { active: boolean, started: boolean, current: number, total: number };
 | 
			
		||||
  _urlLoader?: NodeJS.URLLoader;
 | 
			
		||||
  _response?: IncomingMessage;
 | 
			
		||||
 | 
			
		||||
  constructor (options: ClientRequestConstructorOptions | string, callback?: (message: IncomingMessage) => void) {
 | 
			
		||||
    super({ autoDestroy: true });
 | 
			
		||||
 | 
			
		||||
    if (!app.isReady()) {
 | 
			
		||||
      throw new Error('net module can only be used after app is ready');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (callback) {
 | 
			
		||||
      this.once('response', callback);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const { redirectPolicy, ...urlLoaderOptions } = parseOptions(options);
 | 
			
		||||
    if (urlLoaderOptions.credentials === 'same-origin' && !urlLoaderOptions.origin) { throw new Error('credentials: same-origin requires origin to be set'); }
 | 
			
		||||
    this._urlLoaderOptions = urlLoaderOptions;
 | 
			
		||||
    this._redirectPolicy = redirectPolicy;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  get chunkedEncoding () {
 | 
			
		||||
    return this._chunkedEncoding || false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  set chunkedEncoding (value: boolean) {
 | 
			
		||||
    if (this._started) {
 | 
			
		||||
      throw new Error('chunkedEncoding can only be set before the request is started');
 | 
			
		||||
    }
 | 
			
		||||
    if (typeof this._chunkedEncoding !== 'undefined') {
 | 
			
		||||
      throw new Error('chunkedEncoding can only be set once');
 | 
			
		||||
    }
 | 
			
		||||
    this._chunkedEncoding = !!value;
 | 
			
		||||
    if (this._chunkedEncoding) {
 | 
			
		||||
      this._body = new ChunkedBodyStream(this);
 | 
			
		||||
      this._urlLoaderOptions.body = (pipe: NodeJS.DataPipe) => {
 | 
			
		||||
        (this._body! as ChunkedBodyStream).startReading(pipe);
 | 
			
		||||
      };
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  setHeader (name: string, value: string) {
 | 
			
		||||
    if (typeof name !== 'string') {
 | 
			
		||||
      throw new TypeError('`name` should be a string in setHeader(name, value)');
 | 
			
		||||
    }
 | 
			
		||||
    if (value == null) {
 | 
			
		||||
      throw new Error('`value` required in setHeader("' + name + '", value)');
 | 
			
		||||
    }
 | 
			
		||||
    if (this._started || this._firstWrite) {
 | 
			
		||||
      throw new Error('Can\'t set headers after they are sent');
 | 
			
		||||
    }
 | 
			
		||||
    if (!isValidHeaderName(name)) {
 | 
			
		||||
      throw new Error(`Invalid header name: '${name}'`);
 | 
			
		||||
    }
 | 
			
		||||
    if (!isValidHeaderValue(value.toString())) {
 | 
			
		||||
      throw new Error(`Invalid value for header '${name}': '${value}'`);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const key = name.toLowerCase();
 | 
			
		||||
    this._urlLoaderOptions.headers[key] = { name, value };
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  getHeader (name: string) {
 | 
			
		||||
    if (name == null) {
 | 
			
		||||
      throw new Error('`name` is required for getHeader(name)');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const key = name.toLowerCase();
 | 
			
		||||
    const header = this._urlLoaderOptions.headers[key];
 | 
			
		||||
    return header && header.value as any;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  removeHeader (name: string) {
 | 
			
		||||
    if (name == null) {
 | 
			
		||||
      throw new Error('`name` is required for removeHeader(name)');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (this._started || this._firstWrite) {
 | 
			
		||||
      throw new Error('Can\'t remove headers after they are sent');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const key = name.toLowerCase();
 | 
			
		||||
    delete this._urlLoaderOptions.headers[key];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  _write (chunk: Buffer, encoding: BufferEncoding, callback: () => void) {
 | 
			
		||||
    this._firstWrite = true;
 | 
			
		||||
    if (!this._body) {
 | 
			
		||||
      this._body = new SlurpStream();
 | 
			
		||||
      this._body.on('finish', () => {
 | 
			
		||||
        this._urlLoaderOptions.body = (this._body as SlurpStream).data();
 | 
			
		||||
        this._startRequest();
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
    // TODO: is this the right way to forward to another stream?
 | 
			
		||||
    this._body.write(chunk, encoding, callback);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  _final (callback: () => void) {
 | 
			
		||||
    if (this._body) {
 | 
			
		||||
      // TODO: is this the right way to forward to another stream?
 | 
			
		||||
      this._body.end(callback);
 | 
			
		||||
    } else {
 | 
			
		||||
      // end() called without a body, go ahead and start the request
 | 
			
		||||
      this._startRequest();
 | 
			
		||||
      callback();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  _startRequest () {
 | 
			
		||||
    this._started = true;
 | 
			
		||||
    const stringifyValues = (obj: Record<string, { name: string, value: string | string[] }>) => {
 | 
			
		||||
      const ret: Record<string, string> = {};
 | 
			
		||||
      for (const k of Object.keys(obj)) {
 | 
			
		||||
        const kv = obj[k];
 | 
			
		||||
        ret[kv.name] = kv.value.toString();
 | 
			
		||||
      }
 | 
			
		||||
      return ret;
 | 
			
		||||
    };
 | 
			
		||||
    this._urlLoaderOptions.referrer = this.getHeader('referer') || '';
 | 
			
		||||
    this._urlLoaderOptions.origin = this._urlLoaderOptions.origin || this.getHeader('origin') || '';
 | 
			
		||||
    this._urlLoaderOptions.hasUserActivation = this.getHeader('sec-fetch-user') === '?1';
 | 
			
		||||
    this._urlLoaderOptions.mode = this.getHeader('sec-fetch-mode') || '';
 | 
			
		||||
    this._urlLoaderOptions.destination = this.getHeader('sec-fetch-dest') || '';
 | 
			
		||||
    const opts = { ...this._urlLoaderOptions, extraHeaders: stringifyValues(this._urlLoaderOptions.headers) };
 | 
			
		||||
    this._urlLoader = createURLLoader(opts);
 | 
			
		||||
    this._urlLoader.on('response-started', (event, finalUrl, responseHead) => {
 | 
			
		||||
      const response = this._response = new IncomingMessage(responseHead);
 | 
			
		||||
      this.emit('response', response);
 | 
			
		||||
    });
 | 
			
		||||
    this._urlLoader.on('data', (event, data, resume) => {
 | 
			
		||||
      this._response!._storeInternalData(Buffer.from(data), resume);
 | 
			
		||||
    });
 | 
			
		||||
    this._urlLoader.on('complete', () => {
 | 
			
		||||
      if (this._response) { this._response._storeInternalData(null, null); }
 | 
			
		||||
    });
 | 
			
		||||
    this._urlLoader.on('error', (event, netErrorString) => {
 | 
			
		||||
      const error = new Error(netErrorString);
 | 
			
		||||
      if (this._response) this._response.destroy(error);
 | 
			
		||||
      this._die(error);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    this._urlLoader.on('login', (event, authInfo, callback) => {
 | 
			
		||||
      const handled = this.emit('login', authInfo, callback);
 | 
			
		||||
      if (!handled) {
 | 
			
		||||
        // If there were no listeners, cancel the authentication request.
 | 
			
		||||
        callback();
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    this._urlLoader.on('redirect', (event, redirectInfo, headers) => {
 | 
			
		||||
      const { statusCode, newMethod, newUrl } = redirectInfo;
 | 
			
		||||
      if (this._redirectPolicy === 'error') {
 | 
			
		||||
        this._die(new Error('Attempted to redirect, but redirect policy was \'error\''));
 | 
			
		||||
      } else if (this._redirectPolicy === 'manual') {
 | 
			
		||||
        let _followRedirect = false;
 | 
			
		||||
        this._followRedirectCb = () => { _followRedirect = true; };
 | 
			
		||||
        try {
 | 
			
		||||
          this.emit('redirect', statusCode, newMethod, newUrl, headers);
 | 
			
		||||
        } finally {
 | 
			
		||||
          this._followRedirectCb = undefined;
 | 
			
		||||
          if (!_followRedirect && !this._aborted) {
 | 
			
		||||
            this._die(new Error('Redirect was cancelled'));
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      } else if (this._redirectPolicy === 'follow') {
 | 
			
		||||
        // Calling followRedirect() when the redirect policy is 'follow' is
 | 
			
		||||
        // allowed but does nothing. (Perhaps it should throw an error
 | 
			
		||||
        // though...? Since the redirect will happen regardless.)
 | 
			
		||||
        try {
 | 
			
		||||
          this._followRedirectCb = () => {};
 | 
			
		||||
          this.emit('redirect', statusCode, newMethod, newUrl, headers);
 | 
			
		||||
        } finally {
 | 
			
		||||
          this._followRedirectCb = undefined;
 | 
			
		||||
        }
 | 
			
		||||
      } else {
 | 
			
		||||
        this._die(new Error(`Unexpected redirect policy '${this._redirectPolicy}'`));
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    this._urlLoader.on('upload-progress', (event, position, total) => {
 | 
			
		||||
      this._uploadProgress = { active: true, started: true, current: position, total };
 | 
			
		||||
      this.emit('upload-progress', position, total); // Undocumented, for now
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    this._urlLoader.on('download-progress', (event, current) => {
 | 
			
		||||
      if (this._response) {
 | 
			
		||||
        this._response.emit('download-progress', current); // Undocumented, for now
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  followRedirect () {
 | 
			
		||||
    if (this._followRedirectCb) {
 | 
			
		||||
      this._followRedirectCb();
 | 
			
		||||
    } else {
 | 
			
		||||
      throw new Error('followRedirect() called, but was not waiting for a redirect');
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  abort () {
 | 
			
		||||
    if (!this._aborted) {
 | 
			
		||||
      process.nextTick(() => { this.emit('abort'); });
 | 
			
		||||
    }
 | 
			
		||||
    this._aborted = true;
 | 
			
		||||
    this._die();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  _die (err?: Error) {
 | 
			
		||||
    // Node.js assumes that any stream which is ended is no longer capable of emitted events
 | 
			
		||||
    // which is a faulty assumption for the case of an object that is acting like a stream
 | 
			
		||||
    // (our urlRequest). If we don't emit here, this causes errors since we *do* expect
 | 
			
		||||
    // that error events can be emitted after urlRequest.end().
 | 
			
		||||
    if ((this as any)._writableState.destroyed && err) {
 | 
			
		||||
      this.emit('error', err);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    this.destroy(err);
 | 
			
		||||
    if (this._urlLoader) {
 | 
			
		||||
      this._urlLoader.cancel();
 | 
			
		||||
      if (this._response) this._response.destroy(err);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  getUploadProgress (): UploadProgress {
 | 
			
		||||
    return this._uploadProgress ? { ...this._uploadProgress } : { active: false, started: false, current: 0, total: 0 };
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										116
									
								
								lib/browser/api/net-fetch.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										116
									
								
								lib/browser/api/net-fetch.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,116 @@
 | 
			
		|||
import { net, IncomingMessage, Session as SessionT } from 'electron/main';
 | 
			
		||||
import { Readable, Writable, isReadable } from 'stream';
 | 
			
		||||
 | 
			
		||||
function createDeferredPromise<T, E extends Error = Error> (): { promise: Promise<T>; resolve: (x: T) => void; reject: (e: E) => void; } {
 | 
			
		||||
  let res: (x: T) => void;
 | 
			
		||||
  let rej: (e: E) => void;
 | 
			
		||||
  const promise = new Promise<T>((resolve, reject) => {
 | 
			
		||||
    res = resolve;
 | 
			
		||||
    rej = reject;
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  return { promise, resolve: res!, reject: rej! };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function fetchWithSession (input: RequestInfo, init: RequestInit | undefined, session: SessionT): Promise<Response> {
 | 
			
		||||
  const p = createDeferredPromise<Response>();
 | 
			
		||||
  let req: Request;
 | 
			
		||||
  try {
 | 
			
		||||
    req = new Request(input, init);
 | 
			
		||||
  } catch (e: any) {
 | 
			
		||||
    p.reject(e);
 | 
			
		||||
    return p.promise;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (req.signal.aborted) {
 | 
			
		||||
    // 1. Abort the fetch() call with p, request, null, and
 | 
			
		||||
    //    requestObject’s signal’s abort reason.
 | 
			
		||||
    const error = (req.signal as any).reason ?? new DOMException('The operation was aborted.', 'AbortError');
 | 
			
		||||
    p.reject(error);
 | 
			
		||||
 | 
			
		||||
    if (req.body != null && isReadable(req.body as unknown as NodeJS.ReadableStream)) {
 | 
			
		||||
      req.body.cancel(error).catch((err) => {
 | 
			
		||||
        if (err.code === 'ERR_INVALID_STATE') {
 | 
			
		||||
          // Node bug?
 | 
			
		||||
          return;
 | 
			
		||||
        }
 | 
			
		||||
        throw err;
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 2. Return p.
 | 
			
		||||
    return p.promise;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  let locallyAborted = false;
 | 
			
		||||
  req.signal.addEventListener(
 | 
			
		||||
    'abort',
 | 
			
		||||
    () => {
 | 
			
		||||
      // 1. Set locallyAborted to true.
 | 
			
		||||
      locallyAborted = true;
 | 
			
		||||
 | 
			
		||||
      // 2. Abort the fetch() call with p, request, responseObject,
 | 
			
		||||
      //    and requestObject’s signal’s abort reason.
 | 
			
		||||
      const error = (req.signal as any).reason ?? new DOMException('The operation was aborted.', 'AbortError');
 | 
			
		||||
      p.reject(error);
 | 
			
		||||
      if (req.body != null && isReadable(req.body as unknown as NodeJS.ReadableStream)) {
 | 
			
		||||
        req.body.cancel(error).catch((err) => {
 | 
			
		||||
          if (err.code === 'ERR_INVALID_STATE') {
 | 
			
		||||
            // Node bug?
 | 
			
		||||
            return;
 | 
			
		||||
          }
 | 
			
		||||
          throw err;
 | 
			
		||||
        });
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      r.abort();
 | 
			
		||||
    },
 | 
			
		||||
    { once: true }
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  const origin = req.headers.get('origin') ?? undefined;
 | 
			
		||||
  // We can't set credentials to same-origin unless there's an origin set.
 | 
			
		||||
  const credentials = req.credentials === 'same-origin' && !origin ? 'include' : req.credentials;
 | 
			
		||||
 | 
			
		||||
  const r = net.request({
 | 
			
		||||
    session,
 | 
			
		||||
    method: req.method,
 | 
			
		||||
    url: req.url,
 | 
			
		||||
    origin,
 | 
			
		||||
    credentials,
 | 
			
		||||
    cache: req.cache,
 | 
			
		||||
    referrerPolicy: req.referrerPolicy,
 | 
			
		||||
    redirect: req.redirect
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  // cors is the default mode, but we can't set mode=cors without an origin.
 | 
			
		||||
  if (req.mode && (req.mode !== 'cors' || origin)) {
 | 
			
		||||
    r.setHeader('Sec-Fetch-Mode', req.mode);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  for (const [k, v] of req.headers) {
 | 
			
		||||
    r.setHeader(k, v);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  r.on('response', (resp: IncomingMessage) => {
 | 
			
		||||
    if (locallyAborted) return;
 | 
			
		||||
    const headers = new Headers();
 | 
			
		||||
    for (const [k, v] of Object.entries(resp.headers)) { headers.set(k, Array.isArray(v) ? v.join(', ') : v); }
 | 
			
		||||
    const nullBodyStatus = [101, 204, 205, 304];
 | 
			
		||||
    const body = nullBodyStatus.includes(resp.statusCode) || req.method === 'HEAD' ? null : Readable.toWeb(resp as unknown as Readable) as ReadableStream;
 | 
			
		||||
    const rResp = new Response(body, {
 | 
			
		||||
      headers,
 | 
			
		||||
      status: resp.statusCode,
 | 
			
		||||
      statusText: resp.statusMessage
 | 
			
		||||
    });
 | 
			
		||||
    p.resolve(rResp);
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  r.on('error', (err) => {
 | 
			
		||||
    p.reject(err);
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  if (!req.body?.pipeTo(Writable.toWeb(r as unknown as Writable)).then(() => r.end())) { r.end(); }
 | 
			
		||||
 | 
			
		||||
  return p.promise;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,531 +1,17 @@
 | 
			
		|||
import * as url from 'url';
 | 
			
		||||
import { Readable, Writable } from 'stream';
 | 
			
		||||
import { app } from 'electron/main';
 | 
			
		||||
import type { ClientRequestConstructorOptions, UploadProgress } from 'electron/main';
 | 
			
		||||
import { IncomingMessage, session } from 'electron/main';
 | 
			
		||||
import type { ClientRequestConstructorOptions } from 'electron/main';
 | 
			
		||||
import { ClientRequest } from '@electron/internal/browser/api/net-client-request';
 | 
			
		||||
 | 
			
		||||
const {
 | 
			
		||||
  isOnline,
 | 
			
		||||
  isValidHeaderName,
 | 
			
		||||
  isValidHeaderValue,
 | 
			
		||||
  createURLLoader
 | 
			
		||||
} = process._linkedBinding('electron_browser_net');
 | 
			
		||||
 | 
			
		||||
const kSupportedProtocols = new Set(['http:', 'https:']);
 | 
			
		||||
 | 
			
		||||
// set of headers that Node.js discards duplicates for
 | 
			
		||||
// see https://nodejs.org/api/http.html#http_message_headers
 | 
			
		||||
const discardableDuplicateHeaders = new Set([
 | 
			
		||||
  'content-type',
 | 
			
		||||
  'content-length',
 | 
			
		||||
  'user-agent',
 | 
			
		||||
  'referer',
 | 
			
		||||
  'host',
 | 
			
		||||
  'authorization',
 | 
			
		||||
  'proxy-authorization',
 | 
			
		||||
  'if-modified-since',
 | 
			
		||||
  'if-unmodified-since',
 | 
			
		||||
  'from',
 | 
			
		||||
  'location',
 | 
			
		||||
  'max-forwards',
 | 
			
		||||
  'retry-after',
 | 
			
		||||
  'etag',
 | 
			
		||||
  'last-modified',
 | 
			
		||||
  'server',
 | 
			
		||||
  'age',
 | 
			
		||||
  'expires'
 | 
			
		||||
]);
 | 
			
		||||
 | 
			
		||||
class IncomingMessage extends Readable {
 | 
			
		||||
  _shouldPush: boolean = false;
 | 
			
		||||
  _data: (Buffer | null)[] = [];
 | 
			
		||||
  _responseHead: NodeJS.ResponseHead;
 | 
			
		||||
  _resume: (() => void) | null = null;
 | 
			
		||||
 | 
			
		||||
  constructor (responseHead: NodeJS.ResponseHead) {
 | 
			
		||||
    super();
 | 
			
		||||
    this._responseHead = responseHead;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  get statusCode () {
 | 
			
		||||
    return this._responseHead.statusCode;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  get statusMessage () {
 | 
			
		||||
    return this._responseHead.statusMessage;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  get headers () {
 | 
			
		||||
    const filteredHeaders: Record<string, string | string[]> = {};
 | 
			
		||||
    const { headers, rawHeaders } = this._responseHead;
 | 
			
		||||
    for (const [name, values] of Object.entries(headers)) {
 | 
			
		||||
      filteredHeaders[name] = discardableDuplicateHeaders.has(name) ? values[0] : values.join(', ');
 | 
			
		||||
    }
 | 
			
		||||
    const cookies = rawHeaders.filter(({ key }) => key.toLowerCase() === 'set-cookie').map(({ value }) => value);
 | 
			
		||||
    // keep set-cookie as an array per Node.js rules
 | 
			
		||||
    // see https://nodejs.org/api/http.html#http_message_headers
 | 
			
		||||
    if (cookies.length) { filteredHeaders['set-cookie'] = cookies; }
 | 
			
		||||
    return filteredHeaders;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  get rawHeaders () {
 | 
			
		||||
    const rawHeadersArr: string[] = [];
 | 
			
		||||
    const { rawHeaders } = this._responseHead;
 | 
			
		||||
    rawHeaders.forEach(header => {
 | 
			
		||||
      rawHeadersArr.push(header.key, header.value);
 | 
			
		||||
    });
 | 
			
		||||
    return rawHeadersArr;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  get httpVersion () {
 | 
			
		||||
    return `${this.httpVersionMajor}.${this.httpVersionMinor}`;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  get httpVersionMajor () {
 | 
			
		||||
    return this._responseHead.httpVersion.major;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  get httpVersionMinor () {
 | 
			
		||||
    return this._responseHead.httpVersion.minor;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  get rawTrailers () {
 | 
			
		||||
    throw new Error('HTTP trailers are not supported');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  get trailers () {
 | 
			
		||||
    throw new Error('HTTP trailers are not supported');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  _storeInternalData (chunk: Buffer | null, resume: (() => void) | null) {
 | 
			
		||||
    // save the network callback for use in _pushInternalData
 | 
			
		||||
    this._resume = resume;
 | 
			
		||||
    this._data.push(chunk);
 | 
			
		||||
    this._pushInternalData();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  _pushInternalData () {
 | 
			
		||||
    while (this._shouldPush && this._data.length > 0) {
 | 
			
		||||
      const chunk = this._data.shift();
 | 
			
		||||
      this._shouldPush = this.push(chunk);
 | 
			
		||||
    }
 | 
			
		||||
    if (this._shouldPush && this._resume) {
 | 
			
		||||
      // Reset the callback, so that a new one is used for each
 | 
			
		||||
      // batch of throttled data. Do this before calling resume to avoid a
 | 
			
		||||
      // potential race-condition
 | 
			
		||||
      const resume = this._resume;
 | 
			
		||||
      this._resume = null;
 | 
			
		||||
 | 
			
		||||
      resume();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  _read () {
 | 
			
		||||
    this._shouldPush = true;
 | 
			
		||||
    this._pushInternalData();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** Writable stream that buffers up everything written to it. */
 | 
			
		||||
class SlurpStream extends Writable {
 | 
			
		||||
  _data: Buffer;
 | 
			
		||||
  constructor () {
 | 
			
		||||
    super();
 | 
			
		||||
    this._data = Buffer.alloc(0);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  _write (chunk: Buffer, encoding: string, callback: () => void) {
 | 
			
		||||
    this._data = Buffer.concat([this._data, chunk]);
 | 
			
		||||
    callback();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  data () { return this._data; }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class ChunkedBodyStream extends Writable {
 | 
			
		||||
  _pendingChunk: Buffer | undefined;
 | 
			
		||||
  _downstream?: NodeJS.DataPipe;
 | 
			
		||||
  _pendingCallback?: (error?: Error) => void;
 | 
			
		||||
  _clientRequest: ClientRequest;
 | 
			
		||||
 | 
			
		||||
  constructor (clientRequest: ClientRequest) {
 | 
			
		||||
    super();
 | 
			
		||||
    this._clientRequest = clientRequest;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  _write (chunk: Buffer, encoding: string, callback: () => void) {
 | 
			
		||||
    if (this._downstream) {
 | 
			
		||||
      this._downstream.write(chunk).then(callback, callback);
 | 
			
		||||
    } else {
 | 
			
		||||
      // the contract of _write is that we won't be called again until we call
 | 
			
		||||
      // the callback, so we're good to just save a single chunk.
 | 
			
		||||
      this._pendingChunk = chunk;
 | 
			
		||||
      this._pendingCallback = callback;
 | 
			
		||||
 | 
			
		||||
      // The first write to a chunked body stream begins the request.
 | 
			
		||||
      this._clientRequest._startRequest();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  _final (callback: () => void) {
 | 
			
		||||
    this._downstream!.done();
 | 
			
		||||
    callback();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  startReading (pipe: NodeJS.DataPipe) {
 | 
			
		||||
    if (this._downstream) {
 | 
			
		||||
      throw new Error('two startReading calls???');
 | 
			
		||||
    }
 | 
			
		||||
    this._downstream = pipe;
 | 
			
		||||
    if (this._pendingChunk) {
 | 
			
		||||
      const doneWriting = (maybeError: Error | void) => {
 | 
			
		||||
        // If the underlying request has been aborted, we honestly don't care about the error
 | 
			
		||||
        // all work should cease as soon as we abort anyway, this error is probably a
 | 
			
		||||
        // "mojo pipe disconnected" error (code=9)
 | 
			
		||||
        if (this._clientRequest._aborted) return;
 | 
			
		||||
 | 
			
		||||
        const cb = this._pendingCallback!;
 | 
			
		||||
        delete this._pendingCallback;
 | 
			
		||||
        delete this._pendingChunk;
 | 
			
		||||
        cb(maybeError || undefined);
 | 
			
		||||
      };
 | 
			
		||||
      this._downstream.write(this._pendingChunk).then(doneWriting, doneWriting);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type RedirectPolicy = 'manual' | 'follow' | 'error';
 | 
			
		||||
 | 
			
		||||
function parseOptions (optionsIn: ClientRequestConstructorOptions | string): NodeJS.CreateURLLoaderOptions & { redirectPolicy: RedirectPolicy, headers: Record<string, { name: string, value: string | string[] }> } {
 | 
			
		||||
  const options: any = typeof optionsIn === 'string' ? url.parse(optionsIn) : { ...optionsIn };
 | 
			
		||||
 | 
			
		||||
  let urlStr: string = options.url;
 | 
			
		||||
 | 
			
		||||
  if (!urlStr) {
 | 
			
		||||
    const urlObj: url.UrlObject = {};
 | 
			
		||||
    const protocol = options.protocol || 'http:';
 | 
			
		||||
    if (!kSupportedProtocols.has(protocol)) {
 | 
			
		||||
      throw new Error('Protocol "' + protocol + '" not supported');
 | 
			
		||||
    }
 | 
			
		||||
    urlObj.protocol = protocol;
 | 
			
		||||
 | 
			
		||||
    if (options.host) {
 | 
			
		||||
      urlObj.host = options.host;
 | 
			
		||||
    } else {
 | 
			
		||||
      if (options.hostname) {
 | 
			
		||||
        urlObj.hostname = options.hostname;
 | 
			
		||||
      } else {
 | 
			
		||||
        urlObj.hostname = 'localhost';
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (options.port) {
 | 
			
		||||
        urlObj.port = options.port;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (options.path && / /.test(options.path)) {
 | 
			
		||||
      // The actual regex is more like /[^A-Za-z0-9\-._~!$&'()*+,;=/:@]/
 | 
			
		||||
      // with an additional rule for ignoring percentage-escaped characters
 | 
			
		||||
      // but that's a) hard to capture in a regular expression that performs
 | 
			
		||||
      // well, and b) possibly too restrictive for real-world usage. That's
 | 
			
		||||
      // why it only scans for spaces because those are guaranteed to create
 | 
			
		||||
      // an invalid request.
 | 
			
		||||
      throw new TypeError('Request path contains unescaped characters');
 | 
			
		||||
    }
 | 
			
		||||
    const pathObj = url.parse(options.path || '/');
 | 
			
		||||
    urlObj.pathname = pathObj.pathname;
 | 
			
		||||
    urlObj.search = pathObj.search;
 | 
			
		||||
    urlObj.hash = pathObj.hash;
 | 
			
		||||
    urlStr = url.format(urlObj);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const redirectPolicy = options.redirect || 'follow';
 | 
			
		||||
  if (!['follow', 'error', 'manual'].includes(redirectPolicy)) {
 | 
			
		||||
    throw new Error('redirect mode should be one of follow, error or manual');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (options.headers != null && typeof options.headers !== 'object') {
 | 
			
		||||
    throw new TypeError('headers must be an object');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const urlLoaderOptions: NodeJS.CreateURLLoaderOptions & { redirectPolicy: RedirectPolicy, headers: Record<string, { name: string, value: string | string[] }> } = {
 | 
			
		||||
    method: (options.method || 'GET').toUpperCase(),
 | 
			
		||||
    url: urlStr,
 | 
			
		||||
    redirectPolicy,
 | 
			
		||||
    headers: {},
 | 
			
		||||
    body: null as any,
 | 
			
		||||
    useSessionCookies: options.useSessionCookies,
 | 
			
		||||
    credentials: options.credentials,
 | 
			
		||||
    origin: options.origin
 | 
			
		||||
  };
 | 
			
		||||
  const headers: Record<string, string | string[]> = options.headers || {};
 | 
			
		||||
  for (const [name, value] of Object.entries(headers)) {
 | 
			
		||||
    if (!isValidHeaderName(name)) {
 | 
			
		||||
      throw new Error(`Invalid header name: '${name}'`);
 | 
			
		||||
    }
 | 
			
		||||
    if (!isValidHeaderValue(value.toString())) {
 | 
			
		||||
      throw new Error(`Invalid value for header '${name}': '${value}'`);
 | 
			
		||||
    }
 | 
			
		||||
    const key = name.toLowerCase();
 | 
			
		||||
    urlLoaderOptions.headers[key] = { name, value };
 | 
			
		||||
  }
 | 
			
		||||
  if (options.session) {
 | 
			
		||||
    // Weak check, but it should be enough to catch 99% of accidental misuses.
 | 
			
		||||
    if (options.session.constructor && options.session.constructor.name === 'Session') {
 | 
			
		||||
      urlLoaderOptions.session = options.session;
 | 
			
		||||
    } else {
 | 
			
		||||
      throw new TypeError('`session` should be an instance of the Session class');
 | 
			
		||||
    }
 | 
			
		||||
  } else if (options.partition) {
 | 
			
		||||
    if (typeof options.partition === 'string') {
 | 
			
		||||
      urlLoaderOptions.partition = options.partition;
 | 
			
		||||
    } else {
 | 
			
		||||
      throw new TypeError('`partition` should be a string');
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  return urlLoaderOptions;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class ClientRequest extends Writable implements Electron.ClientRequest {
 | 
			
		||||
  _started: boolean = false;
 | 
			
		||||
  _firstWrite: boolean = false;
 | 
			
		||||
  _aborted: boolean = false;
 | 
			
		||||
  _chunkedEncoding: boolean | undefined;
 | 
			
		||||
  _body: Writable | undefined;
 | 
			
		||||
  _urlLoaderOptions: NodeJS.CreateURLLoaderOptions & { headers: Record<string, { name: string, value: string | string[] }> };
 | 
			
		||||
  _redirectPolicy: RedirectPolicy;
 | 
			
		||||
  _followRedirectCb?: () => void;
 | 
			
		||||
  _uploadProgress?: { active: boolean, started: boolean, current: number, total: number };
 | 
			
		||||
  _urlLoader?: NodeJS.URLLoader;
 | 
			
		||||
  _response?: IncomingMessage;
 | 
			
		||||
 | 
			
		||||
  constructor (options: ClientRequestConstructorOptions | string, callback?: (message: IncomingMessage) => void) {
 | 
			
		||||
    super({ autoDestroy: true });
 | 
			
		||||
 | 
			
		||||
    if (!app.isReady()) {
 | 
			
		||||
      throw new Error('net module can only be used after app is ready');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (callback) {
 | 
			
		||||
      this.once('response', callback);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const { redirectPolicy, ...urlLoaderOptions } = parseOptions(options);
 | 
			
		||||
    this._urlLoaderOptions = urlLoaderOptions;
 | 
			
		||||
    this._redirectPolicy = redirectPolicy;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  get chunkedEncoding () {
 | 
			
		||||
    return this._chunkedEncoding || false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  set chunkedEncoding (value: boolean) {
 | 
			
		||||
    if (this._started) {
 | 
			
		||||
      throw new Error('chunkedEncoding can only be set before the request is started');
 | 
			
		||||
    }
 | 
			
		||||
    if (typeof this._chunkedEncoding !== 'undefined') {
 | 
			
		||||
      throw new Error('chunkedEncoding can only be set once');
 | 
			
		||||
    }
 | 
			
		||||
    this._chunkedEncoding = !!value;
 | 
			
		||||
    if (this._chunkedEncoding) {
 | 
			
		||||
      this._body = new ChunkedBodyStream(this);
 | 
			
		||||
      this._urlLoaderOptions.body = (pipe: NodeJS.DataPipe) => {
 | 
			
		||||
        (this._body! as ChunkedBodyStream).startReading(pipe);
 | 
			
		||||
      };
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  setHeader (name: string, value: string) {
 | 
			
		||||
    if (typeof name !== 'string') {
 | 
			
		||||
      throw new TypeError('`name` should be a string in setHeader(name, value)');
 | 
			
		||||
    }
 | 
			
		||||
    if (value == null) {
 | 
			
		||||
      throw new Error('`value` required in setHeader("' + name + '", value)');
 | 
			
		||||
    }
 | 
			
		||||
    if (this._started || this._firstWrite) {
 | 
			
		||||
      throw new Error('Can\'t set headers after they are sent');
 | 
			
		||||
    }
 | 
			
		||||
    if (!isValidHeaderName(name)) {
 | 
			
		||||
      throw new Error(`Invalid header name: '${name}'`);
 | 
			
		||||
    }
 | 
			
		||||
    if (!isValidHeaderValue(value.toString())) {
 | 
			
		||||
      throw new Error(`Invalid value for header '${name}': '${value}'`);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const key = name.toLowerCase();
 | 
			
		||||
    this._urlLoaderOptions.headers[key] = { name, value };
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  getHeader (name: string) {
 | 
			
		||||
    if (name == null) {
 | 
			
		||||
      throw new Error('`name` is required for getHeader(name)');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const key = name.toLowerCase();
 | 
			
		||||
    const header = this._urlLoaderOptions.headers[key];
 | 
			
		||||
    return header && header.value as any;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  removeHeader (name: string) {
 | 
			
		||||
    if (name == null) {
 | 
			
		||||
      throw new Error('`name` is required for removeHeader(name)');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (this._started || this._firstWrite) {
 | 
			
		||||
      throw new Error('Can\'t remove headers after they are sent');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const key = name.toLowerCase();
 | 
			
		||||
    delete this._urlLoaderOptions.headers[key];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  _write (chunk: Buffer, encoding: BufferEncoding, callback: () => void) {
 | 
			
		||||
    this._firstWrite = true;
 | 
			
		||||
    if (!this._body) {
 | 
			
		||||
      this._body = new SlurpStream();
 | 
			
		||||
      this._body.on('finish', () => {
 | 
			
		||||
        this._urlLoaderOptions.body = (this._body as SlurpStream).data();
 | 
			
		||||
        this._startRequest();
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
    // TODO: is this the right way to forward to another stream?
 | 
			
		||||
    this._body.write(chunk, encoding, callback);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  _final (callback: () => void) {
 | 
			
		||||
    if (this._body) {
 | 
			
		||||
      // TODO: is this the right way to forward to another stream?
 | 
			
		||||
      this._body.end(callback);
 | 
			
		||||
    } else {
 | 
			
		||||
      // end() called without a body, go ahead and start the request
 | 
			
		||||
      this._startRequest();
 | 
			
		||||
      callback();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  _startRequest () {
 | 
			
		||||
    this._started = true;
 | 
			
		||||
    const stringifyValues = (obj: Record<string, { name: string, value: string | string[] }>) => {
 | 
			
		||||
      const ret: Record<string, string> = {};
 | 
			
		||||
      for (const k of Object.keys(obj)) {
 | 
			
		||||
        const kv = obj[k];
 | 
			
		||||
        ret[kv.name] = kv.value.toString();
 | 
			
		||||
      }
 | 
			
		||||
      return ret;
 | 
			
		||||
    };
 | 
			
		||||
    this._urlLoaderOptions.referrer = this.getHeader('referer') || '';
 | 
			
		||||
    this._urlLoaderOptions.origin = this._urlLoaderOptions.origin || this.getHeader('origin') || '';
 | 
			
		||||
    this._urlLoaderOptions.hasUserActivation = this.getHeader('sec-fetch-user') === '?1';
 | 
			
		||||
    this._urlLoaderOptions.mode = this.getHeader('sec-fetch-mode') || '';
 | 
			
		||||
    this._urlLoaderOptions.destination = this.getHeader('sec-fetch-dest') || '';
 | 
			
		||||
    const opts = { ...this._urlLoaderOptions, extraHeaders: stringifyValues(this._urlLoaderOptions.headers) };
 | 
			
		||||
    this._urlLoader = createURLLoader(opts);
 | 
			
		||||
    this._urlLoader.on('response-started', (event, finalUrl, responseHead) => {
 | 
			
		||||
      const response = this._response = new IncomingMessage(responseHead);
 | 
			
		||||
      this.emit('response', response);
 | 
			
		||||
    });
 | 
			
		||||
    this._urlLoader.on('data', (event, data, resume) => {
 | 
			
		||||
      this._response!._storeInternalData(Buffer.from(data), resume);
 | 
			
		||||
    });
 | 
			
		||||
    this._urlLoader.on('complete', () => {
 | 
			
		||||
      if (this._response) { this._response._storeInternalData(null, null); }
 | 
			
		||||
    });
 | 
			
		||||
    this._urlLoader.on('error', (event, netErrorString) => {
 | 
			
		||||
      const error = new Error(netErrorString);
 | 
			
		||||
      if (this._response) this._response.destroy(error);
 | 
			
		||||
      this._die(error);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    this._urlLoader.on('login', (event, authInfo, callback) => {
 | 
			
		||||
      const handled = this.emit('login', authInfo, callback);
 | 
			
		||||
      if (!handled) {
 | 
			
		||||
        // If there were no listeners, cancel the authentication request.
 | 
			
		||||
        callback();
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    this._urlLoader.on('redirect', (event, redirectInfo, headers) => {
 | 
			
		||||
      const { statusCode, newMethod, newUrl } = redirectInfo;
 | 
			
		||||
      if (this._redirectPolicy === 'error') {
 | 
			
		||||
        this._die(new Error('Attempted to redirect, but redirect policy was \'error\''));
 | 
			
		||||
      } else if (this._redirectPolicy === 'manual') {
 | 
			
		||||
        let _followRedirect = false;
 | 
			
		||||
        this._followRedirectCb = () => { _followRedirect = true; };
 | 
			
		||||
        try {
 | 
			
		||||
          this.emit('redirect', statusCode, newMethod, newUrl, headers);
 | 
			
		||||
        } finally {
 | 
			
		||||
          this._followRedirectCb = undefined;
 | 
			
		||||
          if (!_followRedirect && !this._aborted) {
 | 
			
		||||
            this._die(new Error('Redirect was cancelled'));
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      } else if (this._redirectPolicy === 'follow') {
 | 
			
		||||
        // Calling followRedirect() when the redirect policy is 'follow' is
 | 
			
		||||
        // allowed but does nothing. (Perhaps it should throw an error
 | 
			
		||||
        // though...? Since the redirect will happen regardless.)
 | 
			
		||||
        try {
 | 
			
		||||
          this._followRedirectCb = () => {};
 | 
			
		||||
          this.emit('redirect', statusCode, newMethod, newUrl, headers);
 | 
			
		||||
        } finally {
 | 
			
		||||
          this._followRedirectCb = undefined;
 | 
			
		||||
        }
 | 
			
		||||
      } else {
 | 
			
		||||
        this._die(new Error(`Unexpected redirect policy '${this._redirectPolicy}'`));
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    this._urlLoader.on('upload-progress', (event, position, total) => {
 | 
			
		||||
      this._uploadProgress = { active: true, started: true, current: position, total };
 | 
			
		||||
      this.emit('upload-progress', position, total); // Undocumented, for now
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    this._urlLoader.on('download-progress', (event, current) => {
 | 
			
		||||
      if (this._response) {
 | 
			
		||||
        this._response.emit('download-progress', current); // Undocumented, for now
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  followRedirect () {
 | 
			
		||||
    if (this._followRedirectCb) {
 | 
			
		||||
      this._followRedirectCb();
 | 
			
		||||
    } else {
 | 
			
		||||
      throw new Error('followRedirect() called, but was not waiting for a redirect');
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  abort () {
 | 
			
		||||
    if (!this._aborted) {
 | 
			
		||||
      process.nextTick(() => { this.emit('abort'); });
 | 
			
		||||
    }
 | 
			
		||||
    this._aborted = true;
 | 
			
		||||
    this._die();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  _die (err?: Error) {
 | 
			
		||||
    // Node.js assumes that any stream which is ended is no longer capable of emitted events
 | 
			
		||||
    // which is a faulty assumption for the case of an object that is acting like a stream
 | 
			
		||||
    // (our urlRequest). If we don't emit here, this causes errors since we *do* expect
 | 
			
		||||
    // that error events can be emitted after urlRequest.end().
 | 
			
		||||
    if ((this as any)._writableState.destroyed && err) {
 | 
			
		||||
      this.emit('error', err);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    this.destroy(err);
 | 
			
		||||
    if (this._urlLoader) {
 | 
			
		||||
      this._urlLoader.cancel();
 | 
			
		||||
      if (this._response) this._response.destroy(err);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  getUploadProgress (): UploadProgress {
 | 
			
		||||
    return this._uploadProgress ? { ...this._uploadProgress } : { active: false, started: false, current: 0, total: 0 };
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
const { isOnline } = process._linkedBinding('electron_browser_net');
 | 
			
		||||
 | 
			
		||||
export function request (options: ClientRequestConstructorOptions | string, callback?: (message: IncomingMessage) => void) {
 | 
			
		||||
  return new ClientRequest(options, callback);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function fetch (input: RequestInfo, init?: RequestInit): Promise<Response> {
 | 
			
		||||
  return session.defaultSession.fetch(input, init);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
exports.isOnline = isOnline;
 | 
			
		||||
 | 
			
		||||
Object.defineProperty(exports, 'online', {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,4 +1,9 @@
 | 
			
		|||
const { fromPartition } = process._linkedBinding('electron_browser_session');
 | 
			
		||||
import { fetchWithSession } from '@electron/internal/browser/api/net-fetch';
 | 
			
		||||
const { fromPartition, Session } = process._linkedBinding('electron_browser_session');
 | 
			
		||||
 | 
			
		||||
Session.prototype.fetch = function (input: RequestInfo, init?: RequestInit) {
 | 
			
		||||
  return fetchWithSession(input, init, this);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
  fromPartition,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1203,10 +1203,16 @@ gin::Handle<Session> Session::FromPartition(v8::Isolate* isolate,
 | 
			
		|||
  return CreateFrom(isolate, browser_context);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
gin::ObjectTemplateBuilder Session::GetObjectTemplateBuilder(
 | 
			
		||||
    v8::Isolate* isolate) {
 | 
			
		||||
  return gin_helper::EventEmitterMixin<Session>::GetObjectTemplateBuilder(
 | 
			
		||||
             isolate)
 | 
			
		||||
// static
 | 
			
		||||
gin::Handle<Session> Session::New() {
 | 
			
		||||
  gin_helper::ErrorThrower(JavascriptEnvironment::GetIsolate())
 | 
			
		||||
      .ThrowError("Session objects cannot be created with 'new'");
 | 
			
		||||
  return gin::Handle<Session>();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Session::FillObjectTemplate(v8::Isolate* isolate,
 | 
			
		||||
                                 v8::Local<v8::ObjectTemplate> templ) {
 | 
			
		||||
  gin::ObjectTemplateBuilder(isolate, "Session", templ)
 | 
			
		||||
      .SetMethod("resolveProxy", &Session::ResolveProxy)
 | 
			
		||||
      .SetMethod("getCacheSize", &Session::GetCacheSize)
 | 
			
		||||
      .SetMethod("clearCache", &Session::ClearCache)
 | 
			
		||||
| 
						 | 
				
			
			@ -1276,7 +1282,8 @@ gin::ObjectTemplateBuilder Session::GetObjectTemplateBuilder(
 | 
			
		|||
      .SetProperty("protocol", &Session::Protocol)
 | 
			
		||||
      .SetProperty("serviceWorkers", &Session::ServiceWorkerContext)
 | 
			
		||||
      .SetProperty("webRequest", &Session::WebRequest)
 | 
			
		||||
      .SetProperty("storagePath", &Session::GetPath);
 | 
			
		||||
      .SetProperty("storagePath", &Session::GetPath)
 | 
			
		||||
      .Build();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const char* Session::GetTypeName() {
 | 
			
		||||
| 
						 | 
				
			
			@ -1307,6 +1314,7 @@ void Initialize(v8::Local<v8::Object> exports,
 | 
			
		|||
                void* priv) {
 | 
			
		||||
  v8::Isolate* isolate = context->GetIsolate();
 | 
			
		||||
  gin_helper::Dictionary dict(isolate, exports);
 | 
			
		||||
  dict.Set("Session", Session::GetConstructor(context));
 | 
			
		||||
  dict.SetMethod("fromPartition", &FromPartition);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -17,6 +17,7 @@
 | 
			
		|||
#include "shell/browser/event_emitter_mixin.h"
 | 
			
		||||
#include "shell/browser/net/resolve_proxy_helper.h"
 | 
			
		||||
#include "shell/common/gin_helper/cleaned_up_at_exit.h"
 | 
			
		||||
#include "shell/common/gin_helper/constructible.h"
 | 
			
		||||
#include "shell/common/gin_helper/error_thrower.h"
 | 
			
		||||
#include "shell/common/gin_helper/function_template_extensions.h"
 | 
			
		||||
#include "shell/common/gin_helper/pinnable.h"
 | 
			
		||||
| 
						 | 
				
			
			@ -57,6 +58,7 @@ namespace api {
 | 
			
		|||
 | 
			
		||||
class Session : public gin::Wrappable<Session>,
 | 
			
		||||
                public gin_helper::Pinnable<Session>,
 | 
			
		||||
                public gin_helper::Constructible<Session>,
 | 
			
		||||
                public gin_helper::EventEmitterMixin<Session>,
 | 
			
		||||
                public gin_helper::CleanedUpAtExit,
 | 
			
		||||
#if BUILDFLAG(ENABLE_BUILTIN_SPELLCHECKER)
 | 
			
		||||
| 
						 | 
				
			
			@ -71,6 +73,7 @@ class Session : public gin::Wrappable<Session>,
 | 
			
		|||
  static gin::Handle<Session> CreateFrom(
 | 
			
		||||
      v8::Isolate* isolate,
 | 
			
		||||
      ElectronBrowserContext* browser_context);
 | 
			
		||||
  static gin::Handle<Session> New();  // Dummy, do not use!
 | 
			
		||||
 | 
			
		||||
  static Session* FromBrowserContext(content::BrowserContext* context);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -83,8 +86,7 @@ class Session : public gin::Wrappable<Session>,
 | 
			
		|||
 | 
			
		||||
  // gin::Wrappable
 | 
			
		||||
  static gin::WrapperInfo kWrapperInfo;
 | 
			
		||||
  gin::ObjectTemplateBuilder GetObjectTemplateBuilder(
 | 
			
		||||
      v8::Isolate* isolate) override;
 | 
			
		||||
  static void FillObjectTemplate(v8::Isolate*, v8::Local<v8::ObjectTemplate>);
 | 
			
		||||
  const char* GetTypeName() override;
 | 
			
		||||
 | 
			
		||||
  // Methods.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -32,6 +32,8 @@
 | 
			
		|||
#include "shell/common/gin_helper/dictionary.h"
 | 
			
		||||
#include "shell/common/gin_helper/object_template_builder.h"
 | 
			
		||||
#include "shell/common/node_includes.h"
 | 
			
		||||
#include "third_party/blink/public/common/loader/referrer_utils.h"
 | 
			
		||||
#include "third_party/blink/public/mojom/fetch/fetch_api_request.mojom.h"
 | 
			
		||||
 | 
			
		||||
namespace gin {
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -59,15 +61,84 @@ struct Converter<network::mojom::CredentialsMode> {
 | 
			
		|||
      *out = network::mojom::CredentialsMode::kOmit;
 | 
			
		||||
    else if (mode == "include")
 | 
			
		||||
      *out = network::mojom::CredentialsMode::kInclude;
 | 
			
		||||
    else if (mode == "same-origin")
 | 
			
		||||
      // Note: This only makes sense if the request specifies the "origin"
 | 
			
		||||
      // option.
 | 
			
		||||
      *out = network::mojom::CredentialsMode::kSameOrigin;
 | 
			
		||||
    else
 | 
			
		||||
      // "same-origin" is technically a member of this enum as well, but it
 | 
			
		||||
      // doesn't make sense in the context of `net.request()`, so don't convert
 | 
			
		||||
      // it.
 | 
			
		||||
      return false;
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template <>
 | 
			
		||||
struct Converter<blink::mojom::FetchCacheMode> {
 | 
			
		||||
  static bool FromV8(v8::Isolate* isolate,
 | 
			
		||||
                     v8::Local<v8::Value> val,
 | 
			
		||||
                     blink::mojom::FetchCacheMode* out) {
 | 
			
		||||
    std::string cache;
 | 
			
		||||
    if (!ConvertFromV8(isolate, val, &cache))
 | 
			
		||||
      return false;
 | 
			
		||||
    if (cache == "default") {
 | 
			
		||||
      *out = blink::mojom::FetchCacheMode::kDefault;
 | 
			
		||||
    } else if (cache == "no-store") {
 | 
			
		||||
      *out = blink::mojom::FetchCacheMode::kNoStore;
 | 
			
		||||
    } else if (cache == "reload") {
 | 
			
		||||
      *out = blink::mojom::FetchCacheMode::kBypassCache;
 | 
			
		||||
    } else if (cache == "no-cache") {
 | 
			
		||||
      *out = blink::mojom::FetchCacheMode::kValidateCache;
 | 
			
		||||
    } else if (cache == "force-cache") {
 | 
			
		||||
      *out = blink::mojom::FetchCacheMode::kForceCache;
 | 
			
		||||
    } else if (cache == "only-if-cached") {
 | 
			
		||||
      *out = blink::mojom::FetchCacheMode::kOnlyIfCached;
 | 
			
		||||
    } else {
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template <>
 | 
			
		||||
struct Converter<net::ReferrerPolicy> {
 | 
			
		||||
  static bool FromV8(v8::Isolate* isolate,
 | 
			
		||||
                     v8::Local<v8::Value> val,
 | 
			
		||||
                     net::ReferrerPolicy* out) {
 | 
			
		||||
    std::string referrer_policy;
 | 
			
		||||
    if (!ConvertFromV8(isolate, val, &referrer_policy))
 | 
			
		||||
      return false;
 | 
			
		||||
    if (base::CompareCaseInsensitiveASCII(referrer_policy, "no-referrer") ==
 | 
			
		||||
        0) {
 | 
			
		||||
      *out = net::ReferrerPolicy::NO_REFERRER;
 | 
			
		||||
    } else if (base::CompareCaseInsensitiveASCII(
 | 
			
		||||
                   referrer_policy, "no-referrer-when-downgrade") == 0) {
 | 
			
		||||
      *out = net::ReferrerPolicy::CLEAR_ON_TRANSITION_FROM_SECURE_TO_INSECURE;
 | 
			
		||||
    } else if (base::CompareCaseInsensitiveASCII(referrer_policy, "origin") ==
 | 
			
		||||
               0) {
 | 
			
		||||
      *out = net::ReferrerPolicy::ORIGIN;
 | 
			
		||||
    } else if (base::CompareCaseInsensitiveASCII(
 | 
			
		||||
                   referrer_policy, "origin-when-cross-origin") == 0) {
 | 
			
		||||
      *out = net::ReferrerPolicy::ORIGIN_ONLY_ON_TRANSITION_CROSS_ORIGIN;
 | 
			
		||||
    } else if (base::CompareCaseInsensitiveASCII(referrer_policy,
 | 
			
		||||
                                                 "unsafe-url") == 0) {
 | 
			
		||||
      *out = net::ReferrerPolicy::NEVER_CLEAR;
 | 
			
		||||
    } else if (base::CompareCaseInsensitiveASCII(referrer_policy,
 | 
			
		||||
                                                 "same-origin") == 0) {
 | 
			
		||||
      *out = net::ReferrerPolicy::CLEAR_ON_TRANSITION_CROSS_ORIGIN;
 | 
			
		||||
    } else if (base::CompareCaseInsensitiveASCII(referrer_policy,
 | 
			
		||||
                                                 "strict-origin") == 0) {
 | 
			
		||||
      *out = net::ReferrerPolicy::
 | 
			
		||||
          ORIGIN_CLEAR_ON_TRANSITION_FROM_SECURE_TO_INSECURE;
 | 
			
		||||
    } else if (referrer_policy == "" ||
 | 
			
		||||
               base::CompareCaseInsensitiveASCII(
 | 
			
		||||
                   referrer_policy, "strict-origin-when-cross-origin") == 0) {
 | 
			
		||||
      *out = net::ReferrerPolicy::REDUCE_GRANULARITY_ON_TRANSITION_CROSS_ORIGIN;
 | 
			
		||||
    } else {
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace gin
 | 
			
		||||
 | 
			
		||||
namespace electron::api {
 | 
			
		||||
| 
						 | 
				
			
			@ -401,6 +472,9 @@ gin::Handle<SimpleURLLoaderWrapper> SimpleURLLoaderWrapper::Create(
 | 
			
		|||
  opts.Get("url", &request->url);
 | 
			
		||||
  request->site_for_cookies = net::SiteForCookies::FromUrl(request->url);
 | 
			
		||||
  opts.Get("referrer", &request->referrer);
 | 
			
		||||
  request->referrer_policy =
 | 
			
		||||
      blink::ReferrerUtils::GetDefaultNetReferrerPolicy();
 | 
			
		||||
  opts.Get("referrerPolicy", &request->referrer_policy);
 | 
			
		||||
  std::string origin;
 | 
			
		||||
  opts.Get("origin", &origin);
 | 
			
		||||
  if (!origin.empty()) {
 | 
			
		||||
| 
						 | 
				
			
			@ -484,6 +558,36 @@ gin::Handle<SimpleURLLoaderWrapper> SimpleURLLoaderWrapper::Create(
 | 
			
		|||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  blink::mojom::FetchCacheMode cache_mode =
 | 
			
		||||
      blink::mojom::FetchCacheMode::kDefault;
 | 
			
		||||
  opts.Get("cache", &cache_mode);
 | 
			
		||||
  switch (cache_mode) {
 | 
			
		||||
    case blink::mojom::FetchCacheMode::kNoStore:
 | 
			
		||||
      request->load_flags |= net::LOAD_DISABLE_CACHE;
 | 
			
		||||
      break;
 | 
			
		||||
    case blink::mojom::FetchCacheMode::kValidateCache:
 | 
			
		||||
      request->load_flags |= net::LOAD_VALIDATE_CACHE;
 | 
			
		||||
      break;
 | 
			
		||||
    case blink::mojom::FetchCacheMode::kBypassCache:
 | 
			
		||||
      request->load_flags |= net::LOAD_BYPASS_CACHE;
 | 
			
		||||
      break;
 | 
			
		||||
    case blink::mojom::FetchCacheMode::kForceCache:
 | 
			
		||||
      request->load_flags |= net::LOAD_SKIP_CACHE_VALIDATION;
 | 
			
		||||
      break;
 | 
			
		||||
    case blink::mojom::FetchCacheMode::kOnlyIfCached:
 | 
			
		||||
      request->load_flags |=
 | 
			
		||||
          net::LOAD_ONLY_FROM_CACHE | net::LOAD_SKIP_CACHE_VALIDATION;
 | 
			
		||||
      break;
 | 
			
		||||
    case blink::mojom::FetchCacheMode::kUnspecifiedOnlyIfCachedStrict:
 | 
			
		||||
      request->load_flags |= net::LOAD_ONLY_FROM_CACHE;
 | 
			
		||||
      break;
 | 
			
		||||
    case blink::mojom::FetchCacheMode::kDefault:
 | 
			
		||||
      break;
 | 
			
		||||
    case blink::mojom::FetchCacheMode::kUnspecifiedForceCacheMiss:
 | 
			
		||||
      request->load_flags |= net::LOAD_ONLY_FROM_CACHE | net::LOAD_BYPASS_CACHE;
 | 
			
		||||
      break;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  bool use_session_cookies = false;
 | 
			
		||||
  opts.Get("useSessionCookies", &use_session_cookies);
 | 
			
		||||
  int options = 0;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1610,7 +1610,15 @@ describe('net module', () => {
 | 
			
		|||
        response.statusMessage = 'OK';
 | 
			
		||||
        response.end();
 | 
			
		||||
      });
 | 
			
		||||
      const urlRequest = net.request(serverUrl);
 | 
			
		||||
      // The referrerPolicy must be unsafe-url because the referrer's origin
 | 
			
		||||
      // doesn't match the loaded page. With the default referrer policy
 | 
			
		||||
      // (strict-origin-when-cross-origin), the request will be canceled by the
 | 
			
		||||
      // network service when the referrer header is invalid.
 | 
			
		||||
      // See:
 | 
			
		||||
      // - https://source.chromium.org/chromium/chromium/src/+/main:net/url_request/url_request.cc;l=682-683;drc=ae587fa7cd2e5cc308ce69353ee9ce86437e5d41
 | 
			
		||||
      // - https://source.chromium.org/chromium/chromium/src/+/main:services/network/public/mojom/network_context.mojom;l=316-318;drc=ae5c7fcf09509843c1145f544cce3a61874b9698
 | 
			
		||||
      // - https://w3c.github.io/webappsec-referrer-policy/#determine-requests-referrer
 | 
			
		||||
      const urlRequest = net.request({ url: serverUrl, referrerPolicy: 'unsafe-url' });
 | 
			
		||||
      urlRequest.setHeader('referer', referrerURL);
 | 
			
		||||
      urlRequest.end();
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -2035,4 +2043,95 @@ describe('net module', () => {
 | 
			
		|||
      await collectStreamBody(await getResponse(urlRequest));
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  describe('net.fetch', () => {
 | 
			
		||||
    // NB. there exist much more comprehensive tests for fetch() in the form of
 | 
			
		||||
    // the WPT: https://github.com/web-platform-tests/wpt/tree/master/fetch
 | 
			
		||||
    // It's possible to run these tests against net.fetch(), but the test
 | 
			
		||||
    // harness to do so is quite complex and hasn't been munged to smoothly run
 | 
			
		||||
    // inside the Electron test runner yet.
 | 
			
		||||
    //
 | 
			
		||||
    // In the meantime, here are some tests for basic functionality and
 | 
			
		||||
    // Electron-specific behavior.
 | 
			
		||||
 | 
			
		||||
    describe('basic', () => {
 | 
			
		||||
      it('can fetch http urls', async () => {
 | 
			
		||||
        const serverUrl = await respondOnce.toSingleURL((request, response) => {
 | 
			
		||||
          response.end('test');
 | 
			
		||||
        });
 | 
			
		||||
        const resp = await net.fetch(serverUrl);
 | 
			
		||||
        expect(resp.ok).to.be.true();
 | 
			
		||||
        expect(await resp.text()).to.equal('test');
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      it('can upload a string body', async () => {
 | 
			
		||||
        const serverUrl = await respondOnce.toSingleURL((request, response) => {
 | 
			
		||||
          request.on('data', chunk => response.write(chunk));
 | 
			
		||||
          request.on('end', () => response.end());
 | 
			
		||||
        });
 | 
			
		||||
        const resp = await net.fetch(serverUrl, {
 | 
			
		||||
          method: 'POST',
 | 
			
		||||
          body: 'anchovies'
 | 
			
		||||
        });
 | 
			
		||||
        expect(await resp.text()).to.equal('anchovies');
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      it('can read response as an array buffer', async () => {
 | 
			
		||||
        const serverUrl = await respondOnce.toSingleURL((request, response) => {
 | 
			
		||||
          request.on('data', chunk => response.write(chunk));
 | 
			
		||||
          request.on('end', () => response.end());
 | 
			
		||||
        });
 | 
			
		||||
        const resp = await net.fetch(serverUrl, {
 | 
			
		||||
          method: 'POST',
 | 
			
		||||
          body: 'anchovies'
 | 
			
		||||
        });
 | 
			
		||||
        expect(new TextDecoder().decode(new Uint8Array(await resp.arrayBuffer()))).to.equal('anchovies');
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      it('can read response as form data', async () => {
 | 
			
		||||
        const serverUrl = await respondOnce.toSingleURL((request, response) => {
 | 
			
		||||
          response.setHeader('content-type', 'application/x-www-form-urlencoded');
 | 
			
		||||
          response.end('foo=bar');
 | 
			
		||||
        });
 | 
			
		||||
        const resp = await net.fetch(serverUrl);
 | 
			
		||||
        const result = await resp.formData();
 | 
			
		||||
        expect(result.get('foo')).to.equal('bar');
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      it('should be able to use a session cookie store', async () => {
 | 
			
		||||
        const serverUrl = await respondOnce.toSingleURL((request, response) => {
 | 
			
		||||
          response.statusCode = 200;
 | 
			
		||||
          response.statusMessage = 'OK';
 | 
			
		||||
          response.setHeader('x-cookie', request.headers.cookie!);
 | 
			
		||||
          response.end();
 | 
			
		||||
        });
 | 
			
		||||
        const sess = session.fromPartition(`cookie-tests-${Math.random()}`);
 | 
			
		||||
        const cookieVal = `${Date.now()}`;
 | 
			
		||||
        await sess.cookies.set({
 | 
			
		||||
          url: serverUrl,
 | 
			
		||||
          name: 'wild_cookie',
 | 
			
		||||
          value: cookieVal
 | 
			
		||||
        });
 | 
			
		||||
        const response = await sess.fetch(serverUrl, {
 | 
			
		||||
          credentials: 'include'
 | 
			
		||||
        });
 | 
			
		||||
        expect(response.headers.get('x-cookie')).to.equal(`wild_cookie=${cookieVal}`);
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      it('should reject promise on DNS failure', async () => {
 | 
			
		||||
        const r = net.fetch('https://i.do.not.exist');
 | 
			
		||||
        await expect(r).to.be.rejectedWith(/ERR_NAME_NOT_RESOLVED/);
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      it('should reject body promise when stream fails', async () => {
 | 
			
		||||
        const serverUrl = await respondOnce.toSingleURL((request, response) => {
 | 
			
		||||
          response.write('first chunk');
 | 
			
		||||
          setTimeout(() => response.destroy());
 | 
			
		||||
        });
 | 
			
		||||
        const r = await net.fetch(serverUrl);
 | 
			
		||||
        expect(r.status).to.equal(200);
 | 
			
		||||
        await expect(r.text()).to.be.rejectedWith(/ERR_INCOMPLETE_CHUNKED_ENCODING/);
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										6
									
								
								typings/internal-ambient.d.ts
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								typings/internal-ambient.d.ts
									
										
									
									
										vendored
									
									
								
							| 
						 | 
				
			
			@ -130,11 +130,13 @@ declare namespace NodeJS {
 | 
			
		|||
    url: string;
 | 
			
		||||
    extraHeaders?: Record<string, string>;
 | 
			
		||||
    useSessionCookies?: boolean;
 | 
			
		||||
    credentials?: 'include' | 'omit';
 | 
			
		||||
    credentials?: 'include' | 'omit' | 'same-origin';
 | 
			
		||||
    body: Uint8Array | BodyFunc;
 | 
			
		||||
    session?: Electron.Session;
 | 
			
		||||
    partition?: string;
 | 
			
		||||
    referrer?: string;
 | 
			
		||||
    referrerPolicy?: string;
 | 
			
		||||
    cache?: string;
 | 
			
		||||
    origin?: string;
 | 
			
		||||
    hasUserActivation?: boolean;
 | 
			
		||||
    mode?: string;
 | 
			
		||||
| 
						 | 
				
			
			@ -224,7 +226,7 @@ declare namespace NodeJS {
 | 
			
		|||
    _linkedBinding(name: 'electron_browser_power_save_blocker'): { powerSaveBlocker: Electron.PowerSaveBlocker };
 | 
			
		||||
    _linkedBinding(name: 'electron_browser_push_notifications'): { pushNotifications: Electron.PushNotifications };
 | 
			
		||||
    _linkedBinding(name: 'electron_browser_safe_storage'): { safeStorage: Electron.SafeStorage };
 | 
			
		||||
    _linkedBinding(name: 'electron_browser_session'): typeof Electron.Session;
 | 
			
		||||
    _linkedBinding(name: 'electron_browser_session'): {fromPartition: typeof Electron.Session.fromPartition, Session: typeof Electron.Session};
 | 
			
		||||
    _linkedBinding(name: 'electron_browser_screen'): { createScreen(): Electron.Screen };
 | 
			
		||||
    _linkedBinding(name: 'electron_browser_system_preferences'): { systemPreferences: Electron.SystemPreferences };
 | 
			
		||||
    _linkedBinding(name: 'electron_browser_tray'): { Tray: Electron.Tray };
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue