Handle incoming HTTP request
(
method: string,
path: string,
headers: Record<string, string>,
)
| 457 | |
| 458 | /** Handle incoming HTTP request */ |
| 459 | private async handleRequest( |
| 460 | method: string, |
| 461 | path: string, |
| 462 | headers: Record<string, string>, |
| 463 | ): Promise<{ status: number; body?: Uint8Array; headers?: Record<string, string> }> { |
| 464 | // Verify pair code (headers may be lowercased by HTTP layer) |
| 465 | const pairCodeKey = Object.keys(headers).find(k => k.toLowerCase() === "x-pair-code"); |
| 466 | const clientPairCode = pairCodeKey ? headers[pairCodeKey] : undefined; |
| 467 | if (clientPairCode !== this.pairCode) { |
| 468 | console.warn(`[LAN Server] Pair code mismatch from client`); |
| 469 | return { status: 403, body: new TextEncoder().encode("Forbidden") }; |
| 470 | } |
| 471 | |
| 472 | try { |
| 473 | // Ping endpoint — no backend required |
| 474 | if (method === "GET" && path === "/ping") { |
| 475 | return { status: 200, body: new TextEncoder().encode("pong") }; |
| 476 | } |
| 477 | |
| 478 | if (!this.backend) { |
| 479 | return { status: 503, body: new TextEncoder().encode("Service Unavailable") }; |
| 480 | } |
| 481 | |
| 482 | // File download |
| 483 | if (method === "GET" && path.startsWith("/file/")) { |
| 484 | const virtualPath = path.substring(5); // Remove "/file" prefix (keep leading slash) |
| 485 | try { |
| 486 | const data = await this.backend.get(virtualPath); |
| 487 | return { |
| 488 | status: 200, |
| 489 | body: data, |
| 490 | headers: { |
| 491 | "Content-Type": "application/octet-stream", |
| 492 | "Content-Length": String(data.length), |
| 493 | }, |
| 494 | }; |
| 495 | } catch (e) { |
| 496 | const error = e instanceof Error ? e.message : String(e); |
| 497 | const statusCode = (e as any).statusCode || 500; |
| 498 | if (statusCode === 404 || error.includes("File not found")) { |
| 499 | return { status: 404, body: new TextEncoder().encode("Not Found") }; |
| 500 | } |
| 501 | throw e; |
| 502 | } |
| 503 | } |
| 504 | |
| 505 | // Directory listing |
| 506 | if (method === "GET" && path.startsWith("/list/")) { |
| 507 | const virtualPath = path.substring(5); // Remove "/list" prefix (keep leading slash) |
| 508 | const files = await this.backend.listDir(virtualPath); |
| 509 | const body = new TextEncoder().encode(JSON.stringify(files)); |
| 510 | return { |
| 511 | status: 200, |
| 512 | body, |
| 513 | headers: { |
| 514 | "Content-Type": "application/json", |
| 515 | }, |
| 516 | }; |