Ajouter un «hook» à toutes les requêtes AJAX sur une page

104

Je voudrais savoir s'il est possible de «se connecter» à chaque requête AJAX (soit au moment où elle est sur le point d'être envoyée, soit sur des événements) et d'effectuer une action. À ce stade, je suppose qu'il existe d'autres scripts tiers sur la page. Certains d'entre eux peuvent utiliser jQuery, tandis que d'autres ne le font pas. Est-ce possible?

Yuliy
la source
C'est possible avec jQuery, donc c'est possible avec du javascript ancien, mais vous auriez besoin d'au moins 2 "hooks" pour chacun d'eux. Quoi qu'il en soit, pourquoi utiliser les deux sur la même page?
yoda
2
Que diriez-vous d'utiliser cette bibliothèque? github.com/slorber/ajax-interceptor
Sebastien Lorber
C'est une bonne solution: stackoverflow.com/questions/25335648/…
phazei
2
Remarque: Les réponses à cette question ne couvrent pas les appels Ajax effectués à partir de la nouvelle fetch()API maintenant dans les navigateurs modernes.
jfriend00
1
@ nirvana-msu - Peut-être ceci: github.com/werk85/fetch-intercept/blob/develop/src/index.js
jfriend00

Réponses:

110

Inspiré par la réponse d' aviv , j'ai fait une petite enquête et c'est ce que j'ai proposé.
Je ne suis pas sûr que ce soit très utile selon les commentaires du script et bien sûr ne fonctionnera que pour les navigateurs utilisant un objet XMLHttpRequest natif .
Je pense que cela fonctionnera si des bibliothèques javascript sont utilisées car elles utiliseront l'objet natif si possible.

function addXMLRequestCallback(callback){
    var oldSend, i;
    if( XMLHttpRequest.callbacks ) {
        // we've already overridden send() so just add the callback
        XMLHttpRequest.callbacks.push( callback );
    } else {
        // create a callback queue
        XMLHttpRequest.callbacks = [callback];
        // store the native send()
        oldSend = XMLHttpRequest.prototype.send;
        // override the native send()
        XMLHttpRequest.prototype.send = function(){
            // process the callback queue
            // the xhr instance is passed into each callback but seems pretty useless
            // you can't tell what its destination is or call abort() without an error
            // so only really good for logging that a request has happened
            // I could be wrong, I hope so...
            // EDIT: I suppose you could override the onreadystatechange handler though
            for( i = 0; i < XMLHttpRequest.callbacks.length; i++ ) {
                XMLHttpRequest.callbacks[i]( this );
            }
            // call the native send()
            oldSend.apply(this, arguments);
        }
    }
}

// e.g.
addXMLRequestCallback( function( xhr ) {
    console.log( xhr.responseText ); // (an empty string)
});
addXMLRequestCallback( function( xhr ) {
    console.dir( xhr ); // have a look if there is anything useful here
});
meouw
la source
ce serait bien d'étendre cette réponse pour prendre en charge les hooks post-request
Sebastien Lorber
1
Sur la base de votre implémentation, j'ai publié quelque chose sur NPM qui fonctionne à la fois avec les demandes et les réponses! github.com/slorber/ajax-interceptor
Sebastien Lorber
4
console.log (xhr.responseText) est une chaîne vide car à ce moment, xhr est vide. si vous passez la variable xhr à la variable globale et définissez un délai de quelques secondes, vous pourrez accéder directement aux propriétés
Toolkit
5
Bonne réponse. Si vous souhaitez obtenir les données de réponse, vérifiez le readyState et l'état lorsque l'état a changé. xhr.onreadystatechange = function () {if (xhr.readyState == 4 && xhr.status == 200) {console.log (JSON.parse (xhr.responseText)); }}
Stanley Wang
1
@ucheng Si nous ajoutons onreadystatechange de xhr, cela remplacera-t-il la méthode originale de changement d'état prêt mieux utiliser xhrObj.addEventListener ("load", fn)
Mohamed Hussain
127

