entexto-cli 2.0.1 → 2.1.1
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/lib/commands/tunnel.js +100 -37
- package/package.json +1 -1
package/lib/commands/tunnel.js
CHANGED
|
@@ -102,7 +102,7 @@ module.exports = async function tunnel(opts) {
|
|
|
102
102
|
|
|
103
103
|
// Procesar requests del server
|
|
104
104
|
socket.on('tunnel-request', (payload, cb) => {
|
|
105
|
-
const { method, path: reqPath, headers, body, id } = payload;
|
|
105
|
+
const { method, path: reqPath, headers, body, id, streamId } = payload;
|
|
106
106
|
|
|
107
107
|
// Construir URL completa al target local
|
|
108
108
|
const localUrl = new URL(reqPath, targetUrl);
|
|
@@ -126,55 +126,118 @@ module.exports = async function tunnel(opts) {
|
|
|
126
126
|
const timestamp = new Date().toLocaleTimeString();
|
|
127
127
|
process.stdout.write(chalk.gray(` [${timestamp}] `) + chalk.yellow(method.padEnd(6)) + chalk.white(reqPath) + ' ');
|
|
128
128
|
|
|
129
|
+
// Detectar si el server soporta streaming (envía streamId)
|
|
130
|
+
const useStreaming = !!streamId;
|
|
131
|
+
|
|
129
132
|
const localReq = proto.request(reqOpts, (localRes) => {
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
let bodyBuf = Buffer.concat(chunks);
|
|
134
|
-
|
|
135
|
-
// Reescribir el host local → URL pública del túnel en respuestas de texto.
|
|
136
|
-
// Esto cubre el caso de Elementor/webpack cuyo __webpack_public_path__
|
|
137
|
-
// queda hardcodeado como cmy-rent-car.local (o el dominio local) en el JS.
|
|
138
|
-
const ct = (localRes.headers['content-type'] || '').toLowerCase();
|
|
139
|
-
const isText = /\b(javascript|css|html|json|xml|svg|text\/)/i.test(ct);
|
|
140
|
-
if (isText && publicUrl && bodyBuf.length > 0) {
|
|
141
|
-
const localOrigins = [
|
|
142
|
-
parsedTarget.origin, // http://cmy-rent-car.local
|
|
143
|
-
parsedTarget.origin.replace(/^http:/, 'https:'), // https://cmy-rent-car.local
|
|
144
|
-
parsedTarget.host, // cmy-rent-car.local
|
|
145
|
-
];
|
|
146
|
-
let text = bodyBuf.toString('utf8');
|
|
147
|
-
for (const origin of localOrigins) {
|
|
148
|
-
// Reemplazar como origen completo primero, luego como host suelto
|
|
149
|
-
text = text.split(origin).join(publicUrl);
|
|
150
|
-
}
|
|
151
|
-
bodyBuf = Buffer.from(text, 'utf8');
|
|
152
|
-
// Corregir Content-Length si venía fijo
|
|
153
|
-
localRes.headers['content-length'] = String(bodyBuf.length);
|
|
154
|
-
}
|
|
133
|
+
const ct = (localRes.headers['content-type'] || '').toLowerCase();
|
|
134
|
+
const isText = /\b(javascript|css|html|json|xml|svg|text\/)/i.test(ct);
|
|
135
|
+
const status = localRes.statusCode;
|
|
155
136
|
|
|
156
|
-
|
|
157
|
-
|
|
137
|
+
if (useStreaming) {
|
|
138
|
+
// ── Modo streaming: enviar head + chunks + end ──
|
|
158
139
|
const statusColor = status < 300 ? chalk.green(status) : status < 400 ? chalk.cyan(status) : chalk.red(status);
|
|
159
|
-
|
|
140
|
+
process.stdout.write(statusColor + ' ');
|
|
141
|
+
|
|
142
|
+
// SSE / event-stream y chunked (transfer-encoding) siempre van streaming real
|
|
143
|
+
const isSSE = /event-stream/i.test(ct);
|
|
144
|
+
const isChunked = /chunked/i.test(localRes.headers['transfer-encoding'] || '');
|
|
145
|
+
// Para responses de texto pequeños, acumular para poder reescribir URLs
|
|
146
|
+
// Para binarios, SSE, chunked o responses grandes, hacer streaming directo
|
|
147
|
+
const isSmallText = isText && !isSSE && !isChunked &&
|
|
148
|
+
localRes.headers['content-length'] && parseInt(localRes.headers['content-length']) < 5 * 1024 * 1024;
|
|
149
|
+
|
|
150
|
+
if (isSmallText) {
|
|
151
|
+
// Acumular texto para URL rewriting, luego enviar en un solo stream
|
|
152
|
+
const chunks = [];
|
|
153
|
+
localRes.on('data', (chunk) => chunks.push(chunk));
|
|
154
|
+
localRes.on('end', () => {
|
|
155
|
+
let bodyBuf = Buffer.concat(chunks);
|
|
156
|
+
if (publicUrl && bodyBuf.length > 0) {
|
|
157
|
+
const localOrigins = [
|
|
158
|
+
parsedTarget.origin,
|
|
159
|
+
parsedTarget.origin.replace(/^http:/, 'https:'),
|
|
160
|
+
parsedTarget.host,
|
|
161
|
+
];
|
|
162
|
+
let text = bodyBuf.toString('utf8');
|
|
163
|
+
for (const origin of localOrigins) {
|
|
164
|
+
text = text.split(origin).join(publicUrl);
|
|
165
|
+
}
|
|
166
|
+
bodyBuf = Buffer.from(text, 'utf8');
|
|
167
|
+
}
|
|
168
|
+
const h = { ...localRes.headers };
|
|
169
|
+
h['content-length'] = String(bodyBuf.length);
|
|
170
|
+
socket.emit('tunnel-stream-head', { streamId, status, headers: h });
|
|
171
|
+
socket.emit('tunnel-stream-chunk', { streamId, data: bodyBuf.toString('base64') });
|
|
172
|
+
socket.emit('tunnel-stream-end', { streamId });
|
|
173
|
+
console.log(chalk.gray(`(${bodyBuf.length}B)`));
|
|
174
|
+
});
|
|
175
|
+
} else {
|
|
176
|
+
// Streaming real: enviar chunks conforme llegan
|
|
177
|
+
const h = { ...localRes.headers };
|
|
178
|
+
delete h['content-length']; // no sabemos el tamaño final
|
|
179
|
+
socket.emit('tunnel-stream-head', { streamId, status, headers: h });
|
|
180
|
+
|
|
181
|
+
let totalBytes = 0;
|
|
182
|
+
localRes.on('data', (chunk) => {
|
|
183
|
+
totalBytes += chunk.length;
|
|
184
|
+
socket.emit('tunnel-stream-chunk', { streamId, data: chunk.toString('base64') });
|
|
185
|
+
});
|
|
186
|
+
localRes.on('end', () => {
|
|
187
|
+
socket.emit('tunnel-stream-end', { streamId });
|
|
188
|
+
console.log(chalk.gray(`(${totalBytes}B streamed)`));
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
} else {
|
|
192
|
+
// ── Modo clásico: acumular todo y callback ──
|
|
193
|
+
const chunks = [];
|
|
194
|
+
localRes.on('data', (chunk) => chunks.push(chunk));
|
|
195
|
+
localRes.on('end', () => {
|
|
196
|
+
let bodyBuf = Buffer.concat(chunks);
|
|
197
|
+
|
|
198
|
+
if (isText && publicUrl && bodyBuf.length > 0) {
|
|
199
|
+
const localOrigins = [
|
|
200
|
+
parsedTarget.origin,
|
|
201
|
+
parsedTarget.origin.replace(/^http:/, 'https:'),
|
|
202
|
+
parsedTarget.host,
|
|
203
|
+
];
|
|
204
|
+
let text = bodyBuf.toString('utf8');
|
|
205
|
+
for (const origin of localOrigins) {
|
|
206
|
+
text = text.split(origin).join(publicUrl);
|
|
207
|
+
}
|
|
208
|
+
bodyBuf = Buffer.from(text, 'utf8');
|
|
209
|
+
localRes.headers['content-length'] = String(bodyBuf.length);
|
|
210
|
+
}
|
|
160
211
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
212
|
+
const statusColor = status < 300 ? chalk.green(status) : status < 400 ? chalk.cyan(status) : chalk.red(status);
|
|
213
|
+
console.log(statusColor + chalk.gray(` (${bodyBuf.length}B)`));
|
|
214
|
+
|
|
215
|
+
cb({
|
|
216
|
+
status: status,
|
|
217
|
+
headers: localRes.headers,
|
|
218
|
+
body: bodyBuf.toString('base64'),
|
|
219
|
+
});
|
|
165
220
|
});
|
|
166
|
-
}
|
|
221
|
+
}
|
|
167
222
|
});
|
|
168
223
|
|
|
169
224
|
localReq.on('error', (err) => {
|
|
170
225
|
console.log(chalk.red('ERR ') + chalk.gray(err.message));
|
|
171
|
-
|
|
226
|
+
if (useStreaming) {
|
|
227
|
+
socket.emit('tunnel-stream-error', { streamId, error: err.message, status: 502 });
|
|
228
|
+
} else {
|
|
229
|
+
cb({ error: err.message, status: 502 });
|
|
230
|
+
}
|
|
172
231
|
});
|
|
173
232
|
|
|
174
|
-
localReq.setTimeout(25000, () => {
|
|
233
|
+
localReq.setTimeout(useStreaming ? 120000 : 25000, () => {
|
|
175
234
|
localReq.destroy();
|
|
176
235
|
console.log(chalk.red('TIMEOUT'));
|
|
177
|
-
|
|
236
|
+
if (useStreaming) {
|
|
237
|
+
socket.emit('tunnel-stream-error', { streamId, error: 'Local server timeout', status: 504 });
|
|
238
|
+
} else {
|
|
239
|
+
cb({ error: 'Local server timeout', status: 504 });
|
|
240
|
+
}
|
|
178
241
|
});
|
|
179
242
|
|
|
180
243
|
if (body) {
|