UNPKG

@v4fire/core

Version:
179 lines (137 loc) 4.07 kB
/*! * V4Fire Core * https://github.com/V4Fire/Core * * Released under the MIT license * https://github.com/V4Fire/Core/blob/master/LICENSE */ /** * [[include:core/request/engines/node/README.md]] * @packageDocumentation */ import got, { Options, Response as GotResponse } from 'got'; import AbortablePromise from 'core/promise/abortable'; import { isOnline } from 'core/net'; import Response from 'core/request/response'; import RequestError from 'core/request/error'; import StreamBuffer from 'core/request/modules/stream-buffer'; import { convertDataToSend } from 'core/request/engines/helpers'; import { writeableStreamMethods } from 'core/request/engines/node/const'; import type { RequestEngine, RequestResponseChunk } from 'core/request/interface'; /** * Creates request by using node.js with the specified parameters and returns a promise * @param params */ const request: RequestEngine = (params) => { const p = params, streamBuffer = new StreamBuffer<RequestResponseChunk>(); const [body, contentType] = convertDataToSend(p.body); const headers = {...p.headers}; if (contentType != null) { Object.assign(headers, { 'content-type': contentType }); } const gotOpts = <Options>{ body, headers, method: p.method, throwHttpErrors: false, timeout: p.timeout, retry: 0 }; return new AbortablePromise<Response>(async (resolve, reject, onAbort) => { const {status} = await AbortablePromise.resolve(isOnline(), p.parent); if (!status) { return reject(new RequestError(RequestError.Offline)); } const stream = got.stream(p.url, Object.cast(gotOpts)); onAbort((reason) => { streamBuffer.destroy(reason); stream.destroy(); }); if (needEndStream(gotOpts)) { stream.end(); } const registeredEvents = Object.createDict<boolean>(); p.emitter.on('newListener', (event: string) => { if (registeredEvents[event]) { return; } registeredEvents[event] = true; stream.on(event, (e) => p.emitter.emit(event, e)); }); p.emitter.emit('drainListeners'); stream.on('error', (error) => { const type = error.name === 'TimeoutError' ? RequestError.Timeout : RequestError.Engine, requestError = new RequestError(type, {error}); streamBuffer.destroy(requestError); reject(requestError); }); stream.on('response', (response: GotResponse) => { const contentLength = <string | null>response.headers['content-length'], total = contentLength != null ? Number(contentLength) : undefined; let loaded = 0; (async () => { try { for await (const data of stream) { loaded += data.length; streamBuffer.add({total, loaded, data}); } streamBuffer.close(); } catch {} })(); const getResponse = async () => { const completeData: Array<[number, Uint8Array]> = []; let pos = 0; for await (const {data} of streamBuffer) { if (data == null) { continue; } completeData.push([pos, data]); pos += data.length; } return completeData.reduce( (buffer, [pos, data]) => { buffer.set(data, pos); return buffer; }, new Uint8Array(loaded) ).buffer; }; getResponse[Symbol.asyncIterator] = streamBuffer[Symbol.asyncIterator].bind(streamBuffer); const res = new Response(getResponse, { url: response.url, redirected: response.redirectUrls.length !== 0, parent: p.parent, important: p.important, okStatuses: p.okStatuses, noContentStatuses: p.noContentStatuses, status: response.statusCode, statusText: response.statusMessage, headers: Object.cast(response.headers), responseType: p.responseType, forceResponseType: p.forceResponseType, decoder: p.decoders, streamDecoder: p.streamDecoders, jsonReviver: p.jsonReviver }); resolve(res); }); function needEndStream({body, method}: Options): boolean { return body == null && method != null && writeableStreamMethods[method.toUpperCase()]; } }, p.parent); }; export default request;