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