@duckfly/proxy 1.0.3 → 1.0.5

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/README.md CHANGED
@@ -24,6 +24,7 @@ This allows your API documentation and MCP (Model Context Protocol) servers to b
24
24
  - Continuously enriched API documentation
25
25
  - MCP server generation for AI integrations
26
26
  - Documentation aligned with real API usage
27
+ - No code changes required; just point your application to the proxy
27
28
 
28
29
  ## Quick Start
29
30
 
@@ -39,15 +40,41 @@ npm install -g @duckfly/proxy
39
40
  npx @duckfly/proxy
40
41
  ```
41
42
 
42
- After running, the CLI will ask for:
43
+ ### Interactive mode
44
+
45
+ When you run the CLI without arguments, it will guide you through the setup:
43
46
 
44
47
  | Prompt | Description | Default |
45
48
  |---|---|---|
46
- | **Token** | Your Duckfly application token | |
49
+ | **Token** | Your Duckfly application token | |
47
50
  | **Proxy Port** | Port where the proxy listens | `8080` |
48
- | **Target URL** | Your backend API address | `http://localhost:3000` |
51
+ | **Target URL** | Your backend API address | App URL from token |
52
+
53
+ Configuration is saved locally in `.duckfly-proxy.json` so you don't need to enter it every time.
54
+
55
+ ### Command line mode
56
+
57
+ You can pass all options via CLI arguments. The CLI will only ask for what is missing.
58
+
59
+ ```bash
60
+ # Full command line (no prompts)
61
+ duckfly-proxy --token abc123 --port 8080 --target http://localhost:3000
62
+
63
+ # Hybrid: pass what you know, it asks the rest
64
+ duckfly-proxy --token abc123
65
+
66
+ # With npx
67
+ npx @duckfly/proxy --token abc123 --port 8080 --target http://localhost:3000
68
+ ```
69
+
70
+ All available flags:
49
71
 
50
- Configuration is saved locally in `.duckfly-proxy.json` so you don't need to re-enter it every time.
72
+ | Flag | Description |
73
+ |---|---|
74
+ | `--token <token>` | Duckfly application token |
75
+ | `--port <N>` | Proxy port (default: 8080) |
76
+ | `--target <url>` | URL to forward requests to |
77
+ | `--help` | Show help |
51
78
 
52
79
  ## How It Works
53
80
 
@@ -75,7 +102,25 @@ Configuration is saved locally in `.duckfly-proxy.json` so you don't need to re-
75
102
 
76
103
  Duckfly Proxy processes only technical and structural information such as HTTP methods, routes, headers, and payload formats.
77
104
 
78
- Request and response values are replaced with **placeholders** before being sent, ensuring no sensitive or user-specific data is transmitted.
105
+ Request and response values are replaced with **placeholders** before being sent, ensuring no sensitive or user specific data is transmitted.
106
+
107
+ ### What is sent
108
+
109
+ - HTTP methods and route paths
110
+ - Header names (values are redacted)
111
+ - Body structure and field names (values replaced with type appropriate placeholders)
112
+ - Status codes and content types
113
+
114
+ ### What is NOT sent
115
+
116
+ - Authentication tokens, passwords, or secrets
117
+ - Request/response body values
118
+ - Cookie values
119
+ - Query parameter values for sensitive fields
120
+
121
+ ## Host Alias
122
+
123
+ When your application domain (from the Duckfly token) differs from `localhost`, the proxy automatically adjusts the Host header so that observed requests are associated with the correct domain. This happens automatically; no configuration is needed.
79
124
 
80
125
  ## License
81
126
 
package/bin/cli.js CHANGED
@@ -8,42 +8,96 @@ const ApiClient = require('../src/api-client');
8
8
  const fs = require('fs');
9
9
  const path = require('path');
10
10
 
11
+ // ─── Constants ───────────────────────────────────────────────────
12
+
13
+ const DUCKFLY_API_URL = 'https://api.duckfly.dev';
14
+ const DUCKFLY_PROXY_URL = 'https://proxy.duckfly.dev';
15
+
16
+ const CONFIG_FILE = path.join(process.cwd(), '.duckfly-proxy.json');
17
+
18
+ // ─── Logo ────────────────────────────────────────────────────────
19
+
11
20
  const yellow = chalk.hex('#FFD54F');
12
21
  const gold = chalk.hex('#FFC107');
13
22
 
