summaryrefslogtreecommitdiff
path: root/ext/js
diff options
context:
space:
mode:
authortoasted-nutbread <toasted-nutbread@users.noreply.github.com>2022-05-28 21:55:37 -0400
committerGitHub <noreply@github.com>2022-05-28 21:55:37 -0400
commit4e4fa49b0b1fd6ec5a018e742eb9910aa32e7637 (patch)
tree53ff2717de5c0654b0050c58f86fe49b8c89c6a4 /ext/js
parent756cfc027696901f0afc9b84bb439c67c8b620ad (diff)
Audio request errors (#2161)
* Generalize _onBeforeSendHeadersAddListener * Simplify filter assignment * Use requestId rather than done * Properly support Firefox addListener without arguments * Add details to fetchAnonymous errors * Refactor * Enable support for no header modifications * Update MV3 support for error details * Expose errors in downloadTermAudio * Throw an error if audio download fails due to potential permissions reasons
Diffstat (limited to 'ext/js')
-rw-r--r--ext/js/background/backend.js24
-rw-r--r--ext/js/background/request-builder.js114
-rw-r--r--ext/js/media/audio-downloader.js7
3 files changed, 102 insertions, 43 deletions
diff --git a/ext/js/background/backend.js b/ext/js/background/backend.js
index b25b6033..c0f286f8 100644
--- a/ext/js/background/backend.js
+++ b/ext/js/background/backend.js
@@ -1803,6 +1803,8 @@ class Backend {
reading
));
} catch (e) {
+ const error = this._getAudioDownloadError(e);
+ if (error !== null) { throw error; }
// No audio
return null;
}
@@ -1894,6 +1896,28 @@ class Backend {
return {results, errors};
}
+ _getAudioDownloadError(error) {
+ if (isObject(error.data)) {
+ const {errors} = error.data;
+ if (Array.isArray(errors)) {
+ for (const error2 of errors) {
+ if (!isObject(error2.data)) { continue; }
+ const {details} = error2.data;
+ if (!isObject(details)) { continue; }
+ if (details.error === 'net::ERR_FAILED') {
+ // This is potentially an error due to the extension not having enough URL privileges.
+ // The message logged to the console looks like this:
+ // Access to fetch at '<URL>' from origin 'chrome-extension://<ID>' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
+ const result = new Error('Audio download failed due to possible extension permissions error');
+ result.data = {errors};
+ return result;
+ }
+ }
+ }
+ }
+ return null;
+ }
+
_generateAnkiNoteMediaFileName(prefix, extension, timestamp, definitionDetails) {
let fileName = prefix;
diff --git a/ext/js/background/request-builder.js b/ext/js/background/request-builder.js
index 6c99acd6..ad1536f1 100644
--- a/ext/js/background/request-builder.js
+++ b/ext/js/background/request-builder.js
@@ -17,7 +17,6 @@
class RequestBuilder {
constructor() {
- this._extraHeadersSupported = null;
this._onBeforeSendHeadersExtraInfoSpec = ['blocking', 'requestHeaders', 'extraHeaders'];
this._textEncoder = new TextEncoder();
this._ruleIds = new Set();
@@ -36,74 +35,107 @@ class RequestBuilder {
return await this._fetchAnonymousDeclarative(url, init);
}
const originURL = this._getOriginURL(url);
- const modifications = [
+ const headerModifications = [
['cookie', null],
['origin', {name: 'Origin', value: originURL}]
];
- return await this._fetchModifyHeaders(url, init, modifications);
+ return await this._fetchInternal(url, init, headerModifications);
}
// Private
- async _fetchModifyHeaders(url, init, modifications) {
- const matchURL = this._getMatchURL(url);
+ async _fetchInternal(url, init, headerModifications) {
+ const filter = {
+ urls: [this._getMatchURL(url)],
+ types: ['xmlhttprequest']
+ };
- let done = false;
- const callback = (details) => {
- if (done || details.url !== url) { return {}; }
- done = true;
+ let requestId = null;
+ const onBeforeSendHeadersCallback = (details) => {
+ if (requestId !== null || details.url !== url) { return {}; }
+ ({requestId} = details);
+
+ if (headerModifications === null) { return {}; }
const requestHeaders = details.requestHeaders;
- this._modifyHeaders(requestHeaders, modifications);
+ this._modifyHeaders(requestHeaders, headerModifications);
return {requestHeaders};
};
- const filter = {
- urls: [matchURL],
- types: ['xmlhttprequest']
+
+ let errorDetailsTimer = null;
+ let {promise: errorDetailsPromise, resolve: errorDetailsResolve} = deferPromise();
+ const onErrorOccurredCallback = (details) => {
+ if (errorDetailsResolve === null || details.requestId !== requestId) { return; }
+ if (errorDetailsTimer !== null) {
+ clearTimeout(errorDetailsTimer);
+ errorDetailsTimer = null;
+ }
+ errorDetailsResolve(details);
+ errorDetailsResolve = null;
};
- let needsCleanup = false;
+ const eventListeners = [];
+ const onBeforeSendHeadersExtraInfoSpec = (headerModifications !== null ? this._onBeforeSendHeadersExtraInfoSpec : []);
+ this._addWebRequestEventListener(chrome.webRequest.onBeforeSendHeaders, onBeforeSendHeadersCallback, filter, onBeforeSendHeadersExtraInfoSpec, eventListeners);
+ this._addWebRequestEventListener(chrome.webRequest.onErrorOccurred, onErrorOccurredCallback, filter, void 0, eventListeners);
+
try {
- this._onBeforeSendHeadersAddListener(callback, filter);
- needsCleanup = true;
+ return await fetch(url, init);
} catch (e) {
- // NOP
+ // onErrorOccurred is not always invoked by this point, so a delay is needed
+ if (errorDetailsResolve !== null) {
+ errorDetailsTimer = setTimeout(() => {
+ errorDetailsTimer = null;
+ if (errorDetailsResolve === null) { return; }
+ errorDetailsResolve(null);
+ errorDetailsResolve = null;
+ }, 100);
+ }
+ const details = await errorDetailsPromise;
+ e.data = {details};
+ throw e;
+ } finally {
+ this._removeWebRequestEventListeners(eventListeners);
}
+ }
+ _addWebRequestEventListener(target, callback, filter, extraInfoSpec, eventListeners) {
try {
- return await fetch(url, init);
- } finally {
- if (needsCleanup) {
+ for (let i = 0; i < 2; ++i) {
try {
- chrome.webRequest.onBeforeSendHeaders.removeListener(callback);
+ if (typeof extraInfoSpec === 'undefined') {
+ target.addListener(callback, filter);
+ } else {
+ target.addListener(callback, filter, extraInfoSpec);
+ }
+ break;
} catch (e) {
- // NOP
+ // Firefox doesn't support the 'extraHeaders' option and will throw the following error:
+ // Type error for parameter extraInfoSpec (Error processing 2: Invalid enumeration value "extraHeaders") for [target].
+ if (i === 0 && `${e.message}`.includes('extraHeaders') && Array.isArray(extraInfoSpec)) {
+ const index = extraInfoSpec.indexOf('extraHeaders');
+ if (index >= 0) {
+ extraInfoSpec.splice(index, 1);
+ continue;
+ }
+ }
+ throw e;
}
}
+ } catch (e) {
+ console.log(e);
+ return;
}
+ eventListeners.push({target, callback});
}
- _onBeforeSendHeadersAddListener(callback, filter) {
- const extraInfoSpec = this._onBeforeSendHeadersExtraInfoSpec;
- for (let i = 0; i < 2; ++i) {
+ _removeWebRequestEventListeners(eventListeners) {
+ for (const {target, callback} of eventListeners) {
try {
- chrome.webRequest.onBeforeSendHeaders.addListener(callback, filter, extraInfoSpec);
- if (this._extraHeadersSupported === null) {
- this._extraHeadersSupported = true;
- }
- break;
+ target.removeListener(callback);
} catch (e) {
- // Firefox doesn't support the 'extraHeaders' option and will throw the following error:
- // Type error for parameter extraInfoSpec (Error processing 2: Invalid enumeration value "extraHeaders") for webRequest.onBeforeSendHeaders.
- if (this._extraHeadersSupported !== null || !`${e.message}`.includes('extraHeaders')) {
- throw e;
- }
+ console.log(e);
}
-
- // addListener failed; remove 'extraHeaders' from extraInfoSpec.
- this._extraHeadersSupported = false;
- const index = extraInfoSpec.indexOf('extraHeaders');
- if (index >= 0) { extraInfoSpec.splice(index, 1); }
}
}
@@ -197,7 +229,7 @@ class RequestBuilder {
await this._updateDynamicRules({addRules});
try {
- return await fetch(url, init);
+ return await this._fetchInternal(url, init, null);
} finally {
await this._tryUpdateDynamicRules({removeRuleIds: [id]});
}
diff --git a/ext/js/media/audio-downloader.js b/ext/js/media/audio-downloader.js
index c2e1742f..0991d14d 100644
--- a/ext/js/media/audio-downloader.js
+++ b/ext/js/media/audio-downloader.js
@@ -51,6 +51,7 @@ class AudioDownloader {
}
async downloadTermAudio(sources, preferredAudioIndex, term, reading) {
+ const errors = [];
for (const source of sources) {
let infoList = await this.getTermAudioInfoList(source, term, reading);
if (typeof preferredAudioIndex === 'number') {
@@ -62,14 +63,16 @@ class AudioDownloader {
try {
return await this._downloadAudioFromUrl(info.url, source.type);
} catch (e) {
- // NOP
+ errors.push(e);
}
break;
}
}
}
- throw new Error('Could not download audio');
+ const error = new Error('Could not download audio');
+ error.data = {errors};
+ throw error;
}
// Private