electron/lib/browser/api/net-fetch.ts
Devraj Mehta 8c71e2adc9
feat: add net module to utility process (#40017)
* chore: initial prototype of net api from utility process

* chore: update url loader to work on both browser and utility processes

* chore: add net files to utility process bundle

* chore: re-add app ready check but only on main process

* chore: replace browser thread dcheck's with sequence checker

* refactor: move url loader from browser to common

* refactor: move net-client-request.ts from browser to common

* docs: add utility process to net api docs

* refactor: move net module app ready check to browser only

* refactor: switch import from main to common after moving to common

* test: add basic net module test for utility process

* refactor: switch browser pid with utility pid

* refactor: move electron_api_net from browser to common

* chore: add fetch to utility net module

* chore: add isOnline and online to utility net module

* refactor: move net spec helpers into helper file

* refactor: break apart net module tests

Adds two additional net module test files: `api-net-session-spec.ts` for
tests that depend on a session being available (aka depend on running on
the main process) and `api-net-custom-protocols-spec.ts` for custom
protocol tests. This enables running `api-net-spec.ts` in the utility
process.

* test: add utility process mocha runner to run net module tests

* docs: add utility process to net module classes

* refactor: update imports in lib/utility to use electron/utility

* chore: check browser context before using in main process

Since the browser context supplied to the SimpleURLLoaderWrapper can now
be null for use in the UtilityProcess, adding a null check for the main
process before use to get a more sensible error if something goes wrong.

Co-authored-by: Cheng Zhao <github@zcbenz.com>

* chore: remove test debugging

* chore: remove unnecessary header include

* docs: add utility process net module limitations

* test: run net module tests in utility process individually

* refactor: clean up prior utility process net tests

* chore: add resolveHost to utility process net module

* chore: replace resolve host dcheck with sequence checker

* test: add net module tests for net.resolveHost

* docs: remove utility process limitation for resolveHost

---------

Co-authored-by: deepak1556 <hop2deep@gmail.com>
Co-authored-by: Cheng Zhao <github@zcbenz.com>
2024-01-04 16:20:37 -05:00

123 lines
3.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { ClientRequestConstructorOptions, ClientRequest, IncomingMessage, Session as SessionT } from 'electron/main';
import { Readable, Writable, isReadable } from 'stream';
import { allowAnyProtocol } from '@electron/internal/common/api/net-client-request';
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 & {bypassCustomProtocolHandlers?: boolean}) | undefined, session: SessionT | undefined,
request: (options: ClientRequestConstructorOptions | string) => ClientRequest) {
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
// requestObjects signals 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 requestObjects signals 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 = request(allowAnyProtocol({
session,
method: req.method,
url: req.url,
origin,
credentials,
cache: req.cache,
referrerPolicy: req.referrerPolicy,
redirect: req.redirect
}));
(r as any)._urlLoaderOptions.bypassCustomProtocolHandlers = !!init?.bypassCustomProtocolHandlers;
// 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
});
(rResp as any).__original_resp = resp;
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;
}