feat: add theme toggle component and theme store for light/dark mode support
- Implemented ThemeToggle component for switching between light, dark, and system themes. - Created themeStore for managing theme state and persisting user preferences in localStorage. - Added utility functions for error message sanitization to prevent sensitive data leakage. - Developed proxy utility functions for API requests, including template variable replacement. - Enhanced layout with dark mode styles and smooth transitions for theme changes. - Updated main layout and page components to integrate theme toggle and improve accessibility. - Added server-side proxy handling with validation and error sanitization for API requests.
This commit is contained in:
117
src/lib/utils/proxy.ts
Normal file
117
src/lib/utils/proxy.ts
Normal file
@@ -0,0 +1,117 @@
|
||||
/**
|
||||
* Proxy utility functions for API enrichment testing
|
||||
* Handles template variable replacement and proxied fetch requests
|
||||
*/
|
||||
|
||||
interface ProxyOptions {
|
||||
url: string;
|
||||
method: string;
|
||||
headers: Record<string, string>;
|
||||
body?: unknown;
|
||||
}
|
||||
|
||||
interface ProxyResponse {
|
||||
data: unknown;
|
||||
status: number;
|
||||
headers: Record<string, string>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes a proxied API request through the SvelteKit backend
|
||||
* This bypasses CORS restrictions and provides consistent error handling
|
||||
*
|
||||
* @param options - Request configuration
|
||||
* @returns Promise with response data, status, and headers
|
||||
* @throws Error if proxy request fails
|
||||
*/
|
||||
export async function proxyFetch(options: ProxyOptions): Promise<ProxyResponse> {
|
||||
const response = await fetch('/api/proxy', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(options)
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json().catch(() => ({ error: response.statusText }));
|
||||
throw new Error(errorData.error || `Proxy Error: ${response.statusText}`);
|
||||
}
|
||||
|
||||
return response.json();
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces template variables in a string with actual values
|
||||
* Supports {{variable}} syntax for substitution
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* renderTemplate("https://api.com/users/{{id}}", { id: 123 })
|
||||
* // Returns: "https://api.com/users/123"
|
||||
* renderTemplate("https://api.com/?name={{服务商}}", { 服务商: "Baidu" })
|
||||
* // Returns: "https://api.com/?name=Baidu"
|
||||
* ```
|
||||
*
|
||||
* @param template - String containing {{variable}} placeholders
|
||||
* @param context - Object mapping variable names to values
|
||||
* @returns String with all variables replaced
|
||||
*/
|
||||
export function renderTemplate(template: string, context: Record<string, unknown>): string {
|
||||
return template.replace(/\{\{\s*([^{}]+?)\s*\}\}/g, (_, key) => {
|
||||
const value = context[key.trim()];
|
||||
return value !== undefined && value !== null ? String(value) : '';
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Safely extracts a value from a nested object using dot notation
|
||||
* Returns undefined if the path cannot be resolved
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* extractByPath({ data: { user: { name: "Alice" } } }, "data.user.name")
|
||||
* // Returns: "Alice"
|
||||
* ```
|
||||
*
|
||||
* @param obj - Object to extract from
|
||||
* @param path - Dot-notation path (e.g., "data.user.name")
|
||||
* @returns Extracted value or undefined
|
||||
*/
|
||||
export function extractByPath(obj: unknown, path: string): unknown {
|
||||
if (!path) return obj;
|
||||
|
||||
const keys = path.split('.');
|
||||
let current: unknown = obj;
|
||||
|
||||
for (const key of keys) {
|
||||
if (current === null || current === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
if (typeof current !== 'object') {
|
||||
return undefined;
|
||||
}
|
||||
current = (current as Record<string, unknown>)[key];
|
||||
}
|
||||
|
||||
return current;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates if a string contains template variables
|
||||
* @param str - String to check
|
||||
* @returns true if string contains {{variable}} patterns
|
||||
*/
|
||||
export function hasTemplateVariables(str: string): boolean {
|
||||
return /\{\{\s*\w+\s*\}\}/.test(str);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts all variable names from a template string
|
||||
* @param template - String containing {{variable}} placeholders
|
||||
* @returns Array of unique variable names
|
||||
*/
|
||||
export function extractVariableNames(template: string): string[] {
|
||||
const matches = template.match(/\{\{\s*(\w+)\s*\}\}/g);
|
||||
if (!matches) return [];
|
||||
|
||||
return [...new Set(matches.map((m) => m.replace(/\{\{\s*|\s*\}\}/g, '')))];
|
||||
}
|
||||
Reference in New Issue
Block a user