web-dev-qa-db-fra.com

Comment puis-je intercepter XMLHttpRequests à partir d'un script Greasemonkey?

J'aimerais capturer le contenu des demandes AJAX à l'aide de Greasemonkey.

Est-ce que quelqu'un sait comment faire cela?

42
Scooby Doo

La réponse acceptée est presque correcte, mais cela pourrait apporter une légère amélioration:

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

Préférez utiliser apply + arguments à call car vous n'avez pas à connaître explicitement tous les arguments donnés pour open qui pourraient changer!

51
Sean Anderson

Que diriez-vous de modifier XMLHttpRequest.prototype.open ou d'envoyer des méthodes avec des remplacements qui configurent leurs propres rappels et appellent les méthodes d'origine? Le rappel peut faire son travail et ensuite appeler le code original spécifié.

En d'autres termes:

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 ;
6
Paul Dixon

Testé sous Chrome 55 et Firefox 50.1.0

Dans mon cas, je voulais modifier le responseText, qui dans Firefox était une propriété en lecture seule. Je devais donc envelopper tout l'objet XMLHttpRequest. Je n'ai pas implémenté l'intégralité de l'API (en particulier le responseType), mais c'était assez bon à utiliser pour toutes les bibliothèques que j'ai.

Usage:

    XHRProxy.addInterceptor(function(method, url, responseText, status) {
        if (url.endsWith('.html') || url.endsWith('.htm')) {
            return "<!-- HTML! -->" + responseText;
        }
    });

Code:

(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);
3
bcoughlan

J'ai écrit du code pour intercepter les appels ajax lors de l'écriture du serveur proxy. Cela devrait fonctionner sur la plupart des navigateurs.

La voici: https://github.com/creotiv/AJAX-calls-intercepter

1
Andrey Nikishaev

Vous pouvez remplacer l'objet unsafeWindow.XMLHttpRequest dans le document par un wrapper. Un petit code (non testé):

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
  };
};

Mais ceci a un petit problème: les scripts Greasemonkey exécutent après une page chargée, de sorte que la page puisse utiliser ou stocker l'objet XMLHttpRequest d'origine pendant sa séquence de chargement; ne pas être suivi par votre script. Aucun moyen que je puisse voir pour contourner cette limitation.

1
waqas

Sur la base de la solution proposée, j'ai implémenté le fichier 'xhr-extensions.ts' qui peut être utilisé dans les solutions TypeScript . Comment utiliser:

  1. Ajouter un fichier avec du code à votre solution 

  2. Importer comme ça

    import { XhrSubscription, subscribToXhr } from "your-path/xhr-extensions";
    
  3. Abonnez-vous comme ça

    const subscription = subscribeToXhr(xhr => {
      if (xhr.status != 200) return;
      ... do something here.
    });
    
  4. Désabonnez-vous lorsque vous n'en avez plus besoin

    subscription.unsubscribe();
    

Contenu du fichier '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
Oleg Polezky

Vous ne savez pas si vous pouvez le faire avec greasemonkey, mais si vous créez une extension, vous pouvez utiliser le service Observateur et l'observateur http-on-exam-response. 

0
Marius