Architecture Overview
This document provides a comprehensive overview of Smyles Station's architecture, design decisions, and technical implementation.
Table of Contents
- High-Level Architecture
- Process Model
- Security Architecture
- Module System
- Data Flow
- Key Components
- Design Decisions
High-Level Architecture
Smyles Station is built on Electron, following a security-first architecture with strict process isolation.
graph TB
subgraph "Smyles Station"
subgraph "Renderer Processes"
R1[Renderer<br/>Admin UI]
R2[Renderer<br/>Game View]
R3[Renderer<br/>Selection UI]
end
P[Preload<br/>Secure Bridge]
M[Main Process<br/>Node.js Runtime]
subgraph "Modules"
SM[Session<br/>Manager]
SEC[Security<br/>Modules]
SS[Shutdown<br/>Schedule]
end
end
subgraph "System"
OS[OS<br/>Services]
FS[File System<br/>Config]
end
R1 --> P
R2 --> P
R3 --> P
P --> M
M --> SM
M --> SEC
M --> SS
M --> OS
M --> FS
Process Model
Main Process
Location: packages/main/src/
Responsibilities: - Application lifecycle management - Window creation and management - IPC communication handling - System-level operations (shutdown, notifications) - Security enforcement (URL blocking, session limits) - Data persistence (config, usage stats)
Key Files:
- index.ts - Application entry point
- modules/ - Feature implementations
Preload Scripts
Location: packages/preload/src/
Responsibilities: - Expose safe APIs to renderer - Bridge between renderer and main - Type-safe IPC wrapper functions - Context isolation enforcement
Key Files:
- index.ts - Exported API surface
Security Guarantees:
- Runs in renderer context but has Node.js access
- Limited to specific Electron APIs
- Functions auto-exposed via contextBridge
Renderer Processes
Location: packages/renderer/src/
Responsibilities: - User interface rendering - User interaction handling - State management (React) - Calling preload APIs
Key Files:
- App.tsx - Main application component
- components/ - UI components
Restrictions: - No Node.js APIs available - Sandboxed environment - Can only communicate via preload bridge
Security Architecture
Security is paramount since Smyles Station is designed for children.
Defense in Depth
graph TD
L1[Layer 1: OS-level kiosk mode<br/>external configuration]
L2[Layer 2: Electron process isolation<br/>sandboxed renderer]
L3[Layer 3: Context isolation<br/>preload bridge]
L4[Layer 4: URL whitelist enforcement<br/>security modules]
L5[Layer 5: Session time limits<br/>session module]
L6[Layer 6: Admin password protection<br/>admin module]
L1 --> L2
L2 --> L3
L3 --> L4
L4 --> L5
L5 --> L6
Process Isolation
// Main process configuration (packages/main/src/index.ts)
const browserWindow = new BrowserWindow({
webPreferences: {
sandbox: true, // Renderer is sandboxed
contextIsolation: true, // Separate contexts
nodeIntegration: false, // No Node.js in renderer
preload: preloadPath, // Secure bridge only
},
});
URL Blocking
Module: BlockNotAllowedOrigins.ts
graph TD
A[User navigates to URL]
B{Is URL in whitelist?}
C[Block navigation<br/>Show warning]
D[Allow navigation]
A --> B
B -->|No| C
B -->|Yes| D
Session Security
Module: SessionModule.ts
- Time-limited access (configurable per session)
- Automatic cleanup on expiry
- Warning alerts before timeout
- Cannot be bypassed from renderer
Module System
All features are implemented as self-contained modules in the main process.
Module Pattern
export class MyModule {
constructor(private readonly browserWindow: BrowserWindow) {
this.init();
}
private init() {
// Setup IPC handlers
ipcMain.handle('my-module:action', this.handleAction.bind(this));
// Setup event listeners
this.browserWindow.on('event', this.handleEvent.bind(this));
}
private async handleAction() {
// Implementation
}
}
Core Modules
| Module | Purpose | IPC Channels |
|---|---|---|
AdminModule |
Password-protected admin access | admin:verify-passwordadmin:change-password |
SessionModule |
Time-limited sessions | session:startsession:endsession:get-remaining-time |
UsageStatsModule |
Track usage statistics | stats:recordstats:get |
ShutdownScheduleModule |
Automatic shutdowns | shutdown:get-scheduleshutdown:set-schedule |
WindowManager |
Window lifecycle | window:createwindow:close |
BlockNotAllowedOrigins |
URL enforcement | (event-based, no IPC) |
Data Flow
Example: Starting a Session
sequenceDiagram
participant UI as Renderer<br/>SessionSelector.tsx
participant P as Preload<br/>index.ts
participant M as Main Process<br/>SessionModule.ts
participant G as Game View
UI->>UI: User clicks "Start Session"
UI->>P: startSession(siteUrl, duration)
P->>M: ipcRenderer.invoke('session:start', ...)
M->>M: Validate duration
M->>M: Create timer
M->>M: Record start in stats
M->>G: Load site in game view
M->>P: Return session ID
P->>UI: Session ID
UI->>UI: Show session timer
G->>G: Display website
Example: URL Blocking
sequenceDiagram
participant W as Website
participant E as Electron Event
participant B as BlockNotAllowedOrigins.ts
participant D as Dialog
W->>E: Attempts navigation
E->>B: 'will-navigate' event
B->>B: Check URL against whitelist
alt URL allowed
B->>W: Allow navigation
else URL not allowed
B->>E: event.preventDefault()
B->>D: Show warning dialog
end
Key Components
Admin Dashboard
Location: packages/renderer/src/components/AdminDashboard.tsx
Features: - Password protection - Site management (add/edit/remove) - Usage statistics display - Shutdown schedule configuration - Session management
Access Control:
// Password verified in main process
const isValid = await verifyAdminPassword(password);
if (isValid) {
setIsAuthenticated(true);
}
Session Manager
Location: packages/main/src/modules/SessionModule.ts
Responsibilities: - Start/end sessions - Track remaining time - Emit warnings before expiry - Clean up on session end
Implementation:
class SessionModule {
private currentSession: {
startTime: number;
duration: number;
timer: NodeJS.Timeout;
} | null = null;
async startSession(duration: number) {
this.currentSession = {
startTime: Date.now(),
duration,
timer: setTimeout(() => this.endSession(), duration),
};
}
}
Shutdown Scheduler
Location: packages/main/src/modules/ShutdownScheduleModule.ts
Features: - Per-day scheduling - 10-minute warning - Platform-specific shutdown (Windows/Linux) - Cannot be cancelled once triggered
Shutdown Methods:
// Windows
exec('shutdown /s /t 0');
// Linux
exec('shutdown -h now');
Design Decisions
Why Electron?
Pros: - Cross-platform (Windows, Linux, macOS) - Sandboxed browser views for untrusted content - Mature security model - Rich ecosystem
Cons: - Larger bundle size - More memory usage
Decision: Security and cross-platform support outweigh resource concerns for a kiosk application.
Why Monorepo?
Benefits: - Clear separation of concerns - Independent testing per package - Shared TypeScript configuration - Type safety across boundaries
Why IPC over Direct Imports?
Renderer cannot import main process code due to security:
// Not possible (renderer is sandboxed)
import {startSession} from '../../main/src/modules/SessionModule';
// Must use IPC bridge
import {startSession} from '@app/preload';
await startSession(siteUrl, duration);
Why Module Pattern?
Benefits: - Self-contained features - Easy to test in isolation - Clear ownership - Simple to add/remove features
Example:
// Adding a new feature is just instantiating a module
new MyNewFeatureModule(browserWindow);
Configuration Storage
Location: User data directory
- Windows: %APPDATA%/smyles-station/
- Linux: ~/.config/smyles-station/
- macOS: ~/Library/Application Support/smyles-station/
Format: JSON files
- config.json - App configuration
- sites.json - Whitelisted sites
- schedule.json - Shutdown schedule
- stats.json - Usage statistics
Why JSON? - Human-readable - Easy to backup/restore - No database dependencies
Performance Considerations
Memory Management
- Each game runs in a separate sandboxed renderer
- Game views are destroyed on session end
- Only one game view active at a time
Startup Time
- Lazy module initialization
- Vite for fast dev builds
- Electron-builder for optimized production builds
Bundle Size
- Tree-shaking enabled
- No unnecessary dependencies in renderer
- Native Node.js APIs used where possible
Future Considerations
Potential Enhancements
- Multi-user support - Different profiles for different kids
- Cloud sync - Sync settings across multiple kiosks
- Parental reports - Email usage reports to parents
- Content filtering - Built-in web content filtering
- Remote management - Admin panel accessible from another device
Scalability
Current design supports: - Single kiosk installation - Hundreds of whitelisted sites - Years of usage statistics
For library systems with multiple kiosks, consider: - Centralized configuration management - Shared usage statistics database - Remote monitoring and updates
Security Audit Checklist
- [x] Context isolation enabled
- [x] Sandbox enabled for renderers
- [x] Node integration disabled in renderer
- [x] Remote module disabled
- [x] URL whitelist enforced
- [x] Admin password protected
- [x] No eval() or similar dangerous functions
- [x] Input validation on all IPC handlers
- [x] Safe external URL handling
Contributing to Architecture
When proposing architectural changes:
- Document the problem
- Propose solution with security analysis
- Consider backward compatibility
- Update this document
- Get review from maintainers
For implementation details, see the Contributing Guide. For development setup, see Local Development Setup.