UNPKG

@restnfeel/agentc-starter-kit

Version:

한국어 기업용 CMS 모듈 - Task Master AI와 함께 빠르게 웹사이트를 구현할 수 있는 재사용 가능한 컴포넌트 시스템

404 lines (346 loc) 13.2 kB
// ================================================================ // AgentC Customer Chat Library - Type Guards & Validators // ================================================================ import { Message, ChatSession, ChatError, DeviceInfo, DeviceType, ChatMode, ChatbotTheme, SupportedLanguage, UnifiedCustomerChatLibraryConfig, ApiConfig, ChatEvent, ChatEventType, CHAT_EVENTS } from './types'; // ================================================================ // Core Type Guards // ================================================================ export function isMessage(value: any): value is Message { return ( typeof value === 'object' && value !== null && typeof value.id === 'string' && typeof value.content === 'string' && ['user', 'ai', 'system'].includes(value.sender) && value.timestamp instanceof Date && ['text', 'image', 'file', 'action'].includes(value.type) ); } export function isChatSession(value: any): value is ChatSession { return ( typeof value === 'object' && value !== null && typeof value.id === 'string' && value.startTime instanceof Date && value.lastActivity instanceof Date && Array.isArray(value.messages) && value.messages.every(isMessage) && typeof value.context === 'object' && ['active', 'ended', 'paused'].includes(value.status) ); } export function isChatError(value: any): value is ChatError { return ( typeof value === 'object' && value !== null && typeof value.code === 'string' && typeof value.message === 'string' && value.timestamp instanceof Date && typeof value.recoverable === 'boolean' ); } // ================================================================ // Device & Responsive Type Guards // ================================================================ export function isDeviceType(value: any): value is DeviceType { return Object.values(DeviceType).includes(value); } export function isChatMode(value: any): value is ChatMode { return Object.values(ChatMode).includes(value); } export function isDeviceInfo(value: any): value is DeviceInfo { return ( typeof value === 'object' && value !== null && isDeviceType(value.type) && typeof value.screenWidth === 'number' && typeof value.screenHeight === 'number' && ['portrait', 'landscape'].includes(value.orientation) && typeof value.isTouchDevice === 'boolean' && typeof value.isKeyboardVisible === 'boolean' && typeof value.userAgent === 'string' && typeof value.pixelRatio === 'number' ); } // ================================================================ // Configuration Type Guards // ================================================================ export function isApiConfig(value: any): value is ApiConfig { return ( typeof value === 'object' && value !== null && (value.baseUrl === undefined || typeof value.baseUrl === 'string') && (value.apiKey === undefined || typeof value.apiKey === 'string') && (value.timeout === undefined || typeof value.timeout === 'number') && (value.retryAttempts === undefined || typeof value.retryAttempts === 'number') ); } export function isSupportedLanguage(value: any): value is SupportedLanguage { const supportedLanguages: SupportedLanguage[] = ['ko', 'en', 'ja', 'zh', 'es', 'fr', 'de']; return supportedLanguages.includes(value); } export function isChatbotTheme(value: any): value is ChatbotTheme { return ( typeof value === 'object' && value !== null && typeof value.primary === 'string' && typeof value.secondary === 'string' && typeof value.background === 'string' && typeof value.surface === 'string' && typeof value.text === 'object' && typeof value.text.primary === 'string' && typeof value.text.secondary === 'string' && typeof value.text.inverse === 'string' ); } // ================================================================ // Event Type Guards // ================================================================ export function isChatEvent(value: any): value is ChatEvent { return ( typeof value === 'object' && value !== null && typeof value.type === 'string' && value.timestamp instanceof Date && value.data !== undefined ); } export function isChatEventType(value: any): value is ChatEventType { return Object.values(CHAT_EVENTS).includes(value); } // ================================================================ // Configuration Validators // ================================================================ export function validateUnifiedChatLibraryConfig( config: Partial<UnifiedCustomerChatLibraryConfig> ): { isValid: boolean; errors: string[] } { const errors: string[] = []; // Validate breakpoints if (config.mobileBreakpoint !== undefined) { if (typeof config.mobileBreakpoint !== 'number' || config.mobileBreakpoint <= 0) { errors.push('mobileBreakpoint must be a positive number'); } } if (config.tabletBreakpoint !== undefined) { if (typeof config.tabletBreakpoint !== 'number' || config.tabletBreakpoint <= 0) { errors.push('tabletBreakpoint must be a positive number'); } } if (config.desktopBreakpoint !== undefined) { if (typeof config.desktopBreakpoint !== 'number' || config.desktopBreakpoint <= 0) { errors.push('desktopBreakpoint must be a positive number'); } } // Validate chat mode if (config.defaultChatMode !== undefined && !isChatMode(config.defaultChatMode)) { errors.push('defaultChatMode must be a valid ChatMode'); } // Validate API config if (config.api !== undefined && !isApiConfig(config.api)) { errors.push('api configuration is invalid'); } // Validate UI config if (config.ui !== undefined) { if (typeof config.ui !== 'object') { errors.push('ui configuration must be an object'); } else { if (config.ui.theme !== undefined && typeof config.ui.theme !== 'string' && !isChatbotTheme(config.ui.theme)) { errors.push('ui.theme must be a theme name or ChatbotTheme object'); } if (config.ui.brandName !== undefined && typeof config.ui.brandName !== 'string') { errors.push('ui.brandName must be a string'); } } } // Validate i18n config if (config.i18n !== undefined) { if (typeof config.i18n !== 'object') { errors.push('i18n configuration must be an object'); } else { if (config.i18n.defaultLanguage !== undefined && !isSupportedLanguage(config.i18n.defaultLanguage)) { errors.push('i18n.defaultLanguage must be a supported language'); } } } return { isValid: errors.length === 0, errors }; } // ================================================================ // Runtime Type Checking Utilities // ================================================================ export function assertIsMessage(value: any, context?: string): asserts value is Message { if (!isMessage(value)) { throw new TypeError(`Expected Message${context ? ` in ${context}` : ''}, got ${typeof value}`); } } export function assertIsChatSession(value: any, context?: string): asserts value is ChatSession { if (!isChatSession(value)) { throw new TypeError(`Expected ChatSession${context ? ` in ${context}` : ''}, got ${typeof value}`); } } export function assertIsDeviceInfo(value: any, context?: string): asserts value is DeviceInfo { if (!isDeviceInfo(value)) { throw new TypeError(`Expected DeviceInfo${context ? ` in ${context}` : ''}, got ${typeof value}`); } } export function assertIsChatMode(value: any, context?: string): asserts value is ChatMode { if (!isChatMode(value)) { throw new TypeError(`Expected ChatMode${context ? ` in ${context}` : ''}, got ${typeof value}`); } } // ================================================================ // Validation Helpers // ================================================================ export function isValidUrl(url: string): boolean { try { new URL(url); return true; } catch { return false; } } export function isValidApiKey(apiKey: string): boolean { return typeof apiKey === 'string' && apiKey.length > 0 && apiKey.trim() === apiKey; } export function isValidEmailAddress(email: string): boolean { const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; return emailRegex.test(email); } export function isValidSessionId(sessionId: string): boolean { return typeof sessionId === 'string' && sessionId.length > 0 && !/\s/.test(sessionId); } export function isValidMessageContent(content: string): boolean { return typeof content === 'string' && content.trim().length > 0; } // ================================================================ // Data Sanitization // ================================================================ export function sanitizeMessage(message: Partial<Message>): Message | null { try { if (!message.content || !isValidMessageContent(message.content)) { return null; } const sanitized: Message = { id: message.id || `msg_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`, content: message.content.trim(), sender: ['user', 'ai', 'system'].includes(message.sender as any) ? message.sender as any : 'user', timestamp: message.timestamp instanceof Date ? message.timestamp : new Date(), type: ['text', 'image', 'file', 'action'].includes(message.type as any) ? message.type as any : 'text', metadata: typeof message.metadata === 'object' ? message.metadata : undefined, status: ['sending', 'sent', 'delivered', 'read', 'error'].includes(message.status as any) ? message.status as any : 'sent' }; return isMessage(sanitized) ? sanitized : null; } catch (error) { console.error('Error sanitizing message:', error); return null; } } export function sanitizeApiConfig(config: Partial<ApiConfig>): ApiConfig { const sanitized: ApiConfig = {}; if (config.baseUrl && typeof config.baseUrl === 'string' && isValidUrl(config.baseUrl)) { sanitized.baseUrl = config.baseUrl.replace(/\/$/, ''); // Remove trailing slash } if (config.apiKey && isValidApiKey(config.apiKey)) { sanitized.apiKey = config.apiKey; } if (typeof config.timeout === 'number' && config.timeout > 0) { sanitized.timeout = Math.min(config.timeout, 60000); // Max 60 seconds } if (typeof config.retryAttempts === 'number' && config.retryAttempts >= 0) { sanitized.retryAttempts = Math.min(config.retryAttempts, 5); // Max 5 retries } if (config.headers && typeof config.headers === 'object') { sanitized.headers = { ...config.headers }; } return sanitized; } // ================================================================ // Performance Validation // ================================================================ export function validatePerformanceConfig(config: any): boolean { return ( typeof config === 'object' && typeof config.enableOptimization === 'boolean' && typeof config.memoryThreshold === 'number' && config.memoryThreshold > 0 && config.memoryThreshold <= 1 && typeof config.maxCacheSize === 'number' && config.maxCacheSize > 0 ); } export function validateAccessibilityConfig(config: any): boolean { return ( typeof config === 'object' && typeof config.enableScreenReader === 'boolean' && typeof config.enableKeyboardNavigation === 'boolean' && typeof config.enableHighContrast === 'boolean' && typeof config.enableFocusVisible === 'boolean' && typeof config.ariaLabels === 'object' ); } // ================================================================ // Error Handling // ================================================================ export class ChatLibraryTypeError extends Error { constructor( message: string, public readonly expectedType: string, public readonly actualType: string, public readonly context?: string ) { super(message); this.name = 'ChatLibraryTypeError'; } } export function createTypeError( expectedType: string, actualValue: any, context?: string ): ChatLibraryTypeError { const actualType = actualValue === null ? 'null' : typeof actualValue; const message = `Expected ${expectedType}${context ? ` in ${context}` : ''}, got ${actualType}`; return new ChatLibraryTypeError(message, expectedType, actualType, context); } // ================================================================ // Development Helpers // ================================================================ export function logTypeValidation(value: any, typeName: string, isValid: boolean): void { if (process.env.NODE_ENV === 'development') { console.log(`Type validation for ${typeName}:`, { value, isValid, type: typeof value, constructor: value?.constructor?.name }); } } export function validateAndLog<T>( value: any, validator: (val: any) => val is T, typeName: string ): value is T { const isValid = validator(value); logTypeValidation(value, typeName, isValid); return isValid; }