Skip to content

Architecture Overview

This document provides a comprehensive overview of Smyles Station's architecture, design decisions, and technical implementation.

Table of Contents

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-password
admin:change-password
SessionModule Time-limited sessions session:start
session:end
session:get-remaining-time
UsageStatsModule Track usage statistics stats:record
stats:get
ShutdownScheduleModule Automatic shutdowns shutdown:get-schedule
shutdown:set-schedule
WindowManager Window lifecycle window:create
window: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

  1. Multi-user support - Different profiles for different kids
  2. Cloud sync - Sync settings across multiple kiosks
  3. Parental reports - Email usage reports to parents
  4. Content filtering - Built-in web content filtering
  5. 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:

  1. Document the problem
  2. Propose solution with security analysis
  3. Consider backward compatibility
  4. Update this document
  5. Get review from maintainers

For implementation details, see the Contributing Guide. For development setup, see Local Development Setup.