Rewild Your Web
18
fork

Configure Feed

Select the types of activity you want to include in your feed.

search: new p2p search provider

Signed-off-by: webbeef <me@webbeef.org>

webbeef 6181377f c286c6fe

+118 -2
+7 -1
ui/shared/search/controller.js
··· 4 4 import { TopSitesProvider } from "//shared.localhost:8888/search/providers/topsites.js"; 5 5 import { FendProvider } from "//shared.localhost:8888/search/providers/fend.js"; 6 6 import { OpenViewsProvider } from "//shared.localhost:8888/search/providers/openviews.js"; 7 + import { RemoteViewsProvider } from "//shared.localhost:8888/search/providers/remoteviews.js"; 7 8 import SearchEngines from "//shared.localhost:8888/search/engines.js"; 8 9 import { isUrl, normalizeUrl, debounce, groupResults } from "./utils.js"; 9 10 ··· 35 36 // Initialize providers 36 37 this.aggregator = new ResultsAggregator([ 37 38 new OpenViewsProvider(), 39 + new RemoteViewsProvider(), 38 40 new FendProvider(), 39 41 new TopSitesProvider(), 40 42 ]); ··· 140 142 if (result.kind === "link") { 141 143 this.navigate(result.value.url); 142 144 } else if (result.kind === "webview") { 143 - this.selectWebView(result.value.windowId, result.value.webviewId); 145 + if (result.value.webviewId != null) { 146 + this.selectWebView(result.value.windowId, result.value.webviewId); 147 + } else { 148 + this.navigate(result.value.url); 149 + } 144 150 } 145 151 } 146 152
+81
ui/shared/search/providers/remoteviews.js
··· 1 + // SPDX-License-Identifier: AGPL-3.0-or-later 2 + 3 + const DISCOVERY_TIMEOUT = 500; // ms to wait for P2P roundtrip 4 + const MAX_RESULTS = 10; 5 + 6 + export class RemoteViewsProvider { 7 + constructor() { 8 + this.name = "Peer Tabs"; 9 + this.icon = "monitor-smartphone"; 10 + this.channel = new BroadcastChannel("beaver-remote-search"); 11 + this.pendingRequestId = null; 12 + this.results = []; 13 + 14 + this.channel.onmessage = (event) => { 15 + let data = event.data; 16 + if ( 17 + data.responseFor && 18 + data.responseFor === this.pendingRequestId && 19 + data.results 20 + ) { 21 + console.warn("[RemoteViews] Received response:", data.results.length, "results"); 22 + console.warn("[RemoteViews] Full response data:", data); 23 + for (const result of data.results) { 24 + console.warn("[RemoteViews] Result:", result); 25 + this.results.push(result); 26 + } 27 + } 28 + }; 29 + } 30 + 31 + async query(text) { 32 + if (!text || text.trim() === "") { 33 + return []; 34 + } 35 + 36 + const query = text.toLowerCase().trim(); 37 + 38 + this.results = []; 39 + this.pendingRequestId = `rs-${Date.now()}-${Math.random().toString(36).slice(2)}`; 40 + 41 + this.channel.postMessage({ 42 + requestId: this.pendingRequestId, 43 + text: query, 44 + }); 45 + console.warn("[RemoteViews] Sent query:", query, "requestId:", this.pendingRequestId); 46 + 47 + await new Promise((resolve) => setTimeout(resolve, DISCOVERY_TIMEOUT)); 48 + 49 + this.pendingRequestId = null; 50 + 51 + // Results are already filtered by the remote responder. 52 + // Score and deduplicate. 53 + const seen = new Set(); 54 + const scored = []; 55 + 56 + for (const r of this.results) { 57 + const url = (r.url || "").toLowerCase(); 58 + if (seen.has(url)) continue; 59 + seen.add(url); 60 + 61 + const title = (r.title || "").toLowerCase(); 62 + const titleMatch = title.includes(query); 63 + const urlMatch = url.includes(query); 64 + const score = (titleMatch ? 0.6 : 0) + (urlMatch ? 0.3 : 0); 65 + 66 + scored.push({ 67 + score, 68 + kind: "webview", 69 + value: { 70 + title: r.title || r.url || "Untitled", 71 + url: r.url, 72 + webviewId: null, 73 + windowId: null, 74 + }, 75 + }); 76 + } 77 + 78 + scored.sort((a, b) => b.score - a.score); 79 + return scored.slice(0, MAX_RESULTS); 80 + } 81 + }
+30 -1
ui/system/index.js
··· 1051 1051 webviews.push({ 1052 1052 webviewId: webviewId, 1053 1053 title: entry.webview.title || "", 1054 - url: entry.webview.url || "", 1054 + url: entry.webview.currentUrl || "", 1055 1055 }); 1056 1056 } 1057 1057 searchChannel.postMessage({ ··· 1070 1070 if (entry) { 1071 1071 layoutManager.scrollToPanel(entry.panelIndex); 1072 1072 } 1073 + } 1074 + }; 1075 + 1076 + // BroadcastChannel for cross-device tab search (P2P). 1077 + // Remote peers send { requestId, text } and we respond with matching local tabs. 1078 + const remoteSearchChannel = new BroadcastChannel("beaver-remote-search"); 1079 + remoteSearchChannel.onmessage = (event) => { 1080 + const { requestId, text } = event.data; 1081 + if (!requestId || !text) { 1082 + return; 1083 + } 1084 + 1085 + const query = text.toLowerCase().trim(); 1086 + const results = []; 1087 + for (const [, entry] of layoutManager.webviews) { 1088 + const url = entry.webview.currentUrl || ""; 1089 + const title = (entry.webview.title || "").toLowerCase(); 1090 + if (title.includes(query) || url.toLowerCase().includes(query)) { 1091 + results.push({ 1092 + title: entry.webview.title || "", 1093 + url, 1094 + }); 1095 + } 1096 + } 1097 + if (results.length > 0) { 1098 + remoteSearchChannel.postMessage({ 1099 + responseFor: requestId, 1100 + results, 1101 + }); 1073 1102 } 1074 1103 }; 1075 1104 });