whistle 2.10.1 → 2.10.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/assets/js/log.js CHANGED
@@ -237,34 +237,31 @@
237
237
  return prefixPath;
238
238
  }
239
239
 
240
- var index = 0;
241
- var MAX_LEN = 1024 * 56;
242
- function addLog(level, text) {
243
- var img = new Image();
244
- var timer;
245
- if (index > 9999) {
246
- index = 0;
247
- }
248
- var logStr = encodeURIComponent(text && (text + ''));
249
- if (logStr.length > MAX_LEN) {
250
- logStr = logStr.substring(0, MAX_LEN);
251
- var percIndex = logStr.indexOf('%', MAX_LEN - 3);
252
- if (percIndex !== -1) {
253
- logStr = logStr.substring(0, percIndex);
254
- }
240
+ var MAX_SIZE = 1024 * 512;
241
+ var MAX_VAL_SIZE = 1024 * 64;
242
+ var LOG_ID = '$LOG_ID';
243
+ var cacheList = [];
244
+ var origin = (location.origin || (location.protocol + '//' + location.host)) + '/';
245
+
246
+ function setLog() {
247
+ if (!cacheList.length) {
248
+ return;
255
249
  }
256
- var baseUrl = '$BASE_URL' + getPathPrefix();
257
- img.src = baseUrl + '$LOG_CGI?id=$LOG_ID&level=' + level + '&text=' + logStr
258
- + '&t=' + new Date().getTime() + '&' + ++index;
259
- var preventGC = function() {
260
- img.onload = img.onerror = null;
261
- clearTimeout(timer);
262
- };
263
- img.onload = img.onerror = preventGC;
264
- timer = setTimeout(preventGC, 3000);
250
+ var list = cacheList.splice(0, 10);
251
+ var xhr = new XMLHttpRequest();
252
+ var url = '$BASE_URL' + getPathPrefix() + '$LOG_CGI';
253
+ xhr.open('POST', url);
254
+ xhr.setRequestHeader('Content-Type', url.indexOf(origin) ? 'text/plain' : 'application/json');
255
+ xhr.onload = function() {};
256
+ xhr.send(JSON.stringify({ list: list }));
257
+ }
258
+
259
+ function addLog(level, text) {
260
+ cacheList.push([new Date().getTime(), level, LOG_ID, text].join('\r'));
265
261
  if (typeof window.onWhistleLogSend === 'function') {
266
262
  window.onWhistleLogSend(level, text);
267
263
  }
264
+ setTimeout(setLog, 60);
268
265
  }
269
266
 
270
267
  function getPageInfo() {
@@ -295,18 +292,37 @@
295
292
  return -1;
296
293
  }
297
294
 
295
+ function fomatText(str, size) {
296
+ var len = str.length;
297
+ if (len <= size + 9) {
298
+ return str;
299
+ }
300
+ return str.substring(0, size) + '...(' + (len - size) + ')';
301
+ }
302
+
298
303
  function stringifyObj(obj) {
299
304
  if (typeof obj === 'string') {
300
305
  return obj;
301
306
  }
307
+ var str;
302
308
  try {
303
- return JSON.stringify(obj);
309
+ str = JSON.stringify(obj);
310
+ if (str.length <= MAX_SIZE) {
311
+ return str;
312
+ }
304
313
  } catch(e) {}
305
314
  try {
306
315
  var keyList = [];
307
316
  var valList = [];
308
- return JSON.stringify(obj, function(key, value) {
309
- if (value && typeof value === 'object') {
317
+ str = JSON.stringify(obj, function(key, value) {
318
+ if (!value) {
319
+ return value;
320
+ }
321
+ var type = typeof value;
322
+ if (type === 'string' && value.length > MAX_VAL_SIZE) {
323
+ return fomatText(value, MAX_VAL_SIZE);
324
+ }
325
+ if (type === 'object') {
310
326
  var index = arrayIndexOf(valList, value);
311
327
  valList.push(value);
312
328
  keyList.push(key);
@@ -316,6 +332,7 @@
316
332
  }
317
333
  return value;
318
334
  });
335
+ return fomatText(str, MAX_SIZE);
319
336
  } catch(e) {}
320
337
  }
321
338
 
package/bin/use.js CHANGED
@@ -1,6 +1,7 @@
1
1
  var path = require('path');
2
2
  var http = require('http');
3
3
  var url = require('url');
4
+ var fs = require('fs');
4
5
  var util = require('./util');
5
6
  var importModule = require('./import');
6
7
  var pkg = require('../package.json');
@@ -10,26 +11,20 @@ var isRunning = util.isRunning;
10
11
  var error = util.error;
11
12
  var warn = util.warn;
12
13
  var info = util.info;
13
- var readConfig = util.readConfig;
14
14
  var MAX_RULES_LEN = 1024 * 256;
15
15
  var DEFAULT_OPTIONS = util.DEFAULT_OPTIONS;
16
- var options;
17
16
 
18
- function showStartWhistleTips(storage, isClient) {
19
- if (isClient) {
20
- error('No running Whistle client. Please install and start the latest Whistle client: https://github.com/avwo/whistle-client');
21
- } else {
22
- error('No running Whistle instances. Execute `w2 start' + (storage ? ' -S ' + storage : '') + '` to start Whistle on the cli');
17
+ function handleRules(options, filepath, callback) {
18
+ if (typeof filepath === 'object') {
19
+ return callback(filepath);
23
20
  }
24
- }
25
-
26
- function handleRules(filepath, callback, port) {
27
21
  importModule(filepath, function(getRules) {
28
22
  if (typeof getRules !== 'function') {
29
23
  return callback(getRules);
30
24
  }
31
25
  var opts = {
32
- port: port,
26
+ host: options.host,
27
+ port: options.port,
33
28
  existsPlugin: existsPlugin
34
29
  };
35
30
  if (options && options.host) {
@@ -43,45 +38,77 @@ function getString(str) {
43
38
  return typeof str !== 'string' ? '' : str.trim();
44
39
  }
45
40
 
46
- function existsPlugin(name) {
41
+ function existsPlugin(name, callback, pluginPaths) {
42
+ if (typeof callback !== 'function') {
43
+ callback = null;
44
+ pluginPaths = null;
45
+ }
47
46
  if (!name || typeof name !== 'string') {
48
- return false;
47
+ return callback ? callback(false) : false;
48
+ }
49
+ pluginPaths = pluginPaths || require('../lib/plugins/module-paths').getPaths().slice();
50
+ var len = pluginPaths.length;
51
+ if (len === 0) {
52
+ return callback ? callback(false) : false;
53
+ }
54
+ if (callback) {
55
+ var pkgFile = path.join(pluginPaths.shift(), name, 'package.json');
56
+ common.getStat(pkgFile, function(err, stats) {
57
+ if (err || !stats.isFile()) {
58
+ return existsPlugin(name, callback, pluginPaths);
59
+ }
60
+ callback(true);
61
+ });
62
+ return;
49
63
  }
50
- var pluginPaths = require('../lib/plugins/module-paths').getPaths();
51
- for (var i = 0, len = pluginPaths.length; i < len; i++) {
52
- var stats = common.getStatSync(path.join(pluginPaths[i], name));
53
- if (stats && stats.isDirectory()) {
64
+ for (var i = 0; i < len; i++) {
65
+ var stats = common.getStatSync(path.join(pluginPaths[i], name, 'package.json'));
66
+ if (stats && stats.isFile()) {
54
67
  return true;
55
68
  }
56
69
  }
57
70
  return false;
58
71
  }
59
72
 
60
- var reqOptions;
61
- function request(body, callback) {
62
- if (!reqOptions) {
63
- reqOptions = url.parse('http://' + util.joinIpPort(options.host || '127.0.0.1', options.port) + '/cgi-bin/rules/project');
64
- reqOptions.headers = {
65
- 'content-type': 'application/x-www-form-urlencoded'
66
- };
67
- if (options.specialAuth) {
68
- reqOptions.headers['x-whistle-special-auth'] = options.specialAuth;
73
+ function getHost(options) {
74
+ return util.joinIpPort(options.host || DEFAULT_OPTIONS.host, options.port);
75
+ }
76
+
77
+ function getReqOptions(options) {
78
+ var reqOptions = url.parse('http://' + getHost(options) + '/cgi-bin/rules/project');
79
+ reqOptions.headers = {
80
+ 'content-type': 'application/x-www-form-urlencoded'
81
+ };
82
+ if (options.specialAuth) {
83
+ reqOptions.headers['x-whistle-special-auth'] = options.specialAuth;
84
+ }
85
+ reqOptions.method = 'POST';
86
+ if (options.username || options.password) {
87
+ var auth = [options.username || '', options.password || ''].join(':');
88
+ reqOptions.headers.authorization = 'Basic ' + new Buffer.from(auth).toString('base64');
89
+ }
90
+ return reqOptions;
91
+ }
92
+
93
+ function request(reqOptions, body, cb) {
94
+ var done;
95
+ var handleCb = function(err, data) {
96
+ if (done) {
97
+ return;
69
98
  }
70
- reqOptions.method = 'POST';
71
- if (options.username || options.password) {
72
- var auth = [options.username || '', options.password || ''].join(':');
73
- reqOptions.headers.authorization = 'Basic ' + new Buffer.from(auth).toString('base64');
99
+ done = true;
100
+ if (err) {
101
+ if (typeof err !== 'string') {
102
+ err = err.message || String(err);
103
+ }
104
+ return cb(err);
74
105
  }
75
- }
106
+ cb(null, data);
107
+ };
76
108
  var req = http.request(reqOptions, function(res) {
77
- util.getBody(res, function(err, data) {
78
- if (err) {
79
- throw err;
80
- }
81
- callback(data);
82
- });
109
+ util.getBody(res, handleCb);
83
110
  });
84
- // 不处理错误,直接抛出终止进程
111
+ req.on('error', handleCb);
85
112
  req.end(body);
86
113
  }
87
114
 
@@ -96,7 +123,7 @@ function checkDefault(running, storage, isClient, callback) {
96
123
  callback && callback(err);
97
124
  callback = null;
98
125
  };
99
- var req = http.get('http://' + DEFAULT_OPTIONS.host + ':' + DEFAULT_OPTIONS.port + '/cgi-bin/status', function(res) {
126
+ var req = http.get('http://' + getHost(DEFAULT_OPTIONS) + '/cgi-bin/status', function(res) {
100
127
  res.on('error', execCallback);
101
128
  util.getBody(res, function(err, data) {
102
129
  if (err || !data || data.name !== pkg.name || data.storage !== storage) {
@@ -109,85 +136,193 @@ function checkDefault(running, storage, isClient, callback) {
109
136
  req.end();
110
137
  }
111
138
 
112
- function readClientConfig() {
113
- var procPath = path.join(common.getHomedir(), '.whistle_client.pid');
114
- var info = (common.readFileTextSync(procPath) || '').split(',');
115
- if (info && info.length === 4) {
116
- return {
117
- pid: info[0],
139
+ function handleClientConfig(text, callback) {
140
+ text = text.split(',');
141
+ if (text.length === 4) {
142
+ return callback(null, {
143
+ pid: text[0],
118
144
  options: {
119
- host: info[1],
120
- port: info[2],
121
- specialAuth: info[3]
145
+ host: text[1],
146
+ port: text[2],
147
+ specialAuth: text[3]
122
148
  }
123
- };
149
+ });
124
150
  }
151
+ callback(new Error('Invalid client config'));
125
152
  }
126
153
 
127
- module.exports = function(filepath, storage, force, isClient) {
128
- var config;
154
+ function readConfig(storage, isClient, callback) {
155
+ var configFile = isClient ? path.join(common.getHomedir(), '.whistle_client.pid') : util.getConfigFile(storage);
156
+ fs.readFile(configFile, { encoding: 'utf8' }, function(err, text) {
157
+ if (err) {
158
+ return callback(err);
159
+ }
160
+ if (isClient) {
161
+ return handleClientConfig(text || '', callback);
162
+ }
163
+ try {
164
+ text = JSON.parse(text);
165
+ if (text) {
166
+ util.formatOptions(text.options);
167
+ callback(null, text);
168
+ } else {
169
+ callback(new Error('Invalid config'));
170
+ }
171
+ } catch(e) {
172
+ callback(e);
173
+ }
174
+ });
175
+ }
176
+
177
+ function removeSpecialAuth(config) {
178
+ if (!config) {
179
+ return config;
180
+ }
181
+ delete config.options.specialAuth;
182
+ return config.options;
183
+ }
184
+
185
+ var getNoRunningError = function(storage, isClient) {
186
+ if (isClient) {
187
+ return new Error('No running Whistle client. Please install and start the latest Whistle client: https://github.com/avwo/whistle-client');
188
+ }
189
+ return new Error('No running Whistle instances. Execute `w2 start' + (storage ? ' -S ' + storage : '') + '` to start Whistle on the cli');
190
+ };
191
+
192
+ function getConfig(filepath, storage, force, isClient, cb) {
129
193
  var dir = '';
194
+ var result;
195
+ var callback;
196
+ if (typeof storage === 'boolean') {
197
+ var temp = force;
198
+ force = storage;
199
+ storage = temp;
200
+ }
201
+ if (typeof storage === 'function') {
202
+ callback = storage;
203
+ storage = '';
204
+ }
205
+ if (filepath && typeof filepath === 'object') {
206
+ storage = storage || filepath.storage;
207
+ isClient = isClient || filepath.isClient || filepath.client;
208
+ force = force || filepath.force;
209
+ if (common.isString(filepath.name) && common.isString(filepath.rules)) {
210
+ result = filepath;
211
+ }
212
+ filepath = filepath.filepath;
213
+ }
130
214
  if (isClient) {
131
215
  storage = '';
132
- config = readClientConfig() || '';
133
216
  } else {
134
217
  storage = storage || '';
135
218
  dir = encodeURIComponent(storage);
136
- config = readConfig(dir) || '';
137
- if (config.options) {
138
- delete config.options.specialAuth;
139
- }
140
219
  }
141
- options = config.options || '';
142
- isRunning(options && config.pid, function(running) {
143
- checkDefault(running, dir, isClient, function(err, port) {
144
- if (err) {
145
- return showStartWhistleTips(storage, isClient);
220
+ readConfig(dir, isClient, function(err, config) {
221
+ if (err) {
222
+ return cb(err.code === 'ENOENT' ? getNoRunningError(storage, isClient) : err);
223
+ }
224
+ !isClient && removeSpecialAuth(config);
225
+ config.dir = dir;
226
+ config.filepath = filepath;
227
+ config.force = force;
228
+ config.isClient = isClient;
229
+ config.result = result;
230
+ isRunning(config.options && config.pid, function(running) {
231
+ checkDefault(running, config.dir, isClient, function(e, port) {
232
+ if (e) {
233
+ return cb(getNoRunningError(storage, isClient));
234
+ }
235
+ var options = config.options || {};
236
+ if (port) {
237
+ options = DEFAULT_OPTIONS;
238
+ } else {
239
+ options.host = options.host || DEFAULT_OPTIONS.host;
240
+ options.port = options.port > 0 ? options.port : pkg.port;
241
+ }
242
+ config.options = options;
243
+ cb(e, config, callback);
244
+ });
245
+ });
246
+ });
247
+ }
248
+
249
+ module.exports = function(filepath, storage, force, isClient) {
250
+ getConfig(filepath, storage, force, isClient, function(err, config, callback) {
251
+ if (err) {
252
+ return callback ? callback(err) : error(err.message);
253
+ }
254
+ var options = config.options;
255
+ isClient = config.isClient;
256
+ var handleError = function(msg) {
257
+ if (callback) {
258
+ callback(new Error(msg));
259
+ } else {
260
+ error(msg);
146
261
  }
147
- filepath = path.resolve(filepath || '.whistle.js');
148
- if (port) {
149
- options = DEFAULT_OPTIONS;
262
+ };
263
+ var handleEnd = function(warning, name) {
264
+ var warnTips = warning ? 'Warning: \'' + name + '\' already exists. Use \'--force\' to override' : null;
265
+ if (callback) {
266
+ callback(warnTips ? new Error(warnTips) : null, warning);
267
+ } else if (warnTips) {
268
+ info('Successfully enabled');
269
+ warn(warnTips);
150
270
  } else {
151
- port = options.port = options.port > 0 ? options.port : pkg.port;
271
+ info('Successfully configured rules for Whistle' + (isClient ? ' client' : '') + ' (' + getHost(options) + ')');
152
272
  }
153
- handleRules(filepath, function(result) {
154
- if (!result) {
155
- error('The name and rules are required');
156
- return;
157
- }
158
- var name = getString(result.name);
159
- if (!name || name.length > 64) {
160
- error('The name must be 1-64 characters');
161
- return;
162
- }
163
- var rules = getString(result.rules);
164
- if (rules.length > MAX_RULES_LEN) {
165
- error('Maximum rules size: 256KB');
166
- return;
167
- }
168
- var groupName = getString(result.groupName) || getString(result.group);
169
- var setRules = function() {
170
- var body = [
171
- 'name=' + encodeURIComponent(name),
172
- 'rules=' + encodeURIComponent(rules),
173
- 'groupName=' + encodeURIComponent(groupName.trim())
174
- ].join('&');
175
- request(body, function() {
176
- info('Successfully configured rules for Whistle' + (isClient ? ' client' : '') + ' (' + util.joinIpPort(options.host || '127.0.0.1', port) + ')');
177
- });
178
- };
179
- if (force) {
180
- return setRules();
181
- }
182
- request('name=' + encodeURIComponent(name) + '&enable=1&top=1', function(data) {
183
- if (data.rules) {
184
- info('Successfully enabled');
185
- warn('Warning: Rule already exists. Use \'--force\' to override');
186
- return;
273
+ };
274
+ filepath = config.result || path.resolve(config.filepath || '.whistle.js');
275
+ handleRules(options, filepath, function(result) {
276
+ if (!result) {
277
+ return handleError('The name and rules are required');
278
+ }
279
+ var name = getString(result.name);
280
+ if (!name || name.length > 64) {
281
+ return handleError('The name must be 1-64 characters');
282
+ }
283
+ var rules = getString(result.rules);
284
+ if (rules.length > MAX_RULES_LEN) {
285
+ return handleError('Maximum rules size: 256KB');
286
+ }
287
+ var groupName = getString(result.groupName) || getString(result.group);
288
+ var reqOptions = getReqOptions(options);
289
+ var setRules = function() {
290
+ var body = [
291
+ 'name=' + encodeURIComponent(name),
292
+ 'rules=' + encodeURIComponent(rules),
293
+ 'groupName=' + encodeURIComponent(groupName.trim())
294
+ ].join('&');
295
+ request(reqOptions, body, function(em) {
296
+ if (em) {
297
+ return handleError(em);
187
298
  }
188
- setRules();
299
+ handleEnd();
189
300
  });
190
- }, port);
301
+ };
302
+ if (config.force) {
303
+ return setRules();
304
+ }
305
+ request(reqOptions, 'name=' + encodeURIComponent(name) + '&enable=1&top=1', function(em, data) {
306
+ if (em) {
307
+ return handleError(em);
308
+ }
309
+ if (data.rules) {
310
+ return handleEnd(true, name);
311
+ }
312
+ setRules();
313
+ });
191
314
  });
192
315
  });
193
316
  };
317
+
318
+ module.exports.existsPlugin = existsPlugin;
319
+ module.exports.getOptions = function(cb, storage, isClient) {
320
+ if (typeof cb !== 'function') {
321
+ var temp = storage;
322
+ storage = cb;
323
+ cb = temp;
324
+ }
325
+ return getConfig(null, storage, null, isClient, function(err, config) {
326
+ return cb(err, removeSpecialAuth(config));
327
+ });
328
+ };
package/bin/util.js CHANGED
@@ -134,11 +134,16 @@ function formatOptions(options) {
134
134
  return options;
135
135
  }
136
136
 
137
+ function getConfigFile(storage) {
138
+ var dataDir = getDataDir();
139
+ return path.join(dataDir, encodeURIComponent('#' + (storage ? storage + '#' : '')));
140
+ }
141
+
142
+ exports.getConfigFile = getConfigFile;
137
143
  exports.formatOptions = formatOptions;
138
144
 
139
145
  function readConfig(storage) {
140
- var dataDir = getDataDir();
141
- var configFile = path.join(dataDir, encodeURIComponent('#' + (storage ? storage + '#' : '')));
146
+ var configFile = getConfigFile(storage);
142
147
  var conf = common.readJsonSync(configFile);
143
148
  conf && formatOptions(conf.options);
144
149
  return conf;
@@ -179,18 +184,16 @@ exports.getDefaultPort = function () {
179
184
  return port > 0 ? port : 8899;
180
185
  };
181
186
 
182
- function getBody(res, callback) {
187
+ exports.getBody = function (res, callback) {
183
188
  var resBody;
184
189
  res.on('data', function(data) {
185
190
  resBody = resBody ? Buffer.concat([resBody, data]) : data;
186
191
  });
187
192
  res.on('end', function() {
188
193
  if (res.statusCode != 200) {
189
- callback(resBody || 'response ' + res.statusCode + ' error');
194
+ callback('Bad response (' + res.statusCode + ')');
190
195
  } else {
191
196
  callback(null, JSON.parse(resBody + ''));
192
197
  }
193
198
  });
194
- }
195
-
196
- exports.getBody = getBody;
199
+ };
@@ -22,10 +22,8 @@ module.exports = function(req, res) {
22
22
  data.ids = null;
23
23
  }
24
24
  var clientIp = util.getClientIp(req);
25
- var stopRecordConsole = data.startLogTime == -3;
26
25
  var stopRecordSvrLog = data.startSvrLogTime == -3;
27
26
  var h = req.headers;
28
- var curLogId = proxy.getLatestId();
29
27
  var curSvrLogId = logger.getLatestId();
30
28
  util.sendGzip(req, res, {
31
29
  ec: 0,
@@ -46,11 +44,8 @@ module.exports = function(req, res) {
46
44
  mvaluesClientId: config.mvaluesClientId,
47
45
  mvaluesTime: config.mvaluesTime,
48
46
  server: util.getServerInfo(req),
49
- curLogId: stopRecordConsole ? undefined : curLogId,
50
47
  curSvrLogId: stopRecordSvrLog ? undefined : curSvrLogId,
51
- lastLogId: stopRecordConsole ? curLogId : undefined,
52
48
  lastSvrLogId: stopRecordSvrLog ? curSvrLogId : undefined,
53
- log: stopRecordConsole ? [] : proxy.getLogs(data.startLogTime, data.count, data.logId),
54
49
  svrLog: stopRecordSvrLog ? [] : logger.getLogs(data.startSvrLogTime, data.count),
55
50
  plugins: pluginMgr.getPlugins(),
56
51
  disabledPlugins: !config.notAllowedDisablePlugins && properties.get('disabledPlugins') || {},
@@ -11,7 +11,6 @@ var logger = proxy.logger;
11
11
  var pluginMgr = proxy.pluginMgr;
12
12
 
13
13
  module.exports = function(req, res) {
14
- var lastLog = proxy.getLogs(0, 1)[0];
15
14
  var lastSvrLog = logger.getLogs(0, 1)[0];
16
15
 
17
16
  util.sendGzip(req, res, {
@@ -23,7 +22,6 @@ module.exports = function(req, res) {
23
22
  custom2: properties.get('Custom2'),
24
23
  hasInvalidCerts: ca.hasInvalidCerts,
25
24
  supportH2: config.enableH2,
26
- lastLogId: lastLog && lastLog.id,
27
25
  lastSvrLogId: lastSvrLog && lastSvrLog.id,
28
26
  lastDataId: proxy.getLastDataId(),
29
27
  clientId: util.getClientId(),
@@ -6,8 +6,8 @@
6
6
  <link rel="shortcut icon" href="img/favicon.ico" />
7
7
  <title>Whistle Web Debugging Proxy</title>
8
8
  </head>
9
- <body style="overscroll-behavior-x: none;">
9
+ <body>
10
10
  <div id="container" class="main"></div>
11
- <script src="js/index.js?v=2.10.1"></script>
11
+ <script src="js/index.js?v=2.10.2"></script>
12
12
  </body>
13
13
  </html>