refactor(homepage): migrate from http fetch to webkit message bridge for api communication
- commit
- d5420a9139bed6133a071d2736484ca7a960b8fd
- author
- bamen · · 2025-09-16 08:13 UTC
- parent
- 324e093
- signature
- unsigned
2 files · +199 -49
M gui/src/pages/Homepage.svelte +174 -47
| @@ -108,20 +108,110 @@ | |||
| 108 | 108 | shortcutsLoading = false; | |
| 109 | 109 | }; | |
| 110 | 110 | ||
| 111 | - | // Fetch recent history from the API | |
| 111 | + | // Setup message bridge callbacks (once) | |
| 112 | + | let callbacksInitialized = false; | |
| 113 | + | const pendingRequests = new Map(); | |
| 114 | + | ||
| 115 | + | const initializeCallbacks = () => { | |
| 116 | + | if (callbacksInitialized) return; | |
| 117 | + | ||
| 118 | + | (window as any).__dumber_history_recent = (data: any[], requestId?: string) => { | |
| 119 | + | const request = pendingRequests.get(requestId || 'default'); | |
| 120 | + | if (request) { | |
| 121 | + | request.resolve(data); | |
| 122 | + | pendingRequests.delete(requestId || 'default'); | |
| 123 | + | } | |
| 124 | + | }; | |
| 125 | + | ||
| 126 | + | (window as any).__dumber_history_error = (error: string, requestId?: string) => { | |
| 127 | + | const request = pendingRequests.get(requestId || 'default'); | |
| 128 | + | if (request) { | |
| 129 | + | request.reject(new Error(error)); | |
| 130 | + | pendingRequests.delete(requestId || 'default'); | |
| 131 | + | } | |
| 132 | + | }; | |
| 133 | + | ||
| 134 | + | // Omnibox suggestions callback - forward to injected script via DOM event bridge | |
| 135 | + | let _pendingSuggestions: any[] | null = null; | |
| 136 | + | (window as any).__dumber_omnibox_suggestions = (suggestions: any[]) => { | |
| 137 | + | try { | |
| 138 | + | console.log('🎯 [HOMEPAGE] Received omnibox suggestions:', suggestions?.length || 0, 'items'); | |
| 139 | + | // Dispatch an event that injected-world listener can consume | |
| 140 | + | const evt = new CustomEvent('dumber:omnibox-suggestions', { | |
| 141 | + | detail: { suggestions } | |
| 142 | + | }); | |
| 143 | + | document.dispatchEvent(evt); | |
| 144 | + | _pendingSuggestions = null; // clear cache on successful dispatch | |
| 145 | + | } catch (err) { | |
| 146 | + | console.warn('⚠️ [HOMEPAGE] Failed to dispatch suggestions event, caching:', err); | |
| 147 | + | _pendingSuggestions = suggestions; | |
| 148 | + | } | |
| 149 | + | }; | |
| 150 | + | ||
| 151 | + | // If omnibox gets ready later, re-send any pending suggestions | |
| 152 | + | document.addEventListener('dumber:omnibox-ready', () => { | |
| 153 | + | if (_pendingSuggestions && Array.isArray(_pendingSuggestions)) { | |
| 154 | + | try { | |
| 155 | + | const evt = new CustomEvent('dumber:omnibox-suggestions', { | |
| 156 | + | detail: { suggestions: _pendingSuggestions } | |
| 157 | + | }); | |
| 158 | + | document.dispatchEvent(evt); | |
| 159 | + | _pendingSuggestions = null; | |
| 160 | + | console.log('🔄 [HOMEPAGE] Re-dispatched pending suggestions after omnibox-ready'); | |
| 161 | + | } catch (e) { | |
| 162 | + | console.warn('⚠️ [HOMEPAGE] Failed to re-dispatch pending suggestions:', e); | |
| 163 | + | } | |
| 164 | + | } | |
| 165 | + | }); | |
| 166 | + | ||
| 167 | + | callbacksInitialized = true; | |
| 168 | + | }; | |
| 169 | + | ||
| 170 | + | const sendHistoryRequest = (type: string, params: any): Promise<any[]> => { | |
| 171 | + | initializeCallbacks(); | |
| 172 | + | ||
| 173 | + | const requestId = `${type}_${Date.now()}_${Math.random()}`; | |
| 174 | + | ||
| 175 | + | return new Promise((resolve, reject) => { | |
| 176 | + | pendingRequests.set(requestId, { resolve, reject }); | |
| 177 | + | ||
| 178 | + | // Timeout after 10 seconds | |
| 179 | + | setTimeout(() => { | |
| 180 | + | if (pendingRequests.has(requestId)) { | |
| 181 | + | pendingRequests.delete(requestId); | |
| 182 | + | reject(new Error('Request timeout')); | |
| 183 | + | } | |
| 184 | + | }, 10000); | |
| 185 | + | ||
| 186 | + | try { | |
| 187 | + | const bridge = (window as any).webkit?.messageHandlers?.dumber; | |
| 188 | + | if (bridge && typeof bridge.postMessage === 'function') { | |
| 189 | + | bridge.postMessage(JSON.stringify({ | |
| 190 | + | ...params, | |
| 191 | + | type, | |
| 192 | + | requestId | |
| 193 | + | })); | |
| 194 | + | } else { | |
| 195 | + | reject(new Error('WebKit message handler not available')); | |
| 196 | + | } | |
| 197 | + | } catch (error) { | |
| 198 | + | pendingRequests.delete(requestId); | |
| 199 | + | reject(error); | |
| 200 | + | } | |
| 201 | + | }); | |
| 202 | + | }; | |
| 203 | + | ||
| 204 | + | // Fetch recent history via message bridge | |
| 112 | 205 | const fetchHistory = async () => { | |
| 113 | 206 | try { | |
| 114 | - | const response = await fetch('dumb://homepage/api/history/recent?limit=20'); | |
| 115 | - | if (response.ok) { | |
| 116 | - | const data = await response.json(); | |
| 117 | - | historyItems = Array.isArray(data) ? data : []; | |
| 118 | - | historyOffset = historyItems.length; | |
| 119 | - | hasMoreHistory = data.length === 20; // If we got full batch, there might be more | |
| 120 | - | } else { | |
| 121 | - | console.warn('Failed to fetch history:', response.status); | |
| 122 | - | historyItems = []; | |
| 123 | - | hasMoreHistory = false; | |
| 124 | - | } | |
| 207 | + | const data = await sendHistoryRequest('history_recent', { | |
| 208 | + | limit: 20, | |
| 209 | + | offset: 0 | |
| 210 | + | }); | |
| 211 | + | ||
| 212 | + | historyItems = Array.isArray(data) ? data : []; | |
| 213 | + | historyOffset = historyItems.length; | |
| 214 | + | hasMoreHistory = data.length === 20; | |
| 125 | 215 | } catch (error) { | |
| 126 | 216 | console.error('Error fetching history:', error); | |
| 127 | 217 | historyItems = []; | |
| @@ -137,18 +227,16 @@ | |||
| 137 | 227 | ||
| 138 | 228 | loadingMoreHistory = true; | |
| 139 | 229 | try { | |
| 140 | - | const response = await fetch(`dumb://homepage/api/history/recent?limit=20&offset=${historyOffset}`); | |
| 141 | - | if (response.ok) { | |
| 142 | - | const data = await response.json(); | |
| 143 | - | if (Array.isArray(data) && data.length > 0) { | |
| 144 | - | historyItems = [...historyItems, ...data]; | |
| 145 | - | historyOffset += data.length; | |
| 146 | - | hasMoreHistory = data.length === 20; // If we got full batch, there might be more | |
| 147 | - | } else { | |
| 148 | - | hasMoreHistory = false; | |
| 149 | - | } | |
| 230 | + | const data = await sendHistoryRequest('history_recent', { | |
| 231 | + | limit: 20, | |
| 232 | + | offset: historyOffset | |
| 233 | + | }); | |
| 234 | + | ||
| 235 | + | if (Array.isArray(data) && data.length > 0) { | |
| 236 | + | historyItems = [...historyItems, ...data]; | |
| 237 | + | historyOffset += data.length; | |
| 238 | + | hasMoreHistory = data.length === 20; // If we got full batch, there might be more | |
| 150 | 239 | } else { | |
| 151 | - | console.warn('Failed to fetch more history:', response.status); | |
| 152 | 240 | hasMoreHistory = false; | |
| 153 | 241 | } | |
| 154 | 242 | } catch (error) { | |
| @@ -174,8 +262,8 @@ | |||
| 174 | 262 | } | |
| 175 | 263 | ||
| 176 | 264 | try { | |
| 177 | - | const response = await fetch(`dumb://homepage/api/history/delete?id=${id}`); | |
| 178 | - | if (response.ok) { | |
| 265 | + | // Set up response handlers | |
| 266 | + | (window as any).__dumber_history_deleted = async () => { | |
| 179 | 267 | // Wait a bit for the fade animation to complete | |
| 180 | 268 | await new Promise(resolve => setTimeout(resolve, 200)); | |
| 181 | 269 | ||
| @@ -185,19 +273,32 @@ | |||
| 185 | 273 | await fetchHistory(); | |
| 186 | 274 | await fetchStats(); | |
| 187 | 275 | await fetchTopVisited(); | |
| 188 | - | } else { | |
| 189 | - | console.error('Failed to delete history entry:', response.status); | |
| 276 | + | ||
| 277 | + | // Remove from deleting array after refresh | |
| 278 | + | deletingIds = deletingIds.filter(i => i !== id); | |
| 279 | + | }; | |
| 280 | + | ||
| 281 | + | (window as any).__dumber_history_error = (error: string) => { | |
| 282 | + | console.error('Error deleting history entry:', error); | |
| 190 | 283 | // Remove from deleting array if failed | |
| 191 | 284 | deletingIds = deletingIds.filter(i => i !== id); | |
| 285 | + | }; | |
| 286 | + | ||
| 287 | + | // Send message to Go backend | |
| 288 | + | const bridge = (window as any).webkit?.messageHandlers?.dumber; | |
| 289 | + | if (bridge && typeof bridge.postMessage === 'function') { | |
| 290 | + | bridge.postMessage(JSON.stringify({ | |
| 291 | + | type: 'history_delete', | |
| 292 | + | historyId: id.toString() | |
| 293 | + | })); | |
| 294 | + | } else { | |
| 295 | + | throw new Error('WebKit message handler not available'); | |
| 192 | 296 | } | |
| 193 | 297 | } catch (error) { | |
| 194 | - | console.error('Error deleting history entry:', error); | |
| 298 | + | console.error('Error sending delete request:', error); | |
| 195 | 299 | // Remove from deleting array if failed | |
| 196 | 300 | deletingIds = deletingIds.filter(i => i !== id); | |
| 197 | 301 | } | |
| 198 | - | ||
| 199 | - | // Remove from deleting array after refresh | |
| 200 | - | deletingIds = deletingIds.filter(i => i !== id); | |
| 201 | 302 | }; | |
| 202 | 303 | ||
| 203 | 304 | // Get domain from URL | |
| @@ -231,40 +332,66 @@ | |||
| 231 | 332 | } | |
| 232 | 333 | }; | |
| 233 | 334 | ||
| 234 | - | // Fetch history stats from the API | |
| 335 | + | // Fetch history stats via message bridge | |
| 235 | 336 | const fetchStats = async () => { | |
| 236 | 337 | try { | |
| 237 | - | const response = await fetch('dumb://homepage/api/history/stats'); | |
| 238 | - | if (response.ok) { | |
| 239 | - | const data = await response.json(); | |
| 338 | + | // Set up response handler | |
| 339 | + | (window as any).__dumber_history_stats = (data: any) => { | |
| 240 | 340 | stats = data; | |
| 241 | - | } else { | |
| 242 | - | console.warn('Failed to fetch stats:', response.status); | |
| 341 | + | statsLoading = false; | |
| 342 | + | }; | |
| 343 | + | ||
| 344 | + | (window as any).__dumber_history_error = (error: string) => { | |
| 345 | + | console.error('Error fetching stats:', error); | |
| 243 | 346 | stats = null; | |
| 347 | + | statsLoading = false; | |
| 348 | + | }; | |
| 349 | + | ||
| 350 | + | // Send message to Go backend | |
| 351 | + | const bridge = (window as any).webkit?.messageHandlers?.dumber; | |
| 352 | + | if (bridge && typeof bridge.postMessage === 'function') { | |
| 353 | + | bridge.postMessage(JSON.stringify({ | |
| 354 | + | type: 'history_stats' | |
| 355 | + | })); | |
| 356 | + | } else { | |
| 357 | + | throw new Error('WebKit message handler not available'); | |
| 244 | 358 | } | |
| 245 | 359 | } catch (error) { | |
| 246 | - | console.error('Error fetching stats:', error); | |
| 360 | + | console.error('Error sending stats request:', error); | |
| 247 | 361 | stats = null; | |
| 248 | - | } finally { | |
| 249 | 362 | statsLoading = false; | |
| 250 | 363 | } | |
| 251 | 364 | }; | |
| 252 | 365 | ||
| 253 | - | // Fetch top visited sites using search endpoint sorted by visit count | |
| 366 | + | // Fetch top visited sites via message bridge | |
| 254 | 367 | const fetchTopVisited = async () => { | |
| 255 | 368 | try { | |
| 256 | - | const response = await fetch('dumb://homepage/api/history/search?q=&limit=5'); | |
| 257 | - | if (response.ok) { | |
| 258 | - | const data = await response.json(); | |
| 369 | + | // Set up response handler | |
| 370 | + | (window as any).__dumber_history_search = (data: any[]) => { | |
| 259 | 371 | topVisited = Array.isArray(data) ? data.slice(0, 5) : []; | |
| 260 | - | } else { | |
| 261 | - | console.warn('Failed to fetch top visited:', response.status); | |
| 372 | + | topVisitedLoading = false; | |
| 373 | + | }; | |
| 374 | + | ||
| 375 | + | (window as any).__dumber_history_error = (error: string) => { | |
| 376 | + | console.error('Error fetching top visited:', error); | |
| 262 | 377 | topVisited = []; | |
| 378 | + | topVisitedLoading = false; | |
| 379 | + | }; | |
| 380 | + | ||
| 381 | + | // Send message to Go backend | |
| 382 | + | const bridge = (window as any).webkit?.messageHandlers?.dumber; | |
| 383 | + | if (bridge && typeof bridge.postMessage === 'function') { | |
| 384 | + | bridge.postMessage(JSON.stringify({ | |
| 385 | + | type: 'history_search', | |
| 386 | + | q: '', | |
| 387 | + | limit: 5 | |
| 388 | + | })); | |
| 389 | + | } else { | |
| 390 | + | throw new Error('WebKit message handler not available'); | |
| 263 | 391 | } | |
| 264 | 392 | } catch (error) { | |
| 265 | - | console.error('Error fetching top visited:', error); | |
| 393 | + | console.error('Error sending top visited request:', error); | |
| 266 | 394 | topVisited = []; | |
| 267 | - | } finally { | |
| 268 | 395 | topVisitedLoading = false; | |
| 269 | 396 | } | |
| 270 | 397 | }; | |
| @@ -108,20 +108,110 @@ | |||||
| 108 | shortcutsLoading = false; | 108 | shortcutsLoading = false; | ||
| 109 | }; | 109 | }; | ||
| 110 | 110 | ||||
| 111 | - | // Fetch recent history from the API | 111 | + | // Setup message bridge callbacks (once) |
| 112 | + | let callbacksInitialized = false; | |||
| 113 | + | const pendingRequests = new Map(); | |||
| 114 | + | ||||
| 115 | + | const initializeCallbacks = () => { | |||
| 116 | + | if (callbacksInitialized) return; | |||
| 117 | + | ||||
| 118 | + | (window as any).__dumber_history_recent = (data: any[], requestId?: string) => { | |||
| 119 | + | const request = pendingRequests.get(requestId || 'default'); | |||
| 120 | + | if (request) { | |||
| 121 | + | request.resolve(data); | |||
| 122 | + | pendingRequests.delete(requestId || 'default'); | |||
| 123 | + | } | |||
| 124 | + | }; | |||
| 125 | + | ||||
| 126 | + | (window as any).__dumber_history_error = (error: string, requestId?: string) => { | |||
| 127 | + | const request = pendingRequests.get(requestId || 'default'); | |||
| 128 | + | if (request) { | |||
| 129 | + | request.reject(new Error(error)); | |||
| 130 | + | pendingRequests.delete(requestId || 'default'); | |||
| 131 | + | } | |||
| 132 | + | }; | |||
| 133 | + | ||||
| 134 | + | // Omnibox suggestions callback - forward to injected script via DOM event bridge | |||
| 135 | + | let _pendingSuggestions: any[] | null = null; | |||
| 136 | + | (window as any).__dumber_omnibox_suggestions = (suggestions: any[]) => { | |||
| 137 | + | try { | |||
| 138 | + | console.log('🎯 [HOMEPAGE] Received omnibox suggestions:', suggestions?.length || 0, 'items'); | |||
| 139 | + | // Dispatch an event that injected-world listener can consume | |||
| 140 | + | const evt = new CustomEvent('dumber:omnibox-suggestions', { | |||
| 141 | + | detail: { suggestions } | |||
| 142 | + | }); | |||
| 143 | + | document.dispatchEvent(evt); | |||
| 144 | + | _pendingSuggestions = null; // clear cache on successful dispatch | |||
| 145 | + | } catch (err) { | |||
| 146 | + | console.warn('⚠️ [HOMEPAGE] Failed to dispatch suggestions event, caching:', err); | |||
| 147 | + | _pendingSuggestions = suggestions; | |||
| 148 | + | } | |||
| 149 | + | }; | |||
| 150 | + | ||||
| 151 | + | // If omnibox gets ready later, re-send any pending suggestions | |||
| 152 | + | document.addEventListener('dumber:omnibox-ready', () => { | |||
| 153 | + | if (_pendingSuggestions && Array.isArray(_pendingSuggestions)) { | |||
| 154 | + | try { | |||
| 155 | + | const evt = new CustomEvent('dumber:omnibox-suggestions', { | |||
| 156 | + | detail: { suggestions: _pendingSuggestions } | |||
| 157 | + | }); | |||
| 158 | + | document.dispatchEvent(evt); | |||
| 159 | + | _pendingSuggestions = null; | |||
| 160 | + | console.log('🔄 [HOMEPAGE] Re-dispatched pending suggestions after omnibox-ready'); | |||
| 161 | + | } catch (e) { | |||
| 162 | + | console.warn('⚠️ [HOMEPAGE] Failed to re-dispatch pending suggestions:', e); | |||
| 163 | + | } | |||
| 164 | + | } | |||
| 165 | + | }); | |||
| 166 | + | ||||
| 167 | + | callbacksInitialized = true; | |||
| 168 | + | }; | |||
| 169 | + | ||||
| 170 | + | const sendHistoryRequest = (type: string, params: any): Promise<any[]> => { | |||
| 171 | + | initializeCallbacks(); | |||
| 172 | + | ||||
| 173 | + | const requestId = `${type}_${Date.now()}_${Math.random()}`; | |||
| 174 | + | ||||
| 175 | + | return new Promise((resolve, reject) => { | |||
| 176 | + | pendingRequests.set(requestId, { resolve, reject }); | |||
| 177 | + | ||||
| 178 | + | // Timeout after 10 seconds | |||
| 179 | + | setTimeout(() => { | |||
| 180 | + | if (pendingRequests.has(requestId)) { | |||
| 181 | + | pendingRequests.delete(requestId); | |||
| 182 | + | reject(new Error('Request timeout')); | |||
| 183 | + | } | |||
| 184 | + | }, 10000); | |||
| 185 | + | ||||
| 186 | + | try { | |||
| 187 | + | const bridge = (window as any).webkit?.messageHandlers?.dumber; | |||
| 188 | + | if (bridge && typeof bridge.postMessage === 'function') { | |||
| 189 | + | bridge.postMessage(JSON.stringify({ | |||
| 190 | + | ...params, | |||
| 191 | + | type, | |||
| 192 | + | requestId | |||
| 193 | + | })); | |||
| 194 | + | } else { | |||
| 195 | + | reject(new Error('WebKit message handler not available')); | |||
| 196 | + | } | |||
| 197 | + | } catch (error) { | |||
| 198 | + | pendingRequests.delete(requestId); | |||
| 199 | + | reject(error); | |||
| 200 | + | } | |||
| 201 | + | }); | |||
| 202 | + | }; | |||
| 203 | + | ||||
| 204 | + | // Fetch recent history via message bridge | |||
| 112 | const fetchHistory = async () => { | 205 | const fetchHistory = async () => { | ||
| 113 | try { | 206 | try { | ||
| 114 | - | const response = await fetch('dumb://homepage/api/history/recent?limit=20'); | 207 | + | const data = await sendHistoryRequest('history_recent', { |
| 115 | - | if (response.ok) { | 208 | + | limit: 20, |
| 116 | - | const data = await response.json(); | 209 | + | offset: 0 |
| 117 | - | historyItems = Array.isArray(data) ? data : []; | 210 | + | }); |
| 118 | - | historyOffset = historyItems.length; | 211 | + | |
| 119 | - | hasMoreHistory = data.length === 20; // If we got full batch, there might be more | 212 | + | historyItems = Array.isArray(data) ? data : []; |
| 120 | - | } else { | 213 | + | historyOffset = historyItems.length; |
| 121 | - | console.warn('Failed to fetch history:', response.status); | 214 | + | hasMoreHistory = data.length === 20; |
| 122 | - | historyItems = []; | |||
| 123 | - | hasMoreHistory = false; | |||
| 124 | - | } | |||
| 125 | } catch (error) { | 215 | } catch (error) { | ||
| 126 | console.error('Error fetching history:', error); | 216 | console.error('Error fetching history:', error); | ||
| 127 | historyItems = []; | 217 | historyItems = []; | ||
| @@ -137,18 +227,16 @@ | |||||
| 137 | 227 | ||||
| 138 | loadingMoreHistory = true; | 228 | loadingMoreHistory = true; | ||
| 139 | try { | 229 | try { | ||
| 140 | - | const response = await fetch(`dumb://homepage/api/history/recent?limit=20&offset=${historyOffset}`); | 230 | + | const data = await sendHistoryRequest('history_recent', { |
| 141 | - | if (response.ok) { | 231 | + | limit: 20, |
| 142 | - | const data = await response.json(); | 232 | + | offset: historyOffset |
| 143 | - | if (Array.isArray(data) && data.length > 0) { | 233 | + | }); |
| 144 | - | historyItems = [...historyItems, ...data]; | 234 | + | |
| 145 | - | historyOffset += data.length; | 235 | + | if (Array.isArray(data) && data.length > 0) { |
| 146 | - | hasMoreHistory = data.length === 20; // If we got full batch, there might be more | 236 | + | historyItems = [...historyItems, ...data]; |
| 147 | - | } else { | 237 | + | historyOffset += data.length; |
| 148 | - | hasMoreHistory = false; | 238 | + | hasMoreHistory = data.length === 20; // If we got full batch, there might be more |
| 149 | - | } | |||
| 150 | } else { | 239 | } else { | ||
| 151 | - | console.warn('Failed to fetch more history:', response.status); | |||
| 152 | hasMoreHistory = false; | 240 | hasMoreHistory = false; | ||
| 153 | } | 241 | } | ||
| 154 | } catch (error) { | 242 | } catch (error) { | ||
| @@ -174,8 +262,8 @@ | |||||
| 174 | } | 262 | } | ||
| 175 | 263 | ||||
| 176 | try { | 264 | try { | ||
| 177 | - | const response = await fetch(`dumb://homepage/api/history/delete?id=${id}`); | 265 | + | // Set up response handlers |
| 178 | - | if (response.ok) { | 266 | + | (window as any).__dumber_history_deleted = async () => { |
| 179 | // Wait a bit for the fade animation to complete | 267 | // Wait a bit for the fade animation to complete | ||
| 180 | await new Promise(resolve => setTimeout(resolve, 200)); | 268 | await new Promise(resolve => setTimeout(resolve, 200)); | ||
| 181 | 269 | ||||
| @@ -185,19 +273,32 @@ | |||||
| 185 | await fetchHistory(); | 273 | await fetchHistory(); | ||
| 186 | await fetchStats(); | 274 | await fetchStats(); | ||
| 187 | await fetchTopVisited(); | 275 | await fetchTopVisited(); | ||
| 188 | - | } else { | 276 | + | |
| 189 | - | console.error('Failed to delete history entry:', response.status); | 277 | + | // Remove from deleting array after refresh |
| 278 | + | deletingIds = deletingIds.filter(i => i !== id); | |||
| 279 | + | }; | |||
| 280 | + | ||||
| 281 | + | (window as any).__dumber_history_error = (error: string) => { | |||
| 282 | + | console.error('Error deleting history entry:', error); | |||
| 190 | // Remove from deleting array if failed | 283 | // Remove from deleting array if failed | ||
| 191 | deletingIds = deletingIds.filter(i => i !== id); | 284 | deletingIds = deletingIds.filter(i => i !== id); | ||
| 285 | + | }; | |||
| 286 | + | ||||
| 287 | + | // Send message to Go backend | |||
| 288 | + | const bridge = (window as any).webkit?.messageHandlers?.dumber; | |||
| 289 | + | if (bridge && typeof bridge.postMessage === 'function') { | |||
| 290 | + | bridge.postMessage(JSON.stringify({ | |||
| 291 | + | type: 'history_delete', | |||
| 292 | + | historyId: id.toString() | |||
| 293 | + | })); | |||
| 294 | + | } else { | |||
| 295 | + | throw new Error('WebKit message handler not available'); | |||
| 192 | } | 296 | } | ||
| 193 | } catch (error) { | 297 | } catch (error) { | ||
| 194 | - | console.error('Error deleting history entry:', error); | 298 | + | console.error('Error sending delete request:', error); |
| 195 | // Remove from deleting array if failed | 299 | // Remove from deleting array if failed | ||
| 196 | deletingIds = deletingIds.filter(i => i !== id); | 300 | deletingIds = deletingIds.filter(i => i !== id); | ||
| 197 | } | 301 | } | ||
| 198 | - | ||||
| 199 | - | // Remove from deleting array after refresh | |||
| 200 | - | deletingIds = deletingIds.filter(i => i !== id); | |||
| 201 | }; | 302 | }; | ||
| 202 | 303 | ||||
| 203 | // Get domain from URL | 304 | // Get domain from URL | ||
| @@ -231,40 +332,66 @@ | |||||
| 231 | } | 332 | } | ||
| 232 | }; | 333 | }; | ||
| 233 | 334 | ||||
| 234 | - | // Fetch history stats from the API | 335 | + | // Fetch history stats via message bridge |
| 235 | const fetchStats = async () => { | 336 | const fetchStats = async () => { | ||
| 236 | try { | 337 | try { | ||
| 237 | - | const response = await fetch('dumb://homepage/api/history/stats'); | 338 | + | // Set up response handler |
| 238 | - | if (response.ok) { | 339 | + | (window as any).__dumber_history_stats = (data: any) => { |
| 239 | - | const data = await response.json(); | |||
| 240 | stats = data; | 340 | stats = data; | ||
| 241 | - | } else { | 341 | + | statsLoading = false; |
| 242 | - | console.warn('Failed to fetch stats:', response.status); | 342 | + | }; |
| 343 | + | ||||
| 344 | + | (window as any).__dumber_history_error = (error: string) => { | |||
| 345 | + | console.error('Error fetching stats:', error); | |||
| 243 | stats = null; | 346 | stats = null; | ||
| 347 | + | statsLoading = false; | |||
| 348 | + | }; | |||
| 349 | + | ||||
| 350 | + | // Send message to Go backend | |||
| 351 | + | const bridge = (window as any).webkit?.messageHandlers?.dumber; | |||
| 352 | + | if (bridge && typeof bridge.postMessage === 'function') { | |||
| 353 | + | bridge.postMessage(JSON.stringify({ | |||
| 354 | + | type: 'history_stats' | |||
| 355 | + | })); | |||
| 356 | + | } else { | |||
| 357 | + | throw new Error('WebKit message handler not available'); | |||
| 244 | } | 358 | } | ||
| 245 | } catch (error) { | 359 | } catch (error) { | ||
| 246 | - | console.error('Error fetching stats:', error); | 360 | + | console.error('Error sending stats request:', error); |
| 247 | stats = null; | 361 | stats = null; | ||
| 248 | - | } finally { | |||
| 249 | statsLoading = false; | 362 | statsLoading = false; | ||
| 250 | } | 363 | } | ||
| 251 | }; | 364 | }; | ||
| 252 | 365 | ||||
| 253 | - | // Fetch top visited sites using search endpoint sorted by visit count | 366 | + | // Fetch top visited sites via message bridge |
| 254 | const fetchTopVisited = async () => { | 367 | const fetchTopVisited = async () => { | ||
| 255 | try { | 368 | try { | ||
| 256 | - | const response = await fetch('dumb://homepage/api/history/search?q=&limit=5'); | 369 | + | // Set up response handler |
| 257 | - | if (response.ok) { | 370 | + | (window as any).__dumber_history_search = (data: any[]) => { |
| 258 | - | const data = await response.json(); | |||
| 259 | topVisited = Array.isArray(data) ? data.slice(0, 5) : []; | 371 | topVisited = Array.isArray(data) ? data.slice(0, 5) : []; | ||
| 260 | - | } else { | 372 | + | topVisitedLoading = false; |
| 261 | - | console.warn('Failed to fetch top visited:', response.status); | 373 | + | }; |
| 374 | + | ||||
| 375 | + | (window as any).__dumber_history_error = (error: string) => { | |||
| 376 | + | console.error('Error fetching top visited:', error); | |||
| 262 | topVisited = []; | 377 | topVisited = []; | ||
| 378 | + | topVisitedLoading = false; | |||
| 379 | + | }; | |||
| 380 | + | ||||
| 381 | + | // Send message to Go backend | |||
| 382 | + | const bridge = (window as any).webkit?.messageHandlers?.dumber; | |||
| 383 | + | if (bridge && typeof bridge.postMessage === 'function') { | |||
| 384 | + | bridge.postMessage(JSON.stringify({ | |||
| 385 | + | type: 'history_search', | |||
| 386 | + | q: '', | |||
| 387 | + | limit: 5 | |||
| 388 | + | })); | |||
| 389 | + | } else { | |||
| 390 | + | throw new Error('WebKit message handler not available'); | |||
| 263 | } | 391 | } | ||
| 264 | } catch (error) { | 392 | } catch (error) { | ||
| 265 | - | console.error('Error fetching top visited:', error); | 393 | + | console.error('Error sending top visited request:', error); |
| 266 | topVisited = []; | 394 | topVisited = []; | ||
| 267 | - | } finally { | |||
| 268 | topVisitedLoading = false; | 395 | topVisitedLoading = false; | ||
| 269 | } | 396 | } | ||
| 270 | }; | 397 | }; | ||
M gui/src/services/app.ts +25 -2
| @@ -26,8 +26,31 @@ export class AppService { | |||
| 26 | 26 | ||
| 27 | 27 | async loadHistory(): Promise<void> { | |
| 28 | 28 | try { | |
| 29 | - | const recentHistory: HistoryEntry[] = await fetch('/api/history/recent?limit=50').then(r => r.json()); | |
| 30 | - | this.history = recentHistory; | |
| 29 | + | // Use message bridge instead of fetch | |
| 30 | + | return new Promise((resolve, reject) => { | |
| 31 | + | // Set up response handler | |
| 32 | + | (window as any).__dumber_history_recent = (data: HistoryEntry[]) => { | |
| 33 | + | this.history = Array.isArray(data) ? data : []; | |
| 34 | + | resolve(); | |
| 35 | + | }; | |
| 36 | + | ||
| 37 | + | (window as any).__dumber_history_error = (error: string) => { | |
| 38 | + | console.error('Failed to load history:', error); | |
| 39 | + | reject(new Error(error)); | |
| 40 | + | }; | |
| 41 | + | ||
| 42 | + | // Send message to Go backend | |
| 43 | + | const bridge = (window as any).webkit?.messageHandlers?.dumber; | |
| 44 | + | if (bridge && typeof bridge.postMessage === 'function') { | |
| 45 | + | bridge.postMessage(JSON.stringify({ | |
| 46 | + | type: 'history_recent', | |
| 47 | + | limit: 50, | |
| 48 | + | offset: 0 | |
| 49 | + | })); | |
| 50 | + | } else { | |
| 51 | + | reject(new Error('WebKit message handler not available')); | |
| 52 | + | } | |
| 53 | + | }); | |
| 31 | 54 | } catch (error) { | |
| 32 | 55 | console.error('Failed to load history:', error); | |
| 33 | 56 | // Mock history for development/fallback | |
| @@ -26,8 +26,31 @@ export class AppService { | |||||
| 26 | 26 | ||||
| 27 | async loadHistory(): Promise<void> { | 27 | async loadHistory(): Promise<void> { | ||
| 28 | try { | 28 | try { | ||
| 29 | - | const recentHistory: HistoryEntry[] = await fetch('/api/history/recent?limit=50').then(r => r.json()); | 29 | + | // Use message bridge instead of fetch |
| 30 | - | this.history = recentHistory; | 30 | + | return new Promise((resolve, reject) => { |
| 31 | + | // Set up response handler | |||
| 32 | + | (window as any).__dumber_history_recent = (data: HistoryEntry[]) => { | |||
| 33 | + | this.history = Array.isArray(data) ? data : []; | |||
| 34 | + | resolve(); | |||
| 35 | + | }; | |||
| 36 | + | ||||
| 37 | + | (window as any).__dumber_history_error = (error: string) => { | |||
| 38 | + | console.error('Failed to load history:', error); | |||
| 39 | + | reject(new Error(error)); | |||
| 40 | + | }; | |||
| 41 | + | ||||
| 42 | + | // Send message to Go backend | |||
| 43 | + | const bridge = (window as any).webkit?.messageHandlers?.dumber; | |||
| 44 | + | if (bridge && typeof bridge.postMessage === 'function') { | |||
| 45 | + | bridge.postMessage(JSON.stringify({ | |||
| 46 | + | type: 'history_recent', | |||
| 47 | + | limit: 50, | |||
| 48 | + | offset: 0 | |||
| 49 | + | })); | |||
| 50 | + | } else { | |||
| 51 | + | reject(new Error('WebKit message handler not available')); | |||
| 52 | + | } | |||
| 53 | + | }); | |||
| 31 | } catch (error) { | 54 | } catch (error) { | ||
| 32 | console.error('Failed to load history:', error); | 55 | console.error('Failed to load history:', error); | ||
| 33 | // Mock history for development/fallback | 56 | // Mock history for development/fallback | ||