diff --git a/apps/desktop/extensions/ai-sidebar/background-helper.test.js b/apps/desktop/extensions/ai-sidebar/background-helper.test.js index 6a7afc8..6364fb2 100644 --- a/apps/desktop/extensions/ai-sidebar/background-helper.test.js +++ b/apps/desktop/extensions/ai-sidebar/background-helper.test.js @@ -21,6 +21,7 @@ describe('extension network helper requests', () => { sendMessage: done(), }, storage: { local: storage(), session: storage() }, + tabs: { onUpdated: { addListener: vi.fn() }, update: done() }, proxy: { settings: { set: done(), clear: done() } }, privacy: { network: { webRTCIPHandlingPolicy: { set: done(), clear: done() } } }, }); diff --git a/apps/desktop/extensions/ai-sidebar/background.js b/apps/desktop/extensions/ai-sidebar/background.js index 8e82730..75a8fac 100644 --- a/apps/desktop/extensions/ai-sidebar/background.js +++ b/apps/desktop/extensions/ai-sidebar/background.js @@ -1,10 +1,19 @@ import { decideInstallTarget, lookupInstalled } from './install-state.js'; import { PIT_SOCKS_PORT, pitProxyConfig } from './pit-proxy.js'; import { installPush } from './push-client.js'; +import { w3bsFromSearch } from './w3bs.js'; // Web Push through TronBrowser's configured push service (the engine has none). installPush(); +// A w3bs:// URI typed in the address bar becomes a web search. w3bs-rules.json +// redirects that search before it loads, but not when the omnibox serves it +// from its search prefetch, so catch whatever a tab lands on as well. +chrome.tabs.onUpdated.addListener((tabId, change) => { + const target = change.url && w3bsFromSearch(change.url); + if (target) chrome.tabs.update(tabId, { url: target }).catch(() => {}); +}); + // Open the AI side panel when the toolbar action is clicked. chrome.sidePanel .setPanelBehavior({ openPanelOnActionClick: true }) diff --git a/apps/desktop/extensions/ai-sidebar/w3bs.js b/apps/desktop/extensions/ai-sidebar/w3bs.js index 33a2dbb..5d84ec7 100644 --- a/apps/desktop/extensions/ai-sidebar/w3bs.js +++ b/apps/desktop/extensions/ai-sidebar/w3bs.js @@ -8,3 +8,22 @@ export function w3bsUrl(input) { const uri = String(input ?? '').trim(); return /^w3bs:/i.test(uri) ? W3BS_GATEWAY + encodeURIComponent(uri) : null; } + +/** + * The resolver URL for a search results page whose query is a W3BS URI, or + * null. Chromium searches a typed w3bs://chovy.hacker instead of opening it, + * and when the omnibox serves that search from its prefetch the + * declarativeNetRequest rule in w3bs-rules.json never sees a request, so the + * background worker also checks every URL a tab lands on. + */ +export function w3bsFromSearch(href) { + let url; + try { + url = new URL(href); + } catch { + return null; + } + if (url.protocol !== 'https:' || url.hostname === 'w3bs.org') return null; + const q = url.searchParams.get('q'); + return q ? w3bsUrl(q) : null; +} diff --git a/apps/desktop/extensions/ai-sidebar/w3bs.test.js b/apps/desktop/extensions/ai-sidebar/w3bs.test.js index 71c3a73..301861c 100644 --- a/apps/desktop/extensions/ai-sidebar/w3bs.test.js +++ b/apps/desktop/extensions/ai-sidebar/w3bs.test.js @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vitest'; -import { w3bsUrl } from './w3bs.js'; +import { w3bsFromSearch, w3bsUrl } from './w3bs.js'; describe('w3bsUrl', () => { it('sends a W3BS URI to the w3bs.org resolver', () => { @@ -20,3 +20,21 @@ describe('w3bsUrl', () => { expect(w3bsUrl('')).toBeNull(); }); }); + +describe('w3bsFromSearch', () => { + it('turns a search for a W3BS URI into the resolver', () => { + expect(w3bsFromSearch('https://pit.world/search?q=w3bs%3A%2F%2Fchovy.hacker')).toBe( + 'https://w3bs.org/browse?q=w3bs%3A%2F%2Fchovy.hacker', + ); + expect(w3bsFromSearch('https://duckduckgo.com/?q=W3BS%3A%2F%2Fprompt%2Fw3bs%2Fresearch&ia=web')).toBe( + 'https://w3bs.org/browse?q=W3BS%3A%2F%2Fprompt%2Fw3bs%2Fresearch', + ); + }); + + it('leaves other pages, other searches and w3bs.org alone', () => { + expect(w3bsFromSearch('https://pit.world/search?q=hello+w3bs')).toBeNull(); + expect(w3bsFromSearch('https://w3bs.org/browse?q=w3bs%3A%2F%2Fchovy.hacker')).toBeNull(); + expect(w3bsFromSearch('chrome://newtab/')).toBeNull(); + expect(w3bsFromSearch('not a url')).toBeNull(); + }); +});