@wxn0brp/falcon-frame 0.8.5 → 0.8.7
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/dist/body-utils.js +4 -3
- package/dist/body.js +2 -2
- package/dist/compress.js +72 -24
- package/dist/cors.js +2 -3
- package/dist/debug.d.ts +1 -0
- package/dist/debug.html +87 -9
- package/dist/debug.js +12 -4
- package/dist/helpers.js +2 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.js +13 -5
- package/dist/middleware.js +5 -2
- package/dist/render.js +9 -3
- package/dist/req.js +4 -4
- package/dist/res.js +1 -1
- package/dist/router.js +6 -2
- package/dist/sse.js +4 -1
- package/dist/static.js +8 -3
- package/dist/types.d.ts +2 -2
- package/dist/valid.js +8 -6
- package/package.json +2 -1
package/dist/body-utils.js
CHANGED
|
@@ -28,7 +28,7 @@ export function getContentType(req) {
|
|
|
28
28
|
export function getRawBody(req, res, limit) {
|
|
29
29
|
return new Promise((resolve, reject) => {
|
|
30
30
|
let body = "";
|
|
31
|
-
req.on("data",
|
|
31
|
+
req.on("data", chunk => {
|
|
32
32
|
body += chunk.toString();
|
|
33
33
|
if (limit && body.length > limit) {
|
|
34
34
|
const error = new Error("Payload Too Large");
|
|
@@ -41,7 +41,7 @@ export function getRawBody(req, res, limit) {
|
|
|
41
41
|
req.on("end", () => {
|
|
42
42
|
resolve(body);
|
|
43
43
|
});
|
|
44
|
-
req.on("error",
|
|
44
|
+
req.on("error", err => {
|
|
45
45
|
reject(err);
|
|
46
46
|
});
|
|
47
47
|
});
|
|
@@ -49,7 +49,8 @@ export function getRawBody(req, res, limit) {
|
|
|
49
49
|
export function getStandardBodyParser(type, parser, opts) {
|
|
50
50
|
const limit = parseLimit(opts.limit || "100k");
|
|
51
51
|
return async (req, res, next) => {
|
|
52
|
-
if ((typeof req.body
|
|
52
|
+
if ((typeof req.body === "object" && Object.keys(req.body).length) ||
|
|
53
|
+
getContentType(req) !== type) {
|
|
53
54
|
return next();
|
|
54
55
|
}
|
|
55
56
|
try {
|
package/dist/body.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import querystring from "querystring";
|
|
2
2
|
import { getStandardBodyParser } from "./body-utils.js";
|
|
3
3
|
export function json(opts = {}) {
|
|
4
|
-
return getStandardBodyParser("application/json",
|
|
4
|
+
return getStandardBodyParser("application/json", body => JSON.parse(body), opts);
|
|
5
5
|
}
|
|
6
6
|
export function urlencoded(opts = {}) {
|
|
7
|
-
return getStandardBodyParser("application/x-www-form-urlencoded",
|
|
7
|
+
return getStandardBodyParser("application/x-www-form-urlencoded", body => querystring.parse(body), opts);
|
|
8
8
|
}
|
package/dist/compress.js
CHANGED
|
@@ -1,4 +1,18 @@
|
|
|
1
|
-
import { createDeflate, createGzip } from "zlib";
|
|
1
|
+
import { createBrotliCompress, createDeflate, createGzip } from "zlib";
|
|
2
|
+
function isCompressible(contentType) {
|
|
3
|
+
if (!contentType)
|
|
4
|
+
return true;
|
|
5
|
+
const type = contentType.split(";")[0].trim().toLowerCase();
|
|
6
|
+
if (/^image\/(?!svg\+xml)/.test(type))
|
|
7
|
+
return false;
|
|
8
|
+
if (/^(video|audio)\//.test(type))
|
|
9
|
+
return false;
|
|
10
|
+
if (type === "text/event-stream")
|
|
11
|
+
return false;
|
|
12
|
+
if (/^application\/(zip|gzip|x-gzip|x-bzip|x-bzip2|x-xz|x-rar|x-7z|x-tar|x-compress|wasm)$/.test(type))
|
|
13
|
+
return false;
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
2
16
|
export function compression(req, res) {
|
|
3
17
|
if (req._compression)
|
|
4
18
|
return;
|
|
@@ -7,47 +21,69 @@ export function compression(req, res) {
|
|
|
7
21
|
if (!encoding || typeof encoding !== "string" || res.headersSent) {
|
|
8
22
|
return;
|
|
9
23
|
}
|
|
10
|
-
let
|
|
24
|
+
let createCompressor;
|
|
11
25
|
let encodingType;
|
|
12
|
-
if (/\
|
|
26
|
+
if (/\bbr\b/.test(encoding)) {
|
|
27
|
+
encodingType = "br";
|
|
28
|
+
createCompressor = createBrotliCompress;
|
|
29
|
+
}
|
|
30
|
+
else if (/\bgzip\b/.test(encoding)) {
|
|
13
31
|
encodingType = "gzip";
|
|
14
|
-
|
|
32
|
+
createCompressor = createGzip;
|
|
15
33
|
}
|
|
16
34
|
else if (/\bdeflate\b/.test(encoding)) {
|
|
17
35
|
encodingType = "deflate";
|
|
18
|
-
|
|
36
|
+
createCompressor = createDeflate;
|
|
19
37
|
}
|
|
20
38
|
else {
|
|
21
39
|
return;
|
|
22
40
|
}
|
|
23
|
-
res.setHeader("Content-Encoding", encodingType);
|
|
24
|
-
res.removeHeader("Content-Length");
|
|
25
41
|
const originalWrite = res.write;
|
|
26
42
|
const originalEnd = res.end;
|
|
27
|
-
compressionStream
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
if (!
|
|
35
|
-
|
|
43
|
+
let compressionStream = null;
|
|
44
|
+
let initialized = false;
|
|
45
|
+
function init() {
|
|
46
|
+
if (initialized)
|
|
47
|
+
return compressionStream !== null;
|
|
48
|
+
initialized = true;
|
|
49
|
+
const contentType = res.getHeader("content-type");
|
|
50
|
+
if (!isCompressible(contentType))
|
|
51
|
+
return false;
|
|
52
|
+
compressionStream = createCompressor();
|
|
53
|
+
res.setHeader("Content-Encoding", encodingType);
|
|
54
|
+
res.removeHeader("Content-Length");
|
|
55
|
+
compressionStream.on("data", chunk => {
|
|
56
|
+
originalWrite.call(res, chunk);
|
|
57
|
+
});
|
|
58
|
+
compressionStream.on("end", () => {
|
|
36
59
|
originalEnd.call(res);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
res.
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
60
|
+
});
|
|
61
|
+
compressionStream.on("error", err => {
|
|
62
|
+
if (!res.headersSent) {
|
|
63
|
+
res.statusCode = 500;
|
|
64
|
+
originalEnd.call(res);
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
res.destroy(err);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
res.on("close", () => {
|
|
71
|
+
compressionStream.destroy();
|
|
72
|
+
});
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
45
75
|
// @ts-ignore
|
|
46
76
|
res.write = (...args) => {
|
|
47
77
|
const [chunk, encoding, cb] = args;
|
|
48
78
|
const callback = typeof encoding === "function" ? encoding : cb;
|
|
49
79
|
const enc = typeof encoding === "function" ? undefined : encoding;
|
|
50
80
|
if (!res.headersSent) {
|
|
81
|
+
if (!init()) {
|
|
82
|
+
res.write = originalWrite;
|
|
83
|
+
res.end = originalEnd;
|
|
84
|
+
res.writeHead(res.statusCode);
|
|
85
|
+
return originalWrite.call(res, chunk, enc, callback);
|
|
86
|
+
}
|
|
51
87
|
res.writeHead(res.statusCode);
|
|
52
88
|
}
|
|
53
89
|
return compressionStream.write(chunk, enc, callback);
|
|
@@ -68,6 +104,18 @@ export function compression(req, res) {
|
|
|
68
104
|
finalCb = encoding;
|
|
69
105
|
finalEncoding = undefined;
|
|
70
106
|
}
|
|
107
|
+
if (!res.headersSent) {
|
|
108
|
+
if (!init()) {
|
|
109
|
+
res.write = originalWrite;
|
|
110
|
+
res.end = originalEnd;
|
|
111
|
+
if (finalCb)
|
|
112
|
+
res.once("finish", finalCb);
|
|
113
|
+
if (finalChunk)
|
|
114
|
+
originalWrite.call(res, finalChunk, finalEncoding);
|
|
115
|
+
originalEnd.call(res);
|
|
116
|
+
return res;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
71
119
|
if (finalCb)
|
|
72
120
|
res.once("finish", finalCb);
|
|
73
121
|
if (finalChunk) {
|
package/dist/cors.js
CHANGED
|
@@ -9,8 +9,7 @@ function parseOrigin(origin) {
|
|
|
9
9
|
const url = new URL(origin);
|
|
10
10
|
return {
|
|
11
11
|
host: url.hostname,
|
|
12
|
-
port: url.port ||
|
|
13
|
-
(url.protocol === "https:" ? "443" : "80")
|
|
12
|
+
port: url.port || (url.protocol === "https:" ? "443" : "80"),
|
|
14
13
|
};
|
|
15
14
|
}
|
|
16
15
|
catch {
|
|
@@ -68,7 +67,7 @@ export function createCORS(allowedOrigins, opts = {}) {
|
|
|
68
67
|
const origin = req.headers.origin;
|
|
69
68
|
if (allowedOrigins.includes("*"))
|
|
70
69
|
return handleEnd(req, res, next, origin || "*");
|
|
71
|
-
if (origin && allowedOrigins.some(
|
|
70
|
+
if (origin && allowedOrigins.some(pattern => matches(origin, pattern)))
|
|
72
71
|
return handleEnd(req, res, next, origin);
|
|
73
72
|
next();
|
|
74
73
|
};
|
package/dist/debug.d.ts
CHANGED
package/dist/debug.html
CHANGED
|
@@ -236,6 +236,11 @@
|
|
|
236
236
|
text-overflow: ellipsis;
|
|
237
237
|
}
|
|
238
238
|
|
|
239
|
+
.url-copy {
|
|
240
|
+
margin-left: 12px;
|
|
241
|
+
vertical-align: middle;
|
|
242
|
+
}
|
|
243
|
+
|
|
239
244
|
.detail-row td {
|
|
240
245
|
padding: 0;
|
|
241
246
|
background: rgba(22, 27, 34, 0.6);
|
|
@@ -253,11 +258,40 @@
|
|
|
253
258
|
min-width: 240px;
|
|
254
259
|
}
|
|
255
260
|
|
|
261
|
+
.detail-section-header {
|
|
262
|
+
display: flex;
|
|
263
|
+
justify-content: space-between;
|
|
264
|
+
align-items: center;
|
|
265
|
+
margin-bottom: 4px;
|
|
266
|
+
}
|
|
267
|
+
|
|
256
268
|
.detail-section h4 {
|
|
257
269
|
font-size: 11px;
|
|
258
270
|
text-transform: uppercase;
|
|
259
271
|
color: var(--text-muted);
|
|
260
|
-
margin
|
|
272
|
+
margin: 0;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
.copy-btn {
|
|
276
|
+
font-family: var(--font);
|
|
277
|
+
font-size: 10px;
|
|
278
|
+
padding: 2px 8px;
|
|
279
|
+
border-radius: var(--radius);
|
|
280
|
+
border: 1px solid var(--border);
|
|
281
|
+
background: transparent;
|
|
282
|
+
color: var(--text-muted);
|
|
283
|
+
cursor: pointer;
|
|
284
|
+
text-transform: uppercase;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
.copy-btn:hover {
|
|
288
|
+
border-color: var(--accent);
|
|
289
|
+
color: var(--accent);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
.copy-btn.copied {
|
|
293
|
+
border-color: var(--success);
|
|
294
|
+
color: var(--success);
|
|
261
295
|
}
|
|
262
296
|
|
|
263
297
|
.detail-section pre {
|
|
@@ -333,9 +367,9 @@
|
|
|
333
367
|
const methodFilters = document.querySelector("#methodFilters");
|
|
334
368
|
const emptyRow = logBody.querySelector(".empty");
|
|
335
369
|
let entries = [];
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
370
|
+
const seq = 0;
|
|
371
|
+
const expanded = new Set();
|
|
372
|
+
const activeMethods = new Set(METHODS);
|
|
339
373
|
|
|
340
374
|
function statusClass(code) {
|
|
341
375
|
if (code < 300) return "s2xx";
|
|
@@ -358,6 +392,21 @@
|
|
|
358
392
|
}
|
|
359
393
|
}
|
|
360
394
|
|
|
395
|
+
function formatBody(e) {
|
|
396
|
+
if (e.method === "POST" && e.url.startsWith("/logs") && Array.isArray(e.body)) {
|
|
397
|
+
return e.body.join("\n");
|
|
398
|
+
}
|
|
399
|
+
return formatJSON(e.body);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
function isEmpty(obj) {
|
|
403
|
+
if (!obj || typeof obj !== "object") return true;
|
|
404
|
+
const keys = Object.keys(obj);
|
|
405
|
+
if (keys.length === 0) return true;
|
|
406
|
+
if (keys.length === 1 && keys[0] === "" && obj[""] === "") return true;
|
|
407
|
+
return false;
|
|
408
|
+
}
|
|
409
|
+
|
|
361
410
|
function esc(str) {
|
|
362
411
|
const el = document.createElement("span");
|
|
363
412
|
el.textContent = str;
|
|
@@ -414,7 +463,7 @@
|
|
|
414
463
|
let html = `<tr data-row="${rowId}">`;
|
|
415
464
|
html += `<td>${idx}</td>`;
|
|
416
465
|
html += `<td><span class="method ${e.method.toLowerCase()}">${esc(e.method)}</span></td>`;
|
|
417
|
-
html += `<td><span class="url-cell" title="${escUrl}">${escUrl}</span></td>`;
|
|
466
|
+
html += `<td><span class="url-cell" title="${escUrl}">${escUrl}</span><button class="copy-btn url-copy" data-copy-url="${esc(e.url.replace(/"/g, '"'))}">Copy</button></td>`;
|
|
418
467
|
html += `<td><span class="status ${statusClass(e.status)}">${e.status}</span></td>`;
|
|
419
468
|
html += `<td><span class="duration ${durationClass(e.duration)}">${dur}</span></td>`;
|
|
420
469
|
html += `<td class="time">${new Date(e.time).toLocaleTimeString()}</td>`;
|
|
@@ -423,10 +472,10 @@
|
|
|
423
472
|
if (isExpanded) {
|
|
424
473
|
html += `<tr class="detail-row"><td colspan="6">`;
|
|
425
474
|
html += `<div class="detail-inner">`;
|
|
426
|
-
html += `<div class="detail-section"><h4>Headers</h4><pre>${esc(formatJSON(e.headers))}</pre></div>`;
|
|
427
|
-
html += `<div class="detail-section"><h4>Query</h4><pre>${esc(formatJSON(e.query))}</pre></div>`;
|
|
428
|
-
html += `<div class="detail-section"><h4>Cookies</h4><pre>${esc(formatJSON(e.cookies))}</pre></div>`;
|
|
429
|
-
html += `<div class="detail-section"><h4>Body</h4><pre>${esc(
|
|
475
|
+
if (!isEmpty(e.headers)) html += `<div class="detail-section"><div class="detail-section-header"><h4>Headers</h4><button class="copy-btn" data-copy>Copy</button></div><pre>${esc(formatJSON(e.headers))}</pre></div>`;
|
|
476
|
+
if (!isEmpty(e.query)) html += `<div class="detail-section"><div class="detail-section-header"><h4>Query</h4><button class="copy-btn" data-copy>Copy</button></div><pre>${esc(formatJSON(e.query))}</pre></div>`;
|
|
477
|
+
if (!isEmpty(e.cookies)) html += `<div class="detail-section"><div class="detail-section-header"><h4>Cookies</h4><button class="copy-btn" data-copy>Copy</button></div><pre>${esc(formatJSON(e.cookies))}</pre></div>`;
|
|
478
|
+
if (!isEmpty(e.body)) html += `<div class="detail-section"><div class="detail-section-header"><h4>Body</h4><button class="copy-btn" data-copy>Copy</button></div><pre>${esc(formatBody(e))}</pre></div>`;
|
|
430
479
|
html += `</div></td></tr>`;
|
|
431
480
|
}
|
|
432
481
|
|
|
@@ -435,6 +484,35 @@
|
|
|
435
484
|
}
|
|
436
485
|
|
|
437
486
|
logBody.addEventListener("click", (e) => {
|
|
487
|
+
const copyBtn = e.target.closest("[data-copy]");
|
|
488
|
+
if (copyBtn) {
|
|
489
|
+
const pre = copyBtn.closest(".detail-section").querySelector("pre");
|
|
490
|
+
navigator.clipboard.writeText(pre.textContent).then(() => {
|
|
491
|
+
copyBtn.textContent = "Copied!";
|
|
492
|
+
copyBtn.classList.add("copied");
|
|
493
|
+
setTimeout(() => {
|
|
494
|
+
copyBtn.textContent = "Copy";
|
|
495
|
+
copyBtn.classList.remove("copied");
|
|
496
|
+
}, 1500);
|
|
497
|
+
});
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
const urlCopyBtn = e.target.closest("[data-copy-url]");
|
|
502
|
+
if (urlCopyBtn) {
|
|
503
|
+
navigator.clipboard.writeText(urlCopyBtn.dataset.copyUrl).then(() => {
|
|
504
|
+
urlCopyBtn.textContent = "Copied!";
|
|
505
|
+
urlCopyBtn.classList.add("copied");
|
|
506
|
+
setTimeout(() => {
|
|
507
|
+
urlCopyBtn.textContent = "Copy";
|
|
508
|
+
urlCopyBtn.classList.remove("copied");
|
|
509
|
+
}, 1500);
|
|
510
|
+
});
|
|
511
|
+
return;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
if (e.target.closest(".detail-row")) return;
|
|
515
|
+
|
|
438
516
|
const row = e.target.closest("tr[data-row]");
|
|
439
517
|
if (!row) return;
|
|
440
518
|
const rid = row.dataset.row;
|
package/dist/debug.js
CHANGED
|
@@ -1,13 +1,19 @@
|
|
|
1
1
|
import { join } from "path";
|
|
2
2
|
import { Router } from "./router.js";
|
|
3
3
|
export function createDebug(opts = {}) {
|
|
4
|
-
const { url = "/debug", hosts = [
|
|
4
|
+
const { url = "/debug", hosts = [
|
|
5
|
+
"localhost",
|
|
6
|
+
"127.0.0.1",
|
|
7
|
+
"::1",
|
|
8
|
+
], logs = true, } = opts;
|
|
5
9
|
if (process.env.NODE_ENV === "production" &&
|
|
6
10
|
!process.env.FF_DEBUG_PRODUCTION_ENABLED)
|
|
7
|
-
return (req, res, next) => {
|
|
11
|
+
return (req, res, next) => {
|
|
12
|
+
next();
|
|
13
|
+
};
|
|
8
14
|
const pass = process.env.FF_DEBUG_PASS;
|
|
9
15
|
function isHostAllowed(req) {
|
|
10
|
-
return hosts.some(
|
|
16
|
+
return hosts.some(h => h === req.socket.remoteAddress);
|
|
11
17
|
}
|
|
12
18
|
function checkAuth(req) {
|
|
13
19
|
if (!pass)
|
|
@@ -31,7 +37,7 @@ export function createDebug(opts = {}) {
|
|
|
31
37
|
router.use("/", (req, res, next) => {
|
|
32
38
|
const start = Date.now();
|
|
33
39
|
const hook = res.end.bind(res);
|
|
34
|
-
res.end =
|
|
40
|
+
res.end = (...args) => {
|
|
35
41
|
const entry = {
|
|
36
42
|
method: req.method,
|
|
37
43
|
url: req.url,
|
|
@@ -48,5 +54,7 @@ export function createDebug(opts = {}) {
|
|
|
48
54
|
};
|
|
49
55
|
next();
|
|
50
56
|
});
|
|
57
|
+
if (logs)
|
|
58
|
+
router.post("/logs", () => "ok");
|
|
51
59
|
return router;
|
|
52
60
|
}
|
package/dist/helpers.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import path from "path";
|
|
2
2
|
export function parseCookies(cookieHeader) {
|
|
3
3
|
const cookies = {};
|
|
4
|
-
cookieHeader.split(";").forEach(
|
|
4
|
+
cookieHeader.split(";").forEach(cookie => {
|
|
5
5
|
const [name, ...valueParts] = cookie.split("=");
|
|
6
6
|
const value = decodeURIComponent(valueParts.join("=").trim());
|
|
7
7
|
cookies[name.trim()] = value;
|
|
@@ -32,4 +32,4 @@ export function getContentType(filePath, utf8 = false) {
|
|
|
32
32
|
contentType += "; charset=utf-8";
|
|
33
33
|
return contentType;
|
|
34
34
|
}
|
|
35
|
-
export const ensureLeadingDot = (str) =>
|
|
35
|
+
export const ensureLeadingDot = (str) => str.startsWith(".") ? str : "." + str;
|
package/dist/index.d.ts
CHANGED
|
@@ -29,7 +29,7 @@ export declare class FalconFrame<Vars extends Record<string, any> = {}> extends
|
|
|
29
29
|
* @param [origin] - An array of allowed origins. (default: ["*"])
|
|
30
30
|
* @example
|
|
31
31
|
* app.setOrigin(["http://example.com", "https://example.com"]);
|
|
32
|
-
|
|
32
|
+
*/
|
|
33
33
|
setOrigin(origin?: string[] | string): void;
|
|
34
34
|
/**
|
|
35
35
|
* Listens to the specified port, or the environment variable PORT if available.
|
package/dist/index.js
CHANGED
|
@@ -12,7 +12,7 @@ export class FalconFrame extends Router {
|
|
|
12
12
|
vars = {};
|
|
13
13
|
opts = {};
|
|
14
14
|
engines = {};
|
|
15
|
-
_400_formatter =
|
|
15
|
+
_400_formatter = err => {
|
|
16
16
|
return {
|
|
17
17
|
err: true,
|
|
18
18
|
msg: "Bad request",
|
|
@@ -40,9 +40,13 @@ export class FalconFrame extends Router {
|
|
|
40
40
|
...opts,
|
|
41
41
|
};
|
|
42
42
|
if (!this.opts.disableJsonParser)
|
|
43
|
-
this.addBodyParser(json({
|
|
43
|
+
this.addBodyParser(json({
|
|
44
|
+
limit: this.opts.bodyLimit,
|
|
45
|
+
}));
|
|
44
46
|
if (!this.opts.disableUrlencodedParser)
|
|
45
|
-
this.addBodyParser(urlencoded({
|
|
47
|
+
this.addBodyParser(urlencoded({
|
|
48
|
+
limit: this.opts.bodyLimit,
|
|
49
|
+
}));
|
|
46
50
|
this.engine(".html", (path, data, callback, FF) => {
|
|
47
51
|
try {
|
|
48
52
|
const content = renderHTML({
|
|
@@ -122,9 +126,13 @@ export class FalconFrame extends Router {
|
|
|
122
126
|
* @param [origin] - An array of allowed origins. (default: ["*"])
|
|
123
127
|
* @example
|
|
124
128
|
* app.setOrigin(["http://example.com", "https://example.com"]);
|
|
125
|
-
|
|
129
|
+
*/
|
|
126
130
|
setOrigin(origin = "*") {
|
|
127
|
-
this.use(createCORS(Array.isArray(origin)
|
|
131
|
+
this.use(createCORS(Array.isArray(origin)
|
|
132
|
+
? origin
|
|
133
|
+
: [
|
|
134
|
+
origin,
|
|
135
|
+
]));
|
|
128
136
|
}
|
|
129
137
|
/**
|
|
130
138
|
* Listens to the specified port, or the environment variable PORT if available.
|
package/dist/middleware.js
CHANGED
|
@@ -26,7 +26,7 @@ export function matchMiddleware(url, middlewares) {
|
|
|
26
26
|
let matches = true;
|
|
27
27
|
for (let i = 0; i < middlewareParts.length; i++) {
|
|
28
28
|
if (middlewareParts[i].startsWith(":")) {
|
|
29
|
-
continue;
|
|
29
|
+
// continue;
|
|
30
30
|
}
|
|
31
31
|
else if (middlewareParts[i] !== urlParts[i]) {
|
|
32
32
|
matches = false;
|
|
@@ -62,7 +62,10 @@ export function getMiddlewares(middlewares, matchUrl, basePath = "") {
|
|
|
62
62
|
result.push(...nested);
|
|
63
63
|
}
|
|
64
64
|
else {
|
|
65
|
-
result.push({
|
|
65
|
+
result.push({
|
|
66
|
+
...middleware,
|
|
67
|
+
path: fullPath,
|
|
68
|
+
});
|
|
66
69
|
}
|
|
67
70
|
}
|
|
68
71
|
return result;
|
package/dist/render.js
CHANGED
|
@@ -35,7 +35,10 @@ export function renderHTML(options) {
|
|
|
35
35
|
const partialPath = path.join(path.dirname(templatePath), partialName + ".html");
|
|
36
36
|
return renderHTML({
|
|
37
37
|
templatePath: partialPath,
|
|
38
|
-
_renderedPaths: [
|
|
38
|
+
_renderedPaths: [
|
|
39
|
+
...renderedPaths,
|
|
40
|
+
realPath,
|
|
41
|
+
],
|
|
39
42
|
noLayout: true,
|
|
40
43
|
data,
|
|
41
44
|
FF,
|
|
@@ -67,9 +70,12 @@ export function renderHTML(options) {
|
|
|
67
70
|
templatePath: FFLayout,
|
|
68
71
|
data: {
|
|
69
72
|
...data,
|
|
70
|
-
body: template
|
|
73
|
+
body: template,
|
|
71
74
|
},
|
|
72
|
-
_renderedPaths: [
|
|
75
|
+
_renderedPaths: [
|
|
76
|
+
...renderedPaths,
|
|
77
|
+
realPath,
|
|
78
|
+
],
|
|
73
79
|
noLayout: true,
|
|
74
80
|
FF,
|
|
75
81
|
FFVar,
|
package/dist/req.js
CHANGED
|
@@ -10,7 +10,7 @@ export function handleRequest(req, res, FF, reRoute = false) {
|
|
|
10
10
|
req.FF = FF;
|
|
11
11
|
res.FF = FF;
|
|
12
12
|
const originalEnd = res.end;
|
|
13
|
-
res.end =
|
|
13
|
+
res.end = (...any) => {
|
|
14
14
|
res._ended = true;
|
|
15
15
|
return originalEnd.call(res, ...any);
|
|
16
16
|
};
|
|
@@ -31,7 +31,7 @@ export function handleRequest(req, res, FF, reRoute = false) {
|
|
|
31
31
|
req.cookies = parseCookies(req.headers.cookie || "");
|
|
32
32
|
req.params = {};
|
|
33
33
|
req.valid = (schema) => validate(schema, req.body);
|
|
34
|
-
req.reRoute =
|
|
34
|
+
req.reRoute = fn => {
|
|
35
35
|
fn(req);
|
|
36
36
|
handleRequest(req, res, FF, true);
|
|
37
37
|
};
|
|
@@ -49,11 +49,11 @@ export function handleRequest(req, res, FF, reRoute = false) {
|
|
|
49
49
|
(req.id ? ` [${req.id}]` : ""));
|
|
50
50
|
const middlewaresPath = req.path + "/";
|
|
51
51
|
const middlewares = getMiddlewares(FF.middlewares, middlewaresPath.replace(/\/+/g, "/"));
|
|
52
|
-
const matchedTypeMiddlewares = middlewares.filter(
|
|
52
|
+
const matchedTypeMiddlewares = middlewares.filter(middleware => middleware.method === req.method.toLowerCase() ||
|
|
53
53
|
middleware.method === "all");
|
|
54
54
|
const matchedMiddlewares = matchMiddleware(req.path, matchedTypeMiddlewares);
|
|
55
55
|
logger.debug("Matched middlewares: " +
|
|
56
|
-
matchedMiddlewares.map(
|
|
56
|
+
matchedMiddlewares.map(middleware => middleware.path).join(", "));
|
|
57
57
|
if (matchedMiddlewares.length === 0) {
|
|
58
58
|
res.status(404);
|
|
59
59
|
FF._404(req, res);
|
package/dist/res.js
CHANGED
|
@@ -108,7 +108,7 @@ export class FFResponse extends http.ServerResponse {
|
|
|
108
108
|
render(view, data = {}, opts = {}) {
|
|
109
109
|
const ff = this.FF;
|
|
110
110
|
const viewsDir = opts.baseDir ??
|
|
111
|
-
(opts.notUseViews ? "." :
|
|
111
|
+
(opts.notUseViews ? "." : ff.getVar("views") || ".");
|
|
112
112
|
let engineName = path.extname(view);
|
|
113
113
|
let filePath = view;
|
|
114
114
|
if (opts.engine) {
|
package/dist/router.js
CHANGED
|
@@ -4,8 +4,12 @@ export class Router {
|
|
|
4
4
|
middlewares = [];
|
|
5
5
|
addRoute(method, path, ...handlers) {
|
|
6
6
|
const handler = handlers.pop();
|
|
7
|
-
handlers.forEach(
|
|
8
|
-
return this.middlewares.push({
|
|
7
|
+
handlers.forEach(middleware => this.use(path, middleware));
|
|
8
|
+
return this.middlewares.push({
|
|
9
|
+
path,
|
|
10
|
+
middleware: handler,
|
|
11
|
+
method,
|
|
12
|
+
});
|
|
9
13
|
}
|
|
10
14
|
use(path = "/", middlewareFn, method = "all") {
|
|
11
15
|
if (typeof path !== "string") {
|
package/dist/sse.js
CHANGED
package/dist/static.js
CHANGED
|
@@ -16,11 +16,16 @@ export function handleStaticFiles(dirPath, opts) {
|
|
|
16
16
|
const serveFile = (req, res, filePath, stats) => {
|
|
17
17
|
if (opts.render) {
|
|
18
18
|
const defaultRenderer = res.FF.getVar("view engine");
|
|
19
|
-
const renderStatement = (defaultRenderer &&
|
|
19
|
+
const renderStatement = (defaultRenderer &&
|
|
20
|
+
filePath.endsWith(ensureLeadingDot(defaultRenderer))) ||
|
|
20
21
|
(!opts.notRenderHtml && filePath.endsWith(".html"));
|
|
21
22
|
if (renderStatement) {
|
|
22
|
-
const relativePath = filePath
|
|
23
|
-
|
|
23
|
+
const relativePath = filePath
|
|
24
|
+
.replace(dirPath + "/", "")
|
|
25
|
+
.replace(".html", "");
|
|
26
|
+
res.render(filePath, opts.renderData?.[relativePath] || {}, {
|
|
27
|
+
notUseViews: true,
|
|
28
|
+
});
|
|
24
29
|
return true;
|
|
25
30
|
}
|
|
26
31
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -88,7 +88,7 @@ export interface FFOpts {
|
|
|
88
88
|
export type FFVars = {
|
|
89
89
|
"render data": Record<string, any>;
|
|
90
90
|
"view engine": string;
|
|
91
|
-
|
|
92
|
-
|
|
91
|
+
views: string;
|
|
92
|
+
layout: string;
|
|
93
93
|
};
|
|
94
94
|
export type CombinedVars<ExtraVars> = ExtraVars & FFVars;
|
package/dist/valid.js
CHANGED
|
@@ -24,8 +24,7 @@ export function validate(schema, data) {
|
|
|
24
24
|
}
|
|
25
25
|
break;
|
|
26
26
|
case "min":
|
|
27
|
-
if (typeof value === "string" &&
|
|
28
|
-
value.length < parseInt(param)) {
|
|
27
|
+
if (typeof value === "string" && value.length < parseInt(param)) {
|
|
29
28
|
fieldErrors.push(`${key} must be at least ${param} characters long`);
|
|
30
29
|
}
|
|
31
30
|
if (typeof value === "number" && value < parseInt(param)) {
|
|
@@ -33,20 +32,20 @@ export function validate(schema, data) {
|
|
|
33
32
|
}
|
|
34
33
|
break;
|
|
35
34
|
case "max":
|
|
36
|
-
if (typeof value === "string" &&
|
|
37
|
-
value.length > parseInt(param)) {
|
|
35
|
+
if (typeof value === "string" && value.length > parseInt(param)) {
|
|
38
36
|
fieldErrors.push(`${key} must not exceed ${param} characters`);
|
|
39
37
|
}
|
|
40
38
|
if (typeof value === "number" && value > parseInt(param)) {
|
|
41
39
|
fieldErrors.push(`${key} must not exceed ${param}`);
|
|
42
40
|
}
|
|
43
41
|
break;
|
|
44
|
-
case "email":
|
|
42
|
+
case "email": {
|
|
45
43
|
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
46
44
|
if (!emailRegex.test(value)) {
|
|
47
45
|
fieldErrors.push(`${key} must be a valid email`);
|
|
48
46
|
}
|
|
49
47
|
break;
|
|
48
|
+
}
|
|
50
49
|
}
|
|
51
50
|
}
|
|
52
51
|
if (fieldErrors.length > 0) {
|
|
@@ -54,7 +53,10 @@ export function validate(schema, data) {
|
|
|
54
53
|
errors[key] = fieldErrors;
|
|
55
54
|
}
|
|
56
55
|
}
|
|
57
|
-
return {
|
|
56
|
+
return {
|
|
57
|
+
valid: isValid,
|
|
58
|
+
validErrors: errors,
|
|
59
|
+
};
|
|
58
60
|
}
|
|
59
61
|
export function validateBody(schema) {
|
|
60
62
|
return (req, res, next) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wxn0brp/falcon-frame",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.7",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"types": "dist/index.d.ts",
|
|
6
6
|
"author": "wxn0brP",
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
],
|
|
25
25
|
"devDependencies": {
|
|
26
26
|
"@types/node": "*",
|
|
27
|
+
"@wxn0brp/biome": "*",
|
|
27
28
|
"tsc-alias": "*",
|
|
28
29
|
"typescript": "^7"
|
|
29
30
|
},
|