diff --git a/CHANGELOG.md b/CHANGELOG.md index 9bbd7016..f2571a19 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - ability to use flags in RegExp [#303](https://github.com/AdguardTeam/Scriptlets/issues/303) +### Fixed + +- error throwing in `prevent-fetch` and `prevent-xhr` scriptlets when a request is blocked + [#334](https://github.com/AdguardTeam/Scriptlets/issues/334) + ## [v1.9.37] - 2023-06-06 ### Added diff --git a/package.json b/package.json index 71747574..7d28d7f6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@adguard/scriptlets", - "version": "1.9.41", + "version": "1.9.42", "description": "AdGuard's JavaScript library of Scriptlets and Redirect resources", "scripts": { "build": "babel-node -x .js,.ts scripts/build.js", diff --git a/src/scriptlets/prevent-fetch.js b/src/scriptlets/prevent-fetch.js index 1f9c8da9..57bbc481 100644 --- a/src/scriptlets/prevent-fetch.js +++ b/src/scriptlets/prevent-fetch.js @@ -4,6 +4,7 @@ import { objectToString, matchRequestProps, logMessage, + noopPromiseResolve, modifyResponse, // following helpers should be imported and injected // because they are used by helpers above @@ -156,14 +157,25 @@ export function preventFetch(source, propsToMatch, responseBody = 'emptyObj', re if (shouldPrevent) { hit(source); - const origResponse = await Reflect.apply(target, thisArg, args); - return modifyResponse( - origResponse, - { - body: strResponseBody, - type: responseType, - }, - ); + try { + const origResponse = await Reflect.apply(target, thisArg, args); + // In the case of apps, the blocked request has status 500 + // and no error is thrown, so it's necessary to check response.ok + // https://github.com/AdguardTeam/Scriptlets/issues/334 + if (!origResponse.ok) { + return noopPromiseResolve(strResponseBody, fetchData.url, responseType); + } + return modifyResponse( + origResponse, + { + body: strResponseBody, + type: responseType, + }, + ); + } catch (ex) { + // https://github.com/AdguardTeam/Scriptlets/issues/334 + return noopPromiseResolve(strResponseBody, fetchData.url, responseType); + } } return Reflect.apply(target, thisArg, args); @@ -190,6 +202,7 @@ preventFetch.injections = [ objectToString, matchRequestProps, logMessage, + noopPromiseResolve, modifyResponse, toRegExp, isValidStrPattern, diff --git a/src/scriptlets/prevent-xhr.js b/src/scriptlets/prevent-xhr.js index d633890a..4d47e9f3 100644 --- a/src/scriptlets/prevent-xhr.js +++ b/src/scriptlets/prevent-xhr.js @@ -185,7 +185,6 @@ export function preventXHR(source, propsToMatch, customResponseText) { readyState, responseURL, responseXML, - status, statusText, } = forgedRequest; @@ -193,11 +192,12 @@ export function preventXHR(source, propsToMatch, customResponseText) { Object.defineProperties(thisArg, { // original values readyState: { value: readyState, writable: false }, - status: { value: status, writable: false }, statusText: { value: statusText, writable: false }, - responseURL: { value: responseURL, writable: false }, + // If the request is blocked, responseURL is an empty string + responseURL: { value: responseURL || xhrData.url, writable: false }, responseXML: { value: responseXML, writable: false }, // modified values + status: { value: 200, writable: false }, response: { value: modifiedResponse, writable: false }, responseText: { value: modifiedResponseText, writable: false }, }); diff --git a/tests/scriptlets/prevent-fetch.test.js b/tests/scriptlets/prevent-fetch.test.js index 8d41f7cd..55fb9474 100644 --- a/tests/scriptlets/prevent-fetch.test.js +++ b/tests/scriptlets/prevent-fetch.test.js @@ -139,6 +139,28 @@ if (!isSupported) { done(); }); + test('prevent not existing fetch call', async (assert) => { + // blocked_request.json doesn't exist, + // it's required for test for blocked requests + const BLOCKED_REQUEST = `${FETCH_OBJECTS_PATH}/blocked_request.json`; + const inputRequest = new Request(BLOCKED_REQUEST); + + runScriptlet(name, ['blocked_request']); + const done = assert.async(1); + + const response = await fetch(inputRequest); + const parsedData = await response.json(); + + if (!response.ok) { + assert.strictEqual(response.ok, true, 'Request blocked'); + done(); + } + assert.ok(response.url.includes('/blocked_request.json'), 'Response URL is mocked'); + assert.ok(isEmptyObject(parsedData), 'Response is mocked'); + assert.strictEqual(window.hit, 'FIRED', 'hit function fired'); + done(); + }); + test('simple fetch - match single pair prop', async (assert) => { const INPUT_JSON_PATH = `${FETCH_OBJECTS_PATH}/test03.json`; const options = { diff --git a/tests/scriptlets/prevent-xhr.test.js b/tests/scriptlets/prevent-xhr.test.js index 8acb5438..57267d94 100644 --- a/tests/scriptlets/prevent-xhr.test.js +++ b/tests/scriptlets/prevent-xhr.test.js @@ -508,6 +508,32 @@ if (isSupported) { xhr.send(); }); + test('Args matched, prevent blocked request', async (assert) => { + // blocked_request.json doesn't exist, + // it's required for test for blocked requests + const METHOD = 'GET'; + const URL = `${FETCH_OBJECTS_PATH}/blocked_request.json`; + const MATCH_DATA = [`blocked_request method:${METHOD}`]; + + runScriptlet(name, MATCH_DATA); + + const done = assert.async(); + + const reqListener = (e) => { + assert.strictEqual(e.target.status, 200, 'Status mocked'); + assert.ok(e.target.responseURL.includes('/blocked_request.json'), 'Origianl URL mocked'); + assert.strictEqual(e.target.readyState, 4, 'Response done'); + assert.strictEqual(e.target.response, '', 'Response data mocked'); + assert.strictEqual(window.hit, 'FIRED', 'hit function fired'); + done(); + }; + + const xhr = new XMLHttpRequest(); + xhr.open(METHOD, URL); + xhr.addEventListener('load', reqListener); + xhr.send(); + }); + test('Args, match, listeners after .send work', async (assert) => { const METHOD = 'GET'; const URL = `${FETCH_OBJECTS_PATH}/test01.json`;