rittenhop-ghost/versions/5.94.2/node_modules/stripe/lib/net/FetchHttpClient.js

131 lines
3.7 KiB
JavaScript
Raw Normal View History

'use strict';
const {HttpClient, HttpClientResponse} = require('./HttpClient');
/**
* HTTP client which uses a `fetch` function to issue requests.
*
* By default relies on the global `fetch` function, but an optional function
* can be passed in. If passing in a function, it is expected to match the Web
* Fetch API. As an example, this could be the function provided by the
* node-fetch package (https://github.com/node-fetch/node-fetch).
*/
class FetchHttpClient extends HttpClient {
constructor(fetchFn) {
super();
this._fetchFn = fetchFn;
}
/** @override. */
getClientName() {
return 'fetch';
}
makeRequest(
host,
port,
path,
method,
headers,
requestData,
protocol,
timeout
) {
const isInsecureConnection = protocol === 'http';
const url = new URL(
path,
`${isInsecureConnection ? 'http' : 'https'}://${host}`
);
url.port = port;
const fetchFn = this._fetchFn || fetch;
const fetchPromise = fetchFn(url.toString(), {
method,
headers,
body: requestData || undefined,
});
// The Fetch API does not support passing in a timeout natively, so a
// timeout promise is constructed to race against the fetch and preempt the
// request, simulating a timeout.
//
// This timeout behavior differs from Node:
// - Fetch uses a single timeout for the entire length of the request.
// - Node is more fine-grained and resets the timeout after each stage of
// the request.
//
// As an example, if the timeout is set to 30s and the connection takes 20s
// to be established followed by 20s for the body, Fetch would timeout but
// Node would not. The more fine-grained timeout cannot be implemented with
// fetch.
let pendingTimeoutId;
const timeoutPromise = new Promise((_, reject) => {
pendingTimeoutId = setTimeout(() => {
pendingTimeoutId = null;
reject(HttpClient.makeTimeoutError());
}, timeout);
});
return Promise.race([fetchPromise, timeoutPromise])
.then((res) => {
return new FetchHttpClientResponse(res);
})
.finally(() => {
if (pendingTimeoutId) {
clearTimeout(pendingTimeoutId);
}
});
}
}
class FetchHttpClientResponse extends HttpClientResponse {
constructor(res) {
super(
res.status,
FetchHttpClientResponse._transformHeadersToObject(res.headers)
);
this._res = res;
}
getRawResponse() {
return this._res;
}
toStream(streamCompleteCallback) {
// Unfortunately `fetch` does not have event handlers for when the stream is
// completely read. We therefore invoke the streamCompleteCallback right
// away. This callback emits a response event with metadata and completes
// metrics, so it's ok to do this without waiting for the stream to be
// completely read.
streamCompleteCallback();
// Fetch's `body` property is expected to be a readable stream of the body.
return this._res.body;
}
toJSON() {
return this._res.json();
}
static _transformHeadersToObject(headers) {
// Fetch uses a Headers instance so this must be converted to a barebones
// JS object to meet the HttpClient interface.
const headersObj = {};
for (const entry of headers) {
if (!Array.isArray(entry) || entry.length != 2) {
throw new Error(
'Response objects produced by the fetch function given to FetchHttpClient do not have an iterable headers map. Response#headers should be an iterable object.'
);
}
headersObj[entry[0]] = entry[1];
}
return headersObj;
}
}
module.exports = {FetchHttpClient, FetchHttpClientResponse};