index.js 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
  1. 'use strict';
  2. const TCPProxy = require('tcp-proxy.js');
  3. const debug = require('debug')('inspector-proxy');
  4. const urllib = require('urllib');
  5. const assert = require('assert');
  6. const EventEmitter = require('events').EventEmitter;
  7. const KEY = '__ws_proxy__';
  8. const linkPrefix =
  9. 'devtools://devtools/bundled/inspector.html?experiments=true&v8only=true&ws=127.0.0.1:';
  10. module.exports = class InterceptorProxy extends EventEmitter {
  11. constructor(options = {}) {
  12. super();
  13. const port = options.port;
  14. assert(port, 'proxy port is needed!');
  15. this.timeout = null;
  16. this.silent = !!options.silent;
  17. this.attached = false;
  18. this.inspectInfo = null;
  19. this.proxyPort = port;
  20. this.proxy = new TCPProxy({ port });
  21. this.url = `${linkPrefix}${port}/${KEY}`;
  22. this.proxy.on('close', () => {
  23. clearTimeout(this.timeout);
  24. });
  25. }
  26. start({ debugPort }) {
  27. this.debugPort = debugPort;
  28. return this.end()
  29. .then(() => {
  30. this.watchingInspect();
  31. return new Promise(resolve => this.once('attached', resolve));
  32. })
  33. .then(() =>
  34. this.proxy.createProxy({
  35. forwardPort: this.debugPort,
  36. interceptor: {
  37. client: chunk => {
  38. if (
  39. !this.inspectInfo ||
  40. chunk[0] !== 0x47 || // G
  41. chunk[1] !== 0x45 || // E
  42. chunk[2] !== 0x54 || // T
  43. chunk[3] !== 0x20 // space
  44. ) {
  45. return;
  46. }
  47. const content = chunk.toString();
  48. const hasKey = content.includes(KEY);
  49. debug('request %s', chunk);
  50. // remind user do not attach again with other client
  51. if (
  52. (hasKey || content.includes(this.inspectInfo.id)) &&
  53. !this.inspectInfo.webSocketDebuggerUrl
  54. ) {
  55. debug('inspectInfo %o', this.inspectInfo);
  56. console.warn(
  57. "Debugger has been attached, can't attach by other client"
  58. );
  59. }
  60. // replace key to websocket id
  61. if (hasKey) {
  62. debug('debugger attach request: %s', chunk);
  63. return content.replace(KEY, this.inspectInfo.id);
  64. }
  65. },
  66. server: chunk => {
  67. debug('response %s', chunk);
  68. },
  69. },
  70. })
  71. );
  72. }
  73. end() {
  74. return this.proxy.end();
  75. }
  76. log(info) {
  77. if (!this.silent) {
  78. console.log(info);
  79. }
  80. }
  81. watchingInspect(delay = 0) {
  82. clearTimeout(this.timeout);
  83. this.timeout = setTimeout(() => {
  84. urllib
  85. .request(`http://127.0.0.1:${this.debugPort}/json`, {
  86. dataType: 'json',
  87. })
  88. .then(({ data }) => {
  89. this.attach(data && data[0]);
  90. })
  91. .catch(e => {
  92. this.detach(e);
  93. });
  94. }, delay);
  95. }
  96. attach(data) {
  97. if (!this.attached) {
  98. this.log(`${this.debugPort} opened`);
  99. debug(`attached ${this.debugPort}: %O`, data);
  100. }
  101. this.attached = true;
  102. this.emit('attached', (this.inspectInfo = data));
  103. this.watchingInspect(1000);
  104. }
  105. detach(e) {
  106. if (e.code === 'HPE_INVALID_CONSTANT') {
  107. // old debugger protocol, it's not http response
  108. debug('legacy protocol');
  109. return this.attach();
  110. }
  111. if (this.attached) {
  112. this.emit('detached');
  113. this.log(`${this.debugPort} closed`);
  114. debug(`detached ${this.debugPort}: %O`, e);
  115. }
  116. this.attached = false;
  117. this.watchingInspect(1000);
  118. }
  119. };