Skip to content

Commit 1bdf08f

Browse files
committed
feat(webui): add Phase 3 WebUI server implementation (WIP)
Copied all Phase 3 WebUI server components from FlashForgeUI-Electron alpha branch: **WebUI Server Core:** - AuthManager.ts (379 lines) - JWT-style token generation, session management - auth-middleware.ts (198 lines) - Express middleware for auth, rate limiting - WebSocketManager.ts - Real-time communication with authentication - WebUIManager.ts - Express HTTP server orchestrator (Electron dependencies removed) - api-routes.ts - Main router orchestrator **API Routes (10 modules):** - camera-routes.ts - Camera proxy and streaming endpoints - context-routes.ts - Multi-printer context management - filtration-routes.ts - Air filtration control - job-routes.ts - Print job management (start, pause, resume, cancel) - printer-control-routes.ts - Printer control commands - printer-status-routes.ts - Status polling and feature detection - spoolman-routes.ts - Spoolman filament tracking integration - temperature-routes.ts - Temperature monitoring - theme-routes.ts - UI theme settings - route-helpers.ts - Shared utilities and dependency contracts **Types & Schemas:** - web-api.types.ts - WebSocket message types, API response types - web-api.schemas.ts - Zod validation schemas - camera.ts (updated) - Added URL resolution types, validation types - gcode.ts (new) - GCode command result types **Utilities:** - camera-utils.ts (242 lines) - Camera config resolution, URL validation - Priority-based resolution (custom > builtin > none) - MJPEG and RTSP stream type detection - Context-aware settings retrieval **Phase 1 Managers (from reference):** - ConnectionFlowManager.ts (1280 lines) - Connection orchestration - PrinterBackendManager.ts (from FlashForgeUI-Electron) **Electron Removal:** - Removed all electron imports from WebUIManager - Removed EnvironmentDetectionService dependency - Removed HeadlessDetection utility - Simplified to pure Node.js Express/WebSocket server - Static files served from dist/webui directory **Configuration:** - Updated tsconfig.json to exclude Electron files - Only includes: managers, printer-backends, services, types, utils, webui **Known Issues (Type Errors):** The copied Phase 1 managers (ConnectionFlowManager, PrinterBackendManager) have dependencies on Phase 1 services not yet ported: - LoadingManager (UI-specific, needs headless adaptation) - DialogIntegrationService (UI-specific, needs removal) - PrinterDiscoveryService, SavedPrinterService, AutoConnectService - ConnectionStateManager, ConnectionEstablishmentService - ThumbnailRequestQueue, validation.utils Next steps: Either port missing Phase 1 services or create minimal adapters for WebUI-only functionality to complete Phase 3.
1 parent 4de206f commit 1bdf08f

23 files changed

Lines changed: 6648 additions & 5 deletions

src/managers/ConnectionFlowManager.ts

Lines changed: 1280 additions & 0 deletions
Large diffs are not rendered by default.

src/managers/PrinterBackendManager.ts

Lines changed: 871 additions & 0 deletions
Large diffs are not rendered by default.

src/types/camera.ts

Lines changed: 90 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
/**
2-
* @fileoverview Camera type definitions for camera proxy system
2+
* @fileoverview Comprehensive type definitions for camera proxy system
33
*
4-
* Provides type safety for camera configuration, proxy server management,
5-
* stream URL resolution, and client connection tracking.
4+
* Provides complete type safety for camera configuration, proxy server management,
5+
* stream URL resolution, and client connection tracking. Supports both built-in printer
6+
* cameras (MJPEG/RTSP) and custom camera URLs with proper validation and type guards.
67
*/
78

9+
import { PrinterFeatureSet } from './printer-backend';
10+
811
/**
912
* Camera source types
1013
*/
@@ -38,6 +41,44 @@ export interface CameraProxyConfig {
3841
};
3942
}
4043

