WebSocketInterceptor.js 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  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 NativeEventEmitter = require('../EventEmitter/NativeEventEmitter');
  11. import NativeWebSocketModule from './NativeWebSocketModule';
  12. const base64 = require('base64-js');
  13. const originalRCTWebSocketConnect = NativeWebSocketModule.connect;
  14. const originalRCTWebSocketSend = NativeWebSocketModule.send;
  15. const originalRCTWebSocketSendBinary = NativeWebSocketModule.sendBinary;
  16. const originalRCTWebSocketClose = NativeWebSocketModule.close;
  17. let eventEmitter: NativeEventEmitter;
  18. let subscriptions: Array<EventSubscription>;
  19. let closeCallback;
  20. let sendCallback;
  21. let connectCallback;
  22. let onOpenCallback;
  23. let onMessageCallback;
  24. let onErrorCallback;
  25. let onCloseCallback;
  26. let isInterceptorEnabled = false;
  27. /**
  28. * A network interceptor which monkey-patches RCTWebSocketModule methods
  29. * to gather all websocket network requests/responses, in order to show
  30. * their information in the React Native inspector development tool.
  31. */
  32. const WebSocketInterceptor = {
  33. /**
  34. * Invoked when RCTWebSocketModule.close(...) is called.
  35. */
  36. setCloseCallback(callback) {
  37. closeCallback = callback;
  38. },
  39. /**
  40. * Invoked when RCTWebSocketModule.send(...) or sendBinary(...) is called.
  41. */
  42. setSendCallback(callback) {
  43. sendCallback = callback;
  44. },
  45. /**
  46. * Invoked when RCTWebSocketModule.connect(...) is called.
  47. */
  48. setConnectCallback(callback) {
  49. connectCallback = callback;
  50. },
  51. /**
  52. * Invoked when event "websocketOpen" happens.
  53. */
  54. setOnOpenCallback(callback) {
  55. onOpenCallback = callback;
  56. },
  57. /**
  58. * Invoked when event "websocketMessage" happens.
  59. */
  60. setOnMessageCallback(callback) {
  61. onMessageCallback = callback;
  62. },
  63. /**
  64. * Invoked when event "websocketFailed" happens.
  65. */
  66. setOnErrorCallback(callback) {
  67. onErrorCallback = callback;
  68. },
  69. /**
  70. * Invoked when event "websocketClosed" happens.
  71. */
  72. setOnCloseCallback(callback) {
  73. onCloseCallback = callback;
  74. },
  75. isInterceptorEnabled() {
  76. return isInterceptorEnabled;
  77. },
  78. _unregisterEvents() {
  79. subscriptions.forEach(e => e.remove());
  80. subscriptions = [];
  81. },
  82. /**
  83. * Add listeners to the RCTWebSocketModule events to intercept them.
  84. */
  85. _registerEvents() {
  86. subscriptions = [
  87. eventEmitter.addListener('websocketMessage', ev => {
  88. if (onMessageCallback) {
  89. onMessageCallback(
  90. ev.id,
  91. ev.type === 'binary'
  92. ? WebSocketInterceptor._arrayBufferToString(ev.data)
  93. : ev.data,
  94. );
  95. }
  96. }),
  97. eventEmitter.addListener('websocketOpen', ev => {
  98. if (onOpenCallback) {
  99. onOpenCallback(ev.id);
  100. }
  101. }),
  102. eventEmitter.addListener('websocketClosed', ev => {
  103. if (onCloseCallback) {
  104. onCloseCallback(ev.id, {code: ev.code, reason: ev.reason});
  105. }
  106. }),
  107. eventEmitter.addListener('websocketFailed', ev => {
  108. if (onErrorCallback) {
  109. onErrorCallback(ev.id, {message: ev.message});
  110. }
  111. }),
  112. ];
  113. },
  114. enableInterception() {
  115. if (isInterceptorEnabled) {
  116. return;
  117. }
  118. eventEmitter = new NativeEventEmitter(NativeWebSocketModule);
  119. WebSocketInterceptor._registerEvents();
  120. // Override `connect` method for all RCTWebSocketModule requests
  121. // to intercept the request url, protocols, options and socketId,
  122. // then pass them through the `connectCallback`.
  123. NativeWebSocketModule.connect = function(
  124. url,
  125. protocols,
  126. options,
  127. socketId,
  128. ) {
  129. if (connectCallback) {
  130. connectCallback(url, protocols, options, socketId);
  131. }
  132. originalRCTWebSocketConnect.apply(this, arguments);
  133. };
  134. // Override `send` method for all RCTWebSocketModule requests to intercept
  135. // the data sent, then pass them through the `sendCallback`.
  136. NativeWebSocketModule.send = function(data, socketId) {
  137. if (sendCallback) {
  138. sendCallback(data, socketId);
  139. }
  140. originalRCTWebSocketSend.apply(this, arguments);
  141. };
  142. // Override `sendBinary` method for all RCTWebSocketModule requests to
  143. // intercept the data sent, then pass them through the `sendCallback`.
  144. NativeWebSocketModule.sendBinary = function(data, socketId) {
  145. if (sendCallback) {
  146. sendCallback(WebSocketInterceptor._arrayBufferToString(data), socketId);
  147. }
  148. originalRCTWebSocketSendBinary.apply(this, arguments);
  149. };
  150. // Override `close` method for all RCTWebSocketModule requests to intercept
  151. // the close information, then pass them through the `closeCallback`.
  152. NativeWebSocketModule.close = function() {
  153. if (closeCallback) {
  154. if (arguments.length === 3) {
  155. closeCallback(arguments[0], arguments[1], arguments[2]);
  156. } else {
  157. closeCallback(null, null, arguments[0]);
  158. }
  159. }
  160. originalRCTWebSocketClose.apply(this, arguments);
  161. };
  162. isInterceptorEnabled = true;
  163. },
  164. _arrayBufferToString(data) {
  165. const value = base64.toByteArray(data).buffer;
  166. if (value === undefined || value === null) {
  167. return '(no value)';
  168. }
  169. if (
  170. typeof ArrayBuffer !== 'undefined' &&
  171. typeof Uint8Array !== 'undefined' &&
  172. value instanceof ArrayBuffer
  173. ) {
  174. return `ArrayBuffer {${String(Array.from(new Uint8Array(value)))}}`;
  175. }
  176. return value;
  177. },
  178. // Unpatch RCTWebSocketModule methods and remove the callbacks.
  179. disableInterception() {
  180. if (!isInterceptorEnabled) {
  181. return;
  182. }
  183. isInterceptorEnabled = false;
  184. NativeWebSocketModule.send = originalRCTWebSocketSend;
  185. NativeWebSocketModule.sendBinary = originalRCTWebSocketSendBinary;
  186. NativeWebSocketModule.close = originalRCTWebSocketClose;
  187. NativeWebSocketModule.connect = originalRCTWebSocketConnect;
  188. connectCallback = null;
  189. closeCallback = null;
  190. sendCallback = null;
  191. onOpenCallback = null;
  192. onMessageCallback = null;
  193. onCloseCallback = null;
  194. onErrorCallback = null;
  195. WebSocketInterceptor._unregisterEvents();
  196. },
  197. };
  198. module.exports = WebSocketInterceptor;