whistle.pastekitlab 1.6.9 → 1.7.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.
Files changed (2) hide show
  1. package/index.js +224 -31
  2. package/package.json +2 -1
package/index.js CHANGED
@@ -1,43 +1,236 @@
1
- // index.js
2
- module.exports = (server, options) => {
3
- console.log('✅ 插件已加载,等待规则匹配...');
1
+ /**
2
+ * Whistle Plugin for PasteKit Lab(最终版 - 只监听)
3
+ */
4
4
 
5
- return {
6
- // 🔑 必须这样返回,Whistle 才会注入 next
7
- request(req, res, next) {
8
- try {
9
- const url = req.fullUrl || req.url;
10
- console.log(`🟡 [REQUEST HOOK] ${req.method} ${url}`);
5
+ const WebSocket = require('ws');
6
+ const axios = require('axios');
7
+ const fs = require('fs');
8
+ const path = require('path');
9
+
10
+ // ==================== 配置 ====================
11
+ const CONFIG = {
12
+ wsPort: 8889,
13
+ logEnabled: true,
14
+ logFile: path.join(__dirname, 'plugin.log')
15
+ };
16
+
17
+ // ==================== 全局状态 ====================
18
+ let wss = null;
19
+ let clients = new Map();
20
+ let clientIdCounter = 0;
21
+ let domainConfig = [];
22
+
23
+ // ==================== 日志 ====================
24
+ function log(message) {
25
+ if (!CONFIG.logEnabled) return;
26
+
27
+ const timestamp = new Date().toISOString();
28
+ const logMessage = `[${timestamp}] ${message}\n`;
29
+
30
+ console.log('[pastekitlab]', message);
31
+
32
+ try {
33
+ fs.appendFileSync(CONFIG.logFile, logMessage, 'utf8');
34
+ } catch (e) {
35
+ }
36
+ }
37
+
38
+ // ==================== 工具 ====================
39
+ function bufferToString(buffer) {
40
+ try {
41
+ if (!buffer) return null;
42
+ const result = buffer.toString('utf8');
43
+ if (/[^\x20-\x7E\r\n\t]/.test(result)) return null;
44
+ return result;
45
+ } catch {
46
+ return null;
47
+ }
48
+ }
11
49
 
12
- // ⚠️ 必须先同步调用 next,否则 Whistle 代理链会阻塞超时
13
- next();
50
+ function bufferToBase64(buffer) {
51
+ try {
52
+ return buffer ? buffer.toString('base64') : null;
53
+ } catch {
54
+ return null;
55
+ }
56
+ }
14
57
 
15
- // 异步获取 Body(不阻塞 next)
16
- req.getBody({ maxLength: 512 * 1024 }).then(buf => {
17
- console.log(`📦 REQ Body 长度: ${buf ? buf.length : 0}`);
18
- // 这里再调用 broadcast(...)
19
- }).catch(e => console.error('❌ getBody error:', e.message));
58
+ function generateId() {
59
+ return 'req_' + Date.now() + '_' + Math.random().toString(36).slice(2);
60
+ }
20
61
 
21
- } catch (e) {
22
- console.error('❌ request 钩子异常:', e.message);
23
- next(); // 异常也必须放行
62
+ // ==================== 域名过滤 ====================
63
+ function shouldIntercept(url) {
64
+ if (!domainConfig.length) return true;
65
+
66
+ try {
67
+ const {hostname} = new URL(url);
68
+
69
+ return domainConfig.some(domain => {
70
+ if (hostname === domain) return true;
71
+ if (domain.startsWith('*.')) {
72
+ const base = domain.slice(2);
73
+ return hostname.endsWith('.' + base) || hostname === base;
24
74
  }
25
- },
75
+ return false;
76
+ });
77
+ } catch {
78
+ return false;
79
+ }
80
+ }
81
+
82
+ // ==================== WebSocket ====================
83
+ function startWebSocketServer() {
84
+ if (wss) return;
85
+
86
+ wss = new WebSocket.Server({
87
+ port: CONFIG.wsPort,
88
+ path: '/ws'
89
+ });
90
+
91
+ wss.on('connection', (ws, req) => {
92
+ const id = ++clientIdCounter;
26
93
 
27
- response(req, res, next) {
94
+ clients.set(id, ws);
95
+ log(`客户端连接: ${id}`);
96
+
97
+ ws.send(JSON.stringify({
98
+ msgType: 'connected',
99
+ clientId: id
100
+ }));
101
+
102
+ ws.on('message', (data) => {
28
103
  try {
29
- const url = req.fullUrl || req.url;
30
- console.log(`🟢 [RESPONSE HOOK] ${res.statusCode} ${url}`);
31
- next();
104
+ const msg = JSON.parse(data.toString());
105
+
106
+ if (msg.msgType === 'domain') {
107
+ domainConfig = msg.domain || [];
108
+ log(`更新域名配置: ${domainConfig.join(',')}`);
109
+ }
110
+ } catch {
111
+ }
112
+ });
32
113
 
33
- res.getBody({ maxLength: 512 * 1024 }).then(buf => {
34
- console.log(`📦 RES Body 长度: ${buf ? buf.length : 0}`);
35
- }).catch(e => console.error('❌ getBody error:', e.message));
114
+ ws.on('close', () => {
115
+ clients.delete(id);
116
+ log(`客户端断开: ${id}`);
117
+ });
118
+ });
36
119
 
37
- } catch (e) {
38
- console.error('❌ response 钩子异常:', e.message);
39
- next();
120
+ log(`WebSocket 启动: ws://127.0.0.1:${CONFIG.wsPort}/ws`);
121
+ }
122
+
123
+ function broadcast(type, data) {
124
+ if (!clients.size) return;
125
+
126
+ const msg = JSON.stringify({
127
+ msgType: type,
128
+ ...data,
129
+ timestamp: Date.now()
130
+ });
131
+
132
+ clients.forEach(ws => {
133
+ if (ws.readyState === WebSocket.OPEN) {
134
+ ws.send(msg);
135
+ }
136
+ });
137
+ }
138
+
139
+ // ==================== Whistle 插件主逻辑 ====================
140
+ module.exports = (server, options) => {
141
+ log('插件已启动(使用 axios 转发请求)');
142
+ startWebSocketServer();
143
+
144
+ server.on('request', async (req, res) => {
145
+ try {
146
+ const url = req.fullUrl || req.url;
147
+ console.log('>>> [GLOBAL]', url);
148
+
149
+ // 收集请求体
150
+ let reqBody = '';
151
+ for await (const chunk of req) {
152
+ reqBody += chunk;
40
153
  }
154
+
155
+ // 构建请求数据
156
+ const requestData = {
157
+ eventId: generateId(),
158
+ url,
159
+ method: req.method,
160
+ headers: {...req.headers},
161
+ body: bufferToString(Buffer.from(reqBody)),
162
+ bodyBase64: Buffer.from(reqBody).toString('base64'),
163
+ timestamp: Date.now()
164
+ };
165
+
166
+ // 通知 Chrome 插件:请求开始
167
+ broadcast('REQUEST', requestData);
168
+ log(`REQ ${req.method} ${url}`);
169
+
170
+ // 通过 axios 转发请求
171
+ const startTime = Date.now();
172
+ const response = await axios({
173
+ method: req.method.toLowerCase(),
174
+ url: url,
175
+ headers: req.headers,
176
+ data: reqBody || undefined,
177
+ validateStatus: () => true // 不抛出 HTTP 错误
178
+ });
179
+ const duration = Date.now() - startTime;
180
+
181
+ // 设置响应状态码和头
182
+ res.statusCode = response.status;
183
+ Object.keys(response.headers).forEach(key => {
184
+ res.setHeader(key, response.headers[key]);
185
+ });
186
+
187
+ // 返回响应体
188
+ res.end(response.data);
189
+
190
+ // 构建响应数据
191
+ const responseData = {
192
+ ...requestData,
193
+ statusCode: response.status,
194
+ responseHeaders: {...response.headers},
195
+ responseBody: bufferToString(
196
+ typeof response.data === 'string'
197
+ ? Buffer.from(response.data)
198
+ : Buffer.from(JSON.stringify(response.data))
199
+ ),
200
+ responseBodyBase64: (
201
+ typeof response.data === 'string'
202
+ ? Buffer.from(response.data)
203
+ : Buffer.from(JSON.stringify(response.data))
204
+ ).toString('base64'),
205
+ duration
206
+ };
207
+
208
+ // 通知 Chrome 插件:响应完成
209
+ broadcast('RESPONSE', responseData);
210
+ log(`转发成功: ${req.method} ${url} -> ${response.status} (${duration}ms)`);
211
+
212
+ } catch (error) {
213
+ log(`转发失败: ${error.message}`);
214
+ res.statusCode = 500;
215
+ res.end(JSON.stringify({ error: error.message }));
216
+
217
+ // 通知 Chrome 插件:请求失败
218
+ broadcast('ERROR', {
219
+ url: req.fullUrl || req.url,
220
+ method: req.method,
221
+ error: error.message,
222
+ timestamp: Date.now()
223
+ });
41
224
  }
42
- };
225
+ });
43
226
  };
227
+
228
+ // UI 接口保持不变
229
+ module.exports.ui = (uiServer, options) => {
230
+ uiServer.on('request', (req, res) => {
231
+ if (req.url === '/plugin/pastekitlab/config') {
232
+ res.setHeader('Content-Type', 'application/json');
233
+ res.end(JSON.stringify({ status: 'ok', wsPort: CONFIG.wsPort }));
234
+ }
235
+ });
236
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "whistle.pastekitlab",
3
- "version": "1.6.9",
3
+ "version": "1.7.2",
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": {
@@ -16,6 +16,7 @@
16
16
  "author": "PasteKit Lab",
17
17
  "license": "MIT",
18
18
  "dependencies": {
19
+ "axios": "^1.6.0",
19
20
  "ws": "^8.19.0"
20
21
  },
21
22
  "whistlePlugin": true,