net-fetch.ts 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125
  1. import { allowAnyProtocol } from '@electron/internal/common/api/net-client-request';
  2. import { ClientRequestConstructorOptions, ClientRequest, IncomingMessage, Session as SessionT } from 'electron/main';
  3. import { Readable, Writable, isReadable } from 'stream';
  4. function createDeferredPromise<T, E extends Error = Error> (): { promise: Promise<T>; resolve: (x: T) => void; reject: (e: E) => void; } {
  5. let res: (x: T) => void;
  6. let rej: (e: E) => void;
  7. const promise = new Promise<T>((resolve, reject) => {
  8. res = resolve;
  9. rej = reject;
  10. });
  11. return { promise, resolve: res!, reject: rej! };
  12. }
  13. export function fetchWithSession (input: RequestInfo, init: (RequestInit & {bypassCustomProtocolHandlers?: boolean}) | undefined, session: SessionT | undefined,
  14. request: (options: ClientRequestConstructorOptions | string) => ClientRequest) {
  15. const p = createDeferredPromise<Response>();
  16. let req: Request;
  17. try {
  18. req = new Request(input, init);
  19. } catch (e: any) {
  20. p.reject(e);
  21. return p.promise;
  22. }
  23. if (req.signal.aborted) {
  24. // 1. Abort the fetch() call with p, request, null, and
  25. // requestObject’s signal’s abort reason.
  26. const error = (req.signal as any).reason ?? new DOMException('The operation was aborted.', 'AbortError');
  27. p.reject(error);
  28. if (req.body != null && isReadable(req.body as unknown as NodeJS.ReadableStream)) {
  29. req.body.cancel(error).catch((err) => {
  30. if (err.code === 'ERR_INVALID_STATE') {
  31. // Node bug?
  32. return;
  33. }
  34. throw err;
  35. });
  36. }
  37. // 2. Return p.
  38. return p.promise;
  39. }
  40. let locallyAborted = false;
  41. req.signal.addEventListener(
  42. 'abort',
  43. () => {
  44. // 1. Set locallyAborted to true.
  45. locallyAborted = true;
  46. // 2. Abort the fetch() call with p, request, responseObject,
  47. // and requestObject’s signal’s abort reason.
  48. const error = (req.signal as any).reason ?? new DOMException('The operation was aborted.', 'AbortError');
  49. p.reject(error);
  50. if (req.body != null && isReadable(req.body as unknown as NodeJS.ReadableStream)) {
  51. req.body.cancel(error).catch((err) => {
  52. if (err.code === 'ERR_INVALID_STATE') {
  53. // Node bug?
  54. return;
  55. }
  56. throw err;
  57. });
  58. }
  59. r.abort();
  60. },
  61. { once: true }
  62. );
  63. const origin = req.headers.get('origin') ?? undefined;
  64. // We can't set credentials to same-origin unless there's an origin set.
  65. const credentials = req.credentials === 'same-origin' && !origin ? 'include' : req.credentials;
  66. const r = request(allowAnyProtocol({
  67. session,
  68. method: req.method,
  69. url: req.url,
  70. origin,
  71. credentials,
  72. cache: req.cache,
  73. referrerPolicy: req.referrerPolicy,
  74. redirect: req.redirect
  75. }));
  76. (r as any)._urlLoaderOptions.bypassCustomProtocolHandlers = !!init?.bypassCustomProtocolHandlers;
  77. // cors is the default mode, but we can't set mode=cors without an origin.
  78. if (req.mode && (req.mode !== 'cors' || origin)) {
  79. r.setHeader('Sec-Fetch-Mode', req.mode);
  80. }
  81. for (const [k, v] of req.headers) {
  82. r.setHeader(k, v);
  83. }
  84. r.on('response', (resp: IncomingMessage) => {
  85. if (locallyAborted) return;
  86. const headers = new Headers();
  87. for (const [k, v] of Object.entries(resp.headers)) {
  88. headers.set(k, Array.isArray(v) ? v.join(', ') : v);
  89. }
  90. const nullBodyStatus = [101, 204, 205, 304];
  91. const body = nullBodyStatus.includes(resp.statusCode) || req.method === 'HEAD' ? null : Readable.toWeb(resp as unknown as Readable) as ReadableStream;
  92. const rResp = new Response(body, {
  93. headers,
  94. status: resp.statusCode,
  95. statusText: resp.statusMessage
  96. });
  97. (rResp as any).__original_resp = resp;
  98. p.resolve(rResp);
  99. });
  100. r.on('error', (err) => {
  101. p.reject(err);
  102. });
  103. if (!req.body?.pipeTo(Writable.toWeb(r as unknown as Writable)).then(() => r.end())) { r.end(); }
  104. return p.promise;
  105. }