/* Copyright (C) 2026 Moko Consulting * * This file is part of a Moko Consulting project. * * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION * DEFGROUP: {{PROJECT_NAME}}.Client * INGROUP: {{PROJECT_NAME}} * REPO: https://git.mokoconsulting.tech/MokoConsulting/{{PROJECT_NAME}} * PATH: /src/client.ts * VERSION: 01.00.00 * BRIEF: HTTP client for {{DISPLAY_NAME}} API */ import * as https from 'node:https'; import * as http from 'node:http'; import type { ApiConnection, ApiResponse } from './types.js'; // ── Customize these ───────────────────────────────────────────────────── // API path prefix appended to baseUrl (e.g. "/api/index.php", "/api/v1") const API_PREFIX = '/api'; const TIMEOUT_MS = 30_000; // ──────────────────────────────────────────────────────────────────────── export class ApiClient { private readonly base_url: string; private readonly headers: Record; private readonly insecure: boolean; constructor(conn: ApiConnection) { this.base_url = conn.baseUrl.replace(/\/+$/, '') + API_PREFIX; // ── Customize auth headers ────────────────────────────────── // Examples: // Bearer token: { 'Authorization': `Bearer ${conn.apiKey}` } // API key header: { 'DOLAPIKEY': conn.apiKey } // Basic auth: { 'Authorization': `Basic ${btoa(user + ':' + pass)}` } this.headers = { 'Authorization': `Bearer ${conn.apiKey}`, 'Content-Type': 'application/json', 'Accept': 'application/json', }; this.insecure = conn.insecure ?? false; } async get(endpoint: string, params?: Record): Promise { return this.request(this.buildUrl(endpoint, params), 'GET'); } async post(endpoint: string, body?: unknown): Promise { return this.request(this.buildUrl(endpoint), 'POST', body); } async put(endpoint: string, body: unknown): Promise { return this.request(this.buildUrl(endpoint), 'PUT', body); } async patch(endpoint: string, body: unknown): Promise { return this.request(this.buildUrl(endpoint), 'PATCH', body); } async delete(endpoint: string): Promise { return this.request(this.buildUrl(endpoint), 'DELETE'); } private buildUrl(endpoint: string, params?: Record): string { const path = endpoint.startsWith('/') ? endpoint : `/${endpoint}`; const url = new URL(`${this.base_url}${path}`); if (params) { for (const [key, value] of Object.entries(params)) { url.searchParams.set(key, value); } } return url.toString(); } private request(url: string, method: string, body?: unknown): Promise { return new Promise((resolve, reject) => { const parsed = new URL(url); const is_https = parsed.protocol === 'https:'; const transport = is_https ? https : http; const options: https.RequestOptions = { hostname: parsed.hostname, port: parsed.port || (is_https ? 443 : 80), path: parsed.pathname + parsed.search, method, headers: { ...this.headers }, timeout: TIMEOUT_MS, }; if (this.insecure && is_https) { options.rejectUnauthorized = false; } const payload = body !== undefined ? JSON.stringify(body) : undefined; if (payload) { (options.headers as Record)['Content-Length'] = Buffer.byteLength(payload).toString(); } const req = transport.request(options, (res) => { const chunks: Buffer[] = []; res.on('data', (chunk: Buffer) => chunks.push(chunk)); res.on('end', () => { const raw = Buffer.concat(chunks).toString('utf-8'); let data: unknown; try { data = JSON.parse(raw); } catch { data = raw; } resolve({ status: res.statusCode ?? 0, data }); }); }); req.on('error', (err) => reject(err)); req.on('timeout', () => { req.destroy(); reject(new Error('Request timed out')); }); if (payload) { req.write(payload); } req.end(); }); } }