REMARQUE: La réponse acceptée ne donne pas la réponse réelle car elle est appelée trop tôt.

Vous pouvez faire ceci qui interceptera génériquement n'importe quel AJAX globalement et ne gâchera aucun callback, etc. qui ont peut-être été assignés par des bibliothèques AJAX tierces.

(function() {
    var origOpen = XMLHttpRequest.prototype.open;
    XMLHttpRequest.prototype.open = function() {
        console.log('request started!');
        this.addEventListener('load', function() {
            console.log('request completed!');
            console.log(this.readyState); //will always be 4 (ajax is completed successfully)
            console.log(this.responseText); //whatever the response was
        });
        origOpen.apply(this, arguments);
    };
})();

Quelques autres documents sur ce que vous pouvez faire ici avec l'API addEventListener ici:

https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/Using_XMLHttpRequest#Monitoring_progress

(Notez que cela ne fonctionne pas <= IE8)

John Culviner
la source
Merci! Cela fonctionne parfaitement. Je ne modifie qu'un peu: this.statusretourne l'état du serveur dans ce cas 200si la requête est OK, 404 si l'élément n'a pas été trouvé, 500, etc. Mais le code fonctionne parfaitement.
MrMins
1
Cela peut vous sembler vieux, mais vous méritez tout l'amour pour cela. J'étais profondément coincé. Merci beaucoup.
Carles Alcolea
Simplement parce que j'ai un peu cherché ça. L' "load"événement n'est appelé qu'en cas de succès. Si vous ne vous souciez pas du résultat (juste que la requête s'est terminée), vous pouvez utiliser l' "loadend"événement
Romuald Brunet
Il a fallu plusieurs jours pour trouver cela. Merci pour ce morceau de génie.
David
J'ai essayé de nombreuses réponses et cela fonctionne parfaitement avec WKWebView. J'avais du mal à utiliser les événements Ajax car jQuery peut ne pas être chargé avant d'appeler evaluJavascript ou d'utiliser addUserScript. Je vous remercie!
Jake Cheng
21

Puisque vous mentionnez jquery, je sais offres jquery une .ajaxSetup()méthode qui définit les options globales ajax qui incluent les déclencheurs d'événements tels que success, erroret beforeSend- ce qui est ce qui ressemble à ce que vous recherchez.

$.ajaxSetup({
    beforeSend: function() {
        //do stuff before request fires
    }
});

bien sûr, vous devrez vérifier la disponibilité de jQuery sur n'importe quelle page sur laquelle vous essayez d'utiliser cette solution.

jondavidjohn
la source
1
Merci pour la suggestion, mais cela n'intercepte malheureusement pas les appels AJAX qui ne sont pas effectués avec AJAX.
Yuliy
8
Dans l'actualité d'aujourd'hui: Licornes tuées par des appels AJAX qui ne sont pas effectués avec AJAX
Dashu
3
il ne s'agit pas d'un objet AJAX. Mais même dans ce cas, vous pourrez utiliser la même instance de jquery à chaque fois
Shishir Arora
1
Très utile. Je veux ajouter, un autre déclencheur est statusCode. En branchant quelque chose comme statusCode: {403: function () {error msg}, vous pouvez fournir un contrôle global auth / perms / role à toutes les fonctions ajax sans avoir à réécrire une seule requête .ajax.
Watercayman
8

Il y a une astuce pour le faire.

Avant que tous les scripts ne s'exécutent, prenez l'objet XHMHttpReuqest d'origine et enregistrez-le dans une autre variable. Remplacez ensuite le XMLHttpRequest d'origine et dirigez tous les appels vers celui-ci via votre propre objet.

Code Psuedo:

 var savd = XMLHttpRequest;
 XMLHttpRequest.prototype = function() {
         this.init = function() {
         }; // your code
         etc' etc'
 };
aviv
la source
10
Cette réponse n'est pas tout à fait correcte, si vous modifiez le prototype d'un objet, même celui enregistré sera changé. De plus, le prototype entier est remplacé par une fonction qui interrompra toutes les requêtes ajax. Cela m'a cependant incité à offrir une réponse.
meouw
8

J'ai trouvé une bonne bibliothèque sur Github qui fait bien le travail, vous devez l'inclure avant tout autre fichier js

https://github.com/jpillora/xhook

voici un exemple qui ajoute un en-tête http à toute réponse entrante

xhook.after(function(request, response) {
  response.headers['Foo'] = 'Bar';
});
Mohamed Selim
la source
2
Elle est bonne! Mais n'a pas fonctionné sur l'application Cordova, même avec un dernier plugin de vue Web Chrome pour Crosswalk!
Islam Attrash
1
Cela a parfaitement fonctionné pour mes besoins particuliers: dans Wordpress, filtrage des événements du calendrier complet du plugin Events Organizer
Alfredo Yong
Ne fonctionne pas pour toutes les demandes, certains fetch et xhr ok, mais par exemple, il n'attrape pas xhr de google recaptcha
Sergio Quintero
@SergioQuintero: Je suppose que c'est votre problème GitHub: github.com/jpillora/xhook/issues/102 . Pensez à supprimer votre commentaire ici, car ce n'était pas un problème avec la bibliothèque.
thirtydot
@SergioQuintero Tout remplacement des interfaces XHR ne se produit que dans ce document, pas globalement dans le navigateur car ce serait une faille massive de sécurité / confidentialité. reCAPTCHA s'exécute dans une iframe et vous ne pouvez pas y exécuter le remplacement.
Walf
5

En utilisant la réponse de "meouw", je suggère d'utiliser la solution suivante si vous voulez voir les résultats de la demande

function addXMLRequestCallback(callback) {
    var oldSend, i;
    if( XMLHttpRequest.callbacks ) {
        // we've already overridden send() so just add the callback
        XMLHttpRequest.callbacks.push( callback );
    } else {
        // create a callback queue
        XMLHttpRequest.callbacks = [callback];
        // store the native send()
        oldSend = XMLHttpRequest.prototype.send;
        // override the native send()
        XMLHttpRequest.prototype.send = function() {
            // call the native send()
            oldSend.apply(this, arguments);

            this.onreadystatechange = function ( progress ) {
               for( i = 0; i < XMLHttpRequest.callbacks.length; i++ ) {
                    XMLHttpRequest.callbacks[i]( progress );
                }
            };       
        }
    }
}

addXMLRequestCallback( function( progress ) {
    if (typeof progress.srcElement.responseText != 'undefined' &&                        progress.srcElement.responseText != '') {
        console.log( progress.srcElement.responseText.length );
    }
});
héroïne
la source
3

jquery ...

<script>
   $(document).ajaxSuccess(
        function(event, xhr, settings){ 
          alert(xhr.responseText);
        }
   );
</script>
Vladimir Miroshnichenko
la source
1
jQuery n'attrapera pas les demandes effectuées à l'aide d'autres bibliothèques. Par exemple ExtJS. Si vous n'utilisez que jQuery, c'est une bonne réponse. Sinon, cela ne fonctionnera pas tout le temps.
npiani
1
il n'attrapera même pas les requêtes jquery si l'instance jquery est différente. Changement de protype si nécessaire
Shishir Arora
3

En plus de la réponse de meouw, j'ai dû injecter du code dans une iframe qui intercepte les appels XHR, et j'ai utilisé la réponse ci-dessus. Cependant, j'ai dû changer

XMLHttpRequest.prototype.send = function(){

À:

XMLHttpRequest.prototype.send = function(body)

Et j'ai dû changer

oldSend.apply(this, arguments);

À:

oldSend.call(this, body);

Cela était nécessaire pour le faire fonctionner dans IE9 avec le mode document IE8 . Si cette modification n'a pas été effectuée, certains rappels générés par le framework de composants (Visual WebGUI) ne fonctionnaient pas. Plus d'informations sur ces liens:

Sans ces modifications, les publications AJAX ne se sont pas terminées.

Walf
la source