XHRInterceptor.js 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  1. /**
  2. * Copyright (c) Facebook, Inc. and its affiliates.
  3. *
  4. * This source code is licensed under the MIT license found in the
  5. * LICENSE file in the root directory of this source tree.
  6. *
  7. * @format
  8. */
  9. 'use strict';
  10. const XMLHttpRequest = require('./XMLHttpRequest');
  11. const originalXHROpen = XMLHttpRequest.prototype.open;
  12. const originalXHRSend = XMLHttpRequest.prototype.send;
  13. const originalXHRSetRequestHeader = XMLHttpRequest.prototype.setRequestHeader;
  14. let openCallback;
  15. let sendCallback;
  16. let requestHeaderCallback;
  17. let headerReceivedCallback;
  18. let responseCallback;
  19. let isInterceptorEnabled = false;
  20. /**
  21. * A network interceptor which monkey-patches XMLHttpRequest methods
  22. * to gather all network requests/responses, in order to show their
  23. * information in the React Native inspector development tool.
  24. * This supports interception with XMLHttpRequest API, including Fetch API
  25. * and any other third party libraries that depend on XMLHttpRequest.
  26. */
  27. const XHRInterceptor = {
  28. /**
  29. * Invoked before XMLHttpRequest.open(...) is called.
  30. */
  31. setOpenCallback(callback) {
  32. openCallback = callback;
  33. },
  34. /**
  35. * Invoked before XMLHttpRequest.send(...) is called.
  36. */
  37. setSendCallback(callback) {
  38. sendCallback = callback;
  39. },
  40. /**
  41. * Invoked after xhr's readyState becomes xhr.HEADERS_RECEIVED.
  42. */
  43. setHeaderReceivedCallback(callback) {
  44. headerReceivedCallback = callback;
  45. },
  46. /**
  47. * Invoked after xhr's readyState becomes xhr.DONE.
  48. */
  49. setResponseCallback(callback) {
  50. responseCallback = callback;
  51. },
  52. /**
  53. * Invoked before XMLHttpRequest.setRequestHeader(...) is called.
  54. */
  55. setRequestHeaderCallback(callback) {
  56. requestHeaderCallback = callback;
  57. },
  58. isInterceptorEnabled() {
  59. return isInterceptorEnabled;
  60. },
  61. enableInterception() {
  62. if (isInterceptorEnabled) {
  63. return;
  64. }
  65. // Override `open` method for all XHR requests to intercept the request
  66. // method and url, then pass them through the `openCallback`.
  67. XMLHttpRequest.prototype.open = function(method, url) {
  68. if (openCallback) {
  69. openCallback(method, url, this);
  70. }
  71. originalXHROpen.apply(this, arguments);
  72. };
  73. // Override `setRequestHeader` method for all XHR requests to intercept
  74. // the request headers, then pass them through the `requestHeaderCallback`.
  75. XMLHttpRequest.prototype.setRequestHeader = function(header, value) {
  76. if (requestHeaderCallback) {
  77. requestHeaderCallback(header, value, this);
  78. }
  79. originalXHRSetRequestHeader.apply(this, arguments);
  80. };
  81. // Override `send` method of all XHR requests to intercept the data sent,
  82. // register listeners to intercept the response, and invoke the callbacks.
  83. XMLHttpRequest.prototype.send = function(data) {
  84. if (sendCallback) {
  85. sendCallback(data, this);
  86. }
  87. if (this.addEventListener) {
  88. this.addEventListener(
  89. 'readystatechange',
  90. () => {
  91. if (!isInterceptorEnabled) {
  92. return;
  93. }
  94. if (this.readyState === this.HEADERS_RECEIVED) {
  95. const contentTypeString = this.getResponseHeader('Content-Type');
  96. const contentLengthString = this.getResponseHeader(
  97. 'Content-Length',
  98. );
  99. let responseContentType, responseSize;
  100. if (contentTypeString) {
  101. responseContentType = contentTypeString.split(';')[0];
  102. }
  103. if (contentLengthString) {
  104. responseSize = parseInt(contentLengthString, 10);
  105. }
  106. if (headerReceivedCallback) {
  107. headerReceivedCallback(
  108. responseContentType,
  109. responseSize,
  110. this.getAllResponseHeaders(),
  111. this,
  112. );
  113. }
  114. }
  115. if (this.readyState === this.DONE) {
  116. if (responseCallback) {
  117. responseCallback(
  118. this.status,
  119. this.timeout,
  120. this.response,
  121. this.responseURL,
  122. this.responseType,
  123. this,
  124. );
  125. }
  126. }
  127. },
  128. false,
  129. );
  130. }
  131. originalXHRSend.apply(this, arguments);
  132. };
  133. isInterceptorEnabled = true;
  134. },
  135. // Unpatch XMLHttpRequest methods and remove the callbacks.
  136. disableInterception() {
  137. if (!isInterceptorEnabled) {
  138. return;
  139. }
  140. isInterceptorEnabled = false;
  141. XMLHttpRequest.prototype.send = originalXHRSend;
  142. XMLHttpRequest.prototype.open = originalXHROpen;
  143. XMLHttpRequest.prototype.setRequestHeader = originalXHRSetRequestHeader;
  144. responseCallback = null;
  145. openCallback = null;
  146. sendCallback = null;
  147. headerReceivedCallback = null;
  148. requestHeaderCallback = null;
  149. },
  150. };
  151. module.exports = XHRInterceptor;