UNPKG

@lobehub/chat

Version:

Lobe Chat - an open-source, high-performance chatbot framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.

257 lines (215 loc) • 8.78 kB
import { app } from 'electron'; import { isDev } from '@/const/env'; import { createLogger } from '@/utils/logger'; import { getProtocolScheme, parseProtocolUrl } from '@/utils/protocol'; import { App } from '../App'; // Create logger const logger = createLogger('core:ProtocolManager'); /** * Protocol handler manager for custom URI schemes */ export class ProtocolManager { private app: App; private protocolScheme: string; private pendingUrls: string[] = []; constructor(app: App) { logger.debug('Initializing ProtocolManager'); this.app = app; this.protocolScheme = getProtocolScheme(); logger.info(`ProtocolManager initialized for scheme: ${this.protocolScheme}://`); } /** * Register protocol handlers and set up event listeners */ public initialize(): void { logger.debug('Setting up protocol handlers'); this.registerProtocolHandlers(); this.setupEventListeners(); logger.debug('Protocol initialization completed'); } /** * Register the application as default protocol client */ private registerProtocolHandlers(): void { logger.debug(`🔗 [Protocol] Registering protocol handlers for ${this.protocolScheme}://`); // Debug info about current app logger.debug(`🔗 [Protocol] App name: ${app.name}`); logger.debug(`🔗 [Protocol] App path: ${app.getPath('exe')}`); logger.debug(`🔗 [Protocol] Is development: ${isDev}`); logger.debug(`🔗 [Protocol] Process argv[0]: ${process.argv[0]}`); // Check if already registered const isCurrentlyRegistered = app.isDefaultProtocolClient(this.protocolScheme); logger.debug(`🔗 [Protocol] Is currently default protocol client: ${isCurrentlyRegistered}`); // Register as default protocol client let registrationResult: boolean; if (isDev) { // In development, use explicit parameters to ensure proper registration const appPath = process.cwd(); // Current working directory (our app) logger.debug(`🔗 [Protocol] Development mode: using explicit registration parameters`); logger.debug(`🔗 [Protocol] Executable path: ${process.execPath}`); logger.debug(`🔗 [Protocol] App path: ${appPath}`); logger.debug(`🔗 [Protocol] Arguments: ${JSON.stringify([appPath])}`); registrationResult = app.setAsDefaultProtocolClient(this.protocolScheme, process.execPath, [ appPath, ]); } else { // In production, use simple registration registrationResult = app.setAsDefaultProtocolClient(this.protocolScheme); } logger.debug(`🔗 [Protocol] Registration result: ${registrationResult}`); if (!registrationResult) { logger.error( `🔗 [Protocol] Failed to register as default protocol client for ${this.protocolScheme}://`, ); } else { logger.debug(`🔗 [Protocol] Successfully registered ${this.protocolScheme}:// protocol`); } // Verify registration const isRegisteredAfter = app.isDefaultProtocolClient(this.protocolScheme); logger.debug(`🔗 [Protocol] Final registration status: ${isRegisteredAfter}`); } /** * Set up protocol event listeners */ private setupEventListeners(): void { // Handle protocol URL from cold start (Windows/Linux) const protocolUrl = this.getProtocolUrlFromArgs(process.argv); if (protocolUrl) { logger.debug(`🔗 [Protocol] Found protocol URL from cold start: ${protocolUrl}`); this.pendingUrls.push(protocolUrl); } // Handle protocol URL from macOS open-url event app.on('open-url', (event, url) => { event.preventDefault(); logger.debug(`🔗 [Protocol] Received URL from open-url event: ${url}`); logger.debug(`🔗 [Protocol] App ready state: ${app.isReady()}`); logger.debug(`🔗 [Protocol] Event prevented, processing URL...`); this.handleProtocolUrl(url); }); // Handle protocol URL from second instance (Windows/Linux) app.on('second-instance', (event, commandLine) => { const url = this.getProtocolUrlFromArgs(commandLine); if (url) { logger.debug(`🔗 [Protocol] Received protocol URL from second instance: ${url}`); this.handleProtocolUrl(url); } // Show main window when second instance is triggered this.app.browserManager.showMainWindow(); }); } /** * Extract protocol URL from command line arguments */ private getProtocolUrlFromArgs(args: string[]): string | null { const protocolPrefix = `${this.protocolScheme}://`; logger.debug(`🔗 [Protocol] Searching for protocol URLs in args: ${JSON.stringify(args)}`); logger.debug(`🔗 [Protocol] Looking for prefix: ${protocolPrefix}`); for (const arg of args) { if (arg.startsWith(protocolPrefix)) { logger.debug(`🔗 [Protocol] Found protocol URL in args: ${arg}`); return arg; } } logger.debug(`🔗 [Protocol] No protocol URL found in args`); return null; } /** * Handle protocol URL - either immediately or store for later processing */ private handleProtocolUrl(url: string): void { try { logger.debug(`🔗 [Protocol] handleProtocolUrl called with: ${url}`); logger.debug(`🔗 [Protocol] App ready state: ${app.isReady()}`); logger.debug(`🔗 [Protocol] Current pending URLs count: ${this.pendingUrls.length}`); if (!app.isReady()) { // App not ready yet, store for later processing logger.debug('🔗 [Protocol] App not ready, storing protocol URL for later processing'); this.pendingUrls.push(url); logger.debug(`🔗 [Protocol] Pending URLs after push: ${this.pendingUrls.length}`); return; } // App is ready, process immediately logger.debug('🔗 [Protocol] App is ready, processing URL immediately'); this.processProtocolUrl(url); } catch (error) { logger.error('🔗 [Protocol] Failed to handle protocol URL:', error); } } /** * Process protocol URL by showing main window and sending to renderer */ private async processProtocolUrl(url: string): Promise<void> { try { logger.debug(`🔗 [Protocol] processProtocolUrl called with: ${url}`); // Basic URL validation - just check if it's our protocol if (!url.startsWith(`${this.protocolScheme}://`)) { logger.warn(`🔗 [Protocol] Invalid protocol scheme in URL: ${url}`); return; } // Show main window logger.debug('🔗 [Protocol] Showing main window...'); this.app.browserManager.showMainWindow(); // Parse protocol URL to extract urlType and action const parsed = parseProtocolUrl(url); if (!parsed) { logger.warn(`🔗 [Protocol] Failed to parse protocol URL: ${url}`); return; } logger.debug( `🔗 [Protocol] Parsed URL - type: ${parsed.urlType}, action: ${parsed.action}, data: %s`, parsed.params, ); // Dispatch to registered protocol handlers via App with parsed data logger.debug('🔗 [Protocol] Dispatching to protocol handlers...'); const handled = await this.app.handleProtocolRequest( parsed.urlType, parsed.action, parsed.params, ); if (handled) { logger.debug('🔗 [Protocol] Protocol URL processed successfully by handler'); } else { logger.warn( `🔗 [Protocol] No handler found for protocol: ${parsed.urlType}:${parsed.action}`, ); } } catch (error) { logger.error('🔗 [Protocol] Failed to process protocol URL:', error); logger.error('🔗 [Protocol] Error details:', error); } } /** * Process any pending protocol URLs after app is ready */ public async processPendingUrls(): Promise<void> { logger.debug(`🔗 [Protocol] processPendingUrls called`); logger.debug(`🔗 [Protocol] Pending URLs count: ${this.pendingUrls.length}`); if (this.pendingUrls.length === 0) { logger.debug(`🔗 [Protocol] No pending URLs to process`); return; } logger.debug( `🔗 [Protocol] Processing ${this.pendingUrls.length} pending protocol URLs:`, this.pendingUrls, ); for (const url of this.pendingUrls) { logger.debug(`🔗 [Protocol] Processing pending URL: ${url}`); await this.processProtocolUrl(url); } // Clear pending URLs this.pendingUrls = []; logger.debug(`🔗 [Protocol] All pending URLs processed and cleared`); } /** * Get current protocol scheme */ public getScheme(): string { return this.protocolScheme; } /** * Check if protocol is registered */ public isRegistered(): boolean { return app.isDefaultProtocolClient(this.protocolScheme); } }