From 99e3134f5fd0e4ba36978d532e95d498fde2867e Mon Sep 17 00:00:00 2001 From: Graeme Arthur Date: Mon, 13 Nov 2023 15:25:37 +0100 Subject: [PATCH 01/21] Update version number --- Configuration/Version.xcconfig | 2 +- DuckDuckGo/Settings.bundle/Root.plist | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Configuration/Version.xcconfig b/Configuration/Version.xcconfig index f16be59a1f..773b0e1646 100644 --- a/Configuration/Version.xcconfig +++ b/Configuration/Version.xcconfig @@ -1 +1 @@ -MARKETING_VERSION = 7.96.0 +MARKETING_VERSION = 7.97.0 diff --git a/DuckDuckGo/Settings.bundle/Root.plist b/DuckDuckGo/Settings.bundle/Root.plist index fb5f1dc72e..52e3696d8c 100644 --- a/DuckDuckGo/Settings.bundle/Root.plist +++ b/DuckDuckGo/Settings.bundle/Root.plist @@ -6,7 +6,7 @@ DefaultValue - 7.96.0 + 7.97.0 Key version Title From 43fbc334fb6abfbe55495887be10645c18955c21 Mon Sep 17 00:00:00 2001 From: Graeme Arthur Date: Mon, 13 Nov 2023 15:25:56 +0100 Subject: [PATCH 02/21] Update embedded files --- .../AppPrivacyConfigurationDataProvider.swift | 4 +- Core/AppTrackerDataSetProvider.swift | 4 +- Core/ios-config.json | 518 ++++++++++++++---- Core/trackerData.json | 506 +++++++++++------ 4 files changed, 774 insertions(+), 258 deletions(-) diff --git a/Core/AppPrivacyConfigurationDataProvider.swift b/Core/AppPrivacyConfigurationDataProvider.swift index 3e07b81b83..4e4e1e6202 100644 --- a/Core/AppPrivacyConfigurationDataProvider.swift +++ b/Core/AppPrivacyConfigurationDataProvider.swift @@ -23,8 +23,8 @@ import BrowserServicesKit final public class AppPrivacyConfigurationDataProvider: EmbeddedDataProvider { public struct Constants { - public static let embeddedDataETag = "\"39c652b21aa330463726faf240e03445\"" - public static let embeddedDataSHA = "b19c28e816008ace47a9e3e58f74eef6558b5cbdcd7d79d098d7d2e56d686075" + public static let embeddedDataETag = "\"71b1b5499f333348df4f9bc52b74185f\"" + public static let embeddedDataSHA = "a5b30cf79e5efc40fec6fb28491a7484822041d927a6e2d8c9c7ccd3bd236c61" } public var embeddedDataEtag: String { diff --git a/Core/AppTrackerDataSetProvider.swift b/Core/AppTrackerDataSetProvider.swift index 625fe0a39b..c41f8ed1b5 100644 --- a/Core/AppTrackerDataSetProvider.swift +++ b/Core/AppTrackerDataSetProvider.swift @@ -23,8 +23,8 @@ import BrowserServicesKit final public class AppTrackerDataSetProvider: EmbeddedDataProvider { public struct Constants { - public static let embeddedDataETag = "\"3b73923ebfd4702c33aea682db0bac11\"" - public static let embeddedDataSHA = "16e1e52530db19badfefc9a07d76620f9c608c12dc144f67020da8a9a4ddb0fa" + public static let embeddedDataETag = "\"3a50d09fd78a893f1a284051d1f777de\"" + public static let embeddedDataSHA = "2c1995807a61fd9fa311baab01633411282759732f098765f5380bda5e92b9e2" } public var embeddedDataEtag: String { diff --git a/Core/ios-config.json b/Core/ios-config.json index 530bbe5602..ce6aeae32a 100644 --- a/Core/ios-config.json +++ b/Core/ios-config.json @@ -1,6 +1,6 @@ { "readme": "https://github.com/duckduckgo/privacy-configuration", - "version": 1699039046694, + "version": 1699875322077, "features": { "adClickAttribution": { "readme": "https://help.duckduckgo.com/duckduckgo-help-pages/privacy/web-tracking-protections/#3rd-party-tracker-loading-protection", @@ -82,6 +82,9 @@ }, { "domain": "marvel.com" + }, + { + "domain": "sundancecatalog.com" } ], "settings": { @@ -107,7 +110,7 @@ ] }, "state": "enabled", - "hash": "5a2395ef011fb7be4acb7885024e63fe" + "hash": "5bfae22c5e0cb13c0a25ce80531827ff" }, "androidBrowserConfig": { "exceptions": [], @@ -247,6 +250,9 @@ }, { "domain": "marvel.com" + }, + { + "domain": "sundancecatalog.com" } ], "settings": { @@ -255,7 +261,7 @@ ] }, "state": "enabled", - "hash": "03b154384cddc4cecfd685959d658256" + "hash": "4719cb6c793d13a9ac81c329f4d206c8" }, "autofill": { "exceptions": [ @@ -298,12 +304,15 @@ }, { "percent": 10 + }, + { + "percent": 100 } ] } } }, - "hash": "968968437514e582ebd4789a22207b91" + "hash": "3d95a9b3c8112148eda0ad09b1002b53" }, "clickToLoad": { "exceptions": [ @@ -915,6 +924,9 @@ }, { "domain": "marvel.com" + }, + { + "domain": "sundancecatalog.com" } ], "settings": { @@ -932,7 +944,7 @@ } }, "state": "disabled", - "hash": "f57113e4f2e7dd0fb93f5f73f9ef4fdb" + "hash": "36e8971fa9bb204b78a5929a14a108dd" }, "clickToPlay": { "exceptions": [ @@ -944,6 +956,9 @@ }, { "domain": "marvel.com" + }, + { + "domain": "sundancecatalog.com" } ], "settings": { @@ -956,7 +971,7 @@ } }, "state": "disabled", - "hash": "6724dd4acc868b402c6d23193bc6ba56" + "hash": "4390af06f967ef97a827aeab0ac0d1ca" }, "contentBlocking": { "state": "enabled", @@ -976,9 +991,6 @@ { "domain": "ads.google.com" }, - { - "domain": "sundancecatalog.com" - }, { "domain": "earth.google.com" }, @@ -987,9 +999,12 @@ }, { "domain": "marvel.com" + }, + { + "domain": "sundancecatalog.com" } ], - "hash": "aa730da73ce0d03c67fcac68f9196b97" + "hash": "71ad0f38f9ce2bbacae5ff7db05511ec" }, "cookie": { "settings": { @@ -1036,10 +1051,13 @@ }, { "domain": "marvel.com" + }, + { + "domain": "sundancecatalog.com" } ], "state": "disabled", - "hash": "01ed94213fc52b4ef93d988070491c49" + "hash": "e41dbd0af3841636937030d91bc305b1" }, "customUserAgent": { "settings": { @@ -1218,12 +1236,18 @@ { "domain": "marvel.com" }, + { + "domain": "sundancecatalog.com" + }, { "domain": "cvs.com" }, { "domain": "facebook.com" }, + { + "domain": "finewineandgoodspirits.com" + }, { "domain": "formula1.com" }, @@ -1261,12 +1285,15 @@ }, { "domain": "marvel.com" + }, + { + "domain": "sundancecatalog.com" } ] }, "exceptions": [], "state": "enabled", - "hash": "232df10f29daf038ce889d28a9064a6b" + "hash": "b101ded240bc51fce0ece83191b2da62" }, "duckPlayer": { "exceptions": [], @@ -1339,60 +1366,6 @@ { "domain": "duckduckgo.com" }, - { - "domain": "bild.de" - }, - { - "domain": "derstandard.at" - }, - { - "domain": "foxnews.com" - }, - { - "domain": "kbb.com" - }, - { - "domain": "wiwo.de" - }, - { - "domain": "metro.co.uk" - }, - { - "domain": "blick.ch" - }, - { - "domain": "thechive.com" - }, - { - "domain": "bizjournals.com" - }, - { - "domain": "slate.com" - }, - { - "domain": "dailycaller.com" - }, - { - "domain": "dailymail.co.uk" - }, - { - "domain": "eltiempo.com" - }, - { - "domain": "dailyherald.com" - }, - { - "domain": "publico.es" - }, - { - "domain": "rawstory.com" - }, - { - "domain": "allgemeine-zeitung.de" - }, - { - "domain": "thehindu.com" - }, { "domain": "earth.google.com" }, @@ -1401,6 +1374,9 @@ }, { "domain": "marvel.com" + }, + { + "domain": "sundancecatalog.com" } ], "settings": { @@ -1610,6 +1586,10 @@ "selector": ".ad_container", "type": "closest-empty" }, + { + "selector": ".ad-container--leaderboard", + "type": "hide-empty" + }, { "selector": ".ads_container", "type": "hide-empty" @@ -1813,10 +1793,6 @@ { "selector": "#ez-content-blocker-container", "type": "hide" - }, - { - "selector": "#notify-adblock", - "type": "hide" } ], "styleTagExceptions": [ @@ -1863,6 +1839,7 @@ "advertisementhide ad", "advertisement - scroll to continue", "advertisement · scroll to continue", + "advertisement - story continues below", "advertising", "advertising\nskip ad", "advertising\nskip ad\nskip ad\nskip ad", @@ -1911,6 +1888,35 @@ } ] }, + { + "domain": "allgemeine-zeitung.de", + "rules": [ + { + "type": "disable-default" + }, + { + "selector": ".adSlot", + "type": "hide-empty" + }, + { + "selector": ".adBorder", + "type": "hide-empty" + }, + { + "selector": ".nativeAd", + "type": "closest-empty" + } + ] + }, + { + "domain": "androidpolice.com", + "rules": [ + { + "selector": "[class*='ad-zone']", + "type": "hide" + } + ] + }, { "domain": "apnews.com", "rules": [ @@ -1951,6 +1957,31 @@ } ] }, + { + "domain": "bild.de", + "rules": [ + { + "type": "disable-default" + }, + { + "selector": "[id^='mrec']", + "type": "hide-empty" + }, + { + "selector": "#superbanner", + "type": "hide-empty" + } + ] + }, + { + "domain": "bizjournals.com", + "rules": [ + { + "selector": ".adwrap", + "type": "closest-empty" + } + ] + }, { "domain": "bleacherreport.com", "rules": [ @@ -1969,6 +2000,15 @@ } ] }, + { + "domain": "blick.ch", + "rules": [ + { + "selector": "[id*='appnexus-placement-']", + "type": "hide-empty" + } + ] + }, { "domain": "bloomberg.com", "rules": [ @@ -2070,6 +2110,31 @@ } ] }, + { + "domain": "dailyherald.com", + "rules": [ + { + "selector": ".instoryAdBlock", + "type": "hide" + } + ] + }, + { + "domain": "dailymail.co.uk", + "rules": [ + { + "type": "disable-default" + }, + { + "selector": "[class*='dmg-ads']", + "type": "hide-empty" + }, + { + "selector": ".mol-ads-label-container", + "type": "closest-empty" + } + ] + }, { "domain": "dallasnews.com", "rules": [ @@ -2235,6 +2300,27 @@ } ] }, + { + "domain": "foodnetwork.co.uk", + "rules": [ + { + "selector": ".bg-bodyBg", + "type": "hide-empty" + }, + { + "selector": ".module--ad-module-primary", + "type": "hide-empty" + }, + { + "selector": "#mtf-1", + "type": "closest-empty" + }, + { + "selector": "#btf-1", + "type": "closest-empty" + } + ] + }, { "domain": "football365.com", "rules": [ @@ -2481,6 +2567,15 @@ } ] }, + { + "domain": "insider.com", + "rules": [ + { + "selector": ".in-post-sticky", + "type": "hide-empty" + } + ] + }, { "domain": "investing.com", "rules": [ @@ -2581,6 +2676,18 @@ } ] }, + { + "domain": "metro.co.uk", + "rules": [ + { + "type": "disable-default" + }, + { + "selector": ".ad-slot-container", + "type": "hide-empty" + } + ] + }, { "domain": "mirror.co.uk", "rules": [ @@ -2847,6 +2954,19 @@ } ] }, + { + "domain": "publico.es", + "rules": [ + { + "selector": ".pb-ads", + "type": "hide-empty" + }, + { + "selector": "#sc_intxt_container", + "type": "hide" + } + ] + }, { "domain": "qz.com", "rules": [ @@ -2856,6 +2976,35 @@ } ] }, + { + "domain": "rawstory.com", + "rules": [ + { + "selector": ".container_proper-ad-unit", + "type": "hide" + }, + { + "selector": ".controlled_via_ad_manager", + "type": "hide" + }, + { + "selector": ".mgid_3x2", + "type": "hide-empty" + }, + { + "selector": ".proper-ad-unit", + "type": "hide" + }, + { + "selector": "[id^='rc-widget-']", + "type": "hide-empty" + }, + { + "selector": "#story-top-ad", + "type": "hide" + } + ] + }, { "domain": "reddit.com", "rules": [ @@ -2983,6 +3132,10 @@ { "selector": ".slate-ad", "type": "hide-empty" + }, + { + "selector": ".top-ad", + "type": "hide" } ] }, @@ -3021,6 +3174,15 @@ } ] }, + { + "domain": "sportbible.com", + "rules": [ + { + "selector": "[class*='Advert']", + "type": "hide" + } + ] + }, { "domain": "target.com", "rules": [ @@ -3039,6 +3201,15 @@ } ] }, + { + "domain": "thegatewaypundit.com", + "rules": [ + { + "selector": ".code-block", + "type": "hide" + } + ] + }, { "domain": "thehour.com", "rules": [ @@ -3048,6 +3219,15 @@ } ] }, + { + "domain": "thehindu.com", + "rules": [ + { + "selector": "#articledivrec", + "type": "hide-empty" + } + ] + }, { "domain": "thetimes.co.uk", "rules": [ @@ -3183,6 +3363,10 @@ { "selector": "[class^='Ad__Container-']", "type": "hide" + }, + { + "selector": "#ac-lre-player", + "type": "hide-empty" } ] }, @@ -3212,6 +3396,15 @@ } ] }, + { + "domain": "westernjournal.com", + "rules": [ + { + "selector": ".sponsor", + "type": "hide-empty" + } + ] + }, { "domain": "washingtonpost.com", "rules": [ @@ -3247,6 +3440,14 @@ } ] }, + { + "domain": "wiwo.de", + "rules": [ + { + "type": "disable-default" + } + ] + }, { "domain": "wsj.com", "rules": [ @@ -3409,7 +3610,7 @@ ] }, "state": "enabled", - "hash": "903602d78d217518a48be612b2a08f3e" + "hash": "15184cba961aadf588965f6caf070306" }, "exceptionHandler": { "exceptions": [ @@ -3421,10 +3622,13 @@ }, { "domain": "marvel.com" + }, + { + "domain": "sundancecatalog.com" } ], "state": "disabled", - "hash": "acf631a1f283d1ea71e5d3116f17a110" + "hash": "5e792dd491428702bc0104240fbce0ce" }, "fingerprintingAudio": { "state": "disabled", @@ -3440,9 +3644,12 @@ }, { "domain": "marvel.com" + }, + { + "domain": "sundancecatalog.com" } ], - "hash": "aec17726775580a32c51d6ad0559c13f" + "hash": "f25a8f2709e865c2bd743828c7ee2f77" }, "fingerprintingBattery": { "exceptions": [ @@ -3457,10 +3664,13 @@ }, { "domain": "marvel.com" + }, + { + "domain": "sundancecatalog.com" } ], "state": "enabled", - "hash": "f74e2255710593a76b5a80571da555a2" + "hash": "440f8d663d59430c93d66208655d9238" }, "fingerprintingCanvas": { "settings": { @@ -3559,10 +3769,13 @@ }, { "domain": "marvel.com" + }, + { + "domain": "sundancecatalog.com" } ], "state": "disabled", - "hash": "55ba309065f40d78a5f0308411d6df97" + "hash": "ea4c565bae27996f0d651300d757594c" }, "fingerprintingHardware": { "settings": { @@ -3610,10 +3823,13 @@ }, { "domain": "marvel.com" + }, + { + "domain": "sundancecatalog.com" } ], "state": "enabled", - "hash": "81bda64a0a893eebb721772fbd64126a" + "hash": "911cbc583f376d416ac75c57ecc577f1" }, "fingerprintingScreenSize": { "settings": { @@ -3661,10 +3877,13 @@ }, { "domain": "marvel.com" + }, + { + "domain": "sundancecatalog.com" } ], "state": "enabled", - "hash": "4fa7c8746240605af7885a953dcbdfe9" + "hash": "0fb22f84b750e0d29bad55bd95d9ce2b" }, "fingerprintingTemporaryStorage": { "exceptions": [ @@ -3682,10 +3901,13 @@ }, { "domain": "marvel.com" + }, + { + "domain": "sundancecatalog.com" } ], "state": "enabled", - "hash": "5e81950bd7e1cbf6489ed4fb7d5b57fb" + "hash": "568a23faa984c8e7eda002294ad8f82f" }, "googleRejected": { "exceptions": [ @@ -3697,10 +3919,13 @@ }, { "domain": "marvel.com" + }, + { + "domain": "sundancecatalog.com" } ], "state": "disabled", - "hash": "acf631a1f283d1ea71e5d3116f17a110" + "hash": "5e792dd491428702bc0104240fbce0ce" }, "gpc": { "state": "enabled", @@ -3737,6 +3962,9 @@ }, { "domain": "marvel.com" + }, + { + "domain": "sundancecatalog.com" } ], "settings": { @@ -3748,7 +3976,7 @@ "privacy-test-pages.site" ] }, - "hash": "0a23d1c1dc75bb6664af7d81c6f80e77" + "hash": "083267b595694bd75cda73218f8811c2" }, "harmfulApis": { "settings": { @@ -3858,10 +4086,13 @@ }, { "domain": "marvel.com" + }, + { + "domain": "sundancecatalog.com" } ], "state": "disabled", - "hash": "cd14e5f0c64cbe269b73cd40b8c89c42" + "hash": "44d3e707cba3ee0a3578f52dc2ce2aa4" }, "https": { "state": "enabled", @@ -3883,9 +4114,12 @@ }, { "domain": "marvel.com" + }, + { + "domain": "sundancecatalog.com" } ], - "hash": "b055b164b3c3a9f20ad93e041be1810e" + "hash": "f772808ed34cc9ea8cbcbb7cdaf74429" }, "incontextSignup": { "exceptions": [], @@ -3923,6 +4157,9 @@ }, { "domain": "marvel.com" + }, + { + "domain": "sundancecatalog.com" } ], "settings": { @@ -3933,7 +4170,7 @@ ] }, "state": "enabled", - "hash": "9cbed3731aad3fb286c8245ba8ff87dd" + "hash": "698de7b963d7d7942c5c5d1e986bb1b1" }, "networkProtection": { "state": "disabled", @@ -3966,10 +4203,13 @@ }, { "domain": "marvel.com" + }, + { + "domain": "sundancecatalog.com" } ], "state": "disabled", - "hash": "e2fe271add53f2e5dafb31b235d1fa54" + "hash": "841fa92b9728c9754f050662678f82c7" }, "privacyDashboard": { "exceptions": [], @@ -4012,10 +4252,13 @@ }, { "domain": "marvel.com" + }, + { + "domain": "sundancecatalog.com" } ], "state": "disabled", - "hash": "6a035dc634a0eaff88ce00de3905a1bb" + "hash": "0d3df0f7c24ebde89d2dced4e2d34322" }, "requestFilterer": { "state": "disabled", @@ -4028,12 +4271,15 @@ }, { "domain": "marvel.com" + }, + { + "domain": "sundancecatalog.com" } ], "settings": { "windowInMs": 0 }, - "hash": "591960d057333a55489a557f5cd7d121" + "hash": "0fff8017d8ea4b5609b8f5c110be1401" }, "runtimeChecks": { "state": "disabled", @@ -4046,10 +4292,13 @@ }, { "domain": "marvel.com" + }, + { + "domain": "sundancecatalog.com" } ], "settings": {}, - "hash": "158fe8be52fa1c0fabf08d8a12fd55b0" + "hash": "800a19533c728bbec7e31e466f898268" }, "serviceworkerInitiatedRequests": { "exceptions": [ @@ -4061,10 +4310,13 @@ }, { "domain": "marvel.com" + }, + { + "domain": "sundancecatalog.com" } ], "state": "disabled", - "hash": "acf631a1f283d1ea71e5d3116f17a110" + "hash": "5e792dd491428702bc0104240fbce0ce" }, "trackerAllowlist": { "state": "enabled", @@ -4191,6 +4443,16 @@ } ] }, + "adserver.adtech.advertising.com": { + "rules": [ + { + "rule": "adserver.adtech.advertising.com/pubapi/3.0/1/54669.7/0/0/ADTECH;v=2;cmd=bid;cors=yes", + "domains": [ + "collider.com" + ] + } + ] + }, "adswizz.com": { "rules": [ { @@ -4198,6 +4460,12 @@ "domains": [ "tunein.com" ] + }, + { + "rule": "adswizz.com/adswizz/js/SynchroClient2.js", + "domains": [ + "tunein.com" + ] } ] }, @@ -4773,7 +5041,6 @@ "rule": "pubads.g.doubleclick.net/gampad/ads", "domains": [ "crunchyroll.com", - "fifa.com", "nhl.com", "rocketnews24.com", "viki.com" @@ -5001,6 +5268,7 @@ { "rule": "app.five9.com", "domains": [ + "gmsdnv.com", "machiassavings.bank" ] } @@ -5205,6 +5473,12 @@ "" ] }, + { + "rule": "www.google.com/url", + "domains": [ + "" + ] + }, { "rule": "www.google.com/maps/", "domains": [ @@ -5213,6 +5487,16 @@ } ] }, + "googleapis.com": { + "rules": [ + { + "rule": "imasdk.googleapis.com/js/sdkloader/ima3.js", + "domains": [ + "nfl.com" + ] + } + ] + }, "googleoptimize.com": { "rules": [ { @@ -5235,6 +5519,14 @@ "zefoy.com" ] }, + { + "rule": "pagead2.googlesyndication.com/pagead/show_ads.js", + "domains": [ + "luckylandslots.com", + "rocketnews24.com", + "zefoy.com" + ] + }, { "rule": "tpc.googlesyndication.com/pagead/js/loader21.html", "domains": [ @@ -5383,6 +5675,7 @@ "thecw38.com", "thecw46.com", "thecwtc.com", + "thenationaldesk.com", "turnto10.com", "univisionseattle.com", "upnorthlive.com", @@ -5483,6 +5776,12 @@ "domains": [ "pippintitle.com" ] + }, + { + "rule": "no-cache.hubspot.com/cta/default/", + "domains": [ + "" + ] } ] }, @@ -5588,6 +5887,16 @@ } ] }, + "jsdelivr.net": { + "rules": [ + { + "rule": "cdn.jsdelivr.net/npm/@fingerprintjs/fingerprintjs@3/dist/fp.js", + "domains": [ + "sbermarket.ru" + ] + } + ] + }, "kampyle.com": { "rules": [ { @@ -5638,6 +5947,7 @@ { "rule": "static.klaviyo.com/onsite/js/klaviyo.js", "domains": [ + "essentialpraxis.com", "kidsguide.com", "urbanebikes.com" ] @@ -6774,9 +7084,12 @@ }, { "domain": "marvel.com" + }, + { + "domain": "sundancecatalog.com" } ], - "hash": "2ea09955d838b29daa5416586bb2f25a" + "hash": "65670bfba426ac832fe6bb2082c432c8" }, "trackingCookies1p": { "settings": { @@ -6794,10 +7107,13 @@ }, { "domain": "marvel.com" + }, + { + "domain": "sundancecatalog.com" } ], "state": "disabled", - "hash": "057704f003231b68cde1faca12adec27" + "hash": "4dddf681372a2aea9788090b13db6e6f" }, "trackingCookies3p": { "settings": { @@ -6812,10 +7128,13 @@ }, { "domain": "marvel.com" + }, + { + "domain": "sundancecatalog.com" } ], "state": "disabled", - "hash": "e2fe271add53f2e5dafb31b235d1fa54" + "hash": "841fa92b9728c9754f050662678f82c7" }, "trackingParameters": { "exceptions": [ @@ -6828,6 +7147,9 @@ { "domain": "marvel.com" }, + { + "domain": "sundancecatalog.com" + }, { "domain": "theverge.com" } @@ -6862,7 +7184,7 @@ ] }, "state": "enabled", - "hash": "2ca96907627936c8912983c971c5f1c9" + "hash": "f2437495da4898e8e048643ab38ef372" }, "userAgentRotation": { "settings": { @@ -6877,10 +7199,13 @@ }, { "domain": "marvel.com" + }, + { + "domain": "sundancecatalog.com" } ], "state": "disabled", - "hash": "3893596da5fb3653a6adc10538f08a2e" + "hash": "f65d10dfdf6739feab99a08d42734747" }, "voiceSearch": { "exceptions": [], @@ -6897,6 +7222,9 @@ }, { "domain": "marvel.com" + }, + { + "domain": "sundancecatalog.com" } ], "state": "enabled", @@ -6929,7 +7257,7 @@ } ] }, - "hash": "3702e5c5e88b46c494d8e4ef4e17a8da" + "hash": "4e94cff79e689ff320de22a57e242bdd" }, "windowsPermissionUsage": { "exceptions": [], diff --git a/Core/trackerData.json b/Core/trackerData.json index 18ddb15be4..84975e1a36 100644 --- a/Core/trackerData.json +++ b/Core/trackerData.json @@ -1,7 +1,7 @@ { "_builtWith": { - "tracker-radar": "29f80529aa526f0a0f8daadb1c8717d6dccdbb1a45f98477de48e8a8d213d386-4013b4e91930c643394cb31c6c745356f133b04f", - "tracker-surrogates": "d61691a2fdf9f4dc062a8d248fd1e78c20b5b892" + "tracker-radar": "ae2fbc01abd26abf1903208a5cabbaed6a96ab2aa13779d2125a98fc45fee2cd-4013b4e91930c643394cb31c6c745356f133b04f", + "tracker-surrogates": "abd6067fac9693cc5a43d48931b111ca08cb0d5a" }, "readme": "https://github.com/duckduckgo/tracker-blocklists", "trackers": { @@ -136,45 +136,14 @@ "Ad Motivated Tracking", "Advertising" ], - "default": "ignore", + "default": "block", "rules": [ { - "rule": "2mdn\\.net/dot\\.gif" - }, - { - "rule": "2mdn\\.net/10533936/1679336805544/" - }, - { - "rule": "2mdn\\.net/8762481/1679421215390" - }, - { - "rule": "2mdn\\.net/879366/" - }, - { - "rule": "2mdn\\.net/ads/" - }, - { - "rule": "2mdn\\.net/creatives/assets/" - }, - { - "rule": "2mdn\\.net/dfp/" - }, - { - "rule": "2mdn\\.net/dynamic/2/" - }, - { - "rule": "2mdn\\.net/sadbundle/" - }, - { - "rule": "2mdn\\.net/simgad/[0-9]+" - }, - { - "rule": "2mdn\\.net/videoplayback" + "rule": "2mdn\\.net/instream/html5/ima3\\.js", + "surrogate": "google-ima.js" }, { "rule": "2mdn\\.net/instream/video/client\\.js", - "fingerprinting": 1, - "cookies": 0, "exceptions": { "domains": [ "dailywire.com", @@ -822,7 +791,17 @@ "Social - Share", "Third-Party Analytics Marketing" ], - "default": "block" + "default": "block", + "rules": [ + { + "rule": "static\\.addtoany\\.com/menu/page\\.js", + "exceptions": { + "domains": [ + "tiny.cc" + ] + } + } + ] }, "adelixir.com": { "domain": "adelixir.com", @@ -2519,9 +2498,16 @@ "foxbusiness.com", "foxnews.com", "fyi.tv", + "gamingbible.co.uk", + "gamingbible.com", "history.com", + "ladbible.com", "mylifetime.com", - "sueddeutsche.de" + "sportbible.com", + "sueddeutsche.de", + "tyla.com", + "unilad.co.uk", + "unilad.com" ] } } @@ -4474,14 +4460,7 @@ "fingerprinting": 2, "cookies": 0.000824, "categories": [], - "default": "ignore", - "rules": [ - { - "rule": "boldchat\\.com/aid/.*/vms\\.js", - "fingerprinting": 2, - "cookies": 0.000201 - } - ] + "default": "ignore" }, "booking.com": { "domain": "booking.com", @@ -7761,9 +7740,14 @@ "default": "ignore", "rules": [ { - "rule": "dbukjj6eu5tsf\\.cloudfront\\.net\\/assets\\.sidearmsports\\.com\\/common\\/js\\/20170825\\/video\\.js", + "rule": "dbukjj6eu5tsf\\.cloudfront\\.net/assets\\.sidearmsports\\.com/common/js/20170825/video\\.js", "fingerprinting": 3, - "cookies": 0 + "cookies": 0, + "exceptions": { + "domains": [ + "utsports.com" + ] + } } ] }, @@ -8257,6 +8241,14 @@ "sbs.com.au" ] } + }, + { + "rule": "g\\.doubleclick\\.net/pagead/ads", + "exceptions": { + "domains": [ + "fukuishimbun.co.jp" + ] + } } ] }, @@ -9825,11 +9817,6 @@ ], "default": "ignore", "rules": [ - { - "rule": "facebook\\.net/.*/all\\.js", - "fingerprinting": 1, - "cookies": 0.0000408 - }, { "rule": "facebook\\.net/.*/fbevents\\.js", "fingerprinting": 1, @@ -10814,14 +10801,7 @@ "fingerprinting": 1, "cookies": 0.0000408, "categories": [], - "default": "ignore", - "rules": [ - { - "rule": "geoip-js\\.com\\/js\\/apis\\/geoip2\\/v2\\.1\\/geoip2\\.js", - "fingerprinting": 1, - "cookies": 0.0000204 - } - ] + "default": "ignore" }, "getblue.io": { "domain": "getblue.io", @@ -11502,7 +11482,33 @@ "Ad Motivated Tracking", "Advertising" ], - "default": "block" + "default": "ignore", + "rules": [ + { + "rule": "googleadservices\\.com/gampad/cookie\\.js" + }, + { + "rule": "googleadservices\\.com/pagead/conversion\\.js" + }, + { + "rule": "googleadservices\\.com/gampad/google_service\\.js" + }, + { + "rule": "googleadservices\\.com/gampad/google_ads\\.js" + }, + { + "rule": "googleadservices\\.com/favicon\\.ico" + }, + { + "rule": "googleadservices\\.com/ga/phone" + }, + { + "rule": "googleadservices\\.com/ccm/conversion/845940546/" + }, + { + "rule": "googleadservices\\.com/pagead/conversion/" + } + ] }, "googlehosted.com": { "domain": "googlehosted.com", @@ -11619,9 +11625,28 @@ "rule": "googlesyndication\\.com/adsbygoogle\\.js", "surrogate": "adsbygoogle.js" }, + { + "rule": "pagead2\\.googlesyndication\\.com/pagead/js/adsbygoogle\\.js", + "surrogate": "adsbygoogle.js", + "exceptions": { + "domains": [ + "dronedj.com", + "fukuishimbun.co.jp", + "spaceexplored.com" + ] + } + }, { "rule": "googlesyndication\\.com/pagead/js/adsbygoogle\\.js", "surrogate": "adsbygoogle.js" + }, + { + "rule": "pagead2\\.googlesyndication\\.com/pagead/managed/js/adsense", + "exceptions": { + "domains": [ + "fukuishimbun.co.jp" + ] + } } ] }, @@ -11986,11 +12011,6 @@ "rule": "groovehq\\.com\\/api\\/shim\\/27299f7da6676b065f217a683a418325", "fingerprinting": 2, "cookies": 0 - }, - { - "rule": "groovehq\\.com\\/_next\\/static\\/chunks\\/9fd8c5e27f99fce506e2e5d3b010ddba7982b0f2\\.7fb5a86b2706698b7a7e\\.js", - "fingerprinting": 2, - "cookies": 0 } ] }, @@ -12908,14 +12928,6 @@ ] } }, - { - "rule": "hubspot\\.com/api/livechat-public/v1/", - "exceptions": { - "types": [ - "xmlhttprequest" - ] - } - }, { "rule": "hubspot\\.com/hubfs/", "exceptions": { @@ -13255,7 +13267,13 @@ "Content Delivery", "Embedded Content" ], - "default": "ignore" + "default": "ignore", + "rules": [ + { + "rule": "imasdk\\.googleapis\\.com/js/sdkloader/ima3\\.js", + "surrogate": "google-ima.js" + } + ] }, "imedia.cz": { "domain": "imedia.cz", @@ -14240,18 +14258,6 @@ } ] }, - "ipify.org": { - "domain": "ipify.org", - "owner": { - "name": "ipify.org", - "displayName": "ipify.org" - }, - "prevalence": 0.00555, - "fingerprinting": 1, - "cookies": 0.000116, - "categories": [], - "default": "block" - }, "iplsc.com": { "domain": "iplsc.com", "owner": { @@ -15484,25 +15490,7 @@ "Embedded Content", "Third-Party Analytics Marketing" ], - "default": "ignore", - "rules": [ - { - "rule": "lightboxcdn\\.com/vendor/.*/user\\.js", - "fingerprinting": 3, - "cookies": 0.0000749 - }, - { - "rule": "lightboxcdn\\.com\\/w37htfhcq2\\/vendor\\/721dac7b-a982-4a33-afe7-ffe31144fbdd\\/user\\.js", - "fingerprinting": 3, - "cookies": 0.0000136 - }, - { - "rule": "lightboxcdn\\.com\\/vendor\\/b90a8cf1-793b-4cc5-b282-f863e44f81fa\\/lightbox_inline\\.js", - "fingerprinting": 0, - "cookies": 0, - "comment": "pixel" - } - ] + "default": "ignore" }, "lijit.com": { "domain": "lijit.com", @@ -15653,7 +15641,60 @@ "Ad Motivated Tracking", "Advertising" ], - "default": "block" + "default": "ignore", + "rules": [ + { + "rule": "linksynergy\\.com/rcs" + }, + { + "rule": "linksynergy\\.com/jsp" + }, + { + "rule": "linksynergy\\.com/cs" + }, + { + "rule": "linksynergy\\.com/act\\.php" + }, + { + "rule": "linksynergy\\.com/t" + }, + { + "rule": "linksynergy\\.com/consent/v1/p" + }, + { + "rule": "linksynergy\\.com/imp" + }, + { + "rule": "linksynergy\\.com/consent/v3/p" + }, + { + "rule": "linksynergy\\.com/phoenix/phoenix-2\\.26\\.min\\.js" + }, + { + "rule": "linksynergy\\.com/privacy/ad_choices\\.png" + }, + { + "rule": "linksynergy\\.com/fs-bin/show" + }, + { + "rule": "linksynergy\\.com/cc" + }, + { + "rule": "linksynergy\\.com/js/" + }, + { + "rule": "linksynergy\\.com/wakeup/" + }, + { + "rule": "linksynergy\\.com/advertisers/owllab9118/" + }, + { + "rule": "linksynergy\\.com/pix/[0-9]+" + }, + { + "rule": "linksynergy\\.com/[0-9]+\\.ct\\.js" + } + ] }, "list-manage.com": { "domain": "list-manage.com", @@ -16454,7 +16495,8 @@ "Embedded Content", "Social - Share" ], - "default": "ignore" + "default": "ignore", + "rules": [] }, "mailchimp.com": { "domain": "mailchimp.com", @@ -18041,7 +18083,20 @@ "fingerprinting": 0, "cookies": 0.00373, "categories": [], - "default": "block" + "default": "block", + "rules": [ + { + "rule": "hello\\.myfonts\\.net/count/", + "exceptions": { + "domains": [ + "condor.com" + ], + "types": [ + "stylesheet" + ] + } + } + ] }, "myregistry.com": { "domain": "myregistry.com", @@ -18105,7 +18160,30 @@ "fingerprinting": 2, "cookies": 0.00189, "categories": [], - "default": "block" + "default": "ignore", + "rules": [ + { + "rule": "nakanohito\\.jp/b3/bi\\.js" + }, + { + "rule": "nakanohito\\.jp/b3/" + }, + { + "rule": "nakanohito\\.jp/uhj2/uh\\.js" + }, + { + "rule": "nakanohito\\.jp/cm/inner\\.css" + }, + { + "rule": "nakanohito\\.jp/ua/uwa\\.js" + }, + { + "rule": "nakanohito\\.jp/ua/" + }, + { + "rule": "nakanohito\\.jp/chatbot_pc\\.css" + } + ] }, "nanorep.co": { "domain": "nanorep.co", @@ -18359,6 +18437,15 @@ ], "default": "block", "rules": [ + { + "rule": "js-agent\\.newrelic\\.com", + "exceptions": { + "domains": [ + "chaturbate.com", + "johnlewis.com" + ] + } + }, { "rule": "newrelic\\.com", "exceptions": { @@ -18712,11 +18799,7 @@ "rules": [ { "rule": "npttech\\.com/advertising\\.js", - "exceptions": { - "types": [ - "script" - ] - } + "surrogate": "noop.js" } ] }, @@ -18734,7 +18817,17 @@ "categories": [ "Analytics" ], - "default": "block" + "default": "block", + "rules": [ + { + "rule": "nr-data\\.net", + "exceptions": { + "domains": [ + "chaturbate.com" + ] + } + } + ] }, "nrich.ai": { "domain": "nrich.ai", @@ -20377,7 +20470,39 @@ "Audience Measurement", "Third-Party Analytics Marketing" ], - "default": "block" + "default": "ignore", + "rules": [ + { + "rule": "parsely\\.com/plogger/" + }, + { + "rule": "parsely\\.com/videoplugins/brightcove/videojs-parsely-loader-v1-latest\\.min\\.js" + }, + { + "rule": "parsely\\.com/videoplugins/brightcove/videojs-parsely-v1-latest\\.min\\.js" + }, + { + "rule": "parsely\\.com/v2/profile" + }, + { + "rule": "parsely\\.com/px/" + }, + { + "rule": "parsely\\.com/v2/related" + }, + { + "rule": "parsely\\.com/v2/analytics/posts" + }, + { + "rule": "parsely\\.com/v2/similar" + }, + { + "rule": "parsely\\.com/keys/adweek\\.com/p\\.js" + }, + { + "rule": "parsely\\.com/v2/search" + } + ] }, "partplanes.com": { "domain": "partplanes.com", @@ -20592,7 +20717,17 @@ "fingerprinting": 2, "cookies": 0.00574, "categories": [], - "default": "block" + "default": "block", + "rules": [ + { + "rule": "edge\\.permutive\\.app", + "exceptions": { + "domains": [ + "globaltv.com" + ] + } + } + ] }, "permutive.com": { "domain": "permutive.com", @@ -21133,11 +21268,6 @@ "categories": [], "default": "ignore", "rules": [ - { - "rule": "powr\\.io\\/powr\\.js", - "fingerprinting": 1, - "cookies": 0.00000681 - }, { "rule": "powr\\.io\\/popup\\/u\\/61af4f5f_1680291259", "fingerprinting": 1, @@ -21409,14 +21539,7 @@ "fingerprinting": 1, "cookies": 0.000197, "categories": [], - "default": "ignore", - "rules": [ - { - "rule": "providesupport\\.com\\/sjs\\/static\\.js", - "fingerprinting": 1, - "cookies": 0.000123 - } - ] + "default": "ignore" }, "pswec.com": { "domain": "pswec.com", @@ -23122,7 +23245,23 @@ "Ad Motivated Tracking", "Advertising" ], - "default": "block" + "default": "block", + "rules": [ + { + "rule": "micro\\.rubiconproject\\.com/prebid/dynamic/.*\\.js", + "exceptions": { + "domains": [ + "gamingbible.co.uk", + "gamingbible.com", + "ladbible.com", + "sportbible.com", + "tyla.com", + "unilad.co.uk", + "unilad.com" + ] + } + } + ] }, "ruralrobin.com": { "domain": "ruralrobin.com", @@ -25973,7 +26112,7 @@ "cookies": 0.0000136 }, { - "rule": "storage\\.googleapis\\.com/code\\.snapengage\\.com/", + "rule": "storage\\.googleapis\\.com/code\\.snapengage\\.com/js/", "fingerprinting": 3, "cookies": 0.0000136 }, @@ -27133,7 +27272,60 @@ "fingerprinting": 1, "cookies": 0.0436, "categories": [], - "default": "block" + "default": "ignore", + "rules": [ + { + "rule": "tiktok\\.com/i18n/pixel/events\\.js" + }, + { + "rule": "tiktok\\.com/i18n/pixel/static/identify_d1af3\\.js" + }, + { + "rule": "tiktok\\.com/api/v2/pixel" + }, + { + "rule": "tiktok\\.com/i18n/pixel/sdk\\.js" + }, + { + "rule": "tiktok\\.com/api/v2/performance_interaction" + }, + { + "rule": "tiktok\\.com/i18n/pixel/config\\.js" + }, + { + "rule": "tiktok\\.com/i18n/pixel/disable_cookie" + }, + { + "rule": "tiktok\\.com/i18n/pixel/static/identify_821f6\\.js" + }, + { + "rule": "tiktok\\.com/v1/user/webid" + }, + { + "rule": "tiktok\\.com/service/2/abtest_config/" + }, + { + "rule": "tiktok\\.com/web/resource" + }, + { + "rule": "tiktok\\.com/v1/list" + }, + { + "rule": "tiktok\\.com/web/report" + }, + { + "rule": "tiktok\\.com/oembed" + }, + { + "rule": "tiktok\\.com/i18n/pixel/enable_cookie" + }, + { + "rule": "tiktok\\.com/i18n/pixel/static/identify_a7248\\.js" + }, + { + "rule": "tiktok\\.com/i18n/pixel/static/main\\..*\\.js" + } + ] }, "tinypass.com": { "domain": "tinypass.com", @@ -29123,7 +29315,18 @@ "categories": [ "Advertising" ], - "default": "block" + "default": "block", + "rules": [ + { + "rule": "cdn\\.viglink\\.com/api/vglnk\\.js", + "exceptions": { + "domains": [ + "9to5mac.com", + "electrek.co" + ] + } + } + ] }, "visualwebsiteoptimizer.com": { "domain": "visualwebsiteoptimizer.com", @@ -29856,6 +30059,14 @@ "categories": [], "default": "ignore", "rules": [ + { + "rule": "universal\\.wgplayer\\.com/tag/", + "exceptions": { + "domains": [ + "yoho.games" + ] + } + }, { "rule": "wgplayer\\.com\\/tag\\/", "fingerprinting": 1, @@ -31264,21 +31475,6 @@ "categories": [], "default": "block" }, - "zopim.com": { - "domain": "zopim.com", - "owner": { - "name": "Zendesk, Inc.", - "displayName": "Zendesk", - "privacyPolicy": "https://www.zendesk.com/company/customers-partners/privacy-policy/" - }, - "prevalence": 0.0078, - "fingerprinting": 1, - "cookies": 0.00153, - "categories": [ - "Embedded Content" - ], - "default": "block" - }, "zprk.io": { "domain": "zprk.io", "owner": { @@ -42275,13 +42471,6 @@ "prevalence": 0.0926, "displayName": "iPerceptions" }, - "ipify.org": { - "domains": [ - "ipify.org" - ], - "prevalence": 0.0163, - "displayName": "ipify.org" - }, "Grupa Interia.pl Sp. z o.o. sp. k.": { "domains": [ "adretail.pl", @@ -51319,7 +51508,6 @@ "iocnt.net": "INFOnline GmbH", "iper2.com": "iPerceptions Inc.", "iperceptions.com": "iPerceptions Inc.", - "ipify.org": "ipify.org", "adretail.pl": "Grupa Interia.pl Sp. z o.o. sp. k.", "adsearch.pl": "Grupa Interia.pl Sp. z o.o. sp. k.", "bryk.pl": "Grupa Interia.pl Sp. z o.o. sp. k.", From 20176c44a2c241fddbc9f8eb72dd460fe63b0457 Mon Sep 17 00:00:00 2001 From: Brad Slayter Date: Mon, 13 Nov 2023 12:54:32 -0600 Subject: [PATCH 03/21] Normalize ampUrl in breakage pixel (#2154) --- DuckDuckGo/BrokenSiteInfo.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DuckDuckGo/BrokenSiteInfo.swift b/DuckDuckGo/BrokenSiteInfo.swift index 9a1ed49e6e..fc516b48ef 100644 --- a/DuckDuckGo/BrokenSiteInfo.swift +++ b/DuckDuckGo/BrokenSiteInfo.swift @@ -104,7 +104,7 @@ public struct BrokenSiteInfo { Keys.manufacturer: manufacturer, Keys.model: model, Keys.gpc: gpc ? "true" : "false", - Keys.ampUrl: ampUrl ?? "", + Keys.ampUrl: normalize(URL(string: ampUrl ?? "")), Keys.urlParametersRemoved: urlParametersRemoved ? "true" : "false", Keys.protectionsState: protectionsState ? "true" : "false" ] From 7243cb683ccc8e44edf1fe921c69241811acd53e Mon Sep 17 00:00:00 2001 From: Sam Symons Date: Mon, 13 Nov 2023 15:40:54 -0800 Subject: [PATCH 04/21] Add NetP widget (#2142) Task/Issue URL: https://app.asana.com/0/414235014887631/1205921206830836/f Tech Design URL: CC: @graeme Description: This PR adds a NetP widget. It's only available in the debug and alpha builds, and only available on iOS 17. --- .github/workflows/alpha.yml | 3 +- Core/AppDeepLinkSchemes.swift | 2 + Core/UserDefaults+NetworkProtection.swift | 6 + DuckDuckGo.xcodeproj/project.pbxproj | 42 ++- DuckDuckGo/AppDelegate+AppDeepLinks.swift | 6 + DuckDuckGo/AppDelegate.swift | 22 +- .../NetworkProtectionStatusViewModel.swift | 3 + .../NetworkProtectionWidgetRefreshModel.swift | 46 +++ DuckDuckGo/VPNIntents.swift | 102 ++++++ ...etworkProtectionPacketTunnelProvider.swift | 14 +- .../vpn-off.imageset/Contents.json | 12 + .../vpn-off.imageset/vpn-off.pdf | Bin 0 -> 8715 bytes .../vpn-on.imageset/Contents.json | 12 + .../vpn-on.imageset/vpn-on.pdf | Bin 0 -> 9104 bytes Widgets/DeepLinks.swift | 1 + Widgets/VPNWidget.swift | 301 ++++++++++++++++++ Widgets/Widgets.swift | 7 + Widgets/WidgetsExtension.entitlements | 1 + WidgetsExtensionAlpha.entitlements | 5 +- 19 files changed, 578 insertions(+), 7 deletions(-) create mode 100644 DuckDuckGo/NetworkProtectionWidgetRefreshModel.swift create mode 100644 DuckDuckGo/VPNIntents.swift create mode 100644 Widgets/Assets.xcassets/vpn-off.imageset/Contents.json create mode 100644 Widgets/Assets.xcassets/vpn-off.imageset/vpn-off.pdf create mode 100644 Widgets/Assets.xcassets/vpn-on.imageset/Contents.json create mode 100644 Widgets/Assets.xcassets/vpn-on.imageset/vpn-on.pdf create mode 100644 Widgets/VPNWidget.swift diff --git a/.github/workflows/alpha.yml b/.github/workflows/alpha.yml index 3d3ba3c8af..01ec4a7540 100644 --- a/.github/workflows/alpha.yml +++ b/.github/workflows/alpha.yml @@ -74,8 +74,9 @@ jobs: restore-keys: | ${{ runner.os }}-spm- + # Using Xcode 15 as the alpha build uses iOS 17 APIs - name: Select Xcode - run: sudo xcode-select -s /Applications/Xcode_$(<.xcode-version).app/Contents/Developer + run: sudo xcode-select -s /Applications/Xcode_15.0.1.app/Contents/Developer - name: Prepare fastlane run: bundle install diff --git a/Core/AppDeepLinkSchemes.swift b/Core/AppDeepLinkSchemes.swift index c88f697cbc..38fa976b99 100644 --- a/Core/AppDeepLinkSchemes.swift +++ b/Core/AppDeepLinkSchemes.swift @@ -31,6 +31,8 @@ public enum AppDeepLinkSchemes: String, CaseIterable { case addFavorite = "ddgAddFavorite" + case openVPN = "ddgOpenVPN" + public var url: URL { URL(string: rawValue + "://")! } diff --git a/Core/UserDefaults+NetworkProtection.swift b/Core/UserDefaults+NetworkProtection.swift index fb9def1004..e137aa51c2 100644 --- a/Core/UserDefaults+NetworkProtection.swift +++ b/Core/UserDefaults+NetworkProtection.swift @@ -31,4 +31,10 @@ public extension UserDefaults { } } +public enum NetworkProtectionUserDefaultKeys { + + public static let lastSelectedServer = "com.duckduckgo.network-protection.last-selected-server" + +} + #endif diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index aace48e673..0a6878724b 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -267,6 +267,7 @@ 37FCAAC029930E26000E420A /* FailedAssertionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37FCAABF29930E26000E420A /* FailedAssertionView.swift */; }; 37FD780F2A29E28B00B36DB1 /* SyncErrorHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37FD780E2A29E28B00B36DB1 /* SyncErrorHandler.swift */; }; 4B0295192537BC6700E00CEF /* ConfigurationDebugViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B0295182537BC6700E00CEF /* ConfigurationDebugViewController.swift */; }; + 4B274F602AFEAECC003F0745 /* NetworkProtectionWidgetRefreshModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B274F5F2AFEAECC003F0745 /* NetworkProtectionWidgetRefreshModel.swift */; }; 4B2754EC29E8C7DF00394032 /* Lottie in Frameworks */ = {isa = PBXBuildFile; productRef = 4B2754EB29E8C7DF00394032 /* Lottie */; }; 4B470ED6299C49800086EBDC /* AppTrackingProtectionDatabase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B470ED5299C49800086EBDC /* AppTrackingProtectionDatabase.swift */; }; 4B470ED9299C4AED0086EBDC /* AppTrackingProtectionModel.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 4B470ED7299C4AED0086EBDC /* AppTrackingProtectionModel.xcdatamodeld */; }; @@ -275,6 +276,8 @@ 4B470EE4299C6DFB0086EBDC /* Core.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F143C2E41E4A4CD400CFDE3A /* Core.framework */; }; 4B52648B25F9613B00CB4C24 /* trackerData.json in Resources */ = {isa = PBXBuildFile; fileRef = 4B52648A25F9613B00CB4C24 /* trackerData.json */; }; 4B53648A26718D0E001AA041 /* EmailWaitlist.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B53648926718D0E001AA041 /* EmailWaitlist.swift */; }; + 4B5C462A2AF2A6E6002A4432 /* VPNIntents.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B5C46292AF2A6E6002A4432 /* VPNIntents.swift */; }; + 4B5C462B2AF2BDC4002A4432 /* VPNIntents.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B5C46292AF2A6E6002A4432 /* VPNIntents.swift */; }; 4B60AC97252EC07B00E8D219 /* fullscreenvideo.js in Resources */ = {isa = PBXBuildFile; fileRef = 4B60AC96252EC07B00E8D219 /* fullscreenvideo.js */; }; 4B60ACA1252EC0B100E8D219 /* FullScreenVideoUserScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B60ACA0252EC0B100E8D219 /* FullScreenVideoUserScript.swift */; }; 4B62C4BA25B930DD008912C6 /* AppConfigurationFetchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B62C4B925B930DD008912C6 /* AppConfigurationFetchTests.swift */; }; @@ -291,6 +294,8 @@ 4B83397329AFB8D2003F7EA9 /* AppTrackingProtectionFeedbackModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B83397229AFB8D2003F7EA9 /* AppTrackingProtectionFeedbackModel.swift */; }; 4B83397529AFBCE6003F7EA9 /* AppTrackingProtectionFeedbackModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B83397429AFBCE6003F7EA9 /* AppTrackingProtectionFeedbackModelTests.swift */; }; 4B948E2629DCCDB9002531FA /* Persistence in Frameworks */ = {isa = PBXBuildFile; productRef = 4B948E2529DCCDB9002531FA /* Persistence */; }; + 4BB7CBB02AF59C310014A35F /* VPNWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB7CBAF2AF59C310014A35F /* VPNWidget.swift */; }; + 4BBBBA872B02E85400D965DA /* DesignResourcesKit in Frameworks */ = {isa = PBXBuildFile; productRef = 4BBBBA862B02E85400D965DA /* DesignResourcesKit */; }; 4BC21A2F27238B7500229F0E /* RunLoopExtensionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BC21A2C272388BD00229F0E /* RunLoopExtensionTests.swift */; }; 4BC6DD1C2A60E6AD001EC129 /* ReportBrokenSiteView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BC6DD1B2A60E6AD001EC129 /* ReportBrokenSiteView.swift */; }; 4BE2756827304F57006B20B0 /* URLRequestExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BE27566272F878F006B20B0 /* URLRequestExtension.swift */; }; @@ -1282,6 +1287,7 @@ 37FCAACB2993149A000E420A /* Waitlist */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = Waitlist; sourceTree = ""; }; 37FD780E2A29E28B00B36DB1 /* SyncErrorHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncErrorHandler.swift; sourceTree = ""; }; 4B0295182537BC6700E00CEF /* ConfigurationDebugViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigurationDebugViewController.swift; sourceTree = ""; }; + 4B274F5F2AFEAECC003F0745 /* NetworkProtectionWidgetRefreshModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionWidgetRefreshModel.swift; sourceTree = ""; }; 4B470ED5299C49800086EBDC /* AppTrackingProtectionDatabase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppTrackingProtectionDatabase.swift; sourceTree = ""; }; 4B470ED8299C4AED0086EBDC /* AppTrackingProtectionModel.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = AppTrackingProtectionModel.xcdatamodel; sourceTree = ""; }; 4B470EDA299C4FB20086EBDC /* AppTrackingProtectionListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppTrackingProtectionListViewModel.swift; sourceTree = ""; }; @@ -1289,6 +1295,7 @@ 4B470EE2299C6DD10086EBDC /* AppTrackingProtectionStoringModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppTrackingProtectionStoringModel.swift; sourceTree = ""; }; 4B52648A25F9613B00CB4C24 /* trackerData.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = trackerData.json; sourceTree = ""; }; 4B53648926718D0E001AA041 /* EmailWaitlist.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmailWaitlist.swift; sourceTree = ""; }; + 4B5C46292AF2A6E6002A4432 /* VPNIntents.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNIntents.swift; sourceTree = ""; }; 4B60AC96252EC07B00E8D219 /* fullscreenvideo.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; path = fullscreenvideo.js; sourceTree = ""; }; 4B60ACA0252EC0B100E8D219 /* FullScreenVideoUserScript.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FullScreenVideoUserScript.swift; sourceTree = ""; }; 4B62C4B925B930DD008912C6 /* AppConfigurationFetchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppConfigurationFetchTests.swift; sourceTree = ""; }; @@ -1303,6 +1310,7 @@ 4B83397029AC18C9003F7EA9 /* AppTrackingProtectionStoringModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppTrackingProtectionStoringModelTests.swift; sourceTree = ""; }; 4B83397229AFB8D2003F7EA9 /* AppTrackingProtectionFeedbackModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppTrackingProtectionFeedbackModel.swift; sourceTree = ""; }; 4B83397429AFBCE6003F7EA9 /* AppTrackingProtectionFeedbackModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppTrackingProtectionFeedbackModelTests.swift; sourceTree = ""; }; + 4BB7CBAF2AF59C310014A35F /* VPNWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNWidget.swift; sourceTree = ""; }; 4BC21A2C272388BD00229F0E /* RunLoopExtensionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunLoopExtensionTests.swift; sourceTree = ""; }; 4BC6DD1B2A60E6AD001EC129 /* ReportBrokenSiteView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReportBrokenSiteView.swift; sourceTree = ""; }; 4BE27566272F878F006B20B0 /* URLRequestExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = URLRequestExtension.swift; path = ../DuckDuckGo/URLRequestExtension.swift; sourceTree = ""; }; @@ -2586,6 +2594,7 @@ 8512EA5124ED30D20073EE19 /* SwiftUI.framework in Frameworks */, 85DF714624F7FE6100C89288 /* Core.framework in Frameworks */, 8512EA4F24ED30D20073EE19 /* WidgetKit.framework in Frameworks */, + 4BBBBA872B02E85400D965DA /* DesignResourcesKit in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -3379,6 +3388,14 @@ name = WindowsBrowser; sourceTree = ""; }; + 4B274F5E2AFEAEB3003F0745 /* Widget */ = { + isa = PBXGroup; + children = ( + 4B274F5F2AFEAECC003F0745 /* NetworkProtectionWidgetRefreshModel.swift */, + ); + name = Widget; + sourceTree = ""; + }; 4B470ED4299C484B0086EBDC /* AppTrackingProtection */ = { isa = PBXGroup; children = ( @@ -3394,6 +3411,14 @@ name = AppTrackingProtection; sourceTree = ""; }; + 4B5C46282AF2A6DB002A4432 /* Intents */ = { + isa = PBXGroup; + children = ( + 4B5C46292AF2A6E6002A4432 /* VPNIntents.swift */, + ); + name = Intents; + sourceTree = ""; + }; 4B6484F427FD1E390050A7A1 /* Waitlist */ = { isa = PBXGroup; children = ( @@ -3701,6 +3726,7 @@ 8512EA5324ED30D20073EE19 /* Widgets.swift */, 853273AF24FEFE4600E3C778 /* WidgetsExtension.entitlements */, 853273A924FEF24300E3C778 /* WidgetViews.swift */, + 4BB7CBAF2AF59C310014A35F /* VPNWidget.swift */, ); path = Widgets; sourceTree = ""; @@ -4496,6 +4522,8 @@ EE0153DF2A6EABAF002A8B26 /* Helpers */, EEFD562D2A65B68B00DAEC48 /* Invite */, EECD94B32A28B96C0085C66E /* Status */, + 4B5C46282AF2A6DB002A4432 /* Intents */, + 4B274F5E2AFEAEB3003F0745 /* Widget */, EE8594982A44791C008A6D06 /* NetworkProtectionTunnelController.swift */, ); name = NetworkProtection; @@ -5417,6 +5445,9 @@ 85DF714924F7FE6100C89288 /* PBXTargetDependency */, ); name = WidgetsExtension; + packageProductDependencies = ( + 4BBBBA862B02E85400D965DA /* DesignResourcesKit */, + ); productName = WidgetsExtension; productReference = 8512EA4D24ED30D20073EE19 /* WidgetsExtension.appex */; productType = "com.apple.product-type.app-extension"; @@ -6379,6 +6410,7 @@ 85C861E628FF1B5F00189466 /* HomeViewSectionRenderersExtension.swift in Sources */, F1D477C61F2126CC0031ED49 /* OmniBarState.swift in Sources */, 85F2FFCD2211F615006BB258 /* MainViewController+KeyCommands.swift in Sources */, + 4B274F602AFEAECC003F0745 /* NetworkProtectionWidgetRefreshModel.swift in Sources */, 0268FC132A449F04000EE6A2 /* OnboardingContainerView.swift in Sources */, 858650D9246B0D3C00C36F8A /* DaxOnboardingViewController.swift in Sources */, 312E5746283BB04A00C18FA0 /* AutofillEmptySearchView.swift in Sources */, @@ -6437,6 +6469,7 @@ 020108A729A6ABF600644F9D /* AppTPToggleView.swift in Sources */, 02A54A982A093126000C8FED /* AppTPHomeViewModel.swift in Sources */, F1617C191E573EA800DEDCAF /* TabSwitcherDelegate.swift in Sources */, + 4B5C462A2AF2A6E6002A4432 /* VPNIntents.swift in Sources */, 310742A62848CD780012660B /* BackForwardMenuHistoryItem.swift in Sources */, 858566FB252E55D6007501B8 /* ImageCacheDebugViewController.swift in Sources */, 0290472E29E99A2F0008FE3C /* GenericIconView.swift in Sources */, @@ -6667,6 +6700,8 @@ 853273B324FF114700E3C778 /* DeepLinks.swift in Sources */, 853273B424FFB36100E3C778 /* UIColorExtension.swift in Sources */, 853273AB24FEF27500E3C778 /* WidgetViews.swift in Sources */, + 4B5C462B2AF2BDC4002A4432 /* VPNIntents.swift in Sources */, + 4BB7CBB02AF59C310014A35F /* VPNWidget.swift in Sources */, 8512EA5424ED30D20073EE19 /* Widgets.swift in Sources */, 85DB12EB2A1FE2A4000A4A72 /* LockScreenWidgets.swift in Sources */, 8544C37C250B827300A0FE73 /* UserText.swift in Sources */, @@ -8499,7 +8534,7 @@ MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG NETWORK_PROTECTION"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG NETWORK_PROTECTION ALPHA"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; TARGETED_DEVICE_FAMILY = "1,2"; VALID_ARCHS = "$(ARCHS_STANDARD_64_BIT)"; @@ -9197,6 +9232,11 @@ package = 98A16C2928A11BDE00A6C003 /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; productName = Persistence; }; + 4BBBBA862B02E85400D965DA /* DesignResourcesKit */ = { + isa = XCSwiftPackageProductDependency; + package = F42D541B29DCA40B004C4FF1 /* XCRemoteSwiftPackageReference "DesignResourcesKit" */; + productName = DesignResourcesKit; + }; 851481872A600EFC00ABC65F /* RemoteMessaging */ = { isa = XCSwiftPackageProductDependency; package = 98A16C2928A11BDE00A6C003 /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; diff --git a/DuckDuckGo/AppDelegate+AppDeepLinks.swift b/DuckDuckGo/AppDelegate+AppDeepLinks.swift index 60788a6abc..ec8d49dbac 100644 --- a/DuckDuckGo/AppDelegate+AppDeepLinks.swift +++ b/DuckDuckGo/AppDelegate+AppDeepLinks.swift @@ -22,6 +22,7 @@ import Core extension AppDelegate { + // swiftlint:disable:next cyclomatic_complexity func handleAppDeepLink(_ app: UIApplication, _ mainViewController: MainViewController?, _ url: URL) -> Bool { guard let mainViewController else { return false } @@ -50,6 +51,11 @@ extension AppDelegate { case .newEmail: mainViewController.newEmailAddress() + case .openVPN: +#if NETWORK_PROTECTION + presentNetworkProtectionStatusSettingsModal() +#endif + default: guard app.applicationState == .active, let currentTab = mainViewController.currentTab else { diff --git a/DuckDuckGo/AppDelegate.swift b/DuckDuckGo/AppDelegate.swift index 9b3a32e512..3732a3fd5e 100644 --- a/DuckDuckGo/AppDelegate.swift +++ b/DuckDuckGo/AppDelegate.swift @@ -67,6 +67,10 @@ class AppDelegate: UIResponder, UIApplicationDelegate { private var appTrackingProtectionDatabase: CoreDataDatabase = AppTrackingProtectionDatabase.make() #endif +#if NETWORK_PROTECTION + private let widgetRefreshModel = NetworkProtectionWidgetRefreshModel() +#endif + private var autoClear: AutoClear? private var showKeyboardIfSettingOn = true private var lastBackgroundDate: Date? @@ -278,6 +282,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { syncDataProviders: syncDataProviders, appSettings: AppDependencyProvider.shared.appSettings) #endif + main.loadViewIfNeeded() window = UIWindow(frame: UIScreen.main.bounds) @@ -316,6 +321,10 @@ class AppDelegate: UIResponder, UIApplicationDelegate { AppDependencyProvider.shared.appSettings.setAutofillIsNewInstallForOnByDefault() } +#if NETWORK_PROTECTION + widgetRefreshModel.beginObservingVPNStatus() +#endif + return true } @@ -411,6 +420,10 @@ class AppDelegate: UIResponder, UIApplicationDelegate { syncService.scheduler.notifyAppLifecycleEvent() fireFailedCompilationsPixelIfNeeded() refreshShortcuts() + +#if NETWORK_PROTECTION + widgetRefreshModel.refreshVPNWidget() +#endif } func applicationWillResignActive(_ application: UIApplication) { @@ -566,7 +579,12 @@ class AppDelegate: UIResponder, UIApplicationDelegate { } NotificationCenter.default.post(name: AutofillLoginListAuthenticator.Notifications.invalidateContext, object: nil) - mainViewController?.clearNavigationStack() + + // The openVPN action handles the navigation stack on its own and does not need it to be cleared + if url != AppDeepLinkSchemes.openVPN.url { + mainViewController?.clearNavigationStack() + } + autoClear?.applicationWillMoveToForeground() showKeyboardIfSettingOn = false @@ -814,7 +832,7 @@ extension AppDelegate: UNUserNotificationCenterDelegate { } #if NETWORK_PROTECTION - private func presentNetworkProtectionStatusSettingsModal() { + func presentNetworkProtectionStatusSettingsModal() { if #available(iOS 15, *) { let networkProtectionRoot = NetworkProtectionRootViewController() presentSettings(with: networkProtectionRoot) diff --git a/DuckDuckGo/NetworkProtectionStatusViewModel.swift b/DuckDuckGo/NetworkProtectionStatusViewModel.swift index dc8924c38b..6d6a817072 100644 --- a/DuckDuckGo/NetworkProtectionStatusViewModel.swift +++ b/DuckDuckGo/NetworkProtectionStatusViewModel.swift @@ -22,6 +22,7 @@ import Foundation import Combine import NetworkProtection +import WidgetKit final class NetworkProtectionStatusViewModel: ObservableObject { private static var dateFormatter: DateComponentsFormatter = { @@ -178,6 +179,8 @@ final class NetworkProtectionStatusViewModel: ObservableObject { } else { await disableNetP() } + + WidgetCenter.shared.reloadTimelines(ofKind: "VPNStatusWidget") } @MainActor diff --git a/DuckDuckGo/NetworkProtectionWidgetRefreshModel.swift b/DuckDuckGo/NetworkProtectionWidgetRefreshModel.swift new file mode 100644 index 0000000000..b9b5871ffc --- /dev/null +++ b/DuckDuckGo/NetworkProtectionWidgetRefreshModel.swift @@ -0,0 +1,46 @@ +// +// NetworkProtectionWidgetRefreshModel.swift +// DuckDuckGo +// +// Copyright © 2023 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#if NETWORK_PROTECTION + +import Foundation +import Combine +import NetworkExtension +import WidgetKit + +class NetworkProtectionWidgetRefreshModel { + + private var cancellable: AnyCancellable? + + public func beginObservingVPNStatus() { + cancellable = NotificationCenter.default.publisher(for: .NEVPNStatusDidChange) + .debounce(for: .seconds(0.5), scheduler: RunLoop.main) + .receive(on: DispatchQueue.main) + .sink { [weak self] _ in + self?.refreshVPNWidget() + } + } + + public func refreshVPNWidget() { + WidgetCenter.shared.reloadTimelines(ofKind: "VPNStatusWidget") + } + +} + +#endif diff --git a/DuckDuckGo/VPNIntents.swift b/DuckDuckGo/VPNIntents.swift new file mode 100644 index 0000000000..716396607f --- /dev/null +++ b/DuckDuckGo/VPNIntents.swift @@ -0,0 +1,102 @@ +// +// VPNIntents.swift +// DuckDuckGo +// +// Copyright © 2023 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import AppIntents +import NetworkExtension +import WidgetKit + +@available(iOS 17.0, *) +struct DisableVPNIntent: AppIntent { + + static let title: LocalizedStringResource = "Disable VPN" + static let description: LocalizedStringResource = "Disables the DuckDuckGo VPN" + static let openAppWhenRun: Bool = false + + @MainActor + func perform() async throws -> some IntentResult { + do { + let managers = try await NETunnelProviderManager.loadAllFromPreferences() + guard let manager = managers.first else { + return .result() + } + + manager.isOnDemandEnabled = false + try await manager.saveToPreferences() + manager.connection.stopVPNTunnel() + + WidgetCenter.shared.reloadTimelines(ofKind: "VPNStatusWidget") + var iterations = 0 + + while iterations <= 10 { + try? await Task.sleep(interval: .seconds(0.5)) + + if manager.connection.status == .disconnected { + return .result() + } + + iterations += 1 + } + + return .result() + } catch { + return .result() + } + } + +} + +@available(iOS 17.0, *) +struct EnableVPNIntent: AppIntent { + + static let title: LocalizedStringResource = "Enable VPN" + static let description: LocalizedStringResource = "Enables the DuckDuckGo VPN" + static let openAppWhenRun: Bool = false + + @MainActor + func perform() async throws -> some IntentResult { + do { + let managers = try await NETunnelProviderManager.loadAllFromPreferences() + guard let manager = managers.first else { + return .result() + } + + manager.isOnDemandEnabled = true + try await manager.saveToPreferences() + try manager.connection.startVPNTunnel() + + WidgetCenter.shared.reloadTimelines(ofKind: "VPNStatusWidget") + var iterations = 0 + + while iterations <= 10 { + try? await Task.sleep(interval: .seconds(0.5)) + + if manager.connection.status == .connected { + return .result() + } + + iterations += 1 + } + + return .result() + } catch { + return .result() + } + } + +} diff --git a/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift b/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift index 5db8d2c3c5..d3ca118411 100644 --- a/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift +++ b/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift @@ -20,15 +20,18 @@ #if NETWORK_PROTECTION import Foundation -import NetworkProtection import Common +import Combine import Core import Networking import NetworkExtension +import NetworkProtection // Initial implementation for initial Network Protection tests. Will be fleshed out with https://app.asana.com/0/1203137811378537/1204630829332227/f final class NetworkProtectionPacketTunnelProvider: PacketTunnelProvider { + private var cancellables = Set() + // MARK: - PacketTunnelProvider.Event reporting private static var packetTunnelProviderEvents: EventMapping = .init { event, _, _, _ in @@ -183,6 +186,7 @@ final class NetworkProtectionPacketTunnelProvider: PacketTunnelProvider { debugEvents: Self.networkProtectionDebugEvents(controllerErrorStore: errorStore), providerEvents: Self.packetTunnelProviderEvents) startMonitoringMemoryPressureEvents() + observeServerChanges() APIRequest.Headers.setUserAgent(DefaultUserAgentManager.duckDuckGoUserAgent) } @@ -209,6 +213,14 @@ final class NetworkProtectionPacketTunnelProvider: PacketTunnelProvider { source.resume() } } + + private func observeServerChanges() { + lastSelectedServerInfoPublisher.sink { server in + let location = server?.serverLocation ?? "Unknown Location" + UserDefaults.networkProtectionGroupDefaults.set(location, forKey: NetworkProtectionUserDefaultKeys.lastSelectedServer) + } + .store(in: &cancellables) + } } #endif diff --git a/Widgets/Assets.xcassets/vpn-off.imageset/Contents.json b/Widgets/Assets.xcassets/vpn-off.imageset/Contents.json new file mode 100644 index 0000000000..839c6e2214 --- /dev/null +++ b/Widgets/Assets.xcassets/vpn-off.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "vpn-off.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Widgets/Assets.xcassets/vpn-off.imageset/vpn-off.pdf b/Widgets/Assets.xcassets/vpn-off.imageset/vpn-off.pdf new file mode 100644 index 0000000000000000000000000000000000000000..8438e0ef8e27b1cdb6d0e04e3cd1fa039064acd1 GIT binary patch literal 8715 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 literal 0 HcmV?d00001 diff --git a/Widgets/Assets.xcassets/vpn-on.imageset/Contents.json b/Widgets/Assets.xcassets/vpn-on.imageset/Contents.json new file mode 100644 index 0000000000..ec33d453f4 --- /dev/null +++ b/Widgets/Assets.xcassets/vpn-on.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "vpn-on.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Widgets/Assets.xcassets/vpn-on.imageset/vpn-on.pdf b/Widgets/Assets.xcassets/vpn-on.imageset/vpn-on.pdf new file mode 100644 index 0000000000000000000000000000000000000000..09c2cae8055b1684d588daff48cae65a0ebc1dac GIT binary patch literal 9104 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 VPNStatusTimelineEntry { + return VPNStatusTimelineEntry(date: Date(), status: .status(.connected), location: "Los Angeles, CA") + } + + func getSnapshot(in context: Context, completion: @escaping (VPNStatusTimelineEntry) -> Void) { + let entry = VPNStatusTimelineEntry(date: Date(), status: .status(.connected), location: "Los Angeles, CA") + completion(entry) + } + + func getTimeline(in context: Context, completion: @escaping (Timeline) -> Void) { + NETunnelProviderManager.loadAllFromPreferences { managers, error in + let defaults = UserDefaults.networkProtectionGroupDefaults + let location = defaults.string(forKey: NetworkProtectionUserDefaultKeys.lastSelectedServer) ?? "Unknown Location" + let expiration = Date().addingTimeInterval(TimeInterval.minutes(5)) + + if error != nil { + let entry = VPNStatusTimelineEntry(date: expiration, status: .error, location: location) + let timeline = Timeline(entries: [entry], policy: .atEnd) + completion(timeline) + return + } + + guard let manager = managers?.first else { + let entry = VPNStatusTimelineEntry(date: expiration, status: .notConfigured, location: location) + let timeline = Timeline(entries: [entry], policy: .atEnd) + completion(timeline) + return + } + + let status = manager.connection.status + let entry = VPNStatusTimelineEntry(date: expiration, status: .status(status), location: location) + let timeline = Timeline(entries: [entry], policy: .atEnd) + + completion(timeline) + } + } +} + +extension NEVPNStatus { + var description: String { + switch self { + case .connected: return "Connected" + case .connecting: return "Connecting" + case .disconnected: return "Disconnected" + case .disconnecting: return "Disconnecting" + case .invalid: return "Invalid" + case .reasserting: return "Reasserting" + default: return "Unknown Status" + } + } + + var isConnected: Bool { + switch self { + case .connected, .connecting, .reasserting: return true + case .disconnecting, .disconnected: return false + default: return false + } + } +} + +@available(iOSApplicationExtension 17.0, *) +struct VPNStatusView: View { + @Environment(\.widgetFamily) var family: WidgetFamily + var entry: VPNStatusTimelineProvider.Entry + + @ViewBuilder + var body: some View { + Group { + switch entry.status { + case .status(let status): + HStack { + connectionView(with: status) + .padding([.leading, .trailing], 16) + + Spacer() + } + case .error: + Text("Error") + .foregroundStyle(Color.black) + case .notConfigured: + Text("VPN Not Configured") + .foregroundStyle(Color.black) + } + } + .frame(maxWidth: .infinity, maxHeight: .infinity) + .containerBackground(for: .widget) { + switch entry.status { + case .status(let status): + switch status { + case .connecting, .connected, .reasserting: + Color.vpnWidgetBackgroundColor + case .disconnecting, .disconnected, .invalid: + Color.white + @unknown default: + Color.white + } + case .error, .notConfigured: + Color.white + } + } + } + + private func connectionView(with status: NEVPNStatus) -> some View { + HStack { + VStack(alignment: .leading, spacing: 0) { + Image(headerImageName(with: status)) + .frame(width: 50, height: 54) + .padding(.top, 15) + + Spacer() + + Text(title(with: status)) + .font(.system(size: 16, weight: .semibold)) + .fontWeight(.semibold) + .foregroundStyle(status.isConnected ? Color.white : Color.black) + + Text(status.isConnected ? entry.location : "VPN is Off") + .font(.system(size: 12, weight: .medium)) + .foregroundStyle(status.isConnected ? Color.white : Color.black) + .opacity(status.isConnected ? 0.8 : 0.6) + + switch status { + case .connected, .connecting, .reasserting: + Button(intent: DisableVPNIntent()) { + Text("Disconnect") + .font(.system(size: 15, weight: .medium)) + .fontWeight(.semibold) + } + .foregroundStyle(Color.vpnWidgetBackgroundColor) + .buttonStyle(.borderedProminent) + .tint(.white) + .disabled(status != .connected) + .padding(.top, 6) + .padding(.bottom, 16) + case .disconnected, .disconnecting: + Button(intent: EnableVPNIntent()) { + Text("Connect") + .font(.system(size: 15, weight: .medium)) + .fontWeight(.semibold) + } + .foregroundStyle(.white) + .buttonStyle(.borderedProminent) + .tint(Color.vpnWidgetBackgroundColor) + .disabled(status != .disconnected) + .padding(.top, 6) + .padding(.bottom, 16) + default: + Spacer() + } + } + } + } + + private func headerImageName(with status: NEVPNStatus) -> String { + switch status { + case .connecting, .connected, .reasserting: return "vpn-on" + case .disconnecting, .disconnected: return "vpn-off" + case .invalid: return "vpn-off" + @unknown default: return "vpn-off" + } + } + + private func title(with status: NEVPNStatus) -> String { + switch status { + case .connecting, .connected, .reasserting: return "Protected" + case .disconnecting, .disconnected: return "Unprotected" + case .invalid: return "Invalid" + @unknown default: return "Unknown" + } + } + +} + +@available(iOSApplicationExtension 17.0, *) +struct VPNStatusWidget: Widget { + let kind: String = "VPNStatusWidget" + + var body: some WidgetConfiguration { + StaticConfiguration(kind: kind, provider: VPNStatusTimelineProvider()) { entry in + VPNStatusView(entry: entry).widgetURL(DeepLinks.openVPN) + } + .configurationDisplayName("VPN Status") + .description("View and manage the VPN connection") + .supportedFamilies([.systemSmall]) + .contentMarginsDisabled() + } +} + +struct VPNStatusView_Previews: PreviewProvider { + + static let connectedState = VPNStatusTimelineProvider.Entry( + date: Date(), + status: .status(.connected), + location: "Paoli, PA" + ) + + static let disconnectedState = VPNStatusTimelineProvider.Entry( + date: Date(), + status: .status(.disconnected), + location: "Paoli, PA" + ) + + static let notConfiguredState = VPNStatusTimelineProvider.Entry( + date: Date(), + status: .notConfigured, + location: "Paoli, PA" + ) + + static var previews: some View { + if #available(iOSApplicationExtension 17.0, *) { + VPNStatusView(entry: connectedState) + .previewContext(WidgetPreviewContext(family: .systemSmall)) + .environment(\.colorScheme, .light) + + VPNStatusView(entry: connectedState) + .previewContext(WidgetPreviewContext(family: .systemSmall)) + .environment(\.colorScheme, .dark) + + VPNStatusView(entry: disconnectedState) + .previewContext(WidgetPreviewContext(family: .systemSmall)) + .environment(\.colorScheme, .light) + + VPNStatusView(entry: disconnectedState) + .previewContext(WidgetPreviewContext(family: .systemSmall)) + .environment(\.colorScheme, .dark) + + VPNStatusView(entry: notConfiguredState) + .previewContext(WidgetPreviewContext(family: .systemSmall)) + .environment(\.colorScheme, .light) + + VPNStatusView(entry: notConfiguredState) + .previewContext(WidgetPreviewContext(family: .systemSmall)) + .environment(\.colorScheme, .dark) + } else { + Text("iOS 17 required") + } + } + +} + +extension Color { + + static var vpnWidgetBackgroundColor: Color { + let color = UIColor(designSystemColor: .accent).resolvedColor(with: UITraitCollection(userInterfaceStyle: .light)) + return Color(color) + } + +} + +#endif diff --git a/Widgets/Widgets.swift b/Widgets/Widgets.swift index ae5ae2b1d2..39ab7b0057 100644 --- a/Widgets/Widgets.swift +++ b/Widgets/Widgets.swift @@ -25,6 +25,7 @@ import CoreData import Kingfisher import Bookmarks import Persistence +import NetworkExtension struct Favorite { @@ -202,6 +203,12 @@ struct Widgets: WidgetBundle { SearchWidget() FavoritesWidget() +#if ALPHA + if #available(iOSApplicationExtension 17.0, *) { + VPNStatusWidget() + } +#endif + if #available(iOSApplicationExtension 16.0, *) { SearchLockScreenWidget() VoiceSearchLockScreenWidget() diff --git a/Widgets/WidgetsExtension.entitlements b/Widgets/WidgetsExtension.entitlements index 2dd2c82001..4d39b3e562 100644 --- a/Widgets/WidgetsExtension.entitlements +++ b/Widgets/WidgetsExtension.entitlements @@ -6,6 +6,7 @@ $(GROUP_ID_PREFIX).bookmarks $(GROUP_ID_PREFIX).database + $(GROUP_ID_PREFIX).netp diff --git a/WidgetsExtensionAlpha.entitlements b/WidgetsExtensionAlpha.entitlements index d5324a15bd..4d39b3e562 100644 --- a/WidgetsExtensionAlpha.entitlements +++ b/WidgetsExtensionAlpha.entitlements @@ -4,8 +4,9 @@ com.apple.security.application-groups - group.com.duckduckgo.alpha.database - group.com.duckduckgo.alpha.bookmarks + $(GROUP_ID_PREFIX).bookmarks + $(GROUP_ID_PREFIX).database + $(GROUP_ID_PREFIX).netp From 686881c51c78beeedbf1ab59d3321eb8b2c6b27a Mon Sep 17 00:00:00 2001 From: Shane Osbourne Date: Tue, 14 Nov 2023 08:26:05 +0000 Subject: [PATCH 05/21] Updating tests to support latest reference tests (#2145) Co-authored-by: Shane Osbourne Co-authored-by: Dominik Kapusta --- DuckDuckGoTests/BrokenSiteReportingTests.swift | 3 ++- submodules/privacy-reference-tests | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/DuckDuckGoTests/BrokenSiteReportingTests.swift b/DuckDuckGoTests/BrokenSiteReportingTests.swift index e65fb7f268..6c663fda98 100644 --- a/DuckDuckGoTests/BrokenSiteReportingTests.swift +++ b/DuckDuckGoTests/BrokenSiteReportingTests.swift @@ -75,7 +75,7 @@ final class BrokenSiteReportingTests: XCTestCase { tdsETag: test.blocklistVersion, ampUrl: nil, urlParametersRemoved: false, - protectionsState: true, + protectionsState: test.protectionsEnabled, model: test.model ?? "", manufacturer: test.manufacturer ?? "", systemVersion: test.os ?? "", @@ -148,6 +148,7 @@ private struct Test: Codable { let exceptPlatforms: [String] let manufacturer, model, os: String? let gpcEnabled: Bool? + let protectionsEnabled: Bool } // MARK: - ExpectReportURLParam diff --git a/submodules/privacy-reference-tests b/submodules/privacy-reference-tests index 7519c3d430..a3acc21947 160000 --- a/submodules/privacy-reference-tests +++ b/submodules/privacy-reference-tests @@ -1 +1 @@ -Subproject commit 7519c3d430e5dcef75b6128bfdadb0de3f463a49 +Subproject commit a3acc2194758bec0f01f57dd0c5f106de01a354e From 60f8601fc44ddacc06b6bff54765c3b408e88e50 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Tue, 14 Nov 2023 12:43:13 +0100 Subject: [PATCH 06/21] Fix for duplicated "atb" in Pixel request (#2139) Task/Issue URL: https://app.asana.com/0/414709148257752/1205754960806321/f Description: This change removes the custom atb value added in the specific pixel request in favour of the one added automatically to every pixel request. This should maintain the --- DuckDuckGo/BrokenSiteInfo.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/DuckDuckGo/BrokenSiteInfo.swift b/DuckDuckGo/BrokenSiteInfo.swift index fc516b48ef..2f310936e3 100644 --- a/DuckDuckGo/BrokenSiteInfo.swift +++ b/DuckDuckGo/BrokenSiteInfo.swift @@ -99,7 +99,6 @@ public struct BrokenSiteInfo { Keys.tds: tdsETag?.trimmingCharacters(in: CharacterSet(charactersIn: "\"")) ?? "", Keys.blockedTrackers: blockedTrackerDomains.joined(separator: ","), Keys.surrogates: installedSurrogates.joined(separator: ","), - Keys.atb: StatisticsUserDefaults().atb ?? "", Keys.os: systemVersion, Keys.manufacturer: manufacturer, Keys.model: model, From 9c4124e8ab392c069d4a0041ef5bcb554be807d2 Mon Sep 17 00:00:00 2001 From: Lorenzo Mattei Date: Tue, 14 Nov 2023 15:27:39 +0100 Subject: [PATCH 07/21] Update sync e2e tests to new setup flow (#2151) --- .github/workflows/sync-end-to-end.yml | 19 ++++++++++++++++ .maestro/shared/sync_create.yaml | 16 ++++++------- .maestro/shared/sync_delete.yaml | 2 +- .maestro/sync_tests/01_create_account.yaml | 6 ++--- .maestro/sync_tests/02_login_account.yaml | 17 ++++++-------- .maestro/sync_tests/03_recover_account.yaml | 21 ++++++++--------- .maestro/sync_tests/04_sync_data.yaml | 25 +++++++++------------ 7 files changed, 57 insertions(+), 49 deletions(-) diff --git a/.github/workflows/sync-end-to-end.yml b/.github/workflows/sync-end-to-end.yml index df2a04df7c..3dc49732fa 100644 --- a/.github/workflows/sync-end-to-end.yml +++ b/.github/workflows/sync-end-to-end.yml @@ -60,4 +60,23 @@ jobs: env: | CODE=${{ steps.sync-recovery-code.outputs.recovery-code }} + - name: Create Asana task when workflow failed + if: ${{ failure() }} + run: | + curl -s "https://app.asana.com/api/1.0/tasks" \ + --header "Accept: application/json" \ + --header "Authorization: Bearer ${{ secrets.ASANA_ACCESS_TOKEN }}" \ + --header "Content-Type: application/json" \ + --data ' { "data": { "name": "GH Workflow Failure - Sync End to end tests", "workspace": "${{ vars.GH_ASANA_WORKSPACE_ID }}", "projects": [ "${{ vars.GH_ASANA_IOS_APP_PROJECT_ID }}" ], "notes" : "The end to end workflow has failed. See https://github.com/duckduckgo/iOS/actions/runs/${{ github.run_id }}" } }' + + - name: Upload logs when workflow failed + uses: actions/upload-artifact@v3 + if: failure() + with: + name: BuildLogs + path: | + xcodebuild.log + DerivedData/Logs/Test/*.xcresult + retention-days: 7 + diff --git a/.maestro/shared/sync_create.yaml b/.maestro/shared/sync_create.yaml index 8164466ac3..bc4bb338d0 100644 --- a/.maestro/shared/sync_create.yaml +++ b/.maestro/shared/sync_create.yaml @@ -1,12 +1,10 @@ appId: com.duckduckgo.mobile.ios --- -- assertVisible: Sync -- tapOn: Sync -- assertVisible: Sync -- tapOn: "0" -- assertVisible: Turn on Sync? -- tapOn: Turn on Sync -- tapOn: Sync Another Device -- tapOn: Show QR Code -- assertVisible: "Go to Settings > Sync in the DuckDuckGo App on a different device and scan this QR code to sync." \ No newline at end of file +- assertVisible: Sync & Back Up +- tapOn: Sync & Back Up +- assertVisible: Sync & Back Up +- tapOn: Start Sync & Back Up +- assertVisible: All Set! +- tapOn: Next +- assertVisible: Save Recovery Code? diff --git a/.maestro/shared/sync_delete.yaml b/.maestro/shared/sync_delete.yaml index a82919c953..54a78f3c15 100644 --- a/.maestro/shared/sync_delete.yaml +++ b/.maestro/shared/sync_delete.yaml @@ -1,7 +1,7 @@ appId: com.duckduckgo.mobile.ios --- -- assertVisible: Sync +- assertVisible: Sync & Back Up - scroll - tapOn: point: 50%,91% # TODO: Revisit after new setup flow has been implemented. diff --git a/.maestro/sync_tests/01_create_account.yaml b/.maestro/sync_tests/01_create_account.yaml index 6e39ee2fd4..5b48e6c829 100644 --- a/.maestro/sync_tests/01_create_account.yaml +++ b/.maestro/sync_tests/01_create_account.yaml @@ -21,9 +21,7 @@ tags: # Clean up -- tapOn: Back -- tapOn: Cancel - tapOn: Not Now -- assertVisible: Sync +- assertVisible: Sync & Back Up - runFlow: - file: ../shared/sync_delete.yaml \ No newline at end of file + file: ../shared/sync_delete.yaml diff --git a/.maestro/sync_tests/02_login_account.yaml b/.maestro/sync_tests/02_login_account.yaml index aef46f2912..99a69684fa 100644 --- a/.maestro/sync_tests/02_login_account.yaml +++ b/.maestro/sync_tests/02_login_account.yaml @@ -21,25 +21,22 @@ tags: file: ../shared/sync_create.yaml # Copy Sync Code and Log Out -- tapOn: Back -- tapOn: Cancel -- assertVisible: Save Recovery Key -- tapOn: Copy Key +- tapOn: Copy Code - tapOn: Not Now -- tapOn: "1" +- assertVisible: Sync & Back Up +- tapOn: Turn Off Sync & Back Up - assertVisible: Turn Off Sync? - tapOn: Remove # Login -- tapOn: "0" -- tapOn: Recover Your Synced Data -- tapOn: Manually Enter Code +- assertVisible: Sync & Back Up +- tapOn: Enter Text Code - tapOn: Paste - assertVisible: Device Synced! - tapOn: Next - tapOn: Not Now # Clean up -- assertVisible: Sync +- assertVisible: Sync & Back Up - runFlow: - file: ../shared/sync_delete.yaml \ No newline at end of file + file: ../shared/sync_delete.yaml diff --git a/.maestro/sync_tests/03_recover_account.yaml b/.maestro/sync_tests/03_recover_account.yaml index 265684b884..d20cbf3b3b 100644 --- a/.maestro/sync_tests/03_recover_account.yaml +++ b/.maestro/sync_tests/03_recover_account.yaml @@ -19,8 +19,12 @@ tags: - tapOn: id: searchEntry - inputText: ${CODE} -- longPressOn: - id: searchEntry +- repeat: + while: + notVisible: "Select All" + commands: + - tapOn: + id: searchEntry - tapOn: Select All - tapOn: Cut - tapOn: @@ -35,14 +39,11 @@ tags: - tapOn: Settings - runFlow: file: ../shared/set_internal_user.yaml -- assertVisible: Sync -- tapOn: Sync -- assertVisible: Sync -- tapOn: "0" -- assertVisible: Turn on Sync? -- tapOn: Recover Your Synced Data -- assertVisible: Scan QR Code -- tapOn: Manually Enter Code +- assertVisible: Sync & Back Up +- tapOn: Sync & Back Up +- assertVisible: Sync & Back up +- tapOn: Recover Your Data +- tapOn: Enter Text Code - tapOn: Paste - assertVisible: Device Synced! - tapOn: Next diff --git a/.maestro/sync_tests/04_sync_data.yaml b/.maestro/sync_tests/04_sync_data.yaml index 643cd1a431..329b22bb80 100644 --- a/.maestro/sync_tests/04_sync_data.yaml +++ b/.maestro/sync_tests/04_sync_data.yaml @@ -56,14 +56,12 @@ tags: - tapOn: id: searchEntry - inputText: ${CODE} -- longPressOn: - id: searchEntry -- runFlow: - when: - visible: - text: searchEntry +- repeat: + while: + notVisible: "Select All" commands: - - tapOn: searchEntry + - tapOn: + id: searchEntry - tapOn: Select All - tapOn: Cut - tapOn: @@ -86,14 +84,11 @@ tags: - tapOn: Settings - runFlow: file: ../shared/set_internal_user.yaml -- assertVisible: Sync -- tapOn: Sync -- assertVisible: Sync -- tapOn: "0" -- assertVisible: Turn on Sync? -- tapOn: Recover Your Synced Data -- assertVisible: Scan QR Code -- tapOn: Manually Enter Code +- assertVisible: Sync & Back Up +- tapOn: Sync & Back Up +- assertVisible: Sync & Back up +- tapOn: Recover Your Data +- tapOn: Enter Text Code - tapOn: Paste - assertVisible: Device Synced! - tapOn: Next From e88ee7fe7748f683dd4aaaf927b2f03bdc752ab3 Mon Sep 17 00:00:00 2001 From: Graeme Arthur Date: Tue, 14 Nov 2023 18:15:44 +0100 Subject: [PATCH 08/21] BSK changes for NetP iOS Geoswitching (#2141) --- Core/PixelEvent.swift | 5 + DuckDuckGo.xcodeproj/project.pbxproj | 20 +++- .../xcshareddata/swiftpm/Package.resolved | 4 +- .../EventMapping+NetworkProtectionError.swift | 7 +- ...orkProtectionConvenienceInitialisers.swift | 29 ++++- .../NetworkProtectionVPNLocationView.swift | 100 ++++++++++++++++++ .../NetworkProtectionVPNSettingsView.swift | 9 ++ ...etworkProtectionVPNSettingsViewModel.swift | 50 +++++++++ DuckDuckGo/UserText.swift | 6 ++ DuckDuckGo/en.lproj/Localizable.strings | 18 ++++ ...etworkProtectionPacketTunnelProvider.swift | 7 +- 11 files changed, 248 insertions(+), 7 deletions(-) create mode 100644 DuckDuckGo/NetworkProtectionVPNLocationView.swift create mode 100644 DuckDuckGo/NetworkProtectionVPNSettingsViewModel.swift diff --git a/Core/PixelEvent.swift b/Core/PixelEvent.swift index a2005a22f2..057446861c 100644 --- a/Core/PixelEvent.swift +++ b/Core/PixelEvent.swift @@ -312,6 +312,8 @@ extension Pixel { case networkProtectionClientFailedToEncodeRegisterKeyRequest case networkProtectionClientFailedToFetchRegisteredServers case networkProtectionClientFailedToParseRegisteredServersResponse + case networkProtectionClientFailedToFetchLocations + case networkProtectionClientFailedToParseLocationsResponse case networkProtectionClientFailedToEncodeRedeemRequest case networkProtectionClientInvalidInviteCode case networkProtectionClientFailedToRedeemInviteCode @@ -810,6 +812,9 @@ extension Pixel.Event { case .networkProtectionClientFailedToFetchRegisteredServers: return "m_netp_backend_api_error_failed_to_fetch_registered_servers" case .networkProtectionClientFailedToParseRegisteredServersResponse: return "m_netp_backend_api_error_parsing_device_registration_response_failed" + case .networkProtectionClientFailedToFetchLocations: return "m_netp_backend_api_error_failed_to_fetch_locations" + case .networkProtectionClientFailedToParseLocationsResponse: + return "m_netp_backend_api_error_parsing_locations_response_failed" case .networkProtectionClientFailedToEncodeRedeemRequest: return "m_netp_backend_api_error_encoding_redeem_request_body_failed" case .networkProtectionClientInvalidInviteCode: return "m_netp_backend_api_error_invalid_invite_code" case .networkProtectionClientFailedToRedeemInviteCode: return "m_netp_backend_api_error_failed_to_redeem_invite_code" diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 0a6878724b..1bbef8b510 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -758,6 +758,8 @@ EE0153EB2A6FF970002A8B26 /* NetworkProtectionRootViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE0153EA2A6FF970002A8B26 /* NetworkProtectionRootViewModelTests.swift */; }; EE0153ED2A6FF9E6002A8B26 /* NetworkProtectionRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE0153EC2A6FF9E6002A8B26 /* NetworkProtectionRootView.swift */; }; EE0153EF2A70021E002A8B26 /* NetworkProtectionInviteView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE0153EE2A70021E002A8B26 /* NetworkProtectionInviteView.swift */; }; + EE01EB402AFBD0000096AAC9 /* NetworkProtectionVPNSettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE01EB3F2AFBD0000096AAC9 /* NetworkProtectionVPNSettingsViewModel.swift */; }; + EE01EB432AFC1E0A0096AAC9 /* NetworkProtectionVPNLocationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE01EB422AFC1E0A0096AAC9 /* NetworkProtectionVPNLocationView.swift */; }; EE276BEA2A77F823009167B6 /* NetworkProtectionRootViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE276BE92A77F823009167B6 /* NetworkProtectionRootViewController.swift */; }; EE3766DE2AC5945500AAB575 /* NetworkProtectionUNNotificationPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE3766DD2AC5945500AAB575 /* NetworkProtectionUNNotificationPresenter.swift */; }; EE3B226B29DE0F110082298A /* MockInternalUserStoring.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE3B226A29DE0F110082298A /* MockInternalUserStoring.swift */; }; @@ -2361,6 +2363,8 @@ EE0153EA2A6FF970002A8B26 /* NetworkProtectionRootViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionRootViewModelTests.swift; sourceTree = ""; }; EE0153EC2A6FF9E6002A8B26 /* NetworkProtectionRootView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionRootView.swift; sourceTree = ""; }; EE0153EE2A70021E002A8B26 /* NetworkProtectionInviteView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionInviteView.swift; sourceTree = ""; }; + EE01EB3F2AFBD0000096AAC9 /* NetworkProtectionVPNSettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionVPNSettingsViewModel.swift; sourceTree = ""; }; + EE01EB422AFC1E0A0096AAC9 /* NetworkProtectionVPNLocationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionVPNLocationView.swift; sourceTree = ""; }; EE276BE92A77F823009167B6 /* NetworkProtectionRootViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionRootViewController.swift; sourceTree = ""; }; EE3766DD2AC5945500AAB575 /* NetworkProtectionUNNotificationPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionUNNotificationPresenter.swift; sourceTree = ""; }; EE3B226A29DE0F110082298A /* MockInternalUserStoring.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockInternalUserStoring.swift; sourceTree = ""; }; @@ -4441,6 +4445,14 @@ name = Root; sourceTree = ""; }; + EE01EB412AFC1DE10096AAC9 /* PreferredLocation */ = { + isa = PBXGroup; + children = ( + EE01EB422AFC1E0A0096AAC9 /* NetworkProtectionVPNLocationView.swift */, + ); + name = PreferredLocation; + sourceTree = ""; + }; EE3766DC2AC5940A00AAB575 /* NetworkProtection */ = { isa = PBXGroup; children = ( @@ -4499,6 +4511,7 @@ isa = PBXGroup; children = ( EE9D68D02AE00CF300B55EF4 /* NetworkProtectionVPNSettingsView.swift */, + EE01EB3F2AFBD0000096AAC9 /* NetworkProtectionVPNSettingsViewModel.swift */, ); name = VPNSettings; sourceTree = ""; @@ -4515,6 +4528,7 @@ EECD94B22A28B8580085C66E /* NetworkProtection */ = { isa = PBXGroup; children = ( + EE01EB412AFC1DE10096AAC9 /* PreferredLocation */, EE9D68D62AE1527F00B55EF4 /* VPNNotifications */, EE9D68CF2AE00CE000B55EF4 /* VPNSettings */, EE458D122ABB651500FC651A /* Debug */, @@ -6444,6 +6458,7 @@ F1D796F01E7B07610019D451 /* BookmarksViewControllerCells.swift in Sources */, 85058369219F424500ED4EDB /* UIColorExtension.swift in Sources */, 85058368219C49E000ED4EDB /* HomeViewSectionRenderers.swift in Sources */, + EE01EB432AFC1E0A0096AAC9 /* NetworkProtectionVPNLocationView.swift in Sources */, F456B3B525810BB900B79B90 /* FireButtonAnimationSettingsViewController.swift in Sources */, 9820EAF522613CD30089094D /* WebProgressWorker.swift in Sources */, B6CB93E5286445AB0090FEB4 /* Base64DownloadSession.swift in Sources */, @@ -6514,6 +6529,7 @@ 31EF52E1281B3BDC0034796E /* AutofillLoginListItemViewModel.swift in Sources */, 1E4FAA6627D8DFC800ADC5B3 /* CompleteDownloadRowViewModel.swift in Sources */, 83004E862193E5ED00DA013C /* TabViewControllerBrowsingMenuExtension.swift in Sources */, + EE01EB402AFBD0000096AAC9 /* NetworkProtectionVPNSettingsViewModel.swift in Sources */, EE72CA852A862D000043B5B3 /* NetworkProtectionDebugViewController.swift in Sources */, C18ED43A2AB6F77600BF3805 /* AutofillSettingsEnableFooterView.swift in Sources */, CB84C7BD29A3EF530088A5B8 /* AppConfigurationURLProvider.swift in Sources */, @@ -8087,7 +8103,7 @@ ); PRODUCT_BUNDLE_IDENTIFIER = "$(APP_ID)"; PRODUCT_NAME = "$(TARGET_NAME)"; - "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "Development - App"; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match Development com.duckduckgo.mobile.ios"; SWIFT_VERSION = 5.0; }; name = Debug; @@ -9115,7 +9131,7 @@ repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 83.0.0; + version = 84.0.0; }; }; C14882EB27F211A000D59F0C /* XCRemoteSwiftPackageReference "SwiftSoup" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index d9cc92bff7..6c8f3f340a 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -15,8 +15,8 @@ "repositoryURL": "https://github.com/DuckDuckGo/BrowserServicesKit", "state": { "branch": null, - "revision": "f7e20cd37bbc0d25ae3c3f25ef52d319366613e7", - "version": "83.0.0" + "revision": "9c2c7f39679a1f4441fec95fda86f4c089724e2e", + "version": "84.0.0" } }, { diff --git a/DuckDuckGo/EventMapping+NetworkProtectionError.swift b/DuckDuckGo/EventMapping+NetworkProtectionError.swift index a131d64443..cefbd105ec 100644 --- a/DuckDuckGo/EventMapping+NetworkProtectionError.swift +++ b/DuckDuckGo/EventMapping+NetworkProtectionError.swift @@ -32,6 +32,12 @@ extension EventMapping where Event == NetworkProtectionError { var params: [String: String] = [:] switch event { + case .failedToFetchLocationList(let error): + pixelEvent = .networkProtectionClientFailedToFetchLocations + pixelError = error + case .failedToParseLocationListResponse(let error): + pixelEvent = .networkProtectionClientFailedToParseLocationsResponse + pixelError = error case .failedToEncodeRedeemRequest: pixelEvent = .networkProtectionClientFailedToEncodeRedeemRequest case .invalidInviteCode: @@ -89,7 +95,6 @@ extension EventMapping where Event == NetworkProtectionError { // Should never be sent from from the app case .unhandledError(function: let function, line: let line, error: let error): pixelEvent = .networkProtectionUnhandledError - } DailyPixel.fireDailyAndCount(pixel: pixelEvent, error: pixelError, withAdditionalParameters: params) diff --git a/DuckDuckGo/NetworkProtectionConvenienceInitialisers.swift b/DuckDuckGo/NetworkProtectionConvenienceInitialisers.swift index 0587a405b8..8cddce00d9 100644 --- a/DuckDuckGo/NetworkProtectionConvenienceInitialisers.swift +++ b/DuckDuckGo/NetworkProtectionConvenienceInitialisers.swift @@ -54,7 +54,12 @@ extension NetworkProtectionKeychainTokenStore { extension NetworkProtectionCodeRedemptionCoordinator { convenience init() { - self.init(tokenStore: NetworkProtectionKeychainTokenStore(), errorEvents: .networkProtectionAppDebugEvents) + let tunnelSettings = TunnelSettings(defaults: .networkProtectionGroupDefaults) + self.init( + environment: tunnelSettings.selectedEnvironment, + tokenStore: NetworkProtectionKeychainTokenStore(), + errorEvents: .networkProtectionAppDebugEvents + ) } } @@ -68,4 +73,26 @@ extension NetworkProtectionVPNNotificationsViewModel { } } +extension NetworkProtectionVPNSettingsViewModel { + convenience init() { + self.init( + tunnelSettings: TunnelSettings(defaults: .networkProtectionGroupDefaults) + ) + } +} + +extension NetworkProtectionVPNLocationViewModel { + convenience init() { + let tunnelSettings = TunnelSettings(defaults: .networkProtectionGroupDefaults) + let locationListRepository = NetworkProtectionLocationListCompositeRepository( + environment: tunnelSettings.selectedEnvironment, + tokenStore: NetworkProtectionKeychainTokenStore() + ) + self.init( + locationListRepository: locationListRepository, + tunnelSettings: TunnelSettings(defaults: .networkProtectionGroupDefaults) + ) + } +} + #endif diff --git a/DuckDuckGo/NetworkProtectionVPNLocationView.swift b/DuckDuckGo/NetworkProtectionVPNLocationView.swift new file mode 100644 index 0000000000..b9d2a337fd --- /dev/null +++ b/DuckDuckGo/NetworkProtectionVPNLocationView.swift @@ -0,0 +1,100 @@ +// +// NetworkProtectionVPNLocationView.swift +// DuckDuckGo +// +// Copyright © 2023 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#if NETWORK_PROTECTION + +import Foundation +import SwiftUI + +@available(iOS 15, *) +struct NetworkProtectionVPNLocationView: View { + @StateObject var model = NetworkProtectionVPNLocationViewModel() + + var body: some View { + List { + Text("⚠️ FEATURE IS WORK IN PROGRESS ⚠️") + Section { + Button(action: model.onNearestItemSelection) { + Text(UserText.netPPreferredLocationNearest) + } + } + Section { + ForEach(model.countryItems) { item in + Button(action: { + model.onCountryItemSelection(countryID: item.countryID) + }, label: { + Text(item.localizedName) + }) + } + } + } + .animation(.default, value: model.countryItems.isEmpty) + .applyInsetGroupedListStyle() + .navigationTitle("VPN Location").onAppear { + Task { + await model.onViewAppeared() + } + } + } +} + +import NetworkProtection + +final class NetworkProtectionVPNLocationViewModel: ObservableObject { + private let locationListRepository: NetworkProtectionLocationListRepository + private let tunnelSettings: TunnelSettings + @Published public var countryItems: [NetworkProtectionVPNCountryItemModel] = [] + + init(locationListRepository: NetworkProtectionLocationListRepository, tunnelSettings: TunnelSettings) { + self.locationListRepository = locationListRepository + self.tunnelSettings = tunnelSettings + } + + @MainActor + func onViewAppeared() async { + guard let list = try? await locationListRepository.fetchLocationList() else { return } + self.countryItems = list.map(NetworkProtectionVPNCountryItemModel.init(netPLocation:)) + } + + func onNearestItemSelection() { + tunnelSettings.selectedLocation = .nearest + } + + func onCountryItemSelection(countryID: String) { + let location = NetworkProtectionSelectedLocation(country: countryID) + tunnelSettings.selectedLocation = .location(location) + } +} + +struct NetworkProtectionVPNCountryItemModel: Identifiable { + let countryID: String + let localizedName: String + let cities: [String] + var id: String { + "\(countryID) - \(cities.count) cities" + } + + init(netPLocation: NetworkProtectionLocation) { + self.countryID = netPLocation.country + self.localizedName = Locale.current.localizedString(forRegionCode: countryID) ?? countryID + self.cities = netPLocation.cities.map(\.name) + } +} + +#endif diff --git a/DuckDuckGo/NetworkProtectionVPNSettingsView.swift b/DuckDuckGo/NetworkProtectionVPNSettingsView.swift index b2dc110fda..f22075f044 100644 --- a/DuckDuckGo/NetworkProtectionVPNSettingsView.swift +++ b/DuckDuckGo/NetworkProtectionVPNSettingsView.swift @@ -20,12 +20,21 @@ #if NETWORK_PROTECTION import SwiftUI +import DesignResourcesKit @available(iOS 15, *) struct NetworkProtectionVPNSettingsView: View { + @StateObject var viewModel = NetworkProtectionVPNSettingsViewModel() var body: some View { List { + NavigationLink(destination: NetworkProtectionVPNLocationView()) { + HStack { + Text(UserText.netPPreferredLocationSettingTitle).daxBodyRegular().foregroundColor(.textPrimary) + Spacer() + Text(viewModel.preferredLocation).daxBodyRegular().foregroundColor(.textSecondary) + } + } toggleSection( text: UserText.netPAlwaysOnSettingTitle, footerText: UserText.netPAlwaysOnSettingFooter diff --git a/DuckDuckGo/NetworkProtectionVPNSettingsViewModel.swift b/DuckDuckGo/NetworkProtectionVPNSettingsViewModel.swift new file mode 100644 index 0000000000..19664143e8 --- /dev/null +++ b/DuckDuckGo/NetworkProtectionVPNSettingsViewModel.swift @@ -0,0 +1,50 @@ +// +// NetworkProtectionVPNSettingsViewModel.swift +// DuckDuckGo +// +// Copyright © 2023 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#if NETWORK_PROTECTION + +import Foundation +import NetworkProtection +import Combine + +final class NetworkProtectionVPNSettingsViewModel: ObservableObject { + private let tunnelSettings: TunnelSettings + private var cancellable: AnyCancellable? + + @Published public var preferredLocation: String = UserText.netPPreferredLocationNearest + + init(tunnelSettings: TunnelSettings) { + self.tunnelSettings = tunnelSettings + cancellable = tunnelSettings.selectedLocationPublisher.map { selectedLocation in + guard let selectedLocation = selectedLocation.location else { + return UserText.netPPreferredLocationNearest + } + guard let city = selectedLocation.city else { + return Self.localizedString(forRegionCode: selectedLocation.country) + } + return "\(city), \(Self.localizedString(forRegionCode: selectedLocation.country))" + }.assign(to: \.preferredLocation, onWeaklyHeld: self) + } + + private static func localizedString(forRegionCode: String) -> String { + Locale.current.localizedString(forRegionCode: forRegionCode) ?? forRegionCode.capitalized + } +} + +#endif diff --git a/DuckDuckGo/UserText.swift b/DuckDuckGo/UserText.swift index afb25e0e08..c333c8f991 100644 --- a/DuckDuckGo/UserText.swift +++ b/DuckDuckGo/UserText.swift @@ -652,6 +652,12 @@ In addition to the details entered into this form, your app issue report will co static let netPStatusViewShareFeedback = NSLocalizedString("network.protection.status.menu.share.feedback", value: "Share Feedback", comment: "The status view 'Share Feedback' button which is shown inline on the status view after the \(netPInviteOnlyMessage) text") static let netPStatusViewErrorConnectionFailedTitle = NSLocalizedString("network.protection.status.view.error.connection.failed.title", value: "Failed to Connect.", comment: "Generic connection failed error title shown in NetworkProtection's status view.") static let netPStatusViewErrorConnectionFailedMessage = NSLocalizedString("network.protection.status.view.error.connection.failed.message", value: "Please try again later.", comment: "Generic connection failed error message shown in NetworkProtection's status view.") + static let netPPreferredLocationSettingTitle = NSLocalizedString("network.protection.vpn.preferred.location.title", value: "Preferred Location", comment: "Title for the Preferred Location VPN Settings item.") + static let netPPreferredLocationNearest = NSLocalizedString("network.protection.vpn.preferred.location.nearest", value: "Nearest Available", comment: "Label for the Preferred Location VPN Settings item when the nearest available location is selected.") + static let netPVPNLocationRecommendedSectionTitle = NSLocalizedString("network.protection.vpn.location.recommended.section.title", value: "Recommended", comment: "Title for the VPN Location screen's Recommended section.") + static let netPVPNLocationAllCountriesSectionTitle = NSLocalizedString("network.protection.vpn.location.all.countries.section.title", value: "All Countries", comment: "Title for the VPN Location screen's All Countries section.") + static let netPVPNLocationNearestAvailableItemTitle = NSLocalizedString("network.protection.vpn.location.nearest.available.item.title", value: "Nearest Available", comment: "Title for the VPN Location screen's Nearest Available selection item.") + static let netPVPNLocationRecommendedSectionFooter = NSLocalizedString("network.protection.vpn.location.recommended.section.footer", value: "Automatically connect to the nearest server we can find", comment: "Footer describing the VPN Location screen's Recommended section which just has Nearest Available.") static let netPAlwaysOnSettingTitle = NSLocalizedString("network.protection.vpn.always.on.setting.title", value: "Always On", comment: "Title for the Always on VPN setting item.") static let netPAlwaysOnSettingFooter = NSLocalizedString("network.protection.vpn.always.on.setting.footer", value: "Automatically restore a VPN connection after interruption.", comment: "Footer text for the Always on VPN setting item.") static let netPSecureDNSSettingTitle = NSLocalizedString("network.protection.vpn.secure.dns.setting.title", value: "Secure DNS", comment: "Title for the Always on VPN setting item.") diff --git a/DuckDuckGo/en.lproj/Localizable.strings b/DuckDuckGo/en.lproj/Localizable.strings index aeac08a7bc..bb9001ad12 100644 --- a/DuckDuckGo/en.lproj/Localizable.strings +++ b/DuckDuckGo/en.lproj/Localizable.strings @@ -1516,9 +1516,27 @@ https://duckduckgo.com/mac"; /* Title for the Always on VPN setting item. */ "network.protection.vpn.always.on.setting.title" = "Always On"; +/* Title for the VPN Location screen's All Countries section. */ +"network.protection.vpn.location.all.countries.section.title" = "All Countries"; + +/* Title for the VPN Location screen's Nearest Available selection item. */ +"network.protection.vpn.location.nearest.available.item.title" = "Nearest Available"; + +/* Footer describing the VPN Location screen's Recommended section which just has Nearest Available. */ +"network.protection.vpn.location.recommended.section.footer" = "Automatically connect to the nearest server we can find"; + +/* Title for the VPN Location screen's Recommended section. */ +"network.protection.vpn.location.recommended.section.title" = "Recommended"; + /* Title for the VPN Notifications management screen. */ "network.protection.vpn.notifications.title" = "VPN Notifications"; +/* Label for the Preferred Location VPN Settings item when the nearest available location is selected. */ +"network.protection.vpn.preferred.location.nearest" = "Nearest Available"; + +/* Title for the Preferred Location VPN Settings item. */ +"network.protection.vpn.preferred.location.title" = "Preferred Location"; + /* Footer text for the Always on VPN setting item. */ "network.protection.vpn.secure.dns.setting.footer" = "Network Protection prevents DNS leaks to your Internet Service Provider by routing DNS queries though the VPN tunnel to our own resolver."; diff --git a/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift b/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift index d3ca118411..4943d79eb3 100644 --- a/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift +++ b/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift @@ -140,6 +140,10 @@ final class NetworkProtectionPacketTunnelProvider: PacketTunnelProvider { params[PixelParameters.function] = function params[PixelParameters.line] = String(line) pixelError = error + case .failedToFetchLocationList: + return + case .failedToParseLocationListResponse: + return } DailyPixel.fireDailyAndCount(pixel: pixelEvent, error: pixelError, withAdditionalParameters: params) } @@ -184,7 +188,8 @@ final class NetworkProtectionPacketTunnelProvider: PacketTunnelProvider { keychainType: .dataProtection(.unspecified), tokenStore: tokenStore, debugEvents: Self.networkProtectionDebugEvents(controllerErrorStore: errorStore), - providerEvents: Self.packetTunnelProviderEvents) + providerEvents: Self.packetTunnelProviderEvents, + tunnelSettings: TunnelSettings(defaults: .networkProtectionGroupDefaults)) startMonitoringMemoryPressureEvents() observeServerChanges() APIRequest.Headers.setUserAgent(DefaultUserAgentManager.duckDuckGoUserAgent) From 84406b0ae5b6f84677d063a25f76dd6e2bfd8cc6 Mon Sep 17 00:00:00 2001 From: Graeme Arthur Date: Tue, 14 Nov 2023 19:05:49 +0100 Subject: [PATCH 09/21] Revert to auto signing (#2158) Task/Issue URL: https://app.asana.com/0/0/1205948248159064/f Description: I accidentally committed fastlane signing. This reverts that. --- DuckDuckGo.xcodeproj/project.pbxproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 1bbef8b510..cbf40d3f8e 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -8103,7 +8103,7 @@ ); PRODUCT_BUNDLE_IDENTIFIER = "$(APP_ID)"; PRODUCT_NAME = "$(TARGET_NAME)"; - "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match Development com.duckduckgo.mobile.ios"; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "Development - App"; SWIFT_VERSION = 5.0; }; name = Debug; From ba86fced48cfc60375b1a0e7c049628733f8db4b Mon Sep 17 00:00:00 2001 From: Christopher Brind Date: Tue, 14 Nov 2023 18:19:10 +0000 Subject: [PATCH 10/21] remove return user pixel (#2146) --- Core/PixelEvent.swift | 4 ---- Core/ReturnUserMeasurement.swift | 31 ------------------------------- 2 files changed, 35 deletions(-) diff --git a/Core/PixelEvent.swift b/Core/PixelEvent.swift index 057446861c..f2186f5827 100644 --- a/Core/PixelEvent.swift +++ b/Core/PixelEvent.swift @@ -360,9 +360,6 @@ extension Pixel { case remoteMessageSecondaryActionClicked case remoteMessageSheet - // MARK: Return user measurement - case returnUser - // MARK: debug pixels case dbCrashDetected @@ -1003,7 +1000,6 @@ extension Pixel.Event { case .compilationFailed: return "m_d_compilation_failed" // MARK: - Return user measurement - case .returnUser: return "m_return_user" case .debugReturnUserAddATB: return "m_debug_return_user_add_atb" case .debugReturnUserReadATB: return "m_debug_return_user_read_atb" case .debugReturnUserUpdateATB: return "m_debug_return_user_update_atb" diff --git a/Core/ReturnUserMeasurement.swift b/Core/ReturnUserMeasurement.swift index 0dce183c42..f4a9507a08 100644 --- a/Core/ReturnUserMeasurement.swift +++ b/Core/ReturnUserMeasurement.swift @@ -45,9 +45,6 @@ class KeychainReturnUserMeasurement: ReturnUserMeasurement { } func installCompletedWithATB(_ atb: Atb) { - if let oldATB = readSecureATB() { - sendReturnUserMeasurement(oldATB, atb.version) - } writeSecureATB(atb.version) } @@ -88,34 +85,6 @@ class KeychainReturnUserMeasurement: ReturnUserMeasurement { } - private func readSecureATB() -> String? { - let query: [String: Any] = [ - kSecClass as String: kSecClassGenericPassword, - kSecAttrAccount as String: Self.SecureATBKeychainName, - kSecReturnData as String: kCFBooleanTrue!, - kSecMatchLimit as String: kSecMatchLimitOne - ] - - var dataTypeRef: AnyObject? - let status: OSStatus = SecItemCopyMatching(query as CFDictionary, &dataTypeRef) - if ![errSecSuccess, errSecItemNotFound].contains(status) { - fireDebugPixel(.debugReturnUserReadATB, errorCode: status) - } - - if let data = dataTypeRef as? Data { - return String(data: data, encoding: .utf8) - } - - return nil - } - - private func sendReturnUserMeasurement(_ oldATB: String, _ newATB: String) { - Pixel.fire(pixel: .returnUser, withAdditionalParameters: [ - PixelParameters.returnUserOldATB: oldATB, - PixelParameters.returnUserNewATB: newATB - ]) - } - private func fireDebugPixel(_ event: Pixel.Event, errorCode: OSStatus) { Pixel.fire(pixel: event, withAdditionalParameters: [ PixelParameters.returnUserErrorCode: "\(errorCode)" From d2c3e1771d80fd4e45cc77d7bc3c66afbdf43800 Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Wed, 15 Nov 2023 12:49:09 +0100 Subject: [PATCH 11/21] Fixes a crasher due to a test line merged by mistake. (#2162) Task/Issue URL: https://app.asana.com/0/0/1205948647103120/f BSK PR: https://github.com/duckduckgo/BrowserServicesKit/pull/563 macOS PR: https://github.com/duckduckgo/macos-browser/pull/1853 ## Description Fixes a crasher in NetP iOS and macOS. --- DuckDuckGo.xcodeproj/project.pbxproj | 2 +- .../project.xcworkspace/xcshareddata/swiftpm/Package.resolved | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index cbf40d3f8e..1e1427e621 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -9131,7 +9131,7 @@ repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 84.0.0; + version = 84.0.1; }; }; C14882EB27F211A000D59F0C /* XCRemoteSwiftPackageReference "SwiftSoup" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 6c8f3f340a..f1c1475dc4 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -15,8 +15,8 @@ "repositoryURL": "https://github.com/DuckDuckGo/BrowserServicesKit", "state": { "branch": null, - "revision": "9c2c7f39679a1f4441fec95fda86f4c089724e2e", - "version": "84.0.0" + "revision": "075773533bfca9196115674d2ab1b085570854dd", + "version": "84.0.1" } }, { From ddb189f5d073544efdb0e2566ba21570eff9230c Mon Sep 17 00:00:00 2001 From: Christopher Brind Date: Wed, 15 Nov 2023 16:29:45 +0000 Subject: [PATCH 12/21] Update about screen (#2152) --- DuckDuckGo/AboutViewController.swift | 96 +++++------ DuckDuckGo/Base.lproj/Settings.storyboard | 199 +++------------------- DuckDuckGo/SettingsViewController.swift | 6 +- DuckDuckGo/UserText.swift | 16 +- DuckDuckGo/bg.lproj/Localizable.strings | 7 +- DuckDuckGo/cs.lproj/Localizable.strings | 7 +- DuckDuckGo/da.lproj/Localizable.strings | 7 +- DuckDuckGo/de.lproj/Localizable.strings | 7 +- DuckDuckGo/el.lproj/Localizable.strings | 7 +- DuckDuckGo/en.lproj/Localizable.strings | 10 +- DuckDuckGo/es.lproj/Localizable.strings | 7 +- DuckDuckGo/et.lproj/Localizable.strings | 7 +- DuckDuckGo/fi.lproj/Localizable.strings | 7 +- DuckDuckGo/fr.lproj/Localizable.strings | 7 +- DuckDuckGo/hr.lproj/Localizable.strings | 7 +- DuckDuckGo/hu.lproj/Localizable.strings | 7 +- DuckDuckGo/it.lproj/Localizable.strings | 7 +- DuckDuckGo/lt.lproj/Localizable.strings | 7 +- DuckDuckGo/lv.lproj/Localizable.strings | 7 +- DuckDuckGo/nb.lproj/Localizable.strings | 7 +- DuckDuckGo/nl.lproj/Localizable.strings | 7 +- DuckDuckGo/pl.lproj/Localizable.strings | 7 +- DuckDuckGo/pt.lproj/Localizable.strings | 7 +- DuckDuckGo/ro.lproj/Localizable.strings | 7 +- DuckDuckGo/ru.lproj/Localizable.strings | 7 +- DuckDuckGo/sk.lproj/Localizable.strings | 7 +- DuckDuckGo/sl.lproj/Localizable.strings | 7 +- DuckDuckGo/sv.lproj/Localizable.strings | 7 +- DuckDuckGo/tr.lproj/Localizable.strings | 7 +- 29 files changed, 219 insertions(+), 276 deletions(-) diff --git a/DuckDuckGo/AboutViewController.swift b/DuckDuckGo/AboutViewController.swift index 0c9d4a1328..765b17d10a 100644 --- a/DuckDuckGo/AboutViewController.swift +++ b/DuckDuckGo/AboutViewController.swift @@ -19,64 +19,64 @@ import UIKit import Core +import SwiftUI +import DesignResourcesKit -class AboutViewController: UIViewController { +class AboutViewController: UIHostingController { - @IBOutlet weak var headerText: UILabel! - @IBOutlet weak var descriptionText: UILabel! - @IBOutlet weak var logoImage: UIImageView! - @IBOutlet weak var moreButton: UIButton! - - override func viewDidLoad() { - super.viewDidLoad() - - applyTheme(ThemeManager.shared.currentTheme) + convenience init() { + self.init(rootView: AboutView()) } - @IBAction func onPrivacyLinkTapped(_ sender: UIButton) { - dismiss(animated: true) { - UIApplication.shared.open(URL.aboutLink, options: [:]) +} + +struct AboutView: View { + + var body: some View { + ScrollView { + VStack(spacing: 12) { + Image("Logo") + .resizable() + .frame(width: 96, height: 96) + .padding(.top) + + Image("TextDuckDuckGo") + + Text("Welcome to the Duck Side!") + .daxHeadline() + + Rectangle() + .frame(width: 80, height: 0.5) + .foregroundColor(Color(designSystemColor: .lines)) + .padding() + + Text(LocalizedStringKey(UserText.aboutText)) + .lineLimit(nil) + .multilineTextAlignment(.leading) + .foregroundColor(.primary) + .tintIfAvailable(Color(designSystemColor: .accent)) + .padding(.horizontal, 32) + .padding(.bottom) + + Spacer() + } + .frame(maxWidth: .infinity) } + .background(Rectangle() + .ignoresSafeArea() + .foregroundColor(Color(designSystemColor: .background))) } + } -extension AboutViewController: Themable { +private extension View { - func decorate(with theme: Theme) { - view.backgroundColor = theme.backgroundColor - - switch theme.currentImageSet { - case .light: - logoImage?.image = UIImage(named: "LogoDarkText") - case .dark: - logoImage?.image = UIImage(named: "LogoLightText") + @ViewBuilder func tintIfAvailable(_ color: Color) -> some View { + if #available(iOS 16.0, *) { + tint(color) + } else { + self } - - decorateDescription(with: theme) - - headerText.textColor = theme.aboutScreenTextColor - moreButton.setTitleColor(theme.aboutScreenButtonColor, for: .normal) } - - private func decorateDescription(with theme: Theme) { - if let attributedText = descriptionText.attributedText, - var font = attributedText.attribute(NSAttributedString.Key.font, at: 0, effectiveRange: nil) as? UIFont { - - let attributes: [NSAttributedString.Key: Any] - if traitCollection.horizontalSizeClass == .regular, - traitCollection.verticalSizeClass == .regular { - font = font.withSize(24.0) - attributes = [.foregroundColor: theme.aboutScreenTextColor, - .font: font] - } else { - attributes = [.foregroundColor: theme.aboutScreenTextColor, - .font: font] - } - let decoratedText = NSMutableAttributedString(string: UserText.settingsAboutText) - decoratedText.addAttributes(attributes, range: NSRange(location: 0, length: decoratedText.length)) - - descriptionText.attributedText = decoratedText - } - } } diff --git a/DuckDuckGo/Base.lproj/Settings.storyboard b/DuckDuckGo/Base.lproj/Settings.storyboard index 679524ecd2..1d4eb7fa61 100644 --- a/DuckDuckGo/Base.lproj/Settings.storyboard +++ b/DuckDuckGo/Base.lproj/Settings.storyboard @@ -117,9 +117,6 @@ - - - @@ -145,9 +142,6 @@ - - - @@ -540,7 +534,7 @@ - +