UNPKG

mcp-quiz-server

Version:

🧠 AI-Powered Quiz Management via Model Context Protocol (MCP) - Create, manage, and take quizzes directly from VS Code, Claude, and other AI agents.

454 lines (418 loc) • 14.1 kB
/** * @moduleName: Demo Configuration Service - URL-Based Settings Management * @version: 1.0.0 * @since: 2025-07-25 * @lastUpdated: 2025-07-27 * @projectSummary: Enhanced MCP Quiz Server - Service managing demo configurations and URL parameter-based settings * @techStack: TypeScript, URL Parameters, Static Configuration, Settings Management * @dependency: DemoConfig, AppSettings interfaces, SettingsManager service * @interModuleDependency: SettingsManager for settings persistence, URL parameter parsing * @requirementsTraceability: * {@link Requirements.REQ_CONFIG_003} (Demo Configurations) * {@link Requirements.REQ_INTEGRATION_001} (URL Parameter Support) * @briefDescription: Static service managing predefined demo configurations accessible via URL parameters for testing and demos * @methods: initialize, getConfig, applyConfig, loadFromUrl, getAllConfigs * @contributors: GitHub Copilot, Demo Configuration Team * @examples: * - await DemoConfigService.initialize() // Load all demo configs * - DemoConfigService.applyConfig('accessibility') // Apply accessibility demo * - DemoConfigService.loadFromUrl() // Apply config from URL params * @vulnerabilitiesAssessment: URL parameter validation, settings validation, XSS prevention in config application */ import { AppSettings, DemoConfig } from '../types/index'; import { SettingsManager } from './SettingsManager'; /** * Service for managing demo configurations and URL parameter settings */ export class DemoConfigService { private static configs: Map<string, DemoConfig> = new Map(); private static initialized = false; /** * Initialize the service and load configurations */ static async initialize(): Promise<void> { if (this.initialized) return; await this.loadBuiltInConfigs(); this.loadFromURLParams(); this.initialized = true; console.log('🎬 DemoConfigService initialized with configs:', Array.from(this.configs.keys())); } /** * Load built-in demo configurations */ private static async loadBuiltInConfigs(): Promise<void> { // Built-in configurations (would normally load from JSON files) const builtInConfigs: DemoConfig[] = [ { name: 'accessibility-demo', description: 'Settings optimized for accessibility and screen reader testing', settings: { ui: { theme: 'high-contrast', fontSize: 'large', navigationVisible: true, reducedMotion: true, animations: false, celebration: false, announceChanges: true, }, accessibility: { screenReaderMode: true, keyboardNavigation: true, skipLinks: true, highContrast: true, }, quiz: { viewMode: 'single', autoAdvance: { enabled: false, delaySeconds: 5, showCountdown: true, }, showProgress: true, largeButtons: true, preloadQuestions: true, minimalUI: false, }, }, metadata: { createdBy: 'UX Team', purpose: 'Accessibility compliance testing', tags: ['accessibility', 'demo', 'testing', 'screen-reader'], lastUpdated: '2025-07-25', version: '1.0', }, }, { name: 'performance-demo', description: 'Lightweight settings optimized for speed and minimal resource usage', settings: { ui: { theme: 'light', fontSize: 'medium', navigationVisible: false, reducedMotion: true, animations: false, celebration: false, announceChanges: false, }, quiz: { viewMode: 'single', autoAdvance: { enabled: true, delaySeconds: 1, showCountdown: false, }, showProgress: false, largeButtons: false, preloadQuestions: false, minimalUI: true, }, performance: { lazyLoading: true, compactMode: true, disableAnalytics: true, cacheOptimized: true, }, }, metadata: { createdBy: 'Performance Team', purpose: 'Performance testing and benchmarking', tags: ['performance', 'demo', 'speed', 'minimal'], lastUpdated: '2025-07-25', version: '1.0', }, }, { name: 'educator-demo', description: 'Settings optimized for educational environments', settings: { ui: { theme: 'light', fontSize: 'large', navigationVisible: true, reducedMotion: false, animations: true, celebration: true, announceChanges: true, }, quiz: { viewMode: 'single', autoAdvance: { enabled: false, delaySeconds: 5, showCountdown: true, }, showProgress: true, largeButtons: true, preloadQuestions: true, minimalUI: false, }, accessibility: { screenReaderMode: false, keyboardNavigation: true, skipLinks: true, highContrast: false, }, }, metadata: { createdBy: 'Education Team', purpose: 'Classroom and educational environment usage', tags: ['education', 'demo', 'classroom', 'teaching'], lastUpdated: '2025-07-25', version: '1.0', }, }, { name: 'mobile-demo', description: 'Settings optimized for mobile devices', settings: { ui: { theme: 'system', fontSize: 'large', navigationVisible: false, reducedMotion: false, animations: true, celebration: true, announceChanges: false, }, quiz: { viewMode: 'single', autoAdvance: { enabled: false, delaySeconds: 3, showCountdown: true, }, showProgress: true, largeButtons: true, preloadQuestions: true, minimalUI: false, }, performance: { lazyLoading: true, compactMode: false, disableAnalytics: false, cacheOptimized: true, }, }, metadata: { createdBy: 'Mobile Team', purpose: 'Mobile device optimization', tags: ['mobile', 'demo', 'touch', 'responsive'], lastUpdated: '2025-07-25', version: '1.0', }, }, ]; // Store built-in configurations builtInConfigs.forEach(config => { this.configs.set(config.name, config); }); // Try to load external config files await this.loadExternalConfigs(); } /** * Load external configuration files */ private static async loadExternalConfigs(): Promise<void> { const configFiles = [ '/config/demo-configs/accessibility-demo.json', '/config/demo-configs/performance-demo.json', ]; for (const file of configFiles) { try { const response = await fetch(file); if (response.ok) { const config: DemoConfig = await response.json(); this.configs.set(config.name, config); console.log(`📋 Loaded external config: ${config.name}`); } } catch (error) { console.warn(`Failed to load external config: ${file}`, error); } } } /** * Load configuration from URL parameters */ private static loadFromURLParams(): void { const urlParams = new URLSearchParams(window.location.search); const configParam = urlParams.get('config'); const settingsParam = urlParams.get('settings'); if (configParam) { try { if (configParam.startsWith('{')) { // Inline JSON configuration const inlineConfig = JSON.parse(decodeURIComponent(configParam)); this.applyConfigurationNow(inlineConfig); console.log('🔗 Applied inline URL configuration'); } else { // Named configuration const config = this.configs.get(configParam); if (config) { this.applyDemoConfig(configParam); console.log(`🔗 Applied URL demo config: ${configParam}`); } else { console.warn(`🔗 URL config not found: ${configParam}`); } } } catch (error) { console.error('🔗 Failed to parse URL configuration:', error); } } if (settingsParam) { try { const settings = JSON.parse(decodeURIComponent(settingsParam)); this.applyConfigurationNow(settings); console.log('🔗 Applied URL settings parameter'); } catch (error) { console.error('🔗 Failed to parse URL settings:', error); } } } /** * Apply a demo configuration by name */ static applyDemoConfig(configName: string): boolean { const config = this.configs.get(configName); if (!config) { console.error(`Demo config not found: ${configName}`); return false; } return this.applyConfigurationNow(config.settings); } /** * Apply configuration settings immediately */ private static applyConfigurationNow(settings: Partial<AppSettings>): boolean { try { const settingsManager = SettingsManager.getInstance(); settingsManager.applyBulkSettings(settings); return true; } catch (error) { console.error('Failed to apply configuration:', error); return false; } } /** * Get available demo configurations */ static getAvailableConfigs(): DemoConfig[] { return Array.from(this.configs.values()); } /** * Get a specific demo configuration */ static getConfig(name: string): DemoConfig | undefined { return this.configs.get(name); } /** * Add a custom demo configuration */ static addCustomConfig(config: DemoConfig): void { this.configs.set(config.name, config); console.log(`➕ Added custom demo config: ${config.name}`); } /** * Remove a demo configuration */ static removeConfig(name: string): boolean { const removed = this.configs.delete(name); if (removed) { console.log(`➖ Removed demo config: ${name}`); } return removed; } /** * Generate shareable URL with current settings */ static generateShareableURL(baseURL?: string): string { const settingsManager = SettingsManager.getInstance(); const currentSettings = settingsManager.getSettings(); const url = new URL(baseURL || window.location.href); url.searchParams.set('settings', encodeURIComponent(JSON.stringify(currentSettings))); return url.toString(); } /** * Generate URL for a specific demo configuration */ static generateDemoURL(configName: string, baseURL?: string): string { const url = new URL(baseURL || window.location.href); url.searchParams.set('config', configName); return url.toString(); } /** * Generate URL with inline configuration */ static generateInlineConfigURL(settings: Partial<AppSettings>, baseURL?: string): string { const url = new URL(baseURL || window.location.href); url.searchParams.set('config', encodeURIComponent(JSON.stringify(settings))); return url.toString(); } /** * Export demo configuration as JSON */ static exportConfig(configName: string): string | null { const config = this.configs.get(configName); if (!config) { return null; } return JSON.stringify(config, null, 2); } /** * Import demo configuration from JSON */ static importConfig(jsonString: string): boolean { try { const config: DemoConfig = JSON.parse(jsonString); if (this.validateConfig(config)) { this.addCustomConfig(config); return true; } return false; } catch (error) { console.error('Failed to import demo config:', error); return false; } } /** * Validate demo configuration structure */ private static validateConfig(config: any): boolean { return ( typeof config === 'object' && typeof config.name === 'string' && typeof config.description === 'string' && typeof config.settings === 'object' && config.settings !== null ); } /** * Reset URL parameters (remove config/settings) */ static clearURLParams(): void { const url = new URL(window.location.href); url.searchParams.delete('config'); url.searchParams.delete('settings'); window.history.replaceState({}, document.title, url.toString()); console.log('🔗 Cleared URL configuration parameters'); } /** * Get configurations by tag */ static getConfigsByTag(tag: string): DemoConfig[] { return Array.from(this.configs.values()).filter(config => config.metadata?.tags?.includes(tag)); } /** * Search configurations by name or description */ static searchConfigs(query: string): DemoConfig[] { const lowerQuery = query.toLowerCase(); return Array.from(this.configs.values()).filter( config => config.name.toLowerCase().includes(lowerQuery) || config.description.toLowerCase().includes(lowerQuery) || config.metadata?.tags?.some(tag => tag.toLowerCase().includes(lowerQuery)) ); } }