vafast 0.1.17 → 0.2.3
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 +167 -185
- package/dist/auth/token.d.ts +39 -2
- package/dist/auth/token.js +124 -0
- package/dist/defineRoute.js +3 -0
- package/dist/index.d.ts +3 -4
- package/dist/index.js +13 -323
- package/dist/middleware/auth.d.ts +6 -0
- package/dist/middleware/auth.js +106 -0
- package/dist/middleware/authMiddleware.js +13 -0
- package/dist/middleware/component-renderer.d.ts +6 -0
- package/dist/middleware/component-renderer.js +132 -0
- package/dist/middleware/component-router.d.ts +10 -0
- package/dist/middleware/component-router.js +42 -0
- package/dist/middleware/cors.js +30 -0
- package/dist/middleware/rateLimit.js +33 -0
- package/dist/middleware.d.ts +1 -1
- package/dist/middleware.js +56 -0
- package/dist/monitoring/index.d.ts +29 -0
- package/dist/monitoring/index.js +24 -0
- package/dist/monitoring/native-monitor.d.ts +38 -0
- package/dist/monitoring/native-monitor.js +176 -0
- package/dist/monitoring/types.d.ts +146 -0
- package/dist/monitoring/types.js +8 -0
- package/dist/router/index.d.ts +5 -0
- package/dist/router/index.js +7 -0
- package/dist/router/radix-tree.d.ts +51 -0
- package/dist/router/radix-tree.js +186 -0
- package/dist/router.d.ts +43 -6
- package/dist/router.js +86 -0
- package/dist/server/base-server.d.ts +34 -0
- package/dist/server/base-server.js +145 -0
- package/dist/server/component-server.d.ts +32 -0
- package/dist/server/component-server.js +146 -0
- package/dist/server/index.d.ts +7 -0
- package/dist/server/index.js +11 -0
- package/dist/server/server-factory.d.ts +42 -0
- package/dist/server/server-factory.js +70 -0
- package/dist/server/server.d.ts +35 -0
- package/dist/server/server.js +97 -0
- package/dist/types/component-route.d.ts +25 -0
- package/dist/types/component-route.js +1 -0
- package/dist/types/index.d.ts +5 -0
- package/dist/types/index.js +4 -0
- package/dist/types/route.d.ts +39 -0
- package/dist/types/route.js +11 -0
- package/dist/types/schema.d.ts +75 -0
- package/dist/types/schema.js +10 -0
- package/dist/types/types.d.ts +22 -0
- package/dist/types/types.js +1 -0
- package/dist/utils/base64url.js +11 -0
- package/dist/utils/create-handler.d.ts +74 -0
- package/dist/utils/create-handler.js +234 -0
- package/dist/utils/dependency-manager.d.ts +23 -0
- package/dist/utils/dependency-manager.js +73 -0
- package/dist/utils/go-await.d.ts +26 -0
- package/dist/utils/go-await.js +30 -0
- package/dist/{cookie.d.ts → utils/handle.d.ts} +3 -0
- package/dist/utils/handle.js +29 -0
- package/dist/utils/html-renderer.d.ts +18 -0
- package/dist/utils/html-renderer.js +64 -0
- package/dist/utils/index.d.ts +12 -0
- package/dist/utils/index.js +21 -0
- package/dist/utils/parsers.d.ts +36 -0
- package/dist/utils/parsers.js +126 -0
- package/dist/utils/path-matcher.d.ts +23 -0
- package/dist/utils/path-matcher.js +83 -0
- package/dist/utils/request-validator.d.ts +63 -0
- package/dist/utils/request-validator.js +94 -0
- package/dist/utils/response.d.ts +17 -0
- package/dist/utils/response.js +110 -0
- package/dist/utils/validators/schema-validator.d.ts +66 -0
- package/dist/utils/validators/schema-validator.js +222 -0
- package/dist/utils/validators/schema-validators-ultra.d.ts +51 -0
- package/dist/utils/validators/schema-validators-ultra.js +289 -0
- package/dist/utils/validators/validators.d.ts +30 -0
- package/dist/utils/validators/validators.js +54 -0
- package/package.json +50 -14
- package/dist/server.d.ts +0 -9
- package/dist/types.d.ts +0 -9
- package/dist/util.d.ts +0 -7
package/dist/index.js
CHANGED
|
@@ -1,323 +1,13 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
if (pat.startsWith(":")) {
|
|
15
|
-
if (!part)
|
|
16
|
-
return { matched: false, params: {} };
|
|
17
|
-
params[pat.slice(1)] = part;
|
|
18
|
-
continue;
|
|
19
|
-
}
|
|
20
|
-
if (pat !== part)
|
|
21
|
-
return { matched: false, params: {} };
|
|
22
|
-
}
|
|
23
|
-
if (patternParts.length !== pathParts.length)
|
|
24
|
-
return { matched: false, params: {} };
|
|
25
|
-
return { matched: true, params };
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// src/util.ts
|
|
29
|
-
function json(data, status = 200, headers = {}) {
|
|
30
|
-
const h = new Headers({
|
|
31
|
-
"Content-Type": "application/json",
|
|
32
|
-
...headers
|
|
33
|
-
});
|
|
34
|
-
return new Response(JSON.stringify(data), {
|
|
35
|
-
status,
|
|
36
|
-
headers: h
|
|
37
|
-
});
|
|
38
|
-
}
|
|
39
|
-
function redirect(location, status = 302) {
|
|
40
|
-
return new Response(null, {
|
|
41
|
-
status,
|
|
42
|
-
headers: {
|
|
43
|
-
Location: location
|
|
44
|
-
}
|
|
45
|
-
});
|
|
46
|
-
}
|
|
47
|
-
function parseQuery(req) {
|
|
48
|
-
return new URL(req.url).searchParams;
|
|
49
|
-
}
|
|
50
|
-
async function parseBody(req) {
|
|
51
|
-
const contentType = req.headers.get("content-type") || "";
|
|
52
|
-
if (contentType.includes("application/json")) {
|
|
53
|
-
return await req.json();
|
|
54
|
-
}
|
|
55
|
-
if (contentType.includes("application/x-www-form-urlencoded")) {
|
|
56
|
-
const text = await req.text();
|
|
57
|
-
return Object.fromEntries(new URLSearchParams(text));
|
|
58
|
-
}
|
|
59
|
-
return await req.text();
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// src/middleware.ts
|
|
63
|
-
class VafastError extends Error {
|
|
64
|
-
status;
|
|
65
|
-
type;
|
|
66
|
-
expose;
|
|
67
|
-
constructor(message, options = {}) {
|
|
68
|
-
super(message);
|
|
69
|
-
this.name = "VafastError";
|
|
70
|
-
this.status = options.status ?? 500;
|
|
71
|
-
this.type = options.type ?? "internal_error";
|
|
72
|
-
this.expose = options.expose ?? false;
|
|
73
|
-
if (options.cause)
|
|
74
|
-
this.cause = options.cause;
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
function composeMiddleware(middleware, finalHandler) {
|
|
78
|
-
const all = [errorHandler, ...middleware];
|
|
79
|
-
return function composedHandler(req) {
|
|
80
|
-
let i = -1;
|
|
81
|
-
const dispatch = (index) => {
|
|
82
|
-
if (index <= i)
|
|
83
|
-
return Promise.reject(new Error("next() called multiple times"));
|
|
84
|
-
i = index;
|
|
85
|
-
const fn = index < all.length ? all[index] : finalHandler;
|
|
86
|
-
return Promise.resolve(fn(req, () => dispatch(index + 1)));
|
|
87
|
-
};
|
|
88
|
-
return dispatch(0);
|
|
89
|
-
};
|
|
90
|
-
}
|
|
91
|
-
var errorHandler = async (req, next) => {
|
|
92
|
-
try {
|
|
93
|
-
return await next();
|
|
94
|
-
} catch (err) {
|
|
95
|
-
console.error("\u672A\u5904\u7406\u7684\u9519\u8BEF:", err);
|
|
96
|
-
if (err instanceof VafastError) {
|
|
97
|
-
return json({
|
|
98
|
-
error: err.type,
|
|
99
|
-
message: err.expose ? err.message : "\u53D1\u751F\u4E86\u4E00\u4E2A\u9519\u8BEF"
|
|
100
|
-
}, err.status);
|
|
101
|
-
}
|
|
102
|
-
return json({ error: "internal_error", message: "\u51FA\u73B0\u4E86\u4E00\u4E9B\u95EE\u9898" }, 500);
|
|
103
|
-
}
|
|
104
|
-
};
|
|
105
|
-
|
|
106
|
-
// src/server.ts
|
|
107
|
-
class Server {
|
|
108
|
-
routes;
|
|
109
|
-
globalMiddleware = [];
|
|
110
|
-
constructor(routes) {
|
|
111
|
-
this.routes = routes;
|
|
112
|
-
}
|
|
113
|
-
use(mw) {
|
|
114
|
-
this.globalMiddleware.push(mw);
|
|
115
|
-
}
|
|
116
|
-
fetch = async (req) => {
|
|
117
|
-
const { pathname } = new URL(req.url);
|
|
118
|
-
const method = req.method;
|
|
119
|
-
let matched;
|
|
120
|
-
let params = {};
|
|
121
|
-
for (const route of this.routes) {
|
|
122
|
-
if (route.method !== method)
|
|
123
|
-
continue;
|
|
124
|
-
const result = matchPath(route.path, pathname);
|
|
125
|
-
if (result.matched) {
|
|
126
|
-
matched = route;
|
|
127
|
-
params = result.params;
|
|
128
|
-
break;
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
const handler = async (req2) => {
|
|
132
|
-
if (matched) {
|
|
133
|
-
req2.params = params;
|
|
134
|
-
return await matched.handler(req2, params);
|
|
135
|
-
} else {
|
|
136
|
-
return new Response("Not Found", { status: 404 });
|
|
137
|
-
}
|
|
138
|
-
};
|
|
139
|
-
const middlewareChain = matched?.middleware ? [...this.globalMiddleware, ...matched.middleware] : this.globalMiddleware;
|
|
140
|
-
const composedHandler = composeMiddleware(middlewareChain, handler);
|
|
141
|
-
return await composedHandler(req);
|
|
142
|
-
};
|
|
143
|
-
}
|
|
144
|
-
// src/cookie.ts
|
|
145
|
-
function getCookie(req, key) {
|
|
146
|
-
const cookie = req.headers.get("cookie");
|
|
147
|
-
console.log("[Vafast] \u63A5\u6536\u5230\u7684 Cookie:", cookie);
|
|
148
|
-
if (!cookie)
|
|
149
|
-
return null;
|
|
150
|
-
const pairs = cookie.split(";").map((c) => c.trim().split("="));
|
|
151
|
-
for (const [k, v] of pairs) {
|
|
152
|
-
if (k === key) {
|
|
153
|
-
console.log(`[Vafast] \u5339\u914D\u7684 Cookie: ${k}=${v}`);
|
|
154
|
-
return decodeURIComponent(v);
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
console.log("[Vafast] \u6CA1\u6709\u5339\u914D\u7684 cookie \u952E:", key);
|
|
158
|
-
return null;
|
|
159
|
-
}
|
|
160
|
-
function setCookie(key, value, options = {}) {
|
|
161
|
-
let cookie = `${key}=${encodeURIComponent(value)}`;
|
|
162
|
-
if (options.path)
|
|
163
|
-
cookie += `; Path=${options.path}`;
|
|
164
|
-
if (options.httpOnly)
|
|
165
|
-
cookie += `; HttpOnly`;
|
|
166
|
-
if (options.secure)
|
|
167
|
-
cookie += `; Secure`;
|
|
168
|
-
if (options.maxAge)
|
|
169
|
-
cookie += `; Max-Age=${options.maxAge}`;
|
|
170
|
-
return cookie;
|
|
171
|
-
}
|
|
172
|
-
// src/middleware/authMiddleware.ts
|
|
173
|
-
var requireAuth = async (req, next) => {
|
|
174
|
-
const token = getCookie(req, "auth");
|
|
175
|
-
if (!token || token !== "valid-token") {
|
|
176
|
-
throw new VafastError("Unauthorized", {
|
|
177
|
-
status: 401,
|
|
178
|
-
type: "unauthorized",
|
|
179
|
-
expose: true
|
|
180
|
-
});
|
|
181
|
-
}
|
|
182
|
-
return next();
|
|
183
|
-
};
|
|
184
|
-
// src/middleware/rateLimit.ts
|
|
185
|
-
var store = new Map;
|
|
186
|
-
function rateLimit(options = {}) {
|
|
187
|
-
const windowMs = options.windowMs ?? 60000;
|
|
188
|
-
const max = options.max ?? 30;
|
|
189
|
-
const keyFn = options.keyFn ?? getIP;
|
|
190
|
-
return async (req, next) => {
|
|
191
|
-
const key = keyFn(req);
|
|
192
|
-
const now = Date.now();
|
|
193
|
-
const entry = store.get(key);
|
|
194
|
-
if (entry && entry.expires > now) {
|
|
195
|
-
if (entry.count >= max) {
|
|
196
|
-
throw new VafastError("Too many requests", {
|
|
197
|
-
status: 429,
|
|
198
|
-
type: "rate_limit",
|
|
199
|
-
expose: true
|
|
200
|
-
});
|
|
201
|
-
}
|
|
202
|
-
entry.count += 1;
|
|
203
|
-
} else {
|
|
204
|
-
store.set(key, { count: 1, expires: now + windowMs });
|
|
205
|
-
}
|
|
206
|
-
return next();
|
|
207
|
-
};
|
|
208
|
-
}
|
|
209
|
-
function getIP(req) {
|
|
210
|
-
return req.headers.get("cf-connecting-ip") || req.headers.get("x-forwarded-for")?.split(",")[0]?.trim() || "unknown";
|
|
211
|
-
}
|
|
212
|
-
// src/middleware/cors.ts
|
|
213
|
-
function createCORS(options = {}) {
|
|
214
|
-
const {
|
|
215
|
-
origin = [],
|
|
216
|
-
methods = ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
|
|
217
|
-
headers = [],
|
|
218
|
-
credentials = false,
|
|
219
|
-
maxAge
|
|
220
|
-
} = options;
|
|
221
|
-
return async (req, next) => {
|
|
222
|
-
const reqOrigin = req.headers.get("Origin") || "";
|
|
223
|
-
const isAllowedOrigin = origin === "*" || origin.includes(reqOrigin);
|
|
224
|
-
if (req.method === "OPTIONS") {
|
|
225
|
-
const resHeaders = new Headers;
|
|
226
|
-
if (isAllowedOrigin) {
|
|
227
|
-
resHeaders.set("Access-Control-Allow-Origin", origin === "*" ? "*" : reqOrigin);
|
|
228
|
-
resHeaders.set("Access-Control-Allow-Methods", methods.join(","));
|
|
229
|
-
resHeaders.set("Access-Control-Allow-Headers", headers.join(","));
|
|
230
|
-
if (credentials)
|
|
231
|
-
resHeaders.set("Access-Control-Allow-Credentials", "true");
|
|
232
|
-
if (maxAge)
|
|
233
|
-
resHeaders.set("Access-Control-Max-Age", maxAge.toString());
|
|
234
|
-
}
|
|
235
|
-
return new Response(null, { status: 204, headers: resHeaders });
|
|
236
|
-
}
|
|
237
|
-
const res = await next();
|
|
238
|
-
if (isAllowedOrigin) {
|
|
239
|
-
res.headers.set("Access-Control-Allow-Origin", origin === "*" ? "*" : reqOrigin);
|
|
240
|
-
if (credentials)
|
|
241
|
-
res.headers.set("Access-Control-Allow-Credentials", "true");
|
|
242
|
-
}
|
|
243
|
-
return res;
|
|
244
|
-
};
|
|
245
|
-
}
|
|
246
|
-
// src/utils/base64url.ts
|
|
247
|
-
function base64urlEncode(str) {
|
|
248
|
-
return btoa(str).replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_");
|
|
249
|
-
}
|
|
250
|
-
function base64urlDecode(str) {
|
|
251
|
-
const pad = str.length % 4 === 0 ? "" : "=".repeat(4 - str.length % 4);
|
|
252
|
-
const base64 = str.replace(/-/g, "+").replace(/_/g, "/") + pad;
|
|
253
|
-
return atob(base64);
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
// src/auth/token.ts
|
|
257
|
-
var encoder = new TextEncoder;
|
|
258
|
-
async function sign(data, secret) {
|
|
259
|
-
const key = await crypto.subtle.importKey("raw", encoder.encode(secret), { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
|
|
260
|
-
const signature = await crypto.subtle.sign("HMAC", key, encoder.encode(data));
|
|
261
|
-
return btoa(String.fromCharCode(...new Uint8Array(signature)));
|
|
262
|
-
}
|
|
263
|
-
async function generateToken(payload, secret) {
|
|
264
|
-
const data = base64urlEncode(JSON.stringify(payload));
|
|
265
|
-
const sig = await sign(data, secret);
|
|
266
|
-
return `${data}.${base64urlEncode(sig)}`;
|
|
267
|
-
}
|
|
268
|
-
async function verifyToken(token, secret) {
|
|
269
|
-
const [data, sig] = token.split(".");
|
|
270
|
-
if (!data || !sig)
|
|
271
|
-
return null;
|
|
272
|
-
const expectedSig = await sign(data, secret);
|
|
273
|
-
const expected = base64urlEncode(expectedSig);
|
|
274
|
-
if (sig !== expected)
|
|
275
|
-
return null;
|
|
276
|
-
try {
|
|
277
|
-
return JSON.parse(base64urlDecode(data));
|
|
278
|
-
} catch {
|
|
279
|
-
return null;
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
// src/middleware/auth.ts
|
|
283
|
-
function createAuth(options) {
|
|
284
|
-
const { secret, cookieName = "auth", headerName = "authorization" } = options;
|
|
285
|
-
return async (req, next) => {
|
|
286
|
-
const token = getCookie(req, cookieName) || req.headers.get(headerName)?.replace("Bearer ", "") || "";
|
|
287
|
-
const user = await verifyToken(token, secret);
|
|
288
|
-
if (!user) {
|
|
289
|
-
throw new VafastError("Unauthorized", {
|
|
290
|
-
status: 401,
|
|
291
|
-
type: "unauthorized",
|
|
292
|
-
expose: true
|
|
293
|
-
});
|
|
294
|
-
}
|
|
295
|
-
req.user = user;
|
|
296
|
-
return next();
|
|
297
|
-
};
|
|
298
|
-
}
|
|
299
|
-
// src/defineRoute.ts
|
|
300
|
-
function defineRoutes(routes) {
|
|
301
|
-
return routes;
|
|
302
|
-
}
|
|
303
|
-
export {
|
|
304
|
-
verifyToken,
|
|
305
|
-
setCookie,
|
|
306
|
-
requireAuth,
|
|
307
|
-
redirect,
|
|
308
|
-
rateLimit,
|
|
309
|
-
parseQuery,
|
|
310
|
-
parseBody,
|
|
311
|
-
matchPath,
|
|
312
|
-
json,
|
|
313
|
-
getCookie,
|
|
314
|
-
generateToken,
|
|
315
|
-
defineRoutes,
|
|
316
|
-
createCORS,
|
|
317
|
-
createAuth,
|
|
318
|
-
composeMiddleware,
|
|
319
|
-
base64urlEncode,
|
|
320
|
-
base64urlDecode,
|
|
321
|
-
VafastError,
|
|
322
|
-
Server
|
|
323
|
-
};
|
|
1
|
+
export * from "./server";
|
|
2
|
+
export * from "./middleware";
|
|
3
|
+
export * from "./utils";
|
|
4
|
+
export * from "./router";
|
|
5
|
+
export * from "./middleware/authMiddleware";
|
|
6
|
+
export * from "./middleware/rateLimit";
|
|
7
|
+
export * from "./middleware/cors";
|
|
8
|
+
export * from "./auth/token";
|
|
9
|
+
export * from "./middleware/auth";
|
|
10
|
+
export * from "./defineRoute";
|
|
11
|
+
export * from "./types/index";
|
|
12
|
+
// 重新导出 Type 以便用户使用
|
|
13
|
+
export { Type } from "@sinclair/typebox";
|
|
@@ -3,6 +3,12 @@ interface AuthOptions {
|
|
|
3
3
|
secret: string;
|
|
4
4
|
cookieName?: string;
|
|
5
5
|
headerName?: string;
|
|
6
|
+
required?: boolean;
|
|
7
|
+
roles?: string[];
|
|
8
|
+
permissions?: string[];
|
|
6
9
|
}
|
|
7
10
|
export declare function createAuth(options: AuthOptions): Middleware;
|
|
11
|
+
export declare function createOptionalAuth(options: Omit<AuthOptions, "required">): Middleware;
|
|
12
|
+
export declare function createRoleAuth(roles: string[], options: Omit<AuthOptions, "roles">): Middleware;
|
|
13
|
+
export declare function createPermissionAuth(permissions: string[], options: Omit<AuthOptions, "permissions">): Middleware;
|
|
8
14
|
export {};
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
// src/middleware/auth.ts
|
|
2
|
+
import { VafastError } from "../middleware";
|
|
3
|
+
import { getCookie } from "../utils/handle";
|
|
4
|
+
import { verifyToken, TokenError } from "../auth/token";
|
|
5
|
+
export function createAuth(options) {
|
|
6
|
+
const { secret, cookieName = "auth", headerName = "authorization", required = true, roles = [], permissions = [], } = options;
|
|
7
|
+
return async (req, next) => {
|
|
8
|
+
const token = getCookie(req, cookieName) ||
|
|
9
|
+
req.headers.get(headerName)?.replace("Bearer ", "") ||
|
|
10
|
+
"";
|
|
11
|
+
if (!token && required) {
|
|
12
|
+
throw new VafastError("缺少认证令牌", {
|
|
13
|
+
status: 401,
|
|
14
|
+
type: "unauthorized",
|
|
15
|
+
expose: true,
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
if (!token && !required) {
|
|
19
|
+
return next();
|
|
20
|
+
}
|
|
21
|
+
try {
|
|
22
|
+
const user = (await verifyToken(token, secret));
|
|
23
|
+
if (!user) {
|
|
24
|
+
throw new VafastError("令牌验证失败", {
|
|
25
|
+
status: 401,
|
|
26
|
+
type: "unauthorized",
|
|
27
|
+
expose: true,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
// 检查角色权限
|
|
31
|
+
if (roles.length > 0 && user.role && !roles.includes(user.role)) {
|
|
32
|
+
throw new VafastError("权限不足", {
|
|
33
|
+
status: 403,
|
|
34
|
+
type: "forbidden",
|
|
35
|
+
expose: true,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
// 检查具体权限
|
|
39
|
+
if (permissions.length > 0 && user.permissions) {
|
|
40
|
+
const userPermissions = Array.isArray(user.permissions)
|
|
41
|
+
? user.permissions
|
|
42
|
+
: [user.permissions];
|
|
43
|
+
const hasPermission = permissions.some((permission) => userPermissions.includes(permission));
|
|
44
|
+
if (!hasPermission) {
|
|
45
|
+
throw new VafastError("权限不足", {
|
|
46
|
+
status: 403,
|
|
47
|
+
type: "forbidden",
|
|
48
|
+
expose: true,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
// 🪄 在这里扩展 Request 对象
|
|
53
|
+
req.user = user;
|
|
54
|
+
req.token = token;
|
|
55
|
+
return next();
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
if (error instanceof TokenError) {
|
|
59
|
+
let status = 401;
|
|
60
|
+
let message = "认证失败";
|
|
61
|
+
switch (error.code) {
|
|
62
|
+
case "EXPIRED_TOKEN":
|
|
63
|
+
status = 401;
|
|
64
|
+
message = "令牌已过期";
|
|
65
|
+
break;
|
|
66
|
+
case "INVALID_SIGNATURE":
|
|
67
|
+
status = 401;
|
|
68
|
+
message = "令牌签名无效";
|
|
69
|
+
break;
|
|
70
|
+
case "MALFORMED_TOKEN":
|
|
71
|
+
status = 400;
|
|
72
|
+
message = "令牌格式错误";
|
|
73
|
+
break;
|
|
74
|
+
default:
|
|
75
|
+
status = 401;
|
|
76
|
+
message = "令牌验证失败";
|
|
77
|
+
}
|
|
78
|
+
throw new VafastError(message, {
|
|
79
|
+
status,
|
|
80
|
+
type: "unauthorized",
|
|
81
|
+
expose: true,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
if (error instanceof VafastError) {
|
|
85
|
+
throw error;
|
|
86
|
+
}
|
|
87
|
+
throw new VafastError("认证过程中发生错误", {
|
|
88
|
+
status: 500,
|
|
89
|
+
type: "internal_error",
|
|
90
|
+
expose: false,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
// 可选认证中间件
|
|
96
|
+
export function createOptionalAuth(options) {
|
|
97
|
+
return createAuth({ ...options, required: false });
|
|
98
|
+
}
|
|
99
|
+
// 角色验证中间件
|
|
100
|
+
export function createRoleAuth(roles, options) {
|
|
101
|
+
return createAuth({ ...options, roles });
|
|
102
|
+
}
|
|
103
|
+
// 权限验证中间件
|
|
104
|
+
export function createPermissionAuth(permissions, options) {
|
|
105
|
+
return createAuth({ ...options, permissions });
|
|
106
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { VafastError } from "../middleware";
|
|
2
|
+
import { getCookie } from "../utils/handle";
|
|
3
|
+
export const requireAuth = async (req, next) => {
|
|
4
|
+
const token = getCookie(req, "auth");
|
|
5
|
+
if (!token || token !== "valid-token") {
|
|
6
|
+
throw new VafastError("Unauthorized", {
|
|
7
|
+
status: 401,
|
|
8
|
+
type: "unauthorized",
|
|
9
|
+
expose: true,
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
return next();
|
|
13
|
+
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 组件渲染中间件 - 专注 SSR
|
|
3
|
+
* 支持 Vue 和 React 的服务端渲染
|
|
4
|
+
*/
|
|
5
|
+
export declare const vueRenderer: (preloadedDeps?: any) => (req: Request, next: () => Promise<Response>) => Promise<Response>;
|
|
6
|
+
export declare const reactRenderer: (preloadedDeps?: any) => (req: Request, next: () => Promise<Response>) => Promise<Response>;
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 组件渲染中间件 - 专注 SSR
|
|
3
|
+
* 支持 Vue 和 React 的服务端渲染
|
|
4
|
+
*/
|
|
5
|
+
// Vue SSR 渲染
|
|
6
|
+
const renderVueSSR = async (componentImport, req, preloadedDeps) => {
|
|
7
|
+
try {
|
|
8
|
+
// 使用预加载的依赖或动态导入
|
|
9
|
+
const { createSSRApp, renderToString } = preloadedDeps ||
|
|
10
|
+
(await Promise.all([import("vue"), import("@vue/server-renderer")]).then(([vue, renderer]) => ({
|
|
11
|
+
createSSRApp: vue.createSSRApp,
|
|
12
|
+
renderToString: renderer.renderToString,
|
|
13
|
+
})));
|
|
14
|
+
const componentModule = await componentImport();
|
|
15
|
+
const component = componentModule.default || componentModule;
|
|
16
|
+
const app = createSSRApp(component);
|
|
17
|
+
// 提供路由信息
|
|
18
|
+
app.provide("routeInfo", {
|
|
19
|
+
params: req.params || {},
|
|
20
|
+
query: Object.fromEntries(new URL(req.url).searchParams),
|
|
21
|
+
pathname: new URL(req.url).pathname,
|
|
22
|
+
});
|
|
23
|
+
const html = await renderToString(app);
|
|
24
|
+
return new Response(`
|
|
25
|
+
<!doctype html>
|
|
26
|
+
<html>
|
|
27
|
+
<head>
|
|
28
|
+
<meta charset="utf-8">
|
|
29
|
+
<title>Vue SSR App</title>
|
|
30
|
+
</head>
|
|
31
|
+
<body>
|
|
32
|
+
<div id="app">${html}</div>
|
|
33
|
+
<script>
|
|
34
|
+
window.__ROUTE_INFO__ = {
|
|
35
|
+
params: ${JSON.stringify(req.params || {})},
|
|
36
|
+
query: ${JSON.stringify(Object.fromEntries(new URL(req.url).searchParams))},
|
|
37
|
+
pathname: '${new URL(req.url).pathname}'
|
|
38
|
+
};
|
|
39
|
+
</script>
|
|
40
|
+
<script type="module" src="/client.js"></script>
|
|
41
|
+
</body>
|
|
42
|
+
</html>
|
|
43
|
+
`, {
|
|
44
|
+
headers: { "Content-Type": "text/html; charset=utf-8" },
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
console.error("Vue SSR 渲染失败:", error);
|
|
49
|
+
return new Response(`
|
|
50
|
+
<!doctype html>
|
|
51
|
+
<html>
|
|
52
|
+
<head><title>渲染错误</title></head>
|
|
53
|
+
<body>
|
|
54
|
+
<h1>Vue SSR 渲染失败</h1>
|
|
55
|
+
<p>错误信息: ${error instanceof Error ? error.message : "未知错误"}</p>
|
|
56
|
+
</body>
|
|
57
|
+
</html>
|
|
58
|
+
`, { status: 500 });
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
// React SSR 渲染
|
|
62
|
+
const renderReactSSR = async (componentImport, req, preloadedDeps) => {
|
|
63
|
+
try {
|
|
64
|
+
// 使用预加载的依赖或动态导入
|
|
65
|
+
const { createElement, renderToString } = preloadedDeps ||
|
|
66
|
+
(await Promise.all([import("react"), import("react-dom/server")]).then(([react, renderer]) => ({
|
|
67
|
+
createElement: react.createElement,
|
|
68
|
+
renderToString: renderer.renderToString,
|
|
69
|
+
})));
|
|
70
|
+
const componentModule = await componentImport();
|
|
71
|
+
const Component = componentModule.default || componentModule;
|
|
72
|
+
const content = createElement(Component, {
|
|
73
|
+
req,
|
|
74
|
+
params: req.params || {},
|
|
75
|
+
query: Object.fromEntries(new URL(req.url).searchParams),
|
|
76
|
+
});
|
|
77
|
+
const html = renderToString(content);
|
|
78
|
+
return new Response(`
|
|
79
|
+
<!doctype html>
|
|
80
|
+
<html>
|
|
81
|
+
<head>
|
|
82
|
+
<meta charset="utf-8">
|
|
83
|
+
<title>React SSR App</title>
|
|
84
|
+
</head>
|
|
85
|
+
<body>
|
|
86
|
+
<div id="root">${html}</div>
|
|
87
|
+
<script>
|
|
88
|
+
window.__ROUTE_INFO__ = {
|
|
89
|
+
params: ${JSON.stringify(req.params || {})},
|
|
90
|
+
query: ${JSON.stringify(Object.fromEntries(new URL(req.url).searchParams))},
|
|
91
|
+
pathname: '${new URL(req.url).pathname}'
|
|
92
|
+
};
|
|
93
|
+
</script>
|
|
94
|
+
<script type="module" src="/client.js"></script>
|
|
95
|
+
</body>
|
|
96
|
+
</html>
|
|
97
|
+
`, {
|
|
98
|
+
headers: { "Content-Type": "text/html; charset=utf-8" },
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
console.error("React SSR 渲染失败:", error);
|
|
103
|
+
return new Response(`
|
|
104
|
+
<!doctype html>
|
|
105
|
+
<html>
|
|
106
|
+
<head><title>渲染错误</title></head>
|
|
107
|
+
<body>
|
|
108
|
+
<h1>React SSR 渲染失败</h1>
|
|
109
|
+
<p>错误信息: ${error instanceof Error ? error.message : "未知错误"}</p>
|
|
110
|
+
</body>
|
|
111
|
+
</html>
|
|
112
|
+
`, { status: 500 });
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
// Vue 组件渲染器 - 专注 SSR
|
|
116
|
+
export const vueRenderer = (preloadedDeps) => {
|
|
117
|
+
return async (req, next) => {
|
|
118
|
+
req.renderVue = async (componentImport) => {
|
|
119
|
+
return await renderVueSSR(componentImport, req, preloadedDeps);
|
|
120
|
+
};
|
|
121
|
+
return next();
|
|
122
|
+
};
|
|
123
|
+
};
|
|
124
|
+
// React 组件渲染器 - 专注 SSR
|
|
125
|
+
export const reactRenderer = (preloadedDeps) => {
|
|
126
|
+
return async (req, next) => {
|
|
127
|
+
req.renderReact = async (componentImport) => {
|
|
128
|
+
return await renderReactSSR(componentImport, req, preloadedDeps);
|
|
129
|
+
};
|
|
130
|
+
return next();
|
|
131
|
+
};
|
|
132
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ComponentRoute, NestedComponentRoute, FlattenedComponentRoute } from "../types/component-route";
|
|
2
|
+
/**
|
|
3
|
+
* 扁平化嵌套组件路由
|
|
4
|
+
*/
|
|
5
|
+
export declare function flattenComponentRoutes(routes: (ComponentRoute | NestedComponentRoute)[]): FlattenedComponentRoute[];
|
|
6
|
+
/**
|
|
7
|
+
* 组件路由处理器中间件
|
|
8
|
+
* 自动检测组件类型并应用相应的渲染器
|
|
9
|
+
*/
|
|
10
|
+
export declare const componentRouter: () => (req: Request, next: () => Promise<Response>) => Promise<Response>;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 扁平化嵌套组件路由
|
|
3
|
+
*/
|
|
4
|
+
export function flattenComponentRoutes(routes) {
|
|
5
|
+
const flattened = [];
|
|
6
|
+
function processRoute(route, parentPath = "", parentMiddleware = []) {
|
|
7
|
+
const currentPath = parentPath + route.path;
|
|
8
|
+
const currentMiddleware = [
|
|
9
|
+
...parentMiddleware,
|
|
10
|
+
...(route.middleware || []),
|
|
11
|
+
];
|
|
12
|
+
if ("component" in route) {
|
|
13
|
+
// 这是一个组件路由
|
|
14
|
+
flattened.push({
|
|
15
|
+
...route,
|
|
16
|
+
fullPath: currentPath,
|
|
17
|
+
middlewareChain: currentMiddleware,
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
else if ("children" in route && route.children) {
|
|
21
|
+
// 这是一个嵌套路由
|
|
22
|
+
for (const child of route.children) {
|
|
23
|
+
processRoute(child, currentPath, currentMiddleware);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
for (const route of routes) {
|
|
28
|
+
processRoute(route);
|
|
29
|
+
}
|
|
30
|
+
return flattened;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* 组件路由处理器中间件
|
|
34
|
+
* 自动检测组件类型并应用相应的渲染器
|
|
35
|
+
*/
|
|
36
|
+
export const componentRouter = () => {
|
|
37
|
+
return async (req, next) => {
|
|
38
|
+
// 这里可以添加组件路由的自动处理逻辑
|
|
39
|
+
// 比如自动检测组件类型,应用相应的渲染器
|
|
40
|
+
return next();
|
|
41
|
+
};
|
|
42
|
+
};
|