From 13c700848b61c785f165681fcd62b4def0e7261d Mon Sep 17 00:00:00 2001 From: Mohammad Sayed <6297436+mohdsayed@users.noreply.github.com> Date: Thu, 18 Jan 2024 17:27:37 +0530 Subject: [PATCH 1/4] Update cookie database (#430) --- third_party/data/open-cookie-database.json | 1017 +++++++++++++++++++- 1 file changed, 991 insertions(+), 26 deletions(-) diff --git a/third_party/data/open-cookie-database.json b/third_party/data/open-cookie-database.json index 8840c1da6..91c1441a9 100644 --- a/third_party/data/open-cookie-database.json +++ b/third_party/data/open-cookie-database.json @@ -1544,6 +1544,17 @@ "dataController": "Adform", "gdprUrl": "https://site.adform.com/privacy-center/overview", "wildcard": "0" + }, + { + "platform": "Xandr", + "category": "Functional", + "name": "token", + "domain": ".adnxs.com", + "description": "Cookies that start with token are helper cookies used as a security measure with industry opt-out pages. They contain a unique value only to verify the origin of opt-out requests.", + "retention": "1440 Minutes", + "dataController": "Xandr", + "gdprUrl": "https://about.ads.microsoft.com/en-us/solutions/xandr/platform-privacy-policy", + "wildcard": "0" } ], "otsid": [ @@ -3611,6 +3622,17 @@ "dataController": "Admatic", "gdprUrl": "http://www.admatic.com.tr/en/privacy-policy.html", "wildcard": "0" + }, + { + "platform": "Xandr", + "category": "Marketing", + "name": "uids", + "domain": ".adnxs.com", + "description": "This cookie contains a base 64 encoded JSON object which contains external unique randomly-generated values that enable other Prebid Server demand partners to distinguish browsers and mobile devices.", + "retention": "120 days", + "dataController": "Xandr", + "gdprUrl": "https://about.ads.microsoft.com/en-us/solutions/xandr/platform-privacy-policy", + "wildcard": "0" } ], "__cfduid": [ @@ -4120,6 +4142,32 @@ "wildcard": "1" } ], + "ELOQUA": [ + { + "platform": "Oracle", + "category": "Analytics", + "name": "ELOQUA", + "domain": ".eloqua.com", + "description": "This cookies allow better understand how visitors use the website. This cookie data may be used to personalise the content or design of the website", + "retention": "13 months", + "dataController": "Oracle", + "gdprUrl": "https://www.oracle.com/privacy", + "wildcard": "0" + } + ], + "ELQSTATUS": [ + { + "platform": "Oracle", + "category": "Analytics", + "name": "ELQSTATUS", + "domain": ".eloqua.com", + "description": "This cookie is used to track individual visitors and their use of the site. It is set when you first visit the site and updated on subsequent visits.", + "retention": "13 months", + "dataController": "Oracle", + "gdprUrl": "https://www.oracle.com/privacy", + "wildcard": "0" + } + ], "laravel_session": [ { "platform": "Laravel", @@ -4367,6 +4415,149 @@ "wildcard": "0" } ], + "sdsc": [ + { + "platform": "LinkedIn", + "category": "Functional", + "name": "sdsc", + "domain": ".linkedin.com", + "description": "This cookie is used for signed data service context cookie used for database routing to ensure consistency across all databases when a change is made. Used to ensure that user-inputted content is immediately available to the submitting user upon submission", + "retention": "Session", + "dataController": "LinkedIn", + "gdprUrl": "https://www.linkedin.com/legal/cookie-policy", + "wildcard": "0" + } + ], + "li_mc": [ + { + "platform": "LinkedIn", + "category": "Functional", + "name": "li_mc", + "domain": ".linkedin.com", + "description": "This cookie is used as a temporary cache to avoid database lookups for a member's consent for use of non-essential cookies and used for having consent information on the client side to enforce consent on the client side", + "retention": "6 months", + "dataController": "LinkedIn", + "gdprUrl": "https://www.linkedin.com/legal/cookie-policy", + "wildcard": "0" + } + ], + "lms_ads": [ + { + "platform": "LinkedIn", + "category": "Marketing", + "name": "lms_ads", + "domain": ".linkedin.com", + "description": "This cookie is used to identify LinkedIn Members off LinkedIn for advertising", + "retention": "30 days", + "dataController": "LinkedIn", + "gdprUrl": "https://www.linkedin.com/legal/cookie-policy", + "wildcard": "0" + } + ], + "_guid": [ + { + "platform": "LinkedIn", + "category": "Marketing", + "name": "_guid", + "domain": "linkedin.com", + "description": "This cookie is used to identify a LinkedIn Member for advertising through Google Ads", + "retention": "90 days", + "dataController": "LinkedIn", + "gdprUrl": "https://www.linkedin.com/legal/cookie-policy", + "wildcard": "0" + } + ], + "BizographicsOptOut": [ + { + "platform": "LinkedIn", + "category": "Marketing", + "name": "BizographicsOptOut", + "domain": ".linkedin.com", + "description": "This cookie is used to determine opt-out status for non-members", + "retention": "10 years", + "dataController": "LinkedIn", + "gdprUrl": "https://www.linkedin.com/legal/cookie-policy", + "wildcard": "0" + } + ], + "IRLD": [ + { + "platform": "LinkedIn", + "category": "Marketing", + "name": "IRLD", + "domain": ".linkedin.com", + "description": "This cookie is used for Affiliate Marketing Cookie for LinkedIn", + "retention": "2 years", + "dataController": "LinkedIn", + "gdprUrl": "https://www.linkedin.com/legal/cookie-policy", + "wildcard": "0" + } + ], + "l_page": [ + { + "platform": "LinkedIn", + "category": "Analytics", + "name": "l_page", + "domain": ".linkedin.com", + "description": "This cookie is used for measuring conversion metrics on LinkedIn", + "retention": "6 months", + "dataController": "LinkedIn", + "gdprUrl": "https://www.linkedin.com/legal/cookie-policy", + "wildcard": "0" + } + ], + "ABSELB": [ + { + "platform": "LinkedIn", + "category": "Marketing", + "name": "ABSELB", + "domain": ".linkedin.com", + "description": "This is Load Balancer Cookie for affiliate marketing", + "retention": "2 years", + "dataController": "LinkedIn", + "gdprUrl": "https://www.linkedin.com/legal/cookie-policy", + "wildcard": "0" + } + ], + "brwsr": [ + { + "platform": "LinkedIn", + "category": "Marketing", + "name": "brwsr", + "domain": ".linkedin.com", + "description": "This cookie is used to Affiliate Marketing Cookie for LinkedIn", + "retention": "2 years", + "dataController": "LinkedIn", + "gdprUrl": "https://www.linkedin.com/legal/cookie-policy", + "wildcard": "0" + } + ], + "oribi_user_guid": [ + { + "platform": "Oribi", + "category": "Analytics", + "name": "oribi_user_guid", + "domain": ".oribi.io", + "description": "This cookie is used to identify a unique visitor", + "retention": "1 year", + "dataController": "Oribi", + "gdprUrl": "https://www.linkedin.com/legal/cookie-policy", + "wildcard": "0" + } + ], + "oribi_cookie_test": [ + { + "platform": "Oribi", + "category": "Analytics", + "name": "oribi_cookie_test", + "domain": "linkedin.com", + "description": "This cookie is used To determine if tracking can be enabled on a current domain", + "retention": "Session", + "dataController": "Oribi", + "gdprUrl": "https://www.linkedin.com/legal/cookie-policy", + "wildcard": "0" + } + ], "AWSALB": [ { "platform": "Amazon Web Services", @@ -5303,6 +5494,19 @@ "wildcard": "0" } ], + "UID": [ + { + "platform": "ComScore", + "category": "Marketing", + "name": "UID", + "domain": ".scorecardresearch.com", + "description": "Collects information of the user and his/her movement, such as timestamp for visits, most recently loaded pages and IP address. The data is used by the marketing research network, Scorecard Research, to analyse traffic patterns and carry out surveys to help their clients better understand the customer's preferences.", + "retention": "2 years", + "dataController": "ComScore", + "gdprUrl": "https://www.comscore.com/About/Privacy-Policy", + "wildcard": "0" + } + ], "SEUNCY": [ { "platform": "semasio.net", @@ -5433,6 +5637,71 @@ "wildcard": "0" } ], + "tt_bluekai": [ + { + "platform": "Teads", + "category": "Marketing", + "name": "tt_bluekai", + "domain": ".teads.tv", + "description": "Avoid calling to bluekai. This avoids unnecessary calls to bluekai.", + "retention": "1 day", + "dataController": "Teads.com", + "gdprUrl": "https://www.teads.com/privacy-policy/", + "wildcard": "0" + } + ], + "tt_exelate": [ + { + "platform": "Teads", + "category": "Marketing", + "name": "tt_exelate", + "domain": ".teads.tv", + "description": "Avoid calling to Exelate. This avoids unnecessary calls to Eleate.", + "retention": "1 day", + "dataController": "Teads.com", + "gdprUrl": "https://www.teads.com/privacy-policy/", + "wildcard": "0" + } + ], + "tt_liveramp": [ + { + "platform": "Teads", + "category": "Marketing", + "name": "tt_liveramp", + "domain": ".teads.tv", + "description": "Avoid calling to Liveramp. This avoids unnecessary calls to Liveramp.", + "retention": "1 day", + "dataController": "Teads.com", + "gdprUrl": "https://www.teads.com/privacy-policy/", + "wildcard": "0" + } + ], + "tt_neustar": [ + { + "platform": "Teads", + "category": "Marketing", + "name": "tt_neustar", + "domain": ".teads.tv", + "description": "Avoid calling to Nuestar. This avoids unnecessary calls to Neustar.", + "retention": "1 day", + "dataController": "Teads.com", + "gdprUrl": "https://www.teads.com/privacy-policy/", + "wildcard": "0" + } + ], + "tt_salesforce": [ + { + "platform": "Teads", + "category": "Marketing", + "name": "tt_salesforce", + "domain": ".teads.tv", + "description": "Avoid calling to Salesforce. This avoids unnecessary calls to Salesforce.", + "retention": "1 day", + "dataController": "Teads.com", + "gdprUrl": "https://www.teads.com/privacy-policy/", + "wildcard": "0" + } + ], "cfid": [ { "platform": "Adobe ColdFusion", @@ -5470,6 +5739,17 @@ "dataController": "CXense", "gdprUrl": "https://www.cxense.com/about-us/platform-privacy-policy", "wildcard": "0" + }, + { + "platform": "Piano", + "category": "Marketing", + "name": "gckp", + "domain": "", + "description": "This cookie is used for building user profile information across sites of a single customer where cx.js is implemented", + "retention": "12 months", + "dataController": "Piano", + "gdprUrl": "https://piano.io/privacy-policy/", + "wildcard": "0" } ], "um2": [ @@ -5766,8 +6046,8 @@ "domain": "smartadserver.com", "description": "Optimises ad display based on the user's movement combined and various advertiser bids for displaying user ads.", "retention": "1 day", - "dataController": "Smartadserver", - "gdprUrl": "http://smartadserver.fr/privacy-policy", + "dataController": "Equativ.com", + "gdprUrl": "https://equativ.com/privacy-policy/", "wildcard": "0" } ], @@ -5789,7 +6069,7 @@ "platform": "Smartadserver", "category": "Marketing", "name": "pid", - "domain": "", + "domain": ".smartadserver.com", "description": "Unique ID associated with an end-user (according to a domain and browser)", "retention": "13 months", "dataController": "Equativ.com", @@ -5797,6 +6077,32 @@ "wildcard": "0" } ], + "pbw": [ + { + "platform": "Smartadserver", + "category": "Analytics", + "name": "pbw", + "domain": ".smartadserver.com", + "description": "This cookie collects cached data by browser ID, operating system ID and screen size", + "retention": "13 months", + "dataController": "Equativ.com", + "gdprUrl": "https://equativ.com/end-users-privacy-policy/", + "wildcard": "0" + } + ], + "lcsrd": [ + { + "platform": "Smartadserver", + "category": "Marketing", + "name": "lcsrd", + "domain": ".smartadserver.com", + "description": "This cookie is used to present the visitor with relevant content and advertisements", + "retention": "13 months", + "dataController": "Equativ.com", + "gdprUrl": "https://equativ.com/end-users-privacy-policy/", + "wildcard": "0" + } + ], ".AspNetCore.Antiforgery.*": [ { "platform": "Microsoft", @@ -5888,6 +6194,32 @@ "wildcard": "0" } ], + "t_pt_gid": [ + { + "platform": "Taboola", + "category": "Functional", + "name": "t_pt_gid", + "domain": ".taboola.com", + "description": "Assigns a unique User ID that Taboola uses for attribution and reporting purposes, and to tailor recommendations to this specific user.", + "retention": "1 Year", + "dataController": "taboola.com", + "gdprUrl": "https://www.taboola.com/policies/cookie-policy", + "wildcard": "0" + } + ], + "taboola_session_id": [ + { + "platform": "Taboola", + "category": "Marketing", + "name": "taboola_session_id", + "domain": ".taboola.com", + "description": "Creates a temporary session ID to avoid the display of duplicate recommendations on the page.", + "retention": "Session", + "dataController": "taboola.com", + "gdprUrl": "https://www.taboola.com/policies/cookie-policy", + "wildcard": "0" + } + ], "__zlcmid": [ { "platform": "Zopim", @@ -6512,19 +6844,6 @@ "wildcard": "1" } ], - "__stid": [ - { - "platform": "ShareThis", - "category": "Analytics", - "name": "__stid", - "domain": "sharethis.com", - "description": "The __stid cookie is set as part of the ShareThis service and monitors user-activity, e.g. Web pages viewed, navigation from page to page, time spent on each page etc.", - "retention": "1 year", - "dataController": "ShareThis", - "gdprUrl": "https://sharethis.com/privacy/", - "wildcard": "0" - } - ], "__uin_mm": [ { "platform": "Sonobi", @@ -8434,6 +8753,17 @@ "dataController": "Shopify.com", "gdprUrl": "https://www.shopify.com/legal/cookies", "wildcard": "0" + }, + { + "platform": "Qualaroo", + "category": "Marketing", + "name": "ki_r", + "domain": "qualaroo.com", + "description": "This cookie is used to store the initial document.referrer when available for targeting purposes.", + "retention": "5 years", + "dataController": "Qualaroo", + "gdprUrl": "https://www.proprofs.com/policies/privacy/", + "wildcard": "0" } ], "ki_t": [ @@ -8447,6 +8777,17 @@ "dataController": "Shopify.com", "gdprUrl": "https://www.shopify.com/legal/cookies", "wildcard": "0" + }, + { + "platform": "Qualaroo", + "category": "Marketing", + "name": "ki_t", + "domain": "qualaroo.com", + "description": "This cookie is used to store Timestamps and view counts.", + "retention": "5 years", + "dataController": "Qualaroo", + "gdprUrl": "https://www.proprofs.com/policies/privacy/", + "wildcard": "0" } ], "_Brochure_session": [ @@ -8813,6 +9154,32 @@ "wildcard": "0" } ], + "sd_identity": [ + { + "platform": "Vimeo", + "category": "Analytics", + "name": "sd_identity", + "domain": "vimeo.com", + "description": "Collects analytical tracking information about videos and enables the player to function properly.", + "retention": "2 years", + "dataController": "Vimeo", + "gdprUrl": "https://vimeo.com/cookie_policy", + "wildcard": "0" + } + ], + "sd_client_id": [ + { + "platform": "Vimeo", + "category": "Analytics", + "name": "sd_client_id", + "domain": "vimeo.com", + "description": "Collects analytical tracking information about videos and enables the player to function properly.", + "retention": "2 years", + "dataController": "Vimeo", + "gdprUrl": "https://vimeo.com/cookie_policy", + "wildcard": "0" + } + ], "__stripe_mid": [ { "platform": "Stripe", @@ -9069,7 +9436,7 @@ "description": "The anj cookie contains data denoting whether a cookie ID is synced with our partners. ID syncing enables our partners to use their data from outside the Platform on the Platform. ", "retention": "90 days", "dataController": "Xandr", - "gdprUrl": "https://www.xandr.com/privacy/platform-privacy-policy/", + "gdprUrl": "https://about.ads.microsoft.com/en-us/solutions/xandr/platform-privacy-policy", "wildcard": "0" } ], @@ -9082,13 +9449,65 @@ "description": "This cookie contains a unique randomly-generated value that enables the Platform to distinguish browsers and devices.", "retention": "90 days", "dataController": "Xandr", - "gdprUrl": "https://www.xandr.com/privacy/platform-privacy-policy/", + "gdprUrl": "https://about.ads.microsoft.com/en-us/solutions/xandr/platform-privacy-policy", "wildcard": "0" } ], - "cc-*": [ + "usersync": [ { - "platform": "Intershop", + "platform": "Xandr", + "category": "Analytics", + "name": "usersync", + "domain": ".adnxs.com", + "description": "This cookie contains data denoting whether a cookie ID is synced with our partners. ID syncing enables our partners to use their data from outside the Platform on the Platform.", + "retention": "90 days", + "dataController": "Xandr", + "gdprUrl": "https://about.ads.microsoft.com/en-us/solutions/xandr/platform-privacy-policy", + "wildcard": "0" + } + ], + "icu": [ + { + "platform": "Xandr", + "category": "Marketing", + "name": "icu", + "domain": ".adnxs.com", + "description": "This cookie is used to select ads and limit the number of times a user sees a particular ad. It contains information such as the number of times an ad has been shown, how recently an ad has been shown, or how many total ads have been shown.", + "retention": "90 days", + "dataController": "Xandr", + "gdprUrl": "https://about.ads.microsoft.com/en-us/solutions/xandr/platform-privacy-policy", + "wildcard": "0" + } + ], + "pses": [ + { + "platform": "Xandr", + "category": "Analytics", + "name": "pses", + "domain": "", + "description": "This cookie is used to measure the time a user spends on a site.", + "retention": "Session", + "dataController": "Xandr", + "gdprUrl": "https://about.ads.microsoft.com/en-us/solutions/xandr/platform-privacy-policy", + "wildcard": "0" + } + ], + "sess": [ + { + "platform": "Xandr", + "category": "Functional", + "name": "sess", + "domain": ".adnxs.com", + "description": "The sess cookie contains a single non-unique value: “1”.It is used by the Platform to test whether a browser is configured to accept cookies from Xandr.", + "retention": "Session", + "dataController": "Xandr", + "gdprUrl": "https://about.ads.microsoft.com/en-us/solutions/xandr/platform-privacy-policy", + "wildcard": "0" + } + ], + "cc-*": [ + { + "platform": "Intershop", "category": "Functional", "name": "cc-*", "domain": "", @@ -13584,24 +14003,24 @@ "wildcard": "0" } ], - "_stid": [ + "__stid": [ { "platform": "ShareThis", - "category": "Marketing", - "name": "_stid", + "category": "Analytics", + "name": "__stid", "domain": "sharethis.com", - "description": "ShareThis cookie ID.", + "description": "The __stid cookie is set as part of the ShareThis service and monitors user-activity, e.g. Web pages viewed, navigation from page to page, time spent on each page etc.", "retention": "1 year", "dataController": "ShareThis", "gdprUrl": "https://sharethis.com/privacy/", "wildcard": "0" } ], - "_stidv": [ + "__stidv": [ { "platform": "ShareThis", "category": "Marketing", - "name": "_stidv", + "name": "__stidv", "domain": "sharethis.com", "description": "ShareThis cookie ID version.", "retention": "10 years", @@ -17002,5 +17421,551 @@ "gdprUrl": "", "wildcard": "0" } + ], + "pa_user": [ + { + "platform": "Piano", + "category": "Analytics", + "name": "pa_user", + "domain": "", + "description": "The pa_user cookie tracks an authenticated visitor (user) over time, even if the user does not log in again during subsequent visits. This cookie is managed by the customer who chooses its value and decides if the cookie should be set or not", + "retention": "13 months", + "dataController": "Piano", + "gdprUrl": "https://piano.io/privacy-policy/", + "wildcard": "0" + } + ], + "xbc": [ + { + "platform": "Piano", + "category": "Functional", + "name": "xbc", + "domain": "", + "description": "This cookie is used by Multiple Composer features, used for, metering, A/B testing, adblocker conversion tracking, credits, affiliates, first-visit segmentation, and AMP reader ID linking.", + "retention": "2 years", + "dataController": "Piano", + "gdprUrl": "https://piano.io/privacy-policy/", + "wildcard": "0" + } + ], + "__tbc": [ + { + "platform": "Piano", + "category": "Functional", + "name": "__tbc", + "domain": "", + "description": "This cookie is used for tracking conversion and external segmentation", + "retention": "2 years", + "dataController": "Piano", + "gdprUrl": "https://piano.io/privacy-policy/", + "wildcard": "0" + } + ], + "__pls": [ + { + "platform": "Piano", + "category": "Functional", + "name": "__pls", + "domain": "", + "description": "This cookie is used to differentiate users has been subscribed to an ESP push list", + "retention": "2 years", + "dataController": "Piano", + "gdprUrl": "https://piano.io/privacy-policy/", + "wildcard": "0" + } + ], + "__tac": [ + { + "platform": "Piano", + "category": "Functional", + "name": "__tac", + "domain": "", + "description": "This cookie is used to check access via JWT won't work and the Composer Cookies stop working", + "retention": "90 days", + "dataController": "Piano", + "gdprUrl": "https://piano.io/privacy-policy/", + "wildcard": "0" + } + ], + "_pcus": [ + { + "platform": "Piano", + "category": "Marketing", + "name": "_pcus", + "domain": "", + "description": "This cookie is used to User segmentation", + "retention": "2 years", + "dataController": "Piano", + "gdprUrl": "https://piano.io/privacy-policy/", + "wildcard": "0" + } + ], + "cX_P": [ + { + "platform": "Piano", + "category": "Analytics", + "name": "cX_P", + "domain": "", + "description": "This cookie contains the browserId that is used in Piano products for reporting and tracking purposes", + "retention": "13 months", + "dataController": "Piano", + "gdprUrl": "https://piano.io/privacy-policy/", + "wildcard": "0" + } + ], + "cX_G": [ + { + "platform": "Piano", + "category": "Marketing", + "name": "cX_G", + "domain": "", + "description": "This cookie is a Global ID mapping different IDs together into one ID. Used for building user profile information across all sites of a single customer where cx.js is implemented", + "retention": "13 months", + "dataController": "Piano", + "gdprUrl": "https://piano.io/privacy-policy/", + "wildcard": "0" + } + ], + "pnespsdk_visitor": [ + { + "platform": "Piano", + "category": "Analytics", + "name": "pnespsdk_visitor", + "domain": "", + "description": "This cookie is used for tracking user visits", + "retention": "12 months", + "dataController": "Piano", + "gdprUrl": "https://piano.io/privacy-policy/", + "wildcard": "0" + } + ], + "pnespsdk_push_subscription_added": [ + { + "platform": "Piano", + "category": "Analytics", + "name": "pnespsdk_push_subscription_added", + "domain": "", + "description": "This cookie is used only in case the Push notifications feature in ESP is activated and allows correct tracking of Push notification subscription events", + "retention": "12 months", + "dataController": "Piano", + "gdprUrl": "https://piano.io/privacy-policy/", + "wildcard": "0" + } + ], + "pnespsdk_pnespid": [ + { + "platform": "Piano", + "category": "Marketing", + "name": "pnespsdk_pnespid", + "domain": "", + "description": "This cookie is used to connect a user visit coming from an email campaign click with a visitor on the website", + "retention": "12 months", + "dataController": "Piano", + "gdprUrl": "https://piano.io/privacy-policy/", + "wildcard": "0" + } + ], + "pnespsdk_ssn": [ + { + "platform": "Piano", + "category": "Functional", + "name": "pnespsdk_ssn", + "domain": "", + "description": "This session cookie is mandatory for the ESP service to be correctly running", + "retention": "session", + "dataController": "Piano", + "gdprUrl": "https://piano.io/privacy-policy/", + "wildcard": "0" + } + ], + "__utp": [ + { + "platform": "Piano", + "category": "Functional", + "name": "__utp", + "domain": "", + "description": "This cookie is used for logged-in user's session, and contains details of a logged-in user. By default, this cookie is set on the top-level domain", + "retention": "2 years", + "dataController": "Piano", + "gdprUrl": "https://piano.io/privacy-policy/", + "wildcard": "0" + } + ], + "__pil": [ + { + "platform": "Piano", + "category": "Functional", + "name": "__pil", + "domain": "", + "description": "This cookie is used to set the preferred language for the Piano templates. Value for example: de_DE. If not available, VX's LANG cookie is used.", + "retention": "30 days", + "dataController": "Piano", + "gdprUrl": "https://piano.io/privacy-policy/", + "wildcard": "0" + } + ], + "__pid": [ + { + "platform": "Piano", + "category": "Functional", + "name": "__pid", + "domain": "", + "description": "This cookie store the domain received on the frontend is used as a domain for other cookies (incl. __utp, __idr, __tae) Example value: .piano.io", + "retention": "30 days", + "dataController": "Piano", + "gdprUrl": "https://piano.io/privacy-policy/", + "wildcard": "0" + } + ], + "__idr": [ + { + "platform": "Piano", + "category": "Functional", + "name": "__idr", + "domain": "", + "description": "The User Session Cookie is set when a user selects the option 'Stay logged in' when signing in. The expiration depends on the value configured in the Piano ID settings. Various browser restrictions and cookie rules affect the expiration as well.", + "retention": "30 days", + "dataController": "Piano", + "gdprUrl": "https://piano.io/privacy-policy/", + "wildcard": "0" + } + ], + "__eea": [ + { + "platform": "Piano", + "category": "Functional", + "name": "__eea", + "domain": "", + "description": "This cookie is used to determine if the user token (stored in __utp) needs to be refreshed with the new expiration automatically every 24 hours.", + "retention": "30 days", + "dataController": "Piano", + "gdprUrl": "https://piano.io/privacy-policy/", + "wildcard": "0" + } + ], + "__code": [ + { + "platform": "Piano", + "category": "Functional", + "name": "__code", + "domain": "", + "description": "This cookie is used for ID OAuth authorization.", + "retention": "session", + "dataController": "Piano", + "gdprUrl": "https://piano.io/privacy-policy/", + "wildcard": "0" + } + ], + "__bid": [ + { + "platform": "Piano", + "category": "Functional", + "name": "__bid", + "domain": "tinypass.com", + "description": "this cookie is used to Identifies the browser of the end user", + "retention": "1 year", + "dataController": "Piano", + "gdprUrl": "https://piano.io/privacy-policy/", + "wildcard": "0" + } + ], + "__ut": [ + { + "platform": "Piano", + "category": "Functional", + "name": "__ut", + "domain": "", + "description": "This cookie is used to Store on your website, the User Token Cookie stores encrypted data used by all Piano User Accounts", + "retention": "2 years", + "dataController": "Piano", + "gdprUrl": "https://piano.io/privacy-policy/", + "wildcard": "0" + } + ], + "__pvi": [ + { + "platform": "Piano", + "category": "Analytics", + "name": "__pvi", + "domain": "", + "description": "This cookie store data about the last visit to the site including the AID, lastTrackedVisitId, domain and time of the visit. Used for reporting only.", + "retention": "1 day", + "dataController": "Piano", + "gdprUrl": "https://piano.io/privacy-policy/", + "wildcard": "0" + } + ], + "__pat": [ + { + "platform": "Piano", + "category": "Analytics", + "name": "__pat", + "domain": "", + "description": "This cookie store difference between the client’s application time zone and UTC. At midnight, (application's local time), the previous visit is expired and a new one is created. The cookie is used for calculation.", + "retention": "30 days", + "dataController": "Piano", + "gdprUrl": "https://piano.io/privacy-policy/", + "wildcard": "0" + } + ], + "__pnahc": [ + { + "platform": "Piano", + "category": "Functional", + "name": "__pnahc", + "domain": "", + "description": "This cookie store the result of previous Adblock detection, removes false-positive AdBlock detection clauses.", + "retention": "90 days", + "dataController": "Piano", + "gdprUrl": "https://piano.io/privacy-policy/", + "wildcard": "0" + } + ], + "LANG": [ + { + "platform": "Piano", + "category": "Functional", + "name": "LANG", + "domain": "tinypass.com", + "description": "This cookie store the selected locale", + "retention": "1500 days", + "dataController": "Piano", + "gdprUrl": "https://piano.io/privacy-policy/", + "wildcard": "0" + } + ], + "LANG_CHANGED": [ + { + "platform": "Piano", + "category": "Functional", + "name": "LANG_CHANGED", + "domain": "tinypass.com", + "description": "This cookie store the temporarily selected locale (e.g. impersonation in Admin dashboard).", + "retention": "1 day", + "dataController": "Piano", + "gdprUrl": "https://piano.io/privacy-policy/", + "wildcard": "0" + } + ], + "_pctx": [ + { + "platform": "Piano", + "category": "Functional", + "name": "_pctx", + "domain": "", + "description": "This cookie is required to sync different Piano product scripts containing common data points. It contains data from different products, for example for Composer Insights or Ad Revenue Insights, but only IF you have implemented any of these products.", + "retention": "13 months", + "dataController": "Piano", + "gdprUrl": "https://piano.io/privacy-policy/", + "wildcard": "0" + } + ], + "_pprv": [ + { + "platform": "Piano", + "category": "Functional", + "name": "_pprv", + "domain": "", + "description": "This cookie contains the property consent (linked to a product) the end-user has consented to. More information about Consent management can be found here.", + "retention": "13 months", + "dataController": "Piano", + "gdprUrl": "https://piano.io/privacy-policy/", + "wildcard": "0" + } + ], + "_pcid": [ + { + "platform": "Piano", + "category": "Functional", + "name": "_pcid", + "domain": "", + "description": "This cookie contains the browserId (BID) that is used in Piano products for reporting and tracking purposes.", + "retention": "13 months", + "dataController": "Piano", + "gdprUrl": "https://piano.io/privacy-policy/", + "wildcard": "0" + } + ], + "DotMetrics.SessionCookieTemp": [ + { + "platform": "Dotmetrics", + "category": "Analytics", + "name": "DotMetrics.SessionCookieTemp", + "domain": ".dotmetrics.net", + "description": "This cookie DotMetrics obtain information about a general site visit collect in the DotMetrics Research Network.", + "retention": "Session", + "dataController": "Dot Metrics", + "gdprUrl": "https://dotmetrics.net/privacy-center.html", + "wildcard": "0" + } + ], + "DotMetrics.UniqueUserIdentityCookie": [ + { + "platform": "Dotmetrics", + "category": "Analytics", + "name": "DotMetrics.UniqueUserIdentityCookie", + "domain": ".dotmetrics.net", + "description": "This cookie contains information about the current user (unique ID, creation time, current tracking mode and version)", + "retention": "Session", + "dataController": "Dot Metrics", + "gdprUrl": "https://dotmetrics.net/privacy-center.html", + "wildcard": "0" + } + ], + "DotMetrics.DeviceKey": [ + { + "platform": "Dotmetrics", + "category": "Analytics", + "name": "DotMetrics.DeviceKey", + "domain": ".dotmetrics.net", + "description": "This cookie collects information about your device. The purpose for which we use it is to provide a high quality view of the survey or some content on your device.", + "retention": "Session", + "dataController": "Dot Metrics", + "gdprUrl": "https://dotmetrics.net/privacy-center.html", + "wildcard": "0" + } + ], + "DotMetrics.SessionCookieTempTimed": [ + { + "platform": "Dotmetrics", + "category": "Analytics", + "name": "DotMetrics.SessionCookieTempTimed", + "domain": ".dotmetrics.net", + "description": "This cookie contains information about the current site from which you access the DotMetrics research network.", + "retention": "Session", + "dataController": "Dot Metrics", + "gdprUrl": "https://dotmetrics.net/privacy-center.html", + "wildcard": "0" + } + ], + "ki_s": [ + { + "platform": "Qualaroo", + "category": "Marketing", + "name": "ki_s", + "domain": "qualaroo.com", + "description": "This cookie is used to store the current state of any survey the user has viewed or interacted with.", + "retention": "5 years", + "dataController": "Qualaroo", + "gdprUrl": "https://www.proprofs.com/policies/privacy/", + "wildcard": "0" + } + ], + "ki_u": [ + { + "platform": "Qualaroo", + "category": "Marketing", + "name": "ki_u", + "domain": "qualaroo.com", + "description": "This cookie is used to store a unique user identifier.", + "retention": "5 years", + "dataController": "Qualaroo", + "gdprUrl": "https://www.proprofs.com/policies/privacy/", + "wildcard": "0" + } + ], + "barometric[cuid]": [ + { + "platform": "Claritas", + "category": "Marketing", + "name": "barometric[cuid]", + "domain": "trkn.us", + "description": "This cookie is used to identify users for Veritone/Barometric Podcast Conversion.", + "retention": "1 year", + "dataController": "Claritas", + "gdprUrl": "https://claritas.com/privacy-legal/", + "wildcard": "0" + } + ], + "barometric[idfa]": [ + { + "platform": "Claritas", + "category": "Marketing", + "name": "barometric[idfa]", + "domain": "trkn.us", + "description": "This cookie is used to to collect visitor statistics. This data is used to categorize users and improve the effectiveness of website advertising.", + "retention": "10 seconds", + "dataController": "Claritas", + "gdprUrl": "https://claritas.com/privacy-legal/", + "wildcard": "0" + } + ], + "__gfp_64b": [ + { + "platform": "Gemius", + "category": "Analytics", + "name": "__gfp_64b", + "domain": ".gemius.pl", + "description": "Stores data on the time spent on the website and its sub-pages, during the current session.", + "retention": "13 months", + "dataController": "Gemius", + "gdprUrl": "https://www.gemius.pl/privacy-notice-for-panelists.html", + "wildcard": "0" + } + ], + "__gfp_s_64b": [ + { + "platform": "Gemius", + "category": "Analytics", + "name": "__gfp_s_64b", + "domain": ".gemius.pl", + "description": "Registers data on the performance of the website’s embedded video-content.", + "retention": "13 months", + "dataController": "Gemius", + "gdprUrl": "https://www.gemius.pl/privacy-notice-for-panelists.html", + "wildcard": "0" + } + ], + "Gdyn": [ + { + "platform": "Gemius", + "category": "Analytics", + "name": "Gdyn", + "domain": ".gemius.pl", + "description": "\tCollects statistics on the visitor's visits to the website, such as the number of visits, average time spent on the website and what pages have been read.", + "retention": "13 months", + "dataController": "Gemius", + "gdprUrl": "https://www.gemius.pl/privacy-notice-for-panelists.html", + "wildcard": "0" + } + ], + "opt_out": [ + { + "platform": "Nativo", + "category": "Marketing", + "name": "opt_out", + "domain": "postrelease.com", + "description": "This cookie is used to remember not to serve that user targeted Ads if they opt out.", + "retention": "1 year", + "dataController": "Nativo", + "gdprUrl": "https://www.nativo.com/privacy-policy", + "wildcard": "0" + } + ], + "visitor": [ + { + "platform": "Nativo", + "category": "Marketing", + "name": "visitor", + "domain": "postrelease.com", + "description": "This cookie is used to identify a unique visitor to the site.", + "retention": "1 year", + "dataController": "Nativo", + "gdprUrl": "https://www.nativo.com/privacy-policy", + "wildcard": "0" + } + ], + "tp": [ + { + "platform": "Bombora", + "category": "Marketing", + "name": "tp", + "domain": ".ml314.com", + "description": "This cookie is used to target the audience", + "retention": "14 days", + "dataController": "Bombora", + "gdprUrl": "https://bombora.com/privacy-philosophy/", + "wildcard": "0" + } ] } From 8b8858934a07b5249cc2926d300700db319f04c3 Mon Sep 17 00:00:00 2001 From: Mohammad Sayed <6297436+mohdsayed@users.noreply.github.com> Date: Thu, 18 Jan 2024 17:28:03 +0530 Subject: [PATCH 2/4] Update related website sets (#431) --- data/related_website_sets.json | 115 ++++++++++++++++++++++++++++++++- 1 file changed, 113 insertions(+), 2 deletions(-) diff --git a/data/related_website_sets.json b/data/related_website_sets.json index 4848f744d..768b371b0 100644 --- a/data/related_website_sets.json +++ b/data/related_website_sets.json @@ -16,12 +16,18 @@ "contact": "wojciech.bialy@grupawp.pl", "primary": "https://wp.pl", "associatedSites": [ + "https://o2.pl", + "https://pudelek.pl", "https://money.pl", - "https://autokult.pl" + "https://abczdrowie.pl", + "https://wpext.pl" ], "rationaleBySite": { + "https://o2.pl": "News portal and email service", + "https://pudelek.pl": "Special Interest Website - celebrity news and gossip", "https://money.pl": "Special Interest Website - news and information on the stock market, currencies, business and economics", - "https://autokult.pl": "Special Interest Website - automotive" + "https://abczdrowie.pl": "Special Interest Website - healthy living and medical health", + "https://wpext.pl": "Content delivery domain for sensitive content websites" } }, { @@ -351,6 +357,111 @@ "https://journaldunet.fr" ] } + }, + { + "contact": "jquintana@lanacion.com.ar", + "primary": "https://lanacion.com.ar", + "associatedSites": [ + "https://bonvivir.com" + ], + "rationaleBySite": { + "https://bonvivir.com": "Wine club for sell wine by La Nación" + } + }, + { + "contact": "domain@nien.com", + "primary": "https://nien.com", + "associatedSites": [ + "https://chennien.com", + "https://nien.org", + "https://nien.co" + ], + "rationaleBySite": { + "https://chennien.com": "Alias domain for Nien Studio", + "https://nien.org": "Campaign pages for Nien Studio", + "https://nien.co": "URL shortener for Nien Studio" + } + }, + { + "contact": "admin@hearty.me", + "primary": "https://hearty.me", + "associatedSites": [ + "https://hearty.app", + "https://hearty.gift", + "https://hj.rs", + "https://heartymail.com", + "https://alice.tw", + "https://jiayi.life", + "https://miss.com.tw" + ], + "rationaleBySite": { + "https://hearty.app": "API endpoints for Hearty Journal", + "https://hearty.gift": "Campaign pages for Hearty Journal", + "https://hj.rs": "URL shortener for Hearty Journal", + "https://heartymail.com": "Email service for Hearty Journal", + "https://alice.tw": "Alice's blog", + "https://jiayi.life": "Jiayi's blog", + "https://miss.com.tw": "Company website" + } + }, + { + "primary": "https://talkdeskqaid.com", + "contact": "support@talkdesk.com", + "associatedSites": [ + "https://trytalkdesk.com" + ], + "rationaleBySite": { + "https://trytalkdesk.com": "Specialized platform for CCaS" + } + }, + { + "primary": "https://talkdeskstgid.com", + "contact": "support@talkdesk.com", + "associatedSites": [ + "https://gettalkdesk.com" + ], + "rationaleBySite": { + "https://gettalkdesk.com": "Specialized platform for CCaS" + } + }, + { + "contact": "tst@ringieraxelspringer.pl", + "primary": "https://onet.pl", + "associatedSites": [ + "https://fakt.pl", + "https://businessinsider.com.pl", + "https://medonet.pl", + "https://plejada.pl" + ], + "serviceSites": [ + "https://ocdn.eu" + ], + "rationaleBySite": { + "https://fakt.pl": "Special interest news website that is part of the Ringier Axel Springer Polska media group. It provides up-to-date news and covers a wide range of topics including politics, sports, entertainment, and lifestyle. Fakt.pl aims to deliver reliable and engaging content to its readers, keeping them informed about the latest events and trends.", + "https://businessinsider.com.pl": "Special interest news website within the Ringier Axel Springer Polska media group. It focuses on business, finance, technology, and related topics. The website provides in-depth analysis, market insights, and news articles tailored for professionals, entrepreneurs, and those interested in the business world.", + "https://medonet.pl": "Special interest news website within the Ringier Axel Springer Polska media group dedicated to health and medical topics. It offers a wide range of articles, news, and features related to various aspects of health, wellness, medical research, treatments, and lifestyle. Medonet.pl aims to provide reliable and accurate information to its readers, promoting healthy living and informed decision-making.", + "https://ocdn.eu": "Service dedicated to processing events that occur on websites, assists in managing and optimizing multimedia content, including images, videos, or other resources, for websites that rely on event-related functionality.", + "https://plejada.pl": "Special interest news website within the Ringier Axel Springer Polska media group dedicated to exclusive information, interviews, videos, and galleries from the world of show business." + } + }, + { + "contact": "dacostaylara@elpais.com.uy", + "primary": "https://elpais.com.uy", + "associatedSites": [ + "https://clubelpais.com.uy", + "https://paula.com.uy", + "https://gallito.com.uy" + ], + "rationaleBySite": { + "https://clubelpais.com.uy": "Domain from El Pais Uruguay", + "https://paula.com.uy": "Domain from El Pais Uruguay", + "https://gallito.com.uy": "Domain from El Pais Uruguay" + }, + "ccTLDs": { + "https://elpais.com.uy": [ + "https://elpais.uy" + ] + } } ] } \ No newline at end of file From ded136b1960f37d994e1bfbd6e5fe7c27607adf8 Mon Sep 17 00:00:00 2001 From: Kudaligi Amoghavarsha Date: Thu, 18 Jan 2024 19:24:15 +0530 Subject: [PATCH 3/4] Feature: Provide switch to turn PSAT's debugging capabilities on and off (#429) * Fix error on extension page. * Fix error on extension page. * Modify cookie landing page to accomodate description for when CDP is off. * Modify onLabel to make optional. * Add Icons for settings tab. * Add methods and listeners to service worker to attach and detach CDP. * Fix TSlint error and the method to get partitionKey. * Add settings store to DevTools * Use allowedNumberOfTabs instead of bringing it from cookie provider. * Add settings component. * Delete settings page from extension. * Add functions to settings storage. * Add settings panel to tabs. * Add test for app.tsx. * Add toggle switch to popup. * Fix failing tests. * Remove tab data as fallback. * Prettify. * Small code improvements * Fix tab.reload tabId issue * Move icons to third party folder. * Update documentation. --------- Co-authored-by: sayedtaqui --- .../utils/parseRequestWillBeSentExtraInfo.ts | 5 +- .../utils/parseResponseReceivedExtraInfo.ts | 4 +- .../cookieLandingHeaderContainer.tsx | 7 + .../src/components/cookiesLanding/index.tsx | 23 +- .../src/components/toggleSwitch/index.tsx | 6 +- packages/design-system/src/icons/copy.svg | 2 +- packages/extension/src/constants.ts | 15 ++ .../extension/src/localStore/cookieStore.ts | 1 + packages/extension/src/manifest.json | 1 - packages/extension/src/serviceWorker/index.ts | 183 +++++++++++++- .../parseResponseCookieHeader.ts | 13 +- .../devtools/components/cookies/index.tsx | 40 ++- .../src/view/devtools/components/index.ts | 1 + .../components/settings/components/index.ts} | 17 +- .../components/informationContainer.tsx | 185 ++++++++++++++ .../settings/components/settingOption.tsx | 50 ++++ .../settings/components/settingsContainer.tsx | 100 ++++++++ .../devtools/components/settings/index.tsx | 67 +++++ .../view/devtools/hooks/useFrameOverlay.ts | 7 +- .../extension/src/view/devtools/index.tsx | 13 +- .../stateProviders/syncCookieStore/index.tsx | 57 ++--- .../syncSettingsStore/index.tsx | 79 +++--- packages/extension/src/view/devtools/tabs.tsx | 15 ++ .../extension/src/view/devtools/tests/app.tsx | 236 ++++++++++++++++++ packages/extension/src/view/popup/app.tsx | 31 +++ .../stateProviders/syncCookieStore/index.tsx | 29 ++- packages/extension/src/view/settings/app.css | 3 - packages/extension/src/view/settings/app.tsx | 47 ---- .../tabContent/information/index.tsx | 101 -------- .../settings/allowedNumberOfTabs/index.tsx | 71 ------ .../components/tabContent/settings/index.tsx | 35 --- .../settings/components/tabHeader/index.tsx | 52 ---- .../components/tabHeader/tests/index.tsx | 56 ----- .../extension/src/view/settings/index.html | 12 - .../extension/src/view/settings/index.tsx | 37 --- packages/extension/src/view/settings/tabs.ts | 35 --- .../src/view/settings/tests/index.tsx | 80 ------ packages/extension/webpack.config.cjs | 24 +- tests/jest.config.cjs | 1 - third_party/icons/done.svg | 3 + third_party/icons/gear.svg | 7 + third_party/icons/information-icon.svg | 3 + third_party/icons/settings-tab-white.svg | 9 + third_party/icons/settings-tab.svg | 9 + 44 files changed, 1078 insertions(+), 694 deletions(-) rename packages/extension/src/view/{settings/components/tabContent/information/informationDisplay.tsx => devtools/components/settings/components/index.ts} (65%) create mode 100644 packages/extension/src/view/devtools/components/settings/components/informationContainer.tsx create mode 100644 packages/extension/src/view/devtools/components/settings/components/settingOption.tsx create mode 100644 packages/extension/src/view/devtools/components/settings/components/settingsContainer.tsx create mode 100644 packages/extension/src/view/devtools/components/settings/index.tsx rename packages/extension/src/view/{settings => devtools}/stateProviders/syncSettingsStore/index.tsx (76%) create mode 100644 packages/extension/src/view/devtools/tests/app.tsx delete mode 100644 packages/extension/src/view/settings/app.css delete mode 100644 packages/extension/src/view/settings/app.tsx delete mode 100644 packages/extension/src/view/settings/components/tabContent/information/index.tsx delete mode 100644 packages/extension/src/view/settings/components/tabContent/settings/allowedNumberOfTabs/index.tsx delete mode 100644 packages/extension/src/view/settings/components/tabContent/settings/index.tsx delete mode 100644 packages/extension/src/view/settings/components/tabHeader/index.tsx delete mode 100644 packages/extension/src/view/settings/components/tabHeader/tests/index.tsx delete mode 100644 packages/extension/src/view/settings/index.html delete mode 100644 packages/extension/src/view/settings/index.tsx delete mode 100644 packages/extension/src/view/settings/tabs.ts delete mode 100644 packages/extension/src/view/settings/tests/index.tsx create mode 100644 third_party/icons/done.svg create mode 100644 third_party/icons/gear.svg create mode 100644 third_party/icons/information-icon.svg create mode 100644 third_party/icons/settings-tab-white.svg create mode 100644 third_party/icons/settings-tab.svg diff --git a/packages/common/src/utils/parseRequestWillBeSentExtraInfo.ts b/packages/common/src/utils/parseRequestWillBeSentExtraInfo.ts index f294f1cf7..042b83174 100644 --- a/packages/common/src/utils/parseRequestWillBeSentExtraInfo.ts +++ b/packages/common/src/utils/parseRequestWillBeSentExtraInfo.ts @@ -55,9 +55,8 @@ export default function parseRequestWillBeSentExtraInfo( if (cookie?.domain) { domain = cookie?.domain; - } else if (!cookie?.domain && requestMap[request?.requestId]) { - domain = new URL(requestMap[request?.requestId]).hostname; - url = requestMap[request?.requestId] ?? ''; + } else if (!cookie?.domain && url) { + domain = new URL(url).hostname; } const singleCookie = { diff --git a/packages/common/src/utils/parseResponseReceivedExtraInfo.ts b/packages/common/src/utils/parseResponseReceivedExtraInfo.ts index 915ec8631..c9612558f 100644 --- a/packages/common/src/utils/parseResponseReceivedExtraInfo.ts +++ b/packages/common/src/utils/parseResponseReceivedExtraInfo.ts @@ -74,8 +74,8 @@ export default function parseResponseReceivedExtraInfo( if (parsedCookie?.domain) { domain = parsedCookie?.domain; - } else if (!parsedCookie?.domain && requestMap[response?.requestId]) { - domain = new URL(requestMap[response?.requestId]).hostname; + } else if (!parsedCookie?.domain && url) { + domain = new URL(url).hostname; } const singleCookie = { diff --git a/packages/design-system/src/components/cookiesLanding/cookieLandingHeaderContainer.tsx b/packages/design-system/src/components/cookiesLanding/cookieLandingHeaderContainer.tsx index b1a59945c..b307966d1 100644 --- a/packages/design-system/src/components/cookiesLanding/cookieLandingHeaderContainer.tsx +++ b/packages/design-system/src/components/cookiesLanding/cookieLandingHeaderContainer.tsx @@ -24,18 +24,25 @@ interface CookiesLandingContainerProps { showLandingHeader?: boolean; testId?: string | null; children?: React.ReactNode; + description?: React.ReactNode; } const CookiesLandingContainer = ({ dataMapping = [], showLandingHeader = true, testId = 'cookie-landing-insights', + description = '', children, }: CookiesLandingContainerProps) => { return (
{showLandingHeader && } + {description && ( +
+

{description}

+
+ )}
{children}
diff --git a/packages/design-system/src/components/cookiesLanding/index.tsx b/packages/design-system/src/components/cookiesLanding/index.tsx index e1e92efa0..4343e797e 100644 --- a/packages/design-system/src/components/cookiesLanding/index.tsx +++ b/packages/design-system/src/components/cookiesLanding/index.tsx @@ -40,6 +40,7 @@ interface CookiesLandingProps { associatedCookiesCount?: number | null; showMessageBoxBody?: boolean; showBlockedCookiesSection?: boolean; + description?: React.ReactNode; } const CookiesLanding = ({ @@ -51,6 +52,7 @@ const CookiesLanding = ({ showMessageBoxBody = true, showBlockedCookiesSection = false, showHorizontalMatrix = false, + description = '', }: CookiesLandingProps) => { const cookieStats = prepareCookiesCount(tabCookies); const cookiesStatsComponents = prepareCookieStatsComponents(cookieStats); @@ -112,19 +114,22 @@ const CookiesLanding = ({ {showBlockedCookiesSection && ( {cookiesStatsComponents.blockedCookiesLegend.length > 0 && ( - + <> + + )} )} diff --git a/packages/design-system/src/components/toggleSwitch/index.tsx b/packages/design-system/src/components/toggleSwitch/index.tsx index c3e2ca045..96e36a1fb 100644 --- a/packages/design-system/src/components/toggleSwitch/index.tsx +++ b/packages/design-system/src/components/toggleSwitch/index.tsx @@ -21,14 +21,14 @@ import React from 'react'; type ToggleSwitchProps = { enabled: boolean; setEnabled: (newValue: boolean) => void; - onLabel: string; + onLabel?: string; additionalStyles?: string; }; const ToggleSwitch = ({ enabled, setEnabled, - onLabel, + onLabel = '', additionalStyles = 'relative', }: ToggleSwitchProps) => { return ( @@ -56,7 +56,7 @@ const ToggleSwitch = ({ > - {onLabel} {enabled ? 'On' : 'Off'} + {onLabel} diff --git a/packages/design-system/src/icons/copy.svg b/packages/design-system/src/icons/copy.svg index fe62d2bf4..437f1b8f9 100644 --- a/packages/design-system/src/icons/copy.svg +++ b/packages/design-system/src/icons/copy.svg @@ -1,4 +1,4 @@ - + \ No newline at end of file diff --git a/packages/extension/src/constants.ts b/packages/extension/src/constants.ts index e268cf727..3d13215ad 100644 --- a/packages/extension/src/constants.ts +++ b/packages/extension/src/constants.ts @@ -44,3 +44,18 @@ export const BLOCKED_REASON_LIST = [ 'ExcludeThirdPartyCookieBlockedInFirstPartySet', 'ExcludeThirdPartyPhaseout', ]; + +export const SETTING_PAGE_CONTROLS = [ + { + id: 'enableCDP', + heading: 'Enable CDP', + description: + 'The Chrome DevTools Protocol allows for tools to instrument, inspect, debug and profile Chromium, Chrome and other Blink-based browsers.', + }, + { + id: 'multitabDebugging', + heading: 'Multitab Debugging', + description: + "By default, the PSAT tool analyzes one tab at a time. You can enable multi-tab debugging by toggling the appropriate option. However, be aware that this may slow down the extension's performance", + }, +]; diff --git a/packages/extension/src/localStore/cookieStore.ts b/packages/extension/src/localStore/cookieStore.ts index bfdc1b58f..de4f01137 100644 --- a/packages/extension/src/localStore/cookieStore.ts +++ b/packages/extension/src/localStore/cookieStore.ts @@ -83,6 +83,7 @@ const CookieStore = { }, isBlocked: blockedReasons.length > 0, blockedReasons, + url: _updatedCookies[cookieKey].url ?? cookie.url, warningReasons: Array.from( new Set([ ...(cookie.warningReasons ?? []), diff --git a/packages/extension/src/manifest.json b/packages/extension/src/manifest.json index 9fc235898..9b0ca4285 100644 --- a/packages/extension/src/manifest.json +++ b/packages/extension/src/manifest.json @@ -31,7 +31,6 @@ "matches": ["*://*/*"] } ], - "options_page": "settings/index.html", "action": { "default_popup": "popup/index.html", "default_icon": "icons/icon-48.png" diff --git a/packages/extension/src/serviceWorker/index.ts b/packages/extension/src/serviceWorker/index.ts index e11431cc8..7e7661b3c 100644 --- a/packages/extension/src/serviceWorker/index.ts +++ b/packages/extension/src/serviceWorker/index.ts @@ -45,11 +45,22 @@ let cookieDB: CookieDatabase | null = null; // Global promise queue. const PROMISE_QUEUE = new PQueue({ concurrency: 1 }); + +let globalIsUsingCDP = false; + const cdpURLToRequestMap: { [tabId: string]: { [requestId: string]: string; }; } = {}; + +const ALLOWED_EVENTS = [ + 'Network.responseReceived', + 'Network.requestWillBeSentExtraInfo', + 'Network.responseReceivedExtraInfo', + 'Audits.issueAdded', +]; + /** * Fires when the browser receives a response from a web server. * @see https://developer.chrome.com/docs/extensions/reference/webRequest/ @@ -126,6 +137,10 @@ chrome.webRequest.onResponseStarted.addListener( ['extraHeaders', 'responseHeaders'] ); +/** + * Fires before sending an HTTP request, once the request headers are available. + * @see https://developer.chrome.com/docs/extensions/reference/api/webRequest#event-onBeforeSendHeaders + */ chrome.webRequest.onBeforeSendHeaders.addListener( ({ url, requestHeaders, tabId, frameId }) => { (async () => { @@ -195,12 +210,18 @@ chrome.webRequest.onBeforeSendHeaders.addListener( ['extraHeaders', 'requestHeaders'] ); +/** + * Fires when a tab is created. + * @see https://developer.chrome.com/docs/extensions/reference/api/tabs#event-onCreated + */ chrome.tabs.onCreated.addListener(async (tab) => { await PROMISE_QUEUE.add(async () => { if (!tab.id) { return; } + const _isSingleTabProcessingMode = await isSingleTabProcessingMode(); + if (_isSingleTabProcessingMode) { const previousTabData = await chrome.storage.local.get(); const doesTabExist = await getTab(previousTabData?.tabToRead); @@ -246,9 +267,13 @@ chrome.tabs.onRemoved.addListener(async (tabId) => { */ chrome.tabs.onUpdated.addListener(async (tabId, changeInfo, tab) => { try { - await chrome.debugger.attach({ tabId }, '1.3'); - chrome.debugger.sendCommand({ tabId }, 'Network.enable'); - chrome.debugger.sendCommand({ tabId }, 'Audits.enable'); + if (globalIsUsingCDP) { + await chrome.debugger.attach({ tabId }, '1.3'); + await chrome.debugger.sendCommand({ tabId }, 'Network.enable'); + await chrome.debugger.sendCommand({ tabId }, 'Audits.enable'); + } else { + await chrome.debugger.detach({ tabId }); + } } catch (error) { //Fail silently } @@ -286,22 +311,39 @@ chrome.runtime.onInstalled.addListener(async (details) => { await chrome.storage.sync.clear(); await chrome.storage.sync.set({ allowedNumberOfTabs: 'single', + isUsingCDP: false, }); } if (details.reason === 'update') { const preSetSettings = await chrome.storage.sync.get(); + if (preSetSettings?.isUsingCDP) { + globalIsUsingCDP = preSetSettings?.isUsingCDP; + } if (preSetSettings?.allowedNumberOfTabs) { return; } + + if (preSetSettings?.isUsingCDP) { + globalIsUsingCDP = preSetSettings?.isUsingCDP; + } + await chrome.storage.sync.clear(); await chrome.storage.sync.set({ allowedNumberOfTabs: 'single', + isUsingCDP: globalIsUsingCDP, }); } }); }); +/** + * Fires whenever debugging target issues instrumentation event. + * @see https://developer.chrome.com/docs/extensions/reference/api/debugger + */ chrome.debugger.onEvent.addListener((source, method, params) => { + if (!ALLOWED_EVENTS.includes(method)) { + return; + } (async () => { // eslint-disable-next-line complexity await PROMISE_QUEUE.add(async () => { @@ -348,7 +390,7 @@ chrome.debugger.onEvent.addListener((source, method, params) => { requestParams, cookieDB, cdpURLToRequestMap[tabId], - url + url ?? '' ); await CookieStore.update(tabId, cookies); @@ -367,7 +409,7 @@ chrome.debugger.onEvent.addListener((source, method, params) => { const allCookies = parseResponseReceivedExtraInfo( responseParams, cdpURLToRequestMap[tabId], - url, + url ?? '', cookieDB ); @@ -428,6 +470,10 @@ const listenToNewTab = async (tabId?: number) => { return newTabId; }; +/** + * Fires when a message is sent from either an extension process (by runtime.sendMessage) or a content script (by tabs.sendMessage). + * @see https://developer.chrome.com/docs/extensions/reference/api/runtime#event-onMessage + */ chrome.runtime.onMessage.addListener((request) => { if (request?.type === 'SET_TAB_TO_READ') { PROMISE_QUEUE.clear(); @@ -445,8 +491,24 @@ chrome.runtime.onMessage.addListener((request) => { await chrome.tabs.reload(Number(newTab)); }); } + if (request.type === 'CHANGE_CDP_SETTING') { + if (typeof request.payload?.isUsingCDP !== 'undefined') { + globalIsUsingCDP = request.payload?.isUsingCDP; + (async () => { + const storage = await chrome.storage.sync.get(); + await chrome.storage.sync.set({ + ...storage, + isUsingCDP: globalIsUsingCDP, + }); + })(); + } + } }); +/** + * Listen to local storage changes. + * @see https://developer.chrome.com/docs/extensions/reference/api/storage#event-onChanged + */ chrome.storage.local.onChanged.addListener( async (changes: { [key: string]: chrome.storage.StorageChange }) => { if (!changes?.tabToRead || !changes?.tabToRead?.oldValue) { @@ -467,3 +529,114 @@ chrome.storage.local.onChanged.addListener( }); } ); + +chrome.storage.sync.onChanged.addListener( + async (changes: { [key: string]: chrome.storage.StorageChange }) => { + if ( + !changes?.allowedNumberOfTabs || + typeof changes?.allowedNumberOfTabs?.newValue === 'undefined' + ) { + return; + } + + if (changes?.allowedNumberOfTabs?.newValue === 'single') { + PROMISE_QUEUE.clear(); + + await PROMISE_QUEUE.add(async () => { + const tabs = await chrome.tabs.query({}); + + await Promise.all( + tabs.map(async (tab) => { + if (!tab?.id) { + return; + } + + await chrome.action.setBadgeText({ + tabId: tab?.id, + text: '', + }); + }) + ); + + await chrome.storage.local.clear(); + }); + } else { + PROMISE_QUEUE.clear(); + + await PROMISE_QUEUE.add(async () => { + const tabs = await chrome.tabs.query({}); + + await Promise.all( + tabs.map(async (tab) => { + if (!tab?.id) { + return; + } + await CookieStore.addTabData(tab.id?.toString()); + await chrome.tabs.reload(Number(tab?.id)); + }) + ); + }); + } + } +); + +chrome.storage.sync.onChanged.addListener( + async (changes: { [key: string]: chrome.storage.StorageChange }) => { + if ( + !changes?.isUsingCDP || + typeof changes?.isUsingCDP?.newValue === 'undefined' + ) { + return; + } + + globalIsUsingCDP = changes?.isUsingCDP?.newValue; + + chrome.runtime.sendMessage({ + type: 'CHANGE_CDP_SETTING', + payload: { + isUsingCDP: changes?.isUsingCDP?.newValue, + }, + }); + + if (!changes?.isUsingCDP?.newValue) { + PROMISE_QUEUE.clear(); + + await PROMISE_QUEUE.add(async () => { + const storedTabData = Object.keys(await chrome.storage.local.get()); + + await Promise.all( + storedTabData.map(async (key) => { + if (!Number(key)) { + return; + } + + try { + await chrome.debugger.detach({ tabId: Number(key) }); + await CookieStore.removeTabData(key); + } catch (error) { + // Fail silently + } finally { + await chrome.tabs.reload(Number(key), { bypassCache: true }); + } + }) + ); + }); + } else { + PROMISE_QUEUE.clear(); + + await PROMISE_QUEUE.add(async () => { + const storedTabData = Object.keys(await chrome.storage.local.get()); + + await Promise.all( + storedTabData.map(async (key) => { + if (!Number(key)) { + return; + } + + await chrome.tabs.reload(Number(key), { bypassCache: true }); + }) + ); + }); + } + } +); diff --git a/packages/extension/src/serviceWorker/parseResponseCookieHeader.ts b/packages/extension/src/serviceWorker/parseResponseCookieHeader.ts index 9a0e32e2d..2a0103f18 100644 --- a/packages/extension/src/serviceWorker/parseResponseCookieHeader.ts +++ b/packages/extension/src/serviceWorker/parseResponseCookieHeader.ts @@ -21,8 +21,9 @@ import { isFirstParty, findAnalyticsMatch, type CookieData, - type NetworkCookie, } from '@ps-analysis-tool/common'; +import { getDomain } from 'tldts'; +import type { Protocol } from 'devtools-protocol'; /** * Internal dependencies. @@ -41,7 +42,7 @@ import { createCookieObject } from './createCookieObject'; * @param {CookieDatabase} dictionary Dictionary from open cookie database * @param {string} tabUrl top url of the tab from which the request originated. * @param {number} frameId Id of a frame in which this cookie is used. - * @param {NetworkCookie[]} cookiesList List cookies from the request. + * @param {Protocol.Network.Cookie[]} cookiesList List cookies from the request. * @returns {Promise} Parsed cookie object. */ const parseResponseCookieHeader = async ( @@ -50,7 +51,7 @@ const parseResponseCookieHeader = async ( dictionary: CookieDatabase, tabUrl: string, frameId: number, - cookiesList: NetworkCookie[] + cookiesList: Protocol.Network.Cookie[] ): Promise => { let parsedCookie: CookieData['parsedCookie'] = cookie.parse(value); @@ -62,10 +63,8 @@ const parseResponseCookieHeader = async ( } const _isFirstParty = isFirstParty(parsedCookie.domain || '', tabUrl); - const partitionKey = - new URL(tabUrl).protocol + - '//' + - new URL(tabUrl).hostname.replace('www.', ''); + const partitionKey = new URL(tabUrl).protocol + '//' + getDomain(tabUrl); + if (value.toLowerCase().includes('partitioned')) { parsedCookie = { ...parsedCookie, diff --git a/packages/extension/src/view/devtools/components/cookies/index.tsx b/packages/extension/src/view/devtools/components/cookies/index.tsx index 63e1a0862..ac1b05b9a 100644 --- a/packages/extension/src/view/devtools/components/cookies/index.tsx +++ b/packages/extension/src/view/devtools/components/cookies/index.tsx @@ -13,23 +13,23 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - /** * External dependencies. */ import React from 'react'; +import { + Button, + CookiesLanding, + ProgressBar, +} from '@ps-analysis-tool/design-system'; +import { type CookieTableData } from '@ps-analysis-tool/common'; /** * Internal dependencies. */ +import { useSettingsStore } from '../../stateProviders/syncSettingsStore'; import { useCookieStore } from '../../stateProviders/syncCookieStore'; import CookiesListing from './cookiesListing'; -import { - Button, - CookiesLanding, - ProgressBar, -} from '@ps-analysis-tool/design-system'; -import type { CookieTableData } from '@ps-analysis-tool/common'; interface CookiesProps { setFilteredCookies: React.Dispatch; @@ -37,7 +37,6 @@ interface CookiesProps { const Cookies = ({ setFilteredCookies }: CookiesProps) => { const { - allowedNumberOfTabs, contextInvalidated, isCurrentTabBeingListenedTo, loading, @@ -47,7 +46,6 @@ const Cookies = ({ setFilteredCookies }: CookiesProps) => { tabFrames, changeListeningToThisTab, } = useCookieStore(({ state, actions }) => ({ - allowedNumberOfTabs: state.allowedNumberOfTabs, contextInvalidated: state.contextInvalidated, isCurrentTabBeingListenedTo: state.isCurrentTabBeingListenedTo, loading: state.loading, @@ -58,6 +56,11 @@ const Cookies = ({ setFilteredCookies }: CookiesProps) => { changeListeningToThisTab: actions.changeListeningToThisTab, })); + const { allowedNumberOfTabs, isUsingCDP } = useSettingsStore(({ state }) => ({ + allowedNumberOfTabs: state.allowedNumberOfTabs, + isUsingCDP: state.isUsingCDP, + })); + if ( loading || (loading && @@ -78,6 +81,24 @@ const Cookies = ({ setFilteredCookies }: CookiesProps) => { allowedNumberOfTabs === 'single') || (allowedNumberOfTabs && allowedNumberOfTabs === 'unlimited') ) { + const description = !isUsingCDP ? ( + <> + To gather data and insights regarding blocked cookies, please enable + PSAT to use the Chrome DevTools protocol. You can do this in the + Settings page or in the extension popup. For more information check the + PSAT  + + Wiki + + + ) : ( + '' + ); return (
{ tabCookies={tabCookies} tabFrames={tabFrames} showBlockedCookiesSection + description={description} /> )}
diff --git a/packages/extension/src/view/devtools/components/index.ts b/packages/extension/src/view/devtools/components/index.ts index 4fec6b98a..d7a38d4d2 100644 --- a/packages/extension/src/view/devtools/components/index.ts +++ b/packages/extension/src/view/devtools/components/index.ts @@ -18,3 +18,4 @@ export * from './privateAdvertising'; export * from './siteBoundaries'; export { default as Cookies } from './cookies'; export { default as PrivacySandbox } from './privacySandbox'; +export { default as Settings } from './settings'; diff --git a/packages/extension/src/view/settings/components/tabContent/information/informationDisplay.tsx b/packages/extension/src/view/devtools/components/settings/components/index.ts similarity index 65% rename from packages/extension/src/view/settings/components/tabContent/information/informationDisplay.tsx rename to packages/extension/src/view/devtools/components/settings/components/index.ts index cfb11cb91..254627460 100644 --- a/packages/extension/src/view/settings/components/tabContent/information/informationDisplay.tsx +++ b/packages/extension/src/view/devtools/components/settings/components/index.ts @@ -13,18 +13,5 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -/** - * External dependencies. - */ -import React from 'react'; - -interface InformationDisplayProps { - information: string; -} - -const InformationDisplay = ({ information }: InformationDisplayProps) => { - return
{information}
; -}; - -export default InformationDisplay; +export { default as InformationContainer } from './informationContainer'; +export { default as SettingsContainer } from './settingsContainer'; diff --git a/packages/extension/src/view/devtools/components/settings/components/informationContainer.tsx b/packages/extension/src/view/devtools/components/settings/components/informationContainer.tsx new file mode 100644 index 000000000..1e65573bc --- /dev/null +++ b/packages/extension/src/view/devtools/components/settings/components/informationContainer.tsx @@ -0,0 +1,185 @@ +/* + * Copyright 2023 Google LLC + * + * 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 + * + * https://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. + */ +/** + * External dependencies. + */ +import React, { useCallback, useEffect, useRef, useState } from 'react'; +import { ArrowUp, Copy } from '@ps-analysis-tool/design-system'; +import classNames from 'classnames'; +/** + * Internal dependencies + */ +import { useSettingsStore } from '../../../stateProviders/syncSettingsStore'; +// @ts-ignore +// eslint-disable-next-line import/no-relative-packages +import InformationIcon from '../../../../../../../../third_party/icons/information-icon.svg'; +// @ts-ignore +// eslint-disable-next-line import/no-relative-packages +import Done from '../../../../../../../../third_party/icons/done.svg'; + +const InformationContainer = () => { + const { currentTabs, currentExtensions, browserInformation, OSInformation } = + useSettingsStore(({ state }) => ({ + currentTabs: state.currentTabs, + currentExtensions: state.currentExtensions, + browserInformation: state.browserInformation, + OSInformation: state.OSInformation, + })); + + const [copying, setCopying] = useState(false); + const timeOutRef = useRef | null>(null); + const [open, setOpen] = useState(true); + + useEffect(() => { + if (copying) { + timeOutRef.current = setTimeout(() => { + setCopying(false); + }, 200); + } else { + if (timeOutRef.current) { + clearTimeout(timeOutRef.current); + } + } + }, [copying]); + + const handleCopy = useCallback(() => { + setCopying(true); + + let clipboardText = `Open Tabs: ${currentTabs}
`; + + clipboardText += `Active Extensions:
`; + currentExtensions?.forEach((extension) => { + clipboardText += `${extension.extensionName}: ${extension.extensionId}
`; + }); + clipboardText += `Chrome Version: ${browserInformation}
`; + clipboardText += `OS - System Architecture: ${OSInformation}`; + + try { + // Need to do this since chrome doesnt allow the clipboard access in extension. + const copyFrom = document.createElement('div'); + copyFrom.style.textAlign = 'left'; + copyFrom.contentEditable = 'true'; + + copyFrom.innerHTML = clipboardText; + + document.body.appendChild(copyFrom); + + const range = new Range(); + range.selectNodeContents(copyFrom); + document.getSelection()?.removeAllRanges(); + document.getSelection()?.addRange(range); + + document.addEventListener('copy', (e) => { + e.clipboardData?.setData('text/html', clipboardText); + e.preventDefault(); + }); + document.execCommand('copy'); + document.removeEventListener('copy', (e) => { + e.clipboardData?.setData('text/html', clipboardText); + e.preventDefault(); + }); + + copyFrom.blur(); + document.body.removeChild(copyFrom); + } catch (error) { + //Fail silently + } + }, [OSInformation, browserInformation, currentExtensions, currentTabs]); + + return ( +
+
+ +
+ +
+
+ Open Tabs + + {currentTabs} + +
+
+ + Chrome version + + + {browserInformation} + +
+
+ + OS - System Architecture + + + {OSInformation} + +
+
+
+
+ + Active Extensions + +
    + {currentExtensions?.map((extension, index) => { + return ( +
  • + {extension.extensionName}: {extension.extensionId} +
  • + ); + })} +
+
+
+
+
+
+ ); +}; + +export default InformationContainer; diff --git a/packages/extension/src/view/devtools/components/settings/components/settingOption.tsx b/packages/extension/src/view/devtools/components/settings/components/settingOption.tsx new file mode 100644 index 000000000..b29488f7c --- /dev/null +++ b/packages/extension/src/view/devtools/components/settings/components/settingOption.tsx @@ -0,0 +1,50 @@ +/* + * Copyright 2023 Google LLC + * + * 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 + * + * https://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. + */ +/** + * External dependencies. + */ +import { ToggleSwitch } from '@ps-analysis-tool/design-system'; +import React from 'react'; + +interface SettingOptionProps { + title: string; + description: string; + switchState: boolean; + changeSwitchState: (newState: boolean) => void; +} + +const SettingOption = ({ + title, + description, + switchState, + changeSwitchState, +}: SettingOptionProps) => { + return ( +
+
+
{title}
+
+ {description} +
+
+
+ +
+
+ ); +}; + +export default SettingOption; diff --git a/packages/extension/src/view/devtools/components/settings/components/settingsContainer.tsx b/packages/extension/src/view/devtools/components/settings/components/settingsContainer.tsx new file mode 100644 index 000000000..4a7bbbbc7 --- /dev/null +++ b/packages/extension/src/view/devtools/components/settings/components/settingsContainer.tsx @@ -0,0 +1,100 @@ +/* + * Copyright 2023 Google LLC + * + * 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 + * + * https://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. + */ +/** + * External dependencies. + */ +import React, { useMemo } from 'react'; + +/** + * Internal dependencies + */ +import SettingOption from './settingOption'; +import { useSettingsStore } from '../../../stateProviders/syncSettingsStore'; +// @ts-ignore +// eslint-disable-next-line import/no-relative-packages +import Gear from '../../../../../../../../third_party/icons/gear.svg'; +import { SETTING_PAGE_CONTROLS } from '../../../../../constants'; + +interface settingsToReturnObject { + id: string; + heading: string; + switchState: boolean; + description: string; + changeSwitchState: (newState: boolean) => void; +} +const SettingsContainer = () => { + const { allowedNumberOfTabs, isUsingCDP, setIsUsingCDP, setProcessingMode } = + useSettingsStore(({ state, actions }) => ({ + allowedNumberOfTabs: state.allowedNumberOfTabs, + isUsingCDP: state.isUsingCDP, + setProcessingMode: actions.setProcessingMode, + setIsUsingCDP: actions.setIsUsingCDP, + })); + + const memoisedSettings = useMemo(() => { + const settingsToReturn: settingsToReturnObject[] = []; + + SETTING_PAGE_CONTROLS.map((setting) => { + switch (setting.id) { + case 'enableCDP': + settingsToReturn.push({ + ...setting, + changeSwitchState: setIsUsingCDP, + switchState: isUsingCDP, + }); + break; + case 'multitabDebugging': + settingsToReturn.push({ + ...setting, + changeSwitchState: setProcessingMode, + switchState: allowedNumberOfTabs === 'unlimited', + }); + break; + default: + break; + } + return setting; + }); + + return settingsToReturn; + }, [allowedNumberOfTabs, isUsingCDP, setIsUsingCDP, setProcessingMode]); + + return ( +
+
+ + + PSAT Extension Settings + +
+
+ {memoisedSettings?.map((setting) => { + return ( + + ); + })} +
+
+ ); +}; + +export default SettingsContainer; diff --git a/packages/extension/src/view/devtools/components/settings/index.tsx b/packages/extension/src/view/devtools/components/settings/index.tsx new file mode 100644 index 000000000..477fa1d0e --- /dev/null +++ b/packages/extension/src/view/devtools/components/settings/index.tsx @@ -0,0 +1,67 @@ +/* + * Copyright 2023 Google LLC + * + * 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 + * + * https://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. + */ +/** + * External dependencies. + */ +import React, { useState } from 'react'; +import classNames from 'classnames'; +import { ArrowUp } from '@ps-analysis-tool/design-system'; + +/** + * Internal dependencies + */ +import { InformationContainer, SettingsContainer } from './components'; + +const Settings = () => { + const [open, setOpen] = useState(true); + + return ( +
+
+
+ +
+
+
+ + +
+
+
+
+ ); +}; + +export default Settings; diff --git a/packages/extension/src/view/devtools/hooks/useFrameOverlay.ts b/packages/extension/src/view/devtools/hooks/useFrameOverlay.ts index 5850e05bd..ff45fe61d 100644 --- a/packages/extension/src/view/devtools/hooks/useFrameOverlay.ts +++ b/packages/extension/src/view/devtools/hooks/useFrameOverlay.ts @@ -24,6 +24,7 @@ import type { CookieTableData } from '@ps-analysis-tool/common'; */ import { WEBPAGE_PORT_NAME } from '../../../constants'; import { useCookieStore } from '../stateProviders/syncCookieStore'; +import { useSettingsStore } from '../stateProviders/syncSettingsStore'; import { getCurrentTabId } from '../../../utils/getCurrentTabId'; interface Response { @@ -42,7 +43,6 @@ const useFrameOverlay = ( setContextInvalidated, selectedFrame, isCurrentTabBeingListenedTo, - allowedNumberOfTabs, tabFrames, setCanStartInspecting, canStartInspecting, @@ -52,12 +52,15 @@ const useFrameOverlay = ( setIsInspecting: actions.setIsInspecting, selectedFrame: state.selectedFrame, isCurrentTabBeingListenedTo: state.isCurrentTabBeingListenedTo, - allowedNumberOfTabs: state.allowedNumberOfTabs, tabFrames: state.tabFrames, setCanStartInspecting: actions.setCanStartInspecting, canStartInspecting: state.canStartInspecting, })); + const { allowedNumberOfTabs } = useSettingsStore(({ state }) => ({ + allowedNumberOfTabs: state.allowedNumberOfTabs, + })); + const [isFrameSelectedFromDevTool, setIsFrameSelectedFromDevTool] = useState(false); diff --git a/packages/extension/src/view/devtools/index.tsx b/packages/extension/src/view/devtools/index.tsx index 94e2f1940..cc01a994c 100644 --- a/packages/extension/src/view/devtools/index.tsx +++ b/packages/extension/src/view/devtools/index.tsx @@ -29,6 +29,7 @@ import { */ import App from './app'; import { Provider as ExternalStoreProvider } from './stateProviders/syncCookieStore'; +import { Provider as SettingsStoreProvider } from './stateProviders/syncSettingsStore'; const isDarkMode = chrome.devtools.panels.themeName === 'dark'; document.body.classList.add(isDarkMode ? 'dark' : 'light'); @@ -38,11 +39,13 @@ const root = document.getElementById('root'); if (root) { createRoot(root).render( - - - - - + + + + + + + ); } diff --git a/packages/extension/src/view/devtools/stateProviders/syncCookieStore/index.tsx b/packages/extension/src/view/devtools/stateProviders/syncCookieStore/index.tsx index e41adc1d1..a466059de 100644 --- a/packages/extension/src/view/devtools/stateProviders/syncCookieStore/index.tsx +++ b/packages/extension/src/view/devtools/stateProviders/syncCookieStore/index.tsx @@ -39,6 +39,7 @@ import useContextSelector from '../../../../utils/useContextSelector'; import { ALLOWED_NUMBER_OF_TABS } from '../../../../constants'; import setDocumentCookies from '../../../../utils/setDocumentCookies'; import isOnRWS from '../../../../contentScript/utils/isOnRWS'; +import { useSettingsStore } from '../syncSettingsStore'; export interface CookieStoreContext { state: { @@ -49,7 +50,6 @@ export interface CookieStoreContext { selectedFrame: string | null; returningToSingleTab: boolean; isCurrentTabBeingListenedTo: boolean; - allowedNumberOfTabs: string | null; isInspecting: boolean; contextInvalidated: boolean; canStartInspecting: boolean; @@ -73,7 +73,6 @@ const initialState: CookieStoreContext = { loading: true, isCurrentTabBeingListenedTo: false, returningToSingleTab: false, - allowedNumberOfTabs: null, isInspecting: false, contextInvalidated: false, canStartInspecting: false, @@ -91,6 +90,7 @@ const initialState: CookieStoreContext = { export const Context = createContext(initialState); export const Provider = ({ children }: PropsWithChildren) => { + // TODO: Refactor: create smaller providers and reduce state from here. const [tabId, setTabId] = useState(null); const [loading, setLoading] = useState(true); const loadingTimeout = useRef | null>(null); @@ -101,9 +101,9 @@ export const Provider = ({ children }: PropsWithChildren) => { const [returningToSingleTab, setReturningToSingleTab] = useState(false); - const [allowedNumberOfTabs, setAllowedNumberOfTabs] = useState( - null - ); + const { allowedNumberOfTabs } = useSettingsStore(({ state }) => ({ + allowedNumberOfTabs: state.allowedNumberOfTabs, + })); const [canStartInspecting, setCanStartInspecting] = useState(false); @@ -121,6 +121,11 @@ export const Provider = ({ children }: PropsWithChildren) => { const [tabFrames, setTabFrames] = useState(null); + /** + * Set tab frames state for frame ids and frame URLs from using chrome.webNavigation.getAllFrames + * + * TODO: Refactor: move it to a utility function. + */ const getAllFramesForCurrentTab = useCallback( async (_tabId: number | null) => { if (!_tabId) { @@ -162,6 +167,12 @@ export const Provider = ({ children }: PropsWithChildren) => { [] ); + /** + * Sets current frames for sidebar, detected if the current tab is to be analysed, + * parses data currently in store, set current tab URL. + * + * TODO: Refactor: Break in smaller parts. + */ const intitialSync = useCallback(async () => { const _tabId = chrome.devtools.inspectedWindow.tabId; @@ -169,22 +180,8 @@ export const Provider = ({ children }: PropsWithChildren) => { setTabId(_tabId); - const extensionStorage = await chrome.storage.sync.get(); - const _allowedNumberOfTabs = - extensionStorage?.allowedNumberOfTabs || 'single'; - - if (!extensionStorage?.allowedNumberOfTabs) { - await chrome.storage.sync.clear(); - await chrome.storage.sync.set({ - ...extensionStorage, - allowedNumberOfTabs: 'single', - }); - } - - setAllowedNumberOfTabs(_allowedNumberOfTabs); - if (_tabId) { - if (extensionStorage?.allowedNumberOfTabs === 'single') { + if (allowedNumberOfTabs === 'single') { const getTabBeingListenedTo = await chrome.storage.local.get(); const availableTabs = await chrome.tabs.query({}); if ( @@ -244,7 +241,7 @@ export const Provider = ({ children }: PropsWithChildren) => { ); setLoading(false); - }, [getAllFramesForCurrentTab]); + }, [allowedNumberOfTabs, getAllFramesForCurrentTab]); const storeChangeListener = useCallback( async (changes: { [key: string]: chrome.storage.StorageChange }) => { @@ -404,35 +401,20 @@ export const Provider = ({ children }: PropsWithChildren) => { } }, []); - const changeSyncStorageListener = useCallback(async () => { - const extensionStorage = await chrome.storage.sync.get(); - - if (extensionStorage?.allowedNumberOfTabs) { - setAllowedNumberOfTabs(extensionStorage?.allowedNumberOfTabs); - } - }, []); - useEffect(() => { intitialSync(); }, [intitialSync]); useEffect(() => { chrome.storage.local.onChanged.addListener(storeChangeListener); - chrome.storage.sync.onChanged.addListener(changeSyncStorageListener); chrome.tabs.onUpdated.addListener(tabUpdateListener); chrome.tabs.onRemoved.addListener(tabRemovedListener); return () => { chrome.storage.local.onChanged.removeListener(storeChangeListener); chrome.tabs.onUpdated.removeListener(tabUpdateListener); chrome.tabs.onRemoved.removeListener(tabRemovedListener); - chrome.storage.sync.onChanged.removeListener(changeSyncStorageListener); }; - }, [ - storeChangeListener, - tabUpdateListener, - tabRemovedListener, - changeSyncStorageListener, - ]); + }, [storeChangeListener, tabUpdateListener, tabRemovedListener]); useEffect(() => { loadingTimeout.current = setTimeout(() => { @@ -457,7 +439,6 @@ export const Provider = ({ children }: PropsWithChildren) => { selectedFrame, isCurrentTabBeingListenedTo, returningToSingleTab, - allowedNumberOfTabs, contextInvalidated, isInspecting, canStartInspecting, diff --git a/packages/extension/src/view/settings/stateProviders/syncSettingsStore/index.tsx b/packages/extension/src/view/devtools/stateProviders/syncSettingsStore/index.tsx similarity index 76% rename from packages/extension/src/view/settings/stateProviders/syncSettingsStore/index.tsx rename to packages/extension/src/view/devtools/stateProviders/syncSettingsStore/index.tsx index e72dc18c8..ecf86e3f9 100644 --- a/packages/extension/src/view/settings/stateProviders/syncSettingsStore/index.tsx +++ b/packages/extension/src/view/devtools/stateProviders/syncSettingsStore/index.tsx @@ -24,7 +24,6 @@ import React, { useCallback, } from 'react'; import { noop } from '@ps-analysis-tool/design-system'; -import { CookieStore } from '../../../../localStore'; enum PLATFORM_OS_MAP { mac = 'MacOS', @@ -48,9 +47,11 @@ export interface SettingStoreContext { | null; browserInformation: string | null; OSInformation: string | null; + isUsingCDP: boolean; }; actions: { - setSettingsInStorage: (key: string, value: string) => void; + setProcessingMode: (newState: boolean) => void; + setIsUsingCDP: (newValue: boolean) => void; }; } @@ -61,9 +62,11 @@ const initialState: SettingStoreContext = { currentExtensions: null, browserInformation: null, OSInformation: null, + isUsingCDP: false, }, actions: { - setSettingsInStorage: noop, + setIsUsingCDP: noop, + setProcessingMode: noop, }, }; @@ -73,6 +76,9 @@ export const Provider = ({ children }: PropsWithChildren) => { const [allowedNumberOfTabs, setAllowedNumberOfTabs] = useState( null ); + + const [isUsingCDP, setIsUsingCDP] = useState(false); + const [currentTabs, setCurrentTabs] = useState(0); const [browserInformation, setBrowserInformation] = useState( @@ -87,6 +93,7 @@ export const Provider = ({ children }: PropsWithChildren) => { const currentSettings = await chrome.storage.sync.get(); setAllowedNumberOfTabs(currentSettings?.allowedNumberOfTabs); + setIsUsingCDP(currentSettings?.isUsingCDP); chrome.tabs.query({}, (tabs) => { setCurrentTabs(tabs.length); @@ -122,48 +129,38 @@ export const Provider = ({ children }: PropsWithChildren) => { } }, [OSInformation]); - const setSettingsInStorage = useCallback( - async (key: string, value: string) => { - const currentSettings = await chrome.storage.sync.get(); + const setProcessingMode = useCallback(async (newState: boolean) => { + const valueToBeSet: boolean | string = newState ? 'unlimited' : 'single'; - chrome.storage.sync.set({ - ...currentSettings, - [key]: value, - }); - }, - [] - ); + const currentSettings = await chrome.storage.sync.get(); + + chrome.storage.sync.set({ + ...currentSettings, + allowedNumberOfTabs: valueToBeSet, + }); + }, []); + + const _setUsingCDP = useCallback((newValue: boolean) => { + chrome.runtime.sendMessage({ + type: 'CHANGE_CDP_SETTING', + payload: { + isUsingCDP: newValue, + }, + }); + setIsUsingCDP(newValue); + }, []); const storeChangeListener = useCallback( - async (changes: { [key: string]: chrome.storage.StorageChange }) => { + (changes: { [key: string]: chrome.storage.StorageChange }) => { if ( - Object.keys(changes).includes('allowedNumberOfTabs') && - changes['allowedNumberOfTabs']?.newValue + changes?.allowedNumberOfTabs && + changes?.allowedNumberOfTabs?.newValue ) { - setAllowedNumberOfTabs(changes['allowedNumberOfTabs']?.newValue); - - if (changes['allowedNumberOfTabs']?.newValue === 'single') { - chrome.tabs.query({}, (tabs) => { - tabs.forEach((tab) => { - chrome.action.setBadgeText({ - tabId: tab?.id, - text: '', - }); - }); - }); + setAllowedNumberOfTabs(changes?.allowedNumberOfTabs?.newValue); + } - await chrome.storage.local.clear(); - } else { - chrome.tabs.query({}, (tabs) => { - tabs.forEach(async (tab) => { - if (!tab.id) { - return; - } - await CookieStore.addTabData(tab.id?.toString()); - await chrome.tabs.reload(tab?.id); - }); - }); - } + if (changes?.isUsingCDP) { + setIsUsingCDP(changes?.isUsingCDP?.newValue); } }, [] @@ -187,9 +184,11 @@ export const Provider = ({ children }: PropsWithChildren) => { currentExtensions, browserInformation, OSInformation, + isUsingCDP, }, actions: { - setSettingsInStorage, + setProcessingMode, + setIsUsingCDP: _setUsingCDP, }, }} > diff --git a/packages/extension/src/view/devtools/tabs.tsx b/packages/extension/src/view/devtools/tabs.tsx index d5fd444e1..13b0c75e9 100644 --- a/packages/extension/src/view/devtools/tabs.tsx +++ b/packages/extension/src/view/devtools/tabs.tsx @@ -46,6 +46,12 @@ import { /** * Internal dependencies. */ +// @ts-ignore +// eslint-disable-next-line import/no-relative-packages +import SettingsTab from '../../../../../third_party/icons/settings-tab.svg'; +// @ts-ignore +// eslint-disable-next-line import/no-relative-packages +import SettingsTabWhite from '../../../../../third_party/icons/settings-tab-white.svg'; import { SiteBoundaries, Chips, @@ -57,6 +63,7 @@ import { BounceTracking, Fingerprinting, PrivacySandbox, + Settings, } from './components'; const TABS: SidebarItems = { @@ -142,6 +149,14 @@ const TABS: SidebarItems = { }, }, }, + settings: { + title: 'Settings', + panel: , + icon: , + selectedIcon: , + dropdownOpen: false, + children: {}, + }, }; export default TABS; diff --git a/packages/extension/src/view/devtools/tests/app.tsx b/packages/extension/src/view/devtools/tests/app.tsx new file mode 100644 index 000000000..d54104228 --- /dev/null +++ b/packages/extension/src/view/devtools/tests/app.tsx @@ -0,0 +1,236 @@ +/* + * Copyright 2023 Google LLC + * + * 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 + * + * https://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. + */ +/** + * External dependencies. + */ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import '@testing-library/jest-dom'; +import { act } from 'react-dom/test-utils'; +import SinonChrome from 'sinon-chrome'; +import { noop } from '@ps-analysis-tool/common'; +import { useTablePersistentSettingsStore } from '@ps-analysis-tool/design-system'; + +/** + * Internal dependencies. + */ +import App from '../app'; +import { useCookieStore } from '../stateProviders/syncCookieStore'; +// @ts-ignore +// eslint-disable-next-line import/no-unresolved +import PSInfo from 'ps-analysis-tool/data/PSInfo.json'; +import data from '../../../utils/test-data/cookieMockData'; +import { useSettingsStore } from '../stateProviders/syncSettingsStore'; + +jest.mock('../stateProviders/syncCookieStore', () => ({ + useCookieStore: jest.fn(), +})); + +jest.mock('../stateProviders/syncSettingsStore', () => ({ + useSettingsStore: jest.fn(), +})); + +jest.mock( + '../../../../../design-system/src/components/table/persistentSettingsStore', + () => ({ + useTablePersistentSettingsStore: jest.fn(), + }) +); + +const mockUseCookieStore = useCookieStore as jest.Mock; +const mockUseTablePersistentSettingStore = + useTablePersistentSettingsStore as jest.Mock; +const mockUseSettingsStore = useSettingsStore as jest.Mock; + +describe('App', () => { + beforeAll(() => { + globalThis.chrome = SinonChrome as unknown as typeof chrome; + globalThis.location.protocol = 'chrome-extension:'; + globalThis.chrome = { + ...SinonChrome, + storage: { + //@ts-ignore + local: { + //@ts-ignore + // eslint-disable-next-line @typescript-eslint/no-unused-vars + get: (_, __) => + // eslint-disable-next-line @typescript-eslint/no-explicit-any + new Promise<{ [key: string]: any }>((resolve) => { + resolve({ + 40245632: { + cookies: data.tabCookies, + selectedSidebarItem: 'privacySandbox#cookies', + tablePersistentSettingsStore: { + cookieListing: { + sortBy: 'parsedCookie.name', + sortOrder: 'asc', + }, + }, + }, + tabToRead: '40245632', + }); + }), + set: () => Promise.resolve(), + //@ts-ignore + onChanged: { + addListener: () => undefined, + removeListener: () => undefined, + }, + }, + sync: { + //@ts-ignore + // eslint-disable-next-line @typescript-eslint/no-unused-vars + get: (_, __) => + // eslint-disable-next-line @typescript-eslint/no-explicit-any + new Promise<{ [key: string]: any }>((resolve) => { + resolve({ + allowedNumberOfTabs: 'single', + }); + }), + set: () => Promise.resolve(), + //@ts-ignore + onChanged: { + addListener: () => undefined, + removeListener: () => undefined, + }, + }, + session: { + //@ts-ignore + // eslint-disable-next-line @typescript-eslint/no-unused-vars + get: (_, __) => Promise.resolve(), + set: () => Promise.resolve(), + //@ts-ignore + onChanged: { + addListener: () => undefined, + removeListener: () => undefined, + }, + }, + }, + devtools: { + //@ts-ignore + inspectedWindow: { + tabId: 40245632, + //@ts-ignore + // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any + eval: (_, callback: any) => { + callback('https://edition.cnn.com'); + }, + }, + }, + tabs: { + //@ts-ignore + onUpdated: { + addListener: () => undefined, + removeListener: () => undefined, + }, + //@ts-ignore + // eslint-disable-next-line @typescript-eslint/no-unused-vars + query: (_, __) => { + return [{ id: 40245632, url: 'https://edition.cnn.com' }]; + }, + connect: () => ({ + //@ts-ignore + onMessage: { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + addListener: () => undefined, + }, + //@ts-ignore + onDisconnect: { + addListener: () => undefined, + }, + }), + }, + }; + global.ResizeObserver = class MockedResizeObserver { + observe = jest.fn(); + unobserve = jest.fn(); + disconnect = jest.fn(); + }; + globalThis.fetch = function () { + return Promise.resolve({ + json: () => + Promise.resolve({ + ...PSInfo, + }), + }); + } as unknown as typeof fetch; + }); + + it('Should show cookie table if frame is selected', async () => { + mockUseCookieStore.mockReturnValue({ + contextInvalidated: false, + setContextInvalidated: noop, + tabCookies: data.tabCookies, + tabFrames: data.tabFrames, + selectedFrame: data.selectedFrame, + cookies: data.tabCookies, + setSelectedFrame: noop, + isInspecting: false, + setIsInspecting: noop, + canStartInspecting: true, + tabUrl: data.tabUrl, + isCurrentTabBeingListenedTo: true, + }); + mockUseTablePersistentSettingStore.mockReturnValue({ + getPreferences: () => '', + setPreferences: noop, + }); + mockUseSettingsStore.mockReturnValue({ + allowedNumberOfTabs: 'single', + }); + + act(() => { + render(); + }); + + expect((await screen.findAllByTestId('body-row')).length).toBe(4); + }); + + it('Should show refresh banner if context invalidated.', async () => { + mockUseCookieStore.mockReturnValue({ + contextInvalidated: true, + setContextInvalidated: noop, + tabCookies: {}, + tabFrames: null, + selectedFrame: null, + cookies: {}, + setSelectedFrame: noop, + isInspecting: false, + setIsInspecting: noop, + canStartInspecting: true, + tabUrl: data.tabUrl, + isCurrentTabBeingListenedTo: true, + }); + mockUseTablePersistentSettingStore.mockReturnValue({ + getPreferences: () => '', + setPreferences: noop, + }); + mockUseSettingsStore.mockReturnValue({ + allowedNumberOfTabs: 'single', + }); + + act(() => { + render(); + }); + + expect(await screen.findByText('Refresh panel')).toBeInTheDocument(); + }); + + afterAll(() => { + globalThis.chrome = undefined as unknown as typeof chrome; + globalThis.fetch = undefined as unknown as typeof fetch; + }); +}); diff --git a/packages/extension/src/view/popup/app.tsx b/packages/extension/src/view/popup/app.tsx index c66002f4c..9abc8caad 100644 --- a/packages/extension/src/view/popup/app.tsx +++ b/packages/extension/src/view/popup/app.tsx @@ -22,6 +22,7 @@ import { Button, CirclePieChart, ProgressBar, + ToggleSwitch, prepareCookieStatsComponents, } from '@ps-analysis-tool/design-system'; @@ -42,6 +43,8 @@ const App: React.FC = () => { changeListeningToThisTab, onChromeUrl, allowedNumberOfTabs, + isUsingCDP, + setIsUsingCDP, } = useCookieStore(({ state, actions }) => ({ cookieStats: state.tabCookieStats, isCurrentTabBeingListenedTo: state.isCurrentTabBeingListenedTo, @@ -49,12 +52,22 @@ const App: React.FC = () => { returningToSingleTab: state.returningToSingleTab, allowedNumberOfTabs: state.allowedNumberOfTabs, onChromeUrl: state.onChromeUrl, + isUsingCDP: state.isUsingCDP, + setIsUsingCDP: actions.setIsUsingCDP, changeListeningToThisTab: actions.changeListeningToThisTab, })); + const cdpLabel = isUsingCDP ? 'Disable CDP' : 'Enable CDP'; + if (onChromeUrl) { return ( <> +

Not much to analyze here

Its emptier than a cookie jar after a midnight snack! 🌌 @@ -81,6 +94,12 @@ const App: React.FC = () => { ) { return ( <> + {!returningToSingleTab && (

This tool works best with a single tab for cookie analysis. @@ -97,6 +116,12 @@ const App: React.FC = () => { ) { return ( <> +

No cookies found on this page

Please try reloading the page @@ -108,6 +133,12 @@ const App: React.FC = () => { return ( <> +

void; + setIsUsingCDP: (newValue: boolean) => void; }; } @@ -77,9 +79,11 @@ const initialState: CookieStoreContext = { onChromeUrl: false, tabId: null, allowedNumberOfTabs: null, + isUsingCDP: false, }, actions: { changeListeningToThisTab: noop, + setIsUsingCDP: noop, }, }; @@ -93,6 +97,7 @@ export const Provider = ({ children }: PropsWithChildren) => { const [allowedNumberOfTabs, setAllowedNumberOfTabs] = useState( null ); + const [isUsingCDP, setIsUsingCDP] = useState(false); const [tabCookieStats, setTabCookieStats] = useState(null); @@ -125,6 +130,16 @@ export const Provider = ({ children }: PropsWithChildren) => { setLoading(false); }, 100); + const _setUsingCDP = useCallback((newValue: boolean) => { + chrome.runtime.sendMessage({ + type: 'CHANGE_CDP_SETTING', + payload: { + isUsingCDP: newValue, + }, + }); + setIsUsingCDP(newValue); + }, []); + const intitialSync = useCallback(async () => { const [tab] = await getCurrentTab(); @@ -133,6 +148,9 @@ export const Provider = ({ children }: PropsWithChildren) => { if (extensionStorage?.allowedNumberOfTabs) { setAllowedNumberOfTabs(extensionStorage?.allowedNumberOfTabs); } + if (Object.keys(extensionStorage).includes('isUsingCDP')) { + setIsUsingCDP(extensionStorage.isUsingCDP); + } if (!tab.id || !tab.url) { return; @@ -249,8 +267,15 @@ export const Provider = ({ children }: PropsWithChildren) => { useEffect(() => { const listener = async (message: { type: string; - payload: { tabId: string }; + payload: { tabId?: string; isUsingCDPNewValue?: boolean }; }) => { + if ( + message.type === 'CHANGE_CDP_SETTING' && + typeof message?.payload?.isUsingCDPNewValue !== 'undefined' + ) { + setIsUsingCDP(message?.payload?.isUsingCDPNewValue); + } + if (message.type === 'syncCookieStore:SET_TAB_TO_READ') { const tab = await getCurrentTab(); @@ -314,9 +339,11 @@ export const Provider = ({ children }: PropsWithChildren) => { returningToSingleTab, onChromeUrl, allowedNumberOfTabs, + isUsingCDP, }, actions: { changeListeningToThisTab, + setIsUsingCDP: _setUsingCDP, }, }} > diff --git a/packages/extension/src/view/settings/app.css b/packages/extension/src/view/settings/app.css deleted file mode 100644 index b5c61c956..000000000 --- a/packages/extension/src/view/settings/app.css +++ /dev/null @@ -1,3 +0,0 @@ -@tailwind base; -@tailwind components; -@tailwind utilities; diff --git a/packages/extension/src/view/settings/app.tsx b/packages/extension/src/view/settings/app.tsx deleted file mode 100644 index 2608797ab..000000000 --- a/packages/extension/src/view/settings/app.tsx +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright 2023 Google LLC - * - * 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 - * - * https://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. - */ - -/** - * External dependencies. - */ -import React, { useState } from 'react'; - -/** - * Internal dependencies. - */ -import './app.css'; -import TABS from './tabs'; -import TabHeader from './components/tabHeader'; - -const App: React.FC = () => { - const [selectedTabIndex, setSelectedTabIndex] = useState(0); - const TabContent = TABS[selectedTabIndex].component; - - return ( -
- tab.display_name)} - setSelectedTabIndex={setSelectedTabIndex} - selectedTabIndex={selectedTabIndex} - /> -
- -
-
- ); -}; - -export default App; diff --git a/packages/extension/src/view/settings/components/tabContent/information/index.tsx b/packages/extension/src/view/settings/components/tabContent/information/index.tsx deleted file mode 100644 index 3d2042f4d..000000000 --- a/packages/extension/src/view/settings/components/tabContent/information/index.tsx +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright 2023 Google LLC - * - * 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 - * - * https://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. - */ - -/** - * External dependencies. - */ -import React, { useCallback, useState } from 'react'; -import { useSettingsStore } from '../../../stateProviders/syncSettingsStore'; -import InformationDisplay from './informationDisplay'; -import { Copy } from '@ps-analysis-tool/design-system'; - -const Information = () => { - const { currentTabs, currentExtensions, browserInformation, OSInformation } = - useSettingsStore(({ state }) => ({ - currentTabs: state.currentTabs, - currentExtensions: state.currentExtensions, - browserInformation: state.browserInformation, - OSInformation: state.OSInformation, - })); - - const [copying, setCopying] = useState(false); - - const handleCopy = useCallback(() => { - setCopying(true); - - const clipboardText = ` - Number of open tabs: ${currentTabs} - Active extensions: - ${currentExtensions?.map((extension) => { - return `${extension.extensionName}: ${extension.extensionId}\n`; - })} - Browser Version: ${browserInformation} - OS information: ${OSInformation} - `; - - //doing this instead of css because if text is too long then user might think the text has been copied and which might result in bad UX. Similar to github's copy button. - navigator.clipboard.writeText(clipboardText).then(() => { - setCopying(false); - }); - }, [OSInformation, browserInformation, currentExtensions, currentTabs]); - - return ( -
-
- -
-

Current Tabs:

-
- -
-
-
-

Current installed extensions:

-
- {currentExtensions?.map((extension, index) => { - return ( - - ); - })} -
-
-
-

Chrome version:

-
- -
-
-
-

OS - System architecture

-
- -
-
-
-
- ); -}; - -export default Information; diff --git a/packages/extension/src/view/settings/components/tabContent/settings/allowedNumberOfTabs/index.tsx b/packages/extension/src/view/settings/components/tabContent/settings/allowedNumberOfTabs/index.tsx deleted file mode 100644 index 2bab35683..000000000 --- a/packages/extension/src/view/settings/components/tabContent/settings/allowedNumberOfTabs/index.tsx +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright 2023 Google LLC - * - * 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 - * - * https://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. - */ - -/** - * External dependencies. - */ -import React from 'react'; - -/** - * Internal dependencies. - */ -import { useSettingsStore } from '../../../../stateProviders/syncSettingsStore'; - -const AllowedNumberOfTabs: React.FC = () => { - const { allowedNumberOfTabs, setSettingsInStorage } = useSettingsStore( - ({ state, actions }) => ({ - allowedNumberOfTabs: state.allowedNumberOfTabs, - setSettingsInStorage: actions.setSettingsInStorage, - }) - ); - - return ( -
-

- Total number of allowed tabs to be processed together: -

-
- - setSettingsInStorage('allowedNumberOfTabs', e.target?.value) - } - checked={allowedNumberOfTabs === 'single'} - /> - Single tab processing -
-
- - setSettingsInStorage('allowedNumberOfTabs', e.target?.value) - } - checked={allowedNumberOfTabs === 'unlimited'} - /> - No restriction (Processing too many tabs may cause the browser to slow - down.) - Tabs would need to be refreshed -
-
- ); -}; - -export default AllowedNumberOfTabs; diff --git a/packages/extension/src/view/settings/components/tabContent/settings/index.tsx b/packages/extension/src/view/settings/components/tabContent/settings/index.tsx deleted file mode 100644 index 3c0bf21b0..000000000 --- a/packages/extension/src/view/settings/components/tabContent/settings/index.tsx +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2023 Google LLC - * - * 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 - * - * https://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. - */ - -/** - * External dependencies. - */ -import React from 'react'; - -/** - * Internal dependencies. - */ -import AllowedNumberOfTabs from './allowedNumberOfTabs'; - -const Settings = () => { - return ( -
- -
- ); -}; - -export default Settings; diff --git a/packages/extension/src/view/settings/components/tabHeader/index.tsx b/packages/extension/src/view/settings/components/tabHeader/index.tsx deleted file mode 100644 index 2dbbe65b3..000000000 --- a/packages/extension/src/view/settings/components/tabHeader/index.tsx +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright 2023 Google LLC - * - * 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 - * - * https://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. - */ - -/** - * External dependencies. - */ -import React from 'react'; - -interface TabHeaderProps { - tabs: string[]; - selectedTabIndex: number; - setSelectedTabIndex: (index: number) => void; -} - -const TabHeader = ({ - tabs, - selectedTabIndex, - setSelectedTabIndex, -}: TabHeaderProps) => { - return ( -
- {tabs.map((tab, index) => ( -
setSelectedTabIndex(index)} - key={tab} - className={`text-xl cursor-pointer px-2 border ${ - index === selectedTabIndex - ? 'border-black border-b-white' - : 'border-white' - }`} - > - {tab} -
- ))} -
- ); -}; - -export default TabHeader; diff --git a/packages/extension/src/view/settings/components/tabHeader/tests/index.tsx b/packages/extension/src/view/settings/components/tabHeader/tests/index.tsx deleted file mode 100644 index 027d65b4d..000000000 --- a/packages/extension/src/view/settings/components/tabHeader/tests/index.tsx +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright 2023 Google LLC - * - * 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 - * - * https://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. - */ -/** - * External dependencies. - */ -import React from 'react'; -import { fireEvent, render, screen } from '@testing-library/react'; -import { act } from 'react-dom/test-utils'; -import '@testing-library/jest-dom'; - -/** - * Internal dependencies. - */ -import TabHeader from '..'; -import TABS from '../../../tabs'; -describe('TabHeader', () => { - it('Should render without issue.', async () => { - act(() => - render( - tab.display_name)} - selectedTabIndex={0} - setSelectedTabIndex={() => undefined} - /> - ) - ); - expect(await screen.findByText('Settings')).toBeInTheDocument(); - }); - it('Should render without issue.', () => { - const setSelectedIndexMock = jest.fn(); - act(() => - render( - tab.display_name)} - selectedTabIndex={0} - setSelectedTabIndex={setSelectedIndexMock} - /> - ) - ); - fireEvent.click(screen.getByText('Settings')); - expect(setSelectedIndexMock).toHaveBeenCalled(); - }); -}); diff --git a/packages/extension/src/view/settings/index.html b/packages/extension/src/view/settings/index.html deleted file mode 100644 index 13c17182a..000000000 --- a/packages/extension/src/view/settings/index.html +++ /dev/null @@ -1,12 +0,0 @@ - - - - - PSAT Settings - - - -
- - - diff --git a/packages/extension/src/view/settings/index.tsx b/packages/extension/src/view/settings/index.tsx deleted file mode 100644 index d687460fd..000000000 --- a/packages/extension/src/view/settings/index.tsx +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2023 Google LLC - * - * 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 - * - * https://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. - */ - -/** - * External dependencies. - */ -import React from 'react'; -import { createRoot } from 'react-dom/client'; - -/** - * Internal dependencies. - */ -import App from './app'; -import { Provider as ExternalStoreProvider } from './stateProviders/syncSettingsStore'; - -const root = document.getElementById('root'); - -if (root) { - createRoot(root).render( - - - - ); -} diff --git a/packages/extension/src/view/settings/tabs.ts b/packages/extension/src/view/settings/tabs.ts deleted file mode 100644 index 53a7119b1..000000000 --- a/packages/extension/src/view/settings/tabs.ts +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2023 Google LLC - * - * 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 - * - * https://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. - */ -/** - * Internal dependencies. - */ -import Information from './components/tabContent/information'; -import Settings from './components/tabContent/settings'; - -const TABS = [ - { - display_name: 'Settings', - id: 'settings', - component: Settings, - }, - { - display_name: 'Information', - id: 'information', - component: Information, - }, -]; - -export default TABS; diff --git a/packages/extension/src/view/settings/tests/index.tsx b/packages/extension/src/view/settings/tests/index.tsx deleted file mode 100644 index 1e0c47347..000000000 --- a/packages/extension/src/view/settings/tests/index.tsx +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright 2023 Google LLC - * - * 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 - * - * https://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. - */ -/** - * External dependencies. - */ -import React from 'react'; -import { render, screen } from '@testing-library/react'; -import { act } from 'react-dom/test-utils'; -import '@testing-library/jest-dom'; -import SinonChrome from 'sinon-chrome'; - -/** - * Internal dependencies. - */ -import App from '../app'; -import { Provider as SettingsProvider } from '../stateProviders/syncSettingsStore'; - -describe('Index', () => { - beforeAll(() => { - globalThis.chrome = SinonChrome as unknown as typeof chrome; - globalThis.chrome = { - ...SinonChrome, - storage: { - //@ts-ignore - local: { - clear: () => Promise.resolve(), - //@ts-ignore - onChanged: { - addListener: () => undefined, - removeListener: () => undefined, - }, - }, - sync: { - //@ts-ignore - // eslint-disable-next-line @typescript-eslint/no-unused-vars - get: (_, __) => - // eslint-disable-next-line @typescript-eslint/no-explicit-any - new Promise<{ [key: string]: any }>((resolve) => { - resolve({ - allowedNumberOfTabs: 'single', - }); - }), - //@ts-ignore - onChanged: { - addListener: () => undefined, - removeListener: () => undefined, - }, - }, - }, - }; - }); - - it('All Providers should be added to DOM', async () => { - act(() => - render( - - - - ) - ); - expect(await screen.findByText('Settings')).toBeInTheDocument(); - }); - - afterAll(() => { - globalThis.chrome = undefined as unknown as typeof chrome; - }); -}); diff --git a/packages/extension/webpack.config.cjs b/packages/extension/webpack.config.cjs index 3434175f5..27cc856cb 100644 --- a/packages/extension/webpack.config.cjs +++ b/packages/extension/webpack.config.cjs @@ -97,26 +97,4 @@ const popup = { ...commonConfig, }; -const settings = { - entry: { - index: './src/view/settings/index.tsx', - }, - output: { - path: path.resolve(__dirname, '../../dist/extension/settings'), - filename: 'index.js', - }, - plugins: [ - new WebpackBar({ - name: 'Settings', - color: '#fcd8ba', - }), - new HtmlWebpackPlugin({ - title: 'PSAT Settings', - template: './src/view/settings/index.html', - inject: false, - }), - ], - ...commonConfig, -}; - -module.exports = [root, devTools, popup, settings]; +module.exports = [root, devTools, popup]; diff --git a/tests/jest.config.cjs b/tests/jest.config.cjs index ce3d1a658..1da8b2a39 100644 --- a/tests/jest.config.cjs +++ b/tests/jest.config.cjs @@ -49,7 +49,6 @@ module.exports = { '/packages/extension/src/view/devtools/index.tsx', '/packages/extension/src/view/popup/index.tsx', '/packages/extension/src/view/devtools/devtools.ts', - '/packages/extension/src/view/settings/index.tsx', ], coverageReporters: ['lcov'], collectCoverageFrom: [ diff --git a/third_party/icons/done.svg b/third_party/icons/done.svg new file mode 100644 index 000000000..6ec740a7f --- /dev/null +++ b/third_party/icons/done.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/third_party/icons/gear.svg b/third_party/icons/gear.svg new file mode 100644 index 000000000..9dac08041 --- /dev/null +++ b/third_party/icons/gear.svg @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/third_party/icons/information-icon.svg b/third_party/icons/information-icon.svg new file mode 100644 index 000000000..7e07bbf68 --- /dev/null +++ b/third_party/icons/information-icon.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/third_party/icons/settings-tab-white.svg b/third_party/icons/settings-tab-white.svg new file mode 100644 index 000000000..a9eeb87f6 --- /dev/null +++ b/third_party/icons/settings-tab-white.svg @@ -0,0 +1,9 @@ + + + + + + + + \ No newline at end of file diff --git a/third_party/icons/settings-tab.svg b/third_party/icons/settings-tab.svg new file mode 100644 index 000000000..c87eec7b8 --- /dev/null +++ b/third_party/icons/settings-tab.svg @@ -0,0 +1,9 @@ + + + + + + + + \ No newline at end of file From 64f0f9f41c67c318a7e8b344901bca743de93151 Mon Sep 17 00:00:00 2001 From: sayedtaqui Date: Thu, 18 Jan 2024 19:51:01 +0530 Subject: [PATCH 4/4] Bump version --- packages/cli-dashboard/package.json | 2 +- packages/cli/package.json | 2 +- packages/cli/src/index.ts | 2 +- packages/common/package.json | 2 +- packages/design-system/package.json | 2 +- packages/eslint-import-resolver/package.json | 2 +- packages/extension/package.json | 2 +- packages/extension/src/manifest.json | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/cli-dashboard/package.json b/packages/cli-dashboard/package.json index fb2beaf98..65a0e337e 100644 --- a/packages/cli-dashboard/package.json +++ b/packages/cli-dashboard/package.json @@ -1,6 +1,6 @@ { "name": "@ps-analysis-tool/cli-dashboard", - "version": "0.4.1", + "version": "0.4.2", "description": "Dashboard for visualizing cli analysis output", "repository": { "type": "git", diff --git a/packages/cli/package.json b/packages/cli/package.json index 320d5936c..a6154c271 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@ps-analysis-tool/cli", - "version": "0.4.1", + "version": "0.4.2", "description": "CLI tool for analysis", "main": "index.js", "scripts": { diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index 80bd5eaff..1214e322e 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -42,7 +42,7 @@ const DELAY_TIME = 20000; const program = new Command(); program - .version('0.4.1') + .version('0.4.2') .description('CLI to test a URL for 3p cookies') .option('-u, --url ', 'URL of a site') .option('-s, --sitemap-url ', 'URL of a sitemap') diff --git a/packages/common/package.json b/packages/common/package.json index 12d944678..b788c792f 100644 --- a/packages/common/package.json +++ b/packages/common/package.json @@ -1,6 +1,6 @@ { "name": "@ps-analysis-tool/common", - "version": "0.4.1", + "version": "0.4.2", "description": "A package for common utilities that are being used in multiple packages", "main": "./dist/index.js", "types": "./dist-types/index.d.ts", diff --git a/packages/design-system/package.json b/packages/design-system/package.json index 96bbdfc39..d651ddbea 100644 --- a/packages/design-system/package.json +++ b/packages/design-system/package.json @@ -1,6 +1,6 @@ { "name": "@ps-analysis-tool/design-system", - "version": "0.4.1", + "version": "0.4.2", "description": "A package for presentational components that are being used in multiple packages", "main": "./src/index.ts", "source":"./src/index.ts", diff --git a/packages/eslint-import-resolver/package.json b/packages/eslint-import-resolver/package.json index cc9d5058a..15d8a74e7 100644 --- a/packages/eslint-import-resolver/package.json +++ b/packages/eslint-import-resolver/package.json @@ -1,6 +1,6 @@ { "name": "@ps-analysis-tool/eslint-import-resolver", - "version": "0.4.1", + "version": "0.4.2", "description": "", "main": "src/index.cjs", "scripts": { diff --git a/packages/extension/package.json b/packages/extension/package.json index a5124c0b9..91f0232b8 100644 --- a/packages/extension/package.json +++ b/packages/extension/package.json @@ -1,6 +1,6 @@ { "name": "@ps-analysis-tool/extension", - "version": "0.4.1", + "version": "0.4.2", "description": "Chrome extension for cookie analysis", "repository": { "type": "git", diff --git a/packages/extension/src/manifest.json b/packages/extension/src/manifest.json index 9b0ca4285..7dbcb3347 100644 --- a/packages/extension/src/manifest.json +++ b/packages/extension/src/manifest.json @@ -1,7 +1,7 @@ { "name": "Privacy Sandbox Analysis Tool", "description": "Tooling for understanding cookie usage and guidance on new privacy-preserving Chrome APIs.", - "version": "0.4.1", + "version": "0.4.2", "manifest_version": 3, "icons": { "16": "icons/icon-16.png",