Greasemonkey 스크립트에서 XMLHttpRequests를 대행 수신하려면 어떻게 해야 합니까?
Greasemonkey를 사용하여 AJAX 요청 내용을 캡처하고 싶습니다.
이거 할 줄 아는 사람 있어요?
인정된 답변은 거의 맞지만 약간의 개선이 필요할 수 있습니다.
(function(open) {
XMLHttpRequest.prototype.open = function() {
this.addEventListener("readystatechange", function() {
console.log(this.readyState);
}, false);
open.apply(this, arguments);
};
})(XMLHttpRequest.prototype.open);
apply + arguments를 호출보다 더 선호합니다. 그러면 변경 가능한 모든 인수를 열기 위해 명시적으로 알 필요가 없습니다.
XMLHttpRequest.protype.open 메서드를 수정하거나 자체 콜백을 설정하고 원래 메서드를 호출하는 대체 메서드를 보내는 것은 어떻습니까?콜백은 그 기능을 수행하고 지정된 원래 코드로 콜백을 호출할 수 있습니다.
즉, 다음과 같습니다.
XMLHttpRequest.prototype.realOpen = XMLHttpRequest.prototype.open;
var myOpen = function(method, url, async, user, password) {
//do whatever mucking around you want here, e.g.
//changing the onload callback to your own version
//call original
this.realOpen (method, url, async, user, password);
}
//ensure all XMLHttpRequests use our custom open method
XMLHttpRequest.prototype.open = myOpen ;
Chrome 55 및 Firefox 50.1.0에서 테스트 완료
내 경우 Firefox에서는 읽기 전용 속성이었던 responseText를 수정하고 싶었기 때문에 XMLHttpRequest 개체 전체를 랩해야 했습니다.API 전체(특히 responseType)를 구현하지는 않았지만, 가지고 있는 모든 라이브러리에 사용할 수 있는 것은 충분했습니다.
사용방법:
XHRProxy.addInterceptor(function(method, url, responseText, status) {
if (url.endsWith('.html') || url.endsWith('.htm')) {
return "<!-- HTML! -->" + responseText;
}
});
코드:
(function(window) {
var OriginalXHR = XMLHttpRequest;
var XHRProxy = function() {
this.xhr = new OriginalXHR();
function delegate(prop) {
Object.defineProperty(this, prop, {
get: function() {
return this.xhr[prop];
},
set: function(value) {
this.xhr.timeout = value;
}
});
}
delegate.call(this, 'timeout');
delegate.call(this, 'responseType');
delegate.call(this, 'withCredentials');
delegate.call(this, 'onerror');
delegate.call(this, 'onabort');
delegate.call(this, 'onloadstart');
delegate.call(this, 'onloadend');
delegate.call(this, 'onprogress');
};
XHRProxy.prototype.open = function(method, url, async, username, password) {
var ctx = this;
function applyInterceptors(src) {
ctx.responseText = ctx.xhr.responseText;
for (var i=0; i < XHRProxy.interceptors.length; i++) {
var applied = XHRProxy.interceptors[i](method, url, ctx.responseText, ctx.xhr.status);
if (applied !== undefined) {
ctx.responseText = applied;
}
}
}
function setProps() {
ctx.readyState = ctx.xhr.readyState;
ctx.responseText = ctx.xhr.responseText;
ctx.responseURL = ctx.xhr.responseURL;
ctx.responseXML = ctx.xhr.responseXML;
ctx.status = ctx.xhr.status;
ctx.statusText = ctx.xhr.statusText;
}
this.xhr.open(method, url, async, username, password);
this.xhr.onload = function(evt) {
if (ctx.onload) {
setProps();
if (ctx.xhr.readyState === 4) {
applyInterceptors();
}
return ctx.onload(evt);
}
};
this.xhr.onreadystatechange = function (evt) {
if (ctx.onreadystatechange) {
setProps();
if (ctx.xhr.readyState === 4) {
applyInterceptors();
}
return ctx.onreadystatechange(evt);
}
};
};
XHRProxy.prototype.addEventListener = function(event, fn) {
return this.xhr.addEventListener(event, fn);
};
XHRProxy.prototype.send = function(data) {
return this.xhr.send(data);
};
XHRProxy.prototype.abort = function() {
return this.xhr.abort();
};
XHRProxy.prototype.getAllResponseHeaders = function() {
return this.xhr.getAllResponseHeaders();
};
XHRProxy.prototype.getResponseHeader = function(header) {
return this.xhr.getResponseHeader(header);
};
XHRProxy.prototype.setRequestHeader = function(header, value) {
return this.xhr.setRequestHeader(header, value);
};
XHRProxy.prototype.overrideMimeType = function(mimetype) {
return this.xhr.overrideMimeType(mimetype);
};
XHRProxy.interceptors = [];
XHRProxy.addInterceptor = function(fn) {
this.interceptors.push(fn);
};
window.XMLHttpRequest = XHRProxy;
})(window);
안전하지 않은 것을 교체할 수 있습니다.Window.XMLHttpRequest 오브젝트(랩퍼 포함)작은 코드(테스트되지 않음):
var oldFunction = unsafeWindow.XMLHttpRequest;
unsafeWindow.XMLHttpRequest = function() {
alert("Hijacked! XHR was constructed.");
var xhr = oldFunction();
return {
open: function(method, url, async, user, password) {
alert("Hijacked! xhr.open().");
return xhr.open(method, url, async, user, password);
}
// TODO: include other xhr methods and properties
};
};
하지만 여기에는 작은 문제가 하나 있습니다.페이지 로드 후 Greasemonkey 스크립트가 실행되므로 페이지가 로드 시퀀스 중에 원래 XMLHttpRequest 개체를 사용하거나 저장할 수 있으므로 스크립트가 실행되기 전 또는 실제 XMLHttpRequest 개체를 사용한 요구는 스크립트에 의해 추적되지 않습니다.이 한계를 극복하는 일은 있을 수 없습니다.
나는 이것을 어떻게 해야 할지 알아내는데 꽤 많은 시간을 소비했다.처음엔 그냥 오버라이드였어window.fetch
하지만 그것은 어떤 이유로 작동을 멈췄다 - 나는 그것이 샌드박스를 시도하고 있는 Tampermonkey와 관련이 있다고 생각한다.window
(--) 그리고 저도 시도했습니다.unsafeWindow
같은 결과를 얻었습니다.
그래서 더 낮은 수준에서 요청을 무시하려고 했어요그XMLHttpRequest
(또한 클래스 이름 대문자 소문자 ew...) Sean의 답변은 시작하는 데 도움이 되었지만 가로채기 후 응답을 재정의하는 방법을 보여주지 않았습니다.이하에, 이것을 실시합니다.
let interceptors = [];
/*
* Add a interceptor.
*/
export const addInterceptor = (interceptor) => {
interceptors.push(interceptor);
};
/*
* Clear interceptors
*/
export const clearInterceptors = () => {
interceptors = [];
};
/*
* XML HTPP requests can be intercepted with interceptors.
* Takes a regex to match against requests made and a callback to process the response.
*/
const createXmlHttpOverride = (
open
) => {
return function (
method: string,
url,
async,
username,
password
) {
this.addEventListener(
"readystatechange",
function () {
if (this.readyState === 4) {
// Override `onreadystatechange` handler, there's no where else this can go.
// Basically replace the client's with our override for interception.
this.onreadystatechange = (function (
originalOnreadystatechange
) {
return function (ev) {
// Only intercept JSON requests.
const contentType = this.getResponseHeader("content-type");
if (!contentType || !contentType.includes("application/json")) {
return (
originalOnreadystatechange &&
originalOnreadystatechange.call(this, ev)
);
}
// Read data from response.
(async function () {
let success = false;
let data;
try {
data =
this.responseType === "blob"
? JSON.parse(await this.response.text())
: JSON.parse(this.responseText);
success = true;
} catch (e) {
console.error("Unable to parse response.");
}
if (!success) {
return (
originalOnreadystatechange &&
originalOnreadystatechange.call(this, ev)
);
}
for (const i in interceptors) {
const { regex, override, callback } = interceptors[i];
// Override.
const match = regex.exec(url);
if (match) {
if (override) {
try {
data = await callback(data);
} catch (e) {
logger.error(`Interceptor '${regex}' failed. ${e}`);
}
}
}
}
// Override the response text.
Object.defineProperty(this, "responseText", {
get() {
return JSON.stringify(data);
},
});
// Tell the client callback that we're done.
return (
originalOnreadystatechange &&
originalOnreadystatechange.call(this, ev)
);
}.call(this));
};
})(this.onreadystatechange);
}
},
false
);
open.call(this, method, url, async, username, password);
};
};
const main = () => {
const urlRegex = /providers/; // Match any url with "providers" in the url.
addInterceptor({
urlRegex,
callback: async (_data) => {
// Replace response data.
return JSON.parse({ hello: 'world' });
},
override: true
});
XMLHttpRequest.prototype.open = createXmlHttpOverride(
XMLHttpRequest.prototype.open
);
};
main();
제안된 솔루션을 기반으로 타이프스크립트 솔루션에서 사용할 수 있는 'xhr-extensions.ts' 파일을 구현했습니다.사용방법:
솔루션에 코드가 포함된 파일 추가
이렇게 가져오기
import { XhrSubscription, subscribToXhr } from "your-path/xhr-extensions";
이렇게 구독하다
const subscription = subscribeToXhr(xhr => { if (xhr.status != 200) return; ... do something here. });
더 이상 구독이 필요하지 않을 때 구독 취소
subscription.unsubscribe();
'xhr-extensions.ts' 파일의 내용
export class XhrSubscription {
constructor(
private callback: (xhr: XMLHttpRequest) => void
) { }
next(xhr: XMLHttpRequest): void {
return this.callback(xhr);
}
unsubscribe(): void {
subscriptions = subscriptions.filter(s => s != this);
}
}
let subscriptions: XhrSubscription[] = [];
export function subscribeToXhr(callback: (xhr: XMLHttpRequest) => void): XhrSubscription {
const subscription = new XhrSubscription(callback);
subscriptions.push(subscription);
return subscription;
}
(function (open) {
XMLHttpRequest.prototype.open = function () {
this.addEventListener("readystatechange", () => {
subscriptions.forEach(s => s.next(this));
}, false);
return open.apply(this, arguments);
};
})(XMLHttpRequest.prototype.open);
greasemonkey를 사용하여 실행할 수 있을지 모르겠지만 확장을 작성하면 옵서버 서비스와 http-on-in-inspired-response 옵서버를 사용할 수 있습니다.
언급URL : https://stackoverflow.com/questions/629671/how-can-i-intercept-xmlhttprequests-from-a-greasemonkey-script
'IT' 카테고리의 다른 글
Gson용 커스텀 JSON 디시리얼라이저를 작성하려면 어떻게 해야 하나요? (0) | 2023.03.29 |
---|---|
스프링 부트 @autowired가 작동하지 않습니다.클래스는 다른 패키지로 되어 있습니다. (0) | 2023.03.29 |
oneOf 개체에 대한 Json 스키마 예제 (0) | 2023.03.29 |
React.js의 선언과 명령의 차이는 무엇입니까? (0) | 2023.03.29 |
유형 스크립트 - React 구성 요소의 "이름 찾을 수 없음" 오류 (0) | 2023.03.29 |