From 03b087145c50a483ec3413cf664d68cb68e12dd4 Mon Sep 17 00:00:00 2001 From: Luke Channings <461449+LukeChannings@users.noreply.github.com> Date: Wed, 3 Jan 2024 18:34:56 +0000 Subject: [PATCH 1/2] Use the Declarative Net Request API --- .gitignore | 1 + safari/.gitignore | 1 + .../Resources/background.js | 773 ++++++------------ .../Resources/manifest.json | 13 +- 4 files changed, 276 insertions(+), 512 deletions(-) create mode 100644 safari/.gitignore diff --git a/.gitignore b/.gitignore index 78ac56e..b68a51d 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ node_modules/ built/ .vscode/ xcuserdata/ +.DS_Store diff --git a/safari/.gitignore b/safari/.gitignore new file mode 100644 index 0000000..e5adae4 --- /dev/null +++ b/safari/.gitignore @@ -0,0 +1 @@ +**xcuserdata** diff --git a/safari/Universal/Shared (Extension)/Resources/background.js b/safari/Universal/Shared (Extension)/Resources/background.js index 0572a2d..cbe8339 100644 --- a/safari/Universal/Shared (Extension)/Resources/background.js +++ b/safari/Universal/Shared (Extension)/Resources/background.js @@ -1,533 +1,284 @@ -const googleUrls = { - "google.com.au": "q", - "google.md": "q", - "google.ru": "q", - "google.me": "q", - "google.com.qa": "q", - "google.com.gt": "q", - "google.se": "q", - "google.tm": "q", - "google.vg": "q", - "google.it": "q", - "google.cat": "q", - "google.com.ru": "q", - "google.com.gr": "q", - "google.ee": "q", - "google.cd": "q", - "google.sk": "q", - "google.com.ly": "q", - "google.hn": "q", - "google.co.jp": "q", - "google.ad": "q", - "google.com.sg": "q", - "google.ie": "q", - "google.co.vi": "q", - "google.kg": "q", - "google.com.kh": "q", - "google.co.ck": "q", - "google.is": "q", - "google.tt": "q", - "google.vu": "q", - "google.bg": "q", - "google.ch": "q", - "google.com.sa": "q", - "google.tn": "q", - "google.pl": "q", - "google.ro": "q", - "google.gm": "q", - "google.tl": "q", - "google.mg": "q", - "google.dk": "q", - "google.com.bo": "q", - "google.je": "q", - "google.com.kw": "q", - "google.dz": "q", - "google.ga": "q", - "google.com.gh": "q", - "google.lt": "q", - "google.com.ag": "q", - "google.ps": "q", - "google.com.vc": "q", - "google.com.pr": "q", - "google.co.cr": "q", - "google.pn": "q", - "google.com.tr": "q", - "google.sn": "q", - "google.tg": "q", - "google.gg": "q", - "google.gr": "q", - "google.com.mt": "q", - "google.nu": "q", - "google.cm": "q", - "google.lk": "q", - "google.co.mz": "q", - "google.cv": "q", - "google.sm": "q", - "google.no": "q", - "google.al": "q", - "google.bi": "q", - "google.com.af": "q", - "google.sr": "q", - "google.jo": "q", - "google.sh": "q", - "google.co.uk": "q", - "google.co.bw": "q", - "google.dm": "q", - "google.at": "q", - "google.co.ug": "q", - "google.dj": "q", - "google.si": "q", - "google.com.pg": "q", - "google.com.tj": "q", - "google.co.za": "q", - "google.nl": "q", - "google.sc": "q", - "google.ae": "q", - "google.mv": "q", - "google.ne": "q", - "google.gy": "q", - "google.com.sl": "q", - "google.co.in": "q", - "google.com.bn": "q", - "google.ht": "q", - "google.com.ua": "q", - "google.com.my": "q", - "google.co.kr": "q", - "google.com": "q", - "google.by": "q", - "google.com.cu": "q", - "google.com.lb": "q", - "google.co.nz": "q", - "google.mu": "q", - "google.com.om": "q", - "google.as": "q", - "google.com.pe": "q", - "google.mk": "q", - "google.td": "q", - "google.es": "q", - "google.az": "q", - "google.com.hk": "q", - "google.com.do": "q", - "google.bt": "q", - "google.am": "q", - "google.fm": "q", - "google.com.mx": "q", - "google.fi": "q", - "google.com.bz": "q", - "google.st": "q", - "google.com.vn": "q", - "google.rs": "q", - "google.bs": "q", - "google.cn": "q", - "google.com.pa": "q", - "google.com.sb": "q", - "google.lv": "q", - "google.co.uz": "q", - "google.co.hu": "q", - "google.co.ve": "q", - "google.co.zw": "q", - "google.com.ai": "q", - "google.com.co": "q", - "google.ci": "q", - "google.com.uy": "q", - "google.cl": "q", - "google.mw": "q", - "google.cz": "q", - "google.co.il": "q", - "google.co.th": "q", - "google.be": "q", - "google.hr": "q", - "google.fr": "q", - "google.im": "q", - "google.com.ec": "q", - "google.cg": "q", - "google.iq": "q", - "google.com.np": "q", - "google.gl": "q", - "google.co.ke": "q", - "google.co.id": "q", - "google.ml": "q", - "google.ms": "q", - "google.com.ni": "q", - "google.mn": "q", - "google.ki": "q", - "google.lu": "q", - "google.hu": "q", - "google.rw": "q", - "google.co.ma": "q", - "google.com.tw": "q", - "google.co.ls": "q", - "google.com.et": "q", - "google.li": "q", - "google.com.br": "q", - "google.bj": "q", - "google.com.py": "q", - "google.co.tz": "q", - "google.ba": "q", - "google.co.ao": "q", - "google.bf": "q", - "google.com.ph": "q", - "google.com.sv": "q", - "google.com.bd": "q", - "google.com.mm": "q", - "google.la": "q", - "google.ws": "q", - "google.com.fj": "q", - "google.co.zm": "q", - "google.cf": "q", - "google.nr": "q", - "google.to": "q", - "google.com.jm": "q", - "google.com.ar": "q", - "google.com.gi": "q", - "google.ca": "q", - "google.kz": "q", - "google.com.cy": "q", - "google.de": "q", - "google.com.na": "q", - "google.com.pk": "q", - "google.pt": "q", - "google.ge": "q", - "google.so": "q", - "google.com.bh": "q", - "google.com.eg": "q", - "google.com.ng": "q" -}; -const yandexUrls = { - "yandex.ru": "text", - "yandex.org": "text", - "yandex.net": "text", - "yandex.net.ru": "text", - "yandex.com.ru": "text", - "yandex.ua": "text", - "yandex.com.ua": "text", - "yandex.by": "text", - "yandex.eu": "text", - "yandex.ee": "text", - "yandex.lt": "text", - "yandex.lv": "text", - "yandex.md": "text", - "yandex.uz": "text", - "yandex.mx": "text", - "yandex.do": "text", - "yandex.tm": "text", - "yandex.de": "text", - "yandex.ie": "text", - "yandex.in": "text", - "yandex.qa": "text", - "yandex.so": "text", - "yandex.nu": "text", - "yandex.tj": "text", - "yandex.dk": "text", - "yandex.es": "text", - "yandex.pt": "text", - "yandex.pl": "text", - "yandex.lu": "text", - "yandex.it": "text", - "yandex.az": "text", - "yandex.ro": "text", - "yandex.rs": "text", - "yandex.sk": "text", - "yandex.no": "text", - "ya.ru": "text", - "yandex.com": "text", - "yandex.asia": "text", - "yandex.mobi": "text" -}; -const ddgUrls = { - "duckduckgo.com": "q", - "duckduckgo.pl": "q", - "duckduckgo.jp": "q", - "duckduckgo.co": "q", - "duckduckco.de": "q", - "duckduckgo.ca": "q", - "duckduckgo.co.uk": "q", - "duckduckgo.com.mx": "q", - "duckduckgo.com.tw": "q", - "duckduckgo.dk": "q", - "duckduckgo.in": "q", - "duckduckgo.ke": "q", - "duckduckgo.mx": "q", - "duckduckgo.nl": "q", - "duckduckgo.org": "q", - "duckduckgo.sg": "q", - "duckduckgo.uk": "q", - "duckgo.com": "q", - "ddg.co": "q", - "ddg.gg": "q", - "duck.co": "q", - "duck.com": "q" -}; -const bingUrls = { - "bing.com": "q" -}; -const baiduUrls = { - "baidu.com": "wd", - "m.baidu.com": "word" -}; -const sogouUrls = { - "sogou.com": "query", - "m.sogou.com": "keyword", - "m.so.com": "q", - "so.com": "q" -}; -const ecosiaUrls = { - "ecosia.org": "q" -}; -const yahooUrls = { - "search.yahoo.com": "p" -}; -const builtInEngines = Object.assign({}, googleUrls, yandexUrls, ddgUrls, bingUrls, baiduUrls, sogouUrls, ecosiaUrls, yahooUrls); -const www = "www."; -const yahoo = "search.yahoo.com"; -const extensionId = "com.kagi.Kagi-Search-for-Safari.Extension (TFVG979488)"; +/** + * Returns redirect rules for a given search engine and kagi token + * https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/declarativeNetRequest#rules + */ +/** + * getRedirectRulesForEngine + * @description generates a set of redirect rules for a user + * @param {string} engine The search engine to enable, or 'All' for all rules. + * @param {string} kagiToken A JWT for the Kagi user + * @returns + */ +const getRedirectRulesForEngine = (engine, kagiToken) => { + if (typeof engine !== "string") { + throw new Error("An engine must be set!"); + } -var ua = {}, - tg = "0", - pt = 0, - os = !0, - rs = !0, - currentEngine = "All", - defaultKagiSearchTemplate = "https://kagi.com/search?q=%s", - kagiSearchTemplate = defaultKagiSearchTemplate, - kagiPrivateSearchTemplate = "", - flagCheckedLocalStorageForPrivateSessionLink = false, - customURLMode = 0, - customURLList = [], - regularTabIds = [], - incognitoTabIds = []; + if (typeof kagiToken !== "string") { + throw new Error( + "A Kagi token is required before a redirect can be configured." + ); + } -function setKagiSearchTemplate(isPrivateTab) { - if (isPrivateTab) { - if (typeof kagiPrivateSearchTemplate == "string" && kagiPrivateSearchTemplate.length > 0) { - kagiSearchTemplate = kagiPrivateSearchTemplate; - return; - } - } - kagiSearchTemplate = defaultKagiSearchTemplate; -} -function captureQuery(a) { - a = new URL(a); - var b = a.host; - b.startsWith(www) && (b = b.slice(www.length)); - b.endsWith(yahoo) && (b = yahoo); - if (b in builtInEngines && (a = (new URLSearchParams(a.search)).get(builtInEngines[b]))) return a; -} + // This rule injects a session cookie into Kagi requests + const kagiInjectTokenCookieRule = { + id: 1, + priority: 1, + action: { + type: "modifyHeaders", + requestHeaders: [ + { + header: "cookie", + operation: "set", + value: `kagi_session=${kagiToken}`, + }, + ], + }, + condition: { resourceTypes: ["main_frame"], urlFilter: "||kagi.com" }, + }; -function rewriteQueryURL(a, b) { - var c = !0, - d, e = !1; - if (0 == pt || 2 == pt) { - if (d = ua[a[0].toLowerCase()]) { - var f = d.ul; - c = d.ec - } - d = a.slice(1) - } - if ((1 == pt || 2 == pt) && !f && 1 < a.length) { - if (d = ua[a[a.length - 1].toLowerCase()]) f = d.ul, c = d.ec; - d = a.slice(0, -1) - } - 1 == os && kagiSearchTemplate && (!f || 1 == a.length && rs) && (e = !0, f = kagiSearchTemplate, c = !0, d = a); - f && (c = c ? d.map(function(g) { - return encodeURIComponent(g) - }).join("%20") : d.join("%20"), b(e, f.replaceAll("%s", c))) -} -var tk = 0; -function checkForSearch(a) { - requestPrivateSessionLinkFromApp(); // Always do a quick check to make sure we're synced. Background scripts may be killed and not update before a search is run. - if (!flagCheckedLocalStorageForPrivateSessionLink) { - console.log("[checkForSearch] Search query started before local private session link was fetched"); - checkLocalStorageForPrivateSessionLink(function(){ - console.log("[checkForSearch] Fetched local private session link as part of first search query during current browsing session"); - _checkForSearch(a); - }); - } else { - _checkForSearch(a); - } -} + const searchRedirectRules = { + Google: { + id: 2, + priority: 1, + action: { + type: "redirect", + redirect: { + transform: { + scheme: "https", + host: "kagi.com", + path: "/search", + queryTransform: { + removeParams: ["client", "rls", "ie", "oe"], + addOrReplaceParams: [ + { + key: "token", + value: kagiToken, + }, + ], + }, + }, + }, + }, + condition: { + resourceTypes: ["main_frame"], + requestDomains: ["google.com"], + }, + }, -function _checkForSearch(a) { - if (-1 == a.parentFrameId && 0 < a.tabId) { - var b = Date.now(), - c = a.url, - d = captureQuery(c).replace(/ +$/, ""); - if (0 == tk || 500 < b - tk) { - if (d) { - var e = d.split(/\s+/); - function tabRewriteURL(f,g) { - g && (new URL(c), matchesCustomURL(c) && (f = 1 == os && kagiSearchTemplate && f, 1 < e.length || f ? browser.tabs.update(a.tabId, { - url: g, - loadReplace: !0 - }) : rs || browser.tabs.update(a.tabId, { - url: g, - loadReplace: !0 - }))) - } - if (incognitoTabIds.includes(a.tabId)) { - setKagiSearchTemplate(true); - rewriteQueryURL(e, tabRewriteURL); - } else if (regularTabIds.includes(a.tabId)) { - setKagiSearchTemplate(false); - rewriteQueryURL(e, tabRewriteURL); - } else { - browser.tabs.get(a.tabId).then( (thisTab) => { - storeIncognitoTabIds(thisTab); - setKagiSearchTemplate(thisTab.incognito); - rewriteQueryURL(e, tabRewriteURL); - }); - } - } - tk = b - } - } -} + DuckDuckGo: { + id: 3, + priority: 1, + action: { + type: "redirect", + redirect: { + transform: { + scheme: "https", + host: "kagi.com", + path: "/search", + queryTransform: { + removeParams: ["t", "ia"], + addOrReplaceParams: [ + { + key: "token", + value: kagiToken, + }, + ], + }, + }, + }, + }, + condition: { + resourceTypes: ["main_frame"], + requestDomains: ["duckduckgo.com"], + }, + }, -function storeIncognitoTabIds(tab) { - if (tab.incognito) { - incognitoTabIds.push(tab.id); - } else { - regularTabIds.push(tab.id); - } -} + Yahoo: { + id: 4, + priority: 1, + action: { + type: "redirect", + redirect: { + regexSubstitution: `https://kagi.com/search?q=\\1&token=${kagiToken}`, + }, + }, + condition: { + regexFilter: `^https?://.*?[?&]p=([^&]+)$`, + resourceTypes: ["main_frame"], + requestDomains: ["yahoo.com"], + }, + }, -function matchesCustomURL(a) { - return !0 -} + Bing: { + id: 5, + priority: 1, + action: { + type: "redirect", + redirect: { + transform: { + scheme: "https", + host: "kagi.com", + path: "/search", + queryTransform: { + removeParams: ["form", "PC"], + addOrReplaceParams: [ + { + key: "token", + value: kagiToken, + }, + ], + }, + }, + }, + }, + condition: { + resourceTypes: ["main_frame"], + requestDomains: ["bing.com"], + }, + }, -function updateEngine(engine) { - if (engine == currentEngine) { - return; - } - currentEngine = engine; - var newFilterUrls = builtInEngines; // defaults to all engines - if (engine == "Google") { - newFilterUrls = googleUrls; - } else if (engine == "Yandex") { - newFilterUrls = yandexUrls; - } else if (engine == "DuckDuckGo") { - newFilterUrls = ddgUrls; - } else if (engine == "Baidu") { - newFilterUrls = baiduUrls; - } else if (engine == "Yahoo") { - newFilterUrls = yahooUrls; - } else if (engine == "Ecosia") { - newFilterUrls = ecosiaUrls; - } else if (engine == "Sogou") { - newFilterUrls = sogouUrls; - } else if (engine == "Bing") { - newFilterUrls = bingUrls; - } - var newFilter = { - url: Object.keys(newFilterUrls).flatMap(function(a) { - var filters = [{ - hostContains: a - }, { - hostContains: www + a - }]; - if (engine == "Yahoo") { - filters.push({hostSuffix: yahoo}); - } - return filters; - }) - }; - browser.webNavigation.onBeforeNavigate.removeListener(checkForSearch); - browser.webNavigation.onBeforeNavigate.addListener(checkForSearch, newFilter); -} + Ecosia: { + id: 6, + priority: 1, + action: { + type: "redirect", + redirect: { + transform: { + scheme: "https", + host: "kagi.com", + path: "/search", + queryTransform: { + removeParams: ["tts"], + addOrReplaceParams: [ + { + key: "token", + value: kagiToken, + }, + ], + }, + }, + }, + }, + condition: { + resourceTypes: ["main_frame"], + requestDomains: ["ecosia.org"], + }, + }, + }; -function updatePrivateSessionLink(link) { - if (link.trim().length > 0) { - // set variable used in current browsing session - kagiPrivateSearchTemplate = link + "&q=%s"; - // cache it in browser storage so we have an available - // link as soon as background.js is loaded, otherwise user's - // first private browsing search will be logged-out and fail - browser.storage.local.set({ kagiPrivateSessionLink: link }); - } else { - kagiPrivateSearchTemplate = ""; - browser.storage.local.set({ kagiPrivateSessionLink: "" }); - } -} + const enabledEngineRules = + engine === "All" + ? Object.values(searchRedirectRules) + : engine in searchRedirectRules + ? [searchRedirectRules[engine]] + : []; -function checkLocalStorageForPrivateSessionLink(callback) { - browser.storage.local.get("kagiPrivateSessionLink", function(value) { - var link = value.kagiPrivateSessionLink; - if (typeof(link) !== "undefined") { - updatePrivateSessionLink(link); - } - flagCheckedLocalStorageForPrivateSessionLink = true; - callback(); - }); -} + if (enabledEngineRules.length < 1) { + throw new Error( + `${engine} is not an available search engine. The options are: ${Object.keys( + searchRedirectRules + ).join(" ")}` + ); + } -function requestCurrentEngineFromApp() { - browser.runtime.sendNativeMessage(extensionId, {"type": "currentEngine"}, function(response) { - let selectedEngine = response.currentEngine; - updateEngine(selectedEngine); - }); + const enabledRules = [kagiInjectTokenCookieRule, ...enabledEngineRules]; + + return { + enabledRules, + allRuleIds: [ + kagiInjectTokenCookieRule.id, + ...Object.values(searchRedirectRules).map((rule) => rule.id), + ], + }; +}; + +async function clearDynamicRules() { + const currentRules = await browser.declarativeNetRequest.getDynamicRules(); + const currentRuleIds = currentRules.map((rule) => rule.id); + await browser.declarativeNetRequest.updateDynamicRules({ + removeRuleIds: currentRuleIds, + }); } -function requestPrivateSessionLinkFromApp() { - browser.runtime.sendNativeMessage(extensionId, {"type": "privateSessionLink"}, function(response) { - let privateSessionLink = response.privateSessionLink; - updatePrivateSessionLink(privateSessionLink); +/** + * @description + * @param {string} engine the search engine + * @param {string} privateSessionLink the user's private session link, should be a URL. + */ +async function synchronizeRules(engine, privateSessionLink) { + const kagiToken = new URL(privateSessionLink)?.searchParams.get("token"); + try { + const { enabledRules, allRuleIds } = getRedirectRulesForEngine( + engine, + kagiToken + ); + + console.log("Setting rules...", enabledRules); + + await clearDynamicRules(); + + await browser.declarativeNetRequest.updateDynamicRules({ + addRules: enabledRules, }); + + console.log("Seems like it worked..."); + } catch (err) { + console.error("Something went wrong", err); + } } -var defaultFilter = { - url: Object.keys(builtInEngines).flatMap(function(a) { - return [{ - hostContains: a - }, { - hostContains: www + a - }, { - hostSuffix: yahoo - }] - }) -}; -let port = browser.runtime.connectNative("com.kagi.Kagi-Search-for-Safari"); -port.onMessage.addListener(function(message){ - if (message.name == "syncData") { - let engine = message.userInfo.currentEngine; - let privateSessionLink = message.userInfo.privateSessionLink; - updateEngine(engine); - updatePrivateSessionLink(privateSessionLink); - } -}); +const port = browser.runtime.connectNative(browser.runtime.id); -browser.webNavigation.onBeforeNavigate.addListener(checkForSearch, defaultFilter); -requestCurrentEngineFromApp(); -requestPrivateSessionLinkFromApp(); +port.onMessage.addListener(async function handleNativeMessage(message) { + console.log(`Received message`, message); -browser.runtime.onInstalled.addListener(function(details){ - - // Checks for upgrade from 1.x to 2.x. If so, attempts to migrate - // the privateSessionLink url from the previous extension - if (!(details.previousVersion.startsWith("1") == true && browser.runtime.getManifest().version.startsWith("2") == true)) { - return - } - browser.storage.local.get("privateSessionLink", function(value) { - var privateSessionLink = value.privateSessionLink; - if (typeof(privateSessionLink) !== "undefined") { - browser.runtime.sendNativeMessage(extensionId, {"type": "migratePrivateSessionLink", "privateSessionLink": privateSessionLink}, function(response){ - // no-op - }); + if (message?.name) { + switch (message.name) { + case "syncData": { + const { currentEngine, privateSessionLink } = message.userInfo; + + if (currentEngine && privateSessionLink) { + await synchronizeRules(currentEngine, privateSessionLink); } - }); + break; + } + } + } }); -// Check for a private session link at startup so that the first search -// in a private window or tab doesn't fail -checkLocalStorageForPrivateSessionLink(function(){ - console.log("Finished startup check for local private session link"); -}); +(async function onLoad() { + console.log("Starting up..."); + + let currentEngine, privateSessionLink; + + try { + const [currentEngineEvent, privateSessionLinkEvent] = await Promise.all([ + browser.runtime.sendNativeMessage(browser.runtime.id, { + type: "currentEngine", + }), + browser.runtime.sendNativeMessage(browser.runtime.id, { + type: "privateSessionLink", + }), + ]); + + currentEngine = currentEngineEvent.currentEngine; + privateSessionLink = privateSessionLinkEvent.privateSessionLink; + + console.log("Received preferences", { currentEngine, privateSessionLink }); + } catch (err) { + console.error("Error requesting engine and private session link", err); + return; + } -// Checks every 5 seconds for a new engine. There's no other way to get new -// info from the app without forcing Safari to come to the foreground -(function loop() { - setTimeout(() => { - requestCurrentEngineFromApp(); - requestPrivateSessionLinkFromApp(); - loop(); - }, 5000); + if ( + typeof currentEngine === "string" && + typeof privateSessionLink === "string" + ) { + await synchronizeRules(currentEngine, privateSessionLink); + } else { + console.log( + `Initialised with no engine or session token link set. We'll need a sync when the private session link is available.` + ); + } })(); diff --git a/safari/Universal/Shared (Extension)/Resources/manifest.json b/safari/Universal/Shared (Extension)/Resources/manifest.json index bcafa19..b8b01e3 100644 --- a/safari/Universal/Shared (Extension)/Resources/manifest.json +++ b/safari/Universal/Shared (Extension)/Resources/manifest.json @@ -27,7 +27,18 @@ "permissions": [ "nativeMessaging", - "webNavigation", + "declarativeNetRequestWithHostAccess", "storage" + ], + + "host_permissions": [ + "*://kagi.com/*", + "*://google.com/*", + "*://duckduckgo.com/*", + "*://yahoo.com/*", + "*://bing.com/*", + "*://*.bing.com/*", + "*://ecosia.org/*", + "*://*.ecosia.org/*" ] } From 02a5447fac22192eae06835d8183d7234e9f8cae Mon Sep 17 00:00:00 2001 From: Luke Channings <461449+LukeChannings@users.noreply.github.com> Date: Wed, 3 Jan 2024 23:16:36 +0000 Subject: [PATCH 2/2] Use manifest V3 (appears to make the allowed sites UI in macOS/iOS better). Minor cleanup for the background script. --- .../Resources/background.js | 65 ++++++++++--------- .../Resources/manifest.json | 4 +- 2 files changed, 36 insertions(+), 33 deletions(-) diff --git a/safari/Universal/Shared (Extension)/Resources/background.js b/safari/Universal/Shared (Extension)/Resources/background.js index cbe8339..f030b62 100644 --- a/safari/Universal/Shared (Extension)/Resources/background.js +++ b/safari/Universal/Shared (Extension)/Resources/background.js @@ -20,23 +20,6 @@ const getRedirectRulesForEngine = (engine, kagiToken) => { ); } - // This rule injects a session cookie into Kagi requests - const kagiInjectTokenCookieRule = { - id: 1, - priority: 1, - action: { - type: "modifyHeaders", - requestHeaders: [ - { - header: "cookie", - operation: "set", - value: `kagi_session=${kagiToken}`, - }, - ], - }, - condition: { resourceTypes: ["main_frame"], urlFilter: "||kagi.com" }, - }; - const searchRedirectRules = { Google: { id: 2, @@ -182,23 +165,35 @@ const getRedirectRulesForEngine = (engine, kagiToken) => { ); } - const enabledRules = [kagiInjectTokenCookieRule, ...enabledEngineRules]; - - return { - enabledRules, - allRuleIds: [ - kagiInjectTokenCookieRule.id, - ...Object.values(searchRedirectRules).map((rule) => rule.id), - ], + // This rule injects a session cookie into Kagi requests + const kagiInjectTokenCookieRule = { + id: 1, + priority: 1, + action: { + type: "modifyHeaders", + requestHeaders: [ + { + header: "cookie", + operation: "set", + value: `kagi_session=${kagiToken}`, + }, + ], + }, + condition: { resourceTypes: ["main_frame"], urlFilter: "||kagi.com" }, }; + + return [kagiInjectTokenCookieRule, ...enabledEngineRules]; }; async function clearDynamicRules() { const currentRules = await browser.declarativeNetRequest.getDynamicRules(); - const currentRuleIds = currentRules.map((rule) => rule.id); - await browser.declarativeNetRequest.updateDynamicRules({ - removeRuleIds: currentRuleIds, - }); + + if (currentRules.length > 0) { + const currentRuleIds = currentRules.map((rule) => rule.id); + await browser.declarativeNetRequest.updateDynamicRules({ + removeRuleIds: currentRuleIds, + }); + } } /** @@ -207,9 +202,17 @@ async function clearDynamicRules() { * @param {string} privateSessionLink the user's private session link, should be a URL. */ async function synchronizeRules(engine, privateSessionLink) { - const kagiToken = new URL(privateSessionLink)?.searchParams.get("token"); + let kagiToken; + + try { + kagiToken = new URL(privateSessionLink)?.searchParams.get("token"); + } catch { + await clearDynamicRules() + return + } + try { - const { enabledRules, allRuleIds } = getRedirectRulesForEngine( + const enabledRules = getRedirectRulesForEngine( engine, kagiToken ); diff --git a/safari/Universal/Shared (Extension)/Resources/manifest.json b/safari/Universal/Shared (Extension)/Resources/manifest.json index b8b01e3..0e46ba3 100644 --- a/safari/Universal/Shared (Extension)/Resources/manifest.json +++ b/safari/Universal/Shared (Extension)/Resources/manifest.json @@ -1,5 +1,5 @@ { - "manifest_version": 2, + "manifest_version": 3, "default_locale": "en", "name": "__MSG_extension_name__", @@ -18,7 +18,7 @@ "persistent": false }, - "browser_action": { + "action": { "default_popup": "popup.html", "default_icon": { "128": "images/ToolbarItemIcon.png"