1import type { HistoryEntry, SearchShortcut } from "../types/generated.js";
2
3export class AppService {
4 private history: HistoryEntry[] = [];
5 private shortcuts: Record<string, SearchShortcut> = {};
6
7 constructor() {}
8
9 async initialize(): Promise<void> {
10 try {
11 await Promise.all([this.loadHistory(), this.loadShortcuts()]);
12 } catch (error) {
13 console.error("Failed to initialize app:", error);
14 }
15 }
16
17 async loadHistory(): Promise<void> {
18 try {
19 // Use message bridge instead of fetch
20 return new Promise((resolve, reject) => {
21 // Set up response handler
22 window.__dumber_history_recent = (data: HistoryEntry[]) => {
23 this.history = Array.isArray(data) ? data : [];
24 resolve();
25 };
26
27 window.__dumber_history_error = (error: string) => {
28 console.error("Failed to load history:", error);
29 reject(new Error(error));
30 };
31
32 // Send message to Go backend
33 const bridge = window.webkit?.messageHandlers?.dumber;
34 if (bridge && typeof bridge.postMessage === "function") {
35 bridge.postMessage(
36 JSON.stringify({
37 type: "history_recent",
38 limit: 50,
39 offset: 0,
40 }),
41 );
42 } else {
43 reject(new Error("WebKit message handler not available"));
44 }
45 });
46 } catch (error) {
47 console.error("Failed to load history:", error);
48 // Mock history for development/fallback
49 this.history = [
50 {
51 id: 1,
52 url: "https://github.com/wailsapp/wails",
53 title: "Wails Framework",
54 visit_count: 3,
55 last_visited: new Date().toISOString(),
56 created_at: new Date().toISOString(),
57 favicon_url: null,
58 },
59 {
60 id: 2,
61 url: "https://go.dev",
62 title: "The Go Programming Language",
63 visit_count: 2,
64 last_visited: new Date().toISOString(),
65 created_at: new Date().toISOString(),
66 favicon_url: null,
67 },
68 {
69 id: 3,
70 url: "https://developer.mozilla.org",
71 title: "MDN Web Docs",
72 visit_count: 1,
73 last_visited: new Date().toISOString(),
74 created_at: new Date().toISOString(),
75 favicon_url: null,
76 },
77 ];
78 }
79 }
80
81 async loadShortcuts(): Promise<void> {
82 try {
83 const cfg = await fetch("/api/config").then((r) => r.json());
84 const raw = cfg?.search_shortcuts || {};
85 // Normalize field casing from backend (supports URL/Description and url/description)
86 const normalized: Record<string, SearchShortcut> = {};
87 for (const [key, value] of Object.entries(raw)) {
88 const v = value as Record<string, unknown>;
89 normalized[key] = {
90 id: 0,
91 shortcut: key,
92 url_template: (v.url ?? v.URL ?? "") as string,
93 description: (v.description ?? v.Description ?? "") as string,
94 created_at: null,
95 };
96 }
97 this.shortcuts = normalized;
98 } catch (error) {
99 console.error("Failed to load shortcuts:", error);
100 // Mock shortcuts for development/fallback
101 this.shortcuts = {
102 g: {
103 id: 0,
104 shortcut: "g",
105 url_template: "https://google.com/search?q={query}",
106 description: "Google Search",
107 created_at: null,
108 },
109 gh: {
110 id: 0,
111 shortcut: "gh",
112 url_template: "https://github.com/search?q={query}",
113 description: "GitHub Search",
114 created_at: null,
115 },
116 so: {
117 id: 0,
118 shortcut: "so",
119 url_template: "https://stackoverflow.com/search?q={query}",
120 description: "Stack Overflow",
121 created_at: null,
122 },
123 w: {
124 id: 0,
125 shortcut: "w",
126 url_template:
127 "https://en.wikipedia.org/wiki/Special:Search?search={query}",
128 description: "Wikipedia",
129 created_at: null,
130 },
131 };
132 }
133 }
134
135 getHistory(): HistoryEntry[] {
136 return this.history;
137 }
138
139 getShortcuts(): Record<string, SearchShortcut> {
140 return this.shortcuts;
141 }
142
143 async copyToClipboard(text: string): Promise<void> {
144 try {
145 await navigator.clipboard.writeText(text);
146 console.log("Copied to clipboard:", text);
147 } catch (err) {
148 console.error("Failed to copy:", err);
149 }
150 }
151}