Replace XMLHttpRequest (#562)

* Replace XMLHttpRequest with fetch

* Implement fetch placeholder for tests
This commit is contained in:
toasted-nutbread 2020-06-13 10:23:04 -04:00 committed by GitHub
parent 5cba421201
commit 8a7ff6a18c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 73 additions and 106 deletions

View File

@ -82,16 +82,24 @@ class AudioUriBuilder {
} }
async _getUriJpod101Alternate(definition) { async _getUriJpod101Alternate(definition) {
const response = await new Promise((resolve, reject) => { const fetchUrl = 'https://www.japanesepod101.com/learningcenter/reference/dictionary_post';
const xhr = new XMLHttpRequest(); const data = `post=dictionary_reference&match_type=exact&search_query=${encodeURIComponent(definition.expression)}`;
xhr.open('POST', 'https://www.japanesepod101.com/learningcenter/reference/dictionary_post'); const response = await fetch(fetchUrl, {
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); method: 'POST',
xhr.addEventListener('error', () => reject(new Error('Failed to scrape audio data'))); mode: 'no-cors',
xhr.addEventListener('load', () => resolve(xhr.responseText)); cache: 'default',
xhr.send(`post=dictionary_reference&match_type=exact&search_query=${encodeURIComponent(definition.expression)}`); credentials: 'omit',
redirect: 'follow',
referrerPolicy: 'no-referrer',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: data
}); });
const responseText = await response.text();
console.log(responseText);
const dom = new DOMParser().parseFromString(response, 'text/html'); const dom = new DOMParser().parseFromString(responseText, 'text/html');
for (const row of dom.getElementsByClassName('dc-result-row')) { for (const row of dom.getElementsByClassName('dc-result-row')) {
try { try {
const url = row.querySelector('audio>source[src]').getAttribute('src'); const url = row.querySelector('audio>source[src]').getAttribute('src');
@ -108,15 +116,18 @@ class AudioUriBuilder {
} }
async _getUriJisho(definition) { async _getUriJisho(definition) {
const response = await new Promise((resolve, reject) => { const fetchUrl = `https://jisho.org/search/${definition.expression}`;
const xhr = new XMLHttpRequest(); const response = await fetch(fetchUrl, {
xhr.open('GET', `https://jisho.org/search/${definition.expression}`); method: 'GET',
xhr.addEventListener('error', () => reject(new Error('Failed to scrape audio data'))); mode: 'no-cors',
xhr.addEventListener('load', () => resolve(xhr.responseText)); cache: 'default',
xhr.send(); credentials: 'omit',
redirect: 'follow',
referrerPolicy: 'no-referrer'
}); });
const responseText = await response.text();
const dom = new DOMParser().parseFromString(response, 'text/html'); const dom = new DOMParser().parseFromString(responseText, 'text/html');
try { try {
const audio = dom.getElementById(`audio_${definition.expression}:${definition.reading}`); const audio = dom.getElementById(`audio_${definition.expression}:${definition.reading}`);
if (audio !== null) { if (audio !== null) {

View File

@ -16,28 +16,28 @@
*/ */
function requestText(url, action, params) { async function requestText(url, method, data) {
return new Promise((resolve, reject) => { const response = await fetch(url, {
const xhr = new XMLHttpRequest(); method,
xhr.overrideMimeType('text/plain'); mode: 'no-cors',
xhr.addEventListener('load', () => resolve(xhr.responseText)); cache: 'default',
xhr.addEventListener('error', () => reject(new Error('Failed to connect'))); credentials: 'omit',
xhr.open(action, url); redirect: 'follow',
if (params) { referrerPolicy: 'no-referrer',
xhr.send(JSON.stringify(params)); body: (data ? JSON.stringify(data) : void 0)
} else {
xhr.send();
}
}); });
return await response.text();
} }
async function requestJson(url, action, params) { async function requestJson(url, method, data) {
const responseText = await requestText(url, action, params); const response = await fetch(url, {
try { method,
return JSON.parse(responseText); mode: 'no-cors',
} catch (e) { cache: 'default',
const error = new Error(`Invalid response (${e.message || e})`); credentials: 'omit',
error.data = {url, action, params, responseText}; redirect: 'follow',
throw error; referrerPolicy: 'no-referrer',
} body: (data ? JSON.stringify(data) : void 0)
});
return await response.json();
} }

View File

@ -169,22 +169,22 @@ class AudioSystem {
}); });
} }
_createAudioBinaryFromUrl(url) { async _createAudioBinaryFromUrl(url) {
return new Promise((resolve, reject) => { const response = await fetch(url, {
const xhr = new XMLHttpRequest(); method: 'GET',
xhr.responseType = 'arraybuffer'; mode: 'no-cors',
xhr.addEventListener('load', async () => { cache: 'default',
const arrayBuffer = xhr.response; credentials: 'omit',
if (!await this._isAudioBinaryValid(arrayBuffer)) { redirect: 'follow',
reject(new Error('Could not retrieve audio')); referrerPolicy: 'no-referrer'
} else {
resolve(arrayBuffer);
}
});
xhr.addEventListener('error', () => reject(new Error('Failed to connect')));
xhr.open('GET', url);
xhr.send();
}); });
const arrayBuffer = await response.arrayBuffer();
if (!await this._isAudioBinaryValid(arrayBuffer)) {
throw new Error('Could not retrieve audio');
}
return arrayBuffer;
} }
_isAudioValid(audio) { _isAudioValid(audio) {

View File

@ -38,60 +38,6 @@ const chrome = {
} }
}; };
class XMLHttpRequest {
constructor() {
this._eventCallbacks = new Map();
this._url = '';
this._responseText = null;
}
overrideMimeType() {
// NOP
}
addEventListener(eventName, callback) {
let callbacks = this._eventCallbacks.get(eventName);
if (typeof callbacks === 'undefined') {
callbacks = [];
this._eventCallbacks.set(eventName, callbacks);
}
callbacks.push(callback);
}
open(action, url2) {
this._url = url2;
}
send() {
const filePath = url.fileURLToPath(this._url);
Promise.resolve()
.then(() => {
let source;
try {
source = fs.readFileSync(filePath, {encoding: 'utf8'});
} catch (e) {
this._trigger('error');
return;
}
this._responseText = source;
this._trigger('load');
});
}
get responseText() {
return this._responseText;
}
_trigger(eventName, ...args) {
const callbacks = this._eventCallbacks.get(eventName);
if (typeof callbacks === 'undefined') { return; }
for (let i = 0, ii = callbacks.length; i < ii; ++i) {
callbacks[i](...args);
}
}
}
class Image { class Image {
constructor() { constructor() {
this._src = ''; this._src = '';
@ -138,11 +84,21 @@ class Image {
} }
} }
async function fetch(url2) {
const filePath = url.fileURLToPath(url2);
await Promise.resolve();
const content = fs.readFileSync(filePath, {encoding: null});
return {
text: async () => Promise.resolve(content.toString('utf8')),
json: async () => Promise.resolve(JSON.parse(content.toString('utf8')))
};
}
const vm = new VM({ const vm = new VM({
chrome, chrome,
Image, Image,
XMLHttpRequest, fetch,
indexedDB: global.indexedDB, indexedDB: global.indexedDB,
IDBKeyRange: global.IDBKeyRange, IDBKeyRange: global.IDBKeyRange,
JSZip: yomichanTest.JSZip, JSZip: yomichanTest.JSZip,