diff --git a/safari/Universal/Kagi Search (iOS)/Resources/Grant_Permissions_Animation_dark.gif b/safari/Universal/Kagi Search (iOS)/Resources/Grant_Permissions_Animation_dark.gif index e05d6ce..e7019b5 100644 Binary files a/safari/Universal/Kagi Search (iOS)/Resources/Grant_Permissions_Animation_dark.gif and b/safari/Universal/Kagi Search (iOS)/Resources/Grant_Permissions_Animation_dark.gif differ diff --git a/safari/Universal/Kagi Search (iOS)/Resources/Settings_Animation_dark.gif b/safari/Universal/Kagi Search (iOS)/Resources/Settings_Animation_dark.gif index 61c556a..6ed971d 100644 Binary files a/safari/Universal/Kagi Search (iOS)/Resources/Settings_Animation_dark.gif and b/safari/Universal/Kagi Search (iOS)/Resources/Settings_Animation_dark.gif differ diff --git a/safari/Universal/Kagi Search (iOS)/Resources/Settings_Animation_light.gif b/safari/Universal/Kagi Search (iOS)/Resources/Settings_Animation_light.gif index 233eb1d..12835e9 100644 Binary files a/safari/Universal/Kagi Search (iOS)/Resources/Settings_Animation_light.gif and b/safari/Universal/Kagi Search (iOS)/Resources/Settings_Animation_light.gif differ diff --git a/safari/Universal/Kagi Search.xcodeproj/project.pbxproj b/safari/Universal/Kagi Search.xcodeproj/project.pbxproj index 7aec629..1809360 100644 --- a/safari/Universal/Kagi Search.xcodeproj/project.pbxproj +++ b/safari/Universal/Kagi Search.xcodeproj/project.pbxproj @@ -73,6 +73,12 @@ 3ED5D58F2B0DFC34009DDDAD /* _locales in Resources */ = {isa = PBXBuildFile; fileRef = 3ED5D58D2B0DFC34009DDDAD /* _locales */; }; 3ED5D5912B0DFC81009DDDAD /* images in Resources */ = {isa = PBXBuildFile; fileRef = 3ED5D5902B0DFC81009DDDAD /* images */; }; 3ED5D5922B0DFC81009DDDAD /* images in Resources */ = {isa = PBXBuildFile; fileRef = 3ED5D5902B0DFC81009DDDAD /* images */; }; + FE08124D2C14D36000D0899F /* rule-builder.js in Resources */ = {isa = PBXBuildFile; fileRef = FE08124C2C14D36000D0899F /* rule-builder.js */; }; + FE08124E2C14D36000D0899F /* rule-builder.js in Resources */ = {isa = PBXBuildFile; fileRef = FE08124C2C14D36000D0899F /* rule-builder.js */; }; + FE0812502C14D39200D0899F /* kagi-content-script.js in Resources */ = {isa = PBXBuildFile; fileRef = FE08124F2C14D39200D0899F /* kagi-content-script.js */; }; + FE0812512C14D39200D0899F /* kagi-content-script.js in Resources */ = {isa = PBXBuildFile; fileRef = FE08124F2C14D39200D0899F /* kagi-content-script.js */; }; + FE7FB18C2C053F2F00198663 /* content-script.js in Resources */ = {isa = PBXBuildFile; fileRef = FE7FB18B2C053F2F00198663 /* content-script.js */; }; + FE7FB18D2C053F2F00198663 /* content-script.js in Resources */ = {isa = PBXBuildFile; fileRef = FE7FB18B2C053F2F00198663 /* content-script.js */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -189,6 +195,9 @@ 3ED5D5902B0DFC81009DDDAD /* images */ = {isa = PBXFileReference; lastKnownFileType = folder; path = images; sourceTree = ""; }; C31E691D2823DD4E00B1491B /* Kagi for Safari.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Kagi for Safari.app"; sourceTree = BUILT_PRODUCTS_DIR; }; C39A9F7028588C0100E4C0A3 /* MainConfig.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = MainConfig.xcconfig; sourceTree = ""; }; + FE08124C2C14D36000D0899F /* rule-builder.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; path = "rule-builder.js"; sourceTree = ""; }; + FE08124F2C14D39200D0899F /* kagi-content-script.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; path = "kagi-content-script.js"; sourceTree = ""; }; + FE7FB18B2C053F2F00198663 /* content-script.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; path = "content-script.js"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -342,6 +351,9 @@ 3ED5D58D2B0DFC34009DDDAD /* _locales */, 3ED5D5552B0DE720009DDDAD /* popup.js */, 3ED5D5562B0DE720009DDDAD /* background.js */, + FE08124C2C14D36000D0899F /* rule-builder.js */, + FE7FB18B2C053F2F00198663 /* content-script.js */, + FE08124F2C14D39200D0899F /* kagi-content-script.js */, 3ED5D55A2B0DE720009DDDAD /* popup.html */, 3ED5D55C2B0DE720009DDDAD /* popup.css */, ); @@ -587,12 +599,15 @@ buildActionMask = 2147483647; files = ( 3ED5D5712B0DE720009DDDAD /* popup.css in Resources */, + FE08124D2C14D36000D0899F /* rule-builder.js in Resources */, 3E9DF5082B3E3E70005DF2C3 /* manifest.json in Resources */, 3ED5D58E2B0DFC34009DDDAD /* _locales in Resources */, + FE7FB18C2C053F2F00198663 /* content-script.js in Resources */, 3ED5D56D2B0DE720009DDDAD /* popup.html in Resources */, 3ED5D5672B0DE720009DDDAD /* background.js in Resources */, 3ED5D5912B0DFC81009DDDAD /* images in Resources */, 3ED5D5652B0DE720009DDDAD /* popup.js in Resources */, + FE0812502C14D39200D0899F /* kagi-content-script.js in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -601,12 +616,15 @@ buildActionMask = 2147483647; files = ( 3ED5D5722B0DE720009DDDAD /* popup.css in Resources */, + FE08124E2C14D36000D0899F /* rule-builder.js in Resources */, 3ED5D58F2B0DFC34009DDDAD /* _locales in Resources */, 3ED5D56E2B0DE720009DDDAD /* popup.html in Resources */, + FE7FB18D2C053F2F00198663 /* content-script.js in Resources */, 3E7B281A2B86D220005EB620 /* manifest.json in Resources */, 3ED5D5682B0DE720009DDDAD /* background.js in Resources */, 3ED5D5922B0DFC81009DDDAD /* images in Resources */, 3ED5D5662B0DE720009DDDAD /* popup.js in Resources */, + FE0812512C14D39200D0899F /* kagi-content-script.js in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/safari/Universal/MainConfig.xcconfig b/safari/Universal/MainConfig.xcconfig index 5249193..f57e2f1 100644 --- a/safari/Universal/MainConfig.xcconfig +++ b/safari/Universal/MainConfig.xcconfig @@ -1,3 +1,3 @@ -MARKETING_VERSION = 2.2.3 -CURRENT_PROJECT_VERSION = 25 // this needs to be increased with each version change as well (not set to 1 when version is updated) +MARKETING_VERSION = 2.2.6 +CURRENT_PROJECT_VERSION = 36 // this needs to be increased with each version change as well (not set to 1 when version is updated) PRODUCT_NAME = Kagi for Safari diff --git a/safari/Universal/Shared (App)/Base.lproj/Main.html b/safari/Universal/Shared (App)/Base.lproj/Main.html index 5ca48a2..0fb734f 100644 --- a/safari/Universal/Shared (App)/Base.lproj/Main.html +++ b/safari/Universal/Shared (App)/Base.lproj/Main.html @@ -15,7 +15,7 @@

Instructions

  1. - Enable extension in Safari Extension SettingsSafari Extension Settings. + Enable extension in Safari Extension Settings, and Allow permissions for "kagi.com"Safari Extension Settings.
    diff --git a/safari/Universal/Shared (App)/Resources/Style.css b/safari/Universal/Shared (App)/Resources/Style.css index 89be463..74a60a4 100644 --- a/safari/Universal/Shared (App)/Resources/Style.css +++ b/safari/Universal/Shared (App)/Resources/Style.css @@ -168,13 +168,10 @@ a.screenshot:hover > object { .instruction-animation { display: block; - width: 100vw; + width: 70vw; height: auto; max-width: 450px; /* this is slightly larger than the widest iPhone in portrait mode. we want 100% width in portrait on iPhone, otherwise this maximum keeps things looking good. */ border: solid 2px #ccc; - border-left: none; - border-right: none; - } hr { diff --git a/safari/Universal/Shared (Extension)/Resources/background.js b/safari/Universal/Shared (Extension)/Resources/background.js index e667430..13c17a4 100644 --- a/safari/Universal/Shared (Extension)/Resources/background.js +++ b/safari/Universal/Shared (Extension)/Resources/background.js @@ -278,16 +278,6 @@ const yahooUrls = { "search.yahoo.com": "p" }; const builtInEngines = Object.assign({}, googleUrls, yandexUrls, ddgUrls, bingUrls, baiduUrls, sogouUrls, ecosiaUrls, yahooUrls); -const domainMap = { - "Google": ["google.com.au", "google.md", "google.ru", "google.me", "google.com.qa", "google.com.gt", "google.se", "google.tm", "google.vg", "google.it", "google.cat", "google.com.ru", "google.com.gr", "google.ee", "google.cd", "google.sk", "google.com.ly", "google.hn", "google.co.jp", "google.ad", "google.com.sg", "google.ie", "google.co.vi", "google.kg", "google.com.kh", "google.co.ck", "google.is", "google.tt", "google.vu", "google.bg", "google.ch", "google.com.sa", "google.tn", "google.pl", "google.ro", "google.gm", "google.tl", "google.mg", "google.dk", "google.com.bo", "google.je", "google.com.kw", "google.dz", "google.ga", "google.com.gh", "google.lt", "google.com.ag", "google.ps", "google.com.vc", "google.com.pr", "google.co.cr", "google.pn", "google.com.tr", "google.sn", "google.tg", "google.gg", "google.gr", "google.com.mt", "google.nu", "google.cm", "google.lk", "google.co.mz", "google.cv", "google.sm", "google.no", "google.al", "google.bi", "google.com.af", "google.sr", "google.jo", "google.sh", "google.co.uk", "google.co.bw", "google.dm", "google.at", "google.co.ug", "google.dj", "google.si", "google.com.pg", "google.com.tj", "google.co.za", "google.nl", "google.sc", "google.ae", "google.mv", "google.ne", "google.gy", "google.com.sl", "google.co.in", "google.com.bn", "google.ht", "google.com.ua", "google.com.my", "google.co.kr", "google.com", "google.by", "google.com.cu", "google.com.lb", "google.co.nz", "google.mu", "google.com.om", "google.as", "google.com.pe", "google.mk", "google.td", "google.es", "google.az", "google.com.hk", "google.com.do", "google.bt", "google.am", "google.fm", "google.com.mx", "google.fi", "google.com.bz", "google.st", "google.com.vn", "google.rs", "google.bs", "google.cn", "google.com.pa", "google.com.sb", "google.lv", "google.co.uz", "google.co.hu", "google.co.ve", "google.co.zw", "google.com.ai", "google.com.co", "google.ci", "google.com.uy", "google.cl", "google.mw", "google.cz", "google.co.il", "google.co.th", "google.be", "google.hr", "google.fr", "google.im", "google.com.ec", "google.cg", "google.iq", "google.com.np", "google.gl", "google.co.ke", "google.co.id", "google.ml", "google.ms", "google.com.ni", "google.mn", "google.ki", "google.lu", "google.hu", "google.rw", "google.co.ma", "google.com.tw", "google.co.ls", "google.com.et", "google.li", "google.com.br", "google.bj", "google.com.py", "google.co.tz", "google.ba", "google.co.ao", "google.bf", "google.com.ph", "google.com.sv", "google.com.bd", "google.com.mm", "google.la", "google.ws", "google.com.fj", "google.co.zm", "google.cf", "google.nr", "google.to", "google.com.jm", "google.com.ar", "google.com.gi", "google.ca", "google.kz", "google.com.cy", "google.de", "google.com.na", "google.com.pk", "google.pt", "google.ge", "google.so", "google.com.bh", "google.com.eg", "google.com.ng"], - "DuckDuckGo": ["duckduckgo.com", "duckduckgo.pl", "duckduckgo.jp", "duckduckgo.co", "duckduckco.de", "duckduckgo.ca", "duckduckgo.co.uk", "duckduckgo.com.mx", "duckduckgo.com.tw", "duckduckgo.dk", "duckduckgo.in", "duckduckgo.ke", "duckduckgo.mx", "duckduckgo.nl", "duckduckgo.org", "duckduckgo.sg", "duckduckgo.uk", "duckgo.com", "ddg.co", "ddg.gg", "duck.co", "duck.com"], - "Yahoo": ["search.yahoo.com"], - "Ecosia": ["ecosia.org"], - "Bing": ["bing.com"], - "Sogou": ["m.so.com", "so.com", "sogou.com", "m.sogou.com"], - "Baidu": ["baidu.com", "m.baidu.com"], - "Yandex": ["yandex.ru", "yandex.org", "yandex.net", "yandex.net.ru", "yandex.com.ru", "yandex.ua", "yandex.com.ua", "yandex.by", "yandex.eu", "yandex.ee", "yandex.lt", "yandex.lv", "yandex.md", "yandex.uz", "yandex.mx", "yandex.do", "yandex.tm", "yandex.de", "yandex.ie", "yandex.in", "yandex.qa", "yandex.so", "yandex.nu", "yandex.tj", "yandex.dk", "yandex.es", "yandex.pt", "yandex.pl", "yandex.lu", "yandex.it", "yandex.az", "yandex.ro", "yandex.rs", "yandex.sk", "yandex.no", "ya.ru", "yandex.com", "yandex.asia", "yandex.mobi"] -}; function domainKeyForHost(knownHost) { let domainKeys = Object.keys(domainMap); for (let i=0; i await initialize())(); diff --git a/safari/Universal/Shared (Extension)/Resources/content-script.js b/safari/Universal/Shared (Extension)/Resources/content-script.js new file mode 100644 index 0000000..b40b074 --- /dev/null +++ b/safari/Universal/Shared (Extension)/Resources/content-script.js @@ -0,0 +1,7 @@ +browser.runtime.sendMessage({ greeting: "kagi" }).then((response) => { + console.log("Received response: ", response); +}); + +browser.runtime.onMessage.addListener((request, sender, sendResponse) => { + console.log("Received request: ", request); +}); diff --git a/safari/Universal/Shared (Extension)/Resources/kagi-content-script.js b/safari/Universal/Shared (Extension)/Resources/kagi-content-script.js new file mode 100644 index 0000000..b582a6d --- /dev/null +++ b/safari/Universal/Shared (Extension)/Resources/kagi-content-script.js @@ -0,0 +1,53 @@ +var scrapedToken = ""; +try { + let openSearchHrefSplit = document.querySelector("link[rel=search]").getAttribute("href").split(".xml/"); + if (openSearchHrefSplit.length == 2) { + scrapedToken = openSearchHrefSplit[1]; + } +} catch (e) { + console.error(`[Extension] Error finding Kagi session token on page: ${e.message}`); +} + +// Fetch current session token +fetch("https://kagi.com/user/session", { +credentials: "include" +}) +.then((response) => response.json()) +.then((json) => { + if (json == null) { + return; + } + var token = json["id"]; + if (scrapedToken.length > 0) { + token = scrapedToken; + } + + if (typeof token == "string" && token.length > 0) { + var tokenLink = "https://kagi.com/search?token=" + token + browser.storage.local.set({ "kagiPrivateSessionLink": tokenLink }) + .then((result) => { + browser.runtime.sendMessage({ + "updatedKagiPrivateSessionLink": tokenLink + }); + if (location.href.indexOf("https://kagi.com/signin") == 0) { + let kagi_sse_replay = localStorage.getItem("kagi_sse_replay") + if (typeof kagi_sse_replay !== "undefined" && kagi_sse_replay != null) { + let values = JSON.parse(kagi_sse_replay); + if (typeof values !== "undefined" && values != null) { + let keys = Object.keys(values); + if (keys.length > 0) { + let searchString = keys[0]; + if (searchString.indexOf("search?q=") == 0) { + window.location = "http://kagi.com/" + searchString + "&token=" + token; + } + } + } + } + } + }) + } +}) +.catch((e) => console.error(`[Extension] Error fetching Kagi session token through API: ${e.message}`)); + + + diff --git a/safari/Universal/Shared (Extension)/Resources/popup.css b/safari/Universal/Shared (Extension)/Resources/popup.css index 61619d6..7b1a6e2 100644 --- a/safari/Universal/Shared (Extension)/Resources/popup.css +++ b/safari/Universal/Shared (Extension)/Resources/popup.css @@ -77,6 +77,12 @@ fieldset { input, select, p, label, button { width: 100%; } +input[type="checkbox"], label[checkbox] { + width: auto; +} +label[checkbox] { + display: contents; +} fieldset input, fieldset select { font-size: 0.9em; font-weight: 300; @@ -177,4 +183,9 @@ li.displayChildren:has(.showMore) > *:not(h4) { display: inherit; } #instructions>li:not(:first-child) { border-color: var(--borderColorLight); } -} \ No newline at end of file +} + +body:not(.setupPermissionsGranted) .setupPermissionsGranted, +body.setupPermissionsGranted .noSetupPermissions { + display: none !important; +} diff --git a/safari/Universal/Shared (Extension)/Resources/popup.html b/safari/Universal/Shared (Extension)/Resources/popup.html index d839818..7761b51 100644 --- a/safari/Universal/Shared (Extension)/Resources/popup.html +++ b/safari/Universal/Shared (Extension)/Resources/popup.html @@ -5,6 +5,7 @@ + @@ -38,6 +39,20 @@

    Engine to redirect

    +
  2. +
  3. +

    Setup Pending

    +

    To set up Kagi redirects for search engines, grant setup permissions, or run a search in Safari's location bar, then reopen the extension to enable redirects to Kagi. +

    + +
    + +
    + +
  4. +
  5. + +
  6. Private browsing

    diff --git a/safari/Universal/Shared (Extension)/Resources/popup.js b/safari/Universal/Shared (Extension)/Resources/popup.js index 439f6b8..a3b3f5e 100644 --- a/safari/Universal/Shared (Extension)/Resources/popup.js +++ b/safari/Universal/Shared (Extension)/Resources/popup.js @@ -1,30 +1,4 @@ -const domainMap = { - "Google": ["google.com.au", "google.md", "google.ru", "google.me", "google.com.qa", "google.com.gt", "google.se", "google.tm", "google.vg", "google.it", "google.cat", "google.com.ru", "google.com.gr", "google.ee", "google.cd", "google.sk", "google.com.ly", "google.hn", "google.co.jp", "google.ad", "google.com.sg", "google.ie", "google.co.vi", "google.kg", "google.com.kh", "google.co.ck", "google.is", "google.tt", "google.vu", "google.bg", "google.ch", "google.com.sa", "google.tn", "google.pl", "google.ro", "google.gm", "google.tl", "google.mg", "google.dk", "google.com.bo", "google.je", "google.com.kw", "google.dz", "google.ga", "google.com.gh", "google.lt", "google.com.ag", "google.ps", "google.com.vc", "google.com.pr", "google.co.cr", "google.pn", "google.com.tr", "google.sn", "google.tg", "google.gg", "google.gr", "google.com.mt", "google.nu", "google.cm", "google.lk", "google.co.mz", "google.cv", "google.sm", "google.no", "google.al", "google.bi", "google.com.af", "google.sr", "google.jo", "google.sh", "google.co.uk", "google.co.bw", "google.dm", "google.at", "google.co.ug", "google.dj", "google.si", "google.com.pg", "google.com.tj", "google.co.za", "google.nl", "google.sc", "google.ae", "google.mv", "google.ne", "google.gy", "google.com.sl", "google.co.in", "google.com.bn", "google.ht", "google.com.ua", "google.com.my", "google.co.kr", "google.com", "google.by", "google.com.cu", "google.com.lb", "google.co.nz", "google.mu", "google.com.om", "google.as", "google.com.pe", "google.mk", "google.td", "google.es", "google.az", "google.com.hk", "google.com.do", "google.bt", "google.am", "google.fm", "google.com.mx", "google.fi", "google.com.bz", "google.st", "google.com.vn", "google.rs", "google.bs", "google.cn", "google.com.pa", "google.com.sb", "google.lv", "google.co.uz", "google.co.hu", "google.co.ve", "google.co.zw", "google.com.ai", "google.com.co", "google.ci", "google.com.uy", "google.cl", "google.mw", "google.cz", "google.co.il", "google.co.th", "google.be", "google.hr", "google.fr", "google.im", "google.com.ec", "google.cg", "google.iq", "google.com.np", "google.gl", "google.co.ke", "google.co.id", "google.ml", "google.ms", "google.com.ni", "google.mn", "google.ki", "google.lu", "google.hu", "google.rw", "google.co.ma", "google.com.tw", "google.co.ls", "google.com.et", "google.li", "google.com.br", "google.bj", "google.com.py", "google.co.tz", "google.ba", "google.co.ao", "google.bf", "google.com.ph", "google.com.sv", "google.com.bd", "google.com.mm", "google.la", "google.ws", "google.com.fj", "google.co.zm", "google.cf", "google.nr", "google.to", "google.com.jm", "google.com.ar", "google.com.gi", "google.ca", "google.kz", "google.com.cy", "google.de", "google.com.na", "google.com.pk", "google.pt", "google.ge", "google.so", "google.com.bh", "google.com.eg", "google.com.ng"], - "DuckDuckGo": ["duckduckgo.com", "duckduckgo.pl", "duckduckgo.jp", "duckduckgo.co", "duckduckco.de", "duckduckgo.ca", "duckduckgo.co.uk", "duckduckgo.com.mx", "duckduckgo.com.tw", "duckduckgo.dk", "duckduckgo.in", "duckduckgo.ke", "duckduckgo.mx", "duckduckgo.nl", "duckduckgo.org", "duckduckgo.sg", "duckduckgo.uk", "duckgo.com", "ddg.co", "ddg.gg", "duck.co", "duck.com"], - "Yahoo": ["search.yahoo.com"], - "Ecosia": ["ecosia.org"], - "Bing": ["bing.com"], - "Sogou": ["m.so.com", "so.com", "sogou.com", "m.sogou.com"], - "Baidu": ["baidu.com", "m.baidu.com"], - "Yandex": ["yandex.ru", "yandex.org", "yandex.net", "yandex.net.ru", "yandex.com.ru", "yandex.ua", "yandex.com.ua", "yandex.by", "yandex.eu", "yandex.ee", "yandex.lt", "yandex.lv", "yandex.md", "yandex.uz", "yandex.mx", "yandex.do", "yandex.tm", "yandex.de", "yandex.ie", "yandex.in", "yandex.qa", "yandex.so", "yandex.nu", "yandex.tj", "yandex.dk", "yandex.es", "yandex.pt", "yandex.pl", "yandex.lu", "yandex.it", "yandex.az", "yandex.ro", "yandex.rs", "yandex.sk", "yandex.no", "ya.ru", "yandex.com", "yandex.asia", "yandex.mobi"] -} -const paramDomainMap = { - "text": function() { return domainMap["Yandex"]; }, - "wd": function() { return ["baidu.com"]; }, - "word": function() { return ["m.baidu.com"] }, - "query": function() { return ["sogou.com"]; }, - "keyword": function() { return ["m.sogou.com"]; }, - "p": function() { return domainMap["Yahoo"]; }, - "q": function() { return [...domainMap["Google"], ...domainMap["DuckDuckGo"], ...domainMap["Ecosia"], ...domainMap["Bing"], ...["m.so.com", "so.com"]]; } -}; -const allSupportedDomains = [...domainMap["Google"], - ...domainMap["DuckDuckGo"], - ...domainMap["Ecosia"], - ...domainMap["Bing"], - ...domainMap["Yahoo"], - ...domainMap["Sogou"], - ...domainMap["Yandex"], - ...domainMap["Baidu"]]; + function domainKeyForKnownHost(knownHost) { let domainKeys = Object.keys(domainMap); for (let i=0; i { browser.runtime.sendMessage({ "updatedKagiPrivateSessionLink": newLink + }) + .then((result) => { + if (typeof result !== "boolean") { + console.log("Error sendMessage.updatedKagiPrivateSessionLink"); + (async () => await initialize())(); + } }); }) } @@ -85,6 +65,7 @@ document.querySelector("#private-session-link").addEventListener('paste', privat const defaultEngineToRedirect = "All"; var engineToRedirect = defaultEngineToRedirect; const engineSelect = document.getElementById('engine-to-redirect'); +const dnrStatusCheckbox = document.getElementById('is-dnr-enabled'); // Updates the UI function selectCurrentEngine(currentEngine) { @@ -106,9 +87,14 @@ function updateEngineToRedirect(newEngine) { selectCurrentEngine(newEngine); return browser.storage.local.set({ "kagiEngineToRedirect": newEngine }) .then((result) => { - browser.runtime.sendMessage({ - "updatedKagiEngineToRedirect": newEngine - }); + browser.runtime.sendMessage({ + "updatedKagiEngineToRedirect": newEngine + }).then((result) => { + if (typeof result !== "boolean") { + console.log("Error sendMessage.updatedKagiPrivateSessionLink"); + (async () => await initialize())(); + } + }); }); } @@ -117,6 +103,33 @@ engineSelect.addEventListener('change', engineToRedirectChanged); // ----------------------- // MARK: - Displaying & updating host permissions // ----------------------- + +const setupPermissions = { permissions: ["storage", "webNavigation", "declarativeNetRequestWithHostAccess", "nativeMessaging"], origins: ["*://kagi.com/*"] }; + +function checkSetupPermissions() { + return new Promise((resolve, reject) => { + browser.permissions.contains(setupPermissions) + .then((alreadyGranted) => { + updateSetupPermissionsUI(alreadyGranted); + if (!alreadyGranted) { + console.log("Permissions for storage are not granted"); + resolve(false); + } else { + console.log("Permissions for storage are currently granted"); + resolve(true); + } + }) + .catch((error) => { + console.error(`Storage permission update onRejected function called: ${error.message}`); + reject(error); + }); + }); +} + +function updateSetupPermissionsUI(permissionsActive) { + document.body.classList.toggle("setupPermissionsGranted", permissionsActive); +} + function fetchAndUpdateKnownHostList() { return browser.permissions.getAll() .then((permissions) => { @@ -294,9 +307,67 @@ document.addEventListener("DOMContentLoaded", (event) => { } }) .finally(() => { - selectCurrentEngine(engineToRedirect); - fetchAndUpdateKnownHostList(); + selectCurrentEngine(engineToRedirect); + fetchAndUpdateKnownHostList(); + checkSetupPermissions(); + dnrEnabledStatusUpdateUI(); }); }); +// ----------------------- +// MARK: - Button click handlers +// ----------------------- +document.getElementById("setup-permissions-button").onclick = async function(evt) { + console.log("Checking kagi.com permissions"); + checkSetupPermissions() + .then((alreadyGranted) => { + if (!alreadyGranted) { + return browser.permissions.request(setupPermissions); + } + }) + .then((granted) => { + updateSetupPermissionsUI(granted); + if (!granted) { + return Promise.reject(new Error("Permissions for kagi.com were denied")); + } else { + console.log("Permissions for kagi.com are granted"); + setTimeout(function(){ + window.location.href = "popup.html"; + }, 500); + // browser.runtime.reload(); + } + return Promise.resolve(); + }) + .catch((error) => { + console.error(`kagi.com permission update onRejected function called: ${error.message}`); + }); +}; + +dnrStatusCheckbox.onchange = async function(evt) { + if (dnrStatusCheckbox.checked) { + localStorage.setItem("is-dnr-enabled", true); + (async () => await initialize())(); + } else { + localStorage.setItem("is-dnr-enabled", false); + (async () => await clearDynamicRules())(); + } +}; + +function dnrEnabledStatusUpdateUI() { + if (versionMajor < 17) { + document.getElementById("dnr-status-wrapper").style.display = "none"; + return; + } + let isDNREnabled = localStorage.getItem("is-dnr-enabled") + if (isDNREnabled != null) { + if (isDNREnabled == "true") { + dnrStatusCheckbox.setAttribute("checked", "true"); + } else { + dnrStatusCheckbox.removeAttribute("checked"); + } + } else { + dnrStatusCheckbox.setAttribute("checked", "true"); + } +} + const symbolTrashBase64ImgTag = ``; diff --git a/safari/Universal/Shared (Extension)/Resources/rule-builder.js b/safari/Universal/Shared (Extension)/Resources/rule-builder.js new file mode 100644 index 0000000..9ceedff --- /dev/null +++ b/safari/Universal/Shared (Extension)/Resources/rule-builder.js @@ -0,0 +1,377 @@ +let versionString = navigator.userAgent.split("Version/")[1].split(" ")[0]; +let versionMajor = parseInt(versionString); + +const domainMap = { + "Google": ["google.com.au", "google.md", "google.ru", "google.me", "google.com.qa", "google.com.gt", "google.se", "google.tm", "google.vg", "google.it", "google.cat", "google.com.ru", "google.com.gr", "google.ee", "google.cd", "google.sk", "google.com.ly", "google.hn", "google.co.jp", "google.ad", "google.com.sg", "google.ie", "google.co.vi", "google.kg", "google.com.kh", "google.co.ck", "google.is", "google.tt", "google.vu", "google.bg", "google.ch", "google.com.sa", "google.tn", "google.pl", "google.ro", "google.gm", "google.tl", "google.mg", "google.dk", "google.com.bo", "google.je", "google.com.kw", "google.dz", "google.ga", "google.com.gh", "google.lt", "google.com.ag", "google.ps", "google.com.vc", "google.com.pr", "google.co.cr", "google.pn", "google.com.tr", "google.sn", "google.tg", "google.gg", "google.gr", "google.com.mt", "google.nu", "google.cm", "google.lk", "google.co.mz", "google.cv", "google.sm", "google.no", "google.al", "google.bi", "google.com.af", "google.sr", "google.jo", "google.sh", "google.co.uk", "google.co.bw", "google.dm", "google.at", "google.co.ug", "google.dj", "google.si", "google.com.pg", "google.com.tj", "google.co.za", "google.nl", "google.sc", "google.ae", "google.mv", "google.ne", "google.gy", "google.com.sl", "google.co.in", "google.com.bn", "google.ht", "google.com.ua", "google.com.my", "google.co.kr", "google.com", "google.by", "google.com.cu", "google.com.lb", "google.co.nz", "google.mu", "google.com.om", "google.as", "google.com.pe", "google.mk", "google.td", "google.es", "google.az", "google.com.hk", "google.com.do", "google.bt", "google.am", "google.fm", "google.com.mx", "google.fi", "google.com.bz", "google.st", "google.com.vn", "google.rs", "google.bs", "google.cn", "google.com.pa", "google.com.sb", "google.lv", "google.co.uz", "google.co.hu", "google.co.ve", "google.co.zw", "google.com.ai", "google.com.co", "google.ci", "google.com.uy", "google.cl", "google.mw", "google.cz", "google.co.il", "google.co.th", "google.be", "google.hr", "google.fr", "google.im", "google.com.ec", "google.cg", "google.iq", "google.com.np", "google.gl", "google.co.ke", "google.co.id", "google.ml", "google.ms", "google.com.ni", "google.mn", "google.ki", "google.lu", "google.hu", "google.rw", "google.co.ma", "google.com.tw", "google.co.ls", "google.com.et", "google.li", "google.com.br", "google.bj", "google.com.py", "google.co.tz", "google.ba", "google.co.ao", "google.bf", "google.com.ph", "google.com.sv", "google.com.bd", "google.com.mm", "google.la", "google.ws", "google.com.fj", "google.co.zm", "google.cf", "google.nr", "google.to", "google.com.jm", "google.com.ar", "google.com.gi", "google.ca", "google.kz", "google.com.cy", "google.de", "google.com.na", "google.com.pk", "google.pt", "google.ge", "google.so", "google.com.bh", "google.com.eg", "google.com.ng"], + "DuckDuckGo": ["duckduckgo.com", "duckduckgo.pl", "duckduckgo.jp", "duckduckgo.co", "duckduckco.de", "duckduckgo.ca", "duckduckgo.co.uk", "duckduckgo.com.mx", "duckduckgo.com.tw", "duckduckgo.dk", "duckduckgo.in", "duckduckgo.ke", "duckduckgo.mx", "duckduckgo.nl", "duckduckgo.org", "duckduckgo.sg", "duckduckgo.uk", "duckgo.com", "ddg.co", "ddg.gg", "duck.co", "duck.com"], + "Yahoo": ["search.yahoo.com", "search.yahoo.com"], + "Ecosia": ["ecosia.org"], + "Bing": ["bing.com", "m.baidu.com"], + "Sogou": ["m.so.com", "so.com", "sogou.com", "m.sogou.com"], + "Baidu": ["baidu.com", "m.baidu.com"], + "Yandex": ["yandex.ru", "yandex.org", "yandex.net", "yandex.net.ru", "yandex.com.ru", "yandex.ua", "yandex.com.ua", "yandex.by", "yandex.eu", "yandex.ee", "yandex.lt", "yandex.lv", "yandex.md", "yandex.uz", "yandex.mx", "yandex.do", "yandex.tm", "yandex.de", "yandex.ie", "yandex.in", "yandex.qa", "yandex.so", "yandex.nu", "yandex.tj", "yandex.dk", "yandex.es", "yandex.pt", "yandex.pl", "yandex.lu", "yandex.it", "yandex.az", "yandex.ro", "yandex.rs", "yandex.sk", "yandex.no", "ya.ru", "yandex.com", "yandex.asia", "yandex.mobi"] +} +const paramDomainMap = { + "text": function() { return domainMap["Yandex"]; }, + "wd": function() { return ["baidu.com"]; }, + "word": function() { return ["m.baidu.com"] }, + "query": function() { return ["sogou.com"]; }, + "keyword": function() { return ["m.sogou.com"]; }, + "p": function() { return domainMap["Yahoo"]; }, + "q": function() { return [...domainMap["Google"], ...domainMap["DuckDuckGo"], ...domainMap["Ecosia"], ...domainMap["Bing"], ...["m.so.com", "so.com"]]; } +}; +const allSupportedDomains = [...domainMap["Google"], + ...domainMap["DuckDuckGo"], + ...domainMap["Ecosia"], + ...domainMap["Bing"], + ...domainMap["Yahoo"], + ...domainMap["Sogou"], + ...domainMap["Yandex"], + ...domainMap["Baidu"]]; +const bangDomainMap = { + "!g": domainMap["Google"], + "!google": domainMap["Google"], + "!ddg": domainMap["DuckDuckGo"], + "!duckduckgo": domainMap["DuckDuckGo"], + "!yahoo": domainMap["Yahoo"], + "!y": domainMap["Yahoo"], + "!sogou": domainMap["Sogou"], + "!bing": domainMap["Bing"], + "!b": domainMap["Bing"], + "!yandex": domainMap["Yandex"], + "!ya": domainMap["Yandex"], + "!ec": domainMap["Ecosia"], + "!eco": domainMap["Ecosia"], + "!ecosia": domainMap["Ecosia"], + "!bd": domainMap["Baidu"], + "!baidu": domainMap["Baidu"] +}; +const engineDefaultRedirectInfo = { + "Google": { domain: "www.google.com", path: "search", param: "q"}, + "DuckDuckGo": { domain: "duckduckgo.com", path: "", param: "q"}, + "Yahoo": { domain: "search.yahoo.com", path: "search", param: "p"}, + "Sogou": { domain: "www.sogou.com", path: "web", param: "query"}, + "Bing": { domain: "www.bing.com", path: "search", param: "q"}, + "Yandex": { domain: "www.yandex.ru", path: "yandsearch", param: "text"}, + "Ecosia": { domain: "www.ecosia.org", path: "search", param: "q"}, + "Baidu": { domain: "www.baidu.com", path: "s", param: "wd"} +}; +const bangDefaultRedirectMap = { + "!g": engineDefaultRedirectInfo["Google"], + "!google": engineDefaultRedirectInfo["Google"], + "!ddg": engineDefaultRedirectInfo["DuckDuckGo"], + "!duckduckgo": engineDefaultRedirectInfo["DuckDuckGo"], + "!yahoo": engineDefaultRedirectInfo["Yahoo"], + "!y": engineDefaultRedirectInfo["Yahoo"], + "!sogou": engineDefaultRedirectInfo["Sogou"], + "!bing": engineDefaultRedirectInfo["Bing"], + "!b": engineDefaultRedirectInfo["Bing"], + "!yandex": engineDefaultRedirectInfo["Yandex"], + "!ya": engineDefaultRedirectInfo["Yandex"], + "!ec": engineDefaultRedirectInfo["Ecosia"], + "!eco": engineDefaultRedirectInfo["Ecosia"], + "!ecosia": engineDefaultRedirectInfo["Ecosia"], + "!bd": engineDefaultRedirectInfo["Baidu"], + "!baidu": engineDefaultRedirectInfo["Baidu"] +}; + +function paramsForSelectedEngine(engine) { + + if (engine == "Google") { + return { + "q": function() { return domainMap["Google"]; } + }; + } else if (engine == "DuckDuckGo") { + return { + "q": function() { return domainMap["DuckDuckGo"]; } + }; + } else if (engine == "Yahoo") { + return { + "p": function() { return domainMap["Yahoo"]; } + }; + } else if (engine == "Sogou") { + return { + "query": function() { return ["sogou.com"]; }, + "keyword": function() { return ["m.sogou.com"]; }, + "q": function() { return ["m.so.com", "so.com"]; } + }; + } else if (engine == "Bing") { + return { + "q": function() { return domainMap["Bing"]; } + }; + } else if (engine == "Yandex") { + return { + "q": function() { return domainMap["Yandex"]; } + }; + } else if (engine == "Ecosia") { + return { + "q": function() { return domainMap["Ecosia"]; } + }; + } else if (engine == "Baidu") { + return { + "wd": function() { return ["baidu.com"]; }, + "word": function() { return ["m.baidu.com"] } + }; + } + + return paramDomainMap; +} + +function getRedirectRulesForEngine(engine, kagiToken) { + let ruleTemplate = { + "id": 999, + "priority": 2, + "action": { + "type": "redirect", + "redirect": { + "regexSubstitution": "https://www.kagi.com/search?q=\\1" + } + }, + "condition": { + "resourceTypes": [ "main_frame" ], + "requestDomains": [], + "regexFilter": "^https?.*[?&]{{parameterKey}}=([^&#]*[&#]?.*[^~][^~])$", + "excludedInitiatorDomains": ["kagi.com"] + } + }; + let allowedTemplate = { + "id": 999, + "priority": 5, + "action": { + "type": "allow" + }, + "condition": { + "resourceTypes": [ "main_frame" ], + "regexFilter": "^https?.*\/url[?&]q=?.*$" + } + }; + let bangFromKagiRuleTemplate = { + "id": 999999, + "priority": 3, + "action": { + "type": "redirect", + "redirect": { + "regexSubstitution": "https://{{domain}}/{{path}}?{{parameterKey}}=\\1\\2&~~" + } + }, + "condition": { + "resourceTypes": [ "main_frame" ], + "requestDomains": [ "kagi.com" ], + "regexFilter": "^https?:\/\/.+[?&]q=([^&#]*){{bang}}([^A-Za-z0-9_][^&#]*)[&#]?.*$" + } + }; + let bangFromKagiRuleEndOfURLTemplate = { + "id": 999999, + "priority": 4, + "action": { + "type": "redirect", + "redirect": { + "regexSubstitution": "https://{{domain}}/{{path}}?{{parameterKey}}=\\1&~~" + } + }, + "condition": { + "resourceTypes": [ "main_frame" ], + "requestDomains": [ "kagi.com" ], + "regexFilter": "^(https?:\/\/.+[?&]q=)([^&#]*){{bang}}$" + } + }; + + let kagiCookieTemplate = { + "id": 1, + "priority": 1, + "action": { + "type": "modifyHeaders", + "requestHeaders": [ + { + "header": "Cookie", + "operation": "set", + "value": "kagi_session={{sessionToken}}" + } + ] + }, + "condition": { "urlFilter": "||kagi.com/*", "resourceTypes": [ "main_frame", "other" ] } + }; + // Negate cookies on the login form to avoid bugs. Needed due to limitations in Safari content blocker regex + let kagiCookieNegationTemplate = { + "id": 2, + "priority": 2, + "action": { + "type": "modifyHeaders", + "requestHeaders": [ + { + "header": "Cookie", + "operation": "remove" + } + ] + }, + "condition": { "urlFilter": "||kagi.com/login", "resourceTypes": [ "main_frame" ] } + }; + + var newRules = []; + let hasSessionToken = (kagiToken != null && kagiToken.trim().length > 0); + let engineParamDomainMap = paramsForSelectedEngine(engine); + let paramKeys = Object.keys(engineParamDomainMap); + let bangKeys = Object.keys(bangDomainMap); + var idCounter = 0; + + idCounter += 1; + var allowRule = structuredClone(allowedTemplate); + allowRule["id"] = idCounter; + newRules.push(allowRule); + + for (let i=0; i { return domain.startsWith("www.") ? "" : `www.${domain}`; }).filter((d) => d.length > 0)); + newRule["action"]["redirect"]["regexSubstitution"] = regexSubstitution; + newRules.push(newRule); + } + + for (let bi=0; bi 0) { + console.log("Cleaning up old rules..."); + const currentRuleIds = currentRules.map((rule) => rule.id); + await browser.declarativeNetRequest.updateDynamicRules({ + removeRuleIds: currentRuleIds, + }); + } +} + +async function synchronizeRules(engine, privateSessionLink) { + let kagiToken; + + if (privateSessionLink != null && typeof privateSessionLink === "string") { + if (privateSessionLink.indexOf("https://kagi.com/search?token=") == 0) { + try { + kagiToken = new URL(privateSessionLink)?.searchParams.get("token"); + } catch { + await clearDynamicRules() + return + } + } + } + + try { + const enabledRules = 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); + } +} + +function shouldUseDNR() { + var useDNR = true; + let isDNREnabled = localStorage.getItem("is-dnr-enabled") + if (isDNREnabled != null) { + if (isDNREnabled == "false") { + useDNR = false; + } + } + + if (versionMajor >= 17) { + return useDNR; + } else { + return false; + } +} + +async function initialize() { + console.log("Starting up..."); + + if (shouldUseDNR()) { + let currentEngine, privateSessionLink; + + try { + const prefs = await browser.storage.local.get(["kagiPrivateSessionLink","kagiEngineToRedirect"]); + + currentEngine = prefs.kagiEngineToRedirect; + privateSessionLink = prefs.kagiPrivateSessionLink; + + if (typeof currentEngine === "undefined") { + currentEngine = "All"; + } + + if (typeof privateSessionLink === "undefined") { + privateSessionLink = null; + } + + console.log("Received preferences", { currentEngine, privateSessionLink }); + } catch (err) { + console.error("Error requesting engine and private session link", err); + return; + } + + console.log("Initializing..."); + await synchronizeRules(currentEngine, privateSessionLink); + } + +} + +function stringIsValid(theString) { + return (theString != null && typeof theString == "string" && theString.length > 0); +} diff --git a/safari/Universal/iOS (Extension)/manifest.json b/safari/Universal/iOS (Extension)/manifest.json index 9f5d6e1..e7843b7 100644 --- a/safari/Universal/iOS (Extension)/manifest.json +++ b/safari/Universal/iOS (Extension)/manifest.json @@ -1,10 +1,10 @@ { - "manifest_version": 2, + "manifest_version": 3, "default_locale": "en", "name": "__MSG_extension_name__", "description": "__MSG_extension_description__", - "version": "2.2.3", + "version": "2.2.6", "icons": { "512": "images/Icon.png" @@ -12,12 +12,21 @@ "background": { "scripts": [ - "background.js" + "rule-builder.js", "background.js" ], "persistent": false }, + + "content_scripts": [{ + "js": [ "content-script.js" ], + "matches": [ "" ], + "exclude_matches" : ["*://*.kagi.com/*"] + }, { + "js": ["kagi-content-script.js"], + "matches": ["*://*.kagi.com/*"] + }], - "browser_action": { + "action": { "default_popup": "popup.html", "default_icon": { "128": "images/ToolbarItemIcon.png" @@ -27,8 +36,13 @@ "permissions": [ "nativeMessaging", "webNavigation", + "declarativeNetRequestWithHostAccess", "storage" ], + "host_permissions": [ + "" + ], + "optional_permissions": [] }