resource-loader.js
3.36 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
"use strict";
const fs = require("fs");
const { parseURL } = require("whatwg-url");
const dataURLFromRecord = require("data-urls").fromURLRecord;
const request = require("request-promise-native");
const wrapCookieJarForRequest = require("../../living/helpers/wrap-cookie-jar-for-request");
const packageVersion = require("../../../../package.json").version;
const IS_BROWSER = Object.prototype.toString.call(process) !== "[object process]";
module.exports = class ResourceLoader {
constructor({
strictSSL = true,
proxy = undefined,
userAgent = `Mozilla/5.0 (${process.platform || "unknown OS"}) AppleWebKit/537.36 ` +
`(KHTML, like Gecko) jsdom/${packageVersion}`
} = {}) {
this._strictSSL = strictSSL;
this._proxy = proxy;
this._userAgent = userAgent;
}
_readDataURL(urlRecord) {
const dataURL = dataURLFromRecord(urlRecord);
let timeoutId;
const promise = new Promise(resolve => {
timeoutId = setTimeout(resolve, 0, dataURL.body);
});
promise.abort = () => {
if (timeoutId !== undefined) {
clearTimeout(timeoutId);
}
};
return promise;
}
_readFile(filePath) {
let readableStream;
let abort; // Native Promises doesn't have an "abort" method.
/*
* Creating a promise for two reason:
* 1. fetch always return a promise.
* 2. We need to add an abort handler.
*/
const promise = new Promise((resolve, reject) => {
readableStream = fs.createReadStream(filePath);
let data = Buffer.alloc(0);
abort = reject;
readableStream.on("error", reject);
readableStream.on("data", chunk => {
data = Buffer.concat([data, chunk]);
});
readableStream.on("end", () => {
resolve(data);
});
});
promise.abort = () => {
readableStream.destroy();
const error = new Error("request canceled by user");
error.isAbortError = true;
abort(error);
};
return promise;
}
_getRequestOptions({ cookieJar, referrer, accept = "*/*" }) {
const requestOptions = {
encoding: null,
gzip: true,
jar: wrapCookieJarForRequest(cookieJar),
strictSSL: this._strictSSL,
proxy: this._proxy,
forever: true,
headers: {
"User-Agent": this._userAgent,
"Accept-Language": "en",
Accept: accept
}
};
if (referrer && !IS_BROWSER) {
requestOptions.headers.referer = referrer;
}
return requestOptions;
}
fetch(urlString, options = {}) {
const url = parseURL(urlString);
if (!url) {
return Promise.reject(new Error(`Tried to fetch invalid URL ${urlString}`));
}
switch (url.scheme) {
case "data": {
return this._readDataURL(url);
}
case "http":
case "https": {
const requestOptions = this._getRequestOptions(options);
return request(urlString, requestOptions);
}
case "file": {
// TODO: Improve the URL => file algorithm. See https://github.com/jsdom/jsdom/pull/2279#discussion_r199977987
const filePath = urlString
.replace(/^file:\/\//, "")
.replace(/^\/([a-z]):\//i, "$1:/")
.replace(/%20/g, " ");
return this._readFile(filePath);
}
default: {
return Promise.reject(new Error(`Tried to fetch URL ${urlString} with invalid scheme ${url.scheme}`));
}
}
}
};