@sourceregistry/node-webserver 1.0.0 → 1.1.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 +31 -29
- package/dist/index.cjs.js +1 -1
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.es.js +172 -175
- package/dist/index.es.js.map +1 -1
- package/dist/types/router.d.ts +1 -1
- package/dist/types/server.d.ts +5 -6
- package/examples/simple.ts +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -31,9 +31,9 @@ import { WebServer, json, text } from "@sourceregistry/node-webserver";
|
|
|
31
31
|
|
|
32
32
|
const app = new WebServer();
|
|
33
33
|
|
|
34
|
-
app.
|
|
34
|
+
app.GET("/", () => text("hello world"));
|
|
35
35
|
|
|
36
|
-
app.
|
|
36
|
+
app.GET("/health", () => json({
|
|
37
37
|
ok: true
|
|
38
38
|
}));
|
|
39
39
|
|
|
@@ -52,6 +52,8 @@ import { WebServer } from "@sourceregistry/node-webserver";
|
|
|
52
52
|
const app = new WebServer();
|
|
53
53
|
```
|
|
54
54
|
|
|
55
|
+
`WebServer` extends `Router`, so you can register routes and middleware directly on `app`.
|
|
56
|
+
|
|
55
57
|
You can also pass handler callbacks for `locals` and `platform`:
|
|
56
58
|
|
|
57
59
|
```ts
|
|
@@ -69,15 +71,15 @@ const app = new WebServer({
|
|
|
69
71
|
### Register routes
|
|
70
72
|
|
|
71
73
|
```ts
|
|
72
|
-
app.
|
|
74
|
+
app.GET("/users", async () => {
|
|
73
75
|
return new Response("all users");
|
|
74
76
|
});
|
|
75
77
|
|
|
76
|
-
app.
|
|
78
|
+
app.GET("/users/[id]", async (event) => {
|
|
77
79
|
return new Response(`user ${event.params.id}`);
|
|
78
80
|
});
|
|
79
81
|
|
|
80
|
-
app.
|
|
82
|
+
app.POST("/users", async (event) => {
|
|
81
83
|
const body = await event.request.json();
|
|
82
84
|
return json({ created: true, body }, { status: 201 });
|
|
83
85
|
});
|
|
@@ -103,7 +105,7 @@ const api = new Router();
|
|
|
103
105
|
|
|
104
106
|
api.GET("/status", () => new Response("ok"));
|
|
105
107
|
|
|
106
|
-
app.
|
|
108
|
+
app.use("/api", api);
|
|
107
109
|
```
|
|
108
110
|
|
|
109
111
|
### Response helpers
|
|
@@ -113,9 +115,9 @@ The library exports helpers for common content types:
|
|
|
113
115
|
```ts
|
|
114
116
|
import { html, json, text } from "@sourceregistry/node-webserver";
|
|
115
117
|
|
|
116
|
-
app.
|
|
117
|
-
app.
|
|
118
|
-
app.
|
|
118
|
+
app.GET("/", () => html("<h1>Hello</h1>"));
|
|
119
|
+
app.GET("/message", () => text("plain text"));
|
|
120
|
+
app.GET("/data", () => json({ ok: true }));
|
|
119
121
|
```
|
|
120
122
|
|
|
121
123
|
## Request Handling
|
|
@@ -123,7 +125,7 @@ app.router.GET("/data", () => json({ ok: true }));
|
|
|
123
125
|
Route handlers receive a web-standard `Request` plus extra routing data:
|
|
124
126
|
|
|
125
127
|
```ts
|
|
126
|
-
app.
|
|
128
|
+
app.GET("/posts/[slug]", async (event) => {
|
|
127
129
|
const userAgent = event.request.headers.get("user-agent");
|
|
128
130
|
const slug = event.params.slug;
|
|
129
131
|
const ip = event.getClientAddress();
|
|
@@ -179,7 +181,7 @@ The server will use those `App.Locals` and `App.Platform` definitions automatica
|
|
|
179
181
|
Middleware wraps request handling and can short-circuit the chain.
|
|
180
182
|
|
|
181
183
|
```ts
|
|
182
|
-
app.
|
|
184
|
+
app.useMiddleware(async (event, next) => {
|
|
183
185
|
const startedAt = Date.now();
|
|
184
186
|
const response = await next();
|
|
185
187
|
|
|
@@ -204,7 +206,7 @@ const requireApiKey = async (event, next) => {
|
|
|
204
206
|
return next();
|
|
205
207
|
};
|
|
206
208
|
|
|
207
|
-
app.
|
|
209
|
+
app.GET("/admin", () => new Response("secret"), requireApiKey);
|
|
208
210
|
```
|
|
209
211
|
|
|
210
212
|
## Router Lifecycle Hooks
|
|
@@ -216,7 +218,7 @@ Use `pre()` for logic that should run before route resolution, and `post()` for
|
|
|
216
218
|
`pre()` can short-circuit the request by returning a `Response`.
|
|
217
219
|
|
|
218
220
|
```ts
|
|
219
|
-
app.
|
|
221
|
+
app.pre(async (event) => {
|
|
220
222
|
if (!event.request.headers.get("authorization")) {
|
|
221
223
|
return new Response("Unauthorized", { status: 401 });
|
|
222
224
|
}
|
|
@@ -228,7 +230,7 @@ app.router.pre(async (event) => {
|
|
|
228
230
|
`post()` receives the final response and may replace it.
|
|
229
231
|
|
|
230
232
|
```ts
|
|
231
|
-
app.
|
|
233
|
+
app.post(async (_event, response) => {
|
|
232
234
|
const nextResponse = new Response(response.body, response);
|
|
233
235
|
nextResponse.headers.set("x-powered-by", "node-webserver");
|
|
234
236
|
return nextResponse;
|
|
@@ -238,7 +240,7 @@ app.router.post(async (_event, response) => {
|
|
|
238
240
|
## Cookies
|
|
239
241
|
|
|
240
242
|
```ts
|
|
241
|
-
app.
|
|
243
|
+
app.GET("/login", async (event) => {
|
|
242
244
|
event.cookies.set("session", "abc123", {
|
|
243
245
|
path: "/",
|
|
244
246
|
httpOnly: true,
|
|
@@ -249,12 +251,12 @@ app.router.GET("/login", async (event) => {
|
|
|
249
251
|
return new Response("logged in");
|
|
250
252
|
});
|
|
251
253
|
|
|
252
|
-
app.
|
|
254
|
+
app.GET("/me", async (event) => {
|
|
253
255
|
const session = event.cookies.get("session");
|
|
254
256
|
return json({ session });
|
|
255
257
|
});
|
|
256
258
|
|
|
257
|
-
app.
|
|
259
|
+
app.POST("/logout", async (event) => {
|
|
258
260
|
event.cookies.delete("session", {
|
|
259
261
|
path: "/",
|
|
260
262
|
httpOnly: true,
|
|
@@ -268,7 +270,7 @@ app.router.POST("/logout", async (event) => {
|
|
|
268
270
|
## WebSocket Routes
|
|
269
271
|
|
|
270
272
|
```ts
|
|
271
|
-
app.
|
|
273
|
+
app.WS("/ws/chat/[room]", async (event) => {
|
|
272
274
|
const room = event.params.room;
|
|
273
275
|
const ws = event.websocket;
|
|
274
276
|
|
|
@@ -287,8 +289,8 @@ Use `dir()` to expose a directory through a route, or `serveStatic()` directly i
|
|
|
287
289
|
```ts
|
|
288
290
|
import { dir } from "@sourceregistry/node-webserver";
|
|
289
291
|
|
|
290
|
-
app.
|
|
291
|
-
app.
|
|
292
|
+
app.GET("/assets/[...path]", dir("./public/assets"));
|
|
293
|
+
app.GET("/", dir("./public"));
|
|
292
294
|
```
|
|
293
295
|
|
|
294
296
|
Manual usage:
|
|
@@ -296,7 +298,7 @@ Manual usage:
|
|
|
296
298
|
```ts
|
|
297
299
|
import { serveStatic } from "@sourceregistry/node-webserver";
|
|
298
300
|
|
|
299
|
-
app.
|
|
301
|
+
app.GET("/downloads/[...path]", (event) => {
|
|
300
302
|
return serveStatic("./downloads", event, {
|
|
301
303
|
cacheControl: "public, max-age=3600"
|
|
302
304
|
});
|
|
@@ -341,7 +343,7 @@ Available options:
|
|
|
341
343
|
```ts
|
|
342
344
|
import { CORS } from "@sourceregistry/node-webserver";
|
|
343
345
|
|
|
344
|
-
app.
|
|
346
|
+
app.useMiddleware(CORS.policy({
|
|
345
347
|
origin: ["https://app.example.com"],
|
|
346
348
|
credentials: true,
|
|
347
349
|
methods: ["GET", "POST", "DELETE"]
|
|
@@ -353,7 +355,7 @@ app.router.useMiddleware(CORS.policy({
|
|
|
353
355
|
```ts
|
|
354
356
|
import { RateLimiter } from "@sourceregistry/node-webserver";
|
|
355
357
|
|
|
356
|
-
app.
|
|
358
|
+
app.useMiddleware(RateLimiter.fixedWindowLimit({
|
|
357
359
|
windowMs: 60_000,
|
|
358
360
|
max: 100
|
|
359
361
|
}));
|
|
@@ -373,7 +375,7 @@ const app = new WebServer({
|
|
|
373
375
|
}
|
|
374
376
|
});
|
|
375
377
|
|
|
376
|
-
app.
|
|
378
|
+
app.GET("/", () => new Response("secure"));
|
|
377
379
|
app.listen(3443);
|
|
378
380
|
```
|
|
379
381
|
|
|
@@ -400,7 +402,7 @@ const app = new WebServer({
|
|
|
400
402
|
}
|
|
401
403
|
});
|
|
402
404
|
|
|
403
|
-
app.
|
|
405
|
+
app.pre(async (event) => {
|
|
404
406
|
if (event.url.pathname.startsWith("/private")) {
|
|
405
407
|
const auth = event.request.headers.get("authorization");
|
|
406
408
|
if (!auth) {
|
|
@@ -409,7 +411,7 @@ app.router.pre(async (event) => {
|
|
|
409
411
|
}
|
|
410
412
|
});
|
|
411
413
|
|
|
412
|
-
app.
|
|
414
|
+
app.useMiddleware(
|
|
413
415
|
CORS.policy({
|
|
414
416
|
origin: "https://app.example.com",
|
|
415
417
|
credentials: true
|
|
@@ -420,16 +422,16 @@ app.router.useMiddleware(
|
|
|
420
422
|
})
|
|
421
423
|
);
|
|
422
424
|
|
|
423
|
-
app.
|
|
425
|
+
app.GET("/", () => text("hello"));
|
|
424
426
|
|
|
425
|
-
app.
|
|
427
|
+
app.GET("/users/[id]", (event) => {
|
|
426
428
|
return json({
|
|
427
429
|
id: event.params.id,
|
|
428
430
|
requestId: event.locals.startedAt
|
|
429
431
|
});
|
|
430
432
|
});
|
|
431
433
|
|
|
432
|
-
app.
|
|
434
|
+
app.post(async (_event, response) => {
|
|
433
435
|
const nextResponse = new Response(response.body, response);
|
|
434
436
|
nextResponse.headers.set("x-server", "node-webserver");
|
|
435
437
|
return nextResponse;
|
package/dist/index.cjs.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const D=require("node:fs"),A=require("node:fs/promises"),S=require("node:path"),q=require("node:stream"),U=require("http"),F=require("https"),z=require("tls"),T=require("stream"),b=require("ws"),C=require("cookie"),B={".avif":"image/avif",".css":"text/css; charset=utf-8",".gif":"image/gif",".html":"text/html; charset=utf-8",".ico":"image/x-icon",".jpg":"image/jpeg",".jpeg":"image/jpeg",".js":"text/javascript; charset=utf-8",".json":"application/json; charset=utf-8",".mjs":"text/javascript; charset=utf-8",".pdf":"application/pdf",".png":"image/png",".svg":"image/svg+xml; charset=utf-8",".txt":"text/plain; charset=utf-8",".wasm":"application/wasm",".webp":"image/webp",".xml":"application/xml; charset=utf-8"},$={index:"index.html",cacheControl:"public, max-age=0",dotFiles:"ignore"};async function N(a,e,t={}){const s=K(e),r={...$,...t},n=await X(a),o=G(s,r.dotFiles);if(o instanceof Response)return o;const i=o.length>0?o.join(S.sep):"",u=S.resolve(n,i);if(!j(n,u))return new Response("Forbidden",{status:403});const c=await J(u,n,r.index);if(c instanceof Response)return c;const l=await A.stat(c),d=new Headers({"content-length":String(l.size),"content-type":V(c),"cache-control":r.cacheControl,"last-modified":l.mtime.toUTCString(),"x-content-type-options":"nosniff"});if(t.headers){const h=typeof t.headers=="function"?t.headers(c,l):t.headers;new Headers(h).forEach((f,y)=>{d.set(y,f)})}return new Response(q.Readable.toWeb(D.createReadStream(c)),{status:200,headers:d})}async function X(a){return A.realpath(a)}function G(a,e){if(a.includes("\0"))return new Response("Bad Request",{status:400});const t=a.replace(/\\/g,"/").split("/").filter(Boolean),s=[];for(const r of t){let n;try{n=decodeURIComponent(r)}catch{return new Response("Bad Request",{status:400})}if(!(!n||n===".")){if(n===".."||n.includes("/")||n.includes("\\")||n.includes("\0"))return new Response("Forbidden",{status:403});if(n.startsWith(".")){if(e==="deny")return new Response("Forbidden",{status:403});if(e!=="allow")return new Response("Not Found",{status:404})}s.push(n)}}return s}async function J(a,e,t){try{if((await A.lstat(a)).isDirectory()){const r=S.resolve(a,t);return O(r,e)}return O(a,e)}catch{return new Response("Not Found",{status:404})}}async function O(a,e){try{const t=await A.realpath(a);return j(e,t)?(await A.stat(t)).isFile()?t:new Response("Not Found",{status:404}):new Response("Forbidden",{status:403})}catch{return new Response("Not Found",{status:404})}}function j(a,e){const t=S.relative(a,e);return t===""||!t.startsWith("..")&&!S.isAbsolute(t)}function V(a){return B[S.extname(a).toLowerCase()]??"application/octet-stream"}function K(a){return typeof a.params.path=="string"?a.params.path:a.url.pathname.replace(/^\/+/,"")}function W(a){return a instanceof Response&&a.status>=400&&a.status<600}function L(a){return a instanceof Response&&a.status>=300&&a.status<400}class Y{constructor(e){this.data=new Map,this.windowMs=e.windowMs,this.startCleanup()}async incr(e){const t=Date.now();let s=this.data.get(e);return!s||t>=s.reset?(s={count:1,reset:t+this.windowMs},this.data.set(e,s)):s.count++,{current:s.count,reset:s.reset}}startCleanup(){this.cleanupInterval=setInterval(()=>{const e=Date.now();for(const[t,{reset:s}]of this.data)e>=s&&this.data.delete(t)},Math.min(this.windowMs,3e5))}stop(){this.cleanupInterval&&clearInterval(this.cleanupInterval)}async resetAll(){this.data.clear()}}function Q(a){const{windowMs:e=6e4,max:t,key:s=c=>c.getClientAddress(),message:r="Too many requests, please try again later.",statusCode:n=429,headers:o="include",onRateLimit:i,store:u=new Y({windowMs:e})}=a;return async(c,l)=>{const d=`rl:${s(c)}`,{current:h,reset:f}=await u.incr(d),y=Math.ceil((f-Date.now())/1e3);if(h>t){i&&i(c,{current:h,max:t,key:d});const w={status:n,headers:new Headers},p=w.headers;o==="include"&&(p.set("X-RateLimit-Limit",String(t)),p.set("X-RateLimit-Remaining","0"),p.set("X-RateLimit-Reset",String(Math.floor(f/1e3))),p.set("Retry-After",String(y)));let m;return typeof r=="string"?(m=r,p.set("Content-Type","text/plain")):(m=JSON.stringify(r),p.set("Content-Type","application/json")),new Response(m,w)}if(c.rateLimit={current:h,limit:t,reset:new Date(f),remaining:t-h},o==="include"){const w={"X-RateLimit-Limit":String(t),"X-RateLimit-Remaining":String(t-h),"X-RateLimit-Reset":String(Math.floor(f/1e3))},p=c.setHeaders;c.setHeaders=m=>{p({...w,...m})},p(w)}return l()}}const Z=Object.freeze(Object.defineProperty({__proto__:null,fixedWindowLimit:Q},Symbol.toStringTag,{value:"Module"})),ee=["GET","POST","PUT","DELETE","PATCH","HEAD","OPTIONS"],te=["Accept","Accept-Language","Content-Language","Content-Type","Range"],se=["Authorization","X-Auth-Token","X-Requested-With","X-CSRF-Token","X-HTTP-Method-Override","X-Forwarded-For","X-Real-IP","X-Custom-Header"];function M(a,e){return!a||!e?!1:e==="*"?!0:e==="null"?a==="null":Array.isArray(e)?e.some(t=>M(a,t)):typeof e=="function"?e(a):e instanceof RegExp?e.test(a):a===e}function re(a,e){const{origin:t="*"}=e;return a?t==="*"?e.credentials?a:"*":M(a,t)?a:null:t==="*"?"*":null}function ne(a={}){const{methods:e=ee,allowedHeaders:t=se,exposedHeaders:s,credentials:r=!1,maxAge:n=86400,onResponse:o}=a,i="Origin,Access-Control-Request-Method,Access-Control-Request-Headers",u=e.join(","),c=[...te,...t].join(","),l=[["Vary",i],["Access-Control-Allow-Methods",u],["Access-Control-Allow-Headers",c]];return s&&l.push(["Access-Control-Expose-Headers",s.join(",")]),r&&l.push(["Access-Control-Allow-Credentials","true"]),n&&l.push(["Access-Control-Max-Age",n.toString()]),async(d,h)=>{const f=d.request,y=f.headers.get("Origin"),w=f.method==="OPTIONS"&&y!==null&&f.headers.has("Access-Control-Request-Method"),p=re(y,a);if(w){if(!p)return new Response(null,{status:403});const g=new Response(null,{status:204});for(const[E,I]of l)g.headers.set(E,I);return g.headers.set("Access-Control-Allow-Origin",p),g}const m=await h();if(!m)return;if(!p)return m;const x=new Response(m.body,m);for(const[g,E]of l)x.headers.set(g,E);x.headers.set("Access-Control-Allow-Origin",p);let P=x;if(o){const g=o(P);g&&(P=g)}return P}}const oe=Object.freeze(Object.defineProperty({__proto__:null,policy:ne},Symbol.toStringTag,{value:"Module"})),k=["GET","PUT","POST","DELETE","PATCH","HEAD","OPTIONS"];class v{static{this.cache=new Map}static get(e){return this.cache.get(e)}static set(e,t){this.cache.set(e,t)}}class H{constructor(){this._routes=[],this._wsRoutes=[],this._nestedRouters=[],this._middlewares=[],this._preHandlers=[],this._postHandlers=[],this.routesSorted=!1,this.wsRoutesSorted=!1}get routes(){return this._routes}get nestedRouters(){return this._nestedRouters}GET(e,t,...s){return this.addHandler("GET",e,t,s)}POST(e,t,...s){return this.addHandler("POST",e,t,s)}PUT(e,t,...s){return this.addHandler("PUT",e,t,s)}PATCH(e,t,...s){return this.addHandler("PATCH",e,t,s)}DELETE(e,t,...s){return this.addHandler("DELETE",e,t,s)}HEAD(e,t,...s){return this.addHandler("HEAD",e,t,s)}OPTIONS(e,t,...s){return this.addHandler("OPTIONS",e,t,s)}USE(e,t,...s){return k.forEach(r=>this.addHandler(r,e,t,s)),this}action(e,t,...s){const r=async n=>{try{const o=await t(n);return this.formatActionResult(o)}catch(o){return this.handleActionError(o)}};return this.addHandler("POST",e,r,s)}use(e,t,...s){let r,n,o=s;Array.isArray(e)?([r,n]=e,o=e.length>2?e.slice(2):[]):(r=e,n=t);const i=this.normalizePrefix(r),{regex:u,paramNames:c,isCatchAll:l,priority:d}=this.createPrefixRegex(i);return this._nestedRouters.push({prefix:i,router:n,regex:u,paramNames:c,isCatchAll:l,priority:d,middlewares:o}),this}useMiddleware(...e){return this._middlewares.push(...e),this}pre(...e){return this._preHandlers.push(...e),this}post(...e){return this._postHandlers.push(...e),this}discard(e,t){return this._nestedRouters=this._nestedRouters.filter(s=>s.prefix!==e),this._routes=this._routes.filter(s=>s.path!==e||t&&s.method!==t),this}WS(e,t,...s){const{regex:r,paramNames:n,isCatchAll:o,priority:i}=this.createPathRegex(e);return this._wsRoutes.push({path:e,regex:r,paramNames:n,isCatchAll:o,priority:i,handler:t,middlewares:s}),this.wsRoutesSorted=!1,this}async canHandleWebSocket(e){return this.canHandleWebSocketAtPath(e,e.url.pathname)}async canHandleWebSocketAtPath(e,t){this.wsRoutesSorted||this.sortWsRoutes();for(const s of[...this._nestedRouters].sort((r,n)=>n.priority-r.priority)){const r=t.match(s.regex);if(!r||r.index!==0)continue;const n=r[0],o=t.slice(n.length)||"/",i={...e,params:{...e.params,...this.extractPrefixParams(s,n)}};if(await s.router.canHandleWebSocketAtPath(i,o))return!0}for(const s of this._wsRoutes)if(s.regex.test(t))return!0;return!1}async handleWebSocket(e,t){return this.handleWebSocketAtPath(e,t,e.url.pathname)}async handleWebSocketAtPath(e,t,s){this.wsRoutesSorted||this.sortWsRoutes();for(const r of[...this._nestedRouters].sort((n,o)=>o.priority-n.priority)){const n=s.match(r.regex);if(!n||n.index!==0)continue;const o=n[0],i=s.slice(o.length)||"/",u=this.extractPrefixParams(r,o),c={...e,params:{...e.params,...u}},l=[...this._middlewares,...r.middlewares],d=()=>r.router.handleWebSocketAtPath(c,t,i);if(await this.applyMiddlewaresWithList(c,l,d))return!0}for(const r of this._wsRoutes){if(!r.regex.test(s))continue;const n=s.match(r.regex);if(!n)continue;const o=Object.fromEntries(r.paramNames.map((l,d)=>[l,n[d+1]||""])),i={...e,params:{...e.params,...o},route:{...e.route,id:r.path},websocket:t},u=[...this._middlewares,...r.middlewares];if(await this.applyMiddlewaresWithList(i,u,()=>r.handler(i))===void 0)return!0}return!1}async handle(e){return this.handleAtPath(e,e.url.pathname)}async handleAtPath(e,t){const s=e.request.method;let r=await this.runPreHandlers(e);if(!r){const o=async()=>{const i=await this.handleNestedRouters(e,t);return i||(this.routesSorted||this.sortRoutes(),this.handleLocalRoutes(e,s,t))};r=await this.applyMiddlewaresWithList(e,this._middlewares,o)}const n=r||new Response("No Content",{status:204});return this.runPostHandlers(e,n)}async applyMiddlewaresWithList(e,t,s){const r=[...t],n=async o=>o>=r.length?s():r[o](e,()=>n(o+1));return n(0)}async runPreHandlers(e){for(const t of this._preHandlers){const s=await t(e);if(s instanceof Response)return s}}async runPostHandlers(e,t){let s=t;for(const r of this._postHandlers){const n=await r(e,s);n instanceof Response&&(s=n)}return s}addHandler(e,t,s,r=[]){const{regex:n,paramNames:o,isCatchAll:i,priority:u}=this.createPathRegex(t);return this._routes.push({method:e,path:t,regex:n,paramNames:o,isCatchAll:i,priority:u,handler:s,middlewares:r}),this.routesSorted=!1,this}createPathRegex(e){const t=v.get(e);if(t)return t;const s=[];let r=!1,n;const o=e.split("/").filter(Boolean);n=o.reduce((c,l)=>l.startsWith("[...")?c-10:l.startsWith("[[")?c-5:l.startsWith("[")?c-1:c+1,0);let i="^";for(const c of o)if(c.startsWith("[...")&&c.endsWith("]")){r=!0;const l=c.slice(4,-1);s.push(l),i+="/(.+)"}else if(c.startsWith("[[")&&c.endsWith("]]")){const l=c.slice(2,-2);s.push(l),i+="(?:/([^/]+))?"}else if(c.startsWith("[")&&c.endsWith("]")){const l=c.slice(1,-1);s.push(l),i+="/([^/]+)"}else i+="/"+c.replace(/[-/\\^$*+?.()|[\]{}]/g,"\\$&");i+="/?$";const u={regex:new RegExp(i),paramNames:s,isCatchAll:r,priority:n};return v.set(e,u),u}createPrefixRegex(e){const t=[];let s=!1,r;const n=e.split("/").filter(Boolean);r=n.reduce((i,u)=>u.startsWith("[...")?i-10:u.startsWith("[[")?i-5:u.startsWith("[")?i-1:i+1,0);let o="^";for(const i of n)if(i.startsWith("[...")&&i.endsWith("]")){s=!0;const u=i.slice(4,-1);t.push(u),o+="/(.+)"}else if(i.startsWith("[[")&&i.endsWith("]]")){const u=i.slice(2,-2);t.push(u),o+="(?:/([^/]+))?"}else if(i.startsWith("[")&&i.endsWith("]")){const u=i.slice(1,-1);t.push(u),o+="/([^/]+)"}else o+="/"+i.replace(/[-/\\^$*+?.()|[\]{}]/g,"\\$&");return o+="(?=/|$)",{regex:new RegExp(o),paramNames:t,isCatchAll:s,priority:r}}normalizePrefix(e){return e.startsWith("/")?e.replace(/\/$/,""):`/${e.replace(/\/$/,"")}`}extractPrefixParams(e,t){const s=t.match(e.regex);if(!s)return{};const r={};return e.isCatchAll&&e.paramNames.length===1?r[e.paramNames[0]]=s[1]?.replace(/^\//,"")||"":e.paramNames.forEach((n,o)=>{r[n]=s[o+1]||""}),r}async handleNestedRouters(e,t){const s=[...this._nestedRouters].sort((r,n)=>n.priority-r.priority);for(const r of s){const n=t.match(r.regex);if(!n||n.index!==0)continue;const o=n[0],i=t.slice(o.length)||"/",u=this.extractPrefixParams(r,o),c={...e,params:{...e.params,...u}},l=async()=>await r.router.handleAtPath(c,i),d=await this.applyMiddlewaresWithList(c,r.middlewares,l);if(d)return d}return null}async handleLocalRoutes(e,t,s){const r=new Set;let n=!1;for(const o of this._routes){if(!o.regex.test(s)||(r.add(o.method),o.method==="GET"&&(n=!0,r.add("HEAD")),!(o.method===t||t==="HEAD"&&o.method==="GET")))continue;const u=s.match(o.regex);if(!u)continue;const c=Object.fromEntries(o.paramNames.map((d,h)=>[d,u[h+1]||""]));e.params={...e.params,...c},e.route={...e.route,id:o.path};const l=()=>o.handler(e);return await this.applyMiddlewaresWithList(e,o.middlewares,l)}if(r.size>0||t==="HEAD"&&n){const o=[...r].join(", ");return t==="OPTIONS"?new Response(null,{status:200,headers:{Allow:o}}):new Response("Method Not Allowed",{status:405,headers:{Allow:o}})}return new Response("Not Found",{status:404})}sortRoutes(){this._routes.sort((e,t)=>t.priority-e.priority),this.routesSorted=!0}sortWsRoutes(){this._wsRoutes.sort((e,t)=>t.priority-e.priority),this.wsRoutesSorted=!0}formatActionResult(e){return e instanceof Response?e:e?.type==="failure"&&"status"in e?R.fail(e.status,e.data):R.success(200,e??void 0)}handleActionError(e){if(W(e))return R.error(e.status,e);if(L(e)){const t=e.headers.get("Location")||"/";return R.redirect(e.status,t)}return console.error(e),R.error(500,{message:"Internal Server Error"})}static New(){return new H}}const R={success:(a=200,e)=>new Response(JSON.stringify({data:e,type:"success",status:a}),{status:a,headers:{"Content-Type":"application/json"}}),redirect:(a=302,e)=>new Response(JSON.stringify({location:e,type:"redirect",status:a}),{status:a,headers:{"Content-Type":"application/json"}}),error:(a=500,e)=>new Response(JSON.stringify({error:e,type:"error",status:a}),{status:a,headers:{"Content-Type":"application/json"}}),fail:(a=400,e)=>new Response(JSON.stringify({data:e,type:"failure",status:a}),{status:a,headers:{"Content-Type":"application/json"}})};class ae{constructor(e,t){this.raw=e.headers.get("cookie")??"",this.setCookieHeader=t}get(e,t){return C.parse(this.raw,t)[e]}getAll(e){return Object.entries(C.parse(this.raw,e)).filter(([,t])=>t!==void 0).map(([t,s])=>({name:t,value:s}))}set(e,t,s){this.setCookieHeader(C.serialize(e,t,s))}delete(e,t){this.set(e,"",{...t,maxAge:0})}}class _ extends Error{constructor(e="Payload Too Large"){super(e),this.status=413,this.name="PayloadTooLargeError"}}class ie{constructor(e){this.upgradeHandlerInstalled=!1,this.config=e??{type:"http",options:{}},this.router=new H,this.wss=new b.WebSocketServer({noServer:!0,maxPayload:this.config.security?.maxWebSocketPayload??1024*1024})}get server(){if(!this._server){const e=(t,s)=>{this.handleRequest(t,s).catch(r=>{console.error("Unhandled request error:",r),s.statusCode=500,s.end("Internal Server Error")})};this._server=this.config.type==="https"?F.createServer(this.config.options,e):U.createServer(this.config.options,e)}return this._server}discard(e,t){return this.router.discard(e,t),this}listen(...e){return this.upgradeHandlerInstalled||(this.installUpgradeHandler(),this.upgradeHandlerInstalled=!0),this.server.listen(...e),this}installUpgradeHandler(){this.server.on("upgrade",(e,t,s)=>{if(e.headers.upgrade?.toLowerCase()!=="websocket"){t.destroy();return}let r,n;try{r=this.toURL(e,!0),n=this.toRequest(e,r,!0)}catch{t.destroy();return}const o=this.toRequestEvent(n,r,{getClientAddress:()=>e.socket.remoteAddress??"127.0.0.1",setHeader:()=>{},pushSetCookie:()=>{}});this.router.canHandleWebSocket(o).then(i=>{if(!i||!this.isAllowedWebSocketOrigin(e)){t.destroy();return}this.wss.handleUpgrade(e,t,s,u=>{this.router.handleWebSocket(o,u).then(c=>{!c&&u.readyState===b.WebSocket.OPEN&&u.close(1008,"Route not found")}).catch(c=>{console.error("WebSocket routing error:",c),u.readyState===b.WebSocket.OPEN&&u.close(1011,"Internal error")})})}).catch(()=>t.destroy())})}close(e){this.wss.close(()=>{this.server.close(e)})}address(){return this.server.address()}get listening(){return this.server.listening}async handleRequest(e,t){if(!this.isRequestBodyAllowed(e)){t.statusCode=413,t.end("Payload Too Large");return}const s=this.toWebRequest(e),r=new URL(s.url),n={},o=[],i=this.toRequestEvent(s,r,{getClientAddress:()=>e.socket.remoteAddress??"127.0.0.1",setHeader:(c,l)=>{n[c.toLowerCase()]=l},pushSetCookie:c=>{o.push(c)}});let u;try{u=await this.router.handle(i)}catch(c){u=this.handleError(c)}for(const[c,l]of Object.entries(n))t.setHeader(c,l);o.length>0&&t.setHeader("Set-Cookie",o),await this.sendWebResponse(t,u)}toWebRequest(e){const t=this.toURL(e,!1);return this.toRequest(e,t,!1)}toRequest(e,t,s){const r={method:s?"GET":e.method,headers:this.toHeaders(e.headers),duplex:"half"};return!s&&e.method!=="GET"&&e.method!=="HEAD"&&(r.body=T.Readable.toWeb(this.wrapRequestBody(e))),new Request(t,r)}wrapRequestBody(e){const t=this.config.security?.maxRequestBodySize;if(!t)return e;let s=0;const r=new T.Transform({transform(n,o,i){if(s+=Buffer.byteLength(n),s>t){i(new _);return}i(null,n)}});return e.on("aborted",()=>r.destroy(new Error("Request aborted"))),e.on("error",n=>r.destroy(n)),e.pipe(r),r}toURL(e,t){const s=e.socket instanceof z.TLSSocket?t?"wss":"https":t?"ws":"http",r=this.resolveAuthority(e);return new URL(e.url??"/",`${s}://${r}`)}resolveAuthority(e){const t=this.config.security?.trustHostHeader?this.normalizeTrustedHost(e.headers.host):null;if(t)return t;const s=this.server.address();return s&&typeof s=="object"?`${s.address.includes(":")?`[${s.address}]`:s.address}:${s.port}`:e.socket.localPort?`127.0.0.1:${e.socket.localPort}`:"localhost"}normalizeTrustedHost(e){if(!e)return null;let t;try{t=new URL(`http://${e}`)}catch{return null}if(t.username||t.password||t.pathname!=="/"||t.search||t.hash)return null;const s=t.port?`${t.hostname}:${t.port}`:t.hostname,r=this.config.security?.allowedHosts;return!r||this.matchesValue(s,r)?s:null}matchesValue(e,t){return(Array.isArray(t)?t:[t]).some(r=>typeof r=="string"?r===e:r instanceof RegExp?r.test(e):r(e))}toHeaders(e){const t=new Headers;for(const[s,r]of Object.entries(e))if(r!==void 0){if(Array.isArray(r)){const n=s.toLowerCase()==="cookie"?r.join("; "):r.join(", ");t.set(s,n);continue}t.set(s,r)}return t}isRequestBodyAllowed(e){const t=this.config.security?.maxRequestBodySize;if(!t)return!0;const s=e.headers["content-length"];if(!s)return!0;const r=Number.parseInt(Array.isArray(s)?s[0]:s,10);return Number.isFinite(r)&&r<=t}handleError(e){if(e instanceof _)return new Response(e.message,{status:e.status});if(W(e))return new Response(JSON.stringify({error:e.statusText||"Error",status:e.status}),{status:e.status,headers:{"Content-Type":"application/json"}});if(L(e)){const t=e.headers.get("Location")||"/";return new Response(null,{status:e.status,headers:{Location:t}})}return console.error("Unhandled error:",e),new Response("Internal Server Error",{status:500})}async sendWebResponse(e,t){if(e.statusCode=t.status,t.headers.forEach((n,o)=>{e.setHeader(o,n)}),e.hasHeader("Server")||e.setHeader("Server","WebHTTPServer"),!t.body||this.shouldOmitResponseBody(t,e.req?.method)){e.end();return}const s=t.body.getReader(),r=T.Writable.toWeb(e).getWriter();try{for(;;){const{done:n,value:o}=await s.read();if(n)break;await r.write(o)}}finally{await r.close().catch(()=>{})}}shouldOmitResponseBody(e,t){return t==="HEAD"?!0:e.status===204||e.status===205||e.status===304}isAllowedWebSocketOrigin(e){const t=e.headers.origin;if(!t)return!0;const s=this.config.security?.allowedWebSocketOrigins;return s?this.matchesValue(t,s):!0}toRequestEvent(e,t,s){const r=new ae(e,s.pushSetCookie),n=this.config,o={},i={name:"WebHTTPServer"},u=new Set,c={request:e,url:t,cookies:r,getClientAddress:s.getClientAddress,get locals(){return o},get platform(){return i},params:{},route:{id:""},setHeaders:l=>{for(const[d,h]of Object.entries(l)){const f=d.toLowerCase();if(f==="set-cookie")throw new TypeError("Use event.cookies for Set-Cookie headers");if(u.has(f))throw new TypeError(`Header "${d}" has already been set`);u.add(f),s.setHeader(d,h)}}};return n.locals&&Object.assign(o,n.locals(c)),n.platform&&Object.assign(i,n.platform(c)),c}}const ce=async(a,e)=>{const t=JSON.stringify(await a);return new Response(t,{...e,headers:{"content-type":"application/json","content-length":Buffer.byteLength(t).toString(),...e?.headers}})},ue=async(a,e)=>{const t=await a;return new Response(t,{...e,headers:{"content-type":"text/plain","content-length":Buffer.byteLength(t).toString(),...e?.headers}})},le=async(a,e)=>{const t=await a;return new Response(t,{...e,headers:{"content-type":"text/html","content-length":Buffer.byteLength(t).toString(),...e?.headers}})},de=(a,e={})=>t=>N(a,t,e);exports.Action=R;exports.CORS=oe;exports.RateLimiter=Z;exports.RequestMethods=k;exports.Router=H;exports.WebServer=ie;exports.dir=de;exports.html=le;exports.isHttpError=W;exports.isRedirect=L;exports.json=ce;exports.serveStatic=N;exports.text=ue;
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const D=require("node:fs"),A=require("node:fs/promises"),S=require("node:path"),q=require("node:stream"),U=require("http"),F=require("https"),z=require("tls"),T=require("stream"),b=require("ws"),C=require("cookie"),B={".avif":"image/avif",".css":"text/css; charset=utf-8",".gif":"image/gif",".html":"text/html; charset=utf-8",".ico":"image/x-icon",".jpg":"image/jpeg",".jpeg":"image/jpeg",".js":"text/javascript; charset=utf-8",".json":"application/json; charset=utf-8",".mjs":"text/javascript; charset=utf-8",".pdf":"application/pdf",".png":"image/png",".svg":"image/svg+xml; charset=utf-8",".txt":"text/plain; charset=utf-8",".wasm":"application/wasm",".webp":"image/webp",".xml":"application/xml; charset=utf-8"},$={index:"index.html",cacheControl:"public, max-age=0",dotFiles:"ignore"};async function N(a,e,t={}){const s=K(e),r={...$,...t},n=await X(a),o=G(s,r.dotFiles);if(o instanceof Response)return o;const i=o.length>0?o.join(S.sep):"",u=S.resolve(n,i);if(!j(n,u))return new Response("Forbidden",{status:403});const c=await J(u,n,r.index);if(c instanceof Response)return c;const l=await A.stat(c),d=new Headers({"content-length":String(l.size),"content-type":V(c),"cache-control":r.cacheControl,"last-modified":l.mtime.toUTCString(),"x-content-type-options":"nosniff"});if(t.headers){const h=typeof t.headers=="function"?t.headers(c,l):t.headers;new Headers(h).forEach((f,y)=>{d.set(y,f)})}return new Response(q.Readable.toWeb(D.createReadStream(c)),{status:200,headers:d})}async function X(a){return A.realpath(a)}function G(a,e){if(a.includes("\0"))return new Response("Bad Request",{status:400});const t=a.replace(/\\/g,"/").split("/").filter(Boolean),s=[];for(const r of t){let n;try{n=decodeURIComponent(r)}catch{return new Response("Bad Request",{status:400})}if(!(!n||n===".")){if(n===".."||n.includes("/")||n.includes("\\")||n.includes("\0"))return new Response("Forbidden",{status:403});if(n.startsWith(".")){if(e==="deny")return new Response("Forbidden",{status:403});if(e!=="allow")return new Response("Not Found",{status:404})}s.push(n)}}return s}async function J(a,e,t){try{if((await A.lstat(a)).isDirectory()){const r=S.resolve(a,t);return O(r,e)}return O(a,e)}catch{return new Response("Not Found",{status:404})}}async function O(a,e){try{const t=await A.realpath(a);return j(e,t)?(await A.stat(t)).isFile()?t:new Response("Not Found",{status:404}):new Response("Forbidden",{status:403})}catch{return new Response("Not Found",{status:404})}}function j(a,e){const t=S.relative(a,e);return t===""||!t.startsWith("..")&&!S.isAbsolute(t)}function V(a){return B[S.extname(a).toLowerCase()]??"application/octet-stream"}function K(a){return typeof a.params.path=="string"?a.params.path:a.url.pathname.replace(/^\/+/,"")}function W(a){return a instanceof Response&&a.status>=400&&a.status<600}function L(a){return a instanceof Response&&a.status>=300&&a.status<400}class Y{constructor(e){this.data=new Map,this.windowMs=e.windowMs,this.startCleanup()}async incr(e){const t=Date.now();let s=this.data.get(e);return!s||t>=s.reset?(s={count:1,reset:t+this.windowMs},this.data.set(e,s)):s.count++,{current:s.count,reset:s.reset}}startCleanup(){this.cleanupInterval=setInterval(()=>{const e=Date.now();for(const[t,{reset:s}]of this.data)e>=s&&this.data.delete(t)},Math.min(this.windowMs,3e5))}stop(){this.cleanupInterval&&clearInterval(this.cleanupInterval)}async resetAll(){this.data.clear()}}function Q(a){const{windowMs:e=6e4,max:t,key:s=c=>c.getClientAddress(),message:r="Too many requests, please try again later.",statusCode:n=429,headers:o="include",onRateLimit:i,store:u=new Y({windowMs:e})}=a;return async(c,l)=>{const d=`rl:${s(c)}`,{current:h,reset:f}=await u.incr(d),y=Math.ceil((f-Date.now())/1e3);if(h>t){i&&i(c,{current:h,max:t,key:d});const w={status:n,headers:new Headers},p=w.headers;o==="include"&&(p.set("X-RateLimit-Limit",String(t)),p.set("X-RateLimit-Remaining","0"),p.set("X-RateLimit-Reset",String(Math.floor(f/1e3))),p.set("Retry-After",String(y)));let m;return typeof r=="string"?(m=r,p.set("Content-Type","text/plain")):(m=JSON.stringify(r),p.set("Content-Type","application/json")),new Response(m,w)}if(c.rateLimit={current:h,limit:t,reset:new Date(f),remaining:t-h},o==="include"){const w={"X-RateLimit-Limit":String(t),"X-RateLimit-Remaining":String(t-h),"X-RateLimit-Reset":String(Math.floor(f/1e3))},p=c.setHeaders;c.setHeaders=m=>{p({...w,...m})},p(w)}return l()}}const Z=Object.freeze(Object.defineProperty({__proto__:null,fixedWindowLimit:Q},Symbol.toStringTag,{value:"Module"})),ee=["GET","POST","PUT","DELETE","PATCH","HEAD","OPTIONS"],te=["Accept","Accept-Language","Content-Language","Content-Type","Range"],se=["Authorization","X-Auth-Token","X-Requested-With","X-CSRF-Token","X-HTTP-Method-Override","X-Forwarded-For","X-Real-IP","X-Custom-Header"];function M(a,e){return!a||!e?!1:e==="*"?!0:e==="null"?a==="null":Array.isArray(e)?e.some(t=>M(a,t)):typeof e=="function"?e(a):e instanceof RegExp?e.test(a):a===e}function re(a,e){const{origin:t="*"}=e;return a?t==="*"?e.credentials?a:"*":M(a,t)?a:null:t==="*"?"*":null}function ne(a={}){const{methods:e=ee,allowedHeaders:t=se,exposedHeaders:s,credentials:r=!1,maxAge:n=86400,onResponse:o}=a,i="Origin,Access-Control-Request-Method,Access-Control-Request-Headers",u=e.join(","),c=[...te,...t].join(","),l=[["Vary",i],["Access-Control-Allow-Methods",u],["Access-Control-Allow-Headers",c]];return s&&l.push(["Access-Control-Expose-Headers",s.join(",")]),r&&l.push(["Access-Control-Allow-Credentials","true"]),n&&l.push(["Access-Control-Max-Age",n.toString()]),async(d,h)=>{const f=d.request,y=f.headers.get("Origin"),w=f.method==="OPTIONS"&&y!==null&&f.headers.has("Access-Control-Request-Method"),p=re(y,a);if(w){if(!p)return new Response(null,{status:403});const g=new Response(null,{status:204});for(const[E,I]of l)g.headers.set(E,I);return g.headers.set("Access-Control-Allow-Origin",p),g}const m=await h();if(!m)return;if(!p)return m;const x=new Response(m.body,m);for(const[g,E]of l)x.headers.set(g,E);x.headers.set("Access-Control-Allow-Origin",p);let P=x;if(o){const g=o(P);g&&(P=g)}return P}}const oe=Object.freeze(Object.defineProperty({__proto__:null,policy:ne},Symbol.toStringTag,{value:"Module"})),k=["GET","PUT","POST","DELETE","PATCH","HEAD","OPTIONS"];class v{static{this.cache=new Map}static get(e){return this.cache.get(e)}static set(e,t){this.cache.set(e,t)}}class H{constructor(){this._routes=[],this._wsRoutes=[],this._nestedRouters=[],this._middlewares=[],this._preHandlers=[],this._postHandlers=[],this.routesSorted=!1,this.wsRoutesSorted=!1}get routes(){return this._routes}get nestedRouters(){return this._nestedRouters}GET(e,t,...s){return this.addHandler("GET",e,t,s)}POST(e,t,...s){return this.addHandler("POST",e,t,s)}PUT(e,t,...s){return this.addHandler("PUT",e,t,s)}PATCH(e,t,...s){return this.addHandler("PATCH",e,t,s)}DELETE(e,t,...s){return this.addHandler("DELETE",e,t,s)}HEAD(e,t,...s){return this.addHandler("HEAD",e,t,s)}OPTIONS(e,t,...s){return this.addHandler("OPTIONS",e,t,s)}USE(e,t,...s){return k.forEach(r=>this.addHandler(r,e,t,s)),this}action(e,t,...s){const r=async n=>{try{const o=await t(n);return this.formatActionResult(o)}catch(o){return this.handleActionError(o)}};return this.addHandler("POST",e,r,s)}use(e,t,...s){let r,n,o=s;Array.isArray(e)?([r,n]=e,o=e.length>2?e.slice(2):[]):(r=e,n=t);const i=this.normalizePrefix(r),{regex:u,paramNames:c,isCatchAll:l,priority:d}=this.createPrefixRegex(i);return this._nestedRouters.push({prefix:i,router:n,regex:u,paramNames:c,isCatchAll:l,priority:d,middlewares:o}),this}useMiddleware(...e){return this._middlewares.push(...e),this}pre(...e){return this._preHandlers.push(...e),this}post(...e){return this._postHandlers.push(...e),this}discard(e,t){return this._nestedRouters=this._nestedRouters.filter(s=>s.prefix!==e),this._routes=this._routes.filter(s=>s.path!==e||t&&s.method!==t),this}WS(e,t,...s){const{regex:r,paramNames:n,isCatchAll:o,priority:i}=this.createPathRegex(e);return this._wsRoutes.push({path:e,regex:r,paramNames:n,isCatchAll:o,priority:i,handler:t,middlewares:s}),this.wsRoutesSorted=!1,this}async canHandleWebSocket(e){return this.canHandleWebSocketAtPath(e,e.url.pathname)}async canHandleWebSocketAtPath(e,t){this.wsRoutesSorted||this.sortWsRoutes();for(const s of[...this._nestedRouters].sort((r,n)=>n.priority-r.priority)){const r=t.match(s.regex);if(!r||r.index!==0)continue;const n=r[0],o=t.slice(n.length)||"/",i={...e,params:{...e.params,...this.extractPrefixParams(s,n)}};if(await s.router.canHandleWebSocketAtPath(i,o))return!0}for(const s of this._wsRoutes)if(s.regex.test(t))return!0;return!1}async handleWebSocket(e,t){return this.handleWebSocketAtPath(e,t,e.url.pathname)}async handleWebSocketAtPath(e,t,s){this.wsRoutesSorted||this.sortWsRoutes();for(const r of[...this._nestedRouters].sort((n,o)=>o.priority-n.priority)){const n=s.match(r.regex);if(!n||n.index!==0)continue;const o=n[0],i=s.slice(o.length)||"/",u=this.extractPrefixParams(r,o),c={...e,params:{...e.params,...u}},l=[...this._middlewares,...r.middlewares],d=()=>r.router.handleWebSocketAtPath(c,t,i);if(await this.applyMiddlewaresWithList(c,l,d))return!0}for(const r of this._wsRoutes){if(!r.regex.test(s))continue;const n=s.match(r.regex);if(!n)continue;const o=Object.fromEntries(r.paramNames.map((l,d)=>[l,n[d+1]||""])),i={...e,params:{...e.params,...o},route:{...e.route,id:r.path},websocket:t},u=[...this._middlewares,...r.middlewares];if(await this.applyMiddlewaresWithList(i,u,()=>r.handler(i))===void 0)return!0}return!1}async handle(e){return this.handleAtPath(e,e.url.pathname)}async handleAtPath(e,t){const s=e.request.method;let r=await this.runPreHandlers(e);if(!r){const o=async()=>{const i=await this.handleNestedRouters(e,t);return i||(this.routesSorted||this.sortRoutes(),this.handleLocalRoutes(e,s,t))};r=await this.applyMiddlewaresWithList(e,this._middlewares,o)}const n=r||new Response("No Content",{status:204});return this.runPostHandlers(e,n)}async applyMiddlewaresWithList(e,t,s){const r=[...t],n=async o=>o>=r.length?s():r[o](e,()=>n(o+1));return n(0)}async runPreHandlers(e){for(const t of this._preHandlers){const s=await t(e);if(s instanceof Response)return s}}async runPostHandlers(e,t){let s=t;for(const r of this._postHandlers){const n=await r(e,s);n instanceof Response&&(s=n)}return s}addHandler(e,t,s,r=[]){const{regex:n,paramNames:o,isCatchAll:i,priority:u}=this.createPathRegex(t);return this._routes.push({method:e,path:t,regex:n,paramNames:o,isCatchAll:i,priority:u,handler:s,middlewares:r}),this.routesSorted=!1,this}createPathRegex(e){const t=v.get(e);if(t)return t;const s=[];let r=!1,n;const o=e.split("/").filter(Boolean);n=o.reduce((c,l)=>l.startsWith("[...")?c-10:l.startsWith("[[")?c-5:l.startsWith("[")?c-1:c+1,0);let i="^";for(const c of o)if(c.startsWith("[...")&&c.endsWith("]")){r=!0;const l=c.slice(4,-1);s.push(l),i+="/(.+)"}else if(c.startsWith("[[")&&c.endsWith("]]")){const l=c.slice(2,-2);s.push(l),i+="(?:/([^/]+))?"}else if(c.startsWith("[")&&c.endsWith("]")){const l=c.slice(1,-1);s.push(l),i+="/([^/]+)"}else i+="/"+c.replace(/[-/\\^$*+?.()|[\]{}]/g,"\\$&");i+="/?$";const u={regex:new RegExp(i),paramNames:s,isCatchAll:r,priority:n};return v.set(e,u),u}createPrefixRegex(e){const t=[];let s=!1,r;const n=e.split("/").filter(Boolean);r=n.reduce((i,u)=>u.startsWith("[...")?i-10:u.startsWith("[[")?i-5:u.startsWith("[")?i-1:i+1,0);let o="^";for(const i of n)if(i.startsWith("[...")&&i.endsWith("]")){s=!0;const u=i.slice(4,-1);t.push(u),o+="/(.+)"}else if(i.startsWith("[[")&&i.endsWith("]]")){const u=i.slice(2,-2);t.push(u),o+="(?:/([^/]+))?"}else if(i.startsWith("[")&&i.endsWith("]")){const u=i.slice(1,-1);t.push(u),o+="/([^/]+)"}else o+="/"+i.replace(/[-/\\^$*+?.()|[\]{}]/g,"\\$&");return o+="(?=/|$)",{regex:new RegExp(o),paramNames:t,isCatchAll:s,priority:r}}normalizePrefix(e){return e.startsWith("/")?e.replace(/\/$/,""):`/${e.replace(/\/$/,"")}`}extractPrefixParams(e,t){const s=t.match(e.regex);if(!s)return{};const r={};return e.isCatchAll&&e.paramNames.length===1?r[e.paramNames[0]]=s[1]?.replace(/^\//,"")||"":e.paramNames.forEach((n,o)=>{r[n]=s[o+1]||""}),r}async handleNestedRouters(e,t){const s=[...this._nestedRouters].sort((r,n)=>n.priority-r.priority);for(const r of s){const n=t.match(r.regex);if(!n||n.index!==0)continue;const o=n[0],i=t.slice(o.length)||"/",u=this.extractPrefixParams(r,o),c={...e,params:{...e.params,...u}},l=async()=>await r.router.handleAtPath(c,i),d=await this.applyMiddlewaresWithList(c,r.middlewares,l);if(d)return d}return null}async handleLocalRoutes(e,t,s){const r=new Set;let n=!1;for(const o of this._routes){if(!o.regex.test(s)||(r.add(o.method),o.method==="GET"&&(n=!0,r.add("HEAD")),!(o.method===t||t==="HEAD"&&o.method==="GET")))continue;const u=s.match(o.regex);if(!u)continue;const c=Object.fromEntries(o.paramNames.map((d,h)=>[d,u[h+1]||""]));e.params={...e.params,...c},e.route={...e.route,id:o.path};const l=()=>o.handler(e);return await this.applyMiddlewaresWithList(e,o.middlewares,l)}if(r.size>0||t==="HEAD"&&n){const o=[...r].join(", ");return t==="OPTIONS"?new Response(null,{status:200,headers:{Allow:o}}):new Response("Method Not Allowed",{status:405,headers:{Allow:o}})}return new Response("Not Found",{status:404})}sortRoutes(){this._routes.sort((e,t)=>t.priority-e.priority),this.routesSorted=!0}sortWsRoutes(){this._wsRoutes.sort((e,t)=>t.priority-e.priority),this.wsRoutesSorted=!0}formatActionResult(e){return e instanceof Response?e:e?.type==="failure"&&"status"in e?R.fail(e.status,e.data):R.success(200,e??void 0)}handleActionError(e){if(W(e))return R.error(e.status,e);if(L(e)){const t=e.headers.get("Location")||"/";return R.redirect(e.status,t)}return console.error(e),R.error(500,{message:"Internal Server Error"})}static New(){return new H}}const R={success:(a=200,e)=>new Response(JSON.stringify({data:e,type:"success",status:a}),{status:a,headers:{"Content-Type":"application/json"}}),redirect:(a=302,e)=>new Response(JSON.stringify({location:e,type:"redirect",status:a}),{status:a,headers:{"Content-Type":"application/json"}}),error:(a=500,e)=>new Response(JSON.stringify({error:e,type:"error",status:a}),{status:a,headers:{"Content-Type":"application/json"}}),fail:(a=400,e)=>new Response(JSON.stringify({data:e,type:"failure",status:a}),{status:a,headers:{"Content-Type":"application/json"}})};class ae{constructor(e,t){this.raw=e.headers.get("cookie")??"",this.setCookieHeader=t}get(e,t){return C.parse(this.raw,t)[e]}getAll(e){return Object.entries(C.parse(this.raw,e)).filter(([,t])=>t!==void 0).map(([t,s])=>({name:t,value:s}))}set(e,t,s){this.setCookieHeader(C.serialize(e,t,s))}delete(e,t){this.set(e,"",{...t,maxAge:0})}}class _ extends Error{constructor(e="Payload Too Large"){super(e),this.status=413,this.name="PayloadTooLargeError"}}class ie extends H{constructor(e){super(),this.upgradeHandlerInstalled=!1,this.config=e??{type:"http",options:{}},this.wss=new b.WebSocketServer({noServer:!0,maxPayload:this.config.security?.maxWebSocketPayload??1024*1024})}get server(){if(!this._server){const e=(t,s)=>{this.handleRequest(t,s).catch(r=>{console.error("Unhandled request error:",r),s.statusCode=500,s.end("Internal Server Error")})};this._server=this.config.type==="https"?F.createServer(this.config.options,e):U.createServer(this.config.options,e)}return this._server}listen(...e){return this.upgradeHandlerInstalled||(this.installUpgradeHandler(),this.upgradeHandlerInstalled=!0),this.server.listen(...e),this}installUpgradeHandler(){this.server.on("upgrade",(e,t,s)=>{if(e.headers.upgrade?.toLowerCase()!=="websocket"){t.destroy();return}let r,n;try{r=this.toURL(e,!0),n=this.toRequest(e,r,!0)}catch{t.destroy();return}const o=this.toRequestEvent(n,r,{getClientAddress:()=>e.socket.remoteAddress??"127.0.0.1",setHeader:()=>{},pushSetCookie:()=>{}});this.canHandleWebSocket(o).then(i=>{if(!i||!this.isAllowedWebSocketOrigin(e)){t.destroy();return}this.wss.handleUpgrade(e,t,s,u=>{this.handleWebSocket(o,u).then(c=>{!c&&u.readyState===b.WebSocket.OPEN&&u.close(1008,"Route not found")}).catch(c=>{console.error("WebSocket routing error:",c),u.readyState===b.WebSocket.OPEN&&u.close(1011,"Internal error")})})}).catch(()=>t.destroy())})}close(e){this.wss.close(()=>{this.server.close(e)})}address(){return this.server.address()}get listening(){return this.server.listening}async handleRequest(e,t){if(!this.isRequestBodyAllowed(e)){t.statusCode=413,t.end("Payload Too Large");return}const s=this.toWebRequest(e),r=new URL(s.url),n={},o=[],i=this.toRequestEvent(s,r,{getClientAddress:()=>e.socket.remoteAddress??"127.0.0.1",setHeader:(c,l)=>{n[c.toLowerCase()]=l},pushSetCookie:c=>{o.push(c)}});let u;try{u=await this.handle(i)}catch(c){u=this.handleError(c)}for(const[c,l]of Object.entries(n))t.setHeader(c,l);o.length>0&&t.setHeader("Set-Cookie",o),await this.sendWebResponse(t,u)}toWebRequest(e){const t=this.toURL(e,!1);return this.toRequest(e,t,!1)}toRequest(e,t,s){const r={method:s?"GET":e.method,headers:this.toHeaders(e.headers),duplex:"half"};return!s&&e.method!=="GET"&&e.method!=="HEAD"&&(r.body=T.Readable.toWeb(this.wrapRequestBody(e))),new Request(t,r)}wrapRequestBody(e){const t=this.config.security?.maxRequestBodySize;if(!t)return e;let s=0;const r=new T.Transform({transform(n,o,i){if(s+=Buffer.byteLength(n),s>t){i(new _);return}i(null,n)}});return e.on("aborted",()=>r.destroy(new Error("Request aborted"))),e.on("error",n=>r.destroy(n)),e.pipe(r),r}toURL(e,t){const s=e.socket instanceof z.TLSSocket?t?"wss":"https":t?"ws":"http",r=this.resolveAuthority(e);return new URL(e.url??"/",`${s}://${r}`)}resolveAuthority(e){const t=this.config.security?.trustHostHeader?this.normalizeTrustedHost(e.headers.host):null;if(t)return t;const s=this.server.address();return s&&typeof s=="object"?`${s.address.includes(":")?`[${s.address}]`:s.address}:${s.port}`:e.socket.localPort?`127.0.0.1:${e.socket.localPort}`:"localhost"}normalizeTrustedHost(e){if(!e)return null;let t;try{t=new URL(`http://${e}`)}catch{return null}if(t.username||t.password||t.pathname!=="/"||t.search||t.hash)return null;const s=t.port?`${t.hostname}:${t.port}`:t.hostname,r=this.config.security?.allowedHosts;return!r||this.matchesValue(s,r)?s:null}matchesValue(e,t){return(Array.isArray(t)?t:[t]).some(r=>typeof r=="string"?r===e:r instanceof RegExp?r.test(e):r(e))}toHeaders(e){const t=new Headers;for(const[s,r]of Object.entries(e))if(r!==void 0){if(Array.isArray(r)){const n=s.toLowerCase()==="cookie"?r.join("; "):r.join(", ");t.set(s,n);continue}t.set(s,r)}return t}isRequestBodyAllowed(e){const t=this.config.security?.maxRequestBodySize;if(!t)return!0;const s=e.headers["content-length"];if(!s)return!0;const r=Number.parseInt(Array.isArray(s)?s[0]:s,10);return Number.isFinite(r)&&r<=t}handleError(e){if(e instanceof _)return new Response(e.message,{status:e.status});if(W(e))return new Response(JSON.stringify({error:e.statusText||"Error",status:e.status}),{status:e.status,headers:{"Content-Type":"application/json"}});if(L(e)){const t=e.headers.get("Location")||"/";return new Response(null,{status:e.status,headers:{Location:t}})}return console.error("Unhandled error:",e),new Response("Internal Server Error",{status:500})}async sendWebResponse(e,t){if(e.statusCode=t.status,t.headers.forEach((n,o)=>{e.setHeader(o,n)}),e.hasHeader("Server")||e.setHeader("Server","WebHTTPServer"),!t.body||this.shouldOmitResponseBody(t,e.req?.method)){e.end();return}const s=t.body.getReader(),r=T.Writable.toWeb(e).getWriter();try{for(;;){const{done:n,value:o}=await s.read();if(n)break;await r.write(o)}}finally{await r.close().catch(()=>{})}}shouldOmitResponseBody(e,t){return t==="HEAD"?!0:e.status===204||e.status===205||e.status===304}isAllowedWebSocketOrigin(e){const t=e.headers.origin;if(!t)return!0;const s=this.config.security?.allowedWebSocketOrigins;return s?this.matchesValue(t,s):!0}toRequestEvent(e,t,s){const r=new ae(e,s.pushSetCookie),n=this.config,o={},i={name:"WebHTTPServer"},u=new Set,c={request:e,url:t,cookies:r,getClientAddress:s.getClientAddress,get locals(){return o},get platform(){return i},params:{},route:{id:""},setHeaders:l=>{for(const[d,h]of Object.entries(l)){const f=d.toLowerCase();if(f==="set-cookie")throw new TypeError("Use event.cookies for Set-Cookie headers");if(u.has(f))throw new TypeError(`Header "${d}" has already been set`);u.add(f),s.setHeader(d,h)}}};return n.locals&&Object.assign(o,n.locals(c)),n.platform&&Object.assign(i,n.platform(c)),c}}const ce=async(a,e)=>{const t=JSON.stringify(await a);return new Response(t,{...e,headers:{"content-type":"application/json","content-length":Buffer.byteLength(t).toString(),...e?.headers}})},ue=async(a,e)=>{const t=await a;return new Response(t,{...e,headers:{"content-type":"text/plain","content-length":Buffer.byteLength(t).toString(),...e?.headers}})},le=async(a,e)=>{const t=await a;return new Response(t,{...e,headers:{"content-type":"text/html","content-length":Buffer.byteLength(t).toString(),...e?.headers}})},de=(a,e={})=>t=>N(a,t,e);exports.Action=R;exports.CORS=oe;exports.RateLimiter=Z;exports.RequestMethods=k;exports.Router=H;exports.WebServer=ie;exports.dir=de;exports.html=le;exports.isHttpError=W;exports.isRedirect=L;exports.json=ce;exports.serveStatic=N;exports.text=ue;
|
|
2
2
|
//# sourceMappingURL=index.cjs.js.map
|