44+
/**
45+
* Camera configuration from user settings
46+
*/
47+
export interface CameraUserConfig {
48+
/** Whether custom camera is enabled */
49+
readonly customCameraEnabled: boolean;
50+
/** Custom camera URL if enabled */
51+
readonly customCameraUrl: string | null;
52+
}
53+
54+
/**
55+
* Resolved camera configuration after applying priority logic
56+
*/
57+
export interface ResolvedCameraConfig {
58+
/** Source type of the camera */
59+
readonly sourceType: CameraSourceType;
60+
/** Stream protocol type (MJPEG or RTSP) */
61+
readonly streamType?: CameraStreamType;
62+
/** Final camera stream URL (null if no camera available) */
63+
readonly streamUrl: string | null;
64+
/** Whether camera feature is available */
65+
readonly isAvailable: boolean;
66+
/** Reason if camera is not available */
67+
readonly unavailableReason?: string;
68+
}
69+
70+
/**
71+
* Camera URL resolution parameters
72+
*/
73+
export interface CameraUrlResolutionParams {
74+
/** Printer IP address */
75+
readonly printerIpAddress: string;
76+
/** Printer feature set from backend */
77+
readonly printerFeatures: PrinterFeatureSet;
78+
/** User configuration for camera */
79+
readonly userConfig: CameraUserConfig;
80+
}
81+
4182
/**
4283
* Camera proxy client information
4384
*/
@@ -116,3 +157,49 @@ export interface CameraProxyEvent {
116157
/** Error message if applicable */
117158
readonly error?: string;
118159
}
160+
161+
/**
162+
* Camera URL builder function type
163+
*/
164+
export type CameraUrlBuilder = (ipAddress: string) => string;
165+
166+
/**
167+
* Default camera URL patterns for different printer models
168+
*/
169+
export const DEFAULT_CAMERA_PATTERNS = {
170+
/** Default MJPEG stream pattern for FlashForge printers */
171+
FLASHFORGE_MJPEG: (ip: string) => `http://${ip}:8080/?action=stream`,
172+
} as const;
173+
174+
/**
175+
* Camera validation result
176+
*/
177+
export interface CameraUrlValidationResult {
178+
/** Whether the URL is valid */
179+
readonly isValid: boolean;
180+
/** Validation error message if invalid */
181+
readonly error?: string;
182+
/** Parsed URL object if valid */
183+
readonly parsedUrl?: URL;
184+
}
185+
186+
/**
187+
* Type guard to check if a camera source is available
188+
*/
189+
export function isCameraAvailable(config: ResolvedCameraConfig): config is ResolvedCameraConfig & { streamUrl: string } {
190+
return config.isAvailable && config.streamUrl !== null;
191+
}
192+
193+
/**
194+
* Type guard to check if using custom camera
195+
*/
196+
export function isCustomCamera(config: ResolvedCameraConfig): boolean {
197+
return config.sourceType === 'custom';
198+
}
199+
200+
/**
201+
* Type guard to check if using built-in camera
202+
*/
203+
export function isBuiltinCamera(config: ResolvedCameraConfig): boolean {
204+
return config.sourceType === 'builtin';
205+
}

src/types/gcode.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
/**
2+
* @fileoverview GCode command types and result structures
3+
*/
4+
5+
/**
6+
* GCode command execution result
7+
*/
8+
export interface GCodeCommandResult {
9+
readonly success: boolean;
10+
readonly output?: string;
11+
readonly error?: string;
12+
}

