Как я могу перехватить XMLHttpRequests из скрипта Greasemonkey? - PullRequest
44 голосов
/ 10 марта 2009

Я хотел бы получить содержимое запросов AJAX с помощью Greasemonkey.

Кто-нибудь знает, как это сделать?

Ответы [ 7 ]

52 голосов
/ 27 марта 2015

Принятый ответ почти верен, но он может использовать небольшое улучшение:

(function(open) {
    XMLHttpRequest.prototype.open = function() {
        this.addEventListener("readystatechange", function() {
            console.log(this.readyState);
        }, false);
        open.apply(this, arguments);
    };
})(XMLHttpRequest.prototype.open);

Предпочитайте применять аргументы apply +, а не call, потому что тогда вам не нужно явно знать все аргументы для открытия, которые могут измениться!

6 голосов
/ 10 марта 2009

Как насчет изменения XMLHttpRequest.prototype.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 ;
3 голосов
/ 27 января 2017

Протестировано в Chrome 55 и Firefox 50.1.0

В моем случае я хотел изменить responseText, который в Firefox был только для чтения, поэтому мне пришлось обернуть весь объект 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);
1 голос
/ 05 января 2012

Я написал некоторый код для перехвата вызовов ajax при написании прокси-сервера. Должно работать в большинстве браузеров.

Вот оно: https://github.com/creotiv/AJAX-calls-intercepter

1 голос
/ 10 марта 2009

Вы можете заменить объект unsafeWindow.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 не будет отслеживаться вашим скриптом. Я никак не могу обойти это ограничение.

0 голосов
/ 12 декабря 2018

На основе предложенного решения я реализовал файл 'xhr-extensions.ts', который можно использовать в решениях для машинописи. Как использовать:

  1. Добавить файл с кодом в ваше решение

  2. Импортировать как это

    import { XhrSubscription, subscribToXhr } from "your-path/xhr-extensions";
    
  3. Подписаться, как это

    const subscription = subscribeToXhr(xhr => {
      if (xhr.status != 200) return;
      ... do something here.
    });
    
  4. Отмените подписку, когда вам больше не нужна подписка

    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);
0 голосов
/ 10 марта 2009

Не уверен, что вы можете сделать это с помощью greasemonkey, но если вы создадите расширение, вы можете использовать службу наблюдателя и обозреватель http-on-exam-response.

...