// Run me with node --require /tmp/spy.js const https = require('https'); const http = require('http'); const fs = require('fs'); const log = fs.createWriteStream('/tmp/spy.log', { flags: 'a' }); function w(msg) { log.write(msg + '\n'); } function patchModule(mod, name) { const origRequest = mod.request; mod.request = function() { const req = origRequest.apply(this, arguments); const chunks = []; const origWrite = req.write.bind(req); const origEnd = req.end.bind(req); w(`\n===== ${name} REQUEST ${new Date().toISOString()} =====`); w(`Method: ${req.method} Host: ${req.host} Path: ${req.path}`); req.write = function(chunk, encoding, cb) { if (chunk) chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)); return origWrite(chunk, encoding, cb); }; req.end = function(chunk, encoding, cb) { if (chunk) chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)); try { const hdrs = req.getHeaders ? req.getHeaders() : {}; w('Headers: ' + JSON.stringify(hdrs, null, 2)); } catch(e) { w('Headers: (unavailable)'); } const body = Buffer.concat(chunks).toString('utf8'); if (body.length > 200_000) { w('Body (truncated): ' + body.slice(0, 200_000) + '\n... [truncated]'); } else { w('Body: ' + body); } w('===== END =====\n'); return origEnd(chunk, encoding, cb); }; return req; }; } patchModule(https, 'HTTPS'); patchModule(http, 'HTTP'); // Patch globalThis.fetch (undici-based in Node 24) const origFetch = globalThis.fetch; globalThis.fetch = async function(input, init) { const url = typeof input === 'string' ? input : (input instanceof URL ? input.toString() : input.url); const method = init?.method || (input?.method) || 'GET'; const headers = init?.headers || input?.headers || {}; w(`\n===== FETCH REQUEST ${new Date().toISOString()} =====`); w(`${method} ${url}`); try { w('Headers: ' + JSON.stringify(Object.fromEntries( headers instanceof Headers ? headers.entries() : Array.isArray(headers) ? headers : Object.entries(headers) ), null, 2)); } catch(e) { w('Headers: ' + JSON.stringify(headers, null, 2)); } let bodyForLog = null; if (init?.body) { try { if (typeof init.body === 'string') { bodyForLog = init.body; } else if (Buffer.isBuffer(init.body)) { bodyForLog = init.body.toString('utf8'); } else if (init.body instanceof ArrayBuffer || ArrayBuffer.isView(init.body)) { bodyForLog = Buffer.from(init.body).toString('utf8'); } else { bodyForLog = String(init.body); } } catch(e) { bodyForLog = '(could not read body)'; } } if (bodyForLog) { if (bodyForLog.length > 20000) { w('Body (truncated): ' + bodyForLog.slice(0, 20000) + '\n... [truncated]'); } else { w('Body: ' + bodyForLog); } } else { w('Body: (none)'); } w('===== END =====\n'); return origFetch.apply(this, arguments); }; w('[spy.js] loaded at ' + new Date().toISOString());