Files
Toju/electron/api/http-helpers.ts

109 lines
2.6 KiB
TypeScript

import { IncomingMessage, ServerResponse } from 'http';
export interface RequestContext {
method: string;
url: URL;
pathname: string;
headers: IncomingMessage['headers'];
remoteAddress: string;
bearerToken: string | null;
}
const MAX_BODY_BYTES = 1 * 1024 * 1024; // 1 MiB
export function getBearerToken(headers: IncomingMessage['headers']): string | null {
const raw = headers.authorization;
if (typeof raw !== 'string') {
return null;
}
const trimmed = raw.trim();
if (!/^bearer\s+/iu.test(trimmed)) {
return null;
}
const token = trimmed.replace(/^bearer\s+/iu, '').trim();
return token.length > 0 ? token : null;
}
export async function readJsonBody<T>(req: IncomingMessage): Promise<T> {
const length = Number(req.headers['content-length'] ?? 0);
if (length > MAX_BODY_BYTES) {
throw new HttpError(413, 'Request body too large', 'BODY_TOO_LARGE');
}
const chunks: Buffer[] = [];
let received = 0;
for await (const chunk of req) {
const buffer = chunk instanceof Buffer ? chunk : Buffer.from(chunk as string);
received += buffer.length;
if (received > MAX_BODY_BYTES) {
throw new HttpError(413, 'Request body too large', 'BODY_TOO_LARGE');
}
chunks.push(buffer);
}
if (chunks.length === 0) {
return {} as T;
}
const raw = Buffer.concat(chunks).toString('utf8');
try {
return JSON.parse(raw) as T;
} catch {
throw new HttpError(400, 'Invalid JSON body', 'INVALID_JSON');
}
}
export function sendJson(res: ServerResponse, status: number, payload: unknown): void {
if (!res.headersSent) {
res.statusCode = status;
res.setHeader('Content-Type', 'application/json; charset=utf-8');
res.setHeader('Cache-Control', 'no-store');
}
res.end(JSON.stringify(payload));
}
export function sendText(res: ServerResponse, status: number, text: string, contentType = 'text/plain; charset=utf-8'): void {
if (!res.headersSent) {
res.statusCode = status;
res.setHeader('Content-Type', contentType);
res.setHeader('Cache-Control', 'no-store');
}
res.end(text);
}
export class HttpError extends Error {
readonly status: number;
readonly code: string;
constructor(status: number, message: string, code: string) {
super(message);
this.status = status;
this.code = code;
}
}
export function sendError(res: ServerResponse, error: unknown): void {
if (error instanceof HttpError) {
sendJson(res, error.status, { error: error.message, errorCode: error.code });
return;
}
const message = error instanceof Error ? error.message : 'Internal server error';
sendJson(res, 500, { error: message, errorCode: 'INTERNAL_ERROR' });
}