bxo 0.0.1 → 0.0.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 +570 -6
- package/example.ts +70 -24
- package/index.ts +200 -48
- package/package.json +1 -1
- package/plugins/auth.ts +68 -65
- package/plugins/cors.ts +44 -40
- package/plugins/index.ts +5 -7
- package/plugins/logger.ts +88 -83
- package/plugins/ratelimit.ts +61 -57
package/example.ts
CHANGED
@@ -4,31 +4,46 @@ import { cors, logger, auth, rateLimit, createJWT } from './plugins';
|
|
4
4
|
// Create the app instance
|
5
5
|
const app = new BXO();
|
6
6
|
|
7
|
+
// Enable hot reload
|
8
|
+
app.enableHotReload(['./']); // Watch current directory
|
9
|
+
|
7
10
|
// Add plugins
|
8
11
|
app
|
9
12
|
.use(logger({ format: 'simple' }))
|
10
|
-
.use(cors({
|
13
|
+
.use(cors({
|
11
14
|
origin: ['http://localhost:3000', 'https://example.com'],
|
12
|
-
credentials: true
|
15
|
+
credentials: true
|
13
16
|
}))
|
14
|
-
.use(rateLimit({
|
15
|
-
max: 100,
|
17
|
+
.use(rateLimit({
|
18
|
+
max: 100,
|
16
19
|
window: 60, // 1 minute
|
17
|
-
exclude: ['/health']
|
20
|
+
exclude: ['/health']
|
18
21
|
}))
|
19
|
-
.use(auth({
|
20
|
-
type: 'jwt',
|
22
|
+
.use(auth({
|
23
|
+
type: 'jwt',
|
21
24
|
secret: 'your-secret-key',
|
22
25
|
exclude: ['/', '/login', '/health']
|
23
26
|
}));
|
24
27
|
|
25
|
-
// Add lifecycle hooks
|
28
|
+
// Add simplified lifecycle hooks
|
26
29
|
app
|
27
|
-
.
|
28
|
-
console.log('
|
30
|
+
.onBeforeStart(() => {
|
31
|
+
console.log('🔧 Preparing to start server...');
|
32
|
+
})
|
33
|
+
.onAfterStart(() => {
|
34
|
+
console.log('✅ Server fully started and ready!');
|
35
|
+
})
|
36
|
+
.onBeforeStop(() => {
|
37
|
+
console.log('🔧 Preparing to stop server...');
|
38
|
+
})
|
39
|
+
.onAfterStop(() => {
|
40
|
+
console.log('✅ Server fully stopped!');
|
41
|
+
})
|
42
|
+
.onBeforeRestart(() => {
|
43
|
+
console.log('🔧 Preparing to restart server...');
|
29
44
|
})
|
30
|
-
.
|
31
|
-
console.log('
|
45
|
+
.onAfterRestart(() => {
|
46
|
+
console.log('✅ Server restart completed!');
|
32
47
|
})
|
33
48
|
.onRequest((ctx) => {
|
34
49
|
console.log(`📨 Processing ${ctx.request.method} ${ctx.request.url}`);
|
@@ -48,7 +63,7 @@ app
|
|
48
63
|
.get('/simple', async (ctx) => {
|
49
64
|
return { message: 'Hello World' };
|
50
65
|
})
|
51
|
-
|
66
|
+
|
52
67
|
// Three arguments: path, handler, config
|
53
68
|
.get('/users/:id', async (ctx) => {
|
54
69
|
// ctx.params.id is fully typed as string (UUID)
|
@@ -58,7 +73,7 @@ app
|
|
58
73
|
params: z.object({ id: z.string().uuid() }),
|
59
74
|
query: z.object({ include: z.string().optional() })
|
60
75
|
})
|
61
|
-
|
76
|
+
|
62
77
|
.post('/users', async (ctx) => {
|
63
78
|
// ctx.body is fully typed with name: string, email: string
|
64
79
|
return { created: ctx.body };
|
@@ -71,18 +86,22 @@ app
|
|
71
86
|
|
72
87
|
// Additional examples
|
73
88
|
.get('/health', async (ctx) => {
|
74
|
-
return {
|
89
|
+
return {
|
90
|
+
status: 'ok',
|
91
|
+
timestamp: new Date().toISOString(),
|
92
|
+
server: app.getServerInfo()
|
93
|
+
};
|
75
94
|
})
|
76
95
|
|
77
96
|
.post('/login', async (ctx) => {
|
78
97
|
const { username, password } = ctx.body;
|
79
|
-
|
98
|
+
|
80
99
|
// Simple auth check (in production, verify against database)
|
81
100
|
if (username === 'admin' && password === 'password') {
|
82
101
|
const token = createJWT({ username, role: 'admin' }, 'your-secret-key', 3600);
|
83
102
|
return { token, user: { username, role: 'admin' } };
|
84
103
|
}
|
85
|
-
|
104
|
+
|
86
105
|
ctx.set.status = 401;
|
87
106
|
return { error: 'Invalid credentials' };
|
88
107
|
}, {
|
@@ -97,9 +116,24 @@ app
|
|
97
116
|
return { message: 'This is protected', user: ctx.user };
|
98
117
|
})
|
99
118
|
|
100
|
-
|
119
|
+
// Server control endpoints
|
120
|
+
.post('/restart', async (ctx) => {
|
121
|
+
// Restart the server
|
122
|
+
setTimeout(() => app.restart(3000), 100);
|
123
|
+
return { message: 'Server restart initiated' };
|
124
|
+
})
|
125
|
+
|
126
|
+
.get('/status', async (ctx) => {
|
101
127
|
return {
|
102
|
-
|
128
|
+
...app.getServerInfo(),
|
129
|
+
uptime: process.uptime(),
|
130
|
+
memory: process.memoryUsage()
|
131
|
+
};
|
132
|
+
})
|
133
|
+
|
134
|
+
.put('/users/:id', async (ctx) => {
|
135
|
+
return {
|
136
|
+
updated: ctx.body,
|
103
137
|
id: ctx.params.id,
|
104
138
|
version: ctx.headers['if-match']
|
105
139
|
};
|
@@ -121,17 +155,29 @@ app
|
|
121
155
|
params: z.object({ id: z.string().uuid() })
|
122
156
|
});
|
123
157
|
|
124
|
-
// Start the server
|
125
|
-
app.
|
158
|
+
// Start the server (with hot reload enabled)
|
159
|
+
app.start(3000, 'localhost');
|
126
160
|
|
127
161
|
console.log(`
|
128
|
-
🦊 BXO Framework
|
162
|
+
🦊 BXO Framework with Hot Reload
|
129
163
|
|
130
|
-
|
164
|
+
✨ Features Enabled:
|
165
|
+
- 🔄 Hot reload (edit any .ts/.js file to restart)
|
166
|
+
- 🎣 Full lifecycle hooks (before/after pattern)
|
167
|
+
- 🔒 JWT authentication
|
168
|
+
- 📊 Rate limiting
|
169
|
+
- 🌐 CORS support
|
170
|
+
- 📝 Request logging
|
171
|
+
|
172
|
+
🧪 Try these endpoints:
|
131
173
|
- GET /simple
|
132
174
|
- GET /users/123e4567-e89b-12d3-a456-426614174000?include=profile
|
133
175
|
- POST /users (with JSON body: {"name": "John", "email": "john@example.com"})
|
134
|
-
- GET /health
|
176
|
+
- GET /health (shows server info)
|
135
177
|
- POST /login (with JSON body: {"username": "admin", "password": "password"})
|
136
178
|
- GET /protected (requires Bearer token from /login)
|
179
|
+
- GET /status (server statistics)
|
180
|
+
- POST /restart (restart server programmatically)
|
181
|
+
|
182
|
+
💡 Edit this file and save to see hot reload in action!
|
137
183
|
`);
|
package/index.ts
CHANGED
@@ -30,13 +30,7 @@ export type Context<TConfig extends RouteConfig = {}> = {
|
|
30
30
|
// Handler function type
|
31
31
|
type Handler<TConfig extends RouteConfig = {}> = (ctx: Context<TConfig>) => Promise<any> | any;
|
32
32
|
|
33
|
-
// Plugin interface
|
34
|
-
interface Plugin {
|
35
|
-
name?: string;
|
36
|
-
onRequest?: (ctx: Context) => Promise<void> | void;
|
37
|
-
onResponse?: (ctx: Context, response: any) => Promise<any> | any;
|
38
|
-
onError?: (ctx: Context, error: Error) => Promise<any> | any;
|
39
|
-
}
|
33
|
+
// Note: Plugin interface moved to plugins/index.ts as PluginFactory type
|
40
34
|
|
41
35
|
|
42
36
|
|
@@ -50,8 +44,12 @@ interface Route {
|
|
50
44
|
|
51
45
|
// Lifecycle hooks
|
52
46
|
interface LifecycleHooks {
|
53
|
-
|
54
|
-
|
47
|
+
onBeforeStart?: () => Promise<void> | void;
|
48
|
+
onAfterStart?: () => Promise<void> | void;
|
49
|
+
onBeforeStop?: () => Promise<void> | void;
|
50
|
+
onAfterStop?: () => Promise<void> | void;
|
51
|
+
onBeforeRestart?: () => Promise<void> | void;
|
52
|
+
onAfterRestart?: () => Promise<void> | void;
|
55
53
|
onRequest?: (ctx: Context) => Promise<void> | void;
|
56
54
|
onResponse?: (ctx: Context, response: any) => Promise<any> | any;
|
57
55
|
onError?: (ctx: Context, error: Error) => Promise<any> | any;
|
@@ -59,19 +57,43 @@ interface LifecycleHooks {
|
|
59
57
|
|
60
58
|
export default class BXO {
|
61
59
|
private routes: Route[] = [];
|
62
|
-
private plugins:
|
60
|
+
private plugins: BXO[] = [];
|
63
61
|
private hooks: LifecycleHooks = {};
|
62
|
+
private server?: any;
|
63
|
+
private isRunning: boolean = false;
|
64
|
+
private hotReloadEnabled: boolean = false;
|
65
|
+
private watchedFiles: Set<string> = new Set();
|
64
66
|
|
65
|
-
constructor() {}
|
67
|
+
constructor() { }
|
66
68
|
|
67
69
|
// Lifecycle hook methods
|
68
|
-
|
69
|
-
this.hooks.
|
70
|
+
onBeforeStart(handler: () => Promise<void> | void): this {
|
71
|
+
this.hooks.onBeforeStart = handler;
|
72
|
+
return this;
|
73
|
+
}
|
74
|
+
|
75
|
+
onAfterStart(handler: () => Promise<void> | void): this {
|
76
|
+
this.hooks.onAfterStart = handler;
|
77
|
+
return this;
|
78
|
+
}
|
79
|
+
|
80
|
+
onBeforeStop(handler: () => Promise<void> | void): this {
|
81
|
+
this.hooks.onBeforeStop = handler;
|
70
82
|
return this;
|
71
83
|
}
|
72
84
|
|
73
|
-
|
74
|
-
this.hooks.
|
85
|
+
onAfterStop(handler: () => Promise<void> | void): this {
|
86
|
+
this.hooks.onAfterStop = handler;
|
87
|
+
return this;
|
88
|
+
}
|
89
|
+
|
90
|
+
onBeforeRestart(handler: () => Promise<void> | void): this {
|
91
|
+
this.hooks.onBeforeRestart = handler;
|
92
|
+
return this;
|
93
|
+
}
|
94
|
+
|
95
|
+
onAfterRestart(handler: () => Promise<void> | void): this {
|
96
|
+
this.hooks.onAfterRestart = handler;
|
75
97
|
return this;
|
76
98
|
}
|
77
99
|
|
@@ -90,9 +112,9 @@ export default class BXO {
|
|
90
112
|
return this;
|
91
113
|
}
|
92
114
|
|
93
|
-
// Plugin system
|
94
|
-
use(
|
95
|
-
this.plugins.push(
|
115
|
+
// Plugin system - now accepts other BXO instances
|
116
|
+
use(bxoInstance: BXO): this {
|
117
|
+
this.plugins.push(bxoInstance);
|
96
118
|
return this;
|
97
119
|
}
|
98
120
|
|
@@ -298,10 +320,10 @@ export default class BXO {
|
|
298
320
|
await this.hooks.onRequest(ctx);
|
299
321
|
}
|
300
322
|
|
301
|
-
// Run
|
302
|
-
for (const
|
303
|
-
if (
|
304
|
-
await
|
323
|
+
// Run BXO instance onRequest hooks
|
324
|
+
for (const bxoInstance of this.plugins) {
|
325
|
+
if (bxoInstance.hooks.onRequest) {
|
326
|
+
await bxoInstance.hooks.onRequest(ctx);
|
305
327
|
}
|
306
328
|
}
|
307
329
|
|
@@ -313,10 +335,10 @@ export default class BXO {
|
|
313
335
|
response = await this.hooks.onResponse(ctx, response) || response;
|
314
336
|
}
|
315
337
|
|
316
|
-
// Run
|
317
|
-
for (const
|
318
|
-
if (
|
319
|
-
response = await
|
338
|
+
// Run BXO instance onResponse hooks
|
339
|
+
for (const bxoInstance of this.plugins) {
|
340
|
+
if (bxoInstance.hooks.onResponse) {
|
341
|
+
response = await bxoInstance.hooks.onResponse(ctx, response) || response;
|
320
342
|
}
|
321
343
|
}
|
322
344
|
|
@@ -350,9 +372,9 @@ export default class BXO {
|
|
350
372
|
errorResponse = await this.hooks.onError(ctx, error as Error);
|
351
373
|
}
|
352
374
|
|
353
|
-
for (const
|
354
|
-
if (
|
355
|
-
errorResponse = await
|
375
|
+
for (const bxoInstance of this.plugins) {
|
376
|
+
if (bxoInstance.hooks.onError) {
|
377
|
+
errorResponse = await bxoInstance.hooks.onError(ctx, error as Error) || errorResponse;
|
356
378
|
}
|
357
379
|
}
|
358
380
|
|
@@ -375,35 +397,165 @@ export default class BXO {
|
|
375
397
|
}
|
376
398
|
}
|
377
399
|
|
378
|
-
//
|
379
|
-
|
380
|
-
|
381
|
-
|
400
|
+
// Hot reload functionality
|
401
|
+
enableHotReload(watchPaths: string[] = ['./']): this {
|
402
|
+
this.hotReloadEnabled = true;
|
403
|
+
watchPaths.forEach(path => this.watchedFiles.add(path));
|
404
|
+
return this;
|
405
|
+
}
|
406
|
+
|
407
|
+
private async setupFileWatcher(port: number, hostname: string): Promise<void> {
|
408
|
+
if (!this.hotReloadEnabled) return;
|
409
|
+
|
410
|
+
const fs = require('fs');
|
411
|
+
|
412
|
+
for (const watchPath of this.watchedFiles) {
|
413
|
+
try {
|
414
|
+
fs.watch(watchPath, { recursive: true }, async (eventType: string, filename: string) => {
|
415
|
+
if (filename && (filename.endsWith('.ts') || filename.endsWith('.js'))) {
|
416
|
+
console.log(`🔄 File changed: ${filename}, restarting server...`);
|
417
|
+
await this.restart(port, hostname);
|
418
|
+
}
|
419
|
+
});
|
420
|
+
console.log(`👀 Watching ${watchPath} for changes...`);
|
421
|
+
} catch (error) {
|
422
|
+
console.warn(`⚠️ Could not watch ${watchPath}:`, error);
|
423
|
+
}
|
424
|
+
}
|
425
|
+
}
|
426
|
+
|
427
|
+
// Server management methods
|
428
|
+
async start(port: number = 3000, hostname: string = 'localhost'): Promise<void> {
|
429
|
+
if (this.isRunning) {
|
430
|
+
console.log('⚠️ Server is already running');
|
431
|
+
return;
|
382
432
|
}
|
383
433
|
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
434
|
+
try {
|
435
|
+
// Before start hook
|
436
|
+
if (this.hooks.onBeforeStart) {
|
437
|
+
await this.hooks.onBeforeStart();
|
438
|
+
}
|
389
439
|
|
390
|
-
|
440
|
+
this.server = Bun.serve({
|
441
|
+
port,
|
442
|
+
hostname,
|
443
|
+
fetch: (request) => this.handleRequest(request),
|
444
|
+
});
|
391
445
|
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
446
|
+
this.isRunning = true;
|
447
|
+
|
448
|
+
console.log(`🦊 BXO server running at http://${hostname}:${port}`);
|
449
|
+
|
450
|
+
// After start hook
|
451
|
+
if (this.hooks.onAfterStart) {
|
452
|
+
await this.hooks.onAfterStart();
|
396
453
|
}
|
397
|
-
|
398
|
-
|
399
|
-
|
454
|
+
|
455
|
+
// Setup hot reload
|
456
|
+
await this.setupFileWatcher(port, hostname);
|
457
|
+
|
458
|
+
// Handle graceful shutdown
|
459
|
+
const shutdownHandler = async () => {
|
460
|
+
await this.stop();
|
461
|
+
process.exit(0);
|
462
|
+
};
|
463
|
+
|
464
|
+
process.on('SIGINT', shutdownHandler);
|
465
|
+
process.on('SIGTERM', shutdownHandler);
|
466
|
+
|
467
|
+
} catch (error) {
|
468
|
+
console.error('❌ Failed to start server:', error);
|
469
|
+
throw error;
|
470
|
+
}
|
400
471
|
}
|
472
|
+
|
473
|
+
async stop(): Promise<void> {
|
474
|
+
if (!this.isRunning) {
|
475
|
+
console.log('⚠️ Server is not running');
|
476
|
+
return;
|
477
|
+
}
|
478
|
+
|
479
|
+
try {
|
480
|
+
// Before stop hook
|
481
|
+
if (this.hooks.onBeforeStop) {
|
482
|
+
await this.hooks.onBeforeStop();
|
483
|
+
}
|
484
|
+
|
485
|
+
if (this.server) {
|
486
|
+
this.server.stop();
|
487
|
+
this.server = null;
|
488
|
+
}
|
489
|
+
|
490
|
+
this.isRunning = false;
|
491
|
+
|
492
|
+
console.log('🛑 BXO server stopped');
|
493
|
+
|
494
|
+
// After stop hook
|
495
|
+
if (this.hooks.onAfterStop) {
|
496
|
+
await this.hooks.onAfterStop();
|
497
|
+
}
|
498
|
+
|
499
|
+
} catch (error) {
|
500
|
+
console.error('❌ Error stopping server:', error);
|
501
|
+
throw error;
|
502
|
+
}
|
503
|
+
}
|
504
|
+
|
505
|
+
async restart(port: number = 3000, hostname: string = 'localhost'): Promise<void> {
|
506
|
+
try {
|
507
|
+
// Before restart hook
|
508
|
+
if (this.hooks.onBeforeRestart) {
|
509
|
+
await this.hooks.onBeforeRestart();
|
510
|
+
}
|
511
|
+
|
512
|
+
console.log('🔄 Restarting BXO server...');
|
513
|
+
|
514
|
+
await this.stop();
|
515
|
+
|
516
|
+
// Small delay to ensure cleanup
|
517
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
518
|
+
|
519
|
+
await this.start(port, hostname);
|
520
|
+
|
521
|
+
// After restart hook
|
522
|
+
if (this.hooks.onAfterRestart) {
|
523
|
+
await this.hooks.onAfterRestart();
|
524
|
+
}
|
525
|
+
|
526
|
+
} catch (error) {
|
527
|
+
console.error('❌ Error restarting server:', error);
|
528
|
+
throw error;
|
529
|
+
}
|
530
|
+
}
|
531
|
+
|
532
|
+
// Backward compatibility
|
533
|
+
async listen(port: number = 3000, hostname: string = 'localhost'): Promise<void> {
|
534
|
+
return this.start(port, hostname);
|
535
|
+
}
|
536
|
+
|
537
|
+
// Server status
|
538
|
+
isServerRunning(): boolean {
|
539
|
+
return this.isRunning;
|
540
|
+
}
|
541
|
+
|
542
|
+
getServerInfo(): { running: boolean; hotReload: boolean; watchedFiles: string[] } {
|
543
|
+
return {
|
544
|
+
running: this.isRunning,
|
545
|
+
hotReload: this.hotReloadEnabled,
|
546
|
+
watchedFiles: Array.from(this.watchedFiles)
|
547
|
+
};
|
548
|
+
}
|
549
|
+
}
|
550
|
+
|
551
|
+
const error = (error: Error, status: number = 500) => {
|
552
|
+
return new Response(JSON.stringify({ error: error.message }), { status });
|
401
553
|
}
|
402
554
|
|
403
555
|
// Export Zod for convenience
|
404
|
-
export { z };
|
556
|
+
export { z, error };
|
405
557
|
|
406
|
-
export type {
|
558
|
+
export type { PluginFactory } from './plugins';
|
407
559
|
|
408
560
|
// Export types for external use
|
409
561
|
export type { RouteConfig };
|
package/package.json
CHANGED
package/plugins/auth.ts
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
import BXO from '../index';
|
2
|
+
|
1
3
|
interface AuthOptions {
|
2
4
|
type: 'jwt' | 'bearer' | 'apikey';
|
3
5
|
secret?: string;
|
@@ -6,7 +8,7 @@ interface AuthOptions {
|
|
6
8
|
exclude?: string[];
|
7
9
|
}
|
8
10
|
|
9
|
-
export function auth(options: AuthOptions) {
|
11
|
+
export function auth(options: AuthOptions): BXO {
|
10
12
|
const {
|
11
13
|
type,
|
12
14
|
secret,
|
@@ -15,84 +17,85 @@ export function auth(options: AuthOptions) {
|
|
15
17
|
exclude = []
|
16
18
|
} = options;
|
17
19
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
}
|
30
|
-
return pathname === path || pathname.startsWith(path);
|
31
|
-
})) {
|
32
|
-
return;
|
20
|
+
const authInstance = new BXO();
|
21
|
+
|
22
|
+
authInstance.onRequest(async (ctx: any) => {
|
23
|
+
const url = new URL(ctx.request.url);
|
24
|
+
const pathname = url.pathname;
|
25
|
+
|
26
|
+
// Skip auth for excluded paths
|
27
|
+
if (exclude.some(path => {
|
28
|
+
if (path.includes('*')) {
|
29
|
+
const regex = new RegExp(path.replace(/\*/g, '.*'));
|
30
|
+
return regex.test(pathname);
|
33
31
|
}
|
32
|
+
return pathname === path || pathname.startsWith(path);
|
33
|
+
})) {
|
34
|
+
return;
|
35
|
+
}
|
34
36
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
37
|
+
const authHeader = ctx.request.headers.get(header.toLowerCase());
|
38
|
+
|
39
|
+
if (!authHeader) {
|
40
|
+
throw new Response(JSON.stringify({ error: 'Authorization header required' }), {
|
41
|
+
status: 401,
|
42
|
+
headers: { 'Content-Type': 'application/json' }
|
43
|
+
});
|
44
|
+
}
|
45
|
+
|
46
|
+
let token: string;
|
47
|
+
|
48
|
+
if (type === 'jwt' || type === 'bearer') {
|
49
|
+
if (!authHeader.startsWith('Bearer ')) {
|
50
|
+
throw new Response(JSON.stringify({ error: 'Invalid authorization format. Use Bearer <token>' }), {
|
39
51
|
status: 401,
|
40
52
|
headers: { 'Content-Type': 'application/json' }
|
41
53
|
});
|
42
54
|
}
|
55
|
+
token = authHeader.slice(7);
|
56
|
+
} else if (type === 'apikey') {
|
57
|
+
token = authHeader;
|
58
|
+
} else {
|
59
|
+
token = authHeader;
|
60
|
+
}
|
43
61
|
|
44
|
-
|
62
|
+
try {
|
63
|
+
let user: any;
|
45
64
|
|
46
|
-
if (
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
65
|
+
if (verify) {
|
66
|
+
user = await verify(token, ctx);
|
67
|
+
} else if (type === 'jwt' && secret) {
|
68
|
+
// Simple JWT verification (in production, use a proper JWT library)
|
69
|
+
const [headerB64, payloadB64, signature] = token.split('.');
|
70
|
+
if (!headerB64 || !payloadB64 || !signature) {
|
71
|
+
throw new Error('Invalid JWT format');
|
52
72
|
}
|
53
|
-
token = authHeader.slice(7);
|
54
|
-
} else if (type === 'apikey') {
|
55
|
-
token = authHeader;
|
56
|
-
} else {
|
57
|
-
token = authHeader;
|
58
|
-
}
|
59
|
-
|
60
|
-
try {
|
61
|
-
let user: any;
|
62
73
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
if (!headerB64 || !payloadB64 || !signature) {
|
69
|
-
throw new Error('Invalid JWT format');
|
70
|
-
}
|
71
|
-
|
72
|
-
const payload = JSON.parse(atob(payloadB64));
|
73
|
-
|
74
|
-
// Check expiration
|
75
|
-
if (payload.exp && Date.now() >= payload.exp * 1000) {
|
76
|
-
throw new Error('Token expired');
|
77
|
-
}
|
78
|
-
|
79
|
-
user = payload;
|
80
|
-
} else {
|
81
|
-
user = { token };
|
74
|
+
const payload = JSON.parse(atob(payloadB64));
|
75
|
+
|
76
|
+
// Check expiration
|
77
|
+
if (payload.exp && Date.now() >= payload.exp * 1000) {
|
78
|
+
throw new Error('Token expired');
|
82
79
|
}
|
83
|
-
|
84
|
-
// Attach user to context
|
85
|
-
ctx.user = user;
|
86
80
|
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
status: 401,
|
91
|
-
headers: { 'Content-Type': 'application/json' }
|
92
|
-
});
|
81
|
+
user = payload;
|
82
|
+
} else {
|
83
|
+
user = { token };
|
93
84
|
}
|
85
|
+
|
86
|
+
// Attach user to context
|
87
|
+
ctx.user = user;
|
88
|
+
|
89
|
+
} catch (error) {
|
90
|
+
const message = error instanceof Error ? error.message : 'Invalid token';
|
91
|
+
throw new Response(JSON.stringify({ error: message }), {
|
92
|
+
status: 401,
|
93
|
+
headers: { 'Content-Type': 'application/json' }
|
94
|
+
});
|
94
95
|
}
|
95
|
-
};
|
96
|
+
});
|
97
|
+
|
98
|
+
return authInstance;
|
96
99
|
}
|
97
100
|
|
98
101
|
// Helper function for creating JWT tokens (simple implementation)
|