free-framework 4.6.1 → 4.6.2
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/package.json +1 -1
- package/runtime/server.js +69 -61
package/package.json
CHANGED
package/runtime/server.js
CHANGED
|
@@ -16,25 +16,36 @@ const { LRUCache } = require('lru-cache');
|
|
|
16
16
|
class FreeServer {
|
|
17
17
|
constructor() {
|
|
18
18
|
this.app = new HyperExpress.Server();
|
|
19
|
+
this.viewsPath = process.env.VIEWS_PATH || nodePath.join(process.cwd(), 'views');
|
|
19
20
|
this.namedMiddlewares = {};
|
|
20
21
|
this.errorViews = {};
|
|
21
22
|
this.plugins = [];
|
|
22
|
-
|
|
23
|
+
|
|
24
|
+
// Global Request Logger & Error Handler Initialization
|
|
25
|
+
this.app.set_error_handler((req, res, error) => {
|
|
26
|
+
console.error(`[Free Engine] 🔥 Global Uncaught Error:`, error);
|
|
27
|
+
this.handleError(req, res, error);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
this.app.use((req, res, next) => {
|
|
31
|
+
console.log(`[Free Engine] 📥 Request: ${req.method} ${req.url}`);
|
|
32
|
+
if (next) next();
|
|
33
|
+
});
|
|
23
34
|
|
|
24
35
|
// Static Asset Serving Middleware
|
|
25
36
|
const publicPath = nodePath.join(process.cwd(), 'public');
|
|
26
37
|
this.app.use((req, res, next) => {
|
|
27
38
|
const lookupPath = req.path.startsWith('/') ? req.path.substring(1) : req.path;
|
|
28
|
-
if (!lookupPath) return next();
|
|
39
|
+
if (!lookupPath) return next ? next() : null;
|
|
29
40
|
|
|
30
41
|
const fullPath = nodePath.join(publicPath, lookupPath);
|
|
31
42
|
if (fs.existsSync(fullPath) && fs.statSync(fullPath).isFile()) {
|
|
32
|
-
const content = fs.readFileSync(fullPath);
|
|
33
43
|
const ext = nodePath.extname(fullPath);
|
|
34
|
-
const mimes = { '.js': 'application/javascript', '.css': 'text/css', '.png': 'image/png' };
|
|
35
|
-
|
|
44
|
+
const mimes = { '.js': 'application/javascript', '.css': 'text/css', '.png': 'image/png', '.jpg': 'image/jpeg', '.svg': 'image/svg+xml' };
|
|
45
|
+
res.type(mimes[ext] || 'text/plain');
|
|
46
|
+
return res.send(fs.readFileSync(fullPath));
|
|
36
47
|
}
|
|
37
|
-
next();
|
|
48
|
+
if (next) next();
|
|
38
49
|
});
|
|
39
50
|
|
|
40
51
|
this.app.get('/test-static', (req, res) => res.send('Static serving test'));
|
|
@@ -56,17 +67,17 @@ class FreeServer {
|
|
|
56
67
|
this.app.use(securityMiddleware);
|
|
57
68
|
|
|
58
69
|
this.app.use((req, res, next) => {
|
|
59
|
-
const ip = req.ip || req.headers['x-forwarded-for'] || '
|
|
70
|
+
const ip = req.ip || req.headers['x-forwarded-for'] || '127.0.0.1';
|
|
60
71
|
const reqCount = this.rateLimits.get(ip) || 0;
|
|
61
72
|
|
|
62
73
|
if (reqCount >= 5000) {
|
|
63
74
|
res.status(429);
|
|
64
75
|
res.setHeader('Retry-After', '60');
|
|
65
|
-
return res.send('Too Many Requests
|
|
76
|
+
return res.send('Too Many Requests');
|
|
66
77
|
}
|
|
67
78
|
|
|
68
79
|
this.rateLimits.set(ip, reqCount + 1);
|
|
69
|
-
next();
|
|
80
|
+
if (next) next();
|
|
70
81
|
});
|
|
71
82
|
}
|
|
72
83
|
|
|
@@ -98,58 +109,7 @@ class FreeServer {
|
|
|
98
109
|
|
|
99
110
|
await handler(req, res);
|
|
100
111
|
} catch (e) {
|
|
101
|
-
|
|
102
|
-
console.error(`[Free Engine] Error ${method} ${path}:`, e);
|
|
103
|
-
res.status(e.status || 500).header('Content-Type', 'text/html').send('Internal Error');
|
|
104
|
-
} else {
|
|
105
|
-
// Premium Debug Page (Ignition Style)
|
|
106
|
-
const os = require('os');
|
|
107
|
-
const pkg = require('../package.json');
|
|
108
|
-
const debugTplPath = nodePath.join(__dirname, 'views/debug.html');
|
|
109
|
-
|
|
110
|
-
if (fs.existsSync(debugTplPath)) {
|
|
111
|
-
let html = fs.readFileSync(debugTplPath, 'utf8');
|
|
112
|
-
|
|
113
|
-
// Error Details
|
|
114
|
-
html = html.replace('{{errorName}}', e.name || 'Error');
|
|
115
|
-
html = html.replace('{{errorMessage}}', e.message || 'No message provided');
|
|
116
|
-
|
|
117
|
-
// Stack Trace with highlighting
|
|
118
|
-
const stack = (e.stack || '').split('\n').map(line => {
|
|
119
|
-
const isUserFile = line.includes(process.cwd()) && !line.includes('node_modules');
|
|
120
|
-
return `<span class="stack-line ${isUserFile ? 'highlight' : ''}">${line.replace(/&/g, '&').replace(/</g, '<')}</span>`;
|
|
121
|
-
}).join('');
|
|
122
|
-
html = html.replace('{{stackTrace}}', stack);
|
|
123
|
-
|
|
124
|
-
// Request Details
|
|
125
|
-
let reqHtml = `<tr><th>Method</th><td class="val-text">${req.method}</td></tr>`;
|
|
126
|
-
reqHtml += `<tr><th>URL</th><td class="val-text">${req.url}</td></tr>`;
|
|
127
|
-
Object.keys(req.headers).forEach(k => {
|
|
128
|
-
reqHtml += `<tr><th>Header: ${k}</th><td class="val-text">${req.headers[k]}</td></tr>`;
|
|
129
|
-
});
|
|
130
|
-
html = html.replace('{{requestDetails}}', reqHtml);
|
|
131
|
-
|
|
132
|
-
// Env details (Mask secrets)
|
|
133
|
-
let envHtml = '';
|
|
134
|
-
Object.keys(process.env).forEach(k => {
|
|
135
|
-
const isSecret = k.includes('KEY') || k.includes('SECRET') || k.includes('PASS');
|
|
136
|
-
const val = isSecret ? '********' : process.env[k];
|
|
137
|
-
envHtml += `<tr><th>${k}</th><td class="val-text">${val}</td></tr>`;
|
|
138
|
-
});
|
|
139
|
-
html = html.replace('{{envDetails}}', envHtml);
|
|
140
|
-
|
|
141
|
-
// Engine Stats
|
|
142
|
-
html = html.replace('{{version}}', pkg.version);
|
|
143
|
-
html = html.replace('{{nodeVersion}}', process.version);
|
|
144
|
-
html = html.replace('{{osInfo}}', `${os.type()} ${os.release()} (${os.arch()})`);
|
|
145
|
-
const mem = process.memoryUsage();
|
|
146
|
-
html = html.replace('{{memoryUsage}}', `${Math.round(mem.heapUsed / 1024 / 1024)}MB / ${Math.round(mem.heapTotal / 1024 / 1024)}MB`);
|
|
147
|
-
|
|
148
|
-
res.status(500).header('Content-Type', 'text/html').send(html);
|
|
149
|
-
} else {
|
|
150
|
-
res.status(500).send(`<h1>${e.name || 'Error'}</h1><pre>${e.stack}</pre>`);
|
|
151
|
-
}
|
|
152
|
-
}
|
|
112
|
+
this.handleError(req, res, e);
|
|
153
113
|
}
|
|
154
114
|
};
|
|
155
115
|
|
|
@@ -157,6 +117,54 @@ class FreeServer {
|
|
|
157
117
|
if (routeMethod === 'get') this.app.head(path, ...mwFns, routeHandler);
|
|
158
118
|
}
|
|
159
119
|
|
|
120
|
+
handleError(req, res, e) {
|
|
121
|
+
if (process.env.NODE_ENV === 'production') {
|
|
122
|
+
res.status(500).header('Content-Type', 'text/html').send('Internal Error');
|
|
123
|
+
} else {
|
|
124
|
+
// Premium Debug Page (Ignition Style) logic moved here
|
|
125
|
+
const os = require('os');
|
|
126
|
+
const pkg = require('../package.json');
|
|
127
|
+
const debugTplPath = nodePath.join(__dirname, 'views/debug.html');
|
|
128
|
+
|
|
129
|
+
if (fs.existsSync(debugTplPath)) {
|
|
130
|
+
let html = fs.readFileSync(debugTplPath, 'utf8');
|
|
131
|
+
html = html.replace('{{errorName}}', e.name || 'Error');
|
|
132
|
+
html = html.replace('{{errorMessage}}', e.message || 'No message provided');
|
|
133
|
+
|
|
134
|
+
const stack = (e.stack || '').split('\n').map(line => {
|
|
135
|
+
const isUserFile = line.includes(process.cwd()) && !line.includes('node_modules');
|
|
136
|
+
return `<span class="stack-line ${isUserFile ? 'highlight' : ''}">${line.replace(/&/g, '&').replace(/</g, '<')}</span>`;
|
|
137
|
+
}).join('');
|
|
138
|
+
html = html.replace('{{stackTrace}}', stack);
|
|
139
|
+
|
|
140
|
+
let reqHtml = `<tr><th>Method</th><td class="val-text">${req.method}</td></tr>`;
|
|
141
|
+
reqHtml += `<tr><th>URL</th><td class="val-text">${req.url}</td></tr>`;
|
|
142
|
+
Object.keys(req.headers).forEach(k => {
|
|
143
|
+
reqHtml += `<tr><th>Header: ${k}</th><td class="val-text">${req.headers[k]}</td></tr>`;
|
|
144
|
+
});
|
|
145
|
+
html = html.replace('{{requestDetails}}', reqHtml);
|
|
146
|
+
|
|
147
|
+
let envHtml = '';
|
|
148
|
+
Object.keys(process.env).forEach(k => {
|
|
149
|
+
const isSecret = k.includes('KEY') || k.includes('SECRET') || k.includes('PASS');
|
|
150
|
+
const val = isSecret ? '********' : process.env[k];
|
|
151
|
+
envHtml += `<tr><th>${k}</th><td class="val-text">${val}</td></tr>`;
|
|
152
|
+
});
|
|
153
|
+
html = html.replace('{{envDetails}}', envHtml);
|
|
154
|
+
|
|
155
|
+
html = html.replace('{{version}}', pkg.version);
|
|
156
|
+
html = html.replace('{{nodeVersion}}', process.version);
|
|
157
|
+
html = html.replace('{{osInfo}}', `${os.type()} ${os.release()} (${os.arch()})`);
|
|
158
|
+
const mem = process.memoryUsage();
|
|
159
|
+
html = html.replace('{{memoryUsage}}', `${Math.round(mem.heapUsed / 1024 / 1024)}MB / ${Math.round(mem.heapTotal / 1024 / 1024)}MB`);
|
|
160
|
+
|
|
161
|
+
res.status(500).header('Content-Type', 'text/html').send(html);
|
|
162
|
+
} else {
|
|
163
|
+
res.status(500).send(`<h1>${e.name || 'Error'}</h1><pre>${e.stack}</pre>`);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
160
168
|
middleware(fn) { this.app.use(fn); }
|
|
161
169
|
registerMiddleware(name, fn) { this.namedMiddlewares[name] = fn; }
|
|
162
170
|
use(plugin) { plugin.install(this); this.plugins.push(plugin); }
|