bxo 0.0.5-dev.77 → 0.0.5-dev.79
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/example/url-encoding-example.ts +93 -0
- package/package.json +1 -1
- package/src/index.ts +24 -3
- package/test-url-encoding.ts +19 -0
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import BXO from "../src";
|
|
2
|
+
|
|
3
|
+
async function main() {
|
|
4
|
+
const app = new BXO({ serve: { port: 3000 } });
|
|
5
|
+
|
|
6
|
+
// Route with space in the path
|
|
7
|
+
app.get("/api/resources/Workspace Item", (ctx) => {
|
|
8
|
+
return ctx.json({
|
|
9
|
+
message: "Found Workspace Item resource!",
|
|
10
|
+
path: ctx.request.url,
|
|
11
|
+
pathname: new URL(ctx.request.url).pathname
|
|
12
|
+
});
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
// Route with URL-encoded space
|
|
16
|
+
app.get("/api/resources/Workspace%20Item", (ctx) => {
|
|
17
|
+
return ctx.json({
|
|
18
|
+
message: "Found URL-encoded Workspace Item resource!",
|
|
19
|
+
path: ctx.request.url,
|
|
20
|
+
pathname: new URL(ctx.request.url).pathname
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
// Route with path parameter (recommended approach)
|
|
25
|
+
app.get("/api/resources/:resourceType", (ctx) => {
|
|
26
|
+
return ctx.json({
|
|
27
|
+
message: `Found resource type: ${ctx.params.resourceType}`,
|
|
28
|
+
path: ctx.request.url,
|
|
29
|
+
pathname: new URL(ctx.request.url).pathname,
|
|
30
|
+
params: ctx.params
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
// Route with multiple path parameters including URL-encoded spaces
|
|
35
|
+
app.get("/api/resources/:resourceType/:id", (ctx) => {
|
|
36
|
+
return ctx.json({
|
|
37
|
+
message: `Found resource: ${ctx.params.resourceType} with ID: ${ctx.params.id}`,
|
|
38
|
+
path: ctx.request.url,
|
|
39
|
+
pathname: new URL(ctx.request.url).pathname,
|
|
40
|
+
params: ctx.params
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// Test route to show the difference
|
|
45
|
+
app.get("/test", (ctx) => {
|
|
46
|
+
return ctx.text(`
|
|
47
|
+
<!DOCTYPE html>
|
|
48
|
+
<html>
|
|
49
|
+
<head>
|
|
50
|
+
<title>URL Encoding Test</title>
|
|
51
|
+
</head>
|
|
52
|
+
<body>
|
|
53
|
+
<h1>URL Encoding Test</h1>
|
|
54
|
+
<p>Test the following URLs:</p>
|
|
55
|
+
<ul>
|
|
56
|
+
<li><a href="/api/resources/Workspace Item">/api/resources/Workspace Item</a> (with space)</li>
|
|
57
|
+
<li><a href="/api/resources/Workspace%20Item">/api/resources/Workspace%20Item</a> (URL encoded)</li>
|
|
58
|
+
<li><a href="/api/resources/My%20Resource">/api/resources/My%20Resource</a> (URL encoded with params)</li>
|
|
59
|
+
<li><a href="/api/resources/Doctype%20Permission/01992af8-1c69-7000-9219-9b83c2feb2d6">/api/resources/Doctype%20Permission/01992af8-1c69-7000-9219-9b83c2feb2d6</a> (URL encoded with ID)</li>
|
|
60
|
+
</ul>
|
|
61
|
+
|
|
62
|
+
<h2>Test with JavaScript fetch:</h2>
|
|
63
|
+
<button onclick="testFetch('/api/resources/Workspace Item')">Test with space</button>
|
|
64
|
+
<button onclick="testFetch('/api/resources/Workspace%20Item')">Test URL encoded</button>
|
|
65
|
+
<button onclick="testFetch('/api/resources/My%20Resource')">Test with params</button>
|
|
66
|
+
<button onclick="testFetch('/api/resources/Doctype%20Permission/01992af8-1c69-7000-9219-9b83c2feb2d6')">Test with ID</button>
|
|
67
|
+
|
|
68
|
+
<div id="result"></div>
|
|
69
|
+
|
|
70
|
+
<script>
|
|
71
|
+
async function testFetch(url) {
|
|
72
|
+
try {
|
|
73
|
+
const response = await fetch(url);
|
|
74
|
+
const data = await response.json();
|
|
75
|
+
document.getElementById('result').innerHTML =
|
|
76
|
+
'<h3>Result:</h3><pre>' + JSON.stringify(data, null, 2) + '</pre>';
|
|
77
|
+
} catch (error) {
|
|
78
|
+
document.getElementById('result').innerHTML =
|
|
79
|
+
'<h3>Error:</h3><pre>' + error.message + '</pre>';
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
</script>
|
|
83
|
+
`, 200, {
|
|
84
|
+
"Content-Type": "text/html"
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
app.start();
|
|
89
|
+
console.log(`Server is running on http://localhost:${app.server?.port}`);
|
|
90
|
+
console.log(`Test URL encoding at http://localhost:${app.server?.port}/test`);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
main().catch(console.error);
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -531,7 +531,7 @@ export default class BXO {
|
|
|
531
531
|
return this;
|
|
532
532
|
}
|
|
533
533
|
|
|
534
|
-
private async dispatch(route: InternalRoute, req: Request): Promise<Response> {
|
|
534
|
+
private async dispatch(route: InternalRoute, req: Request, pathname?: string): Promise<Response> {
|
|
535
535
|
// Run beforeRequest hooks
|
|
536
536
|
for (const hook of this.beforeRequestHooks) {
|
|
537
537
|
try {
|
|
@@ -559,7 +559,8 @@ export default class BXO {
|
|
|
559
559
|
}
|
|
560
560
|
|
|
561
561
|
const url = new URL(req.url);
|
|
562
|
-
const
|
|
562
|
+
const actualPathname = pathname || url.pathname;
|
|
563
|
+
const params = this.extractParams(route, actualPathname);
|
|
563
564
|
let queryObj: any;
|
|
564
565
|
let bodyObj: any = undefined;
|
|
565
566
|
const cookieObj = parseCookies(req.headers.get("cookie"));
|
|
@@ -799,12 +800,32 @@ export default class BXO {
|
|
|
799
800
|
if (h) return await h(req);
|
|
800
801
|
}
|
|
801
802
|
|
|
803
|
+
// 1.5) Try URL-decoded pathname for exact matches
|
|
804
|
+
const decodedPathname = decodeURIComponent(url.pathname);
|
|
805
|
+
if (decodedPathname !== url.pathname) {
|
|
806
|
+
const exactDecoded = nativeRoutes[decodedPathname];
|
|
807
|
+
if (exactDecoded) {
|
|
808
|
+
const h = exactDecoded[method] || exactDecoded["DEFAULT"];
|
|
809
|
+
if (h) return await h(req);
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
|
|
802
813
|
// 2) Fallback to our matcher list
|
|
803
814
|
for (const r of this.routes) {
|
|
804
815
|
if (r.matcher === null) continue; // exact paths handled above
|
|
805
816
|
if (r.method !== method && r.method !== "DEFAULT") continue;
|
|
806
817
|
const m = url.pathname.match(r.matcher);
|
|
807
|
-
if (m) return this.dispatch(r, req);
|
|
818
|
+
if (m) return this.dispatch(r, req, url.pathname);
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
// 2.5) Try URL-decoded pathname for pattern matches
|
|
822
|
+
if (decodedPathname !== url.pathname) {
|
|
823
|
+
for (const r of this.routes) {
|
|
824
|
+
if (r.matcher === null) continue; // exact paths handled above
|
|
825
|
+
if (r.method !== method && r.method !== "DEFAULT") continue;
|
|
826
|
+
const m = decodedPathname.match(r.matcher);
|
|
827
|
+
if (m) return this.dispatch(r, req, decodedPathname);
|
|
828
|
+
}
|
|
808
829
|
}
|
|
809
830
|
|
|
810
831
|
// Create 404 response
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import BXO from "./src";
|
|
2
|
+
|
|
3
|
+
const app = new BXO({ serve: { port: 3001 } });
|
|
4
|
+
|
|
5
|
+
// Test the exact scenario you mentioned
|
|
6
|
+
app.get("/api/resources/:resourceType/:id", (ctx) => {
|
|
7
|
+
return ctx.json({
|
|
8
|
+
message: "Success!",
|
|
9
|
+
resourceType: ctx.params.resourceType,
|
|
10
|
+
id: ctx.params.id,
|
|
11
|
+
allParams: ctx.params,
|
|
12
|
+
url: ctx.request.url,
|
|
13
|
+
pathname: new URL(ctx.request.url).pathname
|
|
14
|
+
});
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
app.start();
|
|
18
|
+
console.log(`Test server running on http://localhost:${app.server?.port}`);
|
|
19
|
+
console.log(`Test URL: http://localhost:${app.server?.port}/api/resources/Doctype%20Permission/01992af8-1c69-7000-9219-9b83c2feb2d6`);
|