UNPKG

api-visualizer

Version:

A tunnel-based API inspector for visualizing requests and responses in real time.

161 lines (160 loc) 5.87 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.tunnelMiddleware = void 0; exports.connectToTunnel = connectToTunnel; const socket_io_client_1 = require("socket.io-client"); const crypto_1 = __importDefault(require("crypto")); const Busboy = __importStar(require("busboy")); // --- Socket Management --- let socket = null; let connected = false; let apiKey = ""; const TUNNEL_URL = "https://api-visualizer-cdyu.onrender.com"; function connectToTunnel(key) { apiKey = key; socket = (0, socket_io_client_1.io)(TUNNEL_URL, { query: { apiKey, type: "agent", }, transports: ["websocket"], reconnection: true, reconnectionDelay: 5000, }); socket.on("connect", () => { connected = true; console.log("Connected to API visualizer"); }); socket.on("disconnect", () => { connected = false; console.warn("Disconnected from API visualizer"); }); socket.on("connect_error", (err) => { console.error("WebSocket connection error:", err.message); }); } // --- Encryption --- function encryptJSON(data, secret) { const iv = crypto_1.default.randomBytes(12); const key = crypto_1.default.createHash("sha256").update(secret).digest(); const cipher = crypto_1.default.createCipheriv("aes-256-gcm", key, iv); const json = JSON.stringify(data); let encrypted = cipher.update(json, "utf8"); encrypted = Buffer.concat([encrypted, cipher.final()]); const authTag = cipher.getAuthTag(); return `${iv.toString("base64")}:${authTag.toString("base64")}:${encrypted.toString("base64")}`; } // --- Middleware --- const tunnelMiddleware = function (req, res, outerNext) { const contentType = req.headers["content-type"] || ""; // Optional multipart handler (if needed) if (contentType.startsWith("multipart/form-data")) { const busboy = new Busboy({ headers: req.headers }); const fields = {}; const files = []; busboy.on("field", (name, value) => { fields[name] = value; }); busboy.on("file", (name, file, info) => { files.push({ name: info.filename, type: "file" }); file.resume(); }); busboy.on("finish", () => { req.body = { fields, files }; attachLogger(req, res, outerNext); }); req.pipe(busboy); } else { attachLogger(req, res, outerNext); // let express.json() or urlencoded() handle parsing } }; exports.tunnelMiddleware = tunnelMiddleware; // --- Internal Logging Attach --- function attachLogger(req, res, next) { const start = Date.now(); const chunks = []; const originalWrite = res.write.bind(res); const originalEnd = res.end.bind(res); res.write = function (chunk, ...args) { if (chunk) chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)); return originalWrite(chunk, ...args); }; res.end = function (chunk, ...args) { if (chunk) chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)); return originalEnd(chunk, ...args); }; res.on("finish", () => { const duration = Date.now() - start; let ip = req.ip || req.socket.remoteAddress || ""; if (ip === "::1" || ip === "::ffff:127.0.0.1") ip = "127.0.0.1"; else if (ip.startsWith("::ffff:")) ip = ip.replace("::ffff:", ""); const log = { ip, method: req.method, status: res.statusCode, url: req.originalUrl, clientPort: req.socket.remotePort, duration, timestamp: Date.now(), req: { headers: req.headers, body: req.body || null, }, res: { headers: res.getHeaders(), body: Buffer.concat(chunks).toString(), }, }; if (socket === null || socket === void 0 ? void 0 : socket.connected) { try { const encrypted = encryptJSON(log, apiKey); socket.emit("log", { data: encrypted }); } catch (err) { console.error("Failed to emit encrypted log:", err); } } }); next(); }