@restnfeel/agentc-starter-kit
Version:
한국어 기업용 CMS 모듈 - Task Master AI와 함께 빠르게 웹사이트를 구현할 수 있는 재사용 가능한 컴포넌트 시스템
404 lines (346 loc) • 13.2 kB
text/typescript
// ================================================================
// 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;
}