src/utils/camera-utils.ts

Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
1+
/**
2+
* @fileoverview Camera configuration resolution and validation utilities implementing priority-based
3+
* camera URL selection logic. Supports both built-in printer cameras and custom camera URLs (MJPEG/RTSP),
4+
* with context-aware settings retrieval for multi-printer environments. Provides stream type detection,
5+
* URL validation, and human-readable status messaging.
6+
*
7+
* Key Features:
8+
* - Priority-based camera resolution: custom camera > built-in camera > none
9+
* - MJPEG and RTSP stream type detection and validation
10+
* - Context-aware camera configuration (per-printer or global settings)
11+
* - Automatic URL generation for custom cameras without explicit URLs
12+
* - Comprehensive URL validation (protocol, hostname, format)
13+
* - Camera availability checking with detailed unavailability reasons
14+
* - Proxy URL formatting for client consumption
15+
*
16+
* Resolution Priority:
17+
* 1. Custom camera (if enabled): Uses user-provided URL or auto-generates default FlashForge URL
18+
* 2. Built-in camera: Uses default FlashForge MJPEG pattern if printer supports camera
19+
* 3. No camera: Returns unavailable status with reason
20+
*
21+
* Stream Types Supported:
22+
* - MJPEG (Motion JPEG over HTTP/HTTPS)
23+
* - RTSP (Real-Time Streaming Protocol)
24+
*
25+
* Context Awareness:
26+
* - Supports per-printer camera settings when contextId is provided
27+
* - Falls back to global configuration for backward compatibility
28+
* - Integrates with PrinterContextManager for multi-printer camera configurations
29+
*
30+
* Usage:
31+
* - resolveCameraConfig(): Main resolution function with comprehensive config object
32+
* - validateCameraUrl(): Standalone URL validation with detailed error messages
33+
* - getCameraUserConfig(): Context-aware settings retrieval
34+
* - isCameraFeatureAvailable(): Boolean availability check
35+
*/
36+
37+
import {
38+
CameraUrlResolutionParams,
39+
ResolvedCameraConfig,
40+
CameraUrlValidationResult,
41+
CameraUserConfig,
42+
CameraStreamType,
43+
DEFAULT_CAMERA_PATTERNS
44+
} from '../types/camera';
45+
import { getConfigManager } from '../managers/ConfigManager';
46+
import { getPrinterContextManager } from '../managers/PrinterContextManager';
47+
48+
/**
49+
* Detect stream type from camera URL
50+
*
51+
* @param url - Camera URL to analyze
52+
* @returns Stream type (mjpeg or rtsp)
53+
*/
54+
export function detectStreamType(url: string): CameraStreamType {
55+
try {
56+
const parsedUrl = new URL(url);
57+
return parsedUrl.protocol === 'rtsp:' ? 'rtsp' : 'mjpeg';
58+
} catch {
59+
// Default to MJPEG for invalid URLs
60+
return 'mjpeg';
61+
}
62+
}
63+
64+
/**
65+
* Validate a camera URL
66+
*/
67+
export function validateCameraUrl(url: string | null | undefined): CameraUrlValidationResult {
68+
if (!url || url.trim() === '') {
69+
return {
70+
isValid: false,
71+
error: 'URL is empty or not provided'
72+
};
73+
}
74+
75+
try {
76+
const parsedUrl = new URL(url);
77+
78+
// Check for supported protocols
79+
if (!['http:', 'https:', 'rtsp:'].includes(parsedUrl.protocol)) {
80+
return {
81+
isValid: false,
82+
error: `Unsupported protocol: ${parsedUrl.protocol}. Use http://, https://, or rtsp://`
83+
};
84+
}
85+
86+
// Check for valid hostname
87+
if (!parsedUrl.hostname || parsedUrl.hostname === '') {
88+
return {
89+
isValid: false,
90+
error: 'Invalid hostname in URL'
91+
};
92+
}
93+
94+
return {
95+
isValid: true,
96+
parsedUrl
97+
};
98+
} catch {
99+
return {
100+
isValid: false,
101+
error: 'Invalid URL format'
102+
};
103+
}
104+
}
105+
106+
/**
107+
* Resolve camera configuration based on priority rules
108+
*/
109+
export function resolveCameraConfig(params: CameraUrlResolutionParams): ResolvedCameraConfig {
110+
const { printerIpAddress, printerFeatures, userConfig } = params;
111+
112+
// Priority 1: Check custom camera
113+
if (userConfig.customCameraEnabled) {
114+
// If custom camera is enabled but no URL provided, use automatic URL
115+
if (!userConfig.customCameraUrl || userConfig.customCameraUrl.trim() === '') {
116+
// Use the default FlashForge camera URL pattern when custom camera is enabled
117+
// but no URL is specified. This supports cameras installed on printers that
118+
// don't have them by default.
119+
const autoUrl = `http://${printerIpAddress}:8080/?action=stream`;
120+
121+
122+
return {
123+
sourceType: 'custom',
124+
streamType: 'mjpeg', // Auto URL is always MJPEG
125+
streamUrl: autoUrl,
126+
isAvailable: true
127+
};
128+
}
129+
130+
// Custom camera enabled with a user-provided URL
131+
const validation = validateCameraUrl(userConfig.customCameraUrl);
132+
133+
if (validation.isValid) {
134+
return {
135+
sourceType: 'custom',
136+
streamType: detectStreamType(userConfig.customCameraUrl),
137+
streamUrl: userConfig.customCameraUrl,
138+
isAvailable: true
139+
};
140+
} else {
141+
// Custom camera enabled but URL is invalid
142+
return {
143+
sourceType: 'custom',
144+
streamUrl: null,
145+
isAvailable: false,
146+
unavailableReason: `Custom camera URL is invalid: ${validation.error}`
147+
};
148+
}
149+
}
150+
151+
// Priority 2: Check built-in camera
152+
if (printerFeatures.camera.builtin) {
153+
// Use default FlashForge MJPEG pattern
154+
const streamUrl = DEFAULT_CAMERA_PATTERNS.FLASHFORGE_MJPEG(printerIpAddress);
155+
156+
157+
return {
158+
sourceType: 'builtin',
159+
streamType: 'mjpeg', // Built-in cameras are always MJPEG
160+
streamUrl,
161+
isAvailable: true
162+
};
163+
}
164+
165+
// Priority 3: No camera available
166+
return {
167+
sourceType: 'none',
168+
streamUrl: null,
169+
isAvailable: false,
170+
unavailableReason: 'Printer does not have built-in camera and custom camera is not configured'
171+
};
172+
}
173+
174+
/**
175+
* Get camera configuration from user settings
176+
* Now context-aware: reads from per-printer settings if contextId provided,
177+
* otherwise falls back to global config (for backward compatibility)
178+
*
179+
* @param contextId - Optional context ID to get per-printer camera settings
180+
* @returns Camera user configuration
181+
*/
182+
export function getCameraUserConfig(contextId?: string): CameraUserConfig {
183+
const configManager = getConfigManager();
184+
185+
// If contextId provided, try to get per-printer settings first
186+
if (contextId) {
187+
const contextManager = getPrinterContextManager();
188+
const context = contextManager.getContext(contextId);
189+
190+
if (context?.printerDetails) {
191+
const { customCameraEnabled, customCameraUrl } = context.printerDetails;
192+
193+
// Per-printer settings override global config
194+
if (customCameraEnabled !== undefined) {
195+
return {
196+
customCameraEnabled,
197+
customCameraUrl: customCameraUrl || null
198+
};
199+
}
200+
}
201+
}
202+
203+
// Fall back to global config
204+
return {
205+
customCameraEnabled: configManager.get('CustomCamera') || false,
206+
customCameraUrl: configManager.get('CustomCameraUrl') || null
207+
};
208+
}
209+
210+
/**
211+
* Format camera proxy URL for client consumption
212+
*/
213+
export function formatCameraProxyUrl(port: number): string {
214+
return `http://localhost:${port}/stream`;
215+
}
216+
217+
/**
218+
* Check if camera feature is available for a printer
219+
*/
220+
export function isCameraFeatureAvailable(params: CameraUrlResolutionParams): boolean {
221+
const config = resolveCameraConfig(params);
222+
return config.isAvailable;
223+
}
224+
225+
/**
226+
* Get human-readable camera status message
227+
*/
228+
export function getCameraStatusMessage(config: ResolvedCameraConfig): string {
229+
if (config.isAvailable) {
230+
switch (config.sourceType) {
231+
case 'builtin':
232+
return 'Using printer built-in camera';
233+
case 'custom':
234+
return 'Using custom camera URL';
235+
default:
236+
return 'Camera available';
237+
}
238+
} else {
239+
return config.unavailableReason || 'Camera not available';
240+
}
241+
}

0 commit comments

Comments
 (0)