whistle.pastekitlab 1.8.2 → 1.8.4

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.
Files changed (2) hide show
  1. package/index.js +88 -24
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -86,6 +86,41 @@ function shouldIntercept(url) {
86
86
  }
87
87
  }
88
88
 
89
+ // ==================== 资源类型过滤 ====================
90
+ function isResourceRequest(url, headers = {}) {
91
+ // 检查文件扩展名
92
+ const resourceExtensions = [
93
+ '.jpg', '.jpeg', '.png', '.gif', '.svg', '.webp', '.ico', // 图片
94
+ '.css', '.scss', '.less', // 样式
95
+ '.js', '.mjs', '.ts', // 脚本
96
+ '.woff', '.woff2', '.ttf', '.eot', '.otf', // 字体
97
+ '.mp3', '.mp4', '.avi', '.mov', '.wmv', '.flv', // 视频
98
+ '.pdf', '.zip', '.rar', '.7z', '.tar', '.gz' // 文档和压缩
99
+ ];
100
+
101
+ const urlLower = url.toLowerCase();
102
+ if (resourceExtensions.some(ext => urlLower.includes(ext))) {
103
+ return true;
104
+ }
105
+
106
+ // 检查 Content-Type(如果有)
107
+ const contentType = headers['content-type'] || '';
108
+ const resourceTypes = [
109
+ 'image/',
110
+ 'text/css',
111
+ 'application/javascript',
112
+ 'font/',
113
+ 'video/',
114
+ 'audio/'
115
+ ];
116
+
117
+ if (resourceTypes.some(type => contentType.includes(type))) {
118
+ return true;
119
+ }
120
+
121
+ return false;
122
+ }
123
+
89
124
  // ==================== WebSocket ====================
90
125
  function startWebSocketServer() {
91
126
  if (wss) return;
@@ -152,13 +187,13 @@ async function forwardRequest(req, res, url) {
152
187
  reqBody += chunk;
153
188
  }
154
189
 
155
- // 通过 axios 转发请求(使用 arraybuffer
190
+ // 通过 axios 转发请求(使用 stream
156
191
  const response = await axios({
157
192
  method: req.method.toLowerCase(),
158
193
  url: url,
159
194
  headers: req.headers,
160
195
  data: reqBody || undefined,
161
- responseType: 'arraybuffer',
196
+ responseType: 'stream',
162
197
  validateStatus: () => true
163
198
  });
164
199
 
@@ -168,8 +203,9 @@ async function forwardRequest(req, res, url) {
168
203
  res.setHeader(key, response.headers[key]);
169
204
  });
170
205
 
171
- // 直接返回完整的 Buffer
172
- res.end(Buffer.from(response.data));
206
+ // 直接 pipe 流到响应
207
+ response.data.pipe(res);
208
+
173
209
  log(`直接转发: ${req.method} ${url} -> ${response.status}`);
174
210
 
175
211
  } catch (error) {
@@ -189,6 +225,12 @@ module.exports = (server, options) => {
189
225
  const url = req.fullUrl || req.url;
190
226
  console.log('>>> [GLOBAL]', url);
191
227
 
228
+ // 过滤资源类请求(图片、CSS、JS等)
229
+ if (isResourceRequest(url, req.headers)) {
230
+ log(`跳过资源请求: ${url}`);
231
+ return forwardRequest(req, res, url);
232
+ }
233
+
192
234
  // 域名过滤:如果不匹配则直接放行,不转发也不通知
193
235
  if (!shouldIntercept(url)) {
194
236
  log(`跳过未配置的域名: ${url}`);
@@ -217,15 +259,15 @@ module.exports = (server, options) => {
217
259
  broadcast('REQUEST', requestData);
218
260
  log(`REQ ${req.method} ${url}`);
219
261
 
220
- // 通过 axios 转发请求(使用 arraybuffer 接收二进制数据)
262
+ // 通过 axios 转发请求(使用 stream 模式,支持大文件)
221
263
  const startTime = Date.now();
222
264
  const response = await axios({
223
265
  method: req.method.toLowerCase(),
224
266
  url: url,
225
267
  headers: req.headers,
226
268
  data: reqBody || undefined,
227
- responseType: 'arraybuffer', // 关键:以 ArrayBuffer 接收,保留完整数据
228
- validateStatus: () => true // 不抛出 HTTP 错误
269
+ responseType: 'stream', // 使用流式传输,不占用大量内存
270
+ validateStatus: () => true
229
271
  });
230
272
  const duration = Date.now() - startTime;
231
273
 
@@ -235,25 +277,47 @@ module.exports = (server, options) => {
235
277
  res.setHeader(key, response.headers[key]);
236
278
  });
237
279
 
238
- // 转换响应数据为 Buffer
239
- const responseBody = Buffer.from(response.data);
240
-
241
- // 直接返回完整的 Buffer(不会截断)
242
- res.end(responseBody);
280
+ // 直接 pipe 流到响应(不会截断,支持任意大小)
281
+ response.data.pipe(res);
282
+
283
+ // 收集响应数据用于 WebSocket 通知(限制大小)
284
+ let responseDataBuffer = Buffer.alloc(0);
285
+ const originalPipe = response.data.pipe.bind(response.data);
286
+
287
+ response.data.on('data', (chunk) => {
288
+ // 只收集前 1MB 用于通知
289
+ if (responseDataBuffer.length < 1024 * 1024) {
290
+ const remaining = 1024 * 1024 - responseDataBuffer.length;
291
+ const toCollect = chunk.slice(0, remaining);
292
+ responseDataBuffer = Buffer.concat([responseDataBuffer, toCollect]);
293
+ }
294
+ });
243
295
 
244
- // 构建响应数据
245
- const responseData = {
246
- ...requestData,
247
- statusCode: response.status,
248
- responseHeaders: {...response.headers},
249
- responseBody: truncateString(bufferToString(responseBody)),
250
- responseBodyBase64: responseBody.toString('base64'),
251
- duration
252
- };
296
+ response.data.on('end', () => {
297
+ // 构建响应数据
298
+ const responseData = {
299
+ ...requestData,
300
+ statusCode: response.status,
301
+ responseHeaders: {...response.headers},
302
+ responseBody: truncateString(bufferToString(responseDataBuffer)),
303
+ responseBodyBase64: responseDataBuffer.toString('base64'),
304
+ duration
305
+ };
306
+
307
+ // 通知 Chrome 插件:响应完成
308
+ broadcast('RESPONSE', responseData);
309
+ log(`转发成功: ${req.method} ${url} -> ${response.status} (${duration}ms)`);
310
+ });
253
311
 
254
- // 通知 Chrome 插件:响应完成
255
- broadcast('RESPONSE', responseData);
256
- log(`转发成功: ${req.method} ${url} -> ${response.status} (${duration}ms)`);
312
+ response.data.on('error', (error) => {
313
+ log(`流错误: ${error.message}`);
314
+ broadcast('ERROR', {
315
+ url,
316
+ method: req.method,
317
+ error: error.message,
318
+ timestamp: Date.now()
319
+ });
320
+ });
257
321
 
258
322
  } catch (error) {
259
323
  log(`转发失败: ${error.message}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "whistle.pastekitlab",
3
- "version": "1.8.2",
3
+ "version": "1.8.4",
4
4
  "description": "Whistle plugin for PasteKit Lab - Intercepts requests and sends them to requestlistviewer via WebSocket",
5
5
  "main": "index.js",
6
6
  "scripts": {