whistle 2.10.5 → 2.10.7
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/menu.html +2 -2
- package/assets/modal.html +2 -2
- package/assets/tab.html +2 -2
- package/bin/api.js +545 -0
- package/bin/import.js +3 -3
- package/bin/plugin.js +4 -8
- package/bin/use.js +22 -8
- package/bin/util.js +12 -7
- package/bin/whistle.js +11 -1
- package/biz/webui/cgi-bin/abort.js +14 -5
- package/biz/webui/cgi-bin/frames.js +12 -0
- package/biz/webui/cgi-bin/get-session.js +7 -3
- package/biz/webui/cgi-bin/is-enabled-https.js +6 -0
- package/biz/webui/cgi-bin/plugins/disable-plugin.js +16 -7
- package/biz/webui/cgi-bin/plugins/list.js +17 -0
- package/biz/webui/cgi-bin/plugins/plugin.js +21 -0
- package/biz/webui/cgi-bin/plugins/status.js +21 -0
- package/biz/webui/cgi-bin/rules/is-multi-select.js +7 -0
- package/biz/webui/cgi-bin/rules/move-top.js +11 -0
- package/biz/webui/cgi-bin/rules/select.js +1 -1
- package/biz/webui/cgi-bin/rules/select2.js +16 -0
- package/biz/webui/cgi-bin/rules/status.js +6 -0
- package/biz/webui/cgi-bin/rules/unselect.js +3 -2
- package/biz/webui/cgi-bin/rules/unselect2.js +18 -0
- package/biz/webui/cgi-bin/rules/value.js +19 -0
- package/biz/webui/cgi-bin/sessions.js +6 -0
- package/biz/webui/cgi-bin/util.js +45 -20
- package/biz/webui/htdocs/index.html +1 -1
- package/biz/webui/htdocs/js/index.js +51 -51
- package/biz/webui/lib/index.js +1 -3
- package/lib/https/index.js +7 -5
- package/lib/init.js +1 -0
- package/lib/inspectors/data.js +39 -34
- package/lib/inspectors/req.js +9 -6
- package/lib/inspectors/res.js +10 -14
- package/lib/plugins/index.js +2 -2
- package/lib/plugins/load-plugin.js +19 -8
- package/lib/rules/util.js +20 -10
- package/lib/service/composer.js +7 -22
- package/lib/service/data-center.js +0 -12
- package/lib/service/generate-saz.js +2 -2
- package/lib/service/service.js +8 -7
- package/lib/service/util.js +6 -16
- package/lib/socket-mgr.js +23 -17
- package/lib/tunnel.js +7 -17
- package/lib/upgrade.js +4 -8
- package/lib/util/common.js +78 -11
- package/lib/util/data-server.js +273 -0
- package/lib/util/file-mgr.js +7 -4
- package/lib/util/http-mgr.js +17 -9
- package/lib/util/index.js +67 -54
- package/lib/util/parse-url.js +11 -9
- package/lib/util/patch.js +2 -6
- package/lib/util/speed-transform.js +17 -6
- package/lib/util/whistle-transform.js +18 -7
- package/package.json +10 -7
package/assets/menu.html
CHANGED
|
@@ -189,8 +189,8 @@
|
|
|
189
189
|
toast[name] = options.msgBox[name];
|
|
190
190
|
});
|
|
191
191
|
whistleBridge.getSelectedSessionList = options.getSelectedSessionList;
|
|
192
|
-
whistleBridge.getRawRequest = options.getRawRequest;
|
|
193
|
-
whistleBridge.getRawResponse = options.getRawResponse;
|
|
192
|
+
whistleBridge.getRawRequest = whistleBridge.getRawReq = options.getRawRequest;
|
|
193
|
+
whistleBridge.getRawResponse = whistleBridge.getRawRes = options.getRawResponse;
|
|
194
194
|
whistleBridge.getActiveSession = whistleBridge.getSession = whistleBridge.getSelectedSession = options.getActiveSession;
|
|
195
195
|
whistleBridge.showOption = options.showOption;
|
|
196
196
|
whistleBridge.hideOption = options.hideOption;
|
package/assets/modal.html
CHANGED
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
toast[name] = options.msgBox[name];
|
|
13
13
|
});
|
|
14
14
|
whistleBridge.getSelectedSessionList = options.getSelectedSessionList;
|
|
15
|
-
whistleBridge.getRawRequest = options.getRawRequest;
|
|
16
|
-
whistleBridge.getRawResponse = options.getRawResponse;
|
|
15
|
+
whistleBridge.getRawRequest = whistleBridge.getRawReq = options.getRawRequest;
|
|
16
|
+
whistleBridge.getRawResponse = whistleBridge.getRawRes = options.getRawResponse;
|
|
17
17
|
whistleBridge.getActiveSession = whistleBridge.getSession = whistleBridge.getSelectedSession = options.getActiveSession;
|
|
18
18
|
whistleBridge.showOption = options.showOption;
|
|
19
19
|
whistleBridge.hideOption = options.hideOption;
|
package/assets/tab.html
CHANGED
|
@@ -282,8 +282,8 @@
|
|
|
282
282
|
});
|
|
283
283
|
getActiveSession = options.getActiveSession;
|
|
284
284
|
whistleBridge.getSelectedSessionList = options.getSelectedSessionList;
|
|
285
|
-
whistleBridge.getRawRequest = options.getRawRequest;
|
|
286
|
-
whistleBridge.getRawResponse = options.getRawResponse;
|
|
285
|
+
whistleBridge.getRawRequest = whistleBridge.getRawReq = options.getRawRequest;
|
|
286
|
+
whistleBridge.getRawResponse = whistleBridge.getRawRes = options.getRawResponse;
|
|
287
287
|
whistleBridge.showOption = options.showOption;
|
|
288
288
|
whistleBridge.hideOption = options.hideOption;
|
|
289
289
|
whistleBridge.updateUI = options.updateUI;
|
package/bin/api.js
ADDED
|
@@ -0,0 +1,545 @@
|
|
|
1
|
+
|
|
2
|
+
var http = require('http');
|
|
3
|
+
var extend = require('extend');
|
|
4
|
+
var STATUS_CODES = require('http').STATUS_CODES || {};
|
|
5
|
+
var use = require('./use');
|
|
6
|
+
var util = require('./util');
|
|
7
|
+
var unzip = require('../lib/util/zlib').unzip;
|
|
8
|
+
var common = require('../lib/util/common');
|
|
9
|
+
|
|
10
|
+
var getConfig = use.getConfig;
|
|
11
|
+
var getReqOpts = use.getReqOpts;
|
|
12
|
+
var isUtf8 = common.isUtf8;
|
|
13
|
+
var getHttpMeta = common.getHttpMeta;
|
|
14
|
+
var isString = common.isString;
|
|
15
|
+
|
|
16
|
+
function getOptions() {
|
|
17
|
+
return new Promise((resolve, reject) => {
|
|
18
|
+
getConfig(function(e, conf) {
|
|
19
|
+
if (e) {
|
|
20
|
+
return getConfig(true, function(err, config) {
|
|
21
|
+
if (err) {
|
|
22
|
+
return reject(err);
|
|
23
|
+
}
|
|
24
|
+
var options = config.options;
|
|
25
|
+
options.isClient = true;
|
|
26
|
+
resolve(options);
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
return resolve(conf.options);
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async function request(options) {
|
|
35
|
+
if (isString(options)) {
|
|
36
|
+
options = { url: options };
|
|
37
|
+
}
|
|
38
|
+
var opts = await getOptions();
|
|
39
|
+
var data = options.data;
|
|
40
|
+
var isJson = data && typeof data === 'object' && !Buffer.isBuffer(data);
|
|
41
|
+
var reqOpts = getReqOpts(opts, options.url, isJson || options.isJson);
|
|
42
|
+
reqOpts.method = options.method || (data ? 'POST' : 'GET');
|
|
43
|
+
if (options.headers) {
|
|
44
|
+
extend(reqOpts.headers, options.headers);
|
|
45
|
+
}
|
|
46
|
+
return new Promise((resolve, reject) => {
|
|
47
|
+
var req = http.request(reqOpts, function(res) {
|
|
48
|
+
res.on('error', reject);
|
|
49
|
+
util.getBody(res, function(err, body) {
|
|
50
|
+
if (err) {
|
|
51
|
+
return reject(err);
|
|
52
|
+
}
|
|
53
|
+
resolve(body);
|
|
54
|
+
}, options.type === 'raw');
|
|
55
|
+
});
|
|
56
|
+
req.on('error', reject);
|
|
57
|
+
req.end(isJson ? JSON.stringify(data) : data);
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
exports.getRootCA = async function() {
|
|
62
|
+
return request({ url: 'rootca', type: 'raw' });
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
exports.isEnabledHTTPS = exports.isEnabledHttps = function() {
|
|
66
|
+
return request({ url: 'is-enabled-https' }).then(function(data) {
|
|
67
|
+
return data.enabled;
|
|
68
|
+
});
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
exports.setEnableHTTPS = exports.setEnableHttps = async function (enabled) {
|
|
72
|
+
await request({ url: 'intercept-https-connects', data: 'interceptHttpsConnects=' + (enabled ? 1 : 0) });
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
exports.createFile = function(data) {
|
|
77
|
+
if (Buffer.isBuffer(data)) {
|
|
78
|
+
data = { base64: data.toString('base64') };
|
|
79
|
+
} else {
|
|
80
|
+
data = { value: common.toString(data) };
|
|
81
|
+
}
|
|
82
|
+
return request({ url: 'temp/create', data: data }).then(function(data) {
|
|
83
|
+
return data.filepath;
|
|
84
|
+
});
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
exports.getFile = function(filepath) {
|
|
88
|
+
return request('temp/get?filename=' + encodeURIComponent(filepath))
|
|
89
|
+
.then(getValue);
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
exports.network = {
|
|
93
|
+
/**
|
|
94
|
+
* @async 获取 whistle 相关配置信息
|
|
95
|
+
* @returns {Object} 包含存储目录名称、应用名称和版本号的对象
|
|
96
|
+
* @property {boolean} client - 是否为客户端
|
|
97
|
+
* @property {string} storage - 自定义的存储目录名称
|
|
98
|
+
* @property {string} name - 应用名称,固定为 'whistle'
|
|
99
|
+
* @property {string} version - 当前安装的 whistle 版本号
|
|
100
|
+
* @example
|
|
101
|
+
* // 返回值示例
|
|
102
|
+
* {
|
|
103
|
+
* storage: '',
|
|
104
|
+
* name: 'whistle',
|
|
105
|
+
* version: '2.10.7'
|
|
106
|
+
* }
|
|
107
|
+
*/
|
|
108
|
+
getStatus: function() {
|
|
109
|
+
return request('status');
|
|
110
|
+
},
|
|
111
|
+
/**
|
|
112
|
+
* @async 获取抓包数据
|
|
113
|
+
* @description
|
|
114
|
+
* 用于按条件检索 HTTP/WebSocket 等协议的抓包记录。支持基于游标、时间、资源类型、URL 关键词、请求方法、状态码以及请求/响应头的组合筛选。
|
|
115
|
+
*
|
|
116
|
+
* **过滤逻辑**:
|
|
117
|
+
* - 所有非空条件均以 **AND** 关系叠加(即需同时满足);
|
|
118
|
+
* - 对于 `method` 和 `statusCode`,传入多个值时采用 **OR** 关系(匹配任一即可);
|
|
119
|
+
* - `latest` 优先级最高,一旦为 `true`,则 `startId` 和 `startTime` 均被忽略;
|
|
120
|
+
* - 若同时提供 `startId` 与 `startTime`,系统将以其中对应位置更靠近最新数据的一端作为起始点(即取较新的起点);
|
|
121
|
+
* - `reqHeader` 和 `resHeader` 为对象类型,支持按请求头/响应头的字段名和值进行子串匹配。
|
|
122
|
+
*
|
|
123
|
+
* @param {Object} options - 配置对象
|
|
124
|
+
* @param {boolean} [options.latest=false] - 是否仅获取最新的 N 条数据(N 由外部 `count` 参数指定,本接口入参不包含 `count`)。
|
|
125
|
+
* - `true`:直接返回最新的 N 条记录,忽略 `startId` 和 `startTime`;
|
|
126
|
+
* - `false`:配合 `startId` 或 `startTime` 进行增量拉取。
|
|
127
|
+
* @param {string|number} [options.startId] - 游标 ID(通常为记录的自增主键或唯一序号)。
|
|
128
|
+
* 获取记录 ID **大于**此值的增量数据。
|
|
129
|
+
* 若同时提供 `startTime`,则取两者中更靠近最新数据的一个作为起始位置。
|
|
130
|
+
* @param {number} [options.startTime] - 起始时间戳(Unix 毫秒级)。
|
|
131
|
+
* 获取记录生成时间 **大于**此值的增量数据。
|
|
132
|
+
* 若同时提供 `startId`,则取两者中更靠近最新数据的一个作为起始位置。
|
|
133
|
+
* @param {string} [options.type] - 资源类型过滤,同抓包界面 Network 底部过滤栏选项(不区分大小写)。
|
|
134
|
+
* 支持以下枚举值(传入其它字符串表示不过滤):
|
|
135
|
+
* - `JSON`:JSON 请求/响应
|
|
136
|
+
* - `HTML`:HTML 文档
|
|
137
|
+
* - `CSS`:样式表
|
|
138
|
+
* - `JS`:JavaScript 脚本
|
|
139
|
+
* - `Font`:字体文件
|
|
140
|
+
* - `Img`:图片资源
|
|
141
|
+
* - `Media`:音视频媒体
|
|
142
|
+
* - `WS`:WebSocket 帧数据
|
|
143
|
+
* - `Tunnel`:隧道连接(如 WebSocket 升级)
|
|
144
|
+
* - `Wasm`:WebAssembly 模块
|
|
145
|
+
* - `Mock`:Mock 数据
|
|
146
|
+
* - `Rules`:匹配规则的请求
|
|
147
|
+
* - `Import`:导入的数据
|
|
148
|
+
* - `Composer`:通过 Composer 发送的请求
|
|
149
|
+
* - `Error`:错误请求(状态码 >= 400、captureError、中断的请求等请求)
|
|
150
|
+
* - `captureError`:解析 HTTPS 失败的 Tunnel 请求
|
|
151
|
+
* @param {string} [options.subUrl] - URL 中包含该字符串的请求(不区分大小写)。
|
|
152
|
+
* 例如 `"/api/user"` 将匹配所有包含该路径的 URL。
|
|
153
|
+
* @param {string|string[]} [options.method] - HTTP 请求方法,支持多个(数组或逗号分隔字符串,如 `"GET,POST"`,不区分大小写)。
|
|
154
|
+
* 匹配任一方法即满足条件。
|
|
155
|
+
* @param {number|number[]} [options.statusCode] - HTTP 响应状态码,支持多个(数组或逗号分隔字符串,如 `"200,404"`)。
|
|
156
|
+
* 匹配任一状态码即满足条件。
|
|
157
|
+
* @param {Object} [options.reqHeader] - 请求头过滤条件。
|
|
158
|
+
* @param {string} options.reqHeader.name - 请求头字段名称(如 `X-Request-Id`,不区分大小写)。
|
|
159
|
+
* @param {string} [options.reqHeader.subValue] - 可选,请求头字段值需要包含的子字符串(不区分大小写)。
|
|
160
|
+
* 若不填,则表示仅要求存在该请求头字段(无论值为何)。
|
|
161
|
+
* @param {Object} [options.resHeader] - 响应头过滤条件。
|
|
162
|
+
* @param {string} options.resHeader.name - 响应头字段名称(如 `Content-Type`,不区分大小写)。
|
|
163
|
+
* @param {string} [options.resHeader.subValue] - 可选,响应头字段值需要包含的子字符串(不区分大小写)。
|
|
164
|
+
* 若不填,则表示仅要求存在该响应头字段(无论值为何)。
|
|
165
|
+
*
|
|
166
|
+
* @example
|
|
167
|
+
* // 使用游标增量拉取(不限制类型)
|
|
168
|
+
* getSessions({ startId: '1784905385300-003' });
|
|
169
|
+
*
|
|
170
|
+
* @example
|
|
171
|
+
* // 直接获取最新数据(需外部指定 count)
|
|
172
|
+
* getSessions({ latest: true });
|
|
173
|
+
*
|
|
174
|
+
* @example
|
|
175
|
+
* // 组合过滤:查询状态码为 200 或 304 的 GET 请求,且 URL 包含 "/v1",同时要求存在特定请求头
|
|
176
|
+
* getSessions({
|
|
177
|
+
* subUrl: '/v1',
|
|
178
|
+
* method: 'GET',
|
|
179
|
+
* statusCode: [200, 304],
|
|
180
|
+
* reqHeader: { name: 'X-Token', subValue: 'abc' }
|
|
181
|
+
* });
|
|
182
|
+
*/
|
|
183
|
+
getSessions: function(options) {
|
|
184
|
+
if (isString(options)) {
|
|
185
|
+
options = { subUrl: options };
|
|
186
|
+
}
|
|
187
|
+
return request({ url: 'sessions', data: options || {} });
|
|
188
|
+
},
|
|
189
|
+
saveSessions: async function(sessions, name) {
|
|
190
|
+
if (!Array.isArray(sessions) || !sessions.length) {
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
return request({ url: 'saved/save', data: {
|
|
194
|
+
sessions: sessions,
|
|
195
|
+
filename: isString(name) ? name.trim() : ''
|
|
196
|
+
}}).then(function(data) {
|
|
197
|
+
if (data.ec !== 0) {
|
|
198
|
+
throw new Error(data.em || 'Failed to save sessions');
|
|
199
|
+
}
|
|
200
|
+
return data.filename;
|
|
201
|
+
});
|
|
202
|
+
},
|
|
203
|
+
getSavedSessions: function(filename) {
|
|
204
|
+
filename = common.getSavedFileItem(filename);
|
|
205
|
+
if (!filename) {
|
|
206
|
+
throw new Error('Invalid filename');
|
|
207
|
+
}
|
|
208
|
+
return request({
|
|
209
|
+
url: 'saved/sessions?filename=' + encodeURIComponent(filename.filename) +
|
|
210
|
+
'&count=' + filename.count + '&time=' + filename.time,
|
|
211
|
+
type: 'raw'
|
|
212
|
+
}).then(function(buf) {
|
|
213
|
+
return new Promise(function(resolve, reject) {
|
|
214
|
+
unzip('gzip', buf, function(err, data) {
|
|
215
|
+
if (err) {
|
|
216
|
+
return reject(err);
|
|
217
|
+
}
|
|
218
|
+
try {
|
|
219
|
+
resolve(JSON.parse(data.toString()));
|
|
220
|
+
} catch(e) {
|
|
221
|
+
reject(e);
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
},
|
|
227
|
+
/**
|
|
228
|
+
* @async 获取 WebSocket/Socket 的帧数据
|
|
229
|
+
* @param {Object} options - 配置对象
|
|
230
|
+
* @param {string} options.reqId - 请求唯一标识 ID,用于链路追踪
|
|
231
|
+
* @param {number} [options.count] - 本次查询返回的最大记录数。取值范围:1 ~ 120(含),默认为 120
|
|
232
|
+
* @param {boolean} [options.latest=false] - 是否仅获取最新的 count 条数据(需外部定义 count)
|
|
233
|
+
* - true:配合 startId 或 startTime 返回最新数据
|
|
234
|
+
* - false:配合 startId 或 startTime 进行增量拉取
|
|
235
|
+
* @param {string|number} [options.startId] - 游标 ID,获取记录 ID 大于此值的增量数据
|
|
236
|
+
* 若同时存在 startTime,取离最新数据最近的那个
|
|
237
|
+
* @param {number} [options.startTime] - 起始时间戳(Unix 毫秒级),获取生成时间大于此值的增量数据
|
|
238
|
+
* 若同时存在 startId,取离最新数据最近的那个
|
|
239
|
+
* @param {string} [options.from] - 过滤从哪里发出的请求,默认为全部
|
|
240
|
+
* - client:表示只获取从客户端发出的帧数据
|
|
241
|
+
* - server:表示只获取从服务端发出的帧数据
|
|
242
|
+
* - 其它:表示获取该连接的所有帧数据
|
|
243
|
+
*
|
|
244
|
+
* @example
|
|
245
|
+
* // 使用游标增量拉取
|
|
246
|
+
* getFrames({ reqId: '1784885309943-086', startId: '1784903620142-000', startTime: 1784903620156, count: 10, from: 'client' });
|
|
247
|
+
*
|
|
248
|
+
* @example
|
|
249
|
+
* // 直接获取最新数据(需配合 count)
|
|
250
|
+
* getFrames({ reqId: '1784885309943-086', startId: '1784903620142-000', startTime: 1784903620156, latest: true });
|
|
251
|
+
*/
|
|
252
|
+
getFrames: function(options) {
|
|
253
|
+
if (isString(options)) {
|
|
254
|
+
options = { reqId: options };
|
|
255
|
+
} else if (!options || !options.reqId) {
|
|
256
|
+
throw new Error('reqId is required');
|
|
257
|
+
}
|
|
258
|
+
return request({ url: 'frames', data: options });
|
|
259
|
+
},
|
|
260
|
+
request: function(options) {
|
|
261
|
+
var base64;
|
|
262
|
+
var body;
|
|
263
|
+
if (options.body) {
|
|
264
|
+
if (Buffer.isBuffer(options.body)) {
|
|
265
|
+
base64 = options.body.toString('base64');
|
|
266
|
+
} else {
|
|
267
|
+
body = common.toString(options.body);
|
|
268
|
+
}
|
|
269
|
+
} else if (options.base64) {
|
|
270
|
+
base64 = options.base64;
|
|
271
|
+
}
|
|
272
|
+
return request({
|
|
273
|
+
url: 'composer',
|
|
274
|
+
data: {
|
|
275
|
+
rules: options.rules,
|
|
276
|
+
useH2: options.enableH2 || options.enableHTTP2 ? 1 : '',
|
|
277
|
+
needResponse: options.needResponse !== false,
|
|
278
|
+
url: options.url,
|
|
279
|
+
headers: options.headers,
|
|
280
|
+
method: options.method,
|
|
281
|
+
body: body,
|
|
282
|
+
base64: base64,
|
|
283
|
+
repeatCount: options.times,
|
|
284
|
+
disabledGlobalRules: options.disabledGlobalRules
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
},
|
|
288
|
+
abort: async function(reqId) {
|
|
289
|
+
if (Array.isArray(reqId)) {
|
|
290
|
+
reqId = reqId.join();
|
|
291
|
+
}
|
|
292
|
+
if (!reqId) {
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
await request({ url: 'abort', data: { list: reqId } });
|
|
296
|
+
}
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
async function disableAllRules(disabled) {
|
|
300
|
+
await request({ url: 'rules/disable-all-rules', data: 'disabledAllRules=' + (disabled ? 1 : 0) });
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function getValue(item) {
|
|
304
|
+
return item.value;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function checkSuccess(data) {
|
|
308
|
+
return data.ec === 0;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
exports.rules = {
|
|
312
|
+
getStatus: function() {
|
|
313
|
+
return request('rules/status');
|
|
314
|
+
},
|
|
315
|
+
turnOff: function() {
|
|
316
|
+
return disableAllRules(true);
|
|
317
|
+
},
|
|
318
|
+
turnOn: function() {
|
|
319
|
+
return disableAllRules(false);
|
|
320
|
+
},
|
|
321
|
+
isMultiSelect: function() {
|
|
322
|
+
return request('rules/is-multi-select').then(function(data) {
|
|
323
|
+
return data.multiSelect;
|
|
324
|
+
});
|
|
325
|
+
},
|
|
326
|
+
setMultiSelect: async function(multiSelect) {
|
|
327
|
+
await request({ url: 'rules/allow-multiple-choice', data: 'allowMultipleChoice=' + (multiSelect ? 1 : 0) });
|
|
328
|
+
},
|
|
329
|
+
setLaterFirst: async function (laterRulesFirst) {
|
|
330
|
+
await request({ url: 'rules/enable-back-rules-first', data: 'backRulesFirst=' + (laterRulesFirst ? 1 : 0) });
|
|
331
|
+
},
|
|
332
|
+
getList: function() {
|
|
333
|
+
return request('rules/list').then(function(data) {
|
|
334
|
+
return data.list.map(function(item) {
|
|
335
|
+
return {
|
|
336
|
+
name: item.name,
|
|
337
|
+
value: item.data || '',
|
|
338
|
+
selected: item.selected
|
|
339
|
+
};
|
|
340
|
+
});
|
|
341
|
+
});
|
|
342
|
+
},
|
|
343
|
+
add: async function(name, value, selected) {
|
|
344
|
+
var addToTop = false;
|
|
345
|
+
if (selected && typeof selected !== 'boolean') {
|
|
346
|
+
addToTop = selected.addToTop || selected.top;
|
|
347
|
+
selected = selected.selected;
|
|
348
|
+
}
|
|
349
|
+
var data = { name: name, value: value, selected: selected, addToTop: addToTop };
|
|
350
|
+
await request({ url: 'rules/add', data: data });
|
|
351
|
+
},
|
|
352
|
+
get: function(name) {
|
|
353
|
+
return name ? request('rules/value?name=' + encodeURIComponent(name)).then(getValue) : null;
|
|
354
|
+
},
|
|
355
|
+
select: function(name) {
|
|
356
|
+
return !!name && request({ url: 'rules/select2', data: { name: name } }).then(checkSuccess);
|
|
357
|
+
},
|
|
358
|
+
unselect: function(name) {
|
|
359
|
+
var data ={ name: name === undefined ? false : name };
|
|
360
|
+
return request({ url: 'rules/unselect2', data: data }).then(checkSuccess);
|
|
361
|
+
},
|
|
362
|
+
moveToTop: function(name) {
|
|
363
|
+
return isString(name) && request({ url: 'rules/move-top', data: { name: name } })
|
|
364
|
+
.then(checkSuccess);
|
|
365
|
+
}
|
|
366
|
+
};
|
|
367
|
+
|
|
368
|
+
exports.values = {
|
|
369
|
+
getList: function() {
|
|
370
|
+
return request('values/list').then(function(data) {
|
|
371
|
+
return data.list.map(function(item) {
|
|
372
|
+
return {
|
|
373
|
+
name: item.name,
|
|
374
|
+
value: item.data || ''
|
|
375
|
+
};
|
|
376
|
+
});
|
|
377
|
+
});
|
|
378
|
+
},
|
|
379
|
+
get: function(name) {
|
|
380
|
+
return name ? request('values/value?key=' + encodeURIComponent(name)).then(getValue) : null;
|
|
381
|
+
},
|
|
382
|
+
add: async function(name, value) {
|
|
383
|
+
await request({ url: 'values/add', data: { name: name, value: value } });
|
|
384
|
+
}
|
|
385
|
+
};
|
|
386
|
+
|
|
387
|
+
async function disableAllPlugins(disabled) {
|
|
388
|
+
await request({ url: 'plugins/disable-all-plugins', data: 'disabledAllPlugins=' + (disabled ? 1 : 0) });
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
function disablePlugin(name, disabled) {
|
|
392
|
+
return request({ url: 'plugins/disable-plugin', data: { name: name, disabled: disabled ? 1 : 0 } }).then(function(data) {
|
|
393
|
+
return data.exists;
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
exports.plugins = {
|
|
398
|
+
getStatus: function() {
|
|
399
|
+
return request('plugins/status');
|
|
400
|
+
},
|
|
401
|
+
turnOff: function() {
|
|
402
|
+
return disableAllPlugins(true);
|
|
403
|
+
},
|
|
404
|
+
turnOn: function() {
|
|
405
|
+
return disableAllPlugins(false);
|
|
406
|
+
},
|
|
407
|
+
getList: function() {
|
|
408
|
+
return request('plugins/list');
|
|
409
|
+
},
|
|
410
|
+
get: function(name) {
|
|
411
|
+
return name ? request('plugins/plugin?name=' + encodeURIComponent(name)).then(function(data) {
|
|
412
|
+
return data.plugin;
|
|
413
|
+
}) : undefined;
|
|
414
|
+
},
|
|
415
|
+
select: function(name) {
|
|
416
|
+
return disablePlugin(name, false);
|
|
417
|
+
},
|
|
418
|
+
unselect: function(name) {
|
|
419
|
+
return disablePlugin(name, true);
|
|
420
|
+
}
|
|
421
|
+
};
|
|
422
|
+
|
|
423
|
+
function getText(data, charset) {
|
|
424
|
+
if (!data) {
|
|
425
|
+
return '';
|
|
426
|
+
}
|
|
427
|
+
if (!Buffer.isBuffer(data)) {
|
|
428
|
+
if (!isString(data)) {
|
|
429
|
+
data = data.base64;
|
|
430
|
+
if (!isString(data)) {
|
|
431
|
+
return '';
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
data = Buffer.from(data, 'base64');
|
|
435
|
+
}
|
|
436
|
+
return common.bufferToString(data, charset);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
function getUtf8Buf(data, charset) {
|
|
440
|
+
if (isString(data)) {
|
|
441
|
+
data = Buffer.from(data, 'base64');
|
|
442
|
+
} else if (!Buffer.isBuffer(data)) {
|
|
443
|
+
return Buffer.from(common.toString(data));
|
|
444
|
+
}
|
|
445
|
+
return common.getUtf8Buf(data, charset);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
function getReqBody(req) {
|
|
449
|
+
req = req.req || req;
|
|
450
|
+
return req ? getText(req.base64) : '';
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
function getResBody(res) {
|
|
454
|
+
res = res.res || res;
|
|
455
|
+
return res ? getText(res.base64) : '';
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
function getHttpVersion(req) {
|
|
459
|
+
return 'HTTP/' + ((req && req.httpVersion) || '1.1');
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
function getRawReq(session) {
|
|
463
|
+
var req = session.req || session;
|
|
464
|
+
if (!req) {
|
|
465
|
+
return '';
|
|
466
|
+
}
|
|
467
|
+
return getHttpMeta(req.method, session.url, getHttpVersion(req), req.headers) + getReqBody(req);
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
function getRawRes(session) {
|
|
471
|
+
var res = session.res || session;
|
|
472
|
+
if (!res) {
|
|
473
|
+
return '';
|
|
474
|
+
}
|
|
475
|
+
var msg = res.statusMessage || STATUS_CODES[res.statusCode] || '';
|
|
476
|
+
return getHttpMeta(getHttpVersion(session.req), res.statusCode, msg, res.headers) + getResBody(res);
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
function getRules(rules) {
|
|
480
|
+
rules = rules.rules || rules;
|
|
481
|
+
var result = [];
|
|
482
|
+
if (!rules) {
|
|
483
|
+
return result;
|
|
484
|
+
}
|
|
485
|
+
var addRule = function(rule) {
|
|
486
|
+
var raw = rule && rule.raw;
|
|
487
|
+
if (isString(raw) && result.indexOf(raw) === -1) {
|
|
488
|
+
result.push(raw);
|
|
489
|
+
}
|
|
490
|
+
};
|
|
491
|
+
Object.keys(rules).forEach(function(name) {
|
|
492
|
+
var rule = rules[name];
|
|
493
|
+
if (Array.isArray(rule.list)) {
|
|
494
|
+
rule.list.forEach(addRule);
|
|
495
|
+
} else {
|
|
496
|
+
addRule(rule);
|
|
497
|
+
}
|
|
498
|
+
});
|
|
499
|
+
return result;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
function isNum(val) {
|
|
503
|
+
return typeof val === 'number' && !isNaN(val);
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
function getDiffTime(start, end) {
|
|
507
|
+
return isNum(start) && isNum(end) ? end - start : null;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
function getTimings(session) {
|
|
511
|
+
var startTime = session.startTime;
|
|
512
|
+
var requestTime = session.requestTime;
|
|
513
|
+
var responseTime = session.responseTime;
|
|
514
|
+
var dnsTime = session.dnsTime;
|
|
515
|
+
var endTime = session.endTime;
|
|
516
|
+
|
|
517
|
+
return {
|
|
518
|
+
start: startTime, // 请求开始的时间戳
|
|
519
|
+
ttfb: session.ttfb >= 0 ? session.ttfb : null, // 首字节时间
|
|
520
|
+
dns: getDiffTime(startTime, dnsTime), // dns 解析耗时
|
|
521
|
+
connect: getDiffTime(dnsTime, session.connectTime), // tcp 连接耗时(从dns结束到建立连接耗时)
|
|
522
|
+
request: getDiffTime(dnsTime, requestTime), // 请求发送耗时(从dns结束到请求结束耗时)
|
|
523
|
+
response: getDiffTime(requestTime, responseTime), // 响应接收耗时(从请求结束到接收到响应头耗时)
|
|
524
|
+
download: getDiffTime(responseTime, endTime), // 下载耗时(从接收到响应头到接收完响应体耗时)
|
|
525
|
+
total: getDiffTime(startTime, endTime) // 总耗时
|
|
526
|
+
};
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
exports.utils = {
|
|
530
|
+
isUtf8: isUtf8,
|
|
531
|
+
getText: getText,
|
|
532
|
+
getUtf8Buf: getUtf8Buf,
|
|
533
|
+
getReqBody: getReqBody,
|
|
534
|
+
getResBody: getResBody,
|
|
535
|
+
getReqJson: function(session) {
|
|
536
|
+
return JSON.parse(getReqBody(session));
|
|
537
|
+
},
|
|
538
|
+
getResJson: function(session) {
|
|
539
|
+
return JSON.parse(getResBody(session));
|
|
540
|
+
},
|
|
541
|
+
getRawReq: getRawReq,
|
|
542
|
+
getRawRes: getRawRes,
|
|
543
|
+
getRules: getRules,
|
|
544
|
+
getTimings: getTimings
|
|
545
|
+
};
|
package/bin/import.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
var fs = require('fs');
|
|
2
2
|
|
|
3
|
-
function
|
|
3
|
+
function importModule(filepath, callback) {
|
|
4
4
|
return import(filepath).then(callback);
|
|
5
5
|
}
|
|
6
6
|
|
|
@@ -11,11 +11,11 @@ module.exports = function(filepath, callback) {
|
|
|
11
11
|
var code =e && e.code;
|
|
12
12
|
if (code === 'ERR_REQUIRE_ESM') {
|
|
13
13
|
// ignore eslint & fix type=module
|
|
14
|
-
return
|
|
14
|
+
return importModule(filepath, callback);
|
|
15
15
|
} else if (code === 'MODULE_NOT_FOUND' && /\.js$/i.test(filepath)) {
|
|
16
16
|
filepath = filepath.slice(0, -3) + '.mjs';
|
|
17
17
|
if (fs.existsSync(filepath)) {
|
|
18
|
-
return
|
|
18
|
+
return importModule(filepath, callback);
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
21
|
throw e;
|
package/bin/plugin.js
CHANGED
|
@@ -252,15 +252,11 @@ function installPlugins(cmd, plugins, argv, pluginsCache, deep, handleError) {
|
|
|
252
252
|
});
|
|
253
253
|
}
|
|
254
254
|
try {
|
|
255
|
-
if (
|
|
256
|
-
|
|
255
|
+
if (result.length > MAX_REG_COUNT) {
|
|
256
|
+
result = result.slice(0, MAX_REG_COUNT);
|
|
257
257
|
}
|
|
258
258
|
fse.writeJsonSync(REGISTRY_LIST, result);
|
|
259
|
-
} catch (e) {
|
|
260
|
-
try {
|
|
261
|
-
fse.writeJsonSync(REGISTRY_LIST, result);
|
|
262
|
-
} catch (e) {}
|
|
263
|
-
}
|
|
259
|
+
} catch (e) {}
|
|
264
260
|
}
|
|
265
261
|
}
|
|
266
262
|
if (--count <= 0 && deep < 16) {
|
|
@@ -311,7 +307,7 @@ exports.run = function(cmd, argv) {
|
|
|
311
307
|
var newPath = [];
|
|
312
308
|
fse.ensureDirSync(CUSTOM_PLUGIN_PATH);
|
|
313
309
|
fs.readdirSync(CUSTOM_PLUGIN_PATH).forEach(function(name) {
|
|
314
|
-
if (
|
|
310
|
+
if (name.indexOf('whistle.') === 0) {
|
|
315
311
|
newPath.push(path.join(CUSTOM_PLUGIN_PATH, name, 'node_modules/.bin'));
|
|
316
312
|
} else if (name[0] === '@') {
|
|
317
313
|
try {
|