| 57 | // ── Internals ───────────────────────────────────────────────────────────── |
| 58 | |
| 59 | private extractToken(req: IncomingMessage): string | null { |
| 60 | // 1. ?token= query param (used by WebSocket clients and simple links) |
| 61 | const url = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`); |
| 62 | const qp = url.searchParams.get("token"); |
| 63 | if (qp) return qp; |
| 64 | |
| 65 | // 2. Authorization: Bearer <token> |
| 66 | const auth = req.headers["authorization"]; |
| 67 | if (auth?.startsWith("Bearer ")) return auth.slice(7); |
| 68 | |
| 69 | // 3. Cookie cc_token (set by token-auth login page if any) |
| 70 | const cookieHeader = req.headers.cookie ?? ""; |
| 71 | for (const part of cookieHeader.split(";")) { |
| 72 | const eq = part.indexOf("="); |
| 73 | if (eq === -1) continue; |
| 74 | if (part.slice(0, eq).trim() === "cc_token") { |
| 75 | return decodeURIComponent(part.slice(eq + 1).trim()); |
| 76 | } |
| 77 | } |
| 78 | |
| 79 | return null; |
| 80 | } |
| 81 | } |