feat: plugins v1.7
This commit is contained in:
107
electron/api/http-helpers.ts
Normal file
107
electron/api/http-helpers.ts
Normal file
@@ -0,0 +1,107 @@
|
||||
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' });
|
||||
}
|
||||
Reference in New Issue
Block a user