14
23
  const DUCKFLY_LOGO = `
15
24
  ${yellow(`
16
- @@@@@@@@@
17
- @%+-------=%@
18
- @@+-----------=@@
19
- @@=-----=##=-----@@
20
- @*------@@=#---+*%@
21
- @+------+%%+-=%*+*%@@
22
- @*---------=#%++%+++#%%@
23
- @@=-------=@%#++++++++%@
24
- @@+-------=*%%@@@@@@@
25
- @@@*-------===%@
26
- @%+---------------#@
27
- @%----------+--------#@
28
- @@@@%=------------#-------=@@
29
- @*------+*=-------%-------=@@
30
- @%--+@*+---------##-------+@
31
- @%=-=#%%#+----+%*------=+@@
32
- @@*==+%%##%%#=-----===*@@@
33
- @@#=============+*%#+=+*@@
34
- @@@%****#%%*+#@@@@%@@@
35
- @@@@%#++#@@@
25
+ @@@@@@@@@
26
+ @%+-------=%@
27
+ @@+-----------=@@
28
+ @@=-----=##=-----@@
29
+ @*------@@=#---+*%@
30
+ @+------+%%+-=%*+*%@@
31
+ @*---------=#%++%+++#%%@
32
+ @@=-------=@%#++++++++%@
33
+ @@+-------=*%%@@@@@@@
34
+ @@@*-------===%@
35
+ @%+---------------#@
36
+ @%----------+--------#@
37
+ @@@@%=------------#-------=@@
38
+ @*------+*=-------%-------=@@
39
+ @%--+@*+---------##-------+@
40
+ @%=-=#%%#+----+%*------=+@@
41
+ @@*==+%%##%%#=-----===*@@@
42
+ @@#=============+*%#+=+*@@
43
+ @@@%****#%%*+#@@@@%@@@
44
+ @@@@%#++#@@@
36
45
  `)}
37
46
  ${gold('═══════════════════════════════════════════════════════════════════════════════════════════')}
38
47
  ${chalk.bold.yellow(' 🦆 DUCKFLY PROXY v1.0.0 ')}
39
48
  ${gold('═══════════════════════════════════════════════════════════════════════════════════════════')}
40
- ${chalk.gray(' Capture and document your APIs automatically ')}
49
+ ${chalk.gray(' Observe and document your APIs automatically ')}
41
50
  ${chalk.gray(' https://duckfly.dev ')}
42
51
  ${gold('═══════════════════════════════════════════════════════════════════════════════════════════')}
43
52
  `;
44
53
 
45
- const DUCKFLY_API_URL = 'https://api.duckfly.dev';
46
- const DUCKFLY_PROXY_URL = 'https://proxy.duckfly.dev';
54
+ // ─── CLI arg parsing ─────────────────────────────────────────────
55
+
56
+ function parseArgs() {
57
+ const argv = process.argv.slice(2);
58
+ const args = {};
59
+ for (let i = 0; i < argv.length; i++) {
60
+ const arg = argv[i];
61
+ if (!arg.startsWith('--')) continue;
62
+ const key = arg.slice(2);
63
+ const next = argv[i + 1];
64
+ if (!next || next.startsWith('--')) {
65
+ args[key] = true;
66
+ } else {
67
+ args[key] = next;
68
+ i++;
69
+ }
70
+ }
71
+ return args;
72
+ }
73
+
74
+ const CLI_KEYS = ['token', 'port', 'target'];
75
+
76
+ function hasCliArgs(cliArgs) {
77
+ return CLI_KEYS.some(k => cliArgs[k] !== undefined);
78
+ }
79
+
80
+ // ─── Help ────────────────────────────────────────────────────────
81
+
82
+ function showHelp() {
83
+ console.log(chalk.bold.yellow('\n🦆 Duckfly Proxy\n'));
84
+ console.log(chalk.white('Uso: duckfly-proxy [opções]\n'));
85
+ console.log(chalk.white('Opções:'));
86
+ console.log(chalk.yellow(' --token <token> ') + chalk.gray('Token do Duckfly'));
87
+ console.log(chalk.yellow(' --port <N> ') + chalk.gray('Porta do proxy (default: 8080)'));
88
+ console.log(chalk.yellow(' --target <url> ') + chalk.gray('URL de destino para encaminhar as requisições'));
89
+ console.log(chalk.yellow(' --help ') + chalk.gray('Mostrar esta ajuda'));
90
+ console.log('');
91
+ console.log(chalk.white('Exemplos:'));
92
+ console.log(chalk.gray(' # Modo interativo completo'));
93
+ console.log(chalk.white(' duckfly-proxy\n'));
94
+ console.log(chalk.gray(' # Tudo via CLI (sem prompts)'));
95
+ console.log(chalk.white(' duckfly-proxy --token abc123 --port 8080 --target http://localhost:3000\n'));
96
+ console.log(chalk.gray(' # Híbrido (passa o que sabe, pergunta o que falta)'));
97
+ console.log(chalk.white(' duckfly-proxy --token abc123\n'));
98
+ }
99
+
100
+ // ─── State ───────────────────────────────────────────────────────
47
101
 
