@gagandeep023/api-gateway 0.4.2 → 0.5.1

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.
@@ -1,5 +1,5 @@
1
1
  import { Router, Request, Response, NextFunction } from 'express';
2
- import { RateLimitConfig, RequestLog, GatewayAnalytics, DeviceEntry, GatewayMiddlewareConfig, ApiKeysConfig, IpRules } from '../types/index.mjs';
2
+ import { RateLimitConfig, LogConfig, RequestLog, GatewayAnalytics, DeviceEntry, GatewayMiddlewareConfig, ApiKeysConfig, IpRules } from '../types/index.mjs';
3
3
 
4
4
  declare class RateLimiterService {
5
5
  private tokenBucket;
@@ -23,7 +23,10 @@ declare class AnalyticsService {
23
23
  private logs;
24
24
  private head;
25
25
  private count;
26
+ private fileLogWriter;
27
+ constructor(logConfig?: LogConfig);
26
28
  addLog(log: RequestLog): void;
29
+ destroy(): void;
27
30
  getRecentLogs(limit?: number, offset?: number): RequestLog[];
28
31
  getAnalytics(rateLimitHits: number): GatewayAnalytics;
29
32
  private getOrderedLogs;
@@ -67,14 +70,14 @@ interface GatewayInstances {
67
70
  analyticsService: AnalyticsService;
68
71
  deviceRegistry?: DeviceRegistryService;
69
72
  middleware: Router;
70
- config: Required<GatewayMiddlewareConfig>;
73
+ config: Required<Omit<GatewayMiddlewareConfig, 'logConfig'>> & Pick<GatewayMiddlewareConfig, 'logConfig'>;
71
74
  }
72
75
  declare function createGatewayMiddleware(userConfig?: GatewayMiddlewareConfig): GatewayInstances;
73
76
 
74
77
  interface GatewayRoutesOptions {
75
78
  rateLimiterService: RateLimiterService;
76
79
  analyticsService: AnalyticsService;
77
- config: Required<GatewayMiddlewareConfig>;
80
+ config: Required<Omit<GatewayMiddlewareConfig, 'logConfig'>> & Pick<GatewayMiddlewareConfig, 'logConfig'>;
78
81
  }
79
82
  declare function createGatewayRoutes(options: GatewayRoutesOptions): Router;
80
83
 
@@ -83,6 +86,23 @@ interface DeviceAuthRoutesOptions {
83
86
  }
84
87
  declare function createDeviceAuthRoutes(options: DeviceAuthRoutesOptions): Router;
85
88
 
89
+ declare class FileLogWriter {
90
+ private logDir;
91
+ private appName;
92
+ private maxLines;
93
+ private currentDate;
94
+ private currentLines;
95
+ private fileIndex;
96
+ private currentFilePath;
97
+ private destroyed;
98
+ constructor(config: LogConfig);
99
+ write(log: RequestLog): void;
100
+ destroy(): void;
101
+ /** Scan existing log files for a date to determine the next incremental number. */
102
+ private scanExistingFiles;
103
+ private rotateFile;
104
+ }
105
+
86
106
  declare function createApiKeyAuth(getKeys: () => ApiKeysConfig, deviceRegistry?: DeviceRegistryService): (req: Request, res: Response, next: NextFunction) => void;
87
107
 
88
108
  declare function createIpFilter(getRules: () => IpRules): (req: Request, res: Response, next: NextFunction) => void;
@@ -100,4 +120,4 @@ declare function parseKey(key: string): {
100
120
  } | null;
101
121
  declare function generateSecret(): string;
102
122
 
103
- export { AnalyticsService, type DeviceAuthRoutesOptions, DeviceRegistryService, type GatewayInstances, type GatewayRoutesOptions, RateLimiterService, createApiKeyAuth, createDeviceAuthRoutes, createGatewayMiddleware, createGatewayRoutes, createIpFilter, createRateLimiter, createRequestLogger, formatKey, generateSecret, generateTOTP, parseKey, validateTOTP };
123
+ export { AnalyticsService, type DeviceAuthRoutesOptions, DeviceRegistryService, FileLogWriter, type GatewayInstances, type GatewayRoutesOptions, RateLimiterService, createApiKeyAuth, createDeviceAuthRoutes, createGatewayMiddleware, createGatewayRoutes, createIpFilter, createRateLimiter, createRequestLogger, formatKey, generateSecret, generateTOTP, parseKey, validateTOTP };
@@ -1,5 +1,5 @@
1
1
  import { Router, Request, Response, NextFunction } from 'express';
2
- import { RateLimitConfig, RequestLog, GatewayAnalytics, DeviceEntry, GatewayMiddlewareConfig, ApiKeysConfig, IpRules } from '../types/index.js';
2
+ import { RateLimitConfig, LogConfig, RequestLog, GatewayAnalytics, DeviceEntry, GatewayMiddlewareConfig, ApiKeysConfig, IpRules } from '../types/index.js';
3
3
 
4
4
  declare class RateLimiterService {
5
5
  private tokenBucket;
@@ -23,7 +23,10 @@ declare class AnalyticsService {
23
23
  private logs;
24
24
  private head;
25
25
  private count;
26
+ private fileLogWriter;
27
+ constructor(logConfig?: LogConfig);
26
28
  addLog(log: RequestLog): void;
29
+ destroy(): void;
27
30
  getRecentLogs(limit?: number, offset?: number): RequestLog[];
28
31
  getAnalytics(rateLimitHits: number): GatewayAnalytics;
29
32
  private getOrderedLogs;
@@ -67,14 +70,14 @@ interface GatewayInstances {
67
70
  analyticsService: AnalyticsService;
68
71
  deviceRegistry?: DeviceRegistryService;
69
72
  middleware: Router;
70
- config: Required<GatewayMiddlewareConfig>;
73
+ config: Required<Omit<GatewayMiddlewareConfig, 'logConfig'>> & Pick<GatewayMiddlewareConfig, 'logConfig'>;
71
74
  }
72
75
  declare function createGatewayMiddleware(userConfig?: GatewayMiddlewareConfig): GatewayInstances;
73
76
 
74
77
  interface GatewayRoutesOptions {
75
78
  rateLimiterService: RateLimiterService;
76
79
  analyticsService: AnalyticsService;
77
- config: Required<GatewayMiddlewareConfig>;
80
+ config: Required<Omit<GatewayMiddlewareConfig, 'logConfig'>> & Pick<GatewayMiddlewareConfig, 'logConfig'>;
78
81
  }
79
82
  declare function createGatewayRoutes(options: GatewayRoutesOptions): Router;
80
83
 
@@ -83,6 +86,23 @@ interface DeviceAuthRoutesOptions {
83
86
  }
84
87
  declare function createDeviceAuthRoutes(options: DeviceAuthRoutesOptions): Router;
85
88
 
89
+ declare class FileLogWriter {
90
+ private logDir;
91
+ private appName;
92
+ private maxLines;
93
+ private currentDate;
94
+ private currentLines;
95
+ private fileIndex;
96
+ private currentFilePath;
97
+ private destroyed;
98
+ constructor(config: LogConfig);
99
+ write(log: RequestLog): void;
100
+ destroy(): void;
101
+ /** Scan existing log files for a date to determine the next incremental number. */
102
+ private scanExistingFiles;
103
+ private rotateFile;
104
+ }
105
+
86
106
  declare function createApiKeyAuth(getKeys: () => ApiKeysConfig, deviceRegistry?: DeviceRegistryService): (req: Request, res: Response, next: NextFunction) => void;
87
107
 
88
108
  declare function createIpFilter(getRules: () => IpRules): (req: Request, res: Response, next: NextFunction) => void;
@@ -100,4 +120,4 @@ declare function parseKey(key: string): {
100
120
  } | null;
101
121
  declare function generateSecret(): string;
102
122
 
103
- export { AnalyticsService, type DeviceAuthRoutesOptions, DeviceRegistryService, type GatewayInstances, type GatewayRoutesOptions, RateLimiterService, createApiKeyAuth, createDeviceAuthRoutes, createGatewayMiddleware, createGatewayRoutes, createIpFilter, createRateLimiter, createRequestLogger, formatKey, generateSecret, generateTOTP, parseKey, validateTOTP };
123
+ export { AnalyticsService, type DeviceAuthRoutesOptions, DeviceRegistryService, FileLogWriter, type GatewayInstances, type GatewayRoutesOptions, RateLimiterService, createApiKeyAuth, createDeviceAuthRoutes, createGatewayMiddleware, createGatewayRoutes, createIpFilter, createRateLimiter, createRequestLogger, formatKey, generateSecret, generateTOTP, parseKey, validateTOTP };
@@ -32,6 +32,7 @@ var backend_exports = {};
32
32
  __export(backend_exports, {
33
33
  AnalyticsService: () => AnalyticsService,
34
34
  DeviceRegistryService: () => DeviceRegistryService,
35
+ FileLogWriter: () => FileLogWriter,
35
36
  RateLimiterService: () => RateLimiterService,
36
37
  createApiKeyAuth: () => createApiKeyAuth,
37
38
  createDeviceAuthRoutes: () => createDeviceAuthRoutes,
@@ -176,6 +177,140 @@ var RateLimiterService = class {
176
177
  }
177
178
  };
178
179
 
180
+ // src/backend/services/FileLogWriter.ts
181
+ var import_fs = require("fs");
182
+ var import_path = require("path");
183
+ var import_crypto = require("crypto");
184
+
185
+ // src/config/defaults.ts
186
+ var DEFAULT_LOG_MAX_LINES = 1e4;
187
+ var DEFAULT_RATE_LIMIT_CONFIG = {
188
+ tiers: {
189
+ free: { algorithm: "tokenBucket", maxRequests: 100, windowMs: 6e4, refillRate: 10 },
190
+ pro: { algorithm: "slidingWindow", maxRequests: 1e3, windowMs: 6e4 },
191
+ unlimited: { algorithm: "none" }
192
+ },
193
+ defaultTier: "free",
194
+ globalLimit: { maxRequests: 1e4, windowMs: 6e4 }
195
+ };
196
+ var DEFAULT_IP_RULES = {
197
+ allowlist: [],
198
+ blocklist: [],
199
+ mode: "blocklist"
200
+ };
201
+ var DEFAULT_API_KEYS = {
202
+ keys: []
203
+ };
204
+
205
+ // src/backend/services/FileLogWriter.ts
206
+ function deriveLevel(statusCode) {
207
+ if (statusCode < 400) return "info";
208
+ if (statusCode < 500) return "warn";
209
+ if (statusCode === 503) return "fatal";
210
+ return "error";
211
+ }
212
+ function formatDate(date) {
213
+ const y = date.getFullYear();
214
+ const m = String(date.getMonth() + 1).padStart(2, "0");
215
+ const d = String(date.getDate()).padStart(2, "0");
216
+ return `${y}-${m}-${d}`;
217
+ }
218
+ function formatTime(date) {
219
+ const h = String(date.getHours()).padStart(2, "0");
220
+ const min = String(date.getMinutes()).padStart(2, "0");
221
+ const s = String(date.getSeconds()).padStart(2, "0");
222
+ return `${h}${min}${s}`;
223
+ }
224
+ var FileLogWriter = class {
225
+ logDir;
226
+ appName;
227
+ maxLines;
228
+ currentDate;
229
+ currentLines = 0;
230
+ fileIndex = 0;
231
+ currentFilePath = null;
232
+ destroyed = false;
233
+ constructor(config) {
234
+ this.logDir = config.logDir;
235
+ this.appName = config.appName;
236
+ this.maxLines = config.maxLinesPerFile ?? DEFAULT_LOG_MAX_LINES;
237
+ this.currentDate = formatDate(/* @__PURE__ */ new Date());
238
+ try {
239
+ (0, import_fs.mkdirSync)(this.logDir, { recursive: true });
240
+ } catch (err) {
241
+ process.stderr.write(`[FileLogWriter] Failed to create log directory: ${err}
242
+ `);
243
+ }
244
+ this.fileIndex = this.scanExistingFiles(this.currentDate);
245
+ this.rotateFile();
246
+ }
247
+ write(log) {
248
+ if (this.destroyed) return;
249
+ const now = new Date(log.timestamp);
250
+ const today = formatDate(now);
251
+ if (today !== this.currentDate) {
252
+ this.currentDate = today;
253
+ this.fileIndex = 1;
254
+ this.currentLines = 0;
255
+ this.rotateFile();
256
+ }
257
+ if (this.currentLines >= this.maxLines) {
258
+ this.fileIndex++;
259
+ this.currentLines = 0;
260
+ this.rotateFile();
261
+ }
262
+ const entry = {
263
+ timestamp: now.toISOString(),
264
+ level: deriveLevel(log.statusCode),
265
+ service: this.appName,
266
+ method: log.method,
267
+ path: log.path,
268
+ statusCode: log.statusCode,
269
+ responseTime: log.responseTime,
270
+ requestId: (0, import_crypto.randomUUID)(),
271
+ clientId: log.clientId,
272
+ ip: log.ip,
273
+ authenticated: log.authenticated
274
+ };
275
+ try {
276
+ (0, import_fs.appendFileSync)(this.currentFilePath, JSON.stringify(entry) + "\n");
277
+ this.currentLines++;
278
+ } catch (err) {
279
+ process.stderr.write(`[FileLogWriter] Write error: ${err}
280
+ `);
281
+ }
282
+ }
283
+ destroy() {
284
+ this.destroyed = true;
285
+ this.currentFilePath = null;
286
+ }
287
+ /** Scan existing log files for a date to determine the next incremental number. */
288
+ scanExistingFiles(date) {
289
+ try {
290
+ const prefix = `${this.appName}_${date}_`;
291
+ const files = (0, import_fs.readdirSync)(this.logDir).filter(
292
+ (f) => f.startsWith(prefix) && f.endsWith(".log")
293
+ );
294
+ if (files.length === 0) return 1;
295
+ let maxIndex = 0;
296
+ for (const f of files) {
297
+ const parts = f.replace(".log", "").split("_");
298
+ const idx = parseInt(parts[parts.length - 1], 10);
299
+ if (!isNaN(idx) && idx > maxIndex) maxIndex = idx;
300
+ }
301
+ return maxIndex + 1;
302
+ } catch {
303
+ return 1;
304
+ }
305
+ }
306
+ rotateFile() {
307
+ const time = formatTime(/* @__PURE__ */ new Date());
308
+ const idx = String(this.fileIndex).padStart(3, "0");
309
+ const filename = `${this.appName}_${this.currentDate}_${time}_${idx}.log`;
310
+ this.currentFilePath = (0, import_path.join)(this.logDir, filename);
311
+ }
312
+ };
313
+
179
314
  // src/backend/services/AnalyticsService.ts
180
315
  var MAX_LOG_SIZE = 1e4;
181
316
  var ACTIVE_WINDOW_MS = 3e5;
@@ -183,6 +318,12 @@ var AnalyticsService = class {
183
318
  logs = [];
184
319
  head = 0;
185
320
  count = 0;
321
+ fileLogWriter = null;
322
+ constructor(logConfig) {
323
+ if (logConfig) {
324
+ this.fileLogWriter = new FileLogWriter(logConfig);
325
+ }
326
+ }
186
327
  addLog(log) {
187
328
  if (this.count < MAX_LOG_SIZE) {
188
329
  this.logs.push(log);
@@ -191,6 +332,11 @@ var AnalyticsService = class {
191
332
  this.logs[this.head] = log;
192
333
  this.head = (this.head + 1) % MAX_LOG_SIZE;
193
334
  }
335
+ this.fileLogWriter?.write(log);
336
+ }
337
+ destroy() {
338
+ this.fileLogWriter?.destroy();
339
+ this.fileLogWriter = null;
194
340
  }
195
341
  getRecentLogs(limit = 20, offset = 0) {
196
342
  const ordered = this.getOrderedLogs();
@@ -243,17 +389,17 @@ var AnalyticsService = class {
243
389
  };
244
390
 
245
391
  // src/backend/services/DeviceRegistryService.ts
246
- var import_fs = __toESM(require("fs"));
247
- var import_path = __toESM(require("path"));
392
+ var import_fs2 = __toESM(require("fs"));
393
+ var import_path2 = __toESM(require("path"));
248
394
 
249
395
  // src/backend/utils/totp.ts
250
- var import_crypto = __toESM(require("crypto"));
396
+ var import_crypto2 = __toESM(require("crypto"));
251
397
  var TIME_WINDOW_MS = 3600 * 1e3;
252
398
  var CODE_LENGTH = 16;
253
399
  function generateTOTP(browserId, secret, timeOffset = 0) {
254
400
  const timeWindow = Math.floor(Date.now() / TIME_WINDOW_MS) + timeOffset;
255
401
  const message = `${browserId}:${timeWindow}`;
256
- const hmac = import_crypto.default.createHmac("sha256", secret).update(message).digest("hex");
402
+ const hmac = import_crypto2.default.createHmac("sha256", secret).update(message).digest("hex");
257
403
  return hmac.substring(0, CODE_LENGTH);
258
404
  }
259
405
  function validateTOTP(browserId, secret, providedCode) {
@@ -265,7 +411,7 @@ function timingSafeEqual(a, b) {
265
411
  if (a.length !== b.length) return false;
266
412
  const bufA = Buffer.from(a);
267
413
  const bufB = Buffer.from(b);
268
- return import_crypto.default.timingSafeEqual(bufA, bufB);
414
+ return import_crypto2.default.timingSafeEqual(bufA, bufB);
269
415
  }
270
416
  function formatKey(browserId, code) {
271
417
  return `totp_${browserId}_${code}`;
@@ -280,7 +426,7 @@ function parseKey(key) {
280
426
  return { browserId, code };
281
427
  }
282
428
  function generateSecret() {
283
- return import_crypto.default.randomBytes(32).toString("hex");
429
+ return import_crypto2.default.randomBytes(32).toString("hex");
284
430
  }
285
431
 
286
432
  // src/backend/services/DeviceRegistryService.ts
@@ -305,17 +451,17 @@ var DeviceRegistryService = class {
305
451
  }
306
452
  loadFromDisk() {
307
453
  try {
308
- if (import_fs.default.existsSync(this.filePath)) {
309
- const raw = import_fs.default.readFileSync(this.filePath, "utf-8");
454
+ if (import_fs2.default.existsSync(this.filePath)) {
455
+ const raw = import_fs2.default.readFileSync(this.filePath, "utf-8");
310
456
  const data = JSON.parse(raw);
311
457
  for (const device of data.devices) {
312
458
  this.devices.set(device.browserId, device);
313
459
  }
314
460
  console.log(`[DeviceRegistry] Loaded ${this.devices.size} devices from ${this.filePath}`);
315
461
  } else {
316
- const dir = import_path.default.dirname(this.filePath);
317
- if (!import_fs.default.existsSync(dir)) {
318
- import_fs.default.mkdirSync(dir, { recursive: true });
462
+ const dir = import_path2.default.dirname(this.filePath);
463
+ if (!import_fs2.default.existsSync(dir)) {
464
+ import_fs2.default.mkdirSync(dir, { recursive: true });
319
465
  }
320
466
  this.saveToDiskSync();
321
467
  console.log(`[DeviceRegistry] Created new devices file at ${this.filePath}`);
@@ -328,7 +474,7 @@ var DeviceRegistryService = class {
328
474
  const data = {
329
475
  devices: Array.from(this.devices.values())
330
476
  };
331
- import_fs.default.writeFileSync(this.filePath, JSON.stringify(data, null, 2), "utf-8");
477
+ import_fs2.default.writeFileSync(this.filePath, JSON.stringify(data, null, 2), "utf-8");
332
478
  }
333
479
  debouncedSave() {
334
480
  if (this.writeTimeout) clearTimeout(this.writeTimeout);
@@ -462,25 +608,6 @@ var DeviceRegistryService = class {
462
608
  }
463
609
  };
464
610
 
465
- // src/config/defaults.ts
466
- var DEFAULT_RATE_LIMIT_CONFIG = {
467
- tiers: {
468
- free: { algorithm: "tokenBucket", maxRequests: 100, windowMs: 6e4, refillRate: 10 },
469
- pro: { algorithm: "slidingWindow", maxRequests: 1e3, windowMs: 6e4 },
470
- unlimited: { algorithm: "none" }
471
- },
472
- defaultTier: "free",
473
- globalLimit: { maxRequests: 1e4, windowMs: 6e4 }
474
- };
475
- var DEFAULT_IP_RULES = {
476
- allowlist: [],
477
- blocklist: [],
478
- mode: "blocklist"
479
- };
480
- var DEFAULT_API_KEYS = {
481
- keys: []
482
- };
483
-
484
611
  // src/backend/middleware/apiKeyAuth.ts
485
612
  function createApiKeyAuth(getKeys, deviceRegistry) {
486
613
  return function apiKeyAuth(req, res, next) {
@@ -599,10 +726,11 @@ function createGatewayMiddleware(userConfig) {
599
726
  rateLimits: userConfig?.rateLimits ?? DEFAULT_RATE_LIMIT_CONFIG,
600
727
  ipRules: userConfig?.ipRules ?? DEFAULT_IP_RULES,
601
728
  apiKeys: userConfig?.apiKeys ?? DEFAULT_API_KEYS,
602
- deviceRegistryPath: userConfig?.deviceRegistryPath ?? ""
729
+ deviceRegistryPath: userConfig?.deviceRegistryPath ?? "",
730
+ logConfig: userConfig?.logConfig
603
731
  };
604
732
  const rateLimiterService = new RateLimiterService(config.rateLimits);
605
- const analyticsService = new AnalyticsService();
733
+ const analyticsService = new AnalyticsService(userConfig?.logConfig);
606
734
  let deviceRegistry;
607
735
  if (config.deviceRegistryPath) {
608
736
  deviceRegistry = new DeviceRegistryService(config.deviceRegistryPath);
@@ -623,7 +751,7 @@ function createGatewayMiddleware(userConfig) {
623
751
 
624
752
  // src/backend/routes/gateway.ts
625
753
  var import_express2 = require("express");
626
- var import_crypto2 = __toESM(require("crypto"));
754
+ var import_crypto3 = __toESM(require("crypto"));
627
755
  function createGatewayRoutes(options) {
628
756
  const { rateLimiterService, analyticsService, config } = options;
629
757
  const router = (0, import_express2.Router)();
@@ -666,7 +794,7 @@ function createGatewayRoutes(options) {
666
794
  }
667
795
  const newKey = {
668
796
  id: `key_${String(config.apiKeys.keys.length + 1).padStart(3, "0")}`,
669
- key: `gw_live_${import_crypto2.default.randomBytes(16).toString("hex")}`,
797
+ key: `gw_live_${import_crypto3.default.randomBytes(16).toString("hex")}`,
670
798
  name,
671
799
  tier: tier || "free",
672
800
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -755,6 +883,7 @@ function createDeviceAuthRoutes(options) {
755
883
  0 && (module.exports = {
756
884
  AnalyticsService,
757
885
  DeviceRegistryService,
886
+ FileLogWriter,
758
887
  RateLimiterService,
759
888
  createApiKeyAuth,
760
889
  createDeviceAuthRoutes,