Мне нужно перехватить XHR на странице, я запускаю с прокси объект XHR, первый прокси new
оператор:
class ClassHandler {
constructor(proxy) {
this.proxy = proxy;
}
construct(target, args) {
const obj = new target(...args);
return new Proxy(obj, new this.proxy(obj));
}
}
(function(XMLHttpRequest) {
unsafeWindow.XMLHttpRequest = new Proxy(
XMLHttpRequest,
new ClassHandler(XhrHandler)
);
})(XMLHttpRequest);
Затем XhrProxy
необходимо все прокси до реального объекта XHR, яЭто очень сложно, поэтому разделите его на несколько компонентов.
Основные свойства
const ProxyGetTarget = Symbol('ProxyGetTarget');
const ProxyGetHandler = Symbol('ProxyGetHandler');
class ObjectHandler {
constructor(target) {
this.target = target;
}
get(target, prop, receiver) {
if (target.hasOwnProperty(prop)) {
return Reflect.get(target, prop, receiver);
} else if (prop == ProxyGetTarget) {
return target;
} else if (prop == ProxyGetHandler) {
return this;
} else {
const value = target[prop];
if (typeof value == 'function')
return new Proxy(value, new FunctionHandler(value));
return value;
}
}
set(target, prop, value) {
return Reflect.set(target, prop, value);
}
}
Я ввел свойство ProxyGetTarget
, которое возвращает реальную цель для будущего использования.Далее я объясню, почему прокси-функции с FunctionHandler
в объекте.
вызовы функций-членов
При вызове функции объекта немного сложнее:
xhr.open()
xhr
- это наш прокси-объект, а не реальный собственный XHR-объект, функция open будет вызываться с this
, установленным для прокси-объекта. Чтобы перенаправить открытый вызов реальному xhr-объекту, нам нужен прокси-сервер всехфункции, возвращаемые get
:
class FunctionHandlerBase extends ObjectHandler {
apply(target, thisArg, argumentsList) {
const realTarget = thisArg[ProxyGetTarget];
if (!realTarget) throw new Error('illegal invocations');
return this.call(this.target, thisArg, realTarget, argumentsList);
}
}
class FunctionHandler extends FunctionHandlerBase {
call(fn, proxy, target, argumentsList) {
fn.apply(target, argumentsList);
}
}
При этой настройке я могу внедрить любой код в любую функцию-член следующим способом:
class XhrHandler extends ObjectHandler{
get(target, prop, receiver) {
if (prop === 'open') {
return new Proxy(target.open, new this.open(target.open));
} else {
return super.get(target, prop, receiver);
}
}
}
XhrHandler.prototype.open = class extends FunctionHandlerBase {
call(fn, proxy, realTarget, argumentsList) {
// Do whatever before real xhr.open call
// ...
// ...
// Call real xhr.open
return fn.apply(realTarget, argumentsList);
}
};
Прослушиватели событий
Поскольку слушатели событий будут выставлять реальный объект в качестве аргумента this
в функции обратного вызова, я не хочу, чтобы кто-то получал мою секретную реальную цель, я должен также обернуть ее прокси-сервером.
class EventTargetHandler extends ObjectHandler {
constructor(target) {
super(target);
this.listeners = {};
}
getListeners(type) {
if (!this.listeners.hasOwnProperty(type))
this.listeners[type] = new Map();
return this.listeners[type];
}
get(target, prop, receiver) {
if (prop === 'addEventListener') {
return new Proxy(
target.addEventListener,
new this.addEventListener(target.addEventListener, this)
);
} else if (prop === 'removeEventListener') {
return new Proxy(
target.removeEventListener,
new this.removeEventListener(target.removeEventListener, this)
);
} else return super.get(target, prop, receiver);
}
}
EventTargetHandler.prototype.addEventListener = class extends FunctionHandlerBase {
call(fn, proxy, realTarget, argumentsList) {
const type = argumentsList[0];
const listener = argumentsList[1];
const bridge = listener.bind(proxy);
argumentsList[1] = bridge;
proxy[ProxyGetHandler].getListeners(type).set(listener, bridge);
return fn.apply(realTarget, argumentsList);
}
};
EventTargetHandler.prototype.removeEventListener = class extends FunctionHandlerBase {
call(fn, proxy, realTarget, argumentsList) {
const type = argumentsList[0];
const listener = argumentsList[1];
const cache = proxy[ProxyGetHandler].getListeners(type);
if (cache.has(listener)) {
argumentsList[1] = cache.get(listener);
cache.delete(listener);
}
return fn.apply(realTarget, argumentsList);
}
};
Обработчики событий
Еще не сделано ... Мне нужно обернуть все set
в свойства обработчика событий.
Проблемы
Хм, угон XHR?простая задача, давайте сделаем это за 5 минут.
...
...
...
Через 5 часов яЯ все еще борюсь с этими прокси.
Может быть, я неправильно понял идею разработки Proxy
API, мне было очень сложно обернуть объект, почти невозможно.
Я просто хочу свой проксиведет себя так же, как и исходный объект, Есть ли более простой подход?