48
102
  let config = {
49
103
  token: null,
@@ -51,21 +105,21 @@ let config = {
51
105
  appUrl: null,
52
106
  domainPackageId: null,
53
107
  proxyPort: 8080,
54
- targetUrl: 'http://localhost:3000',
55
- apiUrl: DUCKFLY_API_URL,
56
- proxyUrl: DUCKFLY_PROXY_URL
108
+ targetUrl: null,
109
+ hostAlias: null,
57
110
  };
58
111
 
59
- const CONFIG_FILE = path.join(process.cwd(), '.duckfly-proxy.json');
112
+ // ─── Config persistence ──────────────────────────────────────────
60
113
 
61
114
  function loadConfig() {
62
115
  try {
63
116
  if (fs.existsSync(CONFIG_FILE)) {
64
117
  const saved = JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'));
65
- config = { ...config, ...saved };
118
+ const { apiUrl, proxyUrl, ...rest } = saved;
119
+ config = { ...config, ...rest };
66
120
  return true;
67
121
  }
68
- } catch (error) {
122
+ } catch {
69
123
  console.log(chalk.yellow('⚠️ Erro ao carregar configuração salva'));
70
124
  }
71
125
  return false;
@@ -73,21 +127,23 @@ function loadConfig() {
73
127
 
74
128
  function saveConfig() {
75
129
  try {
76
- fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
77
- } catch (error) {
130
+ const { token, appName, appUrl, domainPackageId, proxyPort, targetUrl, hostAlias } = config;
131
+ fs.writeFileSync(CONFIG_FILE, JSON.stringify({ token, appName, appUrl, domainPackageId, proxyPort, targetUrl, hostAlias }, null, 2));
132
+ } catch {
78
133
  console.log(chalk.yellow('⚠️ Não foi possível salvar a configuração'));
79
134
  }
80
135
  }
81
136
 
137
+ // ─── Token validation ────────────────────────────────────────────
138
+
82
139
  async function validateToken(token) {
83
140
  const spinner = new Spinner(chalk.yellow('%s Validando token...'));
84
141
  spinner.setSpinnerString('|/-\\');
85
142
  spinner.start();
86
143
 
87
144
  try {
88
- const apiClient = new ApiClient(config.apiUrl, config.proxyUrl, token);
145
+ const apiClient = new ApiClient(DUCKFLY_API_URL, DUCKFLY_PROXY_URL, token);
89
146
  const result = await apiClient.validateToken();
90
-
91
147
  spinner.stop(true);
92
148
 
93
149
  if (result.valid) {
@@ -96,7 +152,6 @@ async function validateToken(token) {
96
152
  console.log(chalk.gray('─'.repeat(60)));
97
153
  console.log(chalk.yellow(' Nome: ') + chalk.white(result.appName));
98
154
  console.log(chalk.yellow(' URL: ') + chalk.white(result.appUrl));
99
- console.log(chalk.yellow(' Package ID: ') + chalk.white(result.domainPackageId));
100
155
  console.log(chalk.gray('─'.repeat(60)) + '\n');
101
156
 
102
157
  config.appName = result.appName;
@@ -114,81 +169,101 @@ async function validateToken(token) {
114
169
  }
115
170
  }
116
171
 
117
- async function askQuestions() {
118
- if (!config.token) {
172
+ // ─── Hybrid prompts ──────────────────────────────────────────────
173
+
174
+ async function askTokenIfMissing(cliArgs) {
175
+ if (cliArgs.token) {
176
+ config.token = String(cliArgs.token).trim();
177
+ } else {
119
178
  const { token } = await inquirer.prompt([
120
179
  {
121
180
  type: 'password',
122
181
  name: 'token',
123
182
  message: chalk.yellow('🔑 Cole seu token do Duckfly:'),
124
183
  mask: '*',
125
- validate: (input) => {
126
- if (!input || input.trim().length === 0) {
127
- return 'Token não pode ser vazio';
128
- }
129
- return true;
130
- }
131
- }
184
+ validate: input => (!!input && input.trim().length > 0) || 'Token não pode ser vazio',
185
+ },
132
186
  ]);
187
+ config.token = token.trim();
188
+ }
133
189
 
134
- config.token = token;
135
-
136
- console.log('');
137
- const valid = await validateToken(config.token);
138
-
139
- if (!valid) {
140
- console.log(chalk.red('❌ Token inválido. Não é possível continuar.\n'));
141
- console.log(chalk.yellow('💡 Obtenha um token válido em: ') + chalk.white('https://duckfly.dev\n'));
142
- process.exit(1);
143
- }
190
+ console.log('');
191
+ const valid = await validateToken(config.token);
192
+ if (!valid) {
193
+ console.log(chalk.red('❌ Token inválido. Não é possível continuar.\n'));
194
+ console.log(chalk.yellow('💡 Obtenha um token válido em: ') + chalk.white('https://duckfly.dev\n'));
195
+ process.exit(1);
144
196
  }
197
+ }
145
198
 
146
- config.apiUrl = DUCKFLY_API_URL;
147
- config.proxyUrl = DUCKFLY_PROXY_URL;
148
-
149
- const questions = [
150
- {
151
- type: 'input',
152
- name: 'proxyPort',
153
- message: chalk.yellow('🔌 Porta do proxy (porta que vai receber as requisições):'),
154
- default: config.proxyPort,
155
- validate: (input) => {
156
- const port = parseInt(input);
157
- if (isNaN(port) || port < 1 || port > 65535) {
158
- return 'Porta inválida (1-65535)';
159
- }
160
- return true;
161
- }
162
- },
163
- {
164
- type: 'input',
165
- name: 'targetUrl',
166
- message: chalk.yellow('🎯 URL de destino (para onde encaminhar as requisições):'),
167
- default: config.targetUrl,
168
- validate: (input) => {
169
- try {
170
- new URL(input);
199
+ async function askProxyConfig(cliArgs) {
200
+ // Port
201
+ if (cliArgs.port != null) {
202
+ config.proxyPort = parseInt(cliArgs.port, 10);
203
+ } else {
204
+ const { proxyPort } = await inquirer.prompt([
205
+ {
206
+ type: 'input',
207
+ name: 'proxyPort',
208
+ message: chalk.yellow('🔌 Porta do proxy (porta que vai receber as requisições):'),
209
+ default: config.proxyPort,
210
+ validate: input => {
211
+ const port = parseInt(input, 10);
212
+ if (Number.isNaN(port) || port < 1 || port > 65535) return 'Porta inválida (1-65535)';
171
213
  return true;
172
- } catch {
173
- return 'URL inválida (ex: http://localhost:3000)';
174
- }
175
- }
176
- }
177
- ];
214
+ },
215
+ },
216
+ ]);
217
+ config.proxyPort = proxyPort;
218
+ }
178
219
 
179
- const answers = await inquirer.prompt(questions);
220
+ // Host alias (automatic)
221
+ config.hostAlias = null;
222
+ if (config.appUrl) {
223
+ try {
224
+ const appHost = new URL(config.appUrl).host;
225
+ const proxyHost = `localhost:${config.proxyPort}`;
226
+
227
+ if (appHost && appHost !== proxyHost && appHost !== `127.0.0.1:${config.proxyPort}`) {
228
+ config.hostAlias = appHost;
229
+ console.log('');
230
+ console.log(chalk.blue(' O domínio da aplicação (') + chalk.bold.white(appHost) + chalk.blue(') difere do proxy local.'));
231
+ console.log(chalk.blue(' As requisições observadas serão enviadas com o domínio ') + chalk.bold.white(appHost));
232
+ console.log(chalk.blue(' para que possam ser processadas corretamente pela aplicação.\n'));
233
+ }
234
+ } catch { }
235
+ }
180
236
 
181
- Object.assign(config, answers);
237
+ // Target URL
238
+ if (cliArgs.target) {
239
+ config.targetUrl = cliArgs.target;
240
+ } else {
241
+ const isWildcard = config.appUrl && config.appUrl.includes('*');
242
+ const defaultTarget = isWildcard ? null : (config.appUrl || config.targetUrl);
182
243
 
183
- return answers;
244
+ const { targetUrl } = await inquirer.prompt([
245
+ {
246
+ type: 'input',
247
+ name: 'targetUrl',
248
+ message: chalk.yellow('🎯 URL de destino (para onde encaminhar as requisições):'),
249
+ default: defaultTarget,
250
+ validate: input => {
251
+ try { new URL(input); return true; } catch { return 'URL inválida (ex: http://localhost:3000)'; }
252
+ },
253
+ },
254
+ ]);
255
+ config.targetUrl = targetUrl;
256
+ }
184
257
  }
185
258
 
259
+ // ─── Proxy startup ───────────────────────────────────────────────
260
+
186
261
  let proxyServer = null;
187
262
 
188
263
  async function startProxy() {
189
264
  console.log(chalk.yellow('\n🚀 Iniciando Duckfly Proxy...\n'));
190
265
 
191
- proxyServer = new ProxyServer(config);
266
+ proxyServer = new ProxyServer({ ...config, apiUrl: DUCKFLY_API_URL, proxyUrl: DUCKFLY_PROXY_URL });
192
267
 
193
268
  try {
194
269
  await proxyServer.start();
@@ -200,66 +275,74 @@ async function startProxy() {
200
275
  console.log(chalk.yellow(' Proxy rodando em: ') + chalk.white(`http://localhost:${config.proxyPort}`));
201
276
  console.log(chalk.yellow(' Encaminhando para: ') + chalk.white(config.targetUrl));
202
277
  console.log(chalk.gray('─'.repeat(60)));
203
- console.log(chalk.yellow('\n💡 Dica: Configure sua aplicação para apontar para ') + chalk.white(`http://localhost:${config.proxyPort}`));
204
- console.log(chalk.yellow(' Todas as requisições serão capturadas e documentadas automaticamente!\n'));
278
+ console.log(chalk.blue('\n💡 Dica: Configure sua aplicação para apontar para ') + chalk.white(`http://localhost:${config.proxyPort}`));
279
+ console.log(chalk.blue(' Todas as requisições serão observadas e documentadas automaticamente!\n'));
205
280
  console.log(chalk.gray('Pressione Ctrl+C para parar o proxy\n'));
206
281
 
207
282
  saveConfig();
208
-
209
283
  } catch (error) {
210
284
  console.log(chalk.red('❌ Erro ao iniciar proxy: ') + chalk.gray(error.message));
211
285
  process.exit(1);
212
286
  }
213
287
  }
214
288
 
215
- async function main() {
216
- console.clear();
289
+ // ─── Main ────────────────────────────────────────────────────────
217
290
 
218
- console.log(DUCKFLY_LOGO);
219
-
220
- const hasConfig = loadConfig();
221
- let useExisting = false;
291
+ async function main() {
292
+ const cliArgs = parseArgs();
222
293
 
223
- if (hasConfig) {
224
- console.log(chalk.green('✅ Configuração anterior encontrada!\n'));
225
- console.log(chalk.gray('─'.repeat(60)));
226
- console.log(chalk.yellow(' App: ') + chalk.white(config.appName || 'N/A'));
227
- console.log(chalk.yellow(' Porta: ') + chalk.white(config.proxyPort));
228
- console.log(chalk.yellow(' Alvo: ') + chalk.white(config.targetUrl));
229
- console.log(chalk.gray('─'.repeat(60)) + '\n');
294
+ if (cliArgs.help) {
295
+ showHelp();
296
+ process.exit(0);
297
+ }
230
298
 
231
- const answer = await inquirer.prompt([
232
- {
233
- type: 'confirm',
234
- name: 'useExisting',
235
- message: chalk.yellow('Deseja usar a configuração salva?'),
236
- default: true
237
- }
238
- ]);
299
+ console.clear();
300
+ console.log(DUCKFLY_LOGO);
239
301
 
240
- useExisting = answer.useExisting;
302
+ const hasCli = hasCliArgs(cliArgs);
241
303
 
242
- if (!useExisting) {
243
- config.token = null;
244
- config.appName = null;
245
- config.proxyPort = 8080;
246
- config.targetUrl = 'http://localhost:3000';
247
- }
248
- }
304
+ // If no CLI args, try loading saved config
305
+ if (!hasCli) {
306
+ const hasConfig = loadConfig();
249
307
 
250
- if (!useExisting) {
251
- await askQuestions();
252
- } else {
253
- console.log('');
254
- const valid = await validateToken(config.token);
308
+ if (hasConfig) {
309
+ console.log(chalk.green('✅ Configuração anterior encontrada!\n'));
310
+ console.log(chalk.gray('─'.repeat(60)));
311
+ console.log(chalk.yellow(' App: ') + chalk.white(config.appName || 'N/A'));
312
+ console.log(chalk.yellow(' Porta: ') + chalk.white(config.proxyPort));
313
+ console.log(chalk.yellow(' Alvo: ') + chalk.white(config.targetUrl));
314
+ console.log(chalk.gray('─'.repeat(60)) + '\n');
255
315
 
256
- if (!valid) {
257
- console.log(chalk.red(' Token salvo está inválido. Configurando novamente...\n'));
258
- config.token = null;
259
- await askQuestions();
316
+ const { useExisting } = await inquirer.prompt([
317
+ { type: 'confirm', name: 'useExisting', message: chalk.yellow('Deseja usar a configuração salva?'), default: true },
318
+ ]);
319
+
320
+ if (useExisting) {
321
+ console.log('');
322
+ const valid = await validateToken(config.token);
323
+ if (!valid) {
324
+ console.log(chalk.red('❌ Token salvo está inválido. Configurando novamente...\n'));
325
+ config.token = null;
326
+ } else {
327
+ await startProxy();
328
+ return;
329
+ }
330
+ } else {
331
+ config.token = null;
332
+ config.appName = null;
333
+ config.appUrl = null;
334
+ config.domainPackageId = null;
335
+ config.proxyPort = 8080;
336
+ config.targetUrl = null;
337
+ try { fs.unlinkSync(CONFIG_FILE); } catch { }
338
+ }
260
339
  }
261
340
  }
262
341
 
342
+ // Fresh setup (interactive or CLI args)
343
+ await askTokenIfMissing(cliArgs);
344
+ await askProxyConfig(cliArgs);
345
+ saveConfig();
263
346
  await startProxy();
264
347
  }
265
348
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@duckfly/proxy",
3
- "version": "1.0.3",
3
+ "version": "1.0.5",
4
4
  "description": "Duckfly Proxy observes real API usage to continuously enrich and generate API documentation and MCP servers on Duckfly.",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -20,7 +20,7 @@
20
20
  "duckfly",
21
21
  "http",
22
22
  "request",
23
- "capture"
23
+ "observe"
24
24
  ],
25
25
  "author": "Duckfly",
26
26
  "license": "MIT",
@@ -47,4 +47,4 @@
47
47
  "publishConfig": {
48
48
  "access": "public"
49
49
  }
50
- }
50
+ }
package/src/api-client.js CHANGED
@@ -9,7 +9,7 @@ class ApiClient {
9
9
 
10
10
  async validateToken() {
11
11
  try {
12
- const url = `${this.apiUrl}/internal/v1/core/token/${this.token}`;
12
+ const url = `${this.apiUrl}/internal/v1/core/token/${this.token}?source=plugin-proxy`;
13
13
 
14
14
  const response = await axios.get(url, {
15
15
  timeout: 30000,
@@ -9,6 +9,7 @@ const { sanitizeRequestData } = require('./sanitizer');
9
9
  class ProxyServer {
10
10
  constructor(config) {
11
11
  this.config = config;
12
+ this.hostAlias = config.hostAlias || null;
12
13
  this.proxy = httpProxy.createProxyServer({});
13
14
  this.app = express();
14
15
  this.server = null;
@@ -44,15 +45,28 @@ class ProxyServer {
44
45
  });
45
46
 
46
47
  this.proxy.on('proxyRes', (proxyRes, req, res) => {
48
+ const MAX_CAPTURE = 10 * 1024 * 1024; // 10MB
49
+ const contentType = (proxyRes.headers['content-type'] || '').toLowerCase();
50
+ const binaryTypes = ['image/', 'video/', 'audio/', 'application/octet-stream', 'application/pdf', 'application/zip'];
51
+ const skipBody = binaryTypes.some(t => contentType.includes(t));
52
+
47
53
  let bodyChunks = [];
54
+ let bodySize = 0;
55
+ let overflow = false;
48
56
 
49
57
  proxyRes.on('data', (chunk) => {
50
- bodyChunks.push(chunk);
58
+ bodySize += chunk.length;
59
+ if (!skipBody && !overflow) {
60
+ if (bodySize <= MAX_CAPTURE) {
61
+ bodyChunks.push(chunk);
62
+ } else {
63
+ overflow = true;
64
+ bodyChunks = [];
65
+ }
66
+ }
51
67
  });
52
68
 
53
69
  proxyRes.on('end', () => {
54
- const responseBody = Buffer.concat(bodyChunks);
55
-
56
70
  const statusColor = proxyRes.statusCode >= 400 ? chalk.red :
57
71
  proxyRes.statusCode >= 300 ? chalk.yellow :
58
72
  chalk.green;
@@ -61,9 +75,18 @@ class ProxyServer {
61
75
  chalk.blue('←'),
62
76
  statusColor(proxyRes.statusCode),
63
77
  chalk.gray(req.url),
64
- chalk.dim(`${responseBody.length} bytes`)
78
+ chalk.dim(`${bodySize} bytes`)
65
79
  );
66
80
 
81
+ let responseBody;
82
+ if (skipBody) {
83
+ responseBody = Buffer.from(`[Binary content: ${contentType}, ${bodySize} bytes]`);
84
+ } else if (overflow) {
85
+ responseBody = Buffer.from(`[Body too large: ${bodySize} bytes]`);
86
+ } else {
87
+ responseBody = Buffer.concat(bodyChunks);
88
+ }
89
+
67
90
  this.captureResponse(req, proxyRes, responseBody);
68
91
  });
69
92
  });
@@ -85,10 +108,16 @@ class ProxyServer {
85
108
 
86
109
  captureRequest(req, res) {
87
110
  try {
111
+ const headers = { ...req.headers };
112
+
113
+ if (this.hostAlias) {
114
+ headers.host = this.hostAlias;
115
+ }
116
+
88
117
  req._captureData = {
89
118
  method: req.method,
90
119
  url: req.url,
91
- headers: { ...req.headers },
120
+ headers,
92
121
  body: this.parseBody(req.rawBody, req.headers['content-type']),
93
122
  protocol: req.socket.encrypted ? 'https' : 'http',
94
123
  timestamp: new Date().toISOString(),
@@ -98,7 +127,7 @@ class ProxyServer {
98
127
  }
99
128
  };
100
129
  } catch (error) {
101
- console.log(chalk.yellow('⚠'), chalk.gray('Erro ao capturar requisição:'), error.message);
130
+ console.log(chalk.yellow('⚠'), chalk.gray('Erro ao observar requisição:'), error.message);
102
131
  }
103
132
  }
104
133
 
@@ -140,7 +169,7 @@ class ProxyServer {
140
169
  this.stats.captured++;
141
170
 
142
171
  } catch (error) {
143
- console.log(chalk.yellow('⚠'), chalk.gray('Erro ao capturar resposta:'), error.message);
172
+ console.log(chalk.yellow('⚠'), chalk.gray('Erro ao observar resposta:'), error.message);
144
173
  }
145
174
  }
146
175
 
@@ -396,7 +425,7 @@ class ProxyServer {
396
425
  process.stdout.write('\r' + chalk.bold.white('📊 Estatísticas:') + '\x1b[K\n');
397
426
  process.stdout.write('\r' + chalk.yellow(' Uptime: ') + chalk.white(`${hours}h ${minutes}m ${seconds}s`) + '\x1b[K\n');
398
427
  process.stdout.write('\r' + chalk.yellow(' Total: ') + chalk.white(this.stats.totalRequests) + '\x1b[K\n');
399
- process.stdout.write('\r' + chalk.yellow(' Capturadas: ') + chalk.white(this.stats.captured) + '\x1b[K\n');
428
+ process.stdout.write('\r' + chalk.yellow(' Observadas: ') + chalk.white(this.stats.captured) + '\x1b[K\n');
400
429
  process.stdout.write('\r' + chalk.yellow(' Enviadas: ') + chalk.green(this.stats.sent) + '\x1b[K\n');
401
430
  process.stdout.write('\r' + chalk.yellow(' Falhas: ') + (this.stats.failed > 0 ? chalk.red(this.stats.failed) : chalk.gray(this.stats.failed)) + '\x1b[K\n');
402
431
  process.stdout.write('\r' + chalk.yellow(' Na fila: ') + chalk.yellow(this.requestQueue.getQueueSize()) + '\x1b[K\n');