@aikidosec/broker-client 1.0.7 → 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.
package/README.md CHANGED
@@ -21,7 +21,7 @@ docker compose up -d
21
21
  ## Quick Start
22
22
 
23
23
  1. **Generate CLIENT_SECRET in Aikido UI**:
24
- - Navigate to: Settings → Broker Clients → Add New Client
24
+ - Navigate to: Settings → [Broker Clients](https://app.aikido.dev/settings/integrations/broker/clients) → Add New Client
25
25
  - Copy the generated `CLIENT_SECRET`
26
26
 
27
27
  2. **Configure environment** (`.env`):
package/app/client.js CHANGED
@@ -13,7 +13,6 @@ import dns from 'native-dns';
13
13
  import { ResourceManager } from './resourceManager.js';
14
14
  import { HttpsProxyAgent } from 'https-proxy-agent';
15
15
  import { getClientId, setClientIdCache, getServerUrl, getClientSecret } from './config.js';
16
- import { STREAMING_THRESHOLD } from './streaming/state.js';
17
16
  import { registerStreamingHandlers } from './streaming/handlers.js';
18
17
  import { initStreamingResponse } from './streaming/initStreamingResponse.js';
19
18
  import { startStaleStreamCleanup, cleanupAllStreams } from './streaming/cleanup.js';
@@ -33,7 +32,7 @@ const DNS_SERVERS = process.env.DNS_SERVERS
33
32
  : null;
34
33
 
35
34
  // Configure axios defaults
36
- const MAX_RESPONSE_SIZE = 100 * 1024 * 1024; // 100 MB
35
+ const MAX_RESPONSE_SIZE = 50 * 1024 * 1024; // 50 MB (falls back to streaming if exceeded)
37
36
  const axiosConfig = {
38
37
  timeout: 300000,
39
38
  maxRedirects: 5,
@@ -105,28 +104,44 @@ async function resolveInternalHostname(hostname) {
105
104
  }
106
105
 
107
106
  /**
108
- * Get Content-Length via HEAD request to determine if streaming should be used.
109
- * @param {string} url - URL to check
110
- * @param {object} headers - Headers to send with the request
111
- * @returns {Promise<number|null>} Content-Length in bytes, or null if unavailable
107
+ * Make an internal HTTP request with automatic fallback to streaming if buffered response is too large.
108
+ * @param {object} options - Request options
109
+ * @param {string} options.method - HTTP method
110
+ * @param {string} options.url - Target URL
111
+ * @param {object} options.headers - Request headers
112
+ * @param {*} options.body - Request body
113
+ * @param {string} options.requestId - Request ID for logging
114
+ * @returns {Promise<{response: object, streaming: boolean}>} Response and whether streaming was used
112
115
  */
113
- async function getContentLengthViaHead(url, headers) {
114
- try {
115
- const headResponse = await internalHttpClient.head(url, {
116
+ async function forwardRequestToInternalResource ({ method, url, headers, body, requestId }) {
117
+ const makeRequest = async (streaming) => {
118
+ let requestData = {
119
+ method,
120
+ url,
116
121
  headers,
117
- validateStatus: () => true,
118
- timeout: 5000
119
- });
120
- // Headers can be any case (Content-Length, content-length, CONTENT-LENGTH)
121
- const contentLengthHeader = Object.keys(headResponse.headers)
122
- .find(key => key.toLowerCase() === 'content-length');
123
- const contentLength = contentLengthHeader
124
- ? parseInt(headResponse.headers[contentLengthHeader])
125
- : NaN;
126
- return isNaN(contentLength) ? null : contentLength;
127
- } catch (e) {
128
- log.warn(`HEAD request failed, will use buffered approach: ${e.message}`);
129
- return null;
122
+ data: body,
123
+ validateStatus: () => true, // Accept all status codes
124
+ responseType: streaming ? 'stream' : 'arraybuffer'
125
+ }
126
+ if (streaming) {
127
+ requestData.maxContentLength = Infinity;
128
+ }
129
+ // if streaming, this returns a stream instantly, it does not wait for the full response
130
+ return await internalHttpClient.request(requestData);
131
+ };
132
+
133
+ // Try buffered request first, fallback to streaming if response too large
134
+ try {
135
+ return { response: await makeRequest(false), useStreaming: false };
136
+ } catch (reqError) {
137
+ // Fallback to streaming if buffered request exceeded maxContentLength (axios ERR_BAD_RESPONSE)
138
+ if (axios.isAxiosError(reqError) && // axios does not have a specific error for maxcontent length, so we have to check message (this is about the MAX_RESPONSE_SIZE we set above)
139
+ reqError.code === 'ERR_BAD_RESPONSE' &&
140
+ reqError.message?.includes('maxContentLength')) {
141
+ log.warn(`Buffered request exceeded maxContentLength for ${requestId}, falling back to streaming`);
142
+ return { response: await makeRequest(true), useStreaming: true };
143
+ }
144
+ throw reqError;
130
145
  }
131
146
  }
132
147
 
@@ -238,9 +253,20 @@ const socket = io(SERVER_URL, {
238
253
  pingTimeout: 60000, // 60s (default 20s) - time to wait for pong before considering connection dead
239
254
  });
240
255
 
241
- // Socket.IO event handlers
256
+ // Socket.IO event handlers - listen for engine open to catch transport before upgrade
257
+ socket.io.on('open', () => {
258
+ const transport = socket.io?.engine?.transport?.name;
259
+ log.info(`🔌 Engine opened (initial transport: ${transport})`);
260
+
261
+ // Log transport upgrades (e.g., polling -> websocket)
262
+ socket.io.engine.on('upgrade', (newTransport) => {
263
+ log.info(`🔄 Transport upgraded to: ${newTransport?.name}`);
264
+ });
265
+ });
266
+
242
267
  socket.on('connect', async () => {
243
- log.info('✓ Connected to broker server');
268
+ const transport = socket.io?.engine?.transport?.name;
269
+ log.info(`✓ Connected to broker server (transport: ${transport})`);
244
270
 
245
271
  const clientId = getClientId();
246
272
 
@@ -342,26 +368,13 @@ socket.on('forward_request', async (data, callback) => {
342
368
  }
343
369
  }
344
370
 
345
- // Check Content-Length first with HEAD request to decide streaming vs buffered
346
- let useStreaming = false;
347
- let contentLength = null;
348
-
349
- if (method === 'GET') {
350
- contentLength = await getContentLengthViaHead(resolvedUrl, headers);
351
- if (contentLength !== null && contentLength > STREAMING_THRESHOLD) {
352
- useStreaming = true;
353
- log.info(`Large response detected (${(contentLength / (1024 * 1024)).toFixed(2)} MB) - using streaming`);
354
- }
355
- }
356
-
357
- // Make the request with appropriate response type
358
- const response = await internalHttpClient.request({
371
+ // Make the request (with automatic fallback to streaming if buffered response is too large)
372
+ const { response, useStreaming } = await forwardRequestToInternalResource ({
359
373
  method,
360
374
  url: resolvedUrl,
361
375
  headers,
362
- data: body,
363
- validateStatus: () => true, // Accept any status code
364
- responseType: useStreaming ? 'stream' : 'arraybuffer',
376
+ body,
377
+ requestId
365
378
  });
366
379
 
367
380
  if (useStreaming) {
@@ -409,17 +422,16 @@ socket.on('forward_request', async (data, callback) => {
409
422
  * @param {string} requestId - Request ID for tracking
410
423
  * @param {number} statusCode - HTTP status code
411
424
  * @param {object} headers - Response headers
412
- * @param {Buffer|null} responseData - Raw response data (before base64)
425
+ * @param {Buffer|null} responseData - Raw response data (sent as binary)
413
426
  * @param {function} callback - Socket.IO callback
414
427
  */
415
428
  function sendDirectResponse(requestId, statusCode, headers, responseData, callback) {
416
- const responseBody = responseData ? responseData.toString('base64') : null;
417
429
  callback({
418
430
  request_id: requestId,
419
431
  status_code: statusCode,
420
432
  headers: headers,
421
- body: responseBody,
422
- version: 2
433
+ body: responseData || null, // Send raw Buffer - Socket.IO handles binary natively
434
+ version: 3
423
435
  });
424
436
  }
425
437
 
@@ -64,7 +64,7 @@ export async function handleGetNextChunk(data, callback) {
64
64
 
65
65
  callback({
66
66
  request_id: requestId,
67
- data: chunkData.toString('base64'),
67
+ data: chunkData, // Send raw Buffer - Socket.IO handles binary natively
68
68
  complete: isComplete,
69
69
  chunk_index: state.chunkIndex
70
70
  });
@@ -76,7 +76,7 @@ export function initStreamingResponse({ requestId, statusCode, headers, stream,
76
76
  status_code: statusCode,
77
77
  headers: headers,
78
78
  body: null,
79
- version: 2,
79
+ version: 3,
80
80
  streaming: true
81
81
  });
82
82
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aikidosec/broker-client",
3
- "version": "1.0.7",
3
+ "version": "1.0.9",
4
4
  "description": "Aikido Broker Client - Runs in customer network to forward requests to internal resources",
5
5
  "main": "app/client.js",
6
6
  "type": "module",