@smartsamurai/krapi-sdk
Version:
KRAPI TypeScript SDK - Easy-to-use client SDK for connecting to self-hosted KRAPI servers (like Appwrite SDK)
204 lines (191 loc) • 6.46 kB
text/typescript
/**
* Endpoint Utilities for KRAPI SDK
*
* Provides endpoint validation, normalization, and path handling utilities.
*
* @module utils/endpoint-utils
*/
/**
* Normalize and validate endpoint URL
*
* - Detects if endpoint points to backend port (3470) and warns external clients
* - Does not warn for localhost connections (frontend server making internal calls)
* - Automatically appends appropriate path based on URL type:
* - Backend URLs (port 3470): /krapi/k1 (NO /api prefix)
* - Frontend URLs (port 3498): /api/krapi/k1
* - Handles both frontend URLs (with /api) and direct backend URLs
*
* @param {string} endpoint - Raw endpoint URL
* @param {Object} options - Normalization options
* @param {boolean} [options.warnOnBackendPort] - Whether to warn if connecting to backend port (default: true)
* Note: Warnings are only shown for external clients, not for localhost connections
* @param {boolean} [options.autoAppendPath] - Whether to automatically append path (default: true)
* @returns {string} Normalized endpoint URL
*
* @example
* normalizeEndpoint('http://localhost:3470') // Returns: 'http://localhost:3470/krapi/k1' (no warning for localhost)
* normalizeEndpoint('http://example.com:3470') // Returns: 'http://example.com:3470/krapi/k1' (with warning)
* normalizeEndpoint('http://localhost:3498') // Returns: 'http://localhost:3498/api/krapi/k1'
* normalizeEndpoint('http://localhost:3498/api/krapi/k1') // Returns: 'http://localhost:3498/api/krapi/k1'
*/
export function normalizeEndpoint(
endpoint: string,
options: {
warnOnBackendPort?: boolean;
autoAppendPath?: boolean;
logger?: Console;
} = {}
): string {
const {
warnOnBackendPort = true,
autoAppendPath = true,
logger = console,
} = options;
// Remove trailing slashes
let normalized = endpoint.replace(/\/+$/, "");
// Parse URL to check port
try {
const url = new URL(normalized);
const port = url.port
? parseInt(url.port, 10)
: url.protocol === "https:"
? 443
: 80;
// Warn if connecting to backend port (3470) from external clients
// Don't warn if connecting from localhost - this is likely the frontend server making internal calls
if (warnOnBackendPort && port === 3470) {
const hostname = url.hostname.toLowerCase();
const isLocalhost =
hostname === "localhost" ||
hostname === "127.0.0.1" ||
hostname === "::1" ||
hostname === "";
// Only warn for external clients, not for localhost connections (frontend server)
if (!isLocalhost) {
const frontendPort = 3498;
const suggestedUrl = normalized.replace(`:${port}`, `:${frontendPort}`);
logger.warn(
`⚠️ Warning: You've connected to the backend port (3470).\n` +
` External clients should connect to the frontend port (${frontendPort}).\n` +
` Did you mean: ${suggestedUrl}?\n` +
` Note: Backend port is typically for internal use only.`
);
}
}
} catch {
// Invalid URL, but we'll continue with normalization
// The actual HTTP client will catch this later
}
// Auto-append appropriate path if not present and autoAppendPath is true
// Backend URLs (port 3470) use /krapi/k1 (NO /api prefix)
// Frontend URLs (port 3498) use /api/krapi/k1
if (
autoAppendPath &&
!normalized.includes("/api/krapi/k1") &&
!normalized.includes("/krapi/k1")
) {
try {
const url = new URL(normalized);
const port = url.port
? parseInt(url.port, 10)
: url.protocol === "https:"
? 443
: 80;
if (port === 3470) {
normalized = `${normalized}/krapi/k1`; // Backend: NO /api prefix
} else {
normalized = `${normalized}/api/krapi/k1`; // Frontend: WITH /api prefix
}
} catch {
// Invalid URL, default to frontend path
normalized = `${normalized}/api/krapi/k1`;
}
}
return normalized;
}
/**
* Validate endpoint URL format
*
* @param {string} endpoint - Endpoint URL to validate
* @returns {{ valid: boolean; error?: string }} Validation result
*
* @example
* validateEndpoint('http://localhost:3498') // Returns: { valid: true }
* validateEndpoint('invalid-url') // Returns: { valid: false, error: 'Invalid URL format' }
*/
export function validateEndpoint(endpoint: string): {
valid: boolean;
error?: string;
} {
if (!endpoint || typeof endpoint !== "string") {
return { valid: false, error: "Endpoint must be a non-empty string" };
}
try {
const url = new URL(endpoint);
// Check for valid protocol
if (!["http:", "https:"].includes(url.protocol)) {
return {
valid: false,
error: "Endpoint must use http:// or https:// protocol",
};
}
return { valid: true };
} catch (error) {
return {
valid: false,
error: `Invalid URL format: ${
error instanceof Error ? error.message : "Unknown error"
}`,
};
}
}
/**
* Extract port number from endpoint URL
*
* @param {string} endpoint - Endpoint URL
* @returns {number | null} Port number or null if not found
*
* @example
* extractPort('http://localhost:3498') // Returns: 3498
* extractPort('http://localhost') // Returns: null (default port)
*/
export function extractPort(endpoint: string): number | null {
try {
const url = new URL(endpoint);
if (url.port) {
return parseInt(url.port, 10);
}
// Return default port based on protocol
return url.protocol === "https:" ? 443 : 80;
} catch {
return null;
}
}
/**
* Check if endpoint is a backend URL (port 3470)
*
* @param {string} endpoint - Endpoint URL
* @returns {boolean} True if endpoint uses backend port
*
* @example
* isBackendUrl('http://localhost:3470') // Returns: true
* isBackendUrl('http://localhost:3498') // Returns: false
*/
export function isBackendUrl(endpoint: string): boolean {
const port = extractPort(endpoint);
return port === 3470;
}
/**
* Check if endpoint is a frontend URL (port 3498)
*
* @param {string} endpoint - Endpoint URL
* @returns {boolean} True if endpoint uses frontend port
*
* @example
* isFrontendUrl('http://localhost:3498') // Returns: true
* isFrontendUrl('http://localhost:3470') // Returns: false
*/
export function isFrontendUrl(endpoint: string): boolean {
const port = extractPort(endpoint);
return port === 3498;
}