webhookdrop-tunnel 1.0.3 → 1.0.9

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/bin/tunnel.js +149 -7
  2. package/package.json +1 -1
package/bin/tunnel.js CHANGED
@@ -34,7 +34,7 @@ try {
34
34
  };
35
35
  }
36
36
 
37
- const VERSION = '1.0.3';
37
+ const VERSION = '1.0.8';
38
38
  const DEFAULT_SERVER = 'wss://webhookdrop.app';
39
39
  const POOL_SIZE = 20; // Number of WebSocket connections
40
40
  const PING_INTERVAL = 10000; // 10 seconds
@@ -113,7 +113,7 @@ async function forwardRequest(data) {
113
113
  // Request uncompressed content to avoid encoding issues
114
114
  'accept-encoding': 'identity'
115
115
  },
116
- timeout: 30000
116
+ timeout: 300000 // 5 minutes for very large files like n8n's nodes.json
117
117
  };
118
118
 
119
119
  const req = httpModule.request(requestOptions, (res) => {
@@ -122,13 +122,28 @@ async function forwardRequest(data) {
122
122
  res.on('data', (chunk) => chunks.push(chunk));
123
123
  res.on('end', () => {
124
124
  const buffer = Buffer.concat(chunks);
125
+ const contentType = res.headers['content-type'] || '';
126
+
127
+ // Determine if content is text-based (like OnLocal approach)
128
+ const isText = contentType.includes('text/') ||
129
+ contentType.includes('json') ||
130
+ contentType.includes('javascript') ||
131
+ contentType.includes('xml') ||
132
+ contentType.includes('css');
133
+
134
+ // Use structured body format: {type, data}
135
+ let body;
136
+ if (isText) {
137
+ body = { type: 'text', data: buffer.toString('utf-8') };
138
+ } else {
139
+ body = { type: 'binary', data: buffer.toString('base64') };
140
+ }
141
+
125
142
  resolve({
126
143
  success: true,
127
144
  status_code: res.statusCode,
128
145
  headers: res.headers,
129
- // Send as base64 to preserve binary data
130
- body: buffer.toString('base64'),
131
- body_encoding: 'base64'
146
+ body: body
132
147
  });
133
148
  });
134
149
  });
@@ -159,6 +174,8 @@ async function forwardRequest(data) {
159
174
 
160
175
  // Connection pool
161
176
  const connections = new Map();
177
+ // Local WebSocket sessions for passthrough
178
+ const localWsSessions = new Map();
162
179
  let connectedCount = 0;
163
180
  let totalRequests = 0;
164
181
  let isShuttingDown = false;
@@ -228,6 +245,12 @@ function createConnection(connectionId) {
228
245
  // Forward to local server
229
246
  const response = await forwardRequest(requestData);
230
247
 
248
+ // Log response size for debugging large files
249
+ const bodySize = response.body?.data?.length || 0;
250
+ if (bodySize > 100000) {
251
+ console.log(`${timestamp()} ${chalk.yellow('⚠')} Large response: ${Math.round(bodySize / 1024)}KB`);
252
+ }
253
+
231
254
  // Send response back
232
255
  const responseMessage = {
233
256
  type: message.type === 'webhook_request' ? 'webhook_response' : 'http_response',
@@ -235,13 +258,132 @@ function createConnection(connectionId) {
235
258
  response: response
236
259
  };
237
260
 
238
- if (ws.readyState === WebSocket.OPEN) {
239
- ws.send(JSON.stringify(responseMessage));
261
+ try {
262
+ const jsonStr = JSON.stringify(responseMessage);
263
+ if (ws.readyState === WebSocket.OPEN) {
264
+ ws.send(jsonStr);
265
+ }
266
+ } catch (jsonErr) {
267
+ console.error(`${timestamp()} ${chalk.red('✗')} JSON serialize error: ${jsonErr.message}`);
268
+ // Send error response instead
269
+ if (ws.readyState === WebSocket.OPEN) {
270
+ ws.send(JSON.stringify({
271
+ type: 'http_response',
272
+ request_id: requestId,
273
+ response: { success: false, status_code: 500, error: 'Response too large to serialize' }
274
+ }));
275
+ }
240
276
  }
241
277
 
242
278
  const statusColor = response.success && response.status_code < 400 ? chalk.green : chalk.red;
243
279
  console.log(`${timestamp()} ${statusColor('←')} ${response.status_code || 502} ${response.success ? 'OK' : response.error || 'Error'}`);
244
280
  }
281
+
282
+ // WebSocket passthrough: connect to local WebSocket
283
+ else if (message.type === 'ws_connect') {
284
+ const sessionId = message.data?.session_id || message.session_id;
285
+ const wsPath = message.data?.path || message.path || '/';
286
+ const wsQuery = message.data?.query || message.query || '';
287
+ const requestId = message.request_id;
288
+
289
+ console.log(`${timestamp()} ${chalk.magenta('⚡')} WS Connect: ${wsPath}`);
290
+
291
+ try {
292
+ // Build local WebSocket URL
293
+ const localWsUrl = new URL(wsPath, options.local);
294
+ localWsUrl.protocol = localWsUrl.protocol.replace('http', 'ws');
295
+ if (wsQuery) {
296
+ localWsUrl.search = wsQuery;
297
+ }
298
+
299
+ // Connect to local WebSocket
300
+ const localWs = new WebSocket(localWsUrl.toString());
301
+
302
+ localWs.on('open', () => {
303
+ console.log(`${timestamp()} ${chalk.green('✓')} WS Connected: ${sessionId.slice(0, 8)}`);
304
+ localWsSessions.set(sessionId, { ws: localWs, tunnelWs: ws });
305
+
306
+ // Send success response
307
+ if (ws.readyState === WebSocket.OPEN) {
308
+ ws.send(JSON.stringify({
309
+ type: 'http_response',
310
+ request_id: requestId,
311
+ response: { success: true }
312
+ }));
313
+ }
314
+ });
315
+
316
+ localWs.on('message', (data) => {
317
+ // Forward message from local WebSocket to server
318
+ if (ws.readyState === WebSocket.OPEN) {
319
+ ws.send(JSON.stringify({
320
+ type: 'ws_message',
321
+ session_id: sessionId,
322
+ message: data.toString()
323
+ }));
324
+ }
325
+ });
326
+
327
+ localWs.on('close', () => {
328
+ console.log(`${timestamp()} ${chalk.yellow('○')} WS Closed: ${sessionId.slice(0, 8)}`);
329
+ localWsSessions.delete(sessionId);
330
+
331
+ // Notify server
332
+ if (ws.readyState === WebSocket.OPEN) {
333
+ ws.send(JSON.stringify({
334
+ type: 'ws_closed',
335
+ session_id: sessionId
336
+ }));
337
+ }
338
+ });
339
+
340
+ localWs.on('error', (err) => {
341
+ console.log(`${timestamp()} ${chalk.red('✗')} WS Error: ${err.message}`);
342
+ localWsSessions.delete(sessionId);
343
+
344
+ // Send error response
345
+ if (ws.readyState === WebSocket.OPEN) {
346
+ ws.send(JSON.stringify({
347
+ type: 'http_response',
348
+ request_id: requestId,
349
+ response: { success: false, error: err.message }
350
+ }));
351
+ }
352
+ });
353
+ } catch (e) {
354
+ console.log(`${timestamp()} ${chalk.red('✗')} WS Connect Error: ${e.message}`);
355
+ if (ws.readyState === WebSocket.OPEN) {
356
+ ws.send(JSON.stringify({
357
+ type: 'http_response',
358
+ request_id: requestId,
359
+ response: { success: false, error: e.message }
360
+ }));
361
+ }
362
+ }
363
+ }
364
+
365
+ // WebSocket passthrough: forward message to local WebSocket
366
+ else if (message.type === 'ws_message') {
367
+ const sessionId = message.data?.session_id || message.session_id;
368
+ const wsMessage = message.data?.message || message.message;
369
+
370
+ const session = localWsSessions.get(sessionId);
371
+ if (session && session.ws.readyState === WebSocket.OPEN) {
372
+ session.ws.send(wsMessage);
373
+ }
374
+ }
375
+
376
+ // WebSocket passthrough: close local WebSocket
377
+ else if (message.type === 'ws_close') {
378
+ const sessionId = message.data?.session_id || message.session_id;
379
+
380
+ const session = localWsSessions.get(sessionId);
381
+ if (session) {
382
+ session.ws.close();
383
+ localWsSessions.delete(sessionId);
384
+ console.log(`${timestamp()} ${chalk.yellow('○')} WS Close: ${sessionId.slice(0, 8)}`);
385
+ }
386
+ }
245
387
  } catch (e) {
246
388
  console.error(`${timestamp()} ${chalk.red('Error processing message:')} ${e.message}`);
247
389
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "webhookdrop-tunnel",
3
- "version": "1.0.3",
3
+ "version": "1.0.9",
4
4
  "description": "Forward webhooks from WebhookDrop to your local development server",
5
5
  "main": "index.js",
6
6
  "bin": {