UNPKG

5.44 kBJavaScriptView Raw
1var EventEmitter = require('events').EventEmitter,
2 inherits = require('util').inherits,
3 async = require('async'),
4 util = require('util'),
5 debug = require('debug')('node-inspector:injector');
6
7/**
8 * @param {{inject}} config
9 * @param {DebuggerClient} debuggerClient
10 * @constructor
11 */
12function InjectorClient(config, session) {
13 this._noInject = config.inject === false;
14 this._injected = false;
15 this._appPausedByInjector = false;
16
17 this._debuggerClient = session.debuggerClient;
18 this._frontendClient = session.frontendClient;
19 this._debuggerClient.on('close', this.close.bind(this));
20}
21
22inherits(InjectorClient, EventEmitter);
23
24Object.defineProperties(InjectorClient.prototype, {
25 /** @type {boolean} */
26 needsInject: {
27 get: function() {
28 return !this._noInject && !this._injected;
29 }
30 }
31});
32
33/**
34 * @param {string} sourceLine
35 * @type {boolean}
36 */
37InjectorClient.prototype.tryHandleDebuggerBreak = function(sourceLine, done) {
38 return done(this._appPausedByInjector);
39};
40
41/**
42 */
43InjectorClient.prototype.inject = function(cb) {
44 if (typeof cb !== 'function') {
45 cb = function(error, result) {};
46 }
47
48 var _water = [];
49
50 if (this.needsInject) {
51 _water.unshift(
52 this._injectRequire.bind(this),
53 this._inject.bind(this),
54 this._onInjection.bind(this)
55 );
56 }
57
58 if (this.needsInject && this._debuggerClient.isRunning) {
59 _water.unshift(this._pause.bind(this));
60 _water.push(this._resume.bind(this));
61 }
62
63 async.waterfall(_water, function(error) {
64 if (error) {
65 this._frontendClient.sendLogToConsole('error', error.toString());
66 }
67 cb(error);
68 }.bind(this));
69};
70
71InjectorClient.prototype._pause = function(cb) {
72 this._appPausedByInjector = true;
73 this._debuggerClient.request('suspend', {}, function() {
74 cb();
75 });
76};
77
78InjectorClient.prototype._resume = function(cb) {
79 this._debuggerClient.request('continue', undefined, function() {
80 this._appPausedByInjector = false;
81 cb();
82 }.bind(this));
83};
84
85// inject process._require before injecting others.
86InjectorClient.prototype._injectRequire = function(cb) {
87 var self = this;
88 var breakpoint;
89 var debuggerClient = this._debuggerClient;
90
91 function handleBreak(obj) {
92 if (!(breakpoint && obj.script.id === breakpoint.script_id &&
93 obj.breakpoints.indexOf(breakpoint.breakpoint) > -1)) {
94 return;
95 }
96
97 debuggerClient.removeListener('break', handleBreak);
98 debuggerClient.request('evaluate', {
99 expression: 'process._require = NativeModule.require',
100 }, function(err, res) {
101 if (err) {
102 cb(err);
103 } else {
104 debuggerClient.request('clearbreakpoint', {
105 breakpoint: breakpoint.breakpoint,
106 }, function(err) {
107 self._resume(function() {
108 cb(err);
109 });
110 });
111 }
112 });
113 }
114
115 debuggerClient.on('break', handleBreak);
116
117 debuggerClient.request('scripts', {
118 includeSource: true,
119 }, function(err, res) {
120 if (err) {
121 err.message = 'request scripts, ' + err.message;
122 return cb(err);
123 }
124 var desc;
125 for (var i = 0; i < res.length; i++) {
126 var item = res[i];
127 var name = item.name;
128 if (item.type === 'script' && (name === 'bootstrap_node.js' || name === 'node.js') ) {
129 desc = item;
130 break;
131 }
132 }
133
134 var source = desc.source.split('\n');
135 var line = -1;
136 for(var j = 0; j < source.length; j++) {
137 if (/^NativeModule\.require\s*=\s*function/.test(source[j].trim())) {
138 line = j;
139 break;
140 }
141 }
142
143 self._resume(function() {
144 debuggerClient.request('setbreakpoint', {
145 type: 'scriptId',
146 target: desc.id,
147 line: line + 1
148 }, function(err, res) {
149 if (err) {
150 err.message = 'request setbreakpoint, ' + err.message;
151 return cb(err);
152 }
153 breakpoint = res;
154 // we need to call NativeModule.require
155 // https://github.com/nodejs/node/blob/v7.8.0/lib/console.js#L95
156 debuggerClient.request('evaluate', {
157 global: true,
158 expression: 'try{console.assert();}catch(e){}',
159 });
160 });
161 });
162
163 });
164};
165
166/**
167 * @param {Number} NM - handle of NativeModule object
168 */
169InjectorClient.prototype._inject = function(cb) {
170 var injectorServerPath = JSON.stringify(require.resolve('./InjectorServer'));
171 var options = {
172 'v8-debug': require.resolve('v8-debug'),
173 'convert': require.resolve('./convert')
174 };
175
176 var args = {
177 global: true,
178 expression: '(function (require) {' +
179 'require("module")._load(' + injectorServerPath + ')' +
180 '(' + JSON.stringify(options) + ')' +
181 '})(process._require)'
182 };
183
184 this._debuggerClient.request(
185 'evaluate',
186 args,
187 function(error) {
188 cb(error);
189 }
190 );
191};
192
193/**
194 */
195InjectorClient.prototype._onInjection = function(cb) {
196 this._injected = true;
197 this.emit('inject');
198 cb();
199};
200
201InjectorClient.prototype.close = function() {
202 this._injected = false;
203 this._appPausedByInjector = false;
204 this.emit('close');
205};
206
207InjectorClient.prototype.injection = function(injection, options, callback) {
208 this._debuggerClient.request(
209 'evaluate',
210 {
211 expression: '(' + injection.toString() + ')' +
212 '(process._require, process._debugObject, ' + JSON.stringify(options) + ')',
213 global: true
214 },
215 callback
216 );
217};
218
219module.exports.InjectorClient = InjectorClient;