@v4fire/core
Version:
V4Fire core library
179 lines (137 loc) • 4.07 kB
text/typescript
/*!
* 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
[] = 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;