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
text/typescript
/**
* @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))
);
}
}