apitrap 1.1.0

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 (4) hide show
  1. package/README.md +200 -0
  2. package/index.d.ts +117 -0
  3. package/index.js +395 -0
  4. package/package.json +30 -0
package/README.md ADDED
@@ -0,0 +1,200 @@
1
+ # apitrap
2
+
3
+ **Turn your Express routes into living API documentation — automatically.**
4
+
5
+ Most teams write their API docs manually, or forget to update them after changes.
6
+ `apitrap` captures real API traffic as it happens and sends it to your monitor dashboard, so your collection always reflects what your API _actually does_.
7
+
8
+ ```bash
9
+ npm install apitrap
10
+ ```
11
+
12
+ ---
13
+
14
+ ## How it works
15
+
16
+ Add one middleware per route group. Every time a request hits that route, the library captures the method, path, body, query, response, status code, and duration — then sends it to your monitor server in the background.
17
+
18
+ No manual effort. No extra tools. Just traffic → collection.
19
+
20
+ ---
21
+
22
+ ## Quick start
23
+
24
+ ### 1. Initialize (once, in your entry file)
25
+
26
+ ```javascript
27
+ const { initApiCapture } = require("apitrap");
28
+
29
+ initApiCapture({
30
+ appName: "my-app",
31
+ monitorUrl: "https://your-monitor-server.com/api/capture",
32
+ monitorApiKey: process.env.MONITOR_API_KEY,
33
+ });
34
+ ```
35
+
36
+ ### 2. Add to your routes
37
+
38
+ ```javascript
39
+ const { getClient } = require("apitrap");
40
+
41
+ const router = express.Router();
42
+ const auth = getClient().createMiddlewareFactory("Authentication");
43
+
44
+ router.post(
45
+ "/login",
46
+ auth.capture("User login", "Login page"),
47
+ async (req, res) => {
48
+ // your logic here
49
+ res.json({ success: true });
50
+ },
51
+ );
52
+
53
+ router.post(
54
+ "/register",
55
+ auth.capture("New user registration", ["Login page", "Onboarding"]),
56
+ async (req, res) => {
57
+ res.json({ userId: "..." });
58
+ },
59
+ );
60
+ ```
61
+
62
+ That's it. Both routes are now tracked in your dashboard under the **Authentication** group.
63
+
64
+ ---
65
+
66
+ ## TypeScript
67
+
68
+ ```typescript
69
+ import { initApiCapture, getClient } from "apitrap";
70
+
71
+ initApiCapture({
72
+ appName: "my-ts-app",
73
+ monitorUrl: process.env.MONITOR_URL,
74
+ monitorApiKey: process.env.MONITOR_API_KEY,
75
+ });
76
+
77
+ const api = getClient().createMiddlewareFactory("Products");
78
+
79
+ router.get("/products", api.capture("List all products"), handler);
80
+ ```
81
+
82
+ ---
83
+
84
+ ## Try it without a monitor server
85
+
86
+ Skip `monitorUrl` entirely. The library switches to **debug mode** and prints captured data to your console — perfect for development.
87
+
88
+ ```javascript
89
+ initApiCapture({
90
+ appName: "local-test",
91
+ // no monitorUrl needed
92
+ });
93
+ ```
94
+
95
+ Output:
96
+
97
+ ```
98
+ --- [ApiCapture] [200] 12ms ---
99
+ [POST] /api/login
100
+ Desc: User login
101
+ Body: { "email": "user@example.com", "password": "[REDACTED]" }
102
+ Response: { "success": true }
103
+ ---------------------------
104
+ ```
105
+
106
+ ---
107
+
108
+ ## Save to a local file
109
+
110
+ Useful for offline inspection or building your initial API collection before setting up a monitor server:
111
+
112
+ ```javascript
113
+ initApiCapture({
114
+ appName: "my-app",
115
+ saveLocal: true,
116
+ localPath: "./data/api-capture.json",
117
+ });
118
+ ```
119
+
120
+ ---
121
+
122
+ ## Sensitive data is always masked
123
+
124
+ Passwords, tokens, and secrets are automatically redacted before leaving your server. You can add custom keys too:
125
+
126
+ ```javascript
127
+ initApiCapture({
128
+ sensitiveKeys: ["ssn", "tax-id", "card-number"],
129
+ });
130
+ ```
131
+
132
+ The following keys are masked by default (case-insensitive): `password`, `token`, `secret`, `creditcard`, `pin`, `auth`, `authorization`, `cookie`.
133
+
134
+ ---
135
+
136
+ ## Graceful shutdown
137
+
138
+ If you need to make sure all captured events are flushed before your server stops:
139
+
140
+ ```javascript
141
+ process.on("SIGTERM", async () => {
142
+ await getClient().shutdown();
143
+ process.exit(0);
144
+ });
145
+ ```
146
+
147
+ ---
148
+
149
+ ## Configuration
150
+
151
+ | Option | Type | Default | Description |
152
+ | :-------------- | :--------- | :------------------------ | :------------------------------------------------------ |
153
+ | `appName` | `string` | `"Unknown App"` | App name shown in the monitor dashboard |
154
+ | `monitorUrl` | `string` | — | Endpoint of your monitor server |
155
+ | `monitorApiKey` | `string` | — | API key for authenticating with the monitor server |
156
+ | `enabled` | `boolean` | `true` | Master switch — set to `false` to disable all capturing |
157
+ | `debug` | `boolean` | `true` if no `monitorUrl` | Print captured data to console instead of sending |
158
+ | `saveLocal` | `boolean` | `false` | Append captured events to a local JSON file |
159
+ | `localPath` | `string` | `./captured-apis.json` | Path for the local JSON file |
160
+ | `sensitiveKeys` | `string[]` | See above | Additional keys to redact from bodies and queries |
161
+ | `batchSize` | `number` | `10` | Max events per HTTP batch to the monitor server |
162
+ | `batchInterval` | `number` | `3000` | How often (ms) to flush the event queue |
163
+
164
+ ---
165
+
166
+ ## What gets captured per request
167
+
168
+ | Field | Description |
169
+ | :----------- | :--------------------------------------------- |
170
+ | `route` | Full URL path including query string |
171
+ | `method` | HTTP method (GET, POST, etc.) |
172
+ | `body` | Request body (sensitive keys redacted) |
173
+ | `query` | Query parameters |
174
+ | `response` | Response body (sensitive keys redacted) |
175
+ | `statusCode` | HTTP status code |
176
+ | `durationMs` | Request processing time in milliseconds |
177
+ | `desc` | Your description for this endpoint |
178
+ | `menus` | Menu/page labels for grouping in the UI |
179
+ | `routeName` | Group name (set via `createMiddlewareFactory`) |
180
+ | `capturedAt` | ISO timestamp |
181
+
182
+ ---
183
+
184
+ ## Changelog
185
+
186
+ ### v1.1.0
187
+
188
+ - **Response capture** — `res.json()` is now intercepted to capture response body
189
+ - **Status code + duration** — every captured event includes HTTP status and response time
190
+ - **Async local save** — file I/O is now non-blocking
191
+ - **Batch sender** — events are queued and sent in batches to reduce HTTP overhead
192
+ - **Deep masking fix** — nested sensitive keys are now correctly redacted
193
+ - **`enabled` config** — replaces `openGen` (still supported for backward compatibility)
194
+ - **`shutdown()` method** — flush pending events before process exit
195
+
196
+ ---
197
+
198
+ ## License
199
+
200
+ ISC
package/index.d.ts ADDED
@@ -0,0 +1,117 @@
1
+ import { Request, Response, NextFunction } from "express";
2
+
3
+ export interface ApiCaptureConfig {
4
+ /** ชื่อแอปพลิเคชัน */
5
+ appName?: string;
6
+
7
+ /** URL ของ Monitor Server */
8
+ monitorUrl?: string;
9
+
10
+ /** API Key สำหรับ authenticate กับ Monitor Server */
11
+ monitorApiKey?: string;
12
+
13
+ /**
14
+ * เปิด/ปิดการ capture (ค่าเริ่มต้น: true)
15
+ * @alias openGen (deprecated — ใช้ enabled แทน)
16
+ */
17
+ enabled?: boolean;
18
+
19
+ /** @deprecated ใช้ enabled แทน */
20
+ openGen?: boolean;
21
+
22
+ /** เปิด debug mode — print ข้อมูลลง console แทนการยิง API */
23
+ debug?: boolean;
24
+
25
+ /** บันทึกข้อมูลลงไฟล์ JSON (ค่าเริ่มต้น: false) */
26
+ saveLocal?: boolean;
27
+
28
+ /** path ของไฟล์ที่จะบันทึก (ค่าเริ่มต้น: ./captured-apis.json) */
29
+ localPath?: string;
30
+
31
+ /** Keys เพิ่มเติมที่ต้องการ redact (case-insensitive) */
32
+ sensitiveKeys?: string[];
33
+
34
+ /**
35
+ * จำนวน events ต่อ batch (ค่าเริ่มต้น: 10)
36
+ * เมื่อ queue เต็มจะ flush ทันที
37
+ */
38
+ batchSize?: number;
39
+
40
+ /**
41
+ * ช่วงเวลา flush อัตโนมัติ เป็น ms (ค่าเริ่มต้น: 3000)
42
+ */
43
+ batchInterval?: number;
44
+ }
45
+
46
+ export interface ApiCaptureParams {
47
+ routeName?: string;
48
+ menus?: string[];
49
+ route?: string;
50
+ method?: string;
51
+ desc?: string;
52
+ body?: any;
53
+ query?: any;
54
+ /** v1.x: response body ที่ถูก intercept */
55
+ responseBody?: any;
56
+ /** v1.x: HTTP status code ของ response */
57
+ statusCode?: number;
58
+ /** v1.x: เวลาที่ใช้ในการ process request (ms) */
59
+ durationMs?: number;
60
+ }
61
+
62
+ export type MiddlewareFunction = (
63
+ req: Request,
64
+ res: Response,
65
+ next: NextFunction,
66
+ ) => void;
67
+
68
+ export interface MiddlewareFactory {
69
+ /**
70
+ * สร้าง Express middleware สำหรับ route นั้นๆ
71
+ * @param desc - คำอธิบาย API
72
+ * @param menus - ชื่อเมนูที่ใช้ API นี้ (optional)
73
+ */
74
+ capture: (desc?: string, menus?: string | string[]) => MiddlewareFunction;
75
+ }
76
+
77
+ export class ApiCaptureClient {
78
+ serverUrl: string;
79
+ apiKey: string;
80
+ enabled: boolean;
81
+ appName: string;
82
+ debug: boolean;
83
+ saveLocal: boolean;
84
+ localPath: string;
85
+ sensitiveKeys: string[];
86
+
87
+ constructor(config?: ApiCaptureConfig);
88
+
89
+ maskSensitiveData(data: any): any;
90
+ capture(params: ApiCaptureParams): void;
91
+
92
+ /**
93
+ * สร้าง Middleware factory สำหรับกลุ่ม routes
94
+ * @param routeName - ชื่อกลุ่ม route (แสดงใน dashboard)
95
+ * @param routeEnabled - เปิด/ปิด capture สำหรับกลุ่มนี้ (ค่าเริ่มต้น: true)
96
+ */
97
+ createMiddlewareFactory(
98
+ routeName: string,
99
+ routeEnabled?: boolean,
100
+ ): MiddlewareFactory;
101
+
102
+ /**
103
+ * Flush ข้อมูลที่ค้างอยู่ใน queue และหยุด timer
104
+ * ควรเรียกก่อน process.exit()
105
+ */
106
+ shutdown(): Promise<void>;
107
+ }
108
+
109
+ /**
110
+ * Initialize ApiCapture client แบบ Singleton
111
+ */
112
+ export function initApiCapture(config?: ApiCaptureConfig): ApiCaptureClient;
113
+
114
+ /**
115
+ * ดึง client instance ที่ init ไว้แล้ว
116
+ */
117
+ export function getClient(): ApiCaptureClient;
package/index.js ADDED
@@ -0,0 +1,395 @@
1
+ const http = require("http");
2
+ const https = require("https");
3
+ const fs = require("fs");
4
+ const fsPromises = require("fs").promises;
5
+ const path = require("path");
6
+
7
+ class ApiCaptureClient {
8
+ constructor(config = {}) {
9
+ this.serverUrl = config.monitorUrl || process.env.MONITOR_URL;
10
+ this.apiKey = config.monitorApiKey || process.env.MONITOR_API_KEY || "";
11
+ this.appName = config.appName || "Unknown App";
12
+ this.debug = config.debug === true || !this.serverUrl;
13
+
14
+ // v1.x: รองรับทั้ง enabled และ openGen (backward compat)
15
+ if (config.enabled !== undefined) {
16
+ this.enabled = config.enabled !== false;
17
+ } else if (config.openGen !== undefined) {
18
+ this.enabled = config.openGen !== false;
19
+ } else {
20
+ this.enabled = true;
21
+ }
22
+
23
+ // Local storage config
24
+ this.saveLocal = config.saveLocal === true;
25
+ this.localPath = config.localPath || "./captured-apis.json";
26
+
27
+ // Sensitive keys
28
+ this.sensitiveKeys = [
29
+ "password",
30
+ "token",
31
+ "secret",
32
+ "creditcard",
33
+ "pin",
34
+ "auth",
35
+ "authorization",
36
+ "cookie",
37
+ ...(config.sensitiveKeys || []),
38
+ ];
39
+
40
+ // v1.x: Batch sender — กันยิง HTTP ทุก request
41
+ this._queue = [];
42
+ this._batchSize = config.batchSize || 10;
43
+ this._batchInterval = config.batchInterval || 3000; // ms
44
+ this._flushTimer = null;
45
+
46
+ if (this.serverUrl) {
47
+ this._startBatchFlush();
48
+ }
49
+
50
+ if (!this.serverUrl && !this.debug && !this.saveLocal) {
51
+ this.enabled = false;
52
+ }
53
+ }
54
+
55
+ // ---------------------------------------------------------
56
+ // v1.x FIX: Deep clone ด้วย structuredClone (ไม่ mutate ข้อมูลหลัก)
57
+ // ---------------------------------------------------------
58
+ maskSensitiveData(data) {
59
+ if (!data || typeof data !== "object") return data;
60
+
61
+ // structuredClone รองรับ Node 17+ / ถ้า env เก่าใช้ JSON fallback
62
+ let cloned;
63
+ try {
64
+ cloned = structuredClone(data);
65
+ } catch {
66
+ cloned = JSON.parse(JSON.stringify(data));
67
+ }
68
+
69
+ return this._maskDeep(cloned);
70
+ }
71
+
72
+ _maskDeep(obj) {
73
+ if (!obj || typeof obj !== "object") return obj;
74
+
75
+ for (const key of Object.keys(obj)) {
76
+ const lowerKey = key.toLowerCase();
77
+ const isSensitive = this.sensitiveKeys.some((s) =>
78
+ lowerKey.includes(s.toLowerCase()),
79
+ );
80
+
81
+ if (isSensitive) {
82
+ obj[key] = "[REDACTED]";
83
+ } else if (typeof obj[key] === "object" && obj[key] !== null) {
84
+ obj[key] = this._maskDeep(obj[key]);
85
+ }
86
+ }
87
+
88
+ return obj;
89
+ }
90
+
91
+ // ---------------------------------------------------------
92
+ // v1.x NEW: Intercept res.json() เพื่อ capture response body
93
+ // ---------------------------------------------------------
94
+ _patchResponse(res) {
95
+ const originalJson = res.json.bind(res);
96
+ let capturedResponseBody = null;
97
+
98
+ res.json = function (body) {
99
+ capturedResponseBody = body;
100
+ return originalJson(body);
101
+ };
102
+
103
+ return {
104
+ getBody: () => capturedResponseBody,
105
+ };
106
+ }
107
+
108
+ // ---------------------------------------------------------
109
+ // v1.x NEW: Async local save (ไม่ block event loop)
110
+ // ---------------------------------------------------------
111
+ async _saveLocalAsync(payloadObj) {
112
+ try {
113
+ const fullPath = path.resolve(this.localPath);
114
+ const dir = path.dirname(fullPath);
115
+
116
+ await fsPromises.mkdir(dir, { recursive: true });
117
+
118
+ let existingData = [];
119
+ try {
120
+ const fileContent = await fsPromises.readFile(fullPath, "utf-8");
121
+ const parsed = JSON.parse(fileContent);
122
+ existingData = Array.isArray(parsed) ? parsed : [];
123
+ } catch {
124
+ existingData = [];
125
+ }
126
+
127
+ existingData.push(payloadObj);
128
+ await fsPromises.writeFile(
129
+ fullPath,
130
+ JSON.stringify(existingData, null, 2),
131
+ "utf-8",
132
+ );
133
+
134
+ if (this.debug) {
135
+ console.log(`[ApiCapture] Saved to: ${fullPath}`);
136
+ }
137
+ } catch (err) {
138
+ console.error("[ApiCapture] Failed to save local JSON:", err.message);
139
+ }
140
+ }
141
+
142
+ // ---------------------------------------------------------
143
+ // v1.x NEW: Batch flush — ส่ง HTTP เป็น batch ลด overhead
144
+ // ---------------------------------------------------------
145
+ _startBatchFlush() {
146
+ this._flushTimer = setInterval(() => {
147
+ this._flush();
148
+ }, this._batchInterval);
149
+
150
+ // ป้องกัน timer ค้างตอน process ปิด
151
+ if (this._flushTimer.unref) this._flushTimer.unref();
152
+ }
153
+
154
+ _flush() {
155
+ if (this._queue.length === 0) return;
156
+
157
+ const batch = this._queue.splice(0, this._batchSize);
158
+ this._sendBatch(batch);
159
+ }
160
+
161
+ _sendBatch(batch) {
162
+ const payload = JSON.stringify({ events: batch });
163
+
164
+ try {
165
+ const url = new URL(this.serverUrl);
166
+ const client = url.protocol === "https:" ? https : http;
167
+
168
+ const options = {
169
+ hostname: url.hostname,
170
+ port: url.port || (url.protocol === "https:" ? 443 : 80),
171
+ path: url.pathname,
172
+ method: "POST",
173
+ headers: {
174
+ "Content-Type": "application/json",
175
+ "Content-Length": Buffer.byteLength(payload),
176
+ "x-api-key": this.apiKey,
177
+ },
178
+ };
179
+
180
+ const req = client.request(options, (res) => {
181
+ if (this.debug) {
182
+ let responseData = "";
183
+ res.on("data", (c) => (responseData += c));
184
+ res.on("end", () => {
185
+ if (res.statusCode >= 400) {
186
+ console.error(
187
+ `[ApiCapture] Server error (${res.statusCode}):`,
188
+ responseData,
189
+ );
190
+ } else {
191
+ console.log(
192
+ `[ApiCapture] Sent ${batch.length} event(s) (${res.statusCode})`,
193
+ );
194
+ }
195
+ });
196
+ } else {
197
+ res.on("data", () => {});
198
+ }
199
+ });
200
+
201
+ req.on("error", (err) => {
202
+ if (this.debug) {
203
+ console.error(
204
+ "[ApiCapture] Cannot reach monitor server:",
205
+ err.message,
206
+ );
207
+ }
208
+ // v1.x: put failed events back in queue (simple retry)
209
+ this._queue.unshift(...batch);
210
+ });
211
+
212
+ req.write(payload);
213
+ req.end();
214
+ } catch (err) {
215
+ if (this.debug) {
216
+ console.error("[ApiCapture] Invalid monitor URL:", err.message);
217
+ }
218
+ }
219
+ }
220
+
221
+ // ---------------------------------------------------------
222
+ // capture() — core method
223
+ // ---------------------------------------------------------
224
+ capture(params) {
225
+ if (!this.enabled) return;
226
+
227
+ const {
228
+ routeName,
229
+ menus,
230
+ route,
231
+ method,
232
+ desc,
233
+ body,
234
+ query,
235
+ responseBody, // v1.x NEW
236
+ statusCode, // v1.x NEW
237
+ durationMs, // v1.x NEW
238
+ } = params;
239
+
240
+ const safeBody = this.maskSensitiveData(body);
241
+ const safeQuery = this.maskSensitiveData(query);
242
+ const safeResponse = this.maskSensitiveData(responseBody);
243
+
244
+ const payloadObj = {
245
+ appName: this.appName,
246
+ routeName: routeName || "unknown",
247
+ menus: Array.isArray(menus) ? menus : [],
248
+ route: route || "/",
249
+ method: (method || "GET").toUpperCase(),
250
+ desc: desc || "",
251
+ body: safeBody || null,
252
+ query: safeQuery || null,
253
+ response: safeResponse || null, // v1.x NEW
254
+ statusCode: statusCode || null, // v1.x NEW
255
+ durationMs: durationMs || null, // v1.x NEW
256
+ capturedAt: new Date().toISOString(),
257
+ };
258
+
259
+ // Debug mode
260
+ if (this.debug) {
261
+ const status = statusCode ? ` [${statusCode}]` : "";
262
+ const dur = durationMs ? ` ${durationMs}ms` : "";
263
+ console.log(`\n--- [ApiCapture]${status}${dur} ---`);
264
+ console.log(`[${payloadObj.method}] ${payloadObj.route}`);
265
+ if (payloadObj.desc) console.log(`Desc: ${payloadObj.desc}`);
266
+ if (payloadObj.body)
267
+ console.log("Body:", JSON.stringify(payloadObj.body, null, 2));
268
+ if (payloadObj.query)
269
+ console.log("Query:", JSON.stringify(payloadObj.query, null, 2));
270
+ if (payloadObj.response)
271
+ console.log("Response:", JSON.stringify(payloadObj.response, null, 2));
272
+ console.log("---------------------------\n");
273
+ }
274
+
275
+ // Async local save
276
+ if (this.saveLocal) {
277
+ this._saveLocalAsync(payloadObj);
278
+ }
279
+
280
+ // Push to batch queue
281
+ if (this.serverUrl) {
282
+ this._queue.push(payloadObj);
283
+
284
+ // Flush immediately if batch is full
285
+ if (this._queue.length >= this._batchSize) {
286
+ this._flush();
287
+ }
288
+ }
289
+ }
290
+
291
+ // ---------------------------------------------------------
292
+ // createMiddlewareFactory() — v1.x: now captures response + timing
293
+ // ---------------------------------------------------------
294
+ createMiddlewareFactory(routeName, routeEnabled = true) {
295
+ return {
296
+ capture: (desc = "", menus = []) => {
297
+ if (!routeEnabled || !this.enabled) {
298
+ return (req, res, next) => next();
299
+ }
300
+
301
+ const menuArray = Array.isArray(menus) ? menus : menus ? [menus] : [];
302
+
303
+ return (req, res, next) => {
304
+ const startTime = Date.now();
305
+
306
+ // v1.x: Patch res.json to intercept response body
307
+ const { getBody } = this._patchResponse(res);
308
+
309
+ res.on("finish", () => {
310
+ this.capture({
311
+ routeName,
312
+ menus: menuArray,
313
+ route: req.originalUrl || req.path,
314
+ method: req.method,
315
+ desc,
316
+ body: req.body,
317
+ query: req.query,
318
+ responseBody: getBody(), // v1.x NEW
319
+ statusCode: res.statusCode, // v1.x NEW
320
+ durationMs: Date.now() - startTime, // v1.x NEW
321
+ });
322
+ });
323
+
324
+ next();
325
+ };
326
+ },
327
+ };
328
+ }
329
+
330
+ // ---------------------------------------------------------
331
+ // flush() — manual flush ก่อน process ปิด
332
+ // ---------------------------------------------------------
333
+ async shutdown() {
334
+ if (this._flushTimer) clearInterval(this._flushTimer);
335
+ if (this._queue.length > 0) {
336
+ this._flush();
337
+ // รอนิดนึงให้ HTTP request ไปก่อน
338
+ await new Promise((resolve) => setTimeout(resolve, 500));
339
+ }
340
+ }
341
+ }
342
+
343
+ // ---------------------------------------------------------
344
+ // Singleton
345
+ // ---------------------------------------------------------
346
+ let clientInstance = null;
347
+
348
+ const initApiCapture = (config = {}) => {
349
+ if (!clientInstance) {
350
+ clientInstance = new ApiCaptureClient(config);
351
+ } else {
352
+ if (config.monitorUrl) clientInstance.serverUrl = config.monitorUrl;
353
+ if (config.monitorApiKey) clientInstance.apiKey = config.monitorApiKey;
354
+ if (config.appName) clientInstance.appName = config.appName;
355
+ if (config.enabled !== undefined)
356
+ clientInstance.enabled = config.enabled !== false;
357
+ else if (config.openGen !== undefined)
358
+ clientInstance.enabled = config.openGen !== false;
359
+ if (config.debug !== undefined)
360
+ clientInstance.debug = config.debug === true;
361
+ if (config.saveLocal !== undefined)
362
+ clientInstance.saveLocal = config.saveLocal === true;
363
+ if (config.localPath) clientInstance.localPath = config.localPath;
364
+ if (config.sensitiveKeys) {
365
+ clientInstance.sensitiveKeys = [
366
+ "password",
367
+ "token",
368
+ "secret",
369
+ "creditcard",
370
+ "pin",
371
+ "auth",
372
+ "authorization",
373
+ "cookie",
374
+ ...config.sensitiveKeys,
375
+ ];
376
+ }
377
+ }
378
+ return clientInstance;
379
+ };
380
+
381
+ const getClient = () => {
382
+ if (!clientInstance) {
383
+ console.warn(
384
+ "[ApiCapture] Warning: getClient() called before initApiCapture(). Using defaults.",
385
+ );
386
+ return initApiCapture();
387
+ }
388
+ return clientInstance;
389
+ };
390
+
391
+ module.exports = {
392
+ ApiCaptureClient,
393
+ initApiCapture,
394
+ getClient,
395
+ };
package/package.json ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "apitrap",
3
+ "version": "1.1.0",
4
+ "description": "Express middleware that captures API traffic and sends it to your monitor dashboard — turn real requests into living API documentation.",
5
+ "main": "index.js",
6
+ "types": "index.d.ts",
7
+ "files": [
8
+ "index.js",
9
+ "index.d.ts",
10
+ "README.md"
11
+ ],
12
+ "keywords": [
13
+ "express",
14
+ "api",
15
+ "capture",
16
+ "monitor",
17
+ "middleware",
18
+ "documentation",
19
+ "traffic",
20
+ "collection"
21
+ ],
22
+ "repository": {
23
+ "type": "git",
24
+ "url": "https://github.com/POND9912/apitrap"
25
+ },
26
+ "license": "ISC",
27
+ "engines": {
28
+ "node": ">=16.0.0"
29
+ }
30
+ }