@superdangerous/app-framework 4.9.1 → 4.14.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.
- package/README.md +8 -2
- package/dist/api/logsRouter.d.ts +4 -1
- package/dist/api/logsRouter.d.ts.map +1 -1
- package/dist/api/logsRouter.js +100 -118
- package/dist/api/logsRouter.js.map +1 -1
- package/dist/core/index.d.ts +1 -0
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +1 -1
- package/dist/core/index.js.map +1 -1
- package/dist/middleware/validation.d.ts +36 -31
- package/dist/middleware/validation.d.ts.map +1 -1
- package/dist/middleware/validation.js +48 -43
- package/dist/middleware/validation.js.map +1 -1
- package/dist/services/index.d.ts +1 -1
- package/dist/services/index.d.ts.map +1 -1
- package/dist/services/index.js +1 -1
- package/dist/services/index.js.map +1 -1
- package/dist/services/websocketServer.d.ts +7 -4
- package/dist/services/websocketServer.d.ts.map +1 -1
- package/dist/services/websocketServer.js +22 -16
- package/dist/services/websocketServer.js.map +1 -1
- package/dist/types/index.d.ts +7 -8
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +3 -2
- package/src/api/logsRouter.ts +119 -138
- package/src/core/index.ts +2 -1
- package/src/middleware/validation.ts +82 -90
- package/src/services/index.ts +1 -0
- package/src/services/websocketServer.ts +37 -23
- package/src/types/index.ts +7 -8
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { Server } from "socket.io";
|
|
6
6
|
import { createLogger } from "../core/index.js";
|
|
7
|
-
let logger
|
|
7
|
+
let logger = null;
|
|
8
8
|
class WebSocketServer {
|
|
9
9
|
io = null;
|
|
10
10
|
httpServer;
|
|
@@ -24,7 +24,7 @@ class WebSocketServer {
|
|
|
24
24
|
if (this.io) {
|
|
25
25
|
if (opts?.broadcastHook) {
|
|
26
26
|
this.broadcastHook = opts.broadcastHook;
|
|
27
|
-
logger
|
|
27
|
+
logger?.debug("WebSocket broadcast hook updated");
|
|
28
28
|
}
|
|
29
29
|
return;
|
|
30
30
|
}
|
|
@@ -39,7 +39,7 @@ class WebSocketServer {
|
|
|
39
39
|
this.broadcastHook = opts.broadcastHook;
|
|
40
40
|
}
|
|
41
41
|
this.setupEventHandlers();
|
|
42
|
-
logger
|
|
42
|
+
logger?.info("WebSocket server initialized");
|
|
43
43
|
}
|
|
44
44
|
/**
|
|
45
45
|
* Get the underlying Socket.IO server instance
|
|
@@ -53,7 +53,7 @@ class WebSocketServer {
|
|
|
53
53
|
this.io.on("connection", (socket) => {
|
|
54
54
|
// Only log in debug mode to avoid spam
|
|
55
55
|
if (process.env.LOG_LEVEL?.toLowerCase() === "debug") {
|
|
56
|
-
logger
|
|
56
|
+
logger?.debug(`Client connected: ${socket.id}`);
|
|
57
57
|
}
|
|
58
58
|
this.clients.set(socket.id, {
|
|
59
59
|
id: socket.id,
|
|
@@ -76,13 +76,13 @@ class WebSocketServer {
|
|
|
76
76
|
socket.on("disconnect", () => {
|
|
77
77
|
// Only log in debug mode to avoid spam
|
|
78
78
|
if (process.env.LOG_LEVEL?.toLowerCase() === "debug") {
|
|
79
|
-
logger
|
|
79
|
+
logger?.debug(`Client disconnected: ${socket.id}`);
|
|
80
80
|
}
|
|
81
81
|
this.handleDisconnect(socket);
|
|
82
82
|
});
|
|
83
83
|
// Handle errors
|
|
84
|
-
socket.on("error", (
|
|
85
|
-
logger
|
|
84
|
+
socket.on("error", (error) => {
|
|
85
|
+
logger?.error(`Socket error for ${socket.id}:`, error);
|
|
86
86
|
});
|
|
87
87
|
});
|
|
88
88
|
}
|
|
@@ -102,7 +102,7 @@ class WebSocketServer {
|
|
|
102
102
|
}
|
|
103
103
|
// Join socket.io room for efficient broadcasting
|
|
104
104
|
socket.join(`simulator:${simulatorId}`);
|
|
105
|
-
logger
|
|
105
|
+
logger?.debug(`Client ${socket.id} subscribed to simulator ${simulatorId}`);
|
|
106
106
|
}
|
|
107
107
|
unsubscribeFromSimulator(socket, simulatorId) {
|
|
108
108
|
const client = this.clients.get(socket.id);
|
|
@@ -120,7 +120,7 @@ class WebSocketServer {
|
|
|
120
120
|
}
|
|
121
121
|
// Leave socket.io room
|
|
122
122
|
socket.leave(`simulator:${simulatorId}`);
|
|
123
|
-
logger
|
|
123
|
+
logger?.debug(`Client ${socket.id} unsubscribed from simulator ${simulatorId}`);
|
|
124
124
|
}
|
|
125
125
|
handleDisconnect(socket) {
|
|
126
126
|
const client = this.clients.get(socket.id);
|
|
@@ -179,7 +179,9 @@ class WebSocketServer {
|
|
|
179
179
|
type: "data:update",
|
|
180
180
|
data: {
|
|
181
181
|
simulatorId: data.simulatorId,
|
|
182
|
-
values: data.values || {
|
|
182
|
+
values: data.values || {
|
|
183
|
+
[data.address]: data.value,
|
|
184
|
+
},
|
|
183
185
|
},
|
|
184
186
|
};
|
|
185
187
|
break;
|
|
@@ -189,13 +191,17 @@ class WebSocketServer {
|
|
|
189
191
|
data: data,
|
|
190
192
|
};
|
|
191
193
|
}
|
|
192
|
-
// Broadcast to
|
|
193
|
-
|
|
194
|
-
// If simulator-specific, also send to room
|
|
194
|
+
// Broadcast: if simulator-specific, send only to room; otherwise broadcast globally
|
|
195
|
+
// This prevents duplicate events for subscribed clients
|
|
195
196
|
if (data.simulatorId) {
|
|
196
197
|
this.io.to(`simulator:${data.simulatorId}`).emit(event, message.data);
|
|
198
|
+
const subscriberCount = this.simulatorSubscriptions.get(data.simulatorId)?.size || 0;
|
|
199
|
+
logger?.debug(`Broadcast ${event} to ${subscriberCount} subscribers of simulator ${data.simulatorId}`);
|
|
200
|
+
}
|
|
201
|
+
else {
|
|
202
|
+
this.io.emit(event, message.data);
|
|
203
|
+
logger?.debug(`Broadcast ${event} to ${this.clients.size} clients`);
|
|
197
204
|
}
|
|
198
|
-
logger.debug(`Broadcast ${event} to ${this.clients.size} clients`);
|
|
199
205
|
}
|
|
200
206
|
/**
|
|
201
207
|
* Send event to specific simulator subscribers
|
|
@@ -212,7 +218,7 @@ class WebSocketServer {
|
|
|
212
218
|
};
|
|
213
219
|
this.io.to(`simulator:${simulatorId}`).emit(event, message.data);
|
|
214
220
|
const subscriberCount = this.simulatorSubscriptions.get(simulatorId)?.size || 0;
|
|
215
|
-
logger
|
|
221
|
+
logger?.debug(`Broadcast ${event} to ${subscriberCount} subscribers of simulator ${simulatorId}`);
|
|
216
222
|
}
|
|
217
223
|
/**
|
|
218
224
|
* Get statistics about connected clients
|
|
@@ -255,7 +261,7 @@ class WebSocketServer {
|
|
|
255
261
|
// Close the server
|
|
256
262
|
await new Promise((resolve) => {
|
|
257
263
|
this.io.close(() => {
|
|
258
|
-
logger
|
|
264
|
+
logger?.info("WebSocket server shut down");
|
|
259
265
|
resolve();
|
|
260
266
|
});
|
|
261
267
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"websocketServer.js","sourceRoot":"","sources":["../../src/services/websocketServer.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,MAAM,EAAU,MAAM,WAAW,CAAC;AAE3C,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"websocketServer.js","sourceRoot":"","sources":["../../src/services/websocketServer.ts"],"names":[],"mappings":"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"}
|
package/dist/types/index.d.ts
CHANGED
|
@@ -83,7 +83,6 @@ export interface UpdateCheckResult {
|
|
|
83
83
|
export interface ClientInfo {
|
|
84
84
|
id: string;
|
|
85
85
|
connectedAt: Date;
|
|
86
|
-
lastActivity: Date;
|
|
87
86
|
subscriptions: Set<string>;
|
|
88
87
|
}
|
|
89
88
|
/**
|
|
@@ -98,9 +97,9 @@ export interface Logger {
|
|
|
98
97
|
/**
|
|
99
98
|
* WebSocket message types
|
|
100
99
|
*/
|
|
101
|
-
export interface WebSocketMessage {
|
|
100
|
+
export interface WebSocketMessage<T = unknown> {
|
|
102
101
|
type: string;
|
|
103
|
-
data:
|
|
102
|
+
data: T;
|
|
104
103
|
}
|
|
105
104
|
export interface SimulatorUpdateMessage extends WebSocketMessage {
|
|
106
105
|
type: "simulator:started" | "simulator:stopped" | "simulator:data";
|
|
@@ -114,24 +113,24 @@ export interface DataUpdateMessage extends WebSocketMessage {
|
|
|
114
113
|
type: "data:update";
|
|
115
114
|
data: {
|
|
116
115
|
simulatorId: string;
|
|
117
|
-
values: Record<string,
|
|
116
|
+
values: Record<string, unknown>;
|
|
118
117
|
};
|
|
119
118
|
}
|
|
120
119
|
export interface Simulator {
|
|
121
120
|
id: string;
|
|
122
121
|
name: string;
|
|
123
122
|
status: string;
|
|
124
|
-
[key: string]:
|
|
123
|
+
[key: string]: unknown;
|
|
125
124
|
}
|
|
126
125
|
export interface Template {
|
|
127
126
|
id: string;
|
|
128
127
|
name: string;
|
|
129
|
-
[key: string]:
|
|
128
|
+
[key: string]: unknown;
|
|
130
129
|
}
|
|
131
130
|
export interface AppConfig {
|
|
132
|
-
[key: string]:
|
|
131
|
+
[key: string]: unknown;
|
|
133
132
|
}
|
|
134
133
|
export interface ValidationError extends Error {
|
|
135
|
-
details?:
|
|
134
|
+
details?: unknown[];
|
|
136
135
|
}
|
|
137
136
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;;GAIG;AACH,MAAM,WAAW,WAAW,CAAC,CAAC,GAAG,GAAG;IAClC,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,CAAC,EAAE,CAAC,CAAC;IACT,KAAK,CAAC,EACF,MAAM,GACN;QACE,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;QAChB,OAAO,CAAC,EAAE,GAAG,CAAC;QACd,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,CAAC;IACN,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,oBAAoB,EAAE,CAAC;IAChC,QAAQ,CAAC,EAAE;QACT,SAAS,EAAE,MAAM,CAAC;QAClB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,UAAU,CAAC,EAAE;YACX,IAAI,EAAE,MAAM,CAAC;YACb,KAAK,EAAE,MAAM,CAAC;YACd,KAAK,EAAE,MAAM,CAAC;YACd,OAAO,EAAE,OAAO,CAAC;SAClB,CAAC;KACH,CAAC;IACF,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;GAGG;AACH,MAAM,WAAW,iBAAiB,CAAC,CAAC,GAAG,GAAG;IACxC,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,CAAC,EAAE,CAAC,CAAC;IACT,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,GAAG,CAAC;IACpB,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB,QAAQ,EAAE,OAAO,CAAC;IAClB,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,SAAS,EAAE,OAAO,CAAC;IACnB,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,IAAI,CAAC;IAClB,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@superdangerous/app-framework",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.14.0",
|
|
4
4
|
"description": "Opinionated TypeScript framework for structured vibecoding - building internal web and desktop apps with batteries included",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -8,7 +8,6 @@
|
|
|
8
8
|
"bin": {
|
|
9
9
|
"app-framework": "dist/cli/index.js",
|
|
10
10
|
"framework": "dist/cli/index.js",
|
|
11
|
-
"build-sidecar": "dist/cli/build-sidecar.js",
|
|
12
11
|
"dev-server": "dist/cli/dev-server.js"
|
|
13
12
|
},
|
|
14
13
|
"exports": {
|
|
@@ -120,6 +119,7 @@
|
|
|
120
119
|
"@types/express": "^4.17.23",
|
|
121
120
|
"@types/fs-extra": "^11.0.4",
|
|
122
121
|
"@types/node": "^20.10.0",
|
|
122
|
+
"@types/supertest": "^6.0.3",
|
|
123
123
|
"@types/swagger-jsdoc": "^6.0.4",
|
|
124
124
|
"@types/swagger-ui-express": "^4.1.8",
|
|
125
125
|
"@types/ws": "^8.18.1",
|
|
@@ -131,6 +131,7 @@
|
|
|
131
131
|
"npm-run-all": "^4.1.5",
|
|
132
132
|
"prettier": "^3.5.1",
|
|
133
133
|
"socket.io-client": "^4.8.1",
|
|
134
|
+
"supertest": "^7.1.4",
|
|
134
135
|
"ts-node": "^10.9.2",
|
|
135
136
|
"tsx": "^4.20.4",
|
|
136
137
|
"typedoc": "^0.28.12",
|
package/src/api/logsRouter.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Standardized Logging API Router
|
|
3
|
-
* Provides consistent logging endpoints for all
|
|
3
|
+
* Provides consistent logging endpoints for all EpiSensor applications
|
|
4
|
+
*
|
|
5
|
+
* SECURITY WARNING: This router exposes sensitive log files without authentication.
|
|
6
|
+
* Before production use, implement proper authentication middleware.
|
|
4
7
|
*/
|
|
5
8
|
|
|
6
9
|
import express, { Request, Response } from "express";
|
|
@@ -26,13 +29,99 @@ export interface LogFile {
|
|
|
26
29
|
path?: string;
|
|
27
30
|
}
|
|
28
31
|
|
|
32
|
+
/**
|
|
33
|
+
* Validate and sanitize file path to prevent directory traversal
|
|
34
|
+
*/
|
|
35
|
+
function validateFilePath(filename: string, baseDir: string): string | null {
|
|
36
|
+
if (!filename || typeof filename !== "string") {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Normalize the filename and resolve path
|
|
41
|
+
const safeName = path.normalize(filename).replace(/^(\.\.[/\\])+/, "");
|
|
42
|
+
const resolvedPath = path.resolve(baseDir, safeName);
|
|
43
|
+
const resolvedBase = path.resolve(baseDir);
|
|
44
|
+
|
|
45
|
+
// Ensure the resolved path is within the base directory
|
|
46
|
+
if (
|
|
47
|
+
!resolvedPath.startsWith(resolvedBase + path.sep) &&
|
|
48
|
+
resolvedPath !== resolvedBase
|
|
49
|
+
) {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return resolvedPath;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Safely parse integer with fallback
|
|
58
|
+
*/
|
|
59
|
+
function parseIntSafe(value: any, defaultValue: number): number {
|
|
60
|
+
if (typeof value === "number") return Math.floor(value);
|
|
61
|
+
if (typeof value === "string") {
|
|
62
|
+
const parsed = parseInt(value, 10);
|
|
63
|
+
return isNaN(parsed) ? defaultValue : parsed;
|
|
64
|
+
}
|
|
65
|
+
return defaultValue;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Helper function to format bytes
|
|
69
|
+
function formatBytes(bytes: number, decimals = 2): string {
|
|
70
|
+
if (bytes === 0) return "0 Bytes";
|
|
71
|
+
const k = 1024;
|
|
72
|
+
const dm = decimals < 0 ? 0 : decimals;
|
|
73
|
+
const sizes = ["Bytes", "KB", "MB", "GB"];
|
|
74
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
75
|
+
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + " " + sizes[i];
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Helper to get log files from a directory
|
|
80
|
+
*/
|
|
81
|
+
async function getLogFilesFromDir(
|
|
82
|
+
logsDir: string,
|
|
83
|
+
includeFullPath = false,
|
|
84
|
+
): Promise<LogFile[]> {
|
|
85
|
+
if (!existsSync(logsDir)) {
|
|
86
|
+
return [];
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const files = await fs.readdir(logsDir);
|
|
90
|
+
const logFiles: LogFile[] = [];
|
|
91
|
+
|
|
92
|
+
for (const file of files) {
|
|
93
|
+
if (
|
|
94
|
+
file.endsWith(".log") ||
|
|
95
|
+
file.endsWith(".txt") ||
|
|
96
|
+
file.endsWith(".gz")
|
|
97
|
+
) {
|
|
98
|
+
const filePath = path.join(logsDir, file);
|
|
99
|
+
const stats = await fs.stat(filePath);
|
|
100
|
+
|
|
101
|
+
logFiles.push({
|
|
102
|
+
name: file,
|
|
103
|
+
size: stats.size,
|
|
104
|
+
modified: stats.mtime.toISOString(),
|
|
105
|
+
...(includeFullPath && { path: filePath }),
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Sort by modified date, newest first
|
|
111
|
+
logFiles.sort(
|
|
112
|
+
(a, b) => new Date(b.modified).getTime() - new Date(a.modified).getTime(),
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
return logFiles;
|
|
116
|
+
}
|
|
117
|
+
|
|
29
118
|
/**
|
|
30
119
|
* Get recent log entries
|
|
31
120
|
* GET /api/logs/entries?limit=100&level=info
|
|
32
121
|
*/
|
|
33
122
|
router.get("/entries", async (req: Request, res: Response) => {
|
|
34
123
|
try {
|
|
35
|
-
const limit =
|
|
124
|
+
const limit = parseIntSafe(req.query.limit, 100);
|
|
36
125
|
const level = (req.query.level as string) || "all";
|
|
37
126
|
|
|
38
127
|
const logger = getLogger;
|
|
@@ -59,36 +148,7 @@ router.get("/entries", async (req: Request, res: Response) => {
|
|
|
59
148
|
router.get("/files", async (_req: Request, res: Response) => {
|
|
60
149
|
try {
|
|
61
150
|
const logsDir = path.join(process.cwd(), "data", "logs");
|
|
62
|
-
|
|
63
|
-
if (!existsSync(logsDir)) {
|
|
64
|
-
res.json({
|
|
65
|
-
success: true,
|
|
66
|
-
files: [],
|
|
67
|
-
});
|
|
68
|
-
return;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const files = await fs.readdir(logsDir);
|
|
72
|
-
const logFiles: LogFile[] = [];
|
|
73
|
-
|
|
74
|
-
for (const file of files) {
|
|
75
|
-
if (file.endsWith(".log") || file.endsWith(".txt")) {
|
|
76
|
-
const filePath = path.join(logsDir, file);
|
|
77
|
-
const stats = await fs.stat(filePath);
|
|
78
|
-
|
|
79
|
-
logFiles.push({
|
|
80
|
-
name: file,
|
|
81
|
-
size: stats.size,
|
|
82
|
-
modified: stats.mtime.toISOString(),
|
|
83
|
-
path: filePath,
|
|
84
|
-
});
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// Sort by modified date, newest first
|
|
89
|
-
logFiles.sort(
|
|
90
|
-
(a, b) => new Date(b.modified).getTime() - new Date(a.modified).getTime(),
|
|
91
|
-
);
|
|
151
|
+
const logFiles = await getLogFilesFromDir(logsDir, true);
|
|
92
152
|
|
|
93
153
|
res.json({
|
|
94
154
|
success: true,
|
|
@@ -112,10 +172,10 @@ router.get("/download/:filename", async (req: Request, res: Response) => {
|
|
|
112
172
|
try {
|
|
113
173
|
const { filename } = req.params;
|
|
114
174
|
const logsDir = path.join(process.cwd(), "data", "logs");
|
|
115
|
-
const filePath = path.join(logsDir, filename);
|
|
116
175
|
|
|
117
176
|
// Security check - prevent directory traversal
|
|
118
|
-
|
|
177
|
+
const filePath = validateFilePath(filename, logsDir);
|
|
178
|
+
if (!filePath) {
|
|
119
179
|
res.status(403).json({
|
|
120
180
|
success: false,
|
|
121
181
|
error: "Access denied",
|
|
@@ -149,10 +209,10 @@ router.get("/stream/:filename", async (req: Request, res: Response) => {
|
|
|
149
209
|
try {
|
|
150
210
|
const { filename } = req.params;
|
|
151
211
|
const logsDir = path.join(process.cwd(), "data", "logs");
|
|
152
|
-
const filePath = path.join(logsDir, filename);
|
|
153
212
|
|
|
154
|
-
// Security check
|
|
155
|
-
|
|
213
|
+
// Security check - prevent directory traversal
|
|
214
|
+
const filePath = validateFilePath(filename, logsDir);
|
|
215
|
+
if (!filePath) {
|
|
156
216
|
res.status(403).json({
|
|
157
217
|
success: false,
|
|
158
218
|
error: "Access denied",
|
|
@@ -203,47 +263,21 @@ router.post("/clear", async (_req: Request, res: Response) => {
|
|
|
203
263
|
});
|
|
204
264
|
|
|
205
265
|
/**
|
|
206
|
-
* Get archived log files
|
|
266
|
+
* Get archived log files (alias for /files for backward compatibility)
|
|
207
267
|
* GET /api/logs/archives
|
|
208
268
|
*/
|
|
209
269
|
router.get("/archives", async (_req: Request, res: Response) => {
|
|
210
270
|
try {
|
|
211
271
|
const logsDir = path.join(process.cwd(), "data", "logs");
|
|
272
|
+
const archives = await getLogFilesFromDir(logsDir);
|
|
212
273
|
|
|
213
|
-
|
|
214
|
-
return res.json({
|
|
215
|
-
success: true,
|
|
216
|
-
archives: [],
|
|
217
|
-
});
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
const files = await fs.readdir(logsDir);
|
|
221
|
-
const archives: LogFile[] = [];
|
|
222
|
-
|
|
223
|
-
for (const file of files) {
|
|
224
|
-
if (file.endsWith(".log") || file.endsWith(".txt")) {
|
|
225
|
-
const filePath = path.join(logsDir, file);
|
|
226
|
-
const stats = await fs.stat(filePath);
|
|
227
|
-
archives.push({
|
|
228
|
-
name: file,
|
|
229
|
-
size: stats.size,
|
|
230
|
-
modified: stats.mtime.toISOString(),
|
|
231
|
-
});
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
// Sort by modified date, newest first
|
|
236
|
-
archives.sort(
|
|
237
|
-
(a, b) => new Date(b.modified).getTime() - new Date(a.modified).getTime(),
|
|
238
|
-
);
|
|
239
|
-
|
|
240
|
-
return res.json({
|
|
274
|
+
res.json({
|
|
241
275
|
success: true,
|
|
242
276
|
archives,
|
|
243
277
|
});
|
|
244
278
|
} catch (_error) {
|
|
245
279
|
console.error("Failed to fetch archives:", _error);
|
|
246
|
-
|
|
280
|
+
res.status(500).json({
|
|
247
281
|
success: false,
|
|
248
282
|
error: "Failed to fetch archives",
|
|
249
283
|
archives: [],
|
|
@@ -259,10 +293,10 @@ router.delete("/archive/:filename", async (req: Request, res: Response) => {
|
|
|
259
293
|
try {
|
|
260
294
|
const { filename } = req.params;
|
|
261
295
|
const logsDir = path.join(process.cwd(), "data", "logs");
|
|
262
|
-
const filePath = path.join(logsDir, filename);
|
|
263
296
|
|
|
264
|
-
// Security check
|
|
265
|
-
|
|
297
|
+
// Security check - prevent directory traversal
|
|
298
|
+
const filePath = validateFilePath(filename, logsDir);
|
|
299
|
+
if (!filePath) {
|
|
266
300
|
return res.status(403).json({
|
|
267
301
|
success: false,
|
|
268
302
|
error: "Access denied",
|
|
@@ -338,52 +372,14 @@ router.get("/export", async (req: Request, res: Response) => {
|
|
|
338
372
|
*/
|
|
339
373
|
router.get("/stats", async (_req: Request, res: Response) => {
|
|
340
374
|
try {
|
|
341
|
-
const
|
|
342
|
-
|
|
343
|
-
if (!existsSync(logsDir)) {
|
|
344
|
-
res.json({
|
|
345
|
-
success: true,
|
|
346
|
-
stats: {
|
|
347
|
-
totalSize: 0,
|
|
348
|
-
fileCount: 0,
|
|
349
|
-
oldestLog: null,
|
|
350
|
-
newestLog: null,
|
|
351
|
-
},
|
|
352
|
-
});
|
|
353
|
-
return;
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
const files = await fs.readdir(logsDir);
|
|
357
|
-
let totalSize = 0;
|
|
358
|
-
let oldestTime: Date | null = null;
|
|
359
|
-
let newestTime: Date | null = null;
|
|
360
|
-
let fileCount = 0;
|
|
361
|
-
|
|
362
|
-
for (const file of files) {
|
|
363
|
-
if (file.endsWith(".log") || file.endsWith(".txt")) {
|
|
364
|
-
const filePath = path.join(logsDir, file);
|
|
365
|
-
const stats = await fs.stat(filePath);
|
|
366
|
-
|
|
367
|
-
totalSize += stats.size;
|
|
368
|
-
fileCount++;
|
|
369
|
-
|
|
370
|
-
if (!oldestTime || stats.birthtime < oldestTime) {
|
|
371
|
-
oldestTime = stats.birthtime;
|
|
372
|
-
}
|
|
373
|
-
if (!newestTime || stats.mtime > newestTime) {
|
|
374
|
-
newestTime = stats.mtime;
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
}
|
|
375
|
+
const logger = getLogger;
|
|
376
|
+
const stats = await logger.getLogStats();
|
|
378
377
|
|
|
379
378
|
res.json({
|
|
380
379
|
success: true,
|
|
381
380
|
stats: {
|
|
382
|
-
|
|
383
|
-
totalSizeFormatted: formatBytes(totalSize),
|
|
384
|
-
fileCount,
|
|
385
|
-
oldestLog: oldestTime?.toISOString() || null,
|
|
386
|
-
newestLog: newestTime?.toISOString() || null,
|
|
381
|
+
...stats,
|
|
382
|
+
totalSizeFormatted: formatBytes(stats.totalSize || 0),
|
|
387
383
|
},
|
|
388
384
|
});
|
|
389
385
|
} catch (_error) {
|
|
@@ -506,26 +502,21 @@ router.get(
|
|
|
506
502
|
async (req: Request, res: Response) => {
|
|
507
503
|
try {
|
|
508
504
|
const { filename } = req.params;
|
|
509
|
-
|
|
510
|
-
// Security: prevent directory traversal
|
|
511
|
-
const safeName = path.basename(filename);
|
|
512
|
-
|
|
513
|
-
// Check in logs directory first
|
|
514
505
|
const logsDir = path.join(process.cwd(), "data", "logs");
|
|
515
|
-
let filePath = path.join(logsDir, safeName);
|
|
516
506
|
|
|
517
|
-
//
|
|
518
|
-
|
|
519
|
-
filePath = path.join(logsDir, "archive", safeName);
|
|
520
|
-
}
|
|
507
|
+
// Security: prevent directory traversal using proper validation
|
|
508
|
+
let filePath = validateFilePath(filename, logsDir);
|
|
521
509
|
|
|
522
|
-
// If
|
|
523
|
-
if (!
|
|
524
|
-
const
|
|
525
|
-
|
|
510
|
+
// If not found in main logs, try archive directory
|
|
511
|
+
if (!filePath || !existsSync(filePath)) {
|
|
512
|
+
const archiveDir = path.join(logsDir, "archive");
|
|
513
|
+
const archiveFilename = filename.startsWith("archive/")
|
|
514
|
+
? filename.substring("archive/".length)
|
|
515
|
+
: filename;
|
|
516
|
+
filePath = validateFilePath(archiveFilename, archiveDir);
|
|
526
517
|
}
|
|
527
518
|
|
|
528
|
-
if (!existsSync(filePath)) {
|
|
519
|
+
if (!filePath || !existsSync(filePath)) {
|
|
529
520
|
return res.status(404).json({
|
|
530
521
|
success: false,
|
|
531
522
|
error: "File not found",
|
|
@@ -540,7 +531,7 @@ router.get(
|
|
|
540
531
|
res.setHeader("Content-Type", "text/plain");
|
|
541
532
|
}
|
|
542
533
|
|
|
543
|
-
return res.download(filePath,
|
|
534
|
+
return res.download(filePath, path.basename(filePath));
|
|
544
535
|
} catch (_error) {
|
|
545
536
|
return res.status(500).json({
|
|
546
537
|
success: false,
|
|
@@ -587,14 +578,4 @@ router.post("/rotate", async (_req: Request, res: Response) => {
|
|
|
587
578
|
}
|
|
588
579
|
});
|
|
589
580
|
|
|
590
|
-
// Helper function to format bytes
|
|
591
|
-
function formatBytes(bytes: number, decimals = 2): string {
|
|
592
|
-
if (bytes === 0) return "0 Bytes";
|
|
593
|
-
const k = 1024;
|
|
594
|
-
const dm = decimals < 0 ? 0 : decimals;
|
|
595
|
-
const sizes = ["Bytes", "KB", "MB", "GB"];
|
|
596
|
-
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
597
|
-
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + " " + sizes[i];
|
|
598
|
-
}
|
|
599
|
-
|
|
600
581
|
export default router;
|
package/src/core/index.ts
CHANGED
|
@@ -56,13 +56,14 @@ export interface Logger {
|
|
|
56
56
|
|
|
57
57
|
export type LogLevel = "debug" | "info" | "warn" | "error";
|
|
58
58
|
|
|
59
|
-
// Security
|
|
59
|
+
// Security & Storage
|
|
60
60
|
export {
|
|
61
61
|
getStorageService,
|
|
62
62
|
StorageService,
|
|
63
63
|
getSecureFileHandler,
|
|
64
64
|
SecureFileHandler,
|
|
65
65
|
} from "./storageService.js";
|
|
66
|
+
export type { FileInfo, SaveOptions, ReadOptions } from "./storageService.js";
|
|
66
67
|
|
|
67
68
|
/**
|
|
68
69
|
* Usage Example:
|