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 },
58 {
59 id: 2,
60 url: "https://go.dev",
61 title: "The Go Programming Language",
62 visit_count: 2,
63 last_visited: new Date().toISOString(),
64 created_at: new Date().toISOString(),
65 },
66 {
67 id: 3,
68 url: "https://developer.mozilla.org",
69 title: "MDN Web Docs",
70 visit_count: 1,
71 last_visited: new Date().toISOString(),
72 created_at: new Date().toISOString(),
73 },
74 ];
75 }
76 }
77
78 async loadShortcuts(): Promise<void> {
79 try {
80 const cfg = await fetch("/api/config").then((r) => r.json());
81 const raw = cfg?.search_shortcuts || {};
82 // Normalize field casing from backend (supports URL/Description and url/description)
83 const normalized: Record<string, SearchShortcut> = {};
84 for (const [key, value] of Object.entries(raw)) {
85 const v = value as Record<string, unknown>;
86 normalized[key] = {
87 id: 0,
88 shortcut: key,
89 url_template: (v.url ?? v.URL ?? "") as string,
90 description: (v.description ?? v.Description ?? "") as string,
91 created_at: null,
92 };
93 }
94 this.shortcuts = normalized;
95 } catch (error) {
96 console.error("Failed to load shortcuts:", error);
97 // Mock shortcuts for development/fallback
98 this.shortcuts = {
99 g: {
100 id: 0,
101 shortcut: "g",
102 url_template: "https://google.com/search?q={query}",
103 description: "Google Search",
104 created_at: null,
105 },
106 gh: {
107 id: 0,
108 shortcut: "gh",
109 url_template: "https://github.com/search?q={query}",
110 description: "GitHub Search",
111 created_at: null,
112 },
113 so: {
114 id: 0,
115 shortcut: "so",
116 url_template: "https://stackoverflow.com/search?q={query}",
117 description: "Stack Overflow",
118 created_at: null,
119 },
120 w: {
121 id: 0,
122 shortcut: "w",
123 url_template:
124 "https://en.wikipedia.org/wiki/Special:Search?search={query}",
125 description: "Wikipedia",
126 created_at: null,
127 },
128 };
129 }
130 }
131
132 getHistory(): HistoryEntry[] {
133 return this.history;
134 }
135
136 getShortcuts(): Record<string, SearchShortcut> {
137 return this.shortcuts;
138 }
139
140 async copyToClipboard(text: string): Promise<void> {
141 try {
142 await navigator.clipboard.writeText(text);
143 console.log("Copied to clipboard:", text);
144 } catch (err) {
145 console.error("Failed to copy:", err);
146 }
147 }
148}