1package messaging
2
3import (
4 "bytes"
5 "context"
6 "encoding/json"
7 "fmt"
8 "log"
9 "strconv"
10 "time"
11
12 "github.com/bnema/dumber/internal/app/constants"
13 "github.com/bnema/dumber/internal/app/control"
14 "github.com/bnema/dumber/internal/config"
15 "github.com/bnema/dumber/internal/logging"
16 "github.com/bnema/dumber/internal/services"
17 "github.com/bnema/dumber/pkg/webkit"
18)
19
20// Handler processes script messages from the WebView
21type WorkspaceObserver interface {
22 OnWorkspaceMessage(source *webkit.WebView, msg Message)
23}
24
25type Handler struct {
26 parserService *services.ParserService
27 browserService *services.BrowserService
28 webView *webkit.WebView
29 navigationController *control.NavigationController
30 lastTheme string
31 workspaceObserver WorkspaceObserver
32}
33
34// Message represents a script message from the WebView
35type Message struct {
36 Type string `json:"type"`
37 URL string `json:"url"`
38 Q string `json:"q"`
39 Limit int `json:"limit"`
40 Offset int `json:"offset"`
41 Value string `json:"value"`
42 Event string `json:"event"`
43 Action string `json:"action"`
44 Direction string `json:"direction"`
45 // History operations
46 HistoryID string `json:"historyId"`
47 // Request tracking
48 RequestID string `json:"requestId"`
49 // Popup close tracking
50 WebViewID string `json:"webviewId"`
51 Reason string `json:"reason"`
52 // Wails fetch bridge
53 ID string `json:"id"`
54 Payload json.RawMessage `json:"payload"`
55 // Favorites operations
56 Title string `json:"title"`
57 FaviconURL string `json:"faviconURL"`
58}
59
60// NewHandler creates a new message handler
61func NewHandler(parserService *services.ParserService, browserService *services.BrowserService) *Handler {
62 return &Handler{
63 parserService: parserService,
64 browserService: browserService,
65 }
66}
67
68// SetNavigationController injects the navigation controller for unified navigation flow.
69func (h *Handler) SetNavigationController(controller *control.NavigationController) {
70 h.navigationController = controller
71}
72
73// SetWorkspaceObserver registers a workspace event observer.
74func (h *Handler) SetWorkspaceObserver(observer WorkspaceObserver) {
75 h.workspaceObserver = observer
76}
77
78// Handle processes incoming script messages
79func (h *Handler) Handle(payload string) {
80 msg, err := parseIncomingMessage(payload)
81 if err != nil {
82 log.Printf("[ERROR] Failed to unmarshal message: %v", err)
83 return
84 }
85
86 switch msg.Type {
87 case "navigate":
88 h.handleNavigation(msg)
89 case "query":
90 if h.webView == nil {
91 return
92 }
93 h.handleQuery(msg)
94 case "wails":
95 h.handleWailsBridge(msg)
96 case "theme":
97 h.handleTheme(msg)
98 case "history_recent":
99 h.handleHistoryRecent(msg)
100 case "history_stats":
101 h.handleHistoryStats(msg)
102 case "history_search":
103 h.handleHistorySearch(msg)
104 case "history_delete":
105 h.handleHistoryDelete(msg)
106 case "workspace":
107 h.handleWorkspace(msg)
108 case "close-popup":
109 h.handleClosePopup(msg)
110 case "console-message":
111 h.handleConsoleMessage(msg)
112 case "request-webview-id":
113 h.handleWebViewIDRequest(msg)
114 case "get_search_shortcuts":
115 h.handleGetSearchShortcuts(msg)
116 case "get_color_palettes":
117 h.handleGetColorPalettes(msg)
118 case "get_favorites":
119 h.handleGetFavorites(msg)
120 case "toggle_favorite":
121 h.handleToggleFavorite(msg)
122 case "is_favorite":
123 h.handleIsFavorite(msg)
124 }
125}
126
127func parseIncomingMessage(payload string) (Message, error) {
128 data := []byte(payload)
129 var msg Message
130 if err := json.Unmarshal(data, &msg); err == nil {
131 return msg, nil
132 } else {
133 normalized, normErr := normalizeWebViewIDPayload(data)
134 if normErr != nil {
135 return Message{}, err
136 }
137
138 if err := json.Unmarshal(normalized, &msg); err != nil {
139 return Message{}, err
140 }
141
142 return msg, nil
143 }
144}
145
146func normalizeWebViewIDPayload(data []byte) ([]byte, error) {
147 var raw map[string]json.RawMessage
148 if err := json.Unmarshal(data, &raw); err != nil {
149 return nil, err
150 }
151
152 rawID, ok := raw["webviewId"]
153 if !ok {
154 return nil, fmt.Errorf("webviewId missing in payload")
155 }
156
157 normalizedID, err := parseWebViewIDRaw(rawID)
158 if err != nil {
159 return nil, err
160 }
161
162 raw["webviewId"] = json.RawMessage(strconv.Quote(normalizedID))
163
164 return json.Marshal(raw)
165}
166
167func parseWebViewIDRaw(raw json.RawMessage) (string, error) {
168 trimmed := bytes.TrimSpace(raw)
169 if len(trimmed) == 0 || bytes.Equal(trimmed, []byte("null")) {
170 return "", nil
171 }
172
173 if trimmed[0] == '"' {
174 var id string
175 if err := json.Unmarshal(trimmed, &id); err != nil {
176 return "", err
177 }
178 return id, nil
179 }
180
181 var number json.Number
182 if err := json.Unmarshal(trimmed, &number); err == nil {
183 return number.String(), nil
184 }
185
186 return "", fmt.Errorf("unsupported webviewId format: %s", string(trimmed))
187}
188
189// SetWebView sets the WebView reference (needed for script injection)
190func (h *Handler) SetWebView(webView *webkit.WebView) {
191 h.webView = webView
192}
193
194// handleNavigation processes navigation requests from the frontend
195func (h *Handler) handleNavigation(msg Message) {
196 if h.navigationController != nil {
197 if err := h.navigationController.NavigateToURL(msg.URL); err != nil {
198 log.Printf("[messaging] Navigation controller failed for input %q: %v", msg.URL, err)
199 h.legacyNavigate(msg)
200 }
201 return
202 }
203
204 h.legacyNavigate(msg)
205}
206
207func (h *Handler) legacyNavigate(msg Message) {
208 ctx := context.Background()
209 res, err := h.parserService.ParseInput(ctx, msg.URL)
210 if err != nil {
211 log.Printf("[messaging] Legacy navigation parse failed for %q: %v", msg.URL, err)
212 return
213 }
214
215 if _, navErr := h.browserService.Navigate(ctx, res.URL); navErr != nil {
216 log.Printf("Warning: failed to navigate to %s: %v", res.URL, navErr)
217 }
218
219 if h.webView == nil {
220 return
221 }
222
223 if err := h.webView.LoadURL(res.URL); err != nil {
224 log.Printf("[messaging] Legacy LoadURL failed for %s: %v", res.URL, err)
225 }
226
227 if z, zerr := h.browserService.GetZoomLevel(ctx, res.URL); zerr == nil {
228 if err := h.webView.SetZoom(z); err != nil {
229 log.Printf("[messaging] Legacy SetZoom failed for %s: %v", res.URL, err)
230 }
231 }
232}
233
234// (legacy query handler removed; omnibox suggestions now fetched via dumb://api/omnibox/suggestions)
235// handleQuery computes omnibox suggestions natively and returns them to the GUI without fetch
236func (h *Handler) handleWorkspace(msg Message) {
237 if h.workspaceObserver == nil {
238 log.Printf("[workspace] Received workspace event %q but no observer registered", msg.Event)
239 return
240 }
241 if h.webView == nil {
242 log.Printf("[workspace] Ignoring workspace event %q: webview not attached", msg.Event)
243 return
244 }
245 log.Printf("[workspace] Forwarding workspace event: event=%s direction=%s action=%s webviewId=%s", msg.Event, msg.Direction, msg.Action, msg.WebViewID)
246 h.workspaceObserver.OnWorkspaceMessage(h.webView, msg)
247}
248
249func (h *Handler) handleClosePopup(msg Message) {
250 log.Printf("[messaging] Received close-popup request: webviewId=%s reason=%s", msg.WebViewID, msg.Reason)
251
252 if h.workspaceObserver == nil {
253 log.Printf("[messaging] No workspace observer registered for close-popup request")
254 return
255 }
256 if h.webView == nil {
257 log.Printf("[messaging] No webview attached for close-popup request")
258 return
259 }
260
261 // Forward close-popup request to workspace manager via observer
262 closeMsg := Message{
263 Type: "workspace",
264 Event: "close-popup",
265 WebViewID: msg.WebViewID,
266 Reason: msg.Reason,
267 }
268
269 log.Printf("[messaging] Forwarding close-popup to workspace: webviewId=%s reason=%s", msg.WebViewID, msg.Reason)
270 h.workspaceObserver.OnWorkspaceMessage(h.webView, closeMsg)
271}
272
273func (h *Handler) handleQuery(msg Message) {
274 if h.webView == nil || h.browserService == nil {
275 return
276 }
277 ctx := context.Background()
278 q := msg.Q
279 if q == "" {
280 q = ""
281 }
282 limit := msg.Limit
283 if limit <= 0 {
284 limit = 10
285 }
286
287 // Build suggestions: shortcuts first, then history search
288 type suggestion struct {
289 URL string `json:"url"`
290 Favicon string `json:"favicon,omitempty"`
291 }
292
293 results := make([]suggestion, 0, limit)
294 seen := make(map[string]struct{}, limit*2)
295
296 // Shortcuts intentionally omitted; rely on explicit prefix commands (e.g., gh:) or history results
297
298 // History
299 if len(results) < limit {
300 remaining := limit - len(results)
301 if entries, err := h.browserService.SearchHistory(ctx, q, remaining); err == nil {
302 for _, e := range entries {
303 // JSON roundtrip to map to get url field agnostic of struct tag
304 bb, _ := json.Marshal(e)
305 var m map[string]any
306 _ = json.Unmarshal(bb, &m)
307 var url string
308 if s, ok := m["url"].(string); ok {
309 url = s
310 } else if s, ok := m["URL"].(string); ok {
311 url = s
312 }
313 if url == "" {
314 continue
315 }
316 if _, ok := seen[url]; ok {
317 continue
318 }
319 // Use favicon_url from database (populated by FaviconService)
320 favicon := ""
321 if s, ok := m["favicon_url"].(string); ok && s != "" {
322 favicon = s
323 }
324 results = append(results, suggestion{URL: url, Favicon: favicon})
325 seen[url] = struct{}{}
326 if len(results) >= limit {
327 break
328 }
329 }
330 }
331 }
332
333 // Inject back to GUI
334 if b, err := json.Marshal(results); err == nil {
335 // Prefer unified page-world API; fallback to legacy global function
336 script := "(window.__dumber?.omnibox?.suggestions ? window.__dumber.omnibox.suggestions(" + string(b) + ") : (window.__dumber_omnibox_suggestions && window.__dumber_omnibox_suggestions(" + string(b) + ")))"
337 if err := h.webView.InjectScript(script); err != nil {
338 log.Printf("[ERROR] Failed to inject omnibox suggestions: %v", err)
339 }
340 }
341}
342
343// handleWailsBridge processes Wails runtime bridge calls for homepage
344func (h *Handler) handleWailsBridge(msg Message) {
345 // Payload contains { methodID, methodName?, args }
346 var p struct {
347 MethodID uint32 `json:"methodID"`
348 MethodName string `json:"methodName"`
349 Args json.RawMessage `json:"args"`
350 }
351 if err := json.Unmarshal(msg.Payload, &p); err != nil {
352 return
353 }
354
355 // Only implement the IDs we need
356 switch p.MethodID {
357 case constants.HashGetRecentHistory: // BrowserService.GetRecentHistory(limit)
358 var args []interface{}
359 _ = json.Unmarshal(p.Args, &args)
360 limit := 50
361 if len(args) > 0 {
362 if f, ok := args[0].(float64); ok {
363 limit = int(f)
364 }
365 }
366 ctx := context.Background()
367 entries, err := h.browserService.GetRecentHistory(ctx, limit)
368 if err != nil {
369 return
370 }
371 resp, _ := json.Marshal(entries)
372 if h.webView != nil {
373 _ = h.webView.InjectScript("window.__dumber_wails_resolve('" + msg.ID + "', " + string(resp) + ")")
374 }
375 case constants.HashGetSearchShortcuts: // BrowserService.GetSearchShortcuts()
376 ctx := context.Background()
377 shortcuts, err := h.browserService.GetSearchShortcuts(ctx)
378 if err != nil {
379 return
380 }
381 resp, _ := json.Marshal(shortcuts)
382 if h.webView != nil {
383 _ = h.webView.InjectScript("window.__dumber_wails_resolve('" + msg.ID + "', " + string(resp) + ")")
384 }
385 default:
386 // Return empty JSON to avoid breaking UI
387 if h.webView != nil {
388 _ = h.webView.InjectScript("window.__dumber_wails_resolve('" + msg.ID + "', '{}')")
389 }
390 }
391}
392
393// handleTheme processes theme-related messages
394func (h *Handler) handleTheme(msg Message) {
395 if msg.Value != "" && msg.Value != h.lastTheme {
396 log.Printf("[theme] color-scheme changed: %s", msg.Value)
397 h.lastTheme = msg.Value
398 }
399}
400
401// handleHistoryRecent processes recent history requests
402func (h *Handler) handleHistoryRecent(msg Message) {
403 if h.browserService == nil || h.webView == nil {
404 return
405 }
406
407 ctx := context.Background()
408 limit := msg.Limit
409 if limit <= 0 {
410 limit = 20
411 }
412 offset := msg.Offset
413
414 var entries []interface{}
415 var err error
416
417 // Use paginated version if offset is provided
418 if offset > 0 {
419 histEntries, e := h.browserService.GetRecentHistoryWithOffset(ctx, limit, offset)
420 if e != nil {
421 err = e
422 } else {
423 entries = make([]interface{}, len(histEntries))
424 for i, entry := range histEntries {
425 entries[i] = entry
426 }
427 }
428 } else {
429 // Use original method for backward compatibility
430 histEntries, e := h.browserService.GetRecentHistory(ctx, limit)
431 if e != nil {
432 err = e
433 } else {
434 entries = make([]interface{}, len(histEntries))
435 for i, entry := range histEntries {
436 entries[i] = entry
437 }
438 }
439 }
440
441 if err != nil {
442 log.Printf("[messaging] Failed to get recent history: %v", err)
443 requestIdParam := ""
444 if msg.RequestID != "" {
445 requestIdParam = ", '" + msg.RequestID + "'"
446 }
447 _ = h.webView.InjectScript("window.__dumber_history_error && window.__dumber_history_error('Failed to load recent history'" + requestIdParam + ")")
448 return
449 }
450
451 b, _ := json.Marshal(entries)
452 requestIdParam := ""
453 if msg.RequestID != "" {
454 requestIdParam = ", '" + msg.RequestID + "'"
455 }
456 _ = h.webView.InjectScript("window.__dumber_history_recent && window.__dumber_history_recent(" + string(b) + requestIdParam + ")")
457}
458
459// handleHistoryStats processes history stats requests
460func (h *Handler) handleHistoryStats(_ Message) {
461 if h.browserService == nil || h.webView == nil {
462 return
463 }
464
465 ctx := context.Background()
466 stats, err := h.browserService.GetHistoryStats(ctx)
467 if err != nil {
468 log.Printf("[messaging] Failed to get history stats: %v", err)
469 _ = h.webView.InjectScript("window.__dumber_history_error && window.__dumber_history_error('Failed to load history stats')")
470 return
471 }
472
473 b, _ := json.Marshal(stats)
474 _ = h.webView.InjectScript("window.__dumber_history_stats && window.__dumber_history_stats(" + string(b) + ")")
475}
476
477// handleHistorySearch processes history search requests
478func (h *Handler) handleHistorySearch(msg Message) {
479 if h.browserService == nil || h.webView == nil {
480 return
481 }
482
483 ctx := context.Background()
484 query := msg.Q
485 limit := msg.Limit
486 if limit <= 0 {
487 limit = 5
488 }
489
490 entries, err := h.browserService.SearchHistory(ctx, query, limit)
491 if err != nil {
492 log.Printf("[messaging] Failed to search history: %v", err)
493 _ = h.webView.InjectScript("window.__dumber_history_error && window.__dumber_history_error('Failed to search history')")
494 return
495 }
496
497 b, _ := json.Marshal(entries)
498 _ = h.webView.InjectScript("window.__dumber_history_search && window.__dumber_history_search(" + string(b) + ")")
499}
500
501// handleHistoryDelete processes history deletion requests
502func (h *Handler) handleHistoryDelete(msg Message) {
503 if h.browserService == nil || h.webView == nil {
504 return
505 }
506
507 if msg.HistoryID == "" {
508 log.Printf("[messaging] History delete: missing historyId")
509 _ = h.webView.InjectScript("window.__dumber_history_error && window.__dumber_history_error('Missing history ID')")
510 return
511 }
512
513 // Convert string ID to int64
514 id, err := strconv.ParseInt(msg.HistoryID, 10, 64)
515 if err != nil {
516 log.Printf("[messaging] History delete: invalid ID format: %v", err)
517 _ = h.webView.InjectScript("window.__dumber_history_error && window.__dumber_history_error('Invalid history ID format')")
518 return
519 }
520
521 ctx := context.Background()
522 err = h.browserService.DeleteHistoryEntry(ctx, id)
523 if err != nil {
524 log.Printf("[messaging] Failed to delete history entry: %v", err)
525 _ = h.webView.InjectScript("window.__dumber_history_error && window.__dumber_history_error('Failed to delete history entry')")
526 return
527 }
528
529 // Send success response with the deleted ID
530 successData := map[string]string{"deletedId": msg.HistoryID}
531 b, _ := json.Marshal(successData)
532 _ = h.webView.InjectScript("window.__dumber_history_deleted && window.__dumber_history_deleted(" + string(b) + ")")
533}
534
535// handleConsoleMessage processes console-message from JavaScript
536func (h *Handler) handleConsoleMessage(msg Message) {
537 // Check if console capture is enabled
538 cfg := config.Get()
539 if !cfg.Logging.CaptureConsole {
540 return
541 }
542
543 // Parse the console message payload
544 var consolePayload struct {
545 Level string `json:"level"`
546 Message string `json:"message"`
547 URL string `json:"url"`
548 }
549
550 if err := json.Unmarshal(msg.Payload, &consolePayload); err != nil {
551 log.Printf("[messaging] Failed to unmarshal console-message: %v", err)
552 return
553 }
554
555 // Send to logging system with [CONSOLE] tag
556 logging.CaptureWebKitLog(consolePayload.Message)
557}
558
559// handleWebViewIDRequest responds to JavaScript requests for the webview ID
560func (h *Handler) handleWebViewIDRequest(msg Message) {
561 if h.webView == nil {
562 log.Printf("[messaging] Cannot provide webview ID - no webview available")
563 return
564 }
565
566 // Check if WebView is destroyed before attempting any operations
567 if h.webView.IsDestroyed() {
568 log.Printf("[messaging] Cannot provide webview ID - webview is destroyed")
569 return
570 }
571
572 webViewID := h.webView.ID()
573 log.Printf("[messaging] Sending webview ID %d to JavaScript", webViewID)
574
575 // Send the webview ID back to JavaScript via custom event
576 if err := h.webView.DispatchCustomEvent("dumber:webview-id", map[string]any{
577 "webviewId": webViewID,
578 "timestamp": time.Now().UnixMilli(),
579 }); err != nil {
580 log.Printf("[messaging] Failed to send webview ID: %v", err)
581 }
582}
583
584// handleGetSearchShortcuts sends search shortcuts configuration to JavaScript
585func (h *Handler) handleGetSearchShortcuts(msg Message) {
586 if h.webView == nil {
587 log.Printf("[messaging] Cannot provide search shortcuts - no webview available")
588 return
589 }
590
591 if h.webView.IsDestroyed() {
592 log.Printf("[messaging] Cannot provide search shortcuts - webview is destroyed")
593 return
594 }
595
596 // Get search shortcuts from browser service config
597 shortcuts, err := h.browserService.GetSearchShortcuts(context.Background())
598 if err != nil {
599 log.Printf("[messaging] Failed to get search shortcuts: %v", err)
600 _ = h.webView.InjectScript("window.__dumber_search_shortcuts_error && window.__dumber_search_shortcuts_error('Failed to get search shortcuts')")
601 return
602 }
603
604 // Marshal to JSON
605 b, err := json.Marshal(shortcuts)
606 if err != nil {
607 log.Printf("[messaging] Failed to marshal search shortcuts: %v", err)
608 _ = h.webView.InjectScript("window.__dumber_search_shortcuts_error && window.__dumber_search_shortcuts_error('Failed to load search shortcuts')")
609 return
610 }
611
612 // Inject the search shortcuts into the page
613 log.Printf("[messaging] Sending search shortcuts to JavaScript")
614 _ = h.webView.InjectScript("window.__dumber_search_shortcuts && window.__dumber_search_shortcuts(" + string(b) + ")")
615}
616
617// handleGetColorPalettes sends color palettes configuration to JavaScript
618func (h *Handler) handleGetColorPalettes(msg Message) {
619 if h.webView == nil {
620 log.Printf("[messaging] Cannot provide color palettes - no webview available")
621 return
622 }
623
624 if h.webView.IsDestroyed() {
625 log.Printf("[messaging] Cannot provide color palettes - webview is destroyed")
626 return
627 }
628
629 // Get color palettes from browser service config
630 palettes := h.browserService.GetColorPalettesForMessaging()
631
632 // Marshal to JSON
633 b, err := json.Marshal(palettes)
634 if err != nil {
635 log.Printf("[messaging] Failed to marshal color palettes: %v", err)
636 _ = h.webView.InjectScript("window.__dumber_color_palettes_error && window.__dumber_color_palettes_error('Failed to load color palettes')")
637 return
638 }
639
640 // Inject the color palettes into the page
641 log.Printf("[messaging] Sending color palettes to JavaScript")
642 _ = h.webView.InjectScript("window.__dumber_color_palettes && window.__dumber_color_palettes(" + string(b) + ")")
643}
644
645// handleGetFavorites sends all favorites to JavaScript
646func (h *Handler) handleGetFavorites(msg Message) {
647 if h.webView == nil {
648 log.Printf("[messaging] Cannot provide favorites - no webview available")
649 return
650 }
651
652 if h.webView.IsDestroyed() {
653 log.Printf("[messaging] Cannot provide favorites - webview is destroyed")
654 return
655 }
656
657 // Get favorites from browser service
658 favorites, err := h.browserService.GetFavorites(context.Background())
659 if err != nil {
660 log.Printf("[messaging] Failed to get favorites: %v", err)
661 _ = h.webView.InjectScript("window.__dumber_favorites_error && window.__dumber_favorites_error('Failed to get favorites')")
662 return
663 }
664
665 // Marshal to JSON
666 b, err := json.Marshal(favorites)
667 if err != nil {
668 log.Printf("[messaging] Failed to marshal favorites: %v", err)
669 _ = h.webView.InjectScript("window.__dumber_favorites_error && window.__dumber_favorites_error('Failed to load favorites')")
670 return
671 }
672
673 // Inject the favorites into the page
674 log.Printf("[messaging] Sending %d favorites to JavaScript", len(favorites))
675 _ = h.webView.InjectScript("window.__dumber_favorites && window.__dumber_favorites(" + string(b) + ")")
676}
677
678// handleToggleFavorite adds or removes a URL from favorites
679func (h *Handler) handleToggleFavorite(msg Message) {
680 if h.webView == nil {
681 log.Printf("[messaging] Cannot toggle favorite - no webview available")
682 return
683 }
684
685 if h.webView.IsDestroyed() {
686 log.Printf("[messaging] Cannot toggle favorite - webview is destroyed")
687 return
688 }
689
690 if msg.URL == "" {
691 log.Printf("[messaging] Cannot toggle favorite - URL is empty")
692 return
693 }
694
695 // Toggle the favorite
696 added, err := h.browserService.ToggleFavorite(context.Background(), msg.URL, msg.Title, msg.FaviconURL)
697 if err != nil {
698 log.Printf("[messaging] Failed to toggle favorite for %s: %v", msg.URL, err)
699 _ = h.webView.InjectScript("window.__dumber_favorite_toggled_error && window.__dumber_favorite_toggled_error('Failed to toggle favorite')")
700 return
701 }
702
703 // Send result back to JavaScript
704 result := map[string]interface{}{
705 "url": msg.URL,
706 "added": added,
707 }
708 b, err := json.Marshal(result)
709 if err != nil {
710 log.Printf("[messaging] Failed to marshal toggle result: %v", err)
711 return
712 }
713
714 log.Printf("[messaging] Favorite toggled for %s (added: %v)", msg.URL, added)
715 _ = h.webView.InjectScript("window.__dumber_favorite_toggled && window.__dumber_favorite_toggled(" + string(b) + ")")
716}
717
718// handleIsFavorite checks if a URL is favorited
719func (h *Handler) handleIsFavorite(msg Message) {
720 if h.webView == nil {
721 log.Printf("[messaging] Cannot check favorite - no webview available")
722 return
723 }
724
725 if h.webView.IsDestroyed() {
726 log.Printf("[messaging] Cannot check favorite - webview is destroyed")
727 return
728 }
729
730 if msg.URL == "" {
731 log.Printf("[messaging] Cannot check favorite - URL is empty")
732 return
733 }
734
735 // Check if the URL is favorited
736 isFavorite, err := h.browserService.IsFavorite(context.Background(), msg.URL)
737 if err != nil {
738 log.Printf("[messaging] Failed to check if favorite for %s: %v", msg.URL, err)
739 return
740 }
741
742 // Send result back to JavaScript
743 result := map[string]interface{}{
744 "url": msg.URL,
745 "isFavorite": isFavorite,
746 }
747 b, err := json.Marshal(result)
748 if err != nil {
749 log.Printf("[messaging] Failed to marshal is_favorite result: %v", err)
750 return
751 }
752
753 _ = h.webView.InjectScript("window.__dumber_is_favorite && window.__dumber_is_favorite(" + string(b) + ")")
754}