electron-fetch
Advanced tools
Comparing version 1.2.1 to 1.3.0
@@ -7,2 +7,6 @@ | ||
## V1.3.0 | ||
- Fix TypeScript typings & add tests so they cannot break again | ||
- Updating dependencies | ||
## V1.2.0 | ||
@@ -9,0 +13,0 @@ - Adding TypeScript typings (thanks @BurningEnlightenment) |
@@ -0,1 +1,3 @@ | ||
import * as https from 'https'; | ||
import { Z_SYNC_FLUSH, createGunzip, createInflate, createInflateRaw } from 'zlib'; | ||
import { convert } from 'encoding'; | ||
@@ -6,12 +8,8 @@ import Stream, { PassThrough } from 'stream'; | ||
import { format, parse, resolve } from 'url'; | ||
import * as https from 'https'; | ||
import { Z_SYNC_FLUSH, createGunzip, createInflate, createInflateRaw } from 'zlib'; | ||
// Based on https://github.com/tmpvar/jsdom/blob/aa85b2abf07766ff7bf5c1f6daafb3726f2f2db5/lib/jsdom/living/blob.js | ||
// (MIT licensed) | ||
const BUFFER = Symbol('buffer'); | ||
const TYPE = Symbol('type'); | ||
const CLOSED = Symbol('closed'); | ||
class Blob { | ||
@@ -25,9 +23,6 @@ constructor() { | ||
}); | ||
this[CLOSED] = false; | ||
this[TYPE] = ''; | ||
const blobParts = arguments[0]; | ||
const options = arguments[1]; | ||
const buffers = []; | ||
@@ -38,5 +33,7 @@ | ||
const length = Number(a.length); | ||
for (let i = 0; i < length; i++) { | ||
const element = a[i]; | ||
let buffer; | ||
if (element instanceof Buffer) { | ||
@@ -53,2 +50,3 @@ buffer = element; | ||
} | ||
buffers.push(buffer); | ||
@@ -59,4 +57,4 @@ } | ||
this[BUFFER] = Buffer.concat(buffers); | ||
let type = options && options.type !== undefined && String(options.type).toLowerCase(); | ||
let type = options && options.type !== undefined && String(options.type).toLowerCase(); | ||
if (type && !/[^\u0020-\u007E]/.test(type)) { | ||
@@ -81,6 +79,6 @@ this[TYPE] = type; | ||
const size = this.size; | ||
const start = arguments[0]; | ||
const end = arguments[1]; | ||
let relativeStart, relativeEnd; | ||
if (start === undefined) { | ||
@@ -93,2 +91,3 @@ relativeStart = 0; | ||
} | ||
if (end === undefined) { | ||
@@ -101,7 +100,9 @@ relativeEnd = size; | ||
} | ||
const span = Math.max(relativeEnd - relativeStart, 0); | ||
const buffer = this[BUFFER]; | ||
const slicedBuffer = buffer.slice(relativeStart, relativeStart + span); | ||
const blob = new Blob([], { type: arguments[2] }); | ||
const blob = new Blob([], { | ||
type: arguments[2] | ||
}); | ||
blob[BUFFER] = slicedBuffer; | ||
@@ -115,4 +116,4 @@ blob[CLOSED] = this[CLOSED]; | ||
} | ||
} | ||
Object.defineProperty(Blob.prototype, Symbol.toStringTag, { | ||
@@ -139,3 +140,2 @@ value: 'BlobPrototype', | ||
*/ | ||
const netErrorMap = { | ||
@@ -148,24 +148,25 @@ 'ERR_CONNECTION_REFUSED': 'ECONNREFUSED', | ||
}; | ||
function FetchError(message, type, systemError) { | ||
Error.call(this, message); | ||
const regex = /^.*net::(.*)/; | ||
if (regex.test(message)) { | ||
let errorCode = regex.exec(message)[1]; | ||
// istanbul ignore else | ||
let errorCode = regex.exec(message)[1]; // istanbul ignore else | ||
if (netErrorMap.hasOwnProperty(errorCode)) errorCode = netErrorMap[errorCode]; | ||
systemError = { code: errorCode }; | ||
systemError = { | ||
code: errorCode | ||
}; | ||
} | ||
this.message = message; | ||
this.type = type; | ||
this.type = type; // when err.type is `system`, err.code contains system error code | ||
// when err.type is `system`, err.code contains system error code | ||
if (systemError) { | ||
this.code = this.errno = systemError.code; | ||
} | ||
} // hide custom error implementation details from end-users | ||
// hide custom error implementation details from end-users | ||
Error.captureStackTrace(this, this.constructor); | ||
} | ||
FetchError.prototype = Object.create(Error.prototype); | ||
@@ -180,5 +181,3 @@ FetchError.prototype.constructor = FetchError; | ||
*/ | ||
const DISTURBED = Symbol('disturbed'); | ||
/** | ||
@@ -193,10 +192,7 @@ * Body class | ||
*/ | ||
function Body(body) { | ||
var _ref = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}, | ||
_ref$size = _ref.size; | ||
let size = _ref$size === undefined ? 0 : _ref$size; | ||
var _ref$timeout = _ref.timeout; | ||
let timeout = _ref$timeout === undefined ? 0 : _ref$timeout; | ||
function Body(body, { | ||
size = 0, | ||
timeout = 0 | ||
} = {}) { | ||
if (body == null) { | ||
@@ -210,2 +206,3 @@ // body is undefined or null | ||
} | ||
this.body = body; | ||
@@ -216,3 +213,2 @@ this[DISTURBED] = false; | ||
} | ||
Body.prototype = { | ||
@@ -229,5 +225,3 @@ get bodyUsed() { | ||
arrayBuffer() { | ||
return consumeBody.call(this).then(function (buf) { | ||
return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength); | ||
}); | ||
return consumeBody.call(this).then(buf => buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength)); | ||
}, | ||
@@ -242,11 +236,8 @@ | ||
let ct = this.headers && this.headers.get('content-type') || ''; | ||
return consumeBody.call(this).then(function (buf) { | ||
return Object.assign( | ||
// Prevent copying | ||
new Blob([], { | ||
type: ct.toLowerCase() | ||
}), { | ||
[BUFFER]: buf | ||
}); | ||
}); | ||
return consumeBody.call(this).then(buf => Object.assign( // Prevent copying | ||
new Blob([], { | ||
type: ct.toLowerCase() | ||
}), { | ||
[BUFFER]: buf | ||
})); | ||
}, | ||
@@ -260,5 +251,3 @@ | ||
json() { | ||
return consumeBody.call(this).then(function (buffer) { | ||
return JSON.parse(buffer.toString()); | ||
}); | ||
return consumeBody.call(this).then(buffer => JSON.parse(buffer.toString())); | ||
}, | ||
@@ -272,5 +261,3 @@ | ||
text() { | ||
return consumeBody.call(this).then(function (buffer) { | ||
return buffer.toString(); | ||
}); | ||
return consumeBody.call(this).then(buffer => buffer.toString()); | ||
}, | ||
@@ -294,7 +281,3 @@ | ||
textConverted() { | ||
var _this = this; | ||
return consumeBody.call(this).then(function (buffer) { | ||
return convertBody(buffer, _this.headers); | ||
}); | ||
return consumeBody.call(this).then(buffer => convertBody(buffer, this.headers)); | ||
} | ||
@@ -305,3 +288,16 @@ | ||
Body.mixIn = function (proto) { | ||
for (const name of Object.getOwnPropertyNames(Body.prototype)) { | ||
for (var _iterator = Object.getOwnPropertyNames(Body.prototype), _isArray = Array.isArray(_iterator), _i = 0, _iterator = _isArray ? _iterator : _iterator[Symbol.iterator]();;) { | ||
var _ref; | ||
if (_isArray) { | ||
if (_i >= _iterator.length) break; | ||
_ref = _iterator[_i++]; | ||
} else { | ||
_i = _iterator.next(); | ||
if (_i.done) break; | ||
_ref = _i.value; | ||
} | ||
const name = _ref; | ||
// istanbul ignore else | ||
@@ -314,3 +310,2 @@ if (!(name in proto)) { | ||
}; | ||
/** | ||
@@ -321,5 +316,5 @@ * Decode buffers into utf-8 string | ||
*/ | ||
function consumeBody() { | ||
var _this2 = this; | ||
if (this[DISTURBED]) { | ||
@@ -329,52 +324,48 @@ return Promise.reject(new Error(`body used already for: ${this.url}`)); | ||
this[DISTURBED] = true; | ||
this[DISTURBED] = true; // body is null | ||
// body is null | ||
if (this.body === null) { | ||
return Promise.resolve(Buffer.alloc(0)); | ||
} | ||
} // body is string | ||
// body is string | ||
if (typeof this.body === 'string') { | ||
return Promise.resolve(Buffer.from(this.body)); | ||
} | ||
} // body is blob | ||
// body is blob | ||
if (this.body instanceof Blob) { | ||
return Promise.resolve(this.body[BUFFER]); | ||
} | ||
} // body is buffer | ||
// body is buffer | ||
if (Buffer.isBuffer(this.body)) { | ||
return Promise.resolve(this.body); | ||
} | ||
} // istanbul ignore if: should never happen | ||
// istanbul ignore if: should never happen | ||
if (!(this.body instanceof Stream)) { | ||
return Promise.resolve(Buffer.alloc(0)); | ||
} | ||
} // body is stream | ||
// get ready to actually consume the body | ||
// body is stream | ||
// get ready to actually consume the body | ||
const accum = []; | ||
let accumBytes = 0; | ||
let abort = false; | ||
return new Promise((resolve$$1, reject) => { | ||
let resTimeout; // allow timeout on slow response body | ||
return new Promise(function (resolve$$1, reject) { | ||
let resTimeout; | ||
// allow timeout on slow response body | ||
if (_this2.timeout) { | ||
resTimeout = setTimeout(function () { | ||
if (this.timeout) { | ||
resTimeout = setTimeout(() => { | ||
abort = true; | ||
reject(new FetchError(`Response timeout while trying to fetch ${_this2.url} (over ${_this2.timeout}ms)`, 'body-timeout')); | ||
}, _this2.timeout); | ||
} | ||
reject(new FetchError(`Response timeout while trying to fetch ${this.url} (over ${this.timeout}ms)`, 'body-timeout')); | ||
}, this.timeout); | ||
} // handle stream error, such as incorrect content-encoding | ||
// handle stream error, such as incorrect content-encoding | ||
_this2.body.on('error', function (err) { | ||
reject(new FetchError(`Invalid response body while trying to fetch ${_this2.url}: ${err.message}`, 'system', err)); | ||
this.body.on('error', err => { | ||
reject(new FetchError(`Invalid response body while trying to fetch ${this.url}: ${err.message}`, 'system', err)); | ||
}); | ||
_this2.body.on('data', function (chunk) { | ||
this.body.on('data', chunk => { | ||
if (abort || chunk === null) { | ||
@@ -384,5 +375,5 @@ return; | ||
if (_this2.size && accumBytes + chunk.length > _this2.size) { | ||
if (this.size && accumBytes + chunk.length > this.size) { | ||
abort = true; | ||
reject(new FetchError(`content size at ${_this2.url} over limit: ${_this2.size}`, 'max-size')); | ||
reject(new FetchError(`content size at ${this.url} over limit: ${this.size}`, 'max-size')); | ||
return; | ||
@@ -394,4 +385,3 @@ } | ||
}); | ||
_this2.body.on('end', function () { | ||
this.body.on('end', () => { | ||
if (abort) { | ||
@@ -406,3 +396,2 @@ return; | ||
} | ||
/** | ||
@@ -416,21 +405,21 @@ * Detect buffer encoding and convert to target encoding | ||
*/ | ||
function convertBody(buffer, headers) { | ||
const ct = headers.get('content-type'); | ||
let charset = 'utf-8'; | ||
let res, str; | ||
let res, str; // header | ||
// header | ||
if (ct) { | ||
res = /charset=([^;]*)/i.exec(ct); | ||
} | ||
} // no charset in content type, peek at response body for at most 1024 bytes | ||
// no charset in content type, peek at response body for at most 1024 bytes | ||
str = buffer.slice(0, 1024).toString(); | ||
// html5 | ||
str = buffer.slice(0, 1024).toString(); // html5 | ||
if (!res && str) { | ||
res = /<meta.+?charset=(['"])(.+?)\1/i.exec(str); | ||
} | ||
} // html4 | ||
// html4 | ||
if (!res && str) { | ||
@@ -442,24 +431,22 @@ res = /<meta[\s]+?http-equiv=(['"])content-type\1[\s]+?content=(['"])(.+?)\2/i.exec(str); | ||
} | ||
} | ||
} // xml | ||
// xml | ||
if (!res && str) { | ||
res = /<\?xml.+?encoding=(['"])(.+?)\1/i.exec(str); | ||
} | ||
} // found charset | ||
// found charset | ||
if (res) { | ||
charset = res.pop(); | ||
charset = res.pop(); // prevent decode issues when sites use incorrect encoding | ||
// ref: https://hsivonen.fi/encoding-menu/ | ||
// prevent decode issues when sites use incorrect encoding | ||
// ref: https://hsivonen.fi/encoding-menu/ | ||
if (charset === 'gb2312' || charset === 'gbk') { | ||
charset = 'gb18030'; | ||
} | ||
} | ||
} // turn raw buffers into a single utf-8 buffer | ||
// turn raw buffers into a single utf-8 buffer | ||
return convert(buffer, 'UTF-8', charset).toString(); | ||
} | ||
/** | ||
@@ -471,13 +458,14 @@ * Clone body given Res/Req instance | ||
*/ | ||
function clone(instance) { | ||
let p1, p2; | ||
let body = instance.body; | ||
let body = instance.body; // don't allow cloning a used body | ||
// don't allow cloning a used body | ||
if (instance.bodyUsed) { | ||
throw new Error('cannot clone body after it is used'); | ||
} | ||
} // check that body is a stream and not form-data object | ||
// note: we can't clone the form-data object without having it as a dependency | ||
// check that body is a stream and not form-data object | ||
// note: we can't clone the form-data object without having it as a dependency | ||
if (body instanceof Stream && typeof body.getBoundary !== 'function') { | ||
@@ -488,4 +476,4 @@ // tee instance body | ||
body.pipe(p1); | ||
body.pipe(p2); | ||
// set instance body to teed body and return the other teed body | ||
body.pipe(p2); // set instance body to teed body and return the other teed body | ||
instance.body = p1; | ||
@@ -497,3 +485,2 @@ body = p2; | ||
} | ||
/** | ||
@@ -508,6 +495,5 @@ * Performs the operation "extract a `Content-Type` value from |object|" as | ||
*/ | ||
function extractContentType(instance) { | ||
const body = instance.body; | ||
// istanbul ignore if: Currently, because of a guard in Request, body | ||
const body = instance.body; // istanbul ignore if: Currently, because of a guard in Request, body | ||
// can never be null. Included here for completeness. | ||
@@ -536,8 +522,5 @@ | ||
} | ||
function getTotalBytes(instance) { | ||
const body = instance.body; | ||
const body = instance.body; // istanbul ignore if: included for completion | ||
// istanbul ignore if: included for completion | ||
if (body === null) { | ||
@@ -563,2 +546,3 @@ // body is null | ||
} | ||
return null; | ||
@@ -571,7 +555,5 @@ } else { | ||
} | ||
function writeToStream(dest, instance) { | ||
const body = instance.body; | ||
if (body === null) { | ||
@@ -597,2 +579,3 @@ // body is null | ||
} | ||
body.pipe(new PassThrough()) // I have to put a PassThrough because somehow, FormData streams are not eaten by electron/net | ||
@@ -638,23 +621,31 @@ .pipe(dest); | ||
} | ||
if (ch >= 65 && ch <= 90) { | ||
return true; | ||
} | ||
if (ch === 45) { | ||
return true; | ||
} | ||
if (ch >= 48 && ch <= 57) { | ||
return true; | ||
} | ||
if (ch === 34 || ch === 40 || ch === 41 || ch === 44) { | ||
return false; | ||
} | ||
if (ch >= 33 && ch <= 46) { | ||
return true; | ||
} | ||
if (ch === 124 || ch === 126) { | ||
return true; | ||
} | ||
return false; | ||
} | ||
// istanbul ignore next | ||
} // istanbul ignore next | ||
function checkIsHttpToken(val) { | ||
@@ -664,6 +655,9 @@ if (typeof val !== 'string' || val.length === 0) { | ||
} | ||
if (!isValidTokenChar(val.charCodeAt(0))) { | ||
return false; | ||
} | ||
const len = val.length; | ||
if (len > 1) { | ||
@@ -673,2 +667,3 @@ if (!isValidTokenChar(val.charCodeAt(1))) { | ||
} | ||
if (len > 2) { | ||
@@ -678,2 +673,3 @@ if (!isValidTokenChar(val.charCodeAt(2))) { | ||
} | ||
if (len > 3) { | ||
@@ -683,2 +679,3 @@ if (!isValidTokenChar(val.charCodeAt(3))) { | ||
} | ||
for (let i = 4; i < len; i++) { | ||
@@ -692,5 +689,5 @@ if (!isValidTokenChar(val.charCodeAt(i))) { | ||
} | ||
return true; | ||
} | ||
/** | ||
@@ -707,27 +704,39 @@ * True if val contains an invalid field-vchar | ||
// istanbul ignore next | ||
function checkInvalidHeaderChar(val) { | ||
val += ''; | ||
if (val.length < 1) { | ||
return false; | ||
} | ||
let c = val.charCodeAt(0); | ||
if (c <= 31 && c !== 9 || c > 255 || c === 127) { | ||
return true; | ||
} | ||
if (val.length < 2) { | ||
return false; | ||
} | ||
c = val.charCodeAt(1); | ||
if (c <= 31 && c !== 9 || c > 255 || c === 127) { | ||
return true; | ||
} | ||
if (val.length < 3) { | ||
return false; | ||
} | ||
c = val.charCodeAt(2); | ||
if (c <= 31 && c !== 9 || c > 255 || c === 127) { | ||
return true; | ||
} | ||
for (let i = 3; i < val.length; ++i) { | ||
c = val.charCodeAt(i); | ||
if (c <= 31 && c !== 9 || c > 255 || c === 127) { | ||
@@ -737,2 +746,3 @@ return true; | ||
} | ||
return false; | ||
@@ -749,5 +759,7 @@ } | ||
name += ''; | ||
if (!checkIsHttpToken(name)) { | ||
throw new TypeError(`${name} is not a legal HTTP header name`); | ||
} | ||
return name.toLowerCase(); | ||
@@ -758,5 +770,7 @@ } | ||
value += ''; | ||
if (checkInvalidHeaderChar(value)) { | ||
throw new TypeError(`${value} is not a legal HTTP header value`); | ||
} | ||
return value; | ||
@@ -772,30 +786,46 @@ } | ||
*/ | ||
constructor() { | ||
let init = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : undefined; | ||
constructor(init = undefined) { | ||
this[MAP] = Object.create(null); // We don't worry about converting prop to ByteString here as append() | ||
// will handle it. | ||
this[MAP] = Object.create(null); | ||
// We don't worry about converting prop to ByteString here as append() | ||
// will handle it. | ||
if (init == null) ; else if (typeof init === 'object') { | ||
const method = init[Symbol.iterator]; | ||
if (method != null) { | ||
if (typeof method !== 'function') { | ||
throw new TypeError('Header pairs must be iterable'); | ||
} | ||
} // sequence<sequence<ByteString>> | ||
// Note: per spec we have to first exhaust the lists then process them | ||
// sequence<sequence<ByteString>> | ||
// Note: per spec we have to first exhaust the lists then process them | ||
const pairs = []; | ||
for (const pair of init) { | ||
for (var _iterator = init, _isArray = Array.isArray(_iterator), _i = 0, _iterator = _isArray ? _iterator : _iterator[Symbol.iterator]();;) { | ||
var _ref; | ||
if (_isArray) { | ||
if (_i >= _iterator.length) break; | ||
_ref = _iterator[_i++]; | ||
} else { | ||
_i = _iterator.next(); | ||
if (_i.done) break; | ||
_ref = _i.value; | ||
} | ||
const pair = _ref; | ||
if (typeof pair !== 'object' || typeof pair[Symbol.iterator] !== 'function') { | ||
throw new TypeError('Each header pair must be iterable'); | ||
} | ||
pairs.push(Array.from(pair)); | ||
} | ||
for (const pair of pairs) { | ||
for (var _i2 = 0; _i2 < pairs.length; _i2++) { | ||
const pair = pairs[_i2]; | ||
if (pair.length !== 2) { | ||
throw new TypeError('Each header pair must be a name/value tuple'); | ||
} | ||
this.append(pair[0], pair[1]); | ||
@@ -805,3 +835,6 @@ } | ||
// record<ByteString, ByteString> | ||
for (const key of Object.keys(init)) { | ||
var _arr = Object.keys(init); | ||
for (var _i3 = 0; _i3 < _arr.length; _i3++) { | ||
const key = _arr[_i3]; | ||
const value = init[key]; | ||
@@ -822,3 +855,2 @@ this.append(key, value); | ||
} | ||
/** | ||
@@ -830,4 +862,7 @@ * Return first header value given name | ||
*/ | ||
get(name) { | ||
const list = this[MAP][sanitizeName(name)]; | ||
if (!list) { | ||
@@ -839,3 +874,2 @@ return null; | ||
} | ||
/** | ||
@@ -847,12 +881,12 @@ * Iterate over all headers | ||
*/ | ||
forEach(callback) { | ||
let thisArg = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : undefined; | ||
forEach(callback, thisArg = undefined) { | ||
let pairs = getHeaderPairs(this); | ||
let i = 0; | ||
while (i < pairs.length) { | ||
var _pairs$i = pairs[i]; | ||
const name = _pairs$i[0], | ||
const _pairs$i = pairs[i], | ||
name = _pairs$i[0], | ||
value = _pairs$i[1]; | ||
callback.call(thisArg, value, name, this); | ||
@@ -863,3 +897,2 @@ pairs = getHeaderPairs(this); | ||
} | ||
/** | ||
@@ -871,6 +904,7 @@ * Overwrite header values given name | ||
*/ | ||
set(name, value) { | ||
this[MAP][sanitizeName(name)] = [sanitizeValue(value)]; | ||
} | ||
/** | ||
@@ -882,2 +916,4 @@ * Append a value onto existing header | ||
*/ | ||
append(name, value) { | ||
@@ -891,3 +927,2 @@ if (!this.has(name)) { | ||
} | ||
/** | ||
@@ -899,6 +934,7 @@ * Check for header name existence | ||
*/ | ||
has(name) { | ||
return !!this[MAP][sanitizeName(name)]; | ||
} | ||
/** | ||
@@ -909,6 +945,7 @@ * Delete all header values given name | ||
*/ | ||
delete(name) { | ||
delete this[MAP][sanitizeName(name)]; | ||
} | ||
/** | ||
@@ -919,6 +956,7 @@ * Return raw headers (non-spec api) | ||
*/ | ||
raw() { | ||
return this[MAP]; | ||
} | ||
/** | ||
@@ -929,6 +967,7 @@ * Get an iterator on keys. | ||
*/ | ||
keys() { | ||
return createHeadersIterator(this, 'key'); | ||
} | ||
/** | ||
@@ -939,6 +978,7 @@ * Get an iterator on values. | ||
*/ | ||
values() { | ||
return createHeadersIterator(this, 'value'); | ||
} | ||
/** | ||
@@ -951,8 +991,10 @@ * Get an iterator on entries. | ||
*/ | ||
[Symbol.iterator]() { | ||
return createHeadersIterator(this, 'key+value'); | ||
} | ||
} | ||
Headers.prototype.entries = Headers.prototype[Symbol.iterator]; | ||
Object.defineProperty(Headers.prototype, Symbol.toStringTag, { | ||
@@ -966,11 +1008,36 @@ value: 'HeadersPrototype', | ||
function getHeaderPairs(headers, kind) { | ||
if (kind === 'key') return Object.keys(headers[MAP]).sort().map(function (k) { | ||
return [k]; | ||
}); | ||
if (kind === 'key') return Object.keys(headers[MAP]).sort().map(k => [k]); | ||
const pairs = []; | ||
for (let key of Object.keys(headers[MAP]).sort()) { | ||
for (let value of headers[MAP][key]) { | ||
for (var _iterator2 = Object.keys(headers[MAP]).sort(), _isArray2 = Array.isArray(_iterator2), _i4 = 0, _iterator2 = _isArray2 ? _iterator2 : _iterator2[Symbol.iterator]();;) { | ||
var _ref2; | ||
if (_isArray2) { | ||
if (_i4 >= _iterator2.length) break; | ||
_ref2 = _iterator2[_i4++]; | ||
} else { | ||
_i4 = _iterator2.next(); | ||
if (_i4.done) break; | ||
_ref2 = _i4.value; | ||
} | ||
let key = _ref2; | ||
for (var _iterator3 = headers[MAP][key], _isArray3 = Array.isArray(_iterator3), _i5 = 0, _iterator3 = _isArray3 ? _iterator3 : _iterator3[Symbol.iterator]();;) { | ||
var _ref3; | ||
if (_isArray3) { | ||
if (_i5 >= _iterator3.length) break; | ||
_ref3 = _iterator3[_i5++]; | ||
} else { | ||
_i5 = _iterator3.next(); | ||
if (_i5.done) break; | ||
_ref3 = _i5.value; | ||
} | ||
let value = _ref3; | ||
pairs.push([key, value]); | ||
} | ||
} | ||
return pairs; | ||
@@ -998,9 +1065,9 @@ } | ||
var _INTERNAL = this[INTERNAL]; | ||
const target = _INTERNAL.target, | ||
kind = _INTERNAL.kind, | ||
index = _INTERNAL.index; | ||
const _this$INTERNAL = this[INTERNAL], | ||
target = _this$INTERNAL.target, | ||
kind = _this$INTERNAL.kind, | ||
index = _this$INTERNAL.index; | ||
const values = getHeaderPairs(target, kind); | ||
const len = values.length; | ||
if (index >= len) { | ||
@@ -1015,4 +1082,4 @@ return { | ||
this[INTERNAL].index = index + 1; | ||
let result; | ||
let result; | ||
if (kind === 'key') { | ||
@@ -1031,4 +1098,4 @@ result = pair[0]; | ||
} | ||
}, Object.getPrototypeOf(Object.getPrototypeOf([][Symbol.iterator]()))); | ||
Object.defineProperty(HeadersIteratorPrototype, Symbol.toStringTag, { | ||
@@ -1046,3 +1113,2 @@ value: 'HeadersIterator', | ||
*/ | ||
/** | ||
@@ -1054,9 +1120,6 @@ * Response class | ||
*/ | ||
class Response { | ||
constructor() { | ||
let body = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null; | ||
let opts = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; | ||
constructor(body = null, opts = {}) { | ||
Body.call(this, body, opts); | ||
this.url = opts.url; | ||
@@ -1067,3 +1130,2 @@ this.status = opts.status || 200; | ||
this.useElectronNet = opts.useElectronNet; | ||
Object.defineProperty(this, Symbol.toStringTag, { | ||
@@ -1076,10 +1138,10 @@ value: 'Response', | ||
} | ||
/** | ||
* Convenience property representing if the request ended normally | ||
*/ | ||
get ok() { | ||
return this.status >= 200 && this.status < 300; | ||
} | ||
/** | ||
@@ -1090,2 +1152,4 @@ * Clone this response | ||
*/ | ||
clone() { | ||
@@ -1101,6 +1165,5 @@ return new Response(clone(this), { | ||
} | ||
} | ||
Body.mixIn(Response.prototype); | ||
Object.defineProperty(Response.prototype, Symbol.toStringTag, { | ||
@@ -1118,5 +1181,3 @@ value: 'ResponsePrototype', | ||
*/ | ||
const PARSED_URL = Symbol('url'); | ||
/** | ||
@@ -1128,9 +1189,7 @@ * Request class | ||
*/ | ||
class Request { | ||
constructor(input) { | ||
let init = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; | ||
constructor(input, init = {}) { | ||
let parsedURL; // normalize input | ||
let parsedURL; | ||
// normalize input | ||
if (!(input instanceof Request)) { | ||
@@ -1146,2 +1205,3 @@ if (input && input.href) { | ||
} | ||
input = {}; | ||
@@ -1159,9 +1219,7 @@ } else { | ||
let inputBody = init.body != null ? init.body : input instanceof Request && input.body !== null ? clone(input) : null; | ||
Body.call(this, inputBody, { | ||
timeout: init.timeout || input.timeout || 0, | ||
size: init.size || input.size || 0 | ||
}); | ||
}); // fetch spec options | ||
// fetch spec options | ||
this.method = method.toUpperCase(); | ||
@@ -1172,5 +1230,4 @@ this.redirect = init.redirect || input.redirect || 'follow'; | ||
this.useElectronNet = init.useElectronNet !== undefined // have to do this instead of || because it can be set to false | ||
? init.useElectronNet : input.useElectronNet; | ||
? init.useElectronNet : input.useElectronNet; // istanbul ignore if | ||
// istanbul ignore if | ||
if (this.useElectronNet && !process.versions.electron) throw new Error('Cannot use Electron/net module on Node.js!'); | ||
@@ -1184,12 +1241,12 @@ | ||
const contentType = extractContentType(this); | ||
if (contentType !== null && !this.headers.has('Content-Type')) { | ||
this.headers.append('Content-Type', contentType); | ||
} | ||
} | ||
} // server only options | ||
// server only options | ||
this.follow = init.follow !== undefined ? init.follow : input.follow !== undefined ? input.follow : 20; | ||
this.counter = init.counter || input.counter || 0; | ||
this.session = init.session || input.session; | ||
this[PARSED_URL] = parsedURL; | ||
@@ -1207,3 +1264,2 @@ Object.defineProperty(this, Symbol.toStringTag, { | ||
} | ||
/** | ||
@@ -1214,9 +1270,10 @@ * Clone this request | ||
*/ | ||
clone() { | ||
return new Request(this); | ||
} | ||
} | ||
Body.mixIn(Request.prototype); | ||
Object.defineProperty(Request.prototype, Symbol.toStringTag, { | ||
@@ -1228,13 +1285,11 @@ value: 'RequestPrototype', | ||
}); | ||
function getNodeRequestOptions(request) { | ||
const parsedURL = request[PARSED_URL]; | ||
const headers = new Headers(request.headers); | ||
const headers = new Headers(request.headers); // fetch step 3 | ||
// fetch step 3 | ||
if (!headers.has('Accept')) { | ||
headers.set('Accept', '*/*'); | ||
} | ||
} // Basic fetch | ||
// Basic fetch | ||
if (!parsedURL.protocol || !parsedURL.hostname) { | ||
@@ -1246,11 +1301,14 @@ throw new TypeError('Only absolute URLs are supported'); | ||
throw new TypeError('Only HTTP(S) protocols are supported'); | ||
} | ||
} // HTTP-network-or-cache fetch steps 5-9 | ||
// HTTP-network-or-cache fetch steps 5-9 | ||
let contentLengthValue = null; | ||
if (request.body == null && /^(POST|PUT)$/i.test(request.method)) { | ||
contentLengthValue = '0'; | ||
} | ||
if (request.body != null) { | ||
const totalBytes = getTotalBytes(request); | ||
if (typeof totalBytes === 'number') { | ||
@@ -1260,2 +1318,3 @@ contentLengthValue = String(totalBytes); | ||
} | ||
if (contentLengthValue) { | ||
@@ -1265,10 +1324,10 @@ headers.set('Content-Length', contentLengthValue); | ||
request.chunkedEncoding = true; | ||
} | ||
} // HTTP-network-or-cache fetch step 12 | ||
// HTTP-network-or-cache fetch step 12 | ||
if (!headers.has('User-Agent')) { | ||
headers.set('User-Agent', `electron-fetch/1.0 ${request.useElectronNet ? 'electron' : 'node'} (+https://github.com/arantes555/electron-fetch)`); | ||
} | ||
} // HTTP-network-or-cache fetch step 16 | ||
// HTTP-network-or-cache fetch step 16 | ||
headers.set('Accept-Encoding', 'gzip,deflate'); | ||
@@ -1278,7 +1337,6 @@ | ||
headers.set('Connection', 'close'); | ||
} | ||
// HTTP-network fetch step 4 | ||
} // HTTP-network fetch step 4 | ||
// chunked encoding is handled by Node.js when not running in electron | ||
return Object.assign({}, parsedURL, { | ||
@@ -1295,12 +1353,9 @@ method: request.method, | ||
*/ | ||
let electron; // istanbul ignore else | ||
let electron; | ||
// istanbul ignore else | ||
if (process.versions['electron']) { | ||
electron = require('electron'); | ||
} | ||
const isReady = !electron || electron.app.isReady() ? Promise.resolve() : new Promise(function (resolve$$1) { | ||
return electron.app.once('ready', resolve$$1); | ||
}); | ||
const isReady = !electron || electron.app.isReady() ? Promise.resolve() : new Promise(resolve$$1 => electron.app.once('ready', resolve$$1)); | ||
/** | ||
@@ -1313,176 +1368,199 @@ * Fetch function | ||
*/ | ||
function fetch(url) { | ||
let opts = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; | ||
function fetch(url, opts = {}) { | ||
// wrap http.request into fetch | ||
return isReady.then(function () { | ||
return new Promise(function (resolve$$1, reject) { | ||
// build request object | ||
const request = new Request(url, opts); | ||
const options = getNodeRequestOptions(request); | ||
return isReady.then(() => new Promise((resolve$$1, reject) => { | ||
// build request object | ||
const request = new Request(url, opts); | ||
const options = getNodeRequestOptions(request); | ||
const send = request.useElectronNet ? electron.net.request : (options.protocol === 'https:' ? https : http).request; // http.request only support string as host header, this hack make custom host header possible | ||
const send = request.useElectronNet ? electron.net.request : (options.protocol === 'https:' ? https : http).request; | ||
if (options.headers.host) { | ||
options.headers.host = options.headers.host[0]; | ||
} // send request | ||
// http.request only support string as host header, this hack make custom host header possible | ||
if (options.headers.host) { | ||
options.headers.host = options.headers.host[0]; | ||
} | ||
// send request | ||
let headers; | ||
if (request.useElectronNet) { | ||
headers = options.headers; | ||
delete options.headers; | ||
options.session = opts.session || electron.session.defaultSession; // we have to use a persistent session here, because of https://github.com/electron/electron/issues/13587 | ||
} | ||
const req = send(options); | ||
if (request.useElectronNet) { | ||
for (let headerName in headers) { | ||
if (typeof headers[headerName] === 'string') req.setHeader(headerName, headers[headerName]);else { | ||
for (let headerValue of headers[headerName]) { | ||
req.setHeader(headerName, headerValue); | ||
let headers; | ||
if (request.useElectronNet) { | ||
headers = options.headers; | ||
delete options.headers; | ||
options.session = opts.session || electron.session.defaultSession; // we have to use a persistent session here, because of https://github.com/electron/electron/issues/13587 | ||
} | ||
const req = send(options); | ||
if (request.useElectronNet) { | ||
for (let headerName in headers) { | ||
if (typeof headers[headerName] === 'string') req.setHeader(headerName, headers[headerName]);else { | ||
for (var _iterator = headers[headerName], _isArray = Array.isArray(_iterator), _i = 0, _iterator = _isArray ? _iterator : _iterator[Symbol.iterator]();;) { | ||
var _ref; | ||
if (_isArray) { | ||
if (_i >= _iterator.length) break; | ||
_ref = _iterator[_i++]; | ||
} else { | ||
_i = _iterator.next(); | ||
if (_i.done) break; | ||
_ref = _i.value; | ||
} | ||
let headerValue = _ref; | ||
req.setHeader(headerName, headerValue); | ||
} | ||
} | ||
} | ||
let reqTimeout; | ||
} | ||
if (request.timeout) { | ||
reqTimeout = setTimeout(function () { | ||
req.abort(); | ||
reject(new FetchError(`network timeout at: ${request.url}`, 'request-timeout')); | ||
}, request.timeout); | ||
} | ||
let reqTimeout; | ||
if (request.useElectronNet) { | ||
// handle authenticating proxies | ||
req.on('login', function (authInfo, callback) { | ||
if (opts.user && opts.password) { | ||
callback(opts.user, opts.password); | ||
} else { | ||
req.abort(); | ||
reject(new FetchError(`login event received from ${authInfo.host} but no credentials provided`, 'proxy', { code: 'PROXY_AUTH_FAILED' })); | ||
} | ||
}); | ||
} | ||
if (request.timeout) { | ||
reqTimeout = setTimeout(() => { | ||
req.abort(); | ||
reject(new FetchError(`network timeout at: ${request.url}`, 'request-timeout')); | ||
}, request.timeout); | ||
} | ||
req.on('error', function (err) { | ||
clearTimeout(reqTimeout); | ||
reject(new FetchError(`request to ${request.url} failed, reason: ${err.message}`, 'system', err)); | ||
if (request.useElectronNet) { | ||
// handle authenticating proxies | ||
req.on('login', (authInfo, callback) => { | ||
if (opts.user && opts.password) { | ||
callback(opts.user, opts.password); | ||
} else { | ||
req.abort(); | ||
reject(new FetchError(`login event received from ${authInfo.host} but no credentials provided`, 'proxy', { | ||
code: 'PROXY_AUTH_FAILED' | ||
})); | ||
} | ||
}); | ||
} | ||
req.on('response', function (res) { | ||
clearTimeout(reqTimeout); | ||
req.on('error', err => { | ||
clearTimeout(reqTimeout); | ||
reject(new FetchError(`request to ${request.url} failed, reason: ${err.message}`, 'system', err)); | ||
}); | ||
req.on('response', res => { | ||
clearTimeout(reqTimeout); // handle redirect | ||
// handle redirect | ||
if (fetch.isRedirect(res.statusCode) && request.redirect !== 'manual') { | ||
if (request.redirect === 'error') { | ||
reject(new FetchError(`redirect mode is set to error: ${request.url}`, 'no-redirect')); | ||
return; | ||
} | ||
if (fetch.isRedirect(res.statusCode) && request.redirect !== 'manual') { | ||
if (request.redirect === 'error') { | ||
reject(new FetchError(`redirect mode is set to error: ${request.url}`, 'no-redirect')); | ||
return; | ||
} | ||
if (request.counter >= request.follow) { | ||
reject(new FetchError(`maximum redirect reached at: ${request.url}`, 'max-redirect')); | ||
return; | ||
} | ||
if (request.counter >= request.follow) { | ||
reject(new FetchError(`maximum redirect reached at: ${request.url}`, 'max-redirect')); | ||
return; | ||
} | ||
if (!res.headers.location) { | ||
reject(new FetchError(`redirect location header missing at: ${request.url}`, 'invalid-redirect')); | ||
return; | ||
} | ||
if (!res.headers.location) { | ||
reject(new FetchError(`redirect location header missing at: ${request.url}`, 'invalid-redirect')); | ||
return; | ||
} // per fetch spec, for POST request with 301/302 response, or any request with 303 response, use GET when following redirect | ||
// per fetch spec, for POST request with 301/302 response, or any request with 303 response, use GET when following redirect | ||
if (res.statusCode === 303 || (res.statusCode === 301 || res.statusCode === 302) && request.method === 'POST') { | ||
request.method = 'GET'; | ||
request.body = null; | ||
request.headers.delete('content-length'); | ||
} | ||
request.counter++; | ||
resolve$$1(fetch(resolve(request.url, res.headers.location), request)); | ||
return; | ||
if (res.statusCode === 303 || (res.statusCode === 301 || res.statusCode === 302) && request.method === 'POST') { | ||
request.method = 'GET'; | ||
request.body = null; | ||
request.headers.delete('content-length'); | ||
} | ||
// normalize location header for manual redirect mode | ||
const headers = new Headers(); | ||
for (const name of Object.keys(res.headers)) { | ||
if (Array.isArray(res.headers[name])) { | ||
for (const val of res.headers[name]) { | ||
headers.append(name, val); | ||
request.counter++; | ||
resolve$$1(fetch(resolve(request.url, res.headers.location), request)); | ||
return; | ||
} // normalize location header for manual redirect mode | ||
const headers = new Headers(); | ||
var _arr = Object.keys(res.headers); | ||
for (var _i2 = 0; _i2 < _arr.length; _i2++) { | ||
const name = _arr[_i2]; | ||
if (Array.isArray(res.headers[name])) { | ||
for (var _iterator2 = res.headers[name], _isArray2 = Array.isArray(_iterator2), _i3 = 0, _iterator2 = _isArray2 ? _iterator2 : _iterator2[Symbol.iterator]();;) { | ||
var _ref2; | ||
if (_isArray2) { | ||
if (_i3 >= _iterator2.length) break; | ||
_ref2 = _iterator2[_i3++]; | ||
} else { | ||
_i3 = _iterator2.next(); | ||
if (_i3.done) break; | ||
_ref2 = _i3.value; | ||
} | ||
} else { | ||
headers.append(name, res.headers[name]); | ||
const val = _ref2; | ||
headers.append(name, val); | ||
} | ||
} else { | ||
headers.append(name, res.headers[name]); | ||
} | ||
if (request.redirect === 'manual' && headers.has('location')) { | ||
headers.set('location', resolve(request.url, headers.get('location'))); | ||
} | ||
} | ||
// prepare response | ||
let body = new PassThrough(); | ||
res.on('error', function (err) { | ||
return body.emit('error', err); | ||
}); | ||
res.pipe(body); | ||
const responseOptions = { | ||
url: request.url, | ||
status: res.statusCode, | ||
statusText: res.statusMessage, | ||
headers: headers, | ||
size: request.size, | ||
timeout: request.timeout, | ||
useElectronNet: request.useElectronNet | ||
if (request.redirect === 'manual' && headers.has('location')) { | ||
headers.set('location', resolve(request.url, headers.get('location'))); | ||
} // prepare response | ||
// HTTP-network fetch step 16.1.2 | ||
};const codings = headers.get('Content-Encoding'); | ||
// HTTP-network fetch step 16.1.3: handle content codings | ||
let body = new PassThrough(); | ||
res.on('error', err => body.emit('error', err)); | ||
res.pipe(body); | ||
const responseOptions = { | ||
url: request.url, | ||
status: res.statusCode, | ||
statusText: res.statusMessage, | ||
headers: headers, | ||
size: request.size, | ||
timeout: request.timeout, | ||
useElectronNet: request.useElectronNet // HTTP-network fetch step 16.1.2 | ||
// in following scenarios we ignore compression support | ||
// 1. running on Electron/net module (it manages it for us) | ||
// 2. HEAD request | ||
// 3. no Content-Encoding header | ||
// 4. no content response (204) | ||
// 5. content not modified response (304) | ||
if (!request.useElectronNet && request.method !== 'HEAD' && codings !== null && res.statusCode !== 204 && res.statusCode !== 304) { | ||
// Be less strict when decoding compressed responses, since sometimes | ||
// servers send slightly invalid responses that are still accepted | ||
// by common browsers. | ||
// Always using Z_SYNC_FLUSH is what cURL does. | ||
const zlibOptions = { | ||
flush: Z_SYNC_FLUSH, | ||
finishFlush: Z_SYNC_FLUSH | ||
}; | ||
}; | ||
const codings = headers.get('Content-Encoding'); // HTTP-network fetch step 16.1.3: handle content codings | ||
// in following scenarios we ignore compression support | ||
// 1. running on Electron/net module (it manages it for us) | ||
// 2. HEAD request | ||
// 3. no Content-Encoding header | ||
// 4. no content response (204) | ||
// 5. content not modified response (304) | ||
if (codings === 'gzip' || codings === 'x-gzip') { | ||
// for gzip | ||
body = body.pipe(createGunzip(zlibOptions)); | ||
} else if (codings === 'deflate' || codings === 'x-deflate') { | ||
// for deflate | ||
// handle the infamous raw deflate response from old servers | ||
// a hack for old IIS and Apache servers | ||
const raw = res.pipe(new PassThrough()); | ||
return raw.once('data', function (chunk) { | ||
// see http://stackoverflow.com/questions/37519828 | ||
if ((chunk[0] & 0x0F) === 0x08) { | ||
body = body.pipe(createInflate(zlibOptions)); | ||
} else { | ||
body = body.pipe(createInflateRaw(zlibOptions)); | ||
} | ||
const response = new Response(body, responseOptions); | ||
resolve$$1(response); | ||
}); | ||
} | ||
if (!request.useElectronNet && request.method !== 'HEAD' && codings !== null && res.statusCode !== 204 && res.statusCode !== 304) { | ||
// Be less strict when decoding compressed responses, since sometimes | ||
// servers send slightly invalid responses that are still accepted | ||
// by common browsers. | ||
// Always using Z_SYNC_FLUSH is what cURL does. | ||
const zlibOptions = { | ||
flush: Z_SYNC_FLUSH, | ||
finishFlush: Z_SYNC_FLUSH | ||
}; | ||
if (codings === 'gzip' || codings === 'x-gzip') { | ||
// for gzip | ||
body = body.pipe(createGunzip(zlibOptions)); | ||
} else if (codings === 'deflate' || codings === 'x-deflate') { | ||
// for deflate | ||
// handle the infamous raw deflate response from old servers | ||
// a hack for old IIS and Apache servers | ||
const raw = res.pipe(new PassThrough()); | ||
return raw.once('data', chunk => { | ||
// see http://stackoverflow.com/questions/37519828 | ||
if ((chunk[0] & 0x0F) === 0x08) { | ||
body = body.pipe(createInflate(zlibOptions)); | ||
} else { | ||
body = body.pipe(createInflateRaw(zlibOptions)); | ||
} | ||
const response = new Response(body, responseOptions); | ||
resolve$$1(response); | ||
}); | ||
} | ||
} | ||
const response = new Response(body, responseOptions); | ||
resolve$$1(response); | ||
}); | ||
writeToStream(req, request); | ||
const response = new Response(body, responseOptions); | ||
resolve$$1(response); | ||
}); | ||
}); | ||
writeToStream(req, request); | ||
})); | ||
} | ||
/** | ||
@@ -1494,7 +1572,6 @@ * Redirect code matching | ||
*/ | ||
fetch.isRedirect = function (code) { | ||
return code === 301 || code === 302 || code === 303 || code === 307 || code === 308; | ||
}; | ||
fetch.isRedirect = code => code === 301 || code === 302 || code === 303 || code === 307 || code === 308; | ||
export default fetch; | ||
export { Headers, Request, Response, FetchError }; |
799
lib/index.js
@@ -7,2 +7,4 @@ 'use strict'; | ||
var https = require('https'); | ||
var zlib = require('zlib'); | ||
var encoding = require('encoding'); | ||
@@ -13,12 +15,8 @@ var Stream = require('stream'); | ||
var url = require('url'); | ||
var https = require('https'); | ||
var zlib = require('zlib'); | ||
// Based on https://github.com/tmpvar/jsdom/blob/aa85b2abf07766ff7bf5c1f6daafb3726f2f2db5/lib/jsdom/living/blob.js | ||
// (MIT licensed) | ||
const BUFFER = Symbol('buffer'); | ||
const TYPE = Symbol('type'); | ||
const CLOSED = Symbol('closed'); | ||
class Blob { | ||
@@ -32,9 +30,6 @@ constructor() { | ||
}); | ||
this[CLOSED] = false; | ||
this[TYPE] = ''; | ||
const blobParts = arguments[0]; | ||
const options = arguments[1]; | ||
const buffers = []; | ||
@@ -45,5 +40,7 @@ | ||
const length = Number(a.length); | ||
for (let i = 0; i < length; i++) { | ||
const element = a[i]; | ||
let buffer; | ||
if (element instanceof Buffer) { | ||
@@ -60,2 +57,3 @@ buffer = element; | ||
} | ||
buffers.push(buffer); | ||
@@ -66,4 +64,4 @@ } | ||
this[BUFFER] = Buffer.concat(buffers); | ||
let type = options && options.type !== undefined && String(options.type).toLowerCase(); | ||
let type = options && options.type !== undefined && String(options.type).toLowerCase(); | ||
if (type && !/[^\u0020-\u007E]/.test(type)) { | ||
@@ -88,6 +86,6 @@ this[TYPE] = type; | ||
const size = this.size; | ||
const start = arguments[0]; | ||
const end = arguments[1]; | ||
let relativeStart, relativeEnd; | ||
if (start === undefined) { | ||
@@ -100,2 +98,3 @@ relativeStart = 0; | ||
} | ||
if (end === undefined) { | ||
@@ -108,7 +107,9 @@ relativeEnd = size; | ||
} | ||
const span = Math.max(relativeEnd - relativeStart, 0); | ||
const buffer = this[BUFFER]; | ||
const slicedBuffer = buffer.slice(relativeStart, relativeStart + span); | ||
const blob = new Blob([], { type: arguments[2] }); | ||
const blob = new Blob([], { | ||
type: arguments[2] | ||
}); | ||
blob[BUFFER] = slicedBuffer; | ||
@@ -122,4 +123,4 @@ blob[CLOSED] = this[CLOSED]; | ||
} | ||
} | ||
Object.defineProperty(Blob.prototype, Symbol.toStringTag, { | ||
@@ -146,3 +147,2 @@ value: 'BlobPrototype', | ||
*/ | ||
const netErrorMap = { | ||
@@ -155,24 +155,25 @@ 'ERR_CONNECTION_REFUSED': 'ECONNREFUSED', | ||
}; | ||
function FetchError(message, type, systemError) { | ||
Error.call(this, message); | ||
const regex = /^.*net::(.*)/; | ||
if (regex.test(message)) { | ||
let errorCode = regex.exec(message)[1]; | ||
// istanbul ignore else | ||
let errorCode = regex.exec(message)[1]; // istanbul ignore else | ||
if (netErrorMap.hasOwnProperty(errorCode)) errorCode = netErrorMap[errorCode]; | ||
systemError = { code: errorCode }; | ||
systemError = { | ||
code: errorCode | ||
}; | ||
} | ||
this.message = message; | ||
this.type = type; | ||
this.type = type; // when err.type is `system`, err.code contains system error code | ||
// when err.type is `system`, err.code contains system error code | ||
if (systemError) { | ||
this.code = this.errno = systemError.code; | ||
} | ||
} // hide custom error implementation details from end-users | ||
// hide custom error implementation details from end-users | ||
Error.captureStackTrace(this, this.constructor); | ||
} | ||
FetchError.prototype = Object.create(Error.prototype); | ||
@@ -187,5 +188,3 @@ FetchError.prototype.constructor = FetchError; | ||
*/ | ||
const DISTURBED = Symbol('disturbed'); | ||
/** | ||
@@ -200,10 +199,7 @@ * Body class | ||
*/ | ||
function Body(body) { | ||
var _ref = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}, | ||
_ref$size = _ref.size; | ||
let size = _ref$size === undefined ? 0 : _ref$size; | ||
var _ref$timeout = _ref.timeout; | ||
let timeout = _ref$timeout === undefined ? 0 : _ref$timeout; | ||
function Body(body, { | ||
size = 0, | ||
timeout = 0 | ||
} = {}) { | ||
if (body == null) { | ||
@@ -217,2 +213,3 @@ // body is undefined or null | ||
} | ||
this.body = body; | ||
@@ -223,3 +220,2 @@ this[DISTURBED] = false; | ||
} | ||
Body.prototype = { | ||
@@ -236,5 +232,3 @@ get bodyUsed() { | ||
arrayBuffer() { | ||
return consumeBody.call(this).then(function (buf) { | ||
return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength); | ||
}); | ||
return consumeBody.call(this).then(buf => buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength)); | ||
}, | ||
@@ -249,11 +243,8 @@ | ||
let ct = this.headers && this.headers.get('content-type') || ''; | ||
return consumeBody.call(this).then(function (buf) { | ||
return Object.assign( | ||
// Prevent copying | ||
new Blob([], { | ||
type: ct.toLowerCase() | ||
}), { | ||
[BUFFER]: buf | ||
}); | ||
}); | ||
return consumeBody.call(this).then(buf => Object.assign( // Prevent copying | ||
new Blob([], { | ||
type: ct.toLowerCase() | ||
}), { | ||
[BUFFER]: buf | ||
})); | ||
}, | ||
@@ -267,5 +258,3 @@ | ||
json() { | ||
return consumeBody.call(this).then(function (buffer) { | ||
return JSON.parse(buffer.toString()); | ||
}); | ||
return consumeBody.call(this).then(buffer => JSON.parse(buffer.toString())); | ||
}, | ||
@@ -279,5 +268,3 @@ | ||
text() { | ||
return consumeBody.call(this).then(function (buffer) { | ||
return buffer.toString(); | ||
}); | ||
return consumeBody.call(this).then(buffer => buffer.toString()); | ||
}, | ||
@@ -301,7 +288,3 @@ | ||
textConverted() { | ||
var _this = this; | ||
return consumeBody.call(this).then(function (buffer) { | ||
return convertBody(buffer, _this.headers); | ||
}); | ||
return consumeBody.call(this).then(buffer => convertBody(buffer, this.headers)); | ||
} | ||
@@ -312,3 +295,16 @@ | ||
Body.mixIn = function (proto) { | ||
for (const name of Object.getOwnPropertyNames(Body.prototype)) { | ||
for (var _iterator = Object.getOwnPropertyNames(Body.prototype), _isArray = Array.isArray(_iterator), _i = 0, _iterator = _isArray ? _iterator : _iterator[Symbol.iterator]();;) { | ||
var _ref; | ||
if (_isArray) { | ||
if (_i >= _iterator.length) break; | ||
_ref = _iterator[_i++]; | ||
} else { | ||
_i = _iterator.next(); | ||
if (_i.done) break; | ||
_ref = _i.value; | ||
} | ||
const name = _ref; | ||
// istanbul ignore else | ||
@@ -321,3 +317,2 @@ if (!(name in proto)) { | ||
}; | ||
/** | ||
@@ -328,5 +323,5 @@ * Decode buffers into utf-8 string | ||
*/ | ||
function consumeBody() { | ||
var _this2 = this; | ||
if (this[DISTURBED]) { | ||
@@ -336,52 +331,48 @@ return Promise.reject(new Error(`body used already for: ${this.url}`)); | ||
this[DISTURBED] = true; | ||
this[DISTURBED] = true; // body is null | ||
// body is null | ||
if (this.body === null) { | ||
return Promise.resolve(Buffer.alloc(0)); | ||
} | ||
} // body is string | ||
// body is string | ||
if (typeof this.body === 'string') { | ||
return Promise.resolve(Buffer.from(this.body)); | ||
} | ||
} // body is blob | ||
// body is blob | ||
if (this.body instanceof Blob) { | ||
return Promise.resolve(this.body[BUFFER]); | ||
} | ||
} // body is buffer | ||
// body is buffer | ||
if (Buffer.isBuffer(this.body)) { | ||
return Promise.resolve(this.body); | ||
} | ||
} // istanbul ignore if: should never happen | ||
// istanbul ignore if: should never happen | ||
if (!(this.body instanceof Stream__default)) { | ||
return Promise.resolve(Buffer.alloc(0)); | ||
} | ||
} // body is stream | ||
// get ready to actually consume the body | ||
// body is stream | ||
// get ready to actually consume the body | ||
const accum = []; | ||
let accumBytes = 0; | ||
let abort = false; | ||
return new Promise((resolve, reject) => { | ||
let resTimeout; // allow timeout on slow response body | ||
return new Promise(function (resolve, reject) { | ||
let resTimeout; | ||
// allow timeout on slow response body | ||
if (_this2.timeout) { | ||
resTimeout = setTimeout(function () { | ||
if (this.timeout) { | ||
resTimeout = setTimeout(() => { | ||
abort = true; | ||
reject(new FetchError(`Response timeout while trying to fetch ${_this2.url} (over ${_this2.timeout}ms)`, 'body-timeout')); | ||
}, _this2.timeout); | ||
} | ||
reject(new FetchError(`Response timeout while trying to fetch ${this.url} (over ${this.timeout}ms)`, 'body-timeout')); | ||
}, this.timeout); | ||
} // handle stream error, such as incorrect content-encoding | ||
// handle stream error, such as incorrect content-encoding | ||
_this2.body.on('error', function (err) { | ||
reject(new FetchError(`Invalid response body while trying to fetch ${_this2.url}: ${err.message}`, 'system', err)); | ||
this.body.on('error', err => { | ||
reject(new FetchError(`Invalid response body while trying to fetch ${this.url}: ${err.message}`, 'system', err)); | ||
}); | ||
_this2.body.on('data', function (chunk) { | ||
this.body.on('data', chunk => { | ||
if (abort || chunk === null) { | ||
@@ -391,5 +382,5 @@ return; | ||
if (_this2.size && accumBytes + chunk.length > _this2.size) { | ||
if (this.size && accumBytes + chunk.length > this.size) { | ||
abort = true; | ||
reject(new FetchError(`content size at ${_this2.url} over limit: ${_this2.size}`, 'max-size')); | ||
reject(new FetchError(`content size at ${this.url} over limit: ${this.size}`, 'max-size')); | ||
return; | ||
@@ -401,4 +392,3 @@ } | ||
}); | ||
_this2.body.on('end', function () { | ||
this.body.on('end', () => { | ||
if (abort) { | ||
@@ -413,3 +403,2 @@ return; | ||
} | ||
/** | ||
@@ -423,21 +412,21 @@ * Detect buffer encoding and convert to target encoding | ||
*/ | ||
function convertBody(buffer, headers) { | ||
const ct = headers.get('content-type'); | ||
let charset = 'utf-8'; | ||
let res, str; | ||
let res, str; // header | ||
// header | ||
if (ct) { | ||
res = /charset=([^;]*)/i.exec(ct); | ||
} | ||
} // no charset in content type, peek at response body for at most 1024 bytes | ||
// no charset in content type, peek at response body for at most 1024 bytes | ||
str = buffer.slice(0, 1024).toString(); | ||
// html5 | ||
str = buffer.slice(0, 1024).toString(); // html5 | ||
if (!res && str) { | ||
res = /<meta.+?charset=(['"])(.+?)\1/i.exec(str); | ||
} | ||
} // html4 | ||
// html4 | ||
if (!res && str) { | ||
@@ -449,24 +438,22 @@ res = /<meta[\s]+?http-equiv=(['"])content-type\1[\s]+?content=(['"])(.+?)\2/i.exec(str); | ||
} | ||
} | ||
} // xml | ||
// xml | ||
if (!res && str) { | ||
res = /<\?xml.+?encoding=(['"])(.+?)\1/i.exec(str); | ||
} | ||
} // found charset | ||
// found charset | ||
if (res) { | ||
charset = res.pop(); | ||
charset = res.pop(); // prevent decode issues when sites use incorrect encoding | ||
// ref: https://hsivonen.fi/encoding-menu/ | ||
// prevent decode issues when sites use incorrect encoding | ||
// ref: https://hsivonen.fi/encoding-menu/ | ||
if (charset === 'gb2312' || charset === 'gbk') { | ||
charset = 'gb18030'; | ||
} | ||
} | ||
} // turn raw buffers into a single utf-8 buffer | ||
// turn raw buffers into a single utf-8 buffer | ||
return encoding.convert(buffer, 'UTF-8', charset).toString(); | ||
} | ||
/** | ||
@@ -478,13 +465,14 @@ * Clone body given Res/Req instance | ||
*/ | ||
function clone(instance) { | ||
let p1, p2; | ||
let body = instance.body; | ||
let body = instance.body; // don't allow cloning a used body | ||
// don't allow cloning a used body | ||
if (instance.bodyUsed) { | ||
throw new Error('cannot clone body after it is used'); | ||
} | ||
} // check that body is a stream and not form-data object | ||
// note: we can't clone the form-data object without having it as a dependency | ||
// check that body is a stream and not form-data object | ||
// note: we can't clone the form-data object without having it as a dependency | ||
if (body instanceof Stream__default && typeof body.getBoundary !== 'function') { | ||
@@ -495,4 +483,4 @@ // tee instance body | ||
body.pipe(p1); | ||
body.pipe(p2); | ||
// set instance body to teed body and return the other teed body | ||
body.pipe(p2); // set instance body to teed body and return the other teed body | ||
instance.body = p1; | ||
@@ -504,3 +492,2 @@ body = p2; | ||
} | ||
/** | ||
@@ -515,6 +502,5 @@ * Performs the operation "extract a `Content-Type` value from |object|" as | ||
*/ | ||
function extractContentType(instance) { | ||
const body = instance.body; | ||
// istanbul ignore if: Currently, because of a guard in Request, body | ||
const body = instance.body; // istanbul ignore if: Currently, because of a guard in Request, body | ||
// can never be null. Included here for completeness. | ||
@@ -543,8 +529,5 @@ | ||
} | ||
function getTotalBytes(instance) { | ||
const body = instance.body; | ||
const body = instance.body; // istanbul ignore if: included for completion | ||
// istanbul ignore if: included for completion | ||
if (body === null) { | ||
@@ -570,2 +553,3 @@ // body is null | ||
} | ||
return null; | ||
@@ -578,7 +562,5 @@ } else { | ||
} | ||
function writeToStream(dest, instance) { | ||
const body = instance.body; | ||
if (body === null) { | ||
@@ -604,2 +586,3 @@ // body is null | ||
} | ||
body.pipe(new Stream.PassThrough()) // I have to put a PassThrough because somehow, FormData streams are not eaten by electron/net | ||
@@ -645,23 +628,31 @@ .pipe(dest); | ||
} | ||
if (ch >= 65 && ch <= 90) { | ||
return true; | ||
} | ||
if (ch === 45) { | ||
return true; | ||
} | ||
if (ch >= 48 && ch <= 57) { | ||
return true; | ||
} | ||
if (ch === 34 || ch === 40 || ch === 41 || ch === 44) { | ||
return false; | ||
} | ||
if (ch >= 33 && ch <= 46) { | ||
return true; | ||
} | ||
if (ch === 124 || ch === 126) { | ||
return true; | ||
} | ||
return false; | ||
} | ||
// istanbul ignore next | ||
} // istanbul ignore next | ||
function checkIsHttpToken(val) { | ||
@@ -671,6 +662,9 @@ if (typeof val !== 'string' || val.length === 0) { | ||
} | ||
if (!isValidTokenChar(val.charCodeAt(0))) { | ||
return false; | ||
} | ||
const len = val.length; | ||
if (len > 1) { | ||
@@ -680,2 +674,3 @@ if (!isValidTokenChar(val.charCodeAt(1))) { | ||
} | ||
if (len > 2) { | ||
@@ -685,2 +680,3 @@ if (!isValidTokenChar(val.charCodeAt(2))) { | ||
} | ||
if (len > 3) { | ||
@@ -690,2 +686,3 @@ if (!isValidTokenChar(val.charCodeAt(3))) { | ||
} | ||
for (let i = 4; i < len; i++) { | ||
@@ -699,5 +696,5 @@ if (!isValidTokenChar(val.charCodeAt(i))) { | ||
} | ||
return true; | ||
} | ||
/** | ||
@@ -714,27 +711,39 @@ * True if val contains an invalid field-vchar | ||
// istanbul ignore next | ||
function checkInvalidHeaderChar(val) { | ||
val += ''; | ||
if (val.length < 1) { | ||
return false; | ||
} | ||
let c = val.charCodeAt(0); | ||
if (c <= 31 && c !== 9 || c > 255 || c === 127) { | ||
return true; | ||
} | ||
if (val.length < 2) { | ||
return false; | ||
} | ||
c = val.charCodeAt(1); | ||
if (c <= 31 && c !== 9 || c > 255 || c === 127) { | ||
return true; | ||
} | ||
if (val.length < 3) { | ||
return false; | ||
} | ||
c = val.charCodeAt(2); | ||
if (c <= 31 && c !== 9 || c > 255 || c === 127) { | ||
return true; | ||
} | ||
for (let i = 3; i < val.length; ++i) { | ||
c = val.charCodeAt(i); | ||
if (c <= 31 && c !== 9 || c > 255 || c === 127) { | ||
@@ -744,2 +753,3 @@ return true; | ||
} | ||
return false; | ||
@@ -756,5 +766,7 @@ } | ||
name += ''; | ||
if (!checkIsHttpToken(name)) { | ||
throw new TypeError(`${name} is not a legal HTTP header name`); | ||
} | ||
return name.toLowerCase(); | ||
@@ -765,5 +777,7 @@ } | ||
value += ''; | ||
if (checkInvalidHeaderChar(value)) { | ||
throw new TypeError(`${value} is not a legal HTTP header value`); | ||
} | ||
return value; | ||
@@ -779,30 +793,46 @@ } | ||
*/ | ||
constructor() { | ||
let init = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : undefined; | ||
constructor(init = undefined) { | ||
this[MAP] = Object.create(null); // We don't worry about converting prop to ByteString here as append() | ||
// will handle it. | ||
this[MAP] = Object.create(null); | ||
// We don't worry about converting prop to ByteString here as append() | ||
// will handle it. | ||
if (init == null) ; else if (typeof init === 'object') { | ||
const method = init[Symbol.iterator]; | ||
if (method != null) { | ||
if (typeof method !== 'function') { | ||
throw new TypeError('Header pairs must be iterable'); | ||
} | ||
} // sequence<sequence<ByteString>> | ||
// Note: per spec we have to first exhaust the lists then process them | ||
// sequence<sequence<ByteString>> | ||
// Note: per spec we have to first exhaust the lists then process them | ||
const pairs = []; | ||
for (const pair of init) { | ||
for (var _iterator = init, _isArray = Array.isArray(_iterator), _i = 0, _iterator = _isArray ? _iterator : _iterator[Symbol.iterator]();;) { | ||
var _ref; | ||
if (_isArray) { | ||
if (_i >= _iterator.length) break; | ||
_ref = _iterator[_i++]; | ||
} else { | ||
_i = _iterator.next(); | ||
if (_i.done) break; | ||
_ref = _i.value; | ||
} | ||
const pair = _ref; | ||
if (typeof pair !== 'object' || typeof pair[Symbol.iterator] !== 'function') { | ||
throw new TypeError('Each header pair must be iterable'); | ||
} | ||
pairs.push(Array.from(pair)); | ||
} | ||
for (const pair of pairs) { | ||
for (var _i2 = 0; _i2 < pairs.length; _i2++) { | ||
const pair = pairs[_i2]; | ||
if (pair.length !== 2) { | ||
throw new TypeError('Each header pair must be a name/value tuple'); | ||
} | ||
this.append(pair[0], pair[1]); | ||
@@ -812,3 +842,6 @@ } | ||
// record<ByteString, ByteString> | ||
for (const key of Object.keys(init)) { | ||
var _arr = Object.keys(init); | ||
for (var _i3 = 0; _i3 < _arr.length; _i3++) { | ||
const key = _arr[_i3]; | ||
const value = init[key]; | ||
@@ -829,3 +862,2 @@ this.append(key, value); | ||
} | ||
/** | ||
@@ -837,4 +869,7 @@ * Return first header value given name | ||
*/ | ||
get(name) { | ||
const list = this[MAP][sanitizeName(name)]; | ||
if (!list) { | ||
@@ -846,3 +881,2 @@ return null; | ||
} | ||
/** | ||
@@ -854,12 +888,12 @@ * Iterate over all headers | ||
*/ | ||
forEach(callback) { | ||
let thisArg = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : undefined; | ||
forEach(callback, thisArg = undefined) { | ||
let pairs = getHeaderPairs(this); | ||
let i = 0; | ||
while (i < pairs.length) { | ||
var _pairs$i = pairs[i]; | ||
const name = _pairs$i[0], | ||
const _pairs$i = pairs[i], | ||
name = _pairs$i[0], | ||
value = _pairs$i[1]; | ||
callback.call(thisArg, value, name, this); | ||
@@ -870,3 +904,2 @@ pairs = getHeaderPairs(this); | ||
} | ||
/** | ||
@@ -878,6 +911,7 @@ * Overwrite header values given name | ||
*/ | ||
set(name, value) { | ||
this[MAP][sanitizeName(name)] = [sanitizeValue(value)]; | ||
} | ||
/** | ||
@@ -889,2 +923,4 @@ * Append a value onto existing header | ||
*/ | ||
append(name, value) { | ||
@@ -898,3 +934,2 @@ if (!this.has(name)) { | ||
} | ||
/** | ||
@@ -906,6 +941,7 @@ * Check for header name existence | ||
*/ | ||
has(name) { | ||
return !!this[MAP][sanitizeName(name)]; | ||
} | ||
/** | ||
@@ -916,6 +952,7 @@ * Delete all header values given name | ||
*/ | ||
delete(name) { | ||
delete this[MAP][sanitizeName(name)]; | ||
} | ||
/** | ||
@@ -926,6 +963,7 @@ * Return raw headers (non-spec api) | ||
*/ | ||
raw() { | ||
return this[MAP]; | ||
} | ||
/** | ||
@@ -936,6 +974,7 @@ * Get an iterator on keys. | ||
*/ | ||
keys() { | ||
return createHeadersIterator(this, 'key'); | ||
} | ||
/** | ||
@@ -946,6 +985,7 @@ * Get an iterator on values. | ||
*/ | ||
values() { | ||
return createHeadersIterator(this, 'value'); | ||
} | ||
/** | ||
@@ -958,8 +998,10 @@ * Get an iterator on entries. | ||
*/ | ||
[Symbol.iterator]() { | ||
return createHeadersIterator(this, 'key+value'); | ||
} | ||
} | ||
Headers.prototype.entries = Headers.prototype[Symbol.iterator]; | ||
Object.defineProperty(Headers.prototype, Symbol.toStringTag, { | ||
@@ -973,11 +1015,36 @@ value: 'HeadersPrototype', | ||
function getHeaderPairs(headers, kind) { | ||
if (kind === 'key') return Object.keys(headers[MAP]).sort().map(function (k) { | ||
return [k]; | ||
}); | ||
if (kind === 'key') return Object.keys(headers[MAP]).sort().map(k => [k]); | ||
const pairs = []; | ||
for (let key of Object.keys(headers[MAP]).sort()) { | ||
for (let value of headers[MAP][key]) { | ||
for (var _iterator2 = Object.keys(headers[MAP]).sort(), _isArray2 = Array.isArray(_iterator2), _i4 = 0, _iterator2 = _isArray2 ? _iterator2 : _iterator2[Symbol.iterator]();;) { | ||
var _ref2; | ||
if (_isArray2) { | ||
if (_i4 >= _iterator2.length) break; | ||
_ref2 = _iterator2[_i4++]; | ||
} else { | ||
_i4 = _iterator2.next(); | ||
if (_i4.done) break; | ||
_ref2 = _i4.value; | ||
} | ||
let key = _ref2; | ||
for (var _iterator3 = headers[MAP][key], _isArray3 = Array.isArray(_iterator3), _i5 = 0, _iterator3 = _isArray3 ? _iterator3 : _iterator3[Symbol.iterator]();;) { | ||
var _ref3; | ||
if (_isArray3) { | ||
if (_i5 >= _iterator3.length) break; | ||
_ref3 = _iterator3[_i5++]; | ||
} else { | ||
_i5 = _iterator3.next(); | ||
if (_i5.done) break; | ||
_ref3 = _i5.value; | ||
} | ||
let value = _ref3; | ||
pairs.push([key, value]); | ||
} | ||
} | ||
return pairs; | ||
@@ -1005,9 +1072,9 @@ } | ||
var _INTERNAL = this[INTERNAL]; | ||
const target = _INTERNAL.target, | ||
kind = _INTERNAL.kind, | ||
index = _INTERNAL.index; | ||
const _this$INTERNAL = this[INTERNAL], | ||
target = _this$INTERNAL.target, | ||
kind = _this$INTERNAL.kind, | ||
index = _this$INTERNAL.index; | ||
const values = getHeaderPairs(target, kind); | ||
const len = values.length; | ||
if (index >= len) { | ||
@@ -1022,4 +1089,4 @@ return { | ||
this[INTERNAL].index = index + 1; | ||
let result; | ||
let result; | ||
if (kind === 'key') { | ||
@@ -1038,4 +1105,4 @@ result = pair[0]; | ||
} | ||
}, Object.getPrototypeOf(Object.getPrototypeOf([][Symbol.iterator]()))); | ||
Object.defineProperty(HeadersIteratorPrototype, Symbol.toStringTag, { | ||
@@ -1053,3 +1120,2 @@ value: 'HeadersIterator', | ||
*/ | ||
/** | ||
@@ -1061,9 +1127,6 @@ * Response class | ||
*/ | ||
class Response { | ||
constructor() { | ||
let body = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null; | ||
let opts = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; | ||
constructor(body = null, opts = {}) { | ||
Body.call(this, body, opts); | ||
this.url = opts.url; | ||
@@ -1074,3 +1137,2 @@ this.status = opts.status || 200; | ||
this.useElectronNet = opts.useElectronNet; | ||
Object.defineProperty(this, Symbol.toStringTag, { | ||
@@ -1083,10 +1145,10 @@ value: 'Response', | ||
} | ||
/** | ||
* Convenience property representing if the request ended normally | ||
*/ | ||
get ok() { | ||
return this.status >= 200 && this.status < 300; | ||
} | ||
/** | ||
@@ -1097,2 +1159,4 @@ * Clone this response | ||
*/ | ||
clone() { | ||
@@ -1108,6 +1172,5 @@ return new Response(clone(this), { | ||
} | ||
} | ||
Body.mixIn(Response.prototype); | ||
Object.defineProperty(Response.prototype, Symbol.toStringTag, { | ||
@@ -1125,5 +1188,3 @@ value: 'ResponsePrototype', | ||
*/ | ||
const PARSED_URL = Symbol('url'); | ||
/** | ||
@@ -1135,9 +1196,7 @@ * Request class | ||
*/ | ||
class Request { | ||
constructor(input) { | ||
let init = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; | ||
constructor(input, init = {}) { | ||
let parsedURL; // normalize input | ||
let parsedURL; | ||
// normalize input | ||
if (!(input instanceof Request)) { | ||
@@ -1153,2 +1212,3 @@ if (input && input.href) { | ||
} | ||
input = {}; | ||
@@ -1166,9 +1226,7 @@ } else { | ||
let inputBody = init.body != null ? init.body : input instanceof Request && input.body !== null ? clone(input) : null; | ||
Body.call(this, inputBody, { | ||
timeout: init.timeout || input.timeout || 0, | ||
size: init.size || input.size || 0 | ||
}); | ||
}); // fetch spec options | ||
// fetch spec options | ||
this.method = method.toUpperCase(); | ||
@@ -1179,5 +1237,4 @@ this.redirect = init.redirect || input.redirect || 'follow'; | ||
this.useElectronNet = init.useElectronNet !== undefined // have to do this instead of || because it can be set to false | ||
? init.useElectronNet : input.useElectronNet; | ||
? init.useElectronNet : input.useElectronNet; // istanbul ignore if | ||
// istanbul ignore if | ||
if (this.useElectronNet && !process.versions.electron) throw new Error('Cannot use Electron/net module on Node.js!'); | ||
@@ -1191,12 +1248,12 @@ | ||
const contentType = extractContentType(this); | ||
if (contentType !== null && !this.headers.has('Content-Type')) { | ||
this.headers.append('Content-Type', contentType); | ||
} | ||
} | ||
} // server only options | ||
// server only options | ||
this.follow = init.follow !== undefined ? init.follow : input.follow !== undefined ? input.follow : 20; | ||
this.counter = init.counter || input.counter || 0; | ||
this.session = init.session || input.session; | ||
this[PARSED_URL] = parsedURL; | ||
@@ -1214,3 +1271,2 @@ Object.defineProperty(this, Symbol.toStringTag, { | ||
} | ||
/** | ||
@@ -1221,9 +1277,10 @@ * Clone this request | ||
*/ | ||
clone() { | ||
return new Request(this); | ||
} | ||
} | ||
Body.mixIn(Request.prototype); | ||
Object.defineProperty(Request.prototype, Symbol.toStringTag, { | ||
@@ -1235,13 +1292,11 @@ value: 'RequestPrototype', | ||
}); | ||
function getNodeRequestOptions(request) { | ||
const parsedURL = request[PARSED_URL]; | ||
const headers = new Headers(request.headers); | ||
const headers = new Headers(request.headers); // fetch step 3 | ||
// fetch step 3 | ||
if (!headers.has('Accept')) { | ||
headers.set('Accept', '*/*'); | ||
} | ||
} // Basic fetch | ||
// Basic fetch | ||
if (!parsedURL.protocol || !parsedURL.hostname) { | ||
@@ -1253,11 +1308,14 @@ throw new TypeError('Only absolute URLs are supported'); | ||
throw new TypeError('Only HTTP(S) protocols are supported'); | ||
} | ||
} // HTTP-network-or-cache fetch steps 5-9 | ||
// HTTP-network-or-cache fetch steps 5-9 | ||
let contentLengthValue = null; | ||
if (request.body == null && /^(POST|PUT)$/i.test(request.method)) { | ||
contentLengthValue = '0'; | ||
} | ||
if (request.body != null) { | ||
const totalBytes = getTotalBytes(request); | ||
if (typeof totalBytes === 'number') { | ||
@@ -1267,2 +1325,3 @@ contentLengthValue = String(totalBytes); | ||
} | ||
if (contentLengthValue) { | ||
@@ -1272,10 +1331,10 @@ headers.set('Content-Length', contentLengthValue); | ||
request.chunkedEncoding = true; | ||
} | ||
} // HTTP-network-or-cache fetch step 12 | ||
// HTTP-network-or-cache fetch step 12 | ||
if (!headers.has('User-Agent')) { | ||
headers.set('User-Agent', `electron-fetch/1.0 ${request.useElectronNet ? 'electron' : 'node'} (+https://github.com/arantes555/electron-fetch)`); | ||
} | ||
} // HTTP-network-or-cache fetch step 16 | ||
// HTTP-network-or-cache fetch step 16 | ||
headers.set('Accept-Encoding', 'gzip,deflate'); | ||
@@ -1285,7 +1344,6 @@ | ||
headers.set('Connection', 'close'); | ||
} | ||
// HTTP-network fetch step 4 | ||
} // HTTP-network fetch step 4 | ||
// chunked encoding is handled by Node.js when not running in electron | ||
return Object.assign({}, parsedURL, { | ||
@@ -1302,12 +1360,9 @@ method: request.method, | ||
*/ | ||
let electron; // istanbul ignore else | ||
let electron; | ||
// istanbul ignore else | ||
if (process.versions['electron']) { | ||
electron = require('electron'); | ||
} | ||
const isReady = !electron || electron.app.isReady() ? Promise.resolve() : new Promise(function (resolve) { | ||
return electron.app.once('ready', resolve); | ||
}); | ||
const isReady = !electron || electron.app.isReady() ? Promise.resolve() : new Promise(resolve => electron.app.once('ready', resolve)); | ||
/** | ||
@@ -1320,176 +1375,199 @@ * Fetch function | ||
*/ | ||
function fetch(url$$1) { | ||
let opts = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; | ||
function fetch(url$$1, opts = {}) { | ||
// wrap http.request into fetch | ||
return isReady.then(function () { | ||
return new Promise(function (resolve, reject) { | ||
// build request object | ||
const request = new Request(url$$1, opts); | ||
const options = getNodeRequestOptions(request); | ||
return isReady.then(() => new Promise((resolve, reject) => { | ||
// build request object | ||
const request = new Request(url$$1, opts); | ||
const options = getNodeRequestOptions(request); | ||
const send = request.useElectronNet ? electron.net.request : (options.protocol === 'https:' ? https : http).request; // http.request only support string as host header, this hack make custom host header possible | ||
const send = request.useElectronNet ? electron.net.request : (options.protocol === 'https:' ? https : http).request; | ||
if (options.headers.host) { | ||
options.headers.host = options.headers.host[0]; | ||
} // send request | ||
// http.request only support string as host header, this hack make custom host header possible | ||
if (options.headers.host) { | ||
options.headers.host = options.headers.host[0]; | ||
} | ||
// send request | ||
let headers; | ||
if (request.useElectronNet) { | ||
headers = options.headers; | ||
delete options.headers; | ||
options.session = opts.session || electron.session.defaultSession; // we have to use a persistent session here, because of https://github.com/electron/electron/issues/13587 | ||
} | ||
const req = send(options); | ||
if (request.useElectronNet) { | ||
for (let headerName in headers) { | ||
if (typeof headers[headerName] === 'string') req.setHeader(headerName, headers[headerName]);else { | ||
for (let headerValue of headers[headerName]) { | ||
req.setHeader(headerName, headerValue); | ||
let headers; | ||
if (request.useElectronNet) { | ||
headers = options.headers; | ||
delete options.headers; | ||
options.session = opts.session || electron.session.defaultSession; // we have to use a persistent session here, because of https://github.com/electron/electron/issues/13587 | ||
} | ||
const req = send(options); | ||
if (request.useElectronNet) { | ||
for (let headerName in headers) { | ||
if (typeof headers[headerName] === 'string') req.setHeader(headerName, headers[headerName]);else { | ||
for (var _iterator = headers[headerName], _isArray = Array.isArray(_iterator), _i = 0, _iterator = _isArray ? _iterator : _iterator[Symbol.iterator]();;) { | ||
var _ref; | ||
if (_isArray) { | ||
if (_i >= _iterator.length) break; | ||
_ref = _iterator[_i++]; | ||
} else { | ||
_i = _iterator.next(); | ||
if (_i.done) break; | ||
_ref = _i.value; | ||
} | ||
let headerValue = _ref; | ||
req.setHeader(headerName, headerValue); | ||
} | ||
} | ||
} | ||
let reqTimeout; | ||
} | ||
if (request.timeout) { | ||
reqTimeout = setTimeout(function () { | ||
req.abort(); | ||
reject(new FetchError(`network timeout at: ${request.url}`, 'request-timeout')); | ||
}, request.timeout); | ||
} | ||
let reqTimeout; | ||
if (request.useElectronNet) { | ||
// handle authenticating proxies | ||
req.on('login', function (authInfo, callback) { | ||
if (opts.user && opts.password) { | ||
callback(opts.user, opts.password); | ||
} else { | ||
req.abort(); | ||
reject(new FetchError(`login event received from ${authInfo.host} but no credentials provided`, 'proxy', { code: 'PROXY_AUTH_FAILED' })); | ||
} | ||
}); | ||
} | ||
if (request.timeout) { | ||
reqTimeout = setTimeout(() => { | ||
req.abort(); | ||
reject(new FetchError(`network timeout at: ${request.url}`, 'request-timeout')); | ||
}, request.timeout); | ||
} | ||
req.on('error', function (err) { | ||
clearTimeout(reqTimeout); | ||
reject(new FetchError(`request to ${request.url} failed, reason: ${err.message}`, 'system', err)); | ||
if (request.useElectronNet) { | ||
// handle authenticating proxies | ||
req.on('login', (authInfo, callback) => { | ||
if (opts.user && opts.password) { | ||
callback(opts.user, opts.password); | ||
} else { | ||
req.abort(); | ||
reject(new FetchError(`login event received from ${authInfo.host} but no credentials provided`, 'proxy', { | ||
code: 'PROXY_AUTH_FAILED' | ||
})); | ||
} | ||
}); | ||
} | ||
req.on('response', function (res) { | ||
clearTimeout(reqTimeout); | ||
req.on('error', err => { | ||
clearTimeout(reqTimeout); | ||
reject(new FetchError(`request to ${request.url} failed, reason: ${err.message}`, 'system', err)); | ||
}); | ||
req.on('response', res => { | ||
clearTimeout(reqTimeout); // handle redirect | ||
// handle redirect | ||
if (fetch.isRedirect(res.statusCode) && request.redirect !== 'manual') { | ||
if (request.redirect === 'error') { | ||
reject(new FetchError(`redirect mode is set to error: ${request.url}`, 'no-redirect')); | ||
return; | ||
} | ||
if (fetch.isRedirect(res.statusCode) && request.redirect !== 'manual') { | ||
if (request.redirect === 'error') { | ||
reject(new FetchError(`redirect mode is set to error: ${request.url}`, 'no-redirect')); | ||
return; | ||
} | ||
if (request.counter >= request.follow) { | ||
reject(new FetchError(`maximum redirect reached at: ${request.url}`, 'max-redirect')); | ||
return; | ||
} | ||
if (request.counter >= request.follow) { | ||
reject(new FetchError(`maximum redirect reached at: ${request.url}`, 'max-redirect')); | ||
return; | ||
} | ||
if (!res.headers.location) { | ||
reject(new FetchError(`redirect location header missing at: ${request.url}`, 'invalid-redirect')); | ||
return; | ||
} | ||
if (!res.headers.location) { | ||
reject(new FetchError(`redirect location header missing at: ${request.url}`, 'invalid-redirect')); | ||
return; | ||
} // per fetch spec, for POST request with 301/302 response, or any request with 303 response, use GET when following redirect | ||
// per fetch spec, for POST request with 301/302 response, or any request with 303 response, use GET when following redirect | ||
if (res.statusCode === 303 || (res.statusCode === 301 || res.statusCode === 302) && request.method === 'POST') { | ||
request.method = 'GET'; | ||
request.body = null; | ||
request.headers.delete('content-length'); | ||
} | ||
request.counter++; | ||
resolve(fetch(url.resolve(request.url, res.headers.location), request)); | ||
return; | ||
if (res.statusCode === 303 || (res.statusCode === 301 || res.statusCode === 302) && request.method === 'POST') { | ||
request.method = 'GET'; | ||
request.body = null; | ||
request.headers.delete('content-length'); | ||
} | ||
// normalize location header for manual redirect mode | ||
const headers = new Headers(); | ||
for (const name of Object.keys(res.headers)) { | ||
if (Array.isArray(res.headers[name])) { | ||
for (const val of res.headers[name]) { | ||
headers.append(name, val); | ||
request.counter++; | ||
resolve(fetch(url.resolve(request.url, res.headers.location), request)); | ||
return; | ||
} // normalize location header for manual redirect mode | ||
const headers = new Headers(); | ||
var _arr = Object.keys(res.headers); | ||
for (var _i2 = 0; _i2 < _arr.length; _i2++) { | ||
const name = _arr[_i2]; | ||
if (Array.isArray(res.headers[name])) { | ||
for (var _iterator2 = res.headers[name], _isArray2 = Array.isArray(_iterator2), _i3 = 0, _iterator2 = _isArray2 ? _iterator2 : _iterator2[Symbol.iterator]();;) { | ||
var _ref2; | ||
if (_isArray2) { | ||
if (_i3 >= _iterator2.length) break; | ||
_ref2 = _iterator2[_i3++]; | ||
} else { | ||
_i3 = _iterator2.next(); | ||
if (_i3.done) break; | ||
_ref2 = _i3.value; | ||
} | ||
} else { | ||
headers.append(name, res.headers[name]); | ||
const val = _ref2; | ||
headers.append(name, val); | ||
} | ||
} else { | ||
headers.append(name, res.headers[name]); | ||
} | ||
if (request.redirect === 'manual' && headers.has('location')) { | ||
headers.set('location', url.resolve(request.url, headers.get('location'))); | ||
} | ||
} | ||
// prepare response | ||
let body = new Stream.PassThrough(); | ||
res.on('error', function (err) { | ||
return body.emit('error', err); | ||
}); | ||
res.pipe(body); | ||
const responseOptions = { | ||
url: request.url, | ||
status: res.statusCode, | ||
statusText: res.statusMessage, | ||
headers: headers, | ||
size: request.size, | ||
timeout: request.timeout, | ||
useElectronNet: request.useElectronNet | ||
if (request.redirect === 'manual' && headers.has('location')) { | ||
headers.set('location', url.resolve(request.url, headers.get('location'))); | ||
} // prepare response | ||
// HTTP-network fetch step 16.1.2 | ||
};const codings = headers.get('Content-Encoding'); | ||
// HTTP-network fetch step 16.1.3: handle content codings | ||
let body = new Stream.PassThrough(); | ||
res.on('error', err => body.emit('error', err)); | ||
res.pipe(body); | ||
const responseOptions = { | ||
url: request.url, | ||
status: res.statusCode, | ||
statusText: res.statusMessage, | ||
headers: headers, | ||
size: request.size, | ||
timeout: request.timeout, | ||
useElectronNet: request.useElectronNet // HTTP-network fetch step 16.1.2 | ||
// in following scenarios we ignore compression support | ||
// 1. running on Electron/net module (it manages it for us) | ||
// 2. HEAD request | ||
// 3. no Content-Encoding header | ||
// 4. no content response (204) | ||
// 5. content not modified response (304) | ||
if (!request.useElectronNet && request.method !== 'HEAD' && codings !== null && res.statusCode !== 204 && res.statusCode !== 304) { | ||
// Be less strict when decoding compressed responses, since sometimes | ||
// servers send slightly invalid responses that are still accepted | ||
// by common browsers. | ||
// Always using Z_SYNC_FLUSH is what cURL does. | ||
const zlibOptions = { | ||
flush: zlib.Z_SYNC_FLUSH, | ||
finishFlush: zlib.Z_SYNC_FLUSH | ||
}; | ||
}; | ||
const codings = headers.get('Content-Encoding'); // HTTP-network fetch step 16.1.3: handle content codings | ||
// in following scenarios we ignore compression support | ||
// 1. running on Electron/net module (it manages it for us) | ||
// 2. HEAD request | ||
// 3. no Content-Encoding header | ||
// 4. no content response (204) | ||
// 5. content not modified response (304) | ||
if (codings === 'gzip' || codings === 'x-gzip') { | ||
// for gzip | ||
body = body.pipe(zlib.createGunzip(zlibOptions)); | ||
} else if (codings === 'deflate' || codings === 'x-deflate') { | ||
// for deflate | ||
// handle the infamous raw deflate response from old servers | ||
// a hack for old IIS and Apache servers | ||
const raw = res.pipe(new Stream.PassThrough()); | ||
return raw.once('data', function (chunk) { | ||
// see http://stackoverflow.com/questions/37519828 | ||
if ((chunk[0] & 0x0F) === 0x08) { | ||
body = body.pipe(zlib.createInflate(zlibOptions)); | ||
} else { | ||
body = body.pipe(zlib.createInflateRaw(zlibOptions)); | ||
} | ||
const response = new Response(body, responseOptions); | ||
resolve(response); | ||
}); | ||
} | ||
if (!request.useElectronNet && request.method !== 'HEAD' && codings !== null && res.statusCode !== 204 && res.statusCode !== 304) { | ||
// Be less strict when decoding compressed responses, since sometimes | ||
// servers send slightly invalid responses that are still accepted | ||
// by common browsers. | ||
// Always using Z_SYNC_FLUSH is what cURL does. | ||
const zlibOptions = { | ||
flush: zlib.Z_SYNC_FLUSH, | ||
finishFlush: zlib.Z_SYNC_FLUSH | ||
}; | ||
if (codings === 'gzip' || codings === 'x-gzip') { | ||
// for gzip | ||
body = body.pipe(zlib.createGunzip(zlibOptions)); | ||
} else if (codings === 'deflate' || codings === 'x-deflate') { | ||
// for deflate | ||
// handle the infamous raw deflate response from old servers | ||
// a hack for old IIS and Apache servers | ||
const raw = res.pipe(new Stream.PassThrough()); | ||
return raw.once('data', chunk => { | ||
// see http://stackoverflow.com/questions/37519828 | ||
if ((chunk[0] & 0x0F) === 0x08) { | ||
body = body.pipe(zlib.createInflate(zlibOptions)); | ||
} else { | ||
body = body.pipe(zlib.createInflateRaw(zlibOptions)); | ||
} | ||
const response = new Response(body, responseOptions); | ||
resolve(response); | ||
}); | ||
} | ||
} | ||
const response = new Response(body, responseOptions); | ||
resolve(response); | ||
}); | ||
writeToStream(req, request); | ||
const response = new Response(body, responseOptions); | ||
resolve(response); | ||
}); | ||
}); | ||
writeToStream(req, request); | ||
})); | ||
} | ||
/** | ||
@@ -1501,6 +1579,5 @@ * Redirect code matching | ||
*/ | ||
fetch.isRedirect = function (code) { | ||
return code === 301 || code === 302 || code === 303 || code === 307 || code === 308; | ||
}; | ||
fetch.isRedirect = code => code === 301 || code === 302 || code === 303 || code === 307 || code === 308; | ||
exports.default = fetch; | ||
@@ -1507,0 +1584,0 @@ exports.Headers = Headers; |
{ | ||
"name": "electron-fetch", | ||
"version": "1.2.1", | ||
"version": "1.3.0", | ||
"description": "A light-weight module that brings window.fetch to electron's background process", | ||
"main": "lib/index.js", | ||
"typings": "lib/index.d.ts", | ||
"module": "lib/index.es.js", | ||
"types": "index.d.ts", | ||
"files": [ | ||
"lib/index.js", | ||
"lib/index.es.js", | ||
"lib/index.d.ts" | ||
"index.d.ts" | ||
], | ||
"engines": { | ||
"node": ">=4" | ||
"node": ">=6" | ||
}, | ||
@@ -19,7 +19,9 @@ "scripts": { | ||
"prepublishOnly": "npm run build", | ||
"test": "npm run test:electron && npm run test:node && standard", | ||
"test:electron": "xvfb-maybe cross-env BABEL_ENV=test electron-mocha --require babel-register test/test.js", | ||
"test:node": "cross-env BABEL_ENV=test mocha --require babel-register test/test.js", | ||
"coverage": "xvfb-maybe cross-env BABEL_ENV=coverage electron-mocha --require babel-register test/test.js -R test/coverage-reporter.js", | ||
"report": "standard && npm run coverage && codecov -f coverage/coverage-final.json" | ||
"test": "npm run test:electron && npm run test:node && npm run test:typings && standard", | ||
"pretest:typings": "npm run build", | ||
"test:typings": "ts-node test/test-typescript.ts", | ||
"test:electron": "xvfb-maybe cross-env BABEL_ENV=test electron-mocha --require @babel/register test/test.js", | ||
"test:node": "cross-env BABEL_ENV=test mocha --require @babel/register test/test.js", | ||
"coverage": "xvfb-maybe cross-env BABEL_ENV=coverage electron-mocha --require @babel/register test/test.js -R test/coverage-reporter.js", | ||
"report": "standard && npm run test:typings && npm run coverage && codecov -f coverage/coverage-final.json" | ||
}, | ||
@@ -43,27 +45,30 @@ "repository": { | ||
"devDependencies": { | ||
"babel-eslint": "^8.2.5", | ||
"babel-plugin-istanbul": "^4.1.6", | ||
"babel-preset-env": "^1.7.0", | ||
"babel-register": "^6.26.0", | ||
"@babel/core": "^7.1.6", | ||
"@babel/preset-env": "^7.1.6", | ||
"@babel/register": "^7.0.0", | ||
"babel-eslint": "^10.0.1", | ||
"babel-plugin-istanbul": "^5.1.0", | ||
"basic-auth-parser": "0.0.2", | ||
"chai": "^4.1.2", | ||
"chai": "^4.2.0", | ||
"chai-as-promised": "^7.1.1", | ||
"codecov": "^3.0.4", | ||
"codecov": "^3.1.0", | ||
"cross-env": "^5.2.0", | ||
"electron": "2.0.4", | ||
"electron": "2.0.13", | ||
"electron-mocha": "^6.0.4", | ||
"form-data": ">=2.3.2", | ||
"is-builtin-module": "^2.0.0", | ||
"istanbul-api": "^1.3.1", | ||
"istanbul-lib-coverage": "^1.2.0", | ||
"form-data": "^2.3.3", | ||
"is-builtin-module": "^3.0.0", | ||
"istanbul-api": "^2.0.6", | ||
"istanbul-lib-coverage": "^2.0.1", | ||
"mocha": "^5.2.0", | ||
"nyc": "^12.0.2", | ||
"nyc": "^13.1.0", | ||
"parted": "^0.1.1", | ||
"promise": "^8.0.1", | ||
"promise": "^8.0.2", | ||
"proxy": "^0.2.4", | ||
"resumer": "0.0.0", | ||
"rollup": "^0.62.0", | ||
"rollup-plugin-babel": "^3.0.7", | ||
"standard": "^11.0.1", | ||
"whatwg-url": "^6.5.0", | ||
"rollup": "^0.67.1", | ||
"rollup-plugin-babel": "^4.0.3", | ||
"standard": "^12.0.1", | ||
"ts-node": "^7.0.1", | ||
"typescript": "^3.1.6", | ||
"whatwg-url": "^7.0.0", | ||
"xvfb-maybe": "^0.2.1" | ||
@@ -70,0 +75,0 @@ }, |
@@ -43,3 +43,3 @@ | ||
- Added electron-specific option `useElectronNet`, which can be set to false when running on Electron in order to behave as Node.js. | ||
- Removed possibility to use custom Promise implementation (it's 2017, `Promise` is available everywhere!). | ||
- Removed possibility to use custom Promise implementation (it's 2018, `Promise` is available everywhere!). | ||
- Removed the possibility to forbid content compression (incompatible with Electron's `net` module, and of limited interest) | ||
@@ -60,3 +60,3 @@ - [`standard`-ized](http://standardjs.com) the code. | ||
// or | ||
// const fetch = require('electron-fetch'); | ||
// const fetch = require('electron-fetch').default | ||
@@ -63,0 +63,0 @@ // plain text or html |
106235
29
2558