package main import ( "log" "verstak/internal/core/bridge" "verstak/internal/core/browser" "verstak/internal/core/config" ) // startBridge creates and starts the local HTTP bridge for browser extension. func (a *App) startBridge(appCfg *config.AppConfig) { // Determine bridge config bc := a.bridgeConfig(appCfg) handler := func(events []bridge.Event) { // Convert to browser events and store in staging. be := make([]browser.Event, 0, len(events)) for _, ev := range events { be = append(be, bridgeToBrowser(ev)) } n, err := a.browser.InsertEvents(be) if err != nil { log.Printf("[bridge] store events: %v", err) return } if n > 0 { log.Printf("[bridge] stored %d/%d events", n, len(be)) } } srv := bridge.NewServer(bridge.Config{ Port: bc.Port, Secret: bc.Secret, }, handler) port, err := srv.Start(bridge.Config{ Port: bc.Port, AutoGenPort: bc.AutoGenPort, Secret: bc.Secret, }) if err != nil { log.Printf("[bridge] failed to start: %v", err) return } // Save the actual port and secret back to config if auto-generated. if bc.AutoGenPort { bc.Port = port } if bc.Secret == "" { bc.Secret = srv.Secret() } a.saveBridgeConfig(appCfg, bc) a.mu.Lock() a.bridge = srv a.mu.Unlock() } // bridgeConfig extracts bridge config from app config, generating defaults if needed. func (a *App) bridgeConfig(appCfg *config.AppConfig) *config.BridgeConfig { if appCfg != nil && appCfg.Vault.Bridge.Port != 0 { bc := &appCfg.Vault.Bridge // If secret is empty, generate one on first run if bc.Secret == "" { bc.Secret = bridge.GenerateSecret() } return bc } return &config.BridgeConfig{ Port: 9786, AutoGenPort: true, Secret: bridge.GenerateSecret(), } } // saveBridgeConfig persists the bridge config to disk. func (a *App) saveBridgeConfig(appCfg *config.AppConfig, bc *config.BridgeConfig) { if appCfg == nil { // Load or create fresh loaded, err := config.LoadAppConfig() if err != nil || loaded == nil { loaded = config.DefaultAppConfig() } appCfg = loaded } appCfg.Vault.Bridge = *bc if err := config.SaveAppConfig(appCfg); err != nil { log.Printf("[bridge] save config: %v", err) } } // BridgeInfo returns the current bridge server status. func (a *App) BridgeInfo() map[string]interface{} { info := map[string]interface{}{ "running": false, "port": 0, } if a.bridge != nil { info["running"] = a.bridge.Running() info["port"] = a.bridge.Port() info["secret"] = a.bridge.Secret() } return info } // bridgeToBrowser converts a bridge.Event to a browser.Event. func bridgeToBrowser(ev bridge.Event) browser.Event { deviceID := ev.ID if idx := indexOf(ev.ID, "_"); idx > 0 { deviceID = ev.ID[:idx] } return browser.Event{ ID: ev.ID, DeviceID: deviceID, Type: ev.Type, URL: ev.URL, Title: ev.Title, Domain: ev.Domain, ActiveSeconds: ev.ActiveSeconds, TSStart: ev.TSStart, TSEnd: ev.TSEnd, TS: ev.TS, SelectedText: ev.SelectedText, Note: ev.Note, } } func indexOf(s string, sub string) int { for i := 0; i < len(s); i++ { if string(s[i]) == sub { return i } } return -1 }