Compare commits
	
		
			No commits in common. "develop" and "toolbar-design" have entirely different histories.
		
	
	
		
			develop
			...
			toolbar-de
		
	
		
							
								
								
									
										7
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						@ -5,10 +5,3 @@ sidebar.js
 | 
			
		||||
lib/*
 | 
			
		||||
/yarn-error.log
 | 
			
		||||
/rentgen.zip
 | 
			
		||||
 | 
			
		||||
# Generated PNG icons (build artifacts)
 | 
			
		||||
/assets/icons/*.png
 | 
			
		||||
/assets/icon-addon-*.png
 | 
			
		||||
 | 
			
		||||
# Exception: do not ignore the `browser-api` directory inside `lib`
 | 
			
		||||
!/lib/browser-api/
 | 
			
		||||
							
								
								
									
										9
									
								
								.vscode/settings.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,9 @@
 | 
			
		||||
{
 | 
			
		||||
	"cSpell.words": [
 | 
			
		||||
		"ECLI",
 | 
			
		||||
		"EROD",
 | 
			
		||||
		"targetowania",
 | 
			
		||||
		"targetowaniem",
 | 
			
		||||
		"TSUE"
 | 
			
		||||
	]
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										117
									
								
								README.md
									
									
									
									
									
								
							
							
						
						@ -1,114 +1,17 @@
 | 
			
		||||
<h1 style="display: flex; align-items: center;"><img src="./assets/icon-addon-2048.png" alt="Rentgen logo" style="margin-right: 1rem;" width="48"/> Rentgen</h1>
 | 
			
		||||
 | 
			
		||||
<strong>Rentgen</strong> is an add-on prepared for Firefox-based browsers. This extension will automatically visualize all the data that a given website ~~steals~~ sends to third parties.
 | 
			
		||||
## Description
 | 
			
		||||
 | 
			
		||||
<strong>Rentgen</strong> to wtyczka dla przeglądarek opartych o Firefoxa, która automatycznie wizualizuje, jakie dane zostały ~~wykradzione~~ wysłane do podmiotów trzecich przez odwiedzane strony.
 | 
			
		||||
 | 
			
		||||
<strong>Rentgen</strong> is an add-on prepared for Firefox based browsers. This extension will automatically visualize all the data that a given website sends to third parties.
 | 
			
		||||
 | 
			
		||||
Note: At the moment, we support Polish language because this extension generates mail content that is dedicated to Polish website owners. In further versions of this add-on, we will add other languages as well.
 | 
			
		||||
 | 
			
		||||
**Features:**
 | 
			
		||||
## How build and run add-on
 | 
			
		||||
 | 
			
		||||
-   analysis of web traffic generated by the visited website;
 | 
			
		||||
-   visualization of data transmitted to third parties by the visited site (user's browsing history and cookies);
 | 
			
		||||
-   preparation of screenshots of development tools as evidence of data transmitted to third parties;
 | 
			
		||||
-   assisting in the evaluation of potential work areas for compliance with GDPR;
 | 
			
		||||
-   generating a report or email content that can be sent to an administrator and Personal Data Protection Office in Poland.
 | 
			
		||||
 | 
			
		||||
## Installation
 | 
			
		||||
 | 
			
		||||
Firefox: https://addons.mozilla.org/en-US/firefox/addon/rentgen/
 | 
			
		||||
 | 
			
		||||
## How to build and run Rentgen on your own
 | 
			
		||||
 | 
			
		||||
### Pre-requirements
 | 
			
		||||
 | 
			
		||||
-   OS: Linux x86_64
 | 
			
		||||
-   Node.js: 16.x version
 | 
			
		||||
-   npm: 7.x version or higher
 | 
			
		||||
 | 
			
		||||
### Build steps
 | 
			
		||||
 | 
			
		||||
1. Pull repository or download a zip package
 | 
			
		||||
2. Go to the root directory of the pulled repository
 | 
			
		||||
3. Run command: `npm install`
 | 
			
		||||
4. Run command: `npm run build`
 | 
			
		||||
5. Run command: `npm run create-package`
 | 
			
		||||
6. Go to the `web-ext-artifacts` directory
 | 
			
		||||
7. You will find a zip archive: `rentgen-x-x-x.zip` (`x-x-x` means add-on version)
 | 
			
		||||
 | 
			
		||||
### Run steps
 | 
			
		||||
 | 
			
		||||
1. Run Firefox and go to `about:debugging`
 | 
			
		||||
2. Click _This Firefox_ tab
 | 
			
		||||
3. Click _Load Temporary Add-on..._ button
 | 
			
		||||
4. Pick the zip archive from last step of build process.
 | 
			
		||||
 | 
			
		||||
## Issue tracker
 | 
			
		||||
 | 
			
		||||
If you find a problem, please send us an email: kontakt@internet-czas-dzialac.pl
 | 
			
		||||
 | 
			
		||||
We don't receive issues on Microsoft Github.
 | 
			
		||||
 | 
			
		||||
Each issue will be reviewed and moved to an internal issues list of our Gitea instance: https://git.internet-czas-dzialac.pl/icd/rentgen/issues. We use Gitea and most likely in the future with the federalization of Gitea, we will be able to let users in to report issues directly from the Gitea site.
 | 
			
		||||
 | 
			
		||||
## Screenshots
 | 
			
		||||
 | 
			
		||||
<img src="./assets/screenshots/image-14.png" />
 | 
			
		||||
<img src="./assets/screenshots/image-15.png" />
 | 
			
		||||
<img src="./assets/screenshots/3a.png" />
 | 
			
		||||
<img src="./assets/screenshots/3b.png" />
 | 
			
		||||
<img src="./assets/screenshots/4a.png" />
 | 
			
		||||
<img src="./assets/screenshots/4b.png" />
 | 
			
		||||
<img src="./assets/screenshots/5a.png" />
 | 
			
		||||
<img src="./assets/screenshots/5b.png" />
 | 
			
		||||
<img src="./assets/screenshots/2022-07-14_21-04.png" />
 | 
			
		||||
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
<strong>Rentgen</strong> to wtyczka dla przeglądarek opartych o Firefoxa, która automatycznie wizualizuje, jakie dane zostały ~~wykradzione~~ wysłane do podmiotów trzecich przez odwiedzane strony. Wtyczka obrazuje ilość skryptów śledzących na stronie internetowej i pomaga w sformułowaniu maila do administratora strony, który może być podstawą do skargi RODO w Urzędzie Ochrony Danych Osobowych.
 | 
			
		||||
 | 
			
		||||
**Funkcje Rentgena:**
 | 
			
		||||
 | 
			
		||||
-   analiza ruchu sieciowego generowanego przez stronę internetową;
 | 
			
		||||
-   wizualizacja danych przekazanych do podmiotów trzecich przez odwiedzaną stronę (historia przeglądania użytkownika oraz jego ciasteczka);
 | 
			
		||||
-   przygotowywanie zrzutów ekranów narzędzi deweloperskich będących dowodem przekazanych danych do podmiotów trzecich;
 | 
			
		||||
-   pomoc w oszacowaniu potencjalnych obszarów roboczych względem zgodności z RODO;
 | 
			
		||||
-   generowanie raportu lub treści maila, którą można wysłać do administratora oraz Urzędu Ochrony Danych Osobowych.
 | 
			
		||||
 | 
			
		||||
## Instalacja
 | 
			
		||||
 | 
			
		||||
Firefox: https://addons.mozilla.org/pl/firefox/addon/rentgen/
 | 
			
		||||
 | 
			
		||||
## Jak zbudować i uruchomić Rentgena ze źródeł
 | 
			
		||||
 | 
			
		||||
### Wymagania wstępne
 | 
			
		||||
 | 
			
		||||
-   System operacyjny: Linux x86_64
 | 
			
		||||
-   Node.js: 16.x
 | 
			
		||||
-   npm: 7.x lub wyższy
 | 
			
		||||
 | 
			
		||||
### Proces budowy
 | 
			
		||||
 | 
			
		||||
1. Pobierz repozytorium przez `git pull https://git.internet-czas-dzialac.pl/icd/rentgen.git` lub pobierz archwium zip
 | 
			
		||||
2. Przejdź do głównego katalogu pobranego repozytorium
 | 
			
		||||
3. Uruchom komendę: `npm install`
 | 
			
		||||
4. Uruchom komendę: `npm run build`
 | 
			
		||||
5. Uruchom komendę: `npm run create-package`
 | 
			
		||||
6. Przejdź do katalogu `web-ext-artifacts`
 | 
			
		||||
7. Znajdziesz tam archiwum zip: `rentgen-x-x-x.zip` (`x-x-x` oznaczają wersję wtyczki)
 | 
			
		||||
 | 
			
		||||
### Kroki do uruchomienia
 | 
			
		||||
 | 
			
		||||
1. Uruchom Firefoxa i przejdź do strony `about:debugging`
 | 
			
		||||
2. Kliknij zakładkę _This Firefox_
 | 
			
		||||
3. Kliknij przycisk _Load Temporary Add-on..._
 | 
			
		||||
4. Wybierz archiwum, które zbudowałeś w ostatnim kroku procesu budowy
 | 
			
		||||
 | 
			
		||||
## Zgłaszanie błędów
 | 
			
		||||
 | 
			
		||||
Jeżeli znajdziesz jakieś problem, napisz do nas maila: kontakt@internet-czas-dzialac.pl
 | 
			
		||||
 | 
			
		||||
Nie przyjmujemy zgłoszeń na platformie Microsoft Github.
 | 
			
		||||
 | 
			
		||||
Każdy problem zostanie sprawdzony i przeniesiony na wewnętrzną listę problemów na naszej instancji Gitea: https://git.internet-czas-dzialac.pl/icd/rentgen/issues. Korzystamy z Gitea i najprawdopodobniej w przyszłości dzięki federalizacji Gitea będziemy w stanie wpuścić użytkowników do zgłaszania błędów bezpośrednio ze strony Gitea.
 | 
			
		||||
 | 
			
		||||
---
 | 
			
		||||
1. Use latest node 16.x and npm 8.x
 | 
			
		||||
2. `npm install`
 | 
			
		||||
3. `npm run build`
 | 
			
		||||
4. The build code is located in the `lib/` directory
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
		 Before Width: | Height: | Size: 460 KiB  | 
| 
		 Before Width: | Height: | Size: 480 KiB  | 
| 
		 Before Width: | Height: | Size: 526 KiB  | 
| 
		 Before Width: | Height: | Size: 414 KiB  | 
| 
		 Before Width: | Height: | Size: 275 KiB  | 
| 
		 Before Width: | Height: | Size: 316 KiB  | 
| 
		 Before Width: | Height: | Size: 139 KiB  | 
| 
		 Before Width: | Height: | Size: 215 KiB  | 
| 
		 Before Width: | Height: | Size: 362 KiB  | 
| 
		 Before Width: | Height: | Size: 468 KiB  | 
| 
		 Before Width: | Height: | Size: 752 KiB  | 
| 
		 Before Width: | Height: | Size: 346 KiB  | 
@ -2,24 +2,18 @@ import { RequestCluster } from '../../request-cluster';
 | 
			
		||||
import { ParsedAnswers } from './parse-answers';
 | 
			
		||||
import NoInformationAtAllProblem from './problems/no-information-at-all';
 | 
			
		||||
import { Problem } from './problems/problem';
 | 
			
		||||
import { TransferOutsideEU } from './problems/transfer-outside-eu';
 | 
			
		||||
import { UnknownIdentity } from './problems/unknown-identity';
 | 
			
		||||
import { UnknownLegalBasis } from './problems/unknown-legal-basis';
 | 
			
		||||
import { UnknownPurposes } from './problems/unknown-purpose';
 | 
			
		||||
import { UnlawfulCookieAccess } from './problems/unlawful-cookies';
 | 
			
		||||
 | 
			
		||||
export default function deduceProblems(
 | 
			
		||||
    answers: ParsedAnswers,
 | 
			
		||||
    clusters: Record<string, RequestCluster>
 | 
			
		||||
): Problem[] {
 | 
			
		||||
    return [
 | 
			
		||||
        NoInformationAtAllProblem,
 | 
			
		||||
        UnknownPurposes,
 | 
			
		||||
        UnlawfulCookieAccess,
 | 
			
		||||
        UnknownLegalBasis,
 | 
			
		||||
        UnknownIdentity,
 | 
			
		||||
        TransferOutsideEU,
 | 
			
		||||
    ]
 | 
			
		||||
        .map((c) => new c(answers, clusters))
 | 
			
		||||
        .filter((p) => p.qualifies());
 | 
			
		||||
    const problems = [];
 | 
			
		||||
    if (answers.popup_type === 'none') {
 | 
			
		||||
        problems.push(new NoInformationAtAllProblem(answers, clusters));
 | 
			
		||||
    }
 | 
			
		||||
    if (UnlawfulCookieAccess.qualifies(answers, Object.values(clusters))) {
 | 
			
		||||
        problems.push(new UnlawfulCookieAccess(answers, clusters));
 | 
			
		||||
    }
 | 
			
		||||
    return problems;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -5,6 +5,12 @@ h1 {
 | 
			
		||||
    margin-bottom: calc(24 / 16 * 1rem);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.generator-container {
 | 
			
		||||
    max-width: 100ex;
 | 
			
		||||
    margin: 0 auto;
 | 
			
		||||
    font-size: calc(14 / 16 * 1rem);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.mail-container {
 | 
			
		||||
    box-shadow: rgba(12, 12, 13, 0.1) 0px 1px 4px 0px;
 | 
			
		||||
    background-color: #fff;
 | 
			
		||||
@ -47,28 +53,3 @@ h1 {
 | 
			
		||||
        font-weight: 700;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.buttons-email-container {
 | 
			
		||||
    display: grid;
 | 
			
		||||
    grid-gap: 1rem;
 | 
			
		||||
    grid-template-columns: 1fr 1fr 1fr 1fr;
 | 
			
		||||
    margin: 2rem 0;
 | 
			
		||||
    padding: 1em 0;
 | 
			
		||||
 | 
			
		||||
    &--single {
 | 
			
		||||
        grid-template-columns: 1fr 1fr 1fr;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .sv_prev_btn,
 | 
			
		||||
    .sv_next_btn {
 | 
			
		||||
        margin: 0 !important;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .sv_prev_btn {
 | 
			
		||||
        grid-column: 2/3;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .sv_next_btn--single {
 | 
			
		||||
        grid-column: 2;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,31 +1,23 @@
 | 
			
		||||
import { RequestCluster } from '../../request-cluster';
 | 
			
		||||
import { getDate } from '../../util';
 | 
			
		||||
import deduceProblems from './deduce-problems';
 | 
			
		||||
import { Explainers } from './explainers';
 | 
			
		||||
import { ParsedAnswers } from './parse-answers';
 | 
			
		||||
import { v } from './verbs';
 | 
			
		||||
import './email-content.scss';
 | 
			
		||||
import { Fragment, useState } from 'react';
 | 
			
		||||
import emailIntro from './email-intro';
 | 
			
		||||
import { reportIntro } from './report-intro';
 | 
			
		||||
import { downloadText } from '../../util';
 | 
			
		||||
import { getFakeClusterData } from './fake-clusters';
 | 
			
		||||
 | 
			
		||||
const SS_URL = 'http://65.108.60.135:3000';
 | 
			
		||||
declare var PLUGIN_NAME: string;
 | 
			
		||||
declare var PLUGIN_URL: string;
 | 
			
		||||
 | 
			
		||||
export default function EmailContent({
 | 
			
		||||
    answers,
 | 
			
		||||
    visited_url,
 | 
			
		||||
    clusters,
 | 
			
		||||
    scrRequestPath,
 | 
			
		||||
    downloadFiles,
 | 
			
		||||
    user_role,
 | 
			
		||||
}: {
 | 
			
		||||
    answers: ParsedAnswers;
 | 
			
		||||
    visited_url: string;
 | 
			
		||||
    clusters: Record<string, RequestCluster>;
 | 
			
		||||
    scrRequestPath: string;
 | 
			
		||||
    downloadFiles: Function;
 | 
			
		||||
    user_role: string;
 | 
			
		||||
}) {
 | 
			
		||||
    const _ = (key: string) => v(key, answers.zaimek);
 | 
			
		||||
    const problems = deduceProblems(answers, clusters);
 | 
			
		||||
@ -41,92 +33,56 @@ export default function EmailContent({
 | 
			
		||||
    function copyTextToClipboard() {
 | 
			
		||||
        // Should be changed in the future to Clipboard API (https://developer.mozilla.org/en-US/docs/Web/API/Clipboard/write#browser_compatibility)
 | 
			
		||||
        let r = document.createRange();
 | 
			
		||||
        const container = document.querySelector('.mail-container__content');
 | 
			
		||||
        if (!container) return;
 | 
			
		||||
        r.selectNode(container);
 | 
			
		||||
        window.getSelection()?.addRange(r);
 | 
			
		||||
        r.selectNode(document.querySelector('.mail-container__content'));
 | 
			
		||||
        window.getSelection().addRange(r);
 | 
			
		||||
        document.execCommand('copy');
 | 
			
		||||
        window.getSelection()?.removeAllRanges();
 | 
			
		||||
        window.getSelection().removeAllRanges();
 | 
			
		||||
        setCopy(true);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const mode = answers.user_role === 'user' ? 'email' : 'report';
 | 
			
		||||
    const email_tone = answers.email_type === 'polite_information' ? 'polite' : 'official';
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <Fragment>
 | 
			
		||||
            <div className="generator-container">
 | 
			
		||||
                <h1>Treść {mode === 'email' ? 'maila' : 'raportu'}</h1>
 | 
			
		||||
                <h1>Treść maila</h1>
 | 
			
		||||
                <div className="mail-container">
 | 
			
		||||
                    <div className="mail-container__header">
 | 
			
		||||
                        <div className="mail-container__header--control"></div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <article className="mail-container__content">
 | 
			
		||||
                        {mode === 'email'
 | 
			
		||||
                            ? emailIntro(email_tone, _, visited_url)
 | 
			
		||||
                            : reportIntro(visited_url)}
 | 
			
		||||
                        {problems.map((problem, index) => {
 | 
			
		||||
                            const Component = problem.getEmailContent.bind(problem);
 | 
			
		||||
                            return <Component mode={mode} tone={email_tone} key={index} />;
 | 
			
		||||
                        })}
 | 
			
		||||
                        <p>Dzień dobry,</p>
 | 
			
		||||
                        <p>
 | 
			
		||||
                            w dniu {getDate()} {_('odwiedziłem')} stronę {visited_url}. Po
 | 
			
		||||
                            podejrzeniu ruchu sieciowego generowanego przez tę stronę za pomocą
 | 
			
		||||
                            wtyczki <a href={PLUGIN_URL}>{PLUGIN_NAME}</a> w przeglądarce Firefox{' '}
 | 
			
		||||
                            {_('mam')} pytania dotyczące przetwarzania {_('moich')} danych
 | 
			
		||||
                            osobowych, na które nie {_('znalazłem')} odpowiedzi nigdzie na Państwa
 | 
			
		||||
                            stronie.
 | 
			
		||||
                        </p>
 | 
			
		||||
                        {problems.map((problem) => problem.getEmailContent())}
 | 
			
		||||
                        {explainers.map((explainer) => explainer(answers.zaimek))}
 | 
			
		||||
                        <h2>Państwa rola jako współadministratora danych osobowych</h2>
 | 
			
		||||
                        {mode == 'email' ? (
 | 
			
		||||
                            <p>
 | 
			
		||||
                                {_('Zwracam')} Państwa uwagę na fakt, że w myśl{' '}
 | 
			
		||||
                                <a href="https://curia.europa.eu/juris/document/document.jsf?text=&docid=216555&pageIndex=0&doclang=PL&mode=lst&dir=&occ=first&part=1&cid=1254905">
 | 
			
		||||
                                    treści wyroku TSUE w sprawie C-40/17
 | 
			
		||||
                                </a>{' '}
 | 
			
		||||
                                poprzez wysyłanie moich danych w wyżej opisanym zakresie stają się
 | 
			
		||||
                                Państwo współadministratorem {_('moich')} danych osobowych, nawet
 | 
			
		||||
                                jeżeli nie są Państwo bezpośrednimi autorami osadzonych na Państwa
 | 
			
		||||
                                stronie skryptów czy innych zasobów ujawniających dane użytkowników
 | 
			
		||||
                                Państwa strony podmiotom trzecim. Dlatego ciąży na Państwu obowiązek
 | 
			
		||||
                                odpowiedzi na {_('moje')} pytania na mocy Art. 12 i 13
 | 
			
		||||
                                Rozporządzenia 2016/679 Parlamentu Europejskiego i Rady (UE) z dnia
 | 
			
		||||
                                27 kwietnia 2016 r. w sprawie ochrony osób fizycznych w związku z
 | 
			
		||||
                                przetwarzaniem danych osobowych i w sprawie swobodnego przepływu
 | 
			
		||||
                                takich danych oraz uchylenia dyrektywy 95/46/WE (ogólne
 | 
			
		||||
                                rozporządzenie o ochronie danych) – RODO
 | 
			
		||||
                            </p>
 | 
			
		||||
                        ) : (
 | 
			
		||||
                            <p>
 | 
			
		||||
                                W myśl{' '}
 | 
			
		||||
                                <a href="https://curia.europa.eu/juris/document/document.jsf?text=&docid=216555&pageIndex=0&doclang=PL&mode=lst&dir=&occ=first&part=1&cid=1254905">
 | 
			
		||||
                                    treści wyroku TSUE w sprawie C-40/17
 | 
			
		||||
                                </a>
 | 
			
		||||
                                , ponoszą Państwo współodpowiedzialność za skrypty i inne zasoby
 | 
			
		||||
                                ujawniajace dane osobowe na Państwa stronie, nawet jeżeli nie są
 | 
			
		||||
                                Państwo ich bezpośrednimi autorami.
 | 
			
		||||
                            </p>
 | 
			
		||||
                        )}
 | 
			
		||||
                        <p>
 | 
			
		||||
                            {_('Zwracam')} Państwa uwagę na fakt, że w myśl{' '}
 | 
			
		||||
                            <a href="https://curia.europa.eu/juris/document/document.jsf?text=&docid=216555&pageIndex=0&doclang=PL&mode=lst&dir=&occ=first&part=1&cid=1254905">
 | 
			
		||||
                                treści wyroku TSUE w sprawie C-40/17
 | 
			
		||||
                            </a>{' '}
 | 
			
		||||
                            poprzez wysyłanie moich danych w wyżej opisanym zakresie stają się
 | 
			
		||||
                            Państwo współadministratorem moich danych osobowych, dlatego ciąży na
 | 
			
		||||
                            Państwu obowiązek odpowiedzi na moje pytania na mocy Art. 12 i 13
 | 
			
		||||
                            Rozporządzenia 2016/679 Parlamentu Europejskiego i Rady (UE) z dnia 27
 | 
			
		||||
                            kwietnia 2016 r. w sprawie ochrony osób fizycznych w związku z
 | 
			
		||||
                            przetwarzaniem danych osobowych i w sprawie swobodnego przepływu takich
 | 
			
		||||
                            danych oraz uchylenia dyrektywy 95/46/WE (ogólne rozporządzenie o
 | 
			
		||||
                            ochronie danych, dalej: „RODO”).
 | 
			
		||||
                        </p>
 | 
			
		||||
                    </article>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div
 | 
			
		||||
                    className={
 | 
			
		||||
                        scrRequestPath
 | 
			
		||||
                            ? 'buttons-email-container'
 | 
			
		||||
                            : 'buttons-email-container buttons-email-container--single'
 | 
			
		||||
                    }
 | 
			
		||||
                >
 | 
			
		||||
                    {scrRequestPath ? (
 | 
			
		||||
                        <button
 | 
			
		||||
                            className="sv_prev_btn"
 | 
			
		||||
                            onClick={() => downloadFiles(`${SS_URL}${scrRequestPath}`)}
 | 
			
		||||
                        >
 | 
			
		||||
                            Pobierz zrzuty ekranów
 | 
			
		||||
                        </button>
 | 
			
		||||
                    ) : null}
 | 
			
		||||
                    <button
 | 
			
		||||
                        className={
 | 
			
		||||
                            scrRequestPath ? 'sv_next_btn' : 'sv_next_btn sv_next_btn--single'
 | 
			
		||||
                        }
 | 
			
		||||
                        onClick={() => copyTextToClipboard()}
 | 
			
		||||
                    >
 | 
			
		||||
                        {copied ? 'Skopiowano!' : 'Kopiuj treść'}
 | 
			
		||||
                <div className="buttons-container">
 | 
			
		||||
                    <button className="sv_next_btn" onClick={() => copyTextToClipboard()}>
 | 
			
		||||
                        {copied ? 'Skopiowano!' : 'Kopiuj treść wiadomości'}
 | 
			
		||||
                    </button>
 | 
			
		||||
                </div>
 | 
			
		||||
                {copied && user_role === 'user' ? (
 | 
			
		||||
                {copied ? (
 | 
			
		||||
                    <section className="greeting-text">
 | 
			
		||||
                        <strong>Przed Tobą ostatni krok! 😊</strong>
 | 
			
		||||
                        <p>
 | 
			
		||||
@ -138,23 +94,6 @@ export default function EmailContent({
 | 
			
		||||
                        </p>
 | 
			
		||||
                    </section>
 | 
			
		||||
                ) : null}
 | 
			
		||||
                <div className="diag-toolbox">
 | 
			
		||||
                    <a
 | 
			
		||||
                        href="#"
 | 
			
		||||
                        onClick={() =>
 | 
			
		||||
                            downloadText(
 | 
			
		||||
                                'diag.json',
 | 
			
		||||
                                JSON.stringify({
 | 
			
		||||
                                    answers,
 | 
			
		||||
                                    fake_clusters_data: getFakeClusterData(clusters),
 | 
			
		||||
                                    visited_url,
 | 
			
		||||
                                })
 | 
			
		||||
                            )
 | 
			
		||||
                        }
 | 
			
		||||
                    >
 | 
			
		||||
                        Pobierz plik diagnostyczny
 | 
			
		||||
                    </a>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </Fragment>
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
@ -1,32 +0,0 @@
 | 
			
		||||
import { getDate } from '../../util';
 | 
			
		||||
 | 
			
		||||
declare var PLUGIN_NAME: string;
 | 
			
		||||
declare var PLUGIN_URL: string;
 | 
			
		||||
 | 
			
		||||
export default function emailIntro(
 | 
			
		||||
    tone: 'polite' | 'official',
 | 
			
		||||
    _: (verb: string) => string,
 | 
			
		||||
    visited_url: string
 | 
			
		||||
) {
 | 
			
		||||
    return (
 | 
			
		||||
        <>
 | 
			
		||||
            <p>{tone == 'polite' ? 'Szanowni Państwo' : 'Dzień dobry'},</p>
 | 
			
		||||
            <p>
 | 
			
		||||
                w dniu {getDate()} {_('odwiedziłem')} stronę {visited_url}. Po podejrzeniu ruchu
 | 
			
		||||
                sieciowego generowanego przez tę stronę za pomocą wtyczki{' '}
 | 
			
		||||
                <a href={PLUGIN_URL}>{PLUGIN_NAME}</a> w przeglądarce Firefox{' '}
 | 
			
		||||
                {tone == 'polite' ? (
 | 
			
		||||
                    <>
 | 
			
		||||
                        {_('chciałbym')} zwrócić Państwa uwagę na kilka potencjalnych problemów ze
 | 
			
		||||
                        zgodnością RODO na Państwa stronie.
 | 
			
		||||
                    </>
 | 
			
		||||
                ) : (
 | 
			
		||||
                    <>
 | 
			
		||||
                        {_('mam')} pytania dotyczące przetwarzania {_('moich')} danych osobowych, na
 | 
			
		||||
                        które nie {_('znalazłem')} odpowiedzi nigdzie na Państwa stronie.
 | 
			
		||||
                    </>
 | 
			
		||||
                )}
 | 
			
		||||
            </p>
 | 
			
		||||
        </>
 | 
			
		||||
    );
 | 
			
		||||
}
 | 
			
		||||
@ -1,6 +1,4 @@
 | 
			
		||||
// various explainers that could be related to multiple problems. They are gathered here and added at the end of the email to avoid pasting them multiple times
 | 
			
		||||
 | 
			
		||||
export type ExplainerKey = 'cookies_are_pii' | 'responsibility_for_third_parties';
 | 
			
		||||
export type ExplainerKey = 'cookies_are_pii';
 | 
			
		||||
 | 
			
		||||
export const Explainers: Record<ExplainerKey, (zaimek_index: 0 | 1 | 2 | 3) => JSX.Element> = {
 | 
			
		||||
    cookies_are_pii: () => (
 | 
			
		||||
@ -9,63 +7,7 @@ export const Explainers: Record<ExplainerKey, (zaimek_index: 0 | 1 | 2 | 3) => J
 | 
			
		||||
            <p>
 | 
			
		||||
                Sztucznie wygenerowane identyfikatory przechowywane w plikach Cookies stanowią dane
 | 
			
		||||
                osobowe. Wskazuje na to wprost Art. 4. pkt 1. RODO, wymieniając „identyfikator
 | 
			
		||||
                internetowy” i „numer identyfikacyjny” jako przykłady danych osobowych. Losowe
 | 
			
		||||
                przypisane identyfikatory mogą nie zawierać imienia i nazwiska osoby, której
 | 
			
		||||
                dotyczą, ani nie prowadzić wprost do ich ustalenia, ale pozwalają odróżnić jedną,
 | 
			
		||||
                daną konkretną osobę, od innych.
 | 
			
		||||
            </p>
 | 
			
		||||
            <p>
 | 
			
		||||
                Por. komentarz z D. Lubasz [w:] Ochrona Danych Osobowych [red.] D. Lubasz, Warszawa
 | 
			
		||||
                2020 r., str. 81:
 | 
			
		||||
            </p>
 | 
			
		||||
            <p>
 | 
			
		||||
                <em>
 | 
			
		||||
                    Zidentyfikowaną osobą fizyczną jest osoba, której tożsamość jest ustalona -
 | 
			
		||||
                    bezpośrednio i natychmiast, czyli taka, którą bezpośrednio można wskazać,
 | 
			
		||||
                    wyodrębnić lub wyróżnić z określonej zbiorowości.{' '}
 | 
			
		||||
                    <strong>Nie musi to natomiast polegać na podaniu jej imienia nazwiska</strong>.
 | 
			
		||||
                    Konstatacja ta jest zwłaszcza istotna w środowisku cyfrowym, w którym
 | 
			
		||||
                    identyfikacja sprowadza się do oznaczenia danego użytkownika w celu wywierania
 | 
			
		||||
                    na niego określonego wpływu. (...) Możliwą do zidentyfikowania jest osoba,
 | 
			
		||||
                    której tożsamość dopiero administrator może ustalić -{' '}
 | 
			
		||||
                    <strong>niezależnie od tego, czy to zrobi, czy nie</strong>.
 | 
			
		||||
                </em>
 | 
			
		||||
            </p>
 | 
			
		||||
            <p>
 | 
			
		||||
                Podobnie za{' '}
 | 
			
		||||
                <em>
 | 
			
		||||
                    P. Litwiński [w:] Rozporządzenie UE w sprawie ochrony osób fizycznych w związku
 | 
			
		||||
                    z przetwarzaniem danych osobowych i w sprawie swobodnego przepływu takich
 | 
			
		||||
                    danych, Komentarz [red.] P. Litwiński, Warszawa 2018 r.
 | 
			
		||||
                </em>
 | 
			
		||||
                :
 | 
			
		||||
            </p>
 | 
			
		||||
            <p>
 | 
			
		||||
                <em>
 | 
			
		||||
                    Jak zwrócono uwagę w nauce prawa, identyfikacja osoby powinna być rozumiana jako
 | 
			
		||||
                    możliwość „fizycznego” wskazania tejże osoby, nie zaś jako ustalenie
 | 
			
		||||
                    podstawowych danych tej osoby (...). Analogicznie,{' '}
 | 
			
		||||
                    <em>identyfikacja osoby nie wymaga znajomości jej imienia lub nazwiska</em>,
 | 
			
		||||
                    wymaga natomiast znajomości pewnych unikalnych cech tej osoby, które odróżniają
 | 
			
		||||
                    ją od innych osób (...). W ten sam sposób należy więc rozumieć zwrot „można
 | 
			
		||||
                    zidentyfikować” - nie tylko jako możliwość odniesienia konkretnej informacji do
 | 
			
		||||
                    konkretnej osoby, lecz także jako możliwość wskazania tej osoby, rozumianego
 | 
			
		||||
                    jako faktyczne wyodrębnienie jej spośród innych osób.
 | 
			
		||||
                </em>
 | 
			
		||||
            </p>
 | 
			
		||||
        </>
 | 
			
		||||
    ),
 | 
			
		||||
    responsibility_for_third_parties: () => (
 | 
			
		||||
        <>
 | 
			
		||||
            <h2>Administrator strony ponosi odpowiedzialność za skrypty podmiotów trzecich</h2>
 | 
			
		||||
            <p>
 | 
			
		||||
                W wypadku, gdy ujawnienie czy dostęp do danych osobowych zostało dokonane przez
 | 
			
		||||
                skrypty podmiotów trzecich (np. Google, Facebook, itp), których autorem nie jest
 | 
			
		||||
                Administrator strony, Administrator wciąż jest odpowiedzialny za procesy
 | 
			
		||||
                przetwarzania danych osobowych, jakie realizują te skrypty - w myśl treści{' '}
 | 
			
		||||
                <a href="https://curia.europa.eu/juris/document/document.jsf?text=&docid=216555&pageIndex=0&doclang=PL&mode=lst&dir=&occ=first&part=1&cid=1254905">
 | 
			
		||||
                    wyroku TSUE w sprawie C-40/17
 | 
			
		||||
                </a>
 | 
			
		||||
                internetowy” i „numer identyfikacyjny” jako przykłady danych osobowych.
 | 
			
		||||
            </p>
 | 
			
		||||
        </>
 | 
			
		||||
    ),
 | 
			
		||||
 | 
			
		||||
@ -1,56 +0,0 @@
 | 
			
		||||
// good for diagnostic purposes
 | 
			
		||||
 | 
			
		||||
import { RequestCluster } from '../../request-cluster';
 | 
			
		||||
import { DataLocation } from '../../stolen-data-entry';
 | 
			
		||||
 | 
			
		||||
export type FakeRequestClusterData = {
 | 
			
		||||
    id: string;
 | 
			
		||||
    hasCookies: boolean;
 | 
			
		||||
    hasMarkedCookies: boolean;
 | 
			
		||||
    hasMarks: boolean;
 | 
			
		||||
    exposesOriginWhere: DataLocation[];
 | 
			
		||||
    exposesOrigin: boolean;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export function getFakeClusterData(
 | 
			
		||||
    clusters: Record<string, RequestCluster>
 | 
			
		||||
): Record<string, FakeRequestClusterData> {
 | 
			
		||||
    return Object.fromEntries(
 | 
			
		||||
        Object.entries(clusters).map(([key, cluster]) => [key, cluster.makeDataForFake()])
 | 
			
		||||
    );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function makeFakeClusters(
 | 
			
		||||
    fake_clusters_data: Record<string, FakeRequestClusterData>
 | 
			
		||||
): Record<string, FakeCluster> {
 | 
			
		||||
    return Object.fromEntries(
 | 
			
		||||
        Object.entries(fake_clusters_data).map(([key, cluster_data]) => [
 | 
			
		||||
            key,
 | 
			
		||||
            new FakeCluster(cluster_data),
 | 
			
		||||
        ])
 | 
			
		||||
    );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class FakeCluster extends RequestCluster {
 | 
			
		||||
    constructor(public data: FakeRequestClusterData) {
 | 
			
		||||
        super(data.id);
 | 
			
		||||
 | 
			
		||||
        for (const key of [
 | 
			
		||||
            'hasCookies',
 | 
			
		||||
            'hasMarkedCookies',
 | 
			
		||||
            'hasMarks',
 | 
			
		||||
            'exposesOriginWhere',
 | 
			
		||||
            'exposesOrigin',
 | 
			
		||||
        ]) {
 | 
			
		||||
            //@ts-ignore
 | 
			
		||||
            this[key] = () => {
 | 
			
		||||
                //@ts-ignore
 | 
			
		||||
                return this.data[key];
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    hasCookies() {
 | 
			
		||||
        return this.data.hasCookies;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -1,30 +1,26 @@
 | 
			
		||||
import { RequestCluster } from '../../request-cluster';
 | 
			
		||||
 | 
			
		||||
function generateHostPage(
 | 
			
		||||
    cluster: RequestCluster,
 | 
			
		||||
    host: string,
 | 
			
		||||
    index: number,
 | 
			
		||||
    all_clusters: RequestCluster[]
 | 
			
		||||
): { title: string; elements: any[]; visibleIf?: string } {
 | 
			
		||||
    function f(name: string, c = cluster) {
 | 
			
		||||
        return `${c.id.replace(/\./g, '_')}|${name}`;
 | 
			
		||||
    all_hosts: string[]
 | 
			
		||||
): { title: string; elements: any[]; visibleIf: string } {
 | 
			
		||||
    function f(name: string, h = host) {
 | 
			
		||||
        return `${h.replace(/\./g, '_')}|${name}`;
 | 
			
		||||
    }
 | 
			
		||||
    const previous_cluster: RequestCluster | null = index > 0 ? all_clusters[index - 1] : null;
 | 
			
		||||
    const previous_host: string | null = index > 0 ? all_hosts[index - 1] : null;
 | 
			
		||||
    function defaultValue(name: string) {
 | 
			
		||||
        if (!previous_cluster) {
 | 
			
		||||
        if (!previous_host) {
 | 
			
		||||
            return {};
 | 
			
		||||
        }
 | 
			
		||||
        return { defaultValueExpression: `{${f(name, previous_cluster)}}` };
 | 
			
		||||
        return { defaultValueExpression: `{${f(name, previous_host)}}` };
 | 
			
		||||
    }
 | 
			
		||||
    const domain = cluster.id;
 | 
			
		||||
    const danych = cluster.getDataTypeDescription();
 | 
			
		||||
    return {
 | 
			
		||||
        title: cluster.id,
 | 
			
		||||
        title: host,
 | 
			
		||||
        elements: [
 | 
			
		||||
            {
 | 
			
		||||
                type: 'radiogroup',
 | 
			
		||||
                name: f('present'),
 | 
			
		||||
                isRequired: true,
 | 
			
		||||
                title: `Strona udostępniła właścicielowi domeny ${domain} ${danych}. Cel takiego przetwarzania danych:`,
 | 
			
		||||
                title: `Cel ujawnienia danych właścicielowi domeny ${host}`,
 | 
			
		||||
                ...defaultValue('present'),
 | 
			
		||||
                visibleIf: '{popup_type} != "none"',
 | 
			
		||||
                choices: [
 | 
			
		||||
@ -60,10 +56,10 @@ function generateHostPage(
 | 
			
		||||
                    'present'
 | 
			
		||||
                )}} != "not_mentioned" and {${f('present')}} != "not_before_making_a_choice"`,
 | 
			
		||||
                choices: [
 | 
			
		||||
                    { value: 'consent', text: 'to zgoda (art. 6 ust. 1 lit. a RODO).' },
 | 
			
		||||
                    { value: 'consent', text: 'to zgoda.' },
 | 
			
		||||
                    {
 | 
			
		||||
                        value: 'legitimate_interest',
 | 
			
		||||
                        text: 'to uzasadniony interes (art. 6 ust. 1 lit. f RODO).',
 | 
			
		||||
                        text: 'to uzasadniony interes.',
 | 
			
		||||
                    },
 | 
			
		||||
                    { value: 'not_mentioned', text: 'nie jest wskazana nigdzie na stronie.' },
 | 
			
		||||
                ],
 | 
			
		||||
@ -80,7 +76,7 @@ function generateHostPage(
 | 
			
		||||
                choices: [
 | 
			
		||||
                    {
 | 
			
		||||
                        value: 'claims_consent_but_sends_before_consent',
 | 
			
		||||
                        text: `Strona wysłała {moje} dane do ${domain} zanim {wyraziłem} na to zgodę`,
 | 
			
		||||
                        text: `Strona wysłała {moje} dane do ${host} zanim {wyraziłem} na to zgodę`,
 | 
			
		||||
                    },
 | 
			
		||||
                    {
 | 
			
		||||
                        value: 'claims_consent_but_there_was_no_easy_refuse',
 | 
			
		||||
@ -115,21 +111,18 @@ function generateHostPage(
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                type: 'text',
 | 
			
		||||
                title: `Jak administrator opisał to, na czym polega uzasadniony interes w kontekście ${domain}?`,
 | 
			
		||||
                title: `Jak administrator opisał to, na czym polega uzasadniony interes w kontekście ${host}?`,
 | 
			
		||||
                name: f('legitimate_interest_description'),
 | 
			
		||||
                visibleIf: `{${f('legitimate_interest_activity_specified')}} = 'vague'`,
 | 
			
		||||
                placeholder: 'marketing',
 | 
			
		||||
                defaultValueExpression:
 | 
			
		||||
                    index == 0
 | 
			
		||||
                        ? 'marketing'
 | 
			
		||||
                        : `{${f(
 | 
			
		||||
                              'legitimate_interest_description',
 | 
			
		||||
                              previous_cluster || undefined
 | 
			
		||||
                          )}}`,
 | 
			
		||||
                        : `{${f('legitimate_interest_description', previous_host)}}`,
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                type: 'radiogroup',
 | 
			
		||||
                title: `Czy domena ${domain} należy do podmiotu spoza Europy (np. Google, Facebook)?`,
 | 
			
		||||
                title: `Czy domena ${host} należy do podmiotu spoza Europy (np. Google, Facebook)?`,
 | 
			
		||||
                name: f('outside_eu'),
 | 
			
		||||
                ...defaultValue('outside_eu'),
 | 
			
		||||
                visibleIf: `{${f('legitimate_interest_activity_specified')}} = "precise" or {${f(
 | 
			
		||||
@ -144,15 +137,13 @@ function generateHostPage(
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                type: 'radiogroup',
 | 
			
		||||
                title: `Czy w {Twojej} ocenie ujawnienie {Twoich} danych (${danych}) właścicielowi domeny ${domain} było konieczne do świadczenia zażądanej przez {Ciebie} usługi drogą elektroniczną?`,
 | 
			
		||||
                title: `Czy w {Twojej} ocenie wysłanie {Twoich} danych do właściciela domeny  ${host} było konieczne do świadczenia zażądanej przez {Ciebie} usługi drogą elektroniczną?`,
 | 
			
		||||
                name: f('was_processing_necessary'),
 | 
			
		||||
                isRequired: true,
 | 
			
		||||
                ...defaultValue('was_processing_necessary'),
 | 
			
		||||
                visibleIf: `{${f('legal_basis_type')}} = "legitimate_interest" or {${f(
 | 
			
		||||
                    'present'
 | 
			
		||||
                )}} = "not_mentioned" or {${f(
 | 
			
		||||
                    'present'
 | 
			
		||||
                )}} = "not_before_making_a_choice" or {popup_type} = "none"`,
 | 
			
		||||
                )}} = "not_mentioned" or {popup_type} = "none"`,
 | 
			
		||||
                choices: [
 | 
			
		||||
                    { value: 'yes', text: 'Tak, było konieczne' },
 | 
			
		||||
                    { value: 'no', text: 'Nie, nie było konieczne' },
 | 
			
		||||
@ -163,7 +154,7 @@ function generateHostPage(
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default function generateSurveyQuestions(clusters: RequestCluster[]) {
 | 
			
		||||
export default function generateSurveyQuestions(hosts: string[]) {
 | 
			
		||||
    return {
 | 
			
		||||
        showQuestionNumbers: 'off',
 | 
			
		||||
        showProgressBar: 'top',
 | 
			
		||||
@ -174,60 +165,28 @@ export default function generateSurveyQuestions(clusters: RequestCluster[]) {
 | 
			
		||||
        clearInvisibleValues: 'onHidden',
 | 
			
		||||
        pages: [
 | 
			
		||||
            {
 | 
			
		||||
                title: 'Dodatkowe pytania',
 | 
			
		||||
                title: 'Tytuł - co to za ankieta?',
 | 
			
		||||
                elements: [
 | 
			
		||||
                    {
 | 
			
		||||
                        type: 'html',
 | 
			
		||||
                        name: 'intro',
 | 
			
		||||
                        html: /* HTML */ `<p>
 | 
			
		||||
                            Analiza ruchu sieciowego generowanego przez stronę została zakończona.
 | 
			
		||||
                            Teraz, aby lepiej oszacować, gdzie są potencjalne obszary robocze pod
 | 
			
		||||
                            względem zgodności z RODO, możesz udzielić odpowiedzi na pytania
 | 
			
		||||
                            dotyczące funkcjonowania strony. Wtyczka wtedy wygeneruje raport lub
 | 
			
		||||
                            treść maila, którą możesz wysłać do administratora strony.
 | 
			
		||||
                        </p>`,
 | 
			
		||||
                        html: '<p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Ab, odit dicta at aut esse culpa eveniet iure odio voluptates veniam sit. Libero explicabo, perspiciatis ad expedita officiis inventore impedit ducimus!</p>',
 | 
			
		||||
                    },
 | 
			
		||||
                ],
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                title: 'Kontekst analizy',
 | 
			
		||||
                title: 'Zaimki',
 | 
			
		||||
                elements: [
 | 
			
		||||
                    {
 | 
			
		||||
                        type: 'radiogroup',
 | 
			
		||||
                        name: 'user_role',
 | 
			
		||||
                        title: 'Jestem:',
 | 
			
		||||
                        isRequired: true,
 | 
			
		||||
                        choices: [
 | 
			
		||||
                            { value: 'user', text: 'użytkownikiem strony' },
 | 
			
		||||
                            { value: 'admin', text: 'administratorem strony' },
 | 
			
		||||
                        ],
 | 
			
		||||
                    },
 | 
			
		||||
                    {
 | 
			
		||||
                        type: 'radiogroup',
 | 
			
		||||
                        name: 'email_type',
 | 
			
		||||
                        title: 'Chcę:',
 | 
			
		||||
                        visibleIf: "{user_role} = 'user'",
 | 
			
		||||
                        choices: [
 | 
			
		||||
                            {
 | 
			
		||||
                                value: 'polite_information',
 | 
			
		||||
                                text: 'uprzejmie poinformować administratora strony o potencjalnych problemach ze zgodnością z RODO na jego stronie',
 | 
			
		||||
                            },
 | 
			
		||||
                            {
 | 
			
		||||
                                value: 'official_request',
 | 
			
		||||
                                text: 'wysłać formalne zapytanie do administratora strony, na które ma obowiązek odpowiedzieć. Jeżeli administrator nie odpowie na takie zapytanie, może to być podstawą złożenia skargi do UODO',
 | 
			
		||||
                            },
 | 
			
		||||
                        ],
 | 
			
		||||
                    },
 | 
			
		||||
                    {
 | 
			
		||||
                        type: 'radiogroup',
 | 
			
		||||
                        name: 'zaimek',
 | 
			
		||||
                        title: 'Forma czasownika, jaka będzie użyta w raporcie:',
 | 
			
		||||
                        title: 'Forma czasownika:',
 | 
			
		||||
                        isRequired: true,
 | 
			
		||||
                        choices: [
 | 
			
		||||
                            { value: 0, text: 'wysłałem' },
 | 
			
		||||
                            { value: 1, text: 'wysłałam' },
 | 
			
		||||
                            { value: 2, text: 'wysłałom' },
 | 
			
		||||
                            { value: 3, text: 'wysłaliśmy' },
 | 
			
		||||
                            { value: 0, text: 'Wysłałem' },
 | 
			
		||||
                            { value: 1, text: 'Wysłałam' },
 | 
			
		||||
                            { value: 2, text: 'Wysłałom' },
 | 
			
		||||
                            { value: 3, text: 'Wysłaliśmy' },
 | 
			
		||||
                        ],
 | 
			
		||||
                    },
 | 
			
		||||
                ],
 | 
			
		||||
@ -343,7 +302,7 @@ export default function generateSurveyQuestions(clusters: RequestCluster[]) {
 | 
			
		||||
                            },
 | 
			
		||||
                            {
 | 
			
		||||
                                value: 'yes',
 | 
			
		||||
                                text: 'Nie. {Muszę} wykonać więcej czynności (np. kliknięć) aby odmówić wszystkich zgód, albo opcja niewyrażenia zgody jest mało widoczna.',
 | 
			
		||||
                                text: 'Nie. {Muszę} wykonać więcej czynności aby odmówić wszystkich zgód, albo opcja niewyrażenia zgody jest mało widoczna.',
 | 
			
		||||
                            },
 | 
			
		||||
                        ],
 | 
			
		||||
                    },
 | 
			
		||||
@ -434,7 +393,7 @@ export default function generateSurveyQuestions(clusters: RequestCluster[]) {
 | 
			
		||||
                    },
 | 
			
		||||
                ],
 | 
			
		||||
            },
 | 
			
		||||
            ...clusters.map(generateHostPage),
 | 
			
		||||
            ...hosts.map(generateHostPage),
 | 
			
		||||
        ],
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -16,9 +16,7 @@ function handleNewFile(
 | 
			
		||||
        );
 | 
			
		||||
        setFiltered(new Blob([JSON.stringify(content)], { type: 'application/json' }));
 | 
			
		||||
    });
 | 
			
		||||
    const file = element?.files?.[0];
 | 
			
		||||
    if (!file) throw new Error('file empty?');
 | 
			
		||||
    reader.readAsText(file);
 | 
			
		||||
    reader.readAsText(element.files[0]);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function generateFakeHAR(entries: StolenDataEntry[]) {
 | 
			
		||||
@ -82,11 +80,8 @@ export default function HARConverter({ entries }: { entries: StolenDataEntry[] }
 | 
			
		||||
                type="file"
 | 
			
		||||
                accept=".har"
 | 
			
		||||
                onChange={(e) => {
 | 
			
		||||
                    const file = e.target?.files?.[0];
 | 
			
		||||
                    if (file) {
 | 
			
		||||
                        setFilename(file.name);
 | 
			
		||||
                        handleNewFile(e.target, entries, setFiltered);
 | 
			
		||||
                    }
 | 
			
		||||
                    setFilename(e.target.files[0].name);
 | 
			
		||||
                    handleNewFile(e.target, entries, setFiltered);
 | 
			
		||||
                }}
 | 
			
		||||
            />
 | 
			
		||||
            {(filtered && (
 | 
			
		||||
@ -105,7 +100,7 @@ export default function HARConverter({ entries }: { entries: StolenDataEntry[] }
 | 
			
		||||
                    })
 | 
			
		||||
                )}
 | 
			
		||||
                download={`${getshorthost(
 | 
			
		||||
                    entries[0].request.origin
 | 
			
		||||
                    entries[0].request.originalURL
 | 
			
		||||
                )}-${new Date().toJSON()}-trimmed.har`}
 | 
			
		||||
            >
 | 
			
		||||
                Pobierz "zredukowany" HAR
 | 
			
		||||
 | 
			
		||||
@ -42,8 +42,6 @@ function parseHostAnswers(
 | 
			
		||||
 | 
			
		||||
export function parseAnswers({
 | 
			
		||||
    zaimek,
 | 
			
		||||
    user_role,
 | 
			
		||||
    email_type,
 | 
			
		||||
    is_incognito_different,
 | 
			
		||||
    policy_readable,
 | 
			
		||||
    popup_type,
 | 
			
		||||
@ -52,13 +50,10 @@ export function parseAnswers({
 | 
			
		||||
    mentions_passive_consent,
 | 
			
		||||
    rejection_is_hard,
 | 
			
		||||
    administrator_identity_available_before_choice,
 | 
			
		||||
    popup_action,
 | 
			
		||||
    ...rest
 | 
			
		||||
}: RawAnswers): ParsedAnswers {
 | 
			
		||||
    return {
 | 
			
		||||
        zaimek,
 | 
			
		||||
        user_role,
 | 
			
		||||
        email_type,
 | 
			
		||||
        is_incognito_different,
 | 
			
		||||
        policy_readable,
 | 
			
		||||
        popup_type,
 | 
			
		||||
@ -67,7 +62,6 @@ export function parseAnswers({
 | 
			
		||||
        mentions_passive_consent,
 | 
			
		||||
        rejection_is_hard,
 | 
			
		||||
        administrator_identity_available_before_choice,
 | 
			
		||||
        popup_action,
 | 
			
		||||
        hosts: parseHostAnswers(rest),
 | 
			
		||||
    } as ParsedAnswers;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -3,49 +3,27 @@ import { v } from '../verbs';
 | 
			
		||||
import { Problem } from './problem';
 | 
			
		||||
 | 
			
		||||
export default class NoInformationAtAllProblem extends Problem {
 | 
			
		||||
    qualifies() {
 | 
			
		||||
        return this.answers.popup_type === 'none';
 | 
			
		||||
    }
 | 
			
		||||
    getEmailContent({ mode, tone }: { mode: 'email' | 'report'; tone: 'official' | 'polite' }) {
 | 
			
		||||
    getEmailContent() {
 | 
			
		||||
        const _ = (word: string) => v(word, this.answers.zaimek);
 | 
			
		||||
        return (
 | 
			
		||||
            <>
 | 
			
		||||
                <h2>Brak informacji na temat przetwarzania danych osobowych</h2>
 | 
			
		||||
                {mode == 'email' ? (
 | 
			
		||||
                    tone == 'official' ? (
 | 
			
		||||
                        <p>
 | 
			
		||||
                            {_('Moje')} dane osobowe zostały ujawnione podmiotom, które są
 | 
			
		||||
                            właścicielami domen:
 | 
			
		||||
                        </p>
 | 
			
		||||
                    ) : (
 | 
			
		||||
                        <p>
 | 
			
		||||
                            Państwa strona ujawnia dane użytkowników podmiotom, które są
 | 
			
		||||
                            właścicielami następujących domen:
 | 
			
		||||
                        </p>
 | 
			
		||||
                    )
 | 
			
		||||
                ) : (
 | 
			
		||||
                    <p>
 | 
			
		||||
                        Poprzez skrypty osadzone na stronie dane osobowe użytkownika końcowego są
 | 
			
		||||
                        przekazywane podmiotom, którzy są właścicielami następujacych domen:
 | 
			
		||||
                    </p>
 | 
			
		||||
                )}
 | 
			
		||||
                <p>
 | 
			
		||||
                    {_('Moje')} dane osobowe zostały ujawnione podmiotom, które są właścicielami
 | 
			
		||||
                    domen:
 | 
			
		||||
                </p>
 | 
			
		||||
                {this.getRangeDescription()}
 | 
			
		||||
                <p>
 | 
			
		||||
                    Na stronie brakuje jednak jakichkolwiek informacji o tym, jakie są cele
 | 
			
		||||
                    przetwarzania takich danych oraz jakie są podstawy prawne takiego przetwarzania.
 | 
			
		||||
                </p>
 | 
			
		||||
                {mode == 'email' ? (
 | 
			
		||||
                    <p>Zwracam się zatem do Państwa z następującymi pytaniami:</p>
 | 
			
		||||
                ) : (
 | 
			
		||||
                    <p>Na stronie należy zawrzeć odpowiedzi na następujące pytania:</p>
 | 
			
		||||
                )}
 | 
			
		||||
                <p>Zwracam się zatem do Państwa z następującymi pytaniami:</p>
 | 
			
		||||
                <ul>
 | 
			
		||||
                    <li>Jaka jest tożsamość właścicieli tych domen?</li>
 | 
			
		||||
                    <li>Jaki jest cel takiego przetwarzania danych przez Państwa stronę?</li>
 | 
			
		||||
                    <li>
 | 
			
		||||
                        Jaka jest podstawa prawna takiego przetwarzania{' '}
 | 
			
		||||
                        {mode == 'email' ? _('moich') : ''} danych osobowych
 | 
			
		||||
                        {mode == 'report' ? 'użytkowników końcowych' : ''} przez Państwa stronę?
 | 
			
		||||
                        Jaka jest podstawa prawna takiego przetwarzania moich danych osobowych przez
 | 
			
		||||
                        Państwa stronę?
 | 
			
		||||
                    </li>
 | 
			
		||||
                </ul>
 | 
			
		||||
            </>
 | 
			
		||||
@ -56,11 +34,11 @@ export default class NoInformationAtAllProblem extends Problem {
 | 
			
		||||
 | 
			
		||||
        if (
 | 
			
		||||
            this.getMarkedClusters().some((cluster) => {
 | 
			
		||||
                console.log(cluster);
 | 
			
		||||
                return cluster.hasMarkedCookies();
 | 
			
		||||
            })
 | 
			
		||||
        ) {
 | 
			
		||||
            explainers.push('cookies_are_pii');
 | 
			
		||||
            explainers.push('responsibility_for_third_parties');
 | 
			
		||||
        }
 | 
			
		||||
        return explainers;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -4,6 +4,7 @@ import { ParsedAnswers } from '../parse-answers';
 | 
			
		||||
 | 
			
		||||
function formatRange(cluster: RequestCluster) {
 | 
			
		||||
    const parts = [] as string[];
 | 
			
		||||
    console.log(cluster);
 | 
			
		||||
    if (cluster.hasMarkedCookies()) {
 | 
			
		||||
        parts.push('mojego identyfikatora internetowego pozyskanego z Cookie');
 | 
			
		||||
    }
 | 
			
		||||
@ -16,12 +17,8 @@ function formatRange(cluster: RequestCluster) {
 | 
			
		||||
export abstract class Problem {
 | 
			
		||||
    constructor(public answers: ParsedAnswers, public clusters: Record<string, RequestCluster>) {}
 | 
			
		||||
 | 
			
		||||
    abstract getEmailContent(props: {
 | 
			
		||||
        mode: 'email' | 'report';
 | 
			
		||||
        tone: 'polite' | 'official';
 | 
			
		||||
    }): JSX.Element;
 | 
			
		||||
    abstract getEmailContent(): JSX.Element;
 | 
			
		||||
    abstract getNecessaryExplainers(): ExplainerKey[];
 | 
			
		||||
    abstract qualifies(): boolean;
 | 
			
		||||
 | 
			
		||||
    getMarkedClusters() {
 | 
			
		||||
        return Object.values(this.clusters).filter((c) => c.hasMarks());
 | 
			
		||||
 | 
			
		||||
@ -1,90 +0,0 @@
 | 
			
		||||
import { RequestCluster } from '../../../request-cluster';
 | 
			
		||||
import { ExplainerKey } from '../explainers';
 | 
			
		||||
import { v } from '../verbs';
 | 
			
		||||
import { Problem } from './problem';
 | 
			
		||||
 | 
			
		||||
export class TransferOutsideEU extends Problem {
 | 
			
		||||
    getNecessaryExplainers(): ExplainerKey[] {
 | 
			
		||||
        const has_cookies = this.getRelatedClusters().some((cluster) => cluster.hasCookies());
 | 
			
		||||
        return has_cookies ? ['cookies_are_pii'] : [];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    qualifies(): boolean {
 | 
			
		||||
        return Object.values(this.answers.hosts).some(
 | 
			
		||||
            (hostAnswers) => hostAnswers.outside_eu == 'yes'
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    getRelatedClusters(): RequestCluster[] {
 | 
			
		||||
        return Object.entries(this.answers.hosts)
 | 
			
		||||
            .filter(([_, hostAnswers]) => hostAnswers.outside_eu == 'yes')
 | 
			
		||||
            .map(([id]) => this.clusters[id]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    getEmailContent({ mode }: { mode: 'email' | 'report'; tone: 'official' | 'polite' }) {
 | 
			
		||||
        const clusters = this.getRelatedClusters();
 | 
			
		||||
        const _ = (key: string) => v(key, this.answers.zaimek);
 | 
			
		||||
        return (
 | 
			
		||||
            <>
 | 
			
		||||
                <h2>Transfer danych osobowych poza Europejski Obszar Gospodarczy</h2>
 | 
			
		||||
                {mode == 'email' ? (
 | 
			
		||||
                    <p>
 | 
			
		||||
                        Państwa strona przetworzyła {_('moje')} dane osobowe poprzez przesłanie
 | 
			
		||||
                        danych do:
 | 
			
		||||
                    </p>
 | 
			
		||||
                ) : (
 | 
			
		||||
                    <p>
 | 
			
		||||
                        Strona przetwarza dane osobowe użytkowników końcowych poprzez przesłanie
 | 
			
		||||
                        przekazanie ich do:
 | 
			
		||||
                    </p>
 | 
			
		||||
                )}
 | 
			
		||||
                <ul>
 | 
			
		||||
                    {clusters.map((cluster) => (
 | 
			
		||||
                        <li key={cluster.id}>
 | 
			
		||||
                            właściciela domeny <strong>{cluster.id}</strong>: (w zakresie:{' '}
 | 
			
		||||
                            {cluster.getDataTypeDescription(mode == 'email' ? 'mojej' : '')});
 | 
			
		||||
                        </li>
 | 
			
		||||
                    ))}
 | 
			
		||||
                </ul>
 | 
			
		||||
                {mode == 'email' ? (
 | 
			
		||||
                    <p>
 | 
			
		||||
                        Według {_('mojej')} najlepszej wiedzy, każdy z tych podmiotów utrzymuje
 | 
			
		||||
                        swoje serwery poza Europejskim Obszarem Gospodarczym. Zatem Państwa strona
 | 
			
		||||
                        przesłała
 | 
			
		||||
                        {_('moje')} dane osobowe poza EOG. Jeżeli tak jest, to takie przetwarzanie
 | 
			
		||||
                        danych jest niezgodne z prawem, gdyż dane trafiają do krajów, które nie
 | 
			
		||||
                        gwarantują ochrony danych w stopniu, jakiego wymaga RODO, a tzw. „Tarcza
 | 
			
		||||
                        Prywatności” została unieważniona w 2020r. Zob.{' '}
 | 
			
		||||
                        <a href="https://panoptykon.org/noyb-skargi-schrems-ii">
 | 
			
		||||
                            artykuł Fundacji Panoptykon w tej sprawie
 | 
			
		||||
                        </a>
 | 
			
		||||
                        .
 | 
			
		||||
                    </p>
 | 
			
		||||
                ) : (
 | 
			
		||||
                    <p>
 | 
			
		||||
                        Te podmioty utrzymują swoje centra danych poza Europejskim Obszarem
 | 
			
		||||
                        Gospodarczym. Jako, że tzw. „Tarcza Prywatności” zostałą unieważniona w
 | 
			
		||||
                        2020r., nie można przesyłać danych osobowych obywateli Unii Europejskiej do
 | 
			
		||||
                        krajów, które nie zapewniają ochrony danych o sile odpowiadającej RODO.
 | 
			
		||||
                        Przykłądem kraju, do którego nie można przekazywać danych osobowych
 | 
			
		||||
                        obywateli UE są Stany Zjednoczone.
 | 
			
		||||
                    </p>
 | 
			
		||||
                )}
 | 
			
		||||
                {mode == 'email' ? (
 | 
			
		||||
                    <p>
 | 
			
		||||
                        {_('Zwracam')} się zatem do Państwa z pytaniem:{' '}
 | 
			
		||||
                        <strong>
 | 
			
		||||
                            czy wyżej wymienione podmioty, którym Państwa strona ujawniła moje dane
 | 
			
		||||
                            osobowe, przechowują moje dane poza EOG?
 | 
			
		||||
                        </strong>
 | 
			
		||||
                    </p>
 | 
			
		||||
                ) : (
 | 
			
		||||
                    <p>
 | 
			
		||||
                        Zaleca się rezygnację z korzystania z usług firm, które przetwarzają dane
 | 
			
		||||
                        osobowe użytkowników, a których centra danych znajdują się poza EOG.
 | 
			
		||||
                    </p>
 | 
			
		||||
                )}
 | 
			
		||||
            </>
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -1,55 +0,0 @@
 | 
			
		||||
import { ExplainerKey } from '../explainers';
 | 
			
		||||
import { v } from '../verbs';
 | 
			
		||||
import { Problem } from './problem';
 | 
			
		||||
 | 
			
		||||
export class UnknownIdentity extends Problem {
 | 
			
		||||
    getNecessaryExplainers(): ExplainerKey[] {
 | 
			
		||||
        return ['responsibility_for_third_parties'];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    qualifies(): boolean {
 | 
			
		||||
        return this.answers.administrator_identity_available_before_choice == 'no';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    getEmailContent({ mode, tone }: { mode: 'email' | 'report'; tone: 'official' | 'polite' }) {
 | 
			
		||||
        const _ = (key: string) => v(key, this.answers.zaimek);
 | 
			
		||||
        return (
 | 
			
		||||
            <>
 | 
			
		||||
                <h2>Tożsamość administratora</h2>
 | 
			
		||||
                {mode == 'email' ? (
 | 
			
		||||
                    <p>
 | 
			
		||||
                        Na Państwa stronie nie {_('znalazłem')} sposobu na poznanie tożsamości
 | 
			
		||||
                        administratora strony <strong>przed</strong> podjęciem wyboru dotyczącego
 | 
			
		||||
                        przetwarzania danych mnie dotyczących.
 | 
			
		||||
                    </p>
 | 
			
		||||
                ) : (
 | 
			
		||||
                    <p>Na stronie brakuje sposobu na poznanie tożsamości administratora strony.</p>
 | 
			
		||||
                )}
 | 
			
		||||
                <p>
 | 
			
		||||
                    Zgodnie z treścią Art. 13 RODO, jeżeli dane osobowe osoby, której dane dotyczą,
 | 
			
		||||
                    zbierane są od tej osoby, administrator podczas pozyskiwania danych osobowych
 | 
			
		||||
                    musi podać jej swoją tożsamość i dane kontaktowe.
 | 
			
		||||
                </p>
 | 
			
		||||
                {mode == 'email' ? (
 | 
			
		||||
                    tone == 'official' ? (
 | 
			
		||||
                        <p>
 | 
			
		||||
                            Zwracam się zatem z pytaniem:{' '}
 | 
			
		||||
                            <strong>jaka jest tożsamość administratora tej strony?</strong>
 | 
			
		||||
                        </p>
 | 
			
		||||
                    ) : (
 | 
			
		||||
                        <p>
 | 
			
		||||
                            Apeluję o dodanie do Państwa strony informacji o tym, kto (np. pełna
 | 
			
		||||
                            nazwa firmy + NIP oraz dane kontaktowe) jest administratorem danych
 | 
			
		||||
                            osobowych przetwarzanych przez tę stronę.
 | 
			
		||||
                        </p>
 | 
			
		||||
                    )
 | 
			
		||||
                ) : (
 | 
			
		||||
                    <p>
 | 
			
		||||
                        Zalecane jest dodanie informacji o administratorze strony (pełna nazwa firmy
 | 
			
		||||
                        + NIP i dane kontaktowe) w łatwo dostępnym miejscu na stronie.
 | 
			
		||||
                    </p>
 | 
			
		||||
                )}
 | 
			
		||||
            </>
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -1,121 +0,0 @@
 | 
			
		||||
import { RequestCluster } from '../../../request-cluster';
 | 
			
		||||
import { ExplainerKey } from '../explainers';
 | 
			
		||||
import { ParsedHostAnswers } from '../parse-answers';
 | 
			
		||||
import { v } from '../verbs';
 | 
			
		||||
import { Problem } from './problem';
 | 
			
		||||
 | 
			
		||||
const testCluster: (cluster: RequestCluster, answers: ParsedHostAnswers | undefined) => boolean = (
 | 
			
		||||
    cluster,
 | 
			
		||||
    hostAnswers
 | 
			
		||||
) => {
 | 
			
		||||
    if (!hostAnswers) {
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
    if (cluster.hasMarkedCookies()) {
 | 
			
		||||
        /* if it has cookies, it will be picked up by the UnlawfulCookieAccess problem, and that one
 | 
			
		||||
        is pretty detailed, so no need to mention it here. */
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
    return hostAnswers.legal_basis_type == 'not_mentioned';
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export class UnknownLegalBasis extends Problem {
 | 
			
		||||
    getNecessaryExplainers(): ExplainerKey[] {
 | 
			
		||||
        const has_cookies = this.getRelatedClusters().some((cluster) => cluster.hasCookies());
 | 
			
		||||
        return [
 | 
			
		||||
            'responsibility_for_third_parties',
 | 
			
		||||
            ...(has_cookies ? ['cookies_are_pii' as ExplainerKey] : []),
 | 
			
		||||
        ];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    qualifies(): boolean {
 | 
			
		||||
        return Object.values(this.clusters).some((cluster) =>
 | 
			
		||||
            testCluster(cluster, this.answers.hosts[cluster.id])
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    getRelatedClusters() {
 | 
			
		||||
        return Object.values(this.clusters).filter((cluster) =>
 | 
			
		||||
            testCluster(cluster, this.answers.hosts[cluster.id])
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    getEmailContent({ mode, tone }: { mode: 'email' | 'report'; tone: 'official' | 'polite' }) {
 | 
			
		||||
        const clusters = this.getRelatedClusters();
 | 
			
		||||
        const _ = (key: string) => v(key, this.answers.zaimek);
 | 
			
		||||
        return (
 | 
			
		||||
            <>
 | 
			
		||||
                <h2>Przetwarzanie danych osobowych bez podania podstawy prawnej</h2>
 | 
			
		||||
                {mode == 'email' ? (
 | 
			
		||||
                    <p>Państwa strona przetworzyła {_('moje')} dane osobowe poprzez ujawnienie:</p>
 | 
			
		||||
                ) : (
 | 
			
		||||
                    <p>Państwa strona przetwarza dane osobowe użytkowników poprzez ujawnienie</p>
 | 
			
		||||
                )}
 | 
			
		||||
                <ul>
 | 
			
		||||
                    {clusters.map((cluster) => (
 | 
			
		||||
                        <li key={cluster.id}>
 | 
			
		||||
                            właścicielowi domeny <strong>{cluster.id}</strong>:{' '}
 | 
			
		||||
                            {cluster.getDataTypeDescription(mode == 'email' ? 'mojej' : '')}
 | 
			
		||||
                        </li>
 | 
			
		||||
                    ))}
 | 
			
		||||
                </ul>
 | 
			
		||||
                {mode == 'email' ? (
 | 
			
		||||
                    <p>
 | 
			
		||||
                        {_('Moja')} historia przeglądania stanowi {_('moje')} dane osobowe. Zgodnie
 | 
			
		||||
                        z treścią Artykułu 13 p. 1 lit. c){' '}
 | 
			
		||||
                        <a href="https://eur-lex.europa.eu/legal-content/PL/TXT/HTML/?uri=CELEX:32016R0679&qid=1632163985520&from=PL#d1e1822-1-1">
 | 
			
		||||
                            RODO
 | 
			
		||||
                        </a>
 | 
			
		||||
                        , aby przetwarzać dane osobowe, trzeba poinformować osobę, której dane
 | 
			
		||||
                        dotyczą, o tym, jaka jest podstawa prawna takiego przetwarzania danych.
 | 
			
		||||
                    </p>
 | 
			
		||||
                ) : (
 | 
			
		||||
                    <p>
 | 
			
		||||
                        Na stronie nie znajdują się informacje o tym, jaka jest podstawa prawna
 | 
			
		||||
                        takiego przetwarzania danych osobowych, jakimi jest część historii
 | 
			
		||||
                        przeglądania. Zgodnie z treścią Artykułu 13. p. 1 lit. c) RODO, aby
 | 
			
		||||
                        przetwarzać dane osobowe, trzeba poinformować osobę, której dane dotyczą, o
 | 
			
		||||
                        tym, jaka jest podstawa prawna takiego przetwarzania danych.
 | 
			
		||||
                    </p>
 | 
			
		||||
                )}
 | 
			
		||||
                {mode == 'email' ? (
 | 
			
		||||
                    tone == 'official' ? (
 | 
			
		||||
                        <p>
 | 
			
		||||
                            Zwracam się zatem z pytaniem:{' '}
 | 
			
		||||
                            <strong>
 | 
			
		||||
                                jakie były podstawy prawne ujawnienia moich danych każdemu z wyżej
 | 
			
		||||
                                wymienionych podmiotów przez Państwa stronę?
 | 
			
		||||
                            </strong>
 | 
			
		||||
                        </p>
 | 
			
		||||
                    ) : (
 | 
			
		||||
                        <p>
 | 
			
		||||
                            Dodanie do Państwa strony informacji o tym, jakie są podstawy prawne (w
 | 
			
		||||
                            znaczeniu Art. 6 pkt. 1 RODO) dla każdego z tych procesów przetwarzania
 | 
			
		||||
                            miałoby pozytywny wpływ na przejrzystość informacji dla użytkowników
 | 
			
		||||
                            końcowych, jak i na zgodność strony z obowiązującymi przepisami.
 | 
			
		||||
                        </p>
 | 
			
		||||
                    )
 | 
			
		||||
                ) : (
 | 
			
		||||
                    <>
 | 
			
		||||
                        <p>Możliwe działania:</p>
 | 
			
		||||
                        <ul>
 | 
			
		||||
                            <li>rezygnacja z niektórych skryptów śledzących;</li>
 | 
			
		||||
                            <li>
 | 
			
		||||
                                przeniesienie assetów z CDN-a na samohostowanie (przy korzystaniu z
 | 
			
		||||
                                HTTP2 to może dać zwiększoną wydajność wzgłędem CDN);
 | 
			
		||||
                            </li>
 | 
			
		||||
                            <li>
 | 
			
		||||
                                konfiguracja nagłówka{' '}
 | 
			
		||||
                                <a href="https://developer.mozilla.org/pl/docs/Web/HTTP/Headers/Referrer-Policy">
 | 
			
		||||
                                    Referrer-Policy
 | 
			
		||||
                                </a>{' '}
 | 
			
		||||
                                tak, aby nie ujawniać historii przeglądania właścicielom zasobów z
 | 
			
		||||
                                domen podmiotów trzecich.
 | 
			
		||||
                            </li>
 | 
			
		||||
                        </ul>
 | 
			
		||||
                    </>
 | 
			
		||||
                )}
 | 
			
		||||
            </>
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -1,114 +0,0 @@
 | 
			
		||||
import { RequestCluster } from '../../../request-cluster';
 | 
			
		||||
import { dataLocationToText, wordlist } from '../../../util';
 | 
			
		||||
import { ExplainerKey } from '../explainers';
 | 
			
		||||
import { v } from '../verbs';
 | 
			
		||||
import { Problem } from './problem';
 | 
			
		||||
 | 
			
		||||
export class UnknownPurposes extends Problem {
 | 
			
		||||
    getNecessaryExplainers(): ExplainerKey[] {
 | 
			
		||||
        const has_cookies = this.getAffectedClusters().some((cluster) => cluster.hasCookies());
 | 
			
		||||
        if (has_cookies) {
 | 
			
		||||
            return ['cookies_are_pii'];
 | 
			
		||||
        } else {
 | 
			
		||||
            return [];
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    isHostAffected(host: string) {
 | 
			
		||||
        const answers = this.answers.hosts[host];
 | 
			
		||||
        if (!answers) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
        return (
 | 
			
		||||
            ['not_mentioned', 'not_before_making_a_choice'].includes(answers.present) &&
 | 
			
		||||
            ['no', 'not_sure'].includes(answers.was_processing_necessary) &&
 | 
			
		||||
            (this.clusters[host].hasCookies() || this.clusters[host].exposesOrigin())
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    qualifies(): boolean {
 | 
			
		||||
        return Object.keys(this.answers.hosts).some((host) => this.isHostAffected(host));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    getAffectedClusters(): RequestCluster[] {
 | 
			
		||||
        return Object.keys(this.answers.hosts)
 | 
			
		||||
            .filter((host) => this.isHostAffected(host))
 | 
			
		||||
            .map((host) => this.clusters[host]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    getEmailContent({ mode, tone }: { mode: 'email' | 'report'; tone: 'official' | 'polite' }) {
 | 
			
		||||
        const _ = (key: string) => v(key, this.answers.zaimek);
 | 
			
		||||
        const affected_clusters = this.getAffectedClusters();
 | 
			
		||||
        const has_history = affected_clusters.some((cluster) => cluster.exposesOrigin());
 | 
			
		||||
        const has_cookies = affected_clusters.some((cluster) => cluster.hasCookies());
 | 
			
		||||
 | 
			
		||||
        return (
 | 
			
		||||
            <>
 | 
			
		||||
                <h2>Cele przetwarzania danych</h2>
 | 
			
		||||
                <p>
 | 
			
		||||
                    Państwa strona{' '}
 | 
			
		||||
                    {mode == 'email'
 | 
			
		||||
                        ? `ujawniła dane ${_('mnie')} dotyczące`
 | 
			
		||||
                        : 'ujawnia dane dotyczące użytkowników'}{' '}
 | 
			
		||||
                    w zakresie{' '}
 | 
			
		||||
                    {wordlist([
 | 
			
		||||
                        ...(has_cookies ? ['treści plików cookies'] : []),
 | 
			
		||||
                        ...(has_history
 | 
			
		||||
                            ? [
 | 
			
		||||
                                  mode === 'email'
 | 
			
		||||
                                      ? `części ${_('mojej')} historii przeglądania`
 | 
			
		||||
                                      : `części historii przeglądania`,
 | 
			
		||||
                              ]
 | 
			
		||||
                            : []),
 | 
			
		||||
                    ])}{' '}
 | 
			
		||||
                    podmiotom, które są właścicielami nastepujących domen:
 | 
			
		||||
                </p>
 | 
			
		||||
                <ul>
 | 
			
		||||
                    {affected_clusters.map((cluster, index) => {
 | 
			
		||||
                        const locations = cluster.exposesOriginWhere();
 | 
			
		||||
                        return (
 | 
			
		||||
                            <li>
 | 
			
		||||
                                <strong>{cluster.id}</strong>:{' '}
 | 
			
		||||
                                {wordlist([
 | 
			
		||||
                                    ...(cluster.hasCookies() ? ['treść plików cookies'] : []),
 | 
			
		||||
                                    ...(cluster.exposesOrigin()
 | 
			
		||||
                                        ? [
 | 
			
		||||
                                              (mode === 'email'
 | 
			
		||||
                                                  ? `część ${_('mojej')} historii przeglądania`
 | 
			
		||||
                                                  : `część historii przeglądania użytkownika`) +
 | 
			
		||||
                                                  ' (' +
 | 
			
		||||
                                                  wordlist(
 | 
			
		||||
                                                      locations.map((l) => dataLocationToText(l))
 | 
			
		||||
                                                  ) +
 | 
			
		||||
                                                  ')',
 | 
			
		||||
                                          ]
 | 
			
		||||
                                        : []),
 | 
			
		||||
                                ])}
 | 
			
		||||
                                {index === affected_clusters.length - 1 ? '.' : ';'}
 | 
			
		||||
                            </li>
 | 
			
		||||
                        );
 | 
			
		||||
                    })}
 | 
			
		||||
                </ul>
 | 
			
		||||
                {mode === 'email' ? (
 | 
			
		||||
                    tone === 'official' ? (
 | 
			
		||||
                        <p>
 | 
			
		||||
                            Proszę o wskazanie, jakie są cele takiego przetwarzania danych, które
 | 
			
		||||
                            mnie dotyczą.
 | 
			
		||||
                        </p>
 | 
			
		||||
                    ) : (
 | 
			
		||||
                        <p>
 | 
			
		||||
                            Apeluję o umieszczenie informacji na temat na Państwa stronie, aby jej
 | 
			
		||||
                            użytkownicy mogli podejmować w pełni świadome wybory dotyczące
 | 
			
		||||
                            przetwarzania danych ich dotyczących.
 | 
			
		||||
                        </p>
 | 
			
		||||
                    )
 | 
			
		||||
                ) : (
 | 
			
		||||
                    <p>
 | 
			
		||||
                        <strong>Zalecenie</strong>: warto dodać informacje o tym, jakie są cele
 | 
			
		||||
                        ujawniania wyżej opisanych danych wyżej opisanym podmiotom trzecim.
 | 
			
		||||
                    </p>
 | 
			
		||||
                )}
 | 
			
		||||
            </>
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -1,29 +1,31 @@
 | 
			
		||||
import { RequestCluster } from '../../../request-cluster';
 | 
			
		||||
import { wordlist } from '../../../util';
 | 
			
		||||
import { ExplainerKey } from '../explainers';
 | 
			
		||||
import { ParsedAnswers } from '../parse-answers';
 | 
			
		||||
import { v } from '../verbs';
 | 
			
		||||
import { Problem } from './problem';
 | 
			
		||||
 | 
			
		||||
export class UnlawfulCookieAccess extends Problem {
 | 
			
		||||
    getNecessaryExplainers(): ExplainerKey[] {
 | 
			
		||||
        return ['cookies_are_pii', 'responsibility_for_third_parties'];
 | 
			
		||||
        return [];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    qualifies(): boolean {
 | 
			
		||||
    static qualifies(answers: ParsedAnswers, clusters: RequestCluster[]): boolean {
 | 
			
		||||
        // są cookiesy, nie było zgody, nie są konieczne do działania strony
 | 
			
		||||
        const cookie_clusters = Object.values(this.clusters).filter((c) => c.hasMarkedCookies());
 | 
			
		||||
        const cookie_clusters = Object.values(clusters).filter((c) => c.hasMarkedCookies());
 | 
			
		||||
        return cookie_clusters.some((cluster) => {
 | 
			
		||||
            const hostAnswers = this.answers.hosts[cluster.id];
 | 
			
		||||
            const hostAnswers = answers.hosts[cluster.id];
 | 
			
		||||
            return (
 | 
			
		||||
                (hostAnswers.present == 'not_mentioned' ||
 | 
			
		||||
                    hostAnswers.present == 'not_before_making_a_choice' ||
 | 
			
		||||
                    ['none', 'closed_popup', 'deny_all'].includes(this.answers.popup_action) ||
 | 
			
		||||
                    this.answers.popup_type === 'none') &&
 | 
			
		||||
                    ['none', 'closed_popup', 'deny_all'].includes(answers.popup_action) ||
 | 
			
		||||
                    answers.popup_type === 'none') &&
 | 
			
		||||
                hostAnswers.was_processing_necessary != 'yes'
 | 
			
		||||
            );
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    getEmailContent({ mode, tone }: { mode: 'email' | 'report'; tone: 'official' | 'polite' }) {
 | 
			
		||||
    getEmailContent() {
 | 
			
		||||
        const cookie_clusters = Object.values(this.clusters).filter((c) => c.hasMarkedCookies());
 | 
			
		||||
        const unnecessary_hosts = Object.entries(this.answers.hosts)
 | 
			
		||||
            .filter(([, answers]) => answers.was_processing_necessary === 'no')
 | 
			
		||||
@ -34,14 +36,10 @@ export class UnlawfulCookieAccess extends Problem {
 | 
			
		||||
        const _ = (key: string) => v(key, this.answers.zaimek);
 | 
			
		||||
        return (
 | 
			
		||||
            <>
 | 
			
		||||
                <h2>Dostęp do cookies niezgodny z ustawą Prawo Komunikacji Elektronicznej</h2>
 | 
			
		||||
                <h2>Dostęp do cookies niezgodny z ustawą Prawo Telekomunikacyjne</h2>
 | 
			
		||||
                <p>
 | 
			
		||||
                    Państwa strona {mode == 'email' ? 'dokonała' : 'dokonuje'} odczytu plików Cookie
 | 
			
		||||
                    zapisanych na dysku twardym{' '}
 | 
			
		||||
                    {mode === 'email'
 | 
			
		||||
                        ? _('mojego') + ' komputera.'
 | 
			
		||||
                        : 'komputerach użytkowników końcowych.'}
 | 
			
		||||
                    . Dotyczy to plików cookie przypisanych do domen:
 | 
			
		||||
                    Państwa strona dokonała odczytu plików Cookie zapisanych na dysku twardym mojego
 | 
			
		||||
                    komputera. Dotyczy to plików cookie przypisanych do domen:
 | 
			
		||||
                </p>
 | 
			
		||||
                <ul>
 | 
			
		||||
                    {cookie_clusters.map((cluster, index) => {
 | 
			
		||||
@ -67,9 +65,9 @@ export class UnlawfulCookieAccess extends Problem {
 | 
			
		||||
                    })}
 | 
			
		||||
                </ul>
 | 
			
		||||
                <p>
 | 
			
		||||
                    Zgodnie z treścią Art. 399.{' '}
 | 
			
		||||
                    <a href="https://isap.sejm.gov.pl/isap.nsf/DocDetails.xsp?id=WDU20240001221">
 | 
			
		||||
                        ustawy Prawo Komunikacji Elektronicznej
 | 
			
		||||
                    Zgodnie z treścią Art. 173.{' '}
 | 
			
		||||
                    <a href="https://isap.sejm.gov.pl/isap.nsf/download.xsp/WDU20041711800/U/D20041800Lj.pdf">
 | 
			
		||||
                        ustawy Prawo Telekomunikacyjne
 | 
			
		||||
                    </a>
 | 
			
		||||
                    , strona może pozyskać dostęp do treści plików cookies pod warunkiem spełnienia
 | 
			
		||||
                    jednego z następujących warunków:
 | 
			
		||||
@ -78,10 +76,7 @@ export class UnlawfulCookieAccess extends Problem {
 | 
			
		||||
                    <li>
 | 
			
		||||
                        Użytkownik wyraził zgodę na takie przetwarzanie danych <em>po</em> tym, jak
 | 
			
		||||
                        został poinformowany bezpośrednio o celu uzyskania dostępu do tej
 | 
			
		||||
                        informacji. Zgodnie z Art. 400 ustawy Prawo Komunikacji Elektronicznej, taka
 | 
			
		||||
                        zgoda musi spełniać warunki zgody ustalone przez RODO, aby mogła być użyta
 | 
			
		||||
                        jako podstawa prawna uzyskania dostępu do cookies i podobnych technologii w
 | 
			
		||||
                        przeglądarce;
 | 
			
		||||
                        informacji;
 | 
			
		||||
                    </li>
 | 
			
		||||
                    <li>
 | 
			
		||||
                        Dostęp do treści plików cookies jest konieczny do dostarczania usługi
 | 
			
		||||
@ -90,48 +85,25 @@ export class UnlawfulCookieAccess extends Problem {
 | 
			
		||||
                </ol>
 | 
			
		||||
                {(() => {
 | 
			
		||||
                    if (this.answers.popup_type == 'none' || this.answers.popup_type == 'page') {
 | 
			
		||||
                        return mode === 'email' ? (
 | 
			
		||||
                        return (
 | 
			
		||||
                            <p>
 | 
			
		||||
                                Jako, że strona nie pytała {_('mnie')} nigdy o zgodę, nie jest
 | 
			
		||||
                                spełniony warunek 1.
 | 
			
		||||
                            </p>
 | 
			
		||||
                        ) : (
 | 
			
		||||
                            <p>
 | 
			
		||||
                                Strona nie ma zaimplementowanego mechanizmu pozyskiwania zgód, zatem
 | 
			
		||||
                                nie spełnia warunku opisanego w punkcie 1.
 | 
			
		||||
                            </p>
 | 
			
		||||
                        );
 | 
			
		||||
                    } else if (this.answers.popup_type === 'passive_popup') {
 | 
			
		||||
                        return (
 | 
			
		||||
                            <p>
 | 
			
		||||
                                {mode === 'email' ? (
 | 
			
		||||
                                    <>
 | 
			
		||||
                                        Państwa strona nie dała mi nigdy faktycznego wyboru
 | 
			
		||||
                                        dotyczącego wyrażenia lub odmówienia zgody na takie
 | 
			
		||||
                                        przetwarzanie danych osobowych. Aby zgoda była ważna w
 | 
			
		||||
                                        świetle RODO, musi być dobrowolna. Brak możliwości
 | 
			
		||||
                                        odmówienia zgody sprawia, że tak wyrażona „zgoda” nie jest
 | 
			
		||||
                                        ważna w świetle RODO. Dlatego nie jest spełniony warunek
 | 
			
		||||
                                        1.{' '}
 | 
			
		||||
                                    </>
 | 
			
		||||
                                ) : (
 | 
			
		||||
                                    <>
 | 
			
		||||
                                        Aktualnie zaimplementowane okienko o przetwarzaniu danych
 | 
			
		||||
                                        osobowych nie daje użytkownikom końcowym możliwości odmowy
 | 
			
		||||
                                        wyrażenia zgody, przez co tak wyrażona „zgoda” nie spełnia
 | 
			
		||||
                                        warunku dobrowolności opisanego w motywie (32) RODO. Z tego
 | 
			
		||||
                                        powodu nie jest spełniony warunek opisany w punkcie 1.
 | 
			
		||||
                                        powyżej, zatem tak pozyskana "zgoda" nie może stanowić
 | 
			
		||||
                                        podstawy prawnej dostępu do cookiesów użytkownika końcowego.
 | 
			
		||||
                                    </>
 | 
			
		||||
                                )}{' '}
 | 
			
		||||
                                Państwa strona nie dała mi nigdy faktycznego wyboru dotyczącego
 | 
			
		||||
                                wyrażenia lub odmówienia zgody na takie przetwarzanie danych
 | 
			
		||||
                                osobowych, dlatego nie jest spełniony warunek 1.{' '}
 | 
			
		||||
                                {this.answers.mentions_passive_consent ? (
 | 
			
		||||
                                    <>
 | 
			
		||||
                                        Należy nadmienić także, że zgody wyrażonej w sposób bierny
 | 
			
		||||
                                        lub milczący nie można uznać za ważną w świetle
 | 
			
		||||
                                        obowiązujących przepisów rozporządzenia 2016/679. Dlatego
 | 
			
		||||
                                        zaniechanie zmiany ustawień przeglądarki lub po prostu
 | 
			
		||||
                                        korzystanie ze strony nie stanowi ważnej zgody. Takie jest{' '}
 | 
			
		||||
                                        Zgody wyrażonej w sposób bierny lub milczący nie można uznać
 | 
			
		||||
                                        za ważną w świetle obowiązujących przepisów rozporządzenia
 | 
			
		||||
                                        2016/679. Dlatego zaniechanie zmiany ustawień przeglądarki
 | 
			
		||||
                                        lub po prostu korzystanie ze strony nie stanowi ważnej
 | 
			
		||||
                                        zgody. Takie jest{' '}
 | 
			
		||||
                                        <a href="https://assets.midline.pl/pisma/2021-12-16%20odpowiedz%20UODO%20na%20skarg%C4%99%20i(n)Secure.pdf">
 | 
			
		||||
                                            stanowisko polskiego UODO
 | 
			
		||||
                                        </a>
 | 
			
		||||
@ -144,26 +116,15 @@ export class UnlawfulCookieAccess extends Problem {
 | 
			
		||||
                        );
 | 
			
		||||
                    } else if (this.answers.popup_type === 'some_choice') {
 | 
			
		||||
                        if (this.answers.popup_action === 'none') {
 | 
			
		||||
                            return mode == 'email' ? (
 | 
			
		||||
                            return (
 | 
			
		||||
                                <p>
 | 
			
		||||
                                    Nie {_('wyraziłem')} zgody na takie przetwarzanie {_('moich')}{' '}
 | 
			
		||||
                                    danych osobowych. W okienku pytającym o zgodę nic nie{' '}
 | 
			
		||||
                                    danych osobowych. W okienku pytającym o zgodję nic nie{' '}
 | 
			
		||||
                                    {_('kliknąłem')}. Nie jest zatem spełniony warunek 1.
 | 
			
		||||
                                </p>
 | 
			
		||||
                            ) : (
 | 
			
		||||
                                <p>
 | 
			
		||||
                                    Skrypty pozyskujące dostęp do plików cookie uruchamiają się
 | 
			
		||||
                                    zanim użytkownik końcowy zdąży wybrać jakąkolwiek opcję w
 | 
			
		||||
                                    okienku pytającym o zgodę. Aby zgoda była ważna, musi być
 | 
			
		||||
                                    pozyskana <strong>zanim</strong> nastąpi proces przetwarzania
 | 
			
		||||
                                    danych, którego ta zgoda dotyczy. Z tego powodu nie jest
 | 
			
		||||
                                    spełniony warunek 1. Nie można używać tak pozyskanej „zgody”
 | 
			
		||||
                                    jako podstawy prawnej dostępu do plików cookies na urządzeniu
 | 
			
		||||
                                    użytkownika końcowego.
 | 
			
		||||
                                </p>
 | 
			
		||||
                            );
 | 
			
		||||
                        } else if (this.answers.popup_action === 'closed_popup') {
 | 
			
		||||
                            return mode == 'email' ? (
 | 
			
		||||
                            return (
 | 
			
		||||
                                <p>
 | 
			
		||||
                                    Nie {_('wyraziłem')} zgody na takie przetwarzanie {_('moich')}{' '}
 | 
			
		||||
                                    danych osobowych. {this.answers.popup_closed_how.trim()}
 | 
			
		||||
@ -175,19 +136,9 @@ export class UnlawfulCookieAccess extends Problem {
 | 
			
		||||
                                    jednoznaczności opisanego w Art. 4, pkt 11 RODO. Nie jest zatem
 | 
			
		||||
                                    spełniony warunek 1.
 | 
			
		||||
                                </p>
 | 
			
		||||
                            ) : (
 | 
			
		||||
                                <p>
 | 
			
		||||
                                    Gdy użytkownik końcowy strony nie wyrazi jednoznacznej zgody w
 | 
			
		||||
                                    wyskakującym okienku, a zamiast tego po prostu zamknie to
 | 
			
		||||
                                    okienko, strona nadal pozyskuje dostęp do plików cookies na
 | 
			
		||||
                                    urządzeniu użytkownika. Zamknięcia okienka (np. przyciskiem „x”)
 | 
			
		||||
                                    nie można uznać za ważną zgodę, gdyż taka czyność nie spełnia
 | 
			
		||||
                                    warunku jednoznaczności opisanego w Art. 4. pkt 11. RODO. Nie
 | 
			
		||||
                                    jest zatem spełniony warunek 1.
 | 
			
		||||
                                </p>
 | 
			
		||||
                            );
 | 
			
		||||
                        } else if (this.answers.popup_action == 'deny_all') {
 | 
			
		||||
                            return mode == 'email' ? (
 | 
			
		||||
                            return (
 | 
			
		||||
                                <p>
 | 
			
		||||
                                    {this.answers.popup_deny_all_how.trim()}
 | 
			
		||||
                                    {this.answers.popup_closed_how.trim().at(-1) != '.'
 | 
			
		||||
@ -195,80 +146,28 @@ export class UnlawfulCookieAccess extends Problem {
 | 
			
		||||
                                        : ''}{' '}
 | 
			
		||||
                                    Zatem nie jest spełniony warunek 1.
 | 
			
		||||
                                </p>
 | 
			
		||||
                            ) : (
 | 
			
		||||
                                <p>
 | 
			
		||||
                                    Gdy użytkownik jednoznacznie odmówi zgód na wszystkie cele
 | 
			
		||||
                                    przetwarzania, strona nadal pozyskuje dostęp do plików cookies
 | 
			
		||||
                                    na urządzeniu użytkownika. Jeżeli uzytkownik nie odmówił zgody,
 | 
			
		||||
                                    to nie powinny załączać się procesy przetwarzania powołujące się
 | 
			
		||||
                                    na zgodę jako podstawę prawną.
 | 
			
		||||
                                </p>
 | 
			
		||||
                            );
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                })()}
 | 
			
		||||
                {unnecessary_hosts.length > 0 ? (
 | 
			
		||||
                    mode == 'email' ? (
 | 
			
		||||
                        <p>
 | 
			
		||||
                            W {_('mojej')} ocenie odczytywanie przez Państwa stronę treści plików
 | 
			
		||||
                            cookies z {wordlist(unnecessary_hosts)} nie jest konieczne do
 | 
			
		||||
                            wyświetlenia treści Państwa strony, dlatego nie jest dla nich spełniony
 | 
			
		||||
                            warunek 2. Jeżeli według Państwa oceny jest inaczej, {_('proszę')} o
 | 
			
		||||
                            wskazanie, co jest źródłem tej konieczności i co odróżnia Państwa stronę
 | 
			
		||||
                            od wielu innych stron, które realizują te same funkcjonalności{' '}
 | 
			
		||||
                            <em>bez</em> korzystania z plików Cookie.
 | 
			
		||||
                        </p>
 | 
			
		||||
                    ) : (
 | 
			
		||||
                        <p>
 | 
			
		||||
                            Warto, aby informacje na stronie opisywały w zrozumiały sposob, które z
 | 
			
		||||
                            podmiotów, których skrypty uruchamiają się na stronie (
 | 
			
		||||
                            {wordlist(unnecessary_hosts)}) są konieczne do działania strony, jaki
 | 
			
		||||
                            zakres danych przetwarzają i w jakim celu.
 | 
			
		||||
                        </p>
 | 
			
		||||
                    )
 | 
			
		||||
                    <p>
 | 
			
		||||
                        W {_('mojej')} ocenie odczytywanie przez Państwa stronę treści plików
 | 
			
		||||
                        cookies z {wordlist(unnecessary_hosts)} nie jest konieczne do wyświetlenia
 | 
			
		||||
                        treści Państwa strony, dlatego nie jest dla nich spełniony warunek 2. Jeżeli
 | 
			
		||||
                        Państwa zdaniem jest inaczej, {_('proszę')} o wskazanie, co jest źródłem tej
 | 
			
		||||
                        konieczności i co odróżnia Państwa stronę od wielu innych stron, które
 | 
			
		||||
                        realizują te same funkcjonalności <em>bez</em> korzystania z plików Cookie.
 | 
			
		||||
                    </p>
 | 
			
		||||
                ) : (
 | 
			
		||||
                    ''
 | 
			
		||||
                )}
 | 
			
		||||
                {mode == 'email' ? (
 | 
			
		||||
                    tone === 'official' ? (
 | 
			
		||||
                        <p>
 | 
			
		||||
                            {_('Proszę')} o wskazanie,{' '}
 | 
			
		||||
                            <strong>
 | 
			
		||||
                                czy być może stosowali Państwo inną podstawę prawną do takiego
 | 
			
		||||
                                przetwarzania {_('moich')} danych osobowych, czy być może
 | 
			
		||||
                                przetwarzali je Państwo bez ważnej podstawy prawnej?
 | 
			
		||||
                            </strong>
 | 
			
		||||
                        </p>
 | 
			
		||||
                    ) : (
 | 
			
		||||
                        <p>
 | 
			
		||||
                            Apeluję o wdrożenie zmian na Państwa stronie tak, aby użytkownik miał
 | 
			
		||||
                            faktyczny wybór dotyczący procesów przetwarzania jego danych osobowych,
 | 
			
		||||
                            jakie zachodzą w trakcie odwiedzin tej strony.
 | 
			
		||||
                        </p>
 | 
			
		||||
                    )
 | 
			
		||||
                ) : (
 | 
			
		||||
                    <>
 | 
			
		||||
                        <p>
 | 
			
		||||
                            Jeżeli zgoda nadal ma być używana jako podstawa prawna do odczytu plików
 | 
			
		||||
                            cookies przez skrypty wyżej wymienionych podmiotów, to należy zmienić
 | 
			
		||||
                            mechanizm zgody tak, aby:{' '}
 | 
			
		||||
                        </p>{' '}
 | 
			
		||||
                        <ul>
 | 
			
		||||
                            <li>
 | 
			
		||||
                                dawał użytkownikowi końcowemu możliwość odmowy zgody w sposób równie
 | 
			
		||||
                                łatwy i dostępny, jak na wyrażenie zgody;
 | 
			
		||||
                            </li>
 | 
			
		||||
                            <li>
 | 
			
		||||
                                skrypty śledzące uruchamiały się dopiero po uzyskaniu ważnej zgody;
 | 
			
		||||
                            </li>
 | 
			
		||||
                            <li>
 | 
			
		||||
                                skrypty śledzące nie uruchamiały się, jeżeli użytkownik nie wyraził
 | 
			
		||||
                                na nie zgody.
 | 
			
		||||
                            </li>
 | 
			
		||||
                        </ul>
 | 
			
		||||
                    </>
 | 
			
		||||
                )}
 | 
			
		||||
                {maybe_unnecessary_hosts.length > 1 && mode == 'email' && tone == 'official' ? (
 | 
			
		||||
                <p>
 | 
			
		||||
                    {_('Proszę')} o wskazanie, czy być może stosowali Państwo inną podstawę prawną
 | 
			
		||||
                    do takiego przetwarzania {_('moich')} danych osobowych, czy przetwarzali je
 | 
			
		||||
                    państwo bez ważnej podstawy prawnej?
 | 
			
		||||
                </p>
 | 
			
		||||
                {maybe_unnecessary_hosts.length > 1 ? (
 | 
			
		||||
                    <p>
 | 
			
		||||
                        {_('Proszę')} też o wskazanie, czy dostęp do treści plików cookie z
 | 
			
		||||
                        {wordlist(maybe_unnecessary_hosts)} jest konieczny do poprawnego działania
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										36
									
								
								components/report-window/problems/unlawful-data.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,36 @@
 | 
			
		||||
import { RequestCluster } from '../../../request-cluster';
 | 
			
		||||
import { ExplainerKey } from '../explainers';
 | 
			
		||||
import { ParsedAnswers, ParsedHostAnswers } from '../parse-answers';
 | 
			
		||||
import { v } from '../verbs';
 | 
			
		||||
import { Problem } from './problem';
 | 
			
		||||
 | 
			
		||||
type UnlawfulDataClassification = 'no_purpose';
 | 
			
		||||
 | 
			
		||||
export function classifyUnlawfulData(
 | 
			
		||||
    hostAnswers: ParsedHostAnswers,
 | 
			
		||||
    cluster: RequestCluster
 | 
			
		||||
): UnlawfulDataClassification {
 | 
			
		||||
    if (hostAnswers.present == 'not_mentioned' && hostAnswers.was_processing_necessary == 'no') {
 | 
			
		||||
        return 'no_purpose';
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class UnlawfulData extends Problem {
 | 
			
		||||
    static qualifies(answers: ParsedAnswers, clusters: RequestCluster[]): boolean {}
 | 
			
		||||
    getEmailContent() {
 | 
			
		||||
        const _ = (key: string) => v(key, this.answers.zaimek);
 | 
			
		||||
        return (
 | 
			
		||||
            <>
 | 
			
		||||
                <h2>Przetwarzanie danych osobowych bez ważnej podsawy prawnej</h2>
 | 
			
		||||
                <p>
 | 
			
		||||
                    {_('Moje')} dane osobowe zostały ujawnione podmiotom, które są właścicielami
 | 
			
		||||
                    domen:
 | 
			
		||||
                </p>
 | 
			
		||||
                {this.getRangeDescription()}
 | 
			
		||||
            </>
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
    getNecessaryExplainers() {
 | 
			
		||||
        return [] as ExplainerKey[];
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -1,16 +1,15 @@
 | 
			
		||||
import * as Survey from 'survey-react';
 | 
			
		||||
import { RequestCluster } from '../../request-cluster';
 | 
			
		||||
import RawAnswers from './raw-answers';
 | 
			
		||||
import useSurvey from './use-survey';
 | 
			
		||||
 | 
			
		||||
export default function Questions({
 | 
			
		||||
    clusters,
 | 
			
		||||
    hosts,
 | 
			
		||||
    onComplete,
 | 
			
		||||
}: {
 | 
			
		||||
    clusters: RequestCluster[];
 | 
			
		||||
    hosts: string[];
 | 
			
		||||
    onComplete: (data: RawAnswers) => void;
 | 
			
		||||
}) {
 | 
			
		||||
    const survey = useSurvey(clusters, {
 | 
			
		||||
    const survey = useSurvey(hosts, {
 | 
			
		||||
        onComplete: (sender) => onComplete(sender.data),
 | 
			
		||||
    });
 | 
			
		||||
    if (!survey) {
 | 
			
		||||
 | 
			
		||||
@ -16,8 +16,6 @@ export type HostRawAnswers = {
 | 
			
		||||
 | 
			
		||||
export type BasicRawAnswers = {
 | 
			
		||||
    zaimek: 0 | 1 | 2 | 3;
 | 
			
		||||
    user_role: 'user' | 'admin';
 | 
			
		||||
    email_type: 'polite_information' | 'official_request';
 | 
			
		||||
    is_incognito_different: [] | ['incognito_is_the_same'];
 | 
			
		||||
    policy_readable: 'yes' | 'vague' | 'cant_find';
 | 
			
		||||
    popup_action: 'none' | 'closed_popup' | 'accept_all' | 'deny_all' | 'other';
 | 
			
		||||
 | 
			
		||||
@ -1,3 +0,0 @@
 | 
			
		||||
export function reportIntro(visited_url: string) {
 | 
			
		||||
    return <h2>Analiza skryptów śledzących na {visited_url} - raport</h2>;
 | 
			
		||||
}
 | 
			
		||||
@ -10,11 +10,11 @@
 | 
			
		||||
    >
 | 
			
		||||
    <link
 | 
			
		||||
        rel="stylesheet"
 | 
			
		||||
        href="/node_modules/survey-react/survey.min.css"
 | 
			
		||||
        href="/node_modules/survey-react/survey.css"
 | 
			
		||||
    />
 | 
			
		||||
    <link
 | 
			
		||||
        rel="stylesheet"
 | 
			
		||||
        href="/node_modules/survey-react/modern.min.css"
 | 
			
		||||
        href="/node_modules/survey-react/modern.css"
 | 
			
		||||
    />
 | 
			
		||||
    <link
 | 
			
		||||
        rel="stylesheet"
 | 
			
		||||
@ -29,10 +29,9 @@
 | 
			
		||||
 | 
			
		||||
<body>
 | 
			
		||||
    <div id="app"></div>
 | 
			
		||||
    <script src="/node_modules/react/umd/react.production.min.js"></script>
 | 
			
		||||
    <script src="/node_modules/react-dom/umd/react-dom.production.min.js"></script>
 | 
			
		||||
	<script src="/node_modules/survey-react/survey.react.min.js"></script>
 | 
			
		||||
    <script src="/node_modules/react/umd/react.development.js"></script>
 | 
			
		||||
    <script src="/node_modules/react-dom/umd/react-dom.development.js"></script>
 | 
			
		||||
    <script src="/lib/components/report-window/report-window.js"></script>
 | 
			
		||||
</body>
 | 
			
		||||
 | 
			
		||||
</html>
 | 
			
		||||
</html>
 | 
			
		||||
@ -5,17 +5,6 @@
 | 
			
		||||
    font-family: 'OpenSans' !important;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#app {
 | 
			
		||||
    min-height: 100vh;
 | 
			
		||||
    display: flex;
 | 
			
		||||
    flex-flow: column;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#main-section {
 | 
			
		||||
    flex-grow: 1;
 | 
			
		||||
    margin-bottom: 20px; // to contain diag section
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
html {
 | 
			
		||||
    font-size: 1rem;
 | 
			
		||||
}
 | 
			
		||||
@ -116,10 +105,6 @@ h1 {
 | 
			
		||||
        .sv_body {
 | 
			
		||||
            padding: 0;
 | 
			
		||||
 | 
			
		||||
            .sv_nav {
 | 
			
		||||
                display: flex;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            .sv_p_root {
 | 
			
		||||
                & > .sv_row {
 | 
			
		||||
                    padding: 0.75rem 1.5rem;
 | 
			
		||||
@ -289,20 +274,3 @@ h1 {
 | 
			
		||||
    font-weight: 600;
 | 
			
		||||
    font-size: calc(14 / 16 * 1rem);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.generator-container {
 | 
			
		||||
    max-width: 100ex;
 | 
			
		||||
    margin: 0 auto;
 | 
			
		||||
    font-size: calc(14 / 16 * 1rem);
 | 
			
		||||
    margin-top: 3rem;
 | 
			
		||||
 | 
			
		||||
    a {
 | 
			
		||||
        color: $ultra-black-color;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.diag-toolbox {
 | 
			
		||||
    position: fixed;
 | 
			
		||||
    bottom: 10px;
 | 
			
		||||
    left: 10px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -9,33 +9,18 @@ import EmailContent from './email-content';
 | 
			
		||||
import { parseAnswers, ParsedAnswers } from './parse-answers';
 | 
			
		||||
import ScreenshotGenerator from './screenshot-generator';
 | 
			
		||||
 | 
			
		||||
function downloadFiles(link: string) {
 | 
			
		||||
    let a = document.createElement('a');
 | 
			
		||||
    a.setAttribute('href', link);
 | 
			
		||||
    a.setAttribute('download', '');
 | 
			
		||||
    a.setAttribute('target', '_blank');
 | 
			
		||||
    a.click();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function Report() {
 | 
			
		||||
    try {
 | 
			
		||||
        const url = new URL(document.location.toString());
 | 
			
		||||
        const origin = url.searchParams.get('origin');
 | 
			
		||||
        if (!origin) {
 | 
			
		||||
            return <div>Błąd: brak parametru "origin"</div>;
 | 
			
		||||
        }
 | 
			
		||||
        const [counter] = useEmitter(getMemory());
 | 
			
		||||
        const rawAnswers = url.searchParams.get('answers');
 | 
			
		||||
        const [answers, setAnswers] = React.useState<ParsedAnswers>(
 | 
			
		||||
            rawAnswers ? JSON.parse(rawAnswers) : null
 | 
			
		||||
            url.searchParams.get('answers') ? JSON.parse(url.searchParams.get('answers')) : null
 | 
			
		||||
        );
 | 
			
		||||
        const [mode, setMode] = React.useState(url.searchParams.get('mode') || 'survey');
 | 
			
		||||
        const [scrRequestPath, setScrRequestPath] = React.useState('');
 | 
			
		||||
 | 
			
		||||
        const clusters = getMemory().getClustersForOrigin(origin || '');
 | 
			
		||||
        const clusters = getMemory().getClustersForOrigin(origin);
 | 
			
		||||
 | 
			
		||||
        React.useEffect(() => {
 | 
			
		||||
            if (!origin) return;
 | 
			
		||||
            const url = new URL(document.location.toString());
 | 
			
		||||
            url.searchParams.set('origin', origin);
 | 
			
		||||
            url.searchParams.set('answers', JSON.stringify(answers));
 | 
			
		||||
@ -43,20 +28,16 @@ function Report() {
 | 
			
		||||
            history.pushState({}, 'Rentgen', url.toString());
 | 
			
		||||
        }, [mode, answers, origin]);
 | 
			
		||||
        const visited_url = Object.values(clusters)
 | 
			
		||||
            .sort((a, b) => (a.lastModified > b.lastModified ? -1 : 1))
 | 
			
		||||
            .find((cluster) => !!cluster.lastFullUrl)?.lastFullUrl;
 | 
			
		||||
 | 
			
		||||
        if (!visited_url) {
 | 
			
		||||
            return <div>Wczytywanie...</div>;
 | 
			
		||||
        }
 | 
			
		||||
            .find((cluster) => cluster.getMarkedRequests().length > 0)
 | 
			
		||||
            ?.getMarkedRequests()[0].originalURL;
 | 
			
		||||
 | 
			
		||||
        const result = (
 | 
			
		||||
            <div {...{ 'data-version': counter }}>
 | 
			
		||||
                {mode === 'survey' ? (
 | 
			
		||||
                    <Questions
 | 
			
		||||
                        clusters={Object.values(clusters).filter(
 | 
			
		||||
                            (cluster) => cluster.getMarkedRequests().length > 0
 | 
			
		||||
                        )}
 | 
			
		||||
                        hosts={Object.values(clusters)
 | 
			
		||||
                            .filter((cluster) => cluster.getMarkedRequests().length > 0)
 | 
			
		||||
                            .map((cluster) => cluster.id)}
 | 
			
		||||
                        onComplete={(answers) => {
 | 
			
		||||
                            setAnswers(parseAnswers(answers));
 | 
			
		||||
                            setMode('screenshots');
 | 
			
		||||
@ -67,32 +48,13 @@ function Report() {
 | 
			
		||||
                )}
 | 
			
		||||
                {mode === 'screenshots' ? (
 | 
			
		||||
                    <ScreenshotGenerator
 | 
			
		||||
                        {...{
 | 
			
		||||
                            visited_url,
 | 
			
		||||
                            clusters,
 | 
			
		||||
                            setReportWindowMode: setMode,
 | 
			
		||||
                            setRequestPath: setScrRequestPath,
 | 
			
		||||
                            downloadFiles: downloadFiles,
 | 
			
		||||
                            user_role: answers.user_role,
 | 
			
		||||
                        }}
 | 
			
		||||
                    />
 | 
			
		||||
                ) : (
 | 
			
		||||
                    ''
 | 
			
		||||
                )}
 | 
			
		||||
                {mode === 'preview' ? (
 | 
			
		||||
                    <EmailContent
 | 
			
		||||
                        {...{
 | 
			
		||||
                            answers,
 | 
			
		||||
                            visited_url,
 | 
			
		||||
                            clusters,
 | 
			
		||||
                            scrRequestPath,
 | 
			
		||||
                            downloadFiles: downloadFiles,
 | 
			
		||||
                            user_role: answers.user_role,
 | 
			
		||||
                        }}
 | 
			
		||||
                        {...{ visited_url, clusters, setReportWindowMode: setMode }}
 | 
			
		||||
                    />
 | 
			
		||||
                ) : (
 | 
			
		||||
                    ''
 | 
			
		||||
                )}
 | 
			
		||||
                {mode === 'preview' ? <EmailContent {...{ answers, visited_url, clusters }} /> : ''}
 | 
			
		||||
                {/* <HARConverter {...{ entries }} /> */}
 | 
			
		||||
            </div>
 | 
			
		||||
        );
 | 
			
		||||
        return (
 | 
			
		||||
@ -110,7 +72,7 @@ function Report() {
 | 
			
		||||
                        )}
 | 
			
		||||
                    </div>
 | 
			
		||||
                </header>
 | 
			
		||||
                <section id="main-section">{result}</section>
 | 
			
		||||
                <section>{result}</section>
 | 
			
		||||
            </Fragment>
 | 
			
		||||
        );
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
 | 
			
		||||
@ -23,6 +23,16 @@ h2 {
 | 
			
		||||
    position: relative;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.generator-container {
 | 
			
		||||
    max-width: 100ex;
 | 
			
		||||
    margin: 0 auto;
 | 
			
		||||
    font-size: calc(14 / 16 * 1rem);
 | 
			
		||||
 | 
			
		||||
    a {
 | 
			
		||||
        color: $ultra-black-color;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.buttons-container {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    justify-content: center;
 | 
			
		||||
@ -39,34 +49,20 @@ h2 {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.browser {
 | 
			
		||||
    height: 9.267rem;
 | 
			
		||||
    height: 8.667rem;
 | 
			
		||||
    font-weight: 800 !important;
 | 
			
		||||
    color: $disabled-grey !important;
 | 
			
		||||
    border: 1px solid $disabled-grey;
 | 
			
		||||
    background: linear-gradient(to bottom, $icd-rentgen-color 20%, #fff 20%, #fff 100%);
 | 
			
		||||
    background-size: 100%;
 | 
			
		||||
    background-position-y: 26.5px;
 | 
			
		||||
    background-image: linear-gradient(to bottom, $icd-rentgen-color 20%, #fff 20%, #fff 100%);
 | 
			
		||||
    animation: xray 2s cubic-bezier(0, 1.43, 0.39, 1.43) infinite;
 | 
			
		||||
 | 
			
		||||
    &--filled {
 | 
			
		||||
        background-size: 100%;
 | 
			
		||||
        background-position-y: 26.5px;
 | 
			
		||||
        background-position-y: 19px;
 | 
			
		||||
        animation: none;
 | 
			
		||||
 | 
			
		||||
        &--address-bar {
 | 
			
		||||
            border: 1px solid #8a949f;
 | 
			
		||||
            height: 1rem;
 | 
			
		||||
            width: 10rem;
 | 
			
		||||
            font-size: 0.667rem;
 | 
			
		||||
            font-weight: 400;
 | 
			
		||||
            padding: 0 0.25rem;
 | 
			
		||||
            color: #000;
 | 
			
		||||
            overflow: hidden;
 | 
			
		||||
            word-break: normal;
 | 
			
		||||
            inline-size: 10rem;
 | 
			
		||||
            text-overflow: ellipsis;
 | 
			
		||||
 | 
			
		||||
            background: linear-gradient(to left, $icd-rentgen-color 20%, #fff 20%, #fff 100%);
 | 
			
		||||
            animation: xray-header 2s cubic-bezier(0, 1.43, 0.39, 1.43) infinite;
 | 
			
		||||
        .browser__header {
 | 
			
		||||
            background-color: #fff;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -76,42 +72,28 @@ h2 {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @keyframes xray-header {
 | 
			
		||||
        to {
 | 
			
		||||
            background-position-x: 11.1rem;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &__header {
 | 
			
		||||
        height: 1.667rem;
 | 
			
		||||
        width: 100%;
 | 
			
		||||
        display: flex;
 | 
			
		||||
        justify-content: space-between;
 | 
			
		||||
        align-items: center;
 | 
			
		||||
        background-color: #fff;
 | 
			
		||||
 | 
			
		||||
        padding: 0 0.5rem;
 | 
			
		||||
        font-size: 1.25rem;
 | 
			
		||||
        border-bottom: 1px solid $disabled-grey;
 | 
			
		||||
 | 
			
		||||
        &--in_progress {
 | 
			
		||||
            .browser__header--address-bar {
 | 
			
		||||
                background: linear-gradient(to left, $icd-rentgen-color 20%, #fff 20%, #fff 100%);
 | 
			
		||||
                animation: xray-header 2s cubic-bezier(0, 1.43, 0.39, 1.43) infinite;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        &--address-bar {
 | 
			
		||||
            border: 1px solid #8a949f;
 | 
			
		||||
            border: 1px solid $disabled-grey;
 | 
			
		||||
            height: 1rem;
 | 
			
		||||
            width: 10rem;
 | 
			
		||||
            font-size: 0.667rem;
 | 
			
		||||
            font-weight: 400;
 | 
			
		||||
            padding: 0 0.25rem;
 | 
			
		||||
            padding-left: 0.25rem;
 | 
			
		||||
            color: #000;
 | 
			
		||||
            overflow: hidden;
 | 
			
		||||
            word-break: normal;
 | 
			
		||||
            overflow: visible;
 | 
			
		||||
            word-break: break-all;
 | 
			
		||||
            inline-size: 10rem;
 | 
			
		||||
            text-overflow: ellipsis;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        &--controls {
 | 
			
		||||
 | 
			
		||||
@ -2,7 +2,7 @@ import React, { Fragment } from 'react';
 | 
			
		||||
import { RequestCluster } from '../../request-cluster';
 | 
			
		||||
import './screenshot-generator.scss';
 | 
			
		||||
 | 
			
		||||
const SS_URL = 'https://screenshot-service.internet-czas-dzialac.pl';
 | 
			
		||||
const SS_URL = 'http://65.108.60.135:3000';
 | 
			
		||||
 | 
			
		||||
enum taskState {
 | 
			
		||||
    WAITING = 'waiting',
 | 
			
		||||
@ -12,33 +12,21 @@ enum taskState {
 | 
			
		||||
 | 
			
		||||
type Screenshot = {
 | 
			
		||||
    url: string;
 | 
			
		||||
    thumb_url: string;
 | 
			
		||||
    domain: string;
 | 
			
		||||
    filename: string;
 | 
			
		||||
    found_headers: string[];
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
interface screenshotTask {
 | 
			
		||||
    domains: string[];
 | 
			
		||||
    elapsed_time_s: number;
 | 
			
		||||
    current_action: string;
 | 
			
		||||
    finished_time: number;
 | 
			
		||||
    id: string;
 | 
			
		||||
    images: Screenshot[];
 | 
			
		||||
    jobs_ahead: number;
 | 
			
		||||
    output: string;
 | 
			
		||||
    processing_took: number;
 | 
			
		||||
    request_time: number;
 | 
			
		||||
    started_time: number;
 | 
			
		||||
    status: taskState;
 | 
			
		||||
    url: string;
 | 
			
		||||
    waiting_took: number;
 | 
			
		||||
    zip_url: string;
 | 
			
		||||
    preview: string;
 | 
			
		||||
    domains: string[];
 | 
			
		||||
    id: string;
 | 
			
		||||
    status: taskState;
 | 
			
		||||
    output: string;
 | 
			
		||||
    images: Screenshot[];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function createTaskEndpoint(visited_url: string, domains: string[]) {
 | 
			
		||||
    return `${SS_URL}/api/requests?url=${encodeURIComponent(visited_url)}${domains.reduce(
 | 
			
		||||
    return `${SS_URL}/api/requests?url=${visited_url}${domains.reduce(
 | 
			
		||||
        (prev: string, curr: string) => prev + '&domains[]=' + curr,
 | 
			
		||||
        ''
 | 
			
		||||
    )}`;
 | 
			
		||||
@ -56,38 +44,23 @@ export default function ScreenshotGenerator({
 | 
			
		||||
    visited_url,
 | 
			
		||||
    clusters,
 | 
			
		||||
    setReportWindowMode,
 | 
			
		||||
    setRequestPath,
 | 
			
		||||
    downloadFiles,
 | 
			
		||||
    user_role,
 | 
			
		||||
}: {
 | 
			
		||||
    visited_url: string;
 | 
			
		||||
    clusters: Record<string, RequestCluster>;
 | 
			
		||||
    setReportWindowMode: Function;
 | 
			
		||||
    setRequestPath: Function;
 | 
			
		||||
    downloadFiles: Function;
 | 
			
		||||
    user_role: string;
 | 
			
		||||
}) {
 | 
			
		||||
    const [mode, setMode] = React.useState<string>('idle');
 | 
			
		||||
    const [images, setImages] = React.useState<Screenshot[]>([]);
 | 
			
		||||
    const [taskId, setTaskId] = React.useState<string | null>(null);
 | 
			
		||||
    const [output, setOutput] = React.useState<any>({});
 | 
			
		||||
    const [currentAction, setCurrentAction] = React.useState<string>('');
 | 
			
		||||
    const [preview, setPreview] = React.useState<string>('');
 | 
			
		||||
    const [lastPreview, setLastPreview] = React.useState<string>('');
 | 
			
		||||
    const [taskId, setTaskId] = React.useState<string>(null);
 | 
			
		||||
 | 
			
		||||
    async function subscribeTask(path: string): Promise<screenshotTask> {
 | 
			
		||||
        let response = { status: taskState.WAITING };
 | 
			
		||||
        let last_preview = '';
 | 
			
		||||
        while (response.status === taskState.WAITING || response.status === taskState.RUNNING) {
 | 
			
		||||
            await new Promise((resolve) => setTimeout(resolve, 1000));
 | 
			
		||||
            response = await (await pollTask(path)).json();
 | 
			
		||||
            setImages((response as screenshotTask)?.images);
 | 
			
		||||
            setCurrentAction((response as screenshotTask)?.current_action);
 | 
			
		||||
            setLastPreview(last_preview);
 | 
			
		||||
            setPreview((response as screenshotTask)?.preview);
 | 
			
		||||
            last_preview = (response as screenshotTask)?.preview;
 | 
			
		||||
            document.querySelector('.images')?.scrollTo({
 | 
			
		||||
                top: document.querySelector('.images')?.scrollHeight,
 | 
			
		||||
            document.querySelector('.images').scrollTo({
 | 
			
		||||
                top: document.querySelector('.images').scrollHeight,
 | 
			
		||||
                behavior: 'smooth',
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
@ -98,6 +71,19 @@ export default function ScreenshotGenerator({
 | 
			
		||||
        return response as screenshotTask;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function downloadFiles() {
 | 
			
		||||
        const urls = images.map((el) => `${SS_URL}${el}`);
 | 
			
		||||
 | 
			
		||||
        for (const url of urls) {
 | 
			
		||||
            let a = document.createElement('a');
 | 
			
		||||
            a.setAttribute('href', url);
 | 
			
		||||
            a.setAttribute('download', '');
 | 
			
		||||
            a.setAttribute('target', '_blank');
 | 
			
		||||
            a.click();
 | 
			
		||||
        }
 | 
			
		||||
        setReportWindowMode('preview');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <div className="generator-container">
 | 
			
		||||
            {mode === 'idle' ? (
 | 
			
		||||
@ -105,42 +91,16 @@ export default function ScreenshotGenerator({
 | 
			
		||||
                    <h1>Przygotowanie zrzutów ekranów</h1>
 | 
			
		||||
                    <div className="container">
 | 
			
		||||
                        <h2>Notka informacyjna</h2>
 | 
			
		||||
                        <img
 | 
			
		||||
                            src="/assets/doctor_welcome.png"
 | 
			
		||||
                            style={{
 | 
			
		||||
                                width: '100%',
 | 
			
		||||
                                maxWidth: '360px',
 | 
			
		||||
                                float: 'right',
 | 
			
		||||
                                position: 'relative',
 | 
			
		||||
                                top: '-10px',
 | 
			
		||||
                            }}
 | 
			
		||||
                        />
 | 
			
		||||
                        <Fragment>
 | 
			
		||||
                            <p>
 | 
			
		||||
                                W celu udokumentowania procesów przetwarzania danych, jakie wykryła
 | 
			
		||||
                                nasza wtyczka na tej stronie, warto wykonać zrzuty ekranu, na
 | 
			
		||||
                                których widać przeglądarkę z otwartymi narzędziami deweloperskimi,
 | 
			
		||||
                                ukazując wybrane elementy ruchu sieciowego generowanego przez
 | 
			
		||||
                                stronę.
 | 
			
		||||
                            </p>
 | 
			
		||||
                            <p>Jeżeli chcesz, wtyczka Rentgen może wygenerować je za Ciebie.</p>
 | 
			
		||||
                            <p>
 | 
			
		||||
                                Uwaga: aby to zrobić, adres aktualnie odwiedzonej podstrony
 | 
			
		||||
                                analizowanej witryny będzie wysłany na nasz serwer, aby na nim
 | 
			
		||||
                                odwiedzić tę podstronę i wykonać zrzuty ekranu.
 | 
			
		||||
                            </p>
 | 
			
		||||
                            <p>
 | 
			
		||||
                                Serwer, na którym jest wykonywana analiza należy do inicjatywy{' '}
 | 
			
		||||
                                <a href="https://www.internet-czas-dzialac.pl/contact/">
 | 
			
		||||
                                    <i>Internet. Czas działać!</i>
 | 
			
		||||
                                </a>
 | 
			
		||||
                                . Zebrane dane nie są wysyłane do żadnych podmiotów trzecich i są
 | 
			
		||||
                                usuwane z serwera po 24 godzinach. Wysłanie na serwer informacji o
 | 
			
		||||
                                adresie przeglądanej strony jest konieczne, aby wykonać te zrzuty
 | 
			
		||||
                                ekranu w sposób automatyczny. Jeżeli nie chcesz korzystać z opcji
 | 
			
		||||
                                automatycznej, zachęcamy do wykonania zrzutów ekranu samodzielnie.
 | 
			
		||||
                            </p>
 | 
			
		||||
                        </Fragment>
 | 
			
		||||
                        <p>
 | 
			
		||||
                            Dla potwierdzenia przechwyconych danych, warto załączyć zrzuty ekranów
 | 
			
		||||
                            narzędzi deweloperskich do maila dla administratora oraz Urzędu Ochrony
 | 
			
		||||
                            Danych Osobowych.
 | 
			
		||||
                        </p>
 | 
			
		||||
                        <p>
 | 
			
		||||
                            Jeżeli nie wiesz jak wykonać zrzuty ekranów, skorzystaj z{' '}
 | 
			
		||||
                            <a href="">naszej instrukcji</a> lub wtyczka Rentgen może wygenerować je
 | 
			
		||||
                            za Ciebie.
 | 
			
		||||
                        </p>
 | 
			
		||||
                    </div>
 | 
			
		||||
 | 
			
		||||
                    <div className="buttons-container">
 | 
			
		||||
@ -148,7 +108,6 @@ export default function ScreenshotGenerator({
 | 
			
		||||
                            className="sv_prev_btn"
 | 
			
		||||
                            onClick={() => {
 | 
			
		||||
                                setReportWindowMode('preview');
 | 
			
		||||
                                setRequestPath(null);
 | 
			
		||||
                            }}
 | 
			
		||||
                        >
 | 
			
		||||
                            Pomiń
 | 
			
		||||
@ -157,20 +116,13 @@ export default function ScreenshotGenerator({
 | 
			
		||||
                            className="sv_next_btn"
 | 
			
		||||
                            onClick={async () => {
 | 
			
		||||
                                setMode('in_progress');
 | 
			
		||||
                                const task = await createTask(
 | 
			
		||||
                                    visited_url,
 | 
			
		||||
                                    Object.values(clusters)
 | 
			
		||||
                                        .filter((cluster) => cluster.hasMarks())
 | 
			
		||||
                                        .map((cluster) => cluster.id)
 | 
			
		||||
                                );
 | 
			
		||||
                                const task = await createTask(visited_url, Object.keys(clusters));
 | 
			
		||||
                                const urlArr = task.url.split('/');
 | 
			
		||||
                                setTaskId(urlArr[urlArr.length - 1]);
 | 
			
		||||
                                const response = await subscribeTask(task.url);
 | 
			
		||||
                                setImages(response.images);
 | 
			
		||||
                                setLastPreview(preview);
 | 
			
		||||
                                setPreview(response.preview);
 | 
			
		||||
                                setOutput(response);
 | 
			
		||||
                                setRequestPath(response.zip_url);
 | 
			
		||||
                                console.log('response.images', response.images);
 | 
			
		||||
                                console.log('output', response);
 | 
			
		||||
                            }}
 | 
			
		||||
                        >
 | 
			
		||||
                            Wygeneruj
 | 
			
		||||
@ -190,7 +142,6 @@ export default function ScreenshotGenerator({
 | 
			
		||||
                                    Nasz serwer właśnie odwiedza wskazaną przez Ciebie stronę
 | 
			
		||||
                                    i przygotowuje zrzuty ekranów narzędzi deweloperskich.
 | 
			
		||||
                                </p>
 | 
			
		||||
                                <div>{currentAction}</div>
 | 
			
		||||
                            </Fragment>
 | 
			
		||||
                        ) : null}
 | 
			
		||||
                        {mode === 'finished' ? (
 | 
			
		||||
@ -201,34 +152,18 @@ export default function ScreenshotGenerator({
 | 
			
		||||
                        ) : null}
 | 
			
		||||
 | 
			
		||||
                        <div className="images">
 | 
			
		||||
                            {mode === 'in_progress' ? (
 | 
			
		||||
                                <div
 | 
			
		||||
                                    className="browser"
 | 
			
		||||
                                    style={{
 | 
			
		||||
                                        backgroundImage: `url(${SS_URL}${preview})${
 | 
			
		||||
                                            lastPreview ? `, url(${SS_URL}${lastPreview})` : ''
 | 
			
		||||
                                        }`,
 | 
			
		||||
                                    }}
 | 
			
		||||
                                >
 | 
			
		||||
                                    <div className="browser__header browser__header--in_progress">
 | 
			
		||||
                                        <div className="browser__header--address-bar"></div>
 | 
			
		||||
                                        <div className="browser__header--controls">· · ·</div>
 | 
			
		||||
                                    </div>
 | 
			
		||||
                                    <div className="browser__content"></div>
 | 
			
		||||
                                </div>
 | 
			
		||||
                            ) : null}
 | 
			
		||||
                            {images.map((screenshot) => {
 | 
			
		||||
                                return (
 | 
			
		||||
                                    <div
 | 
			
		||||
                                        key={`${taskId}_${screenshot.url}`}
 | 
			
		||||
                                        className="browser browser--filled"
 | 
			
		||||
                                        style={{
 | 
			
		||||
                                            backgroundImage: `url(${SS_URL}${screenshot.thumb_url})`,
 | 
			
		||||
                                            backgroundImage: `url(${SS_URL}${screenshot.url})`,
 | 
			
		||||
                                        }}
 | 
			
		||||
                                    >
 | 
			
		||||
                                        <div className="browser__header">
 | 
			
		||||
                                            <div className="browser__header--address-bar">
 | 
			
		||||
                                                🕸 {screenshot.domain}
 | 
			
		||||
                                                {screenshot.url.split('-').slice(-2).join('-')}
 | 
			
		||||
                                            </div>
 | 
			
		||||
                                            <div className="browser__header--controls">· · ·</div>
 | 
			
		||||
                                        </div>
 | 
			
		||||
@ -236,18 +171,22 @@ export default function ScreenshotGenerator({
 | 
			
		||||
                                    </div>
 | 
			
		||||
                                );
 | 
			
		||||
                            })}
 | 
			
		||||
 | 
			
		||||
                            {mode === 'in_progress' ? (
 | 
			
		||||
                                <div className="browser">
 | 
			
		||||
                                    <div className="browser__header">
 | 
			
		||||
                                        <div className="browser__header--address-bar"></div>
 | 
			
		||||
                                        <div className="browser__header--controls">· · ·</div>
 | 
			
		||||
                                    </div>
 | 
			
		||||
                                    <div className="browser__content"></div>
 | 
			
		||||
                                </div>
 | 
			
		||||
                            ) : null}
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div className="buttons-container">
 | 
			
		||||
                        {mode === 'finished' ? (
 | 
			
		||||
                            <Fragment>
 | 
			
		||||
                                <button
 | 
			
		||||
                                    className="sv_next_btn"
 | 
			
		||||
                                    onClick={() => {
 | 
			
		||||
                                        downloadFiles(`${SS_URL}${output.zip_url}`);
 | 
			
		||||
                                        setReportWindowMode('preview');
 | 
			
		||||
                                    }}
 | 
			
		||||
                                >
 | 
			
		||||
                                <button className="sv_next_btn" onClick={() => downloadFiles()}>
 | 
			
		||||
                                    Pobierz zrzuty ekranów i przejdź dalej
 | 
			
		||||
                                </button>
 | 
			
		||||
                            </Fragment>
 | 
			
		||||
 | 
			
		||||
@ -1,17 +1,17 @@
 | 
			
		||||
import * as React from 'react';
 | 
			
		||||
import * as Survey from 'survey-react';
 | 
			
		||||
import { RequestCluster } from '../../request-cluster';
 | 
			
		||||
import generateSurveyQuestions from './generate-survey-questions';
 | 
			
		||||
import RawAnswers from './raw-answers';
 | 
			
		||||
import verbs, { v } from './verbs';
 | 
			
		||||
 | 
			
		||||
export default function useSurvey(
 | 
			
		||||
    clusters: RequestCluster[],
 | 
			
		||||
    hosts: string[],
 | 
			
		||||
    { onComplete }: { onComplete: (sender: { data: RawAnswers }) => void }
 | 
			
		||||
): Survey.ReactSurveyModel | null {
 | 
			
		||||
    const [survey, setSurvey] = React.useState<Survey.Model | null>(null);
 | 
			
		||||
): Survey.ReactSurveyModel {
 | 
			
		||||
    const [survey, setSurvey] = React.useState<Survey.Model>(null);
 | 
			
		||||
    React.useEffect(() => {
 | 
			
		||||
        const model = generateSurveyQuestions(clusters);
 | 
			
		||||
        const model = generateSurveyQuestions(hosts);
 | 
			
		||||
        console.log(model);
 | 
			
		||||
        const survey = new Survey.Model(model);
 | 
			
		||||
        survey.onProcessTextValue.add(function (
 | 
			
		||||
            sender: Survey.SurveyModel,
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,5 @@
 | 
			
		||||
const words = {
 | 
			
		||||
    ciebie: ['ciebie', 'ciebie', 'ciebie', 'was'],
 | 
			
		||||
    chciałbym: ['chciałbym', 'chciałabym', 'chciałobym', 'chcielibyśmy'],
 | 
			
		||||
    dokonałeś: ['dokonałeś', 'dokonałaś', 'dokonałoś', 'dokonaliście'],
 | 
			
		||||
    jesteś: ['jesteś', 'jesteś', 'jesteś', 'jesteście'],
 | 
			
		||||
    kliknąłem: ['kliknąłem', 'kliknęłam', 'klinkęłom', 'kliknęliśmy'],
 | 
			
		||||
@ -9,8 +8,6 @@ const words = {
 | 
			
		||||
    mnie: ['mnie', 'mnie', 'mnie', 'nas'],
 | 
			
		||||
    moich: ['moich', 'moich', 'moich', 'naszych'],
 | 
			
		||||
    moje: ['moje', 'moje', 'moje', 'nasze'],
 | 
			
		||||
    mojego: ['mojego', 'mojego', 'mojego', 'naszego'],
 | 
			
		||||
    moja: ['moja', 'moja', 'moja', 'nasza'],
 | 
			
		||||
    mojej: ['mojej', 'mojej', 'mojej', 'naszej'],
 | 
			
		||||
    muszę: ['muszę', 'muszę', 'muszę', 'musimy'],
 | 
			
		||||
    odkliknąłeś: ['odkliknąłeś', 'odkliknęłaś', 'odklikęłoś', 'odkliknęliście'],
 | 
			
		||||
 | 
			
		||||
@ -1,12 +1,20 @@
 | 
			
		||||
import React from 'react';
 | 
			
		||||
import ReactDOM from 'react-dom';
 | 
			
		||||
 | 
			
		||||
import { getMemory } from '../../memory';
 | 
			
		||||
import Options from '../../options';
 | 
			
		||||
import { useEmitter } from '../../util';
 | 
			
		||||
import { StolenData } from './stolen-data';
 | 
			
		||||
import { getshorthost, useEmitter } from '../../util';
 | 
			
		||||
import { getMemory } from '../../memory';
 | 
			
		||||
 | 
			
		||||
async function getCurrentTab() {
 | 
			
		||||
    const [tab] = await browser.tabs.query({
 | 
			
		||||
        active: true,
 | 
			
		||||
        windowId: browser.windows.WINDOW_ID_CURRENT,
 | 
			
		||||
    });
 | 
			
		||||
    return tab;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
import './../../styles/global.scss';
 | 
			
		||||
import './sidebar.scss';
 | 
			
		||||
import { StolenData } from './stolen-data';
 | 
			
		||||
 | 
			
		||||
const Sidebar = () => {
 | 
			
		||||
    const url = new URL(document.location.toString());
 | 
			
		||||
@ -20,8 +28,8 @@ const Sidebar = () => {
 | 
			
		||||
    const [cookiesOnly, setCookiesOnly] = React.useState<boolean>(false);
 | 
			
		||||
    const [stolenDataView, setStolenDataView] = React.useState<boolean>(true);
 | 
			
		||||
    const [cookiesOrOriginOnly, setCookiesOrOriginOnly] = React.useState<boolean>(false);
 | 
			
		||||
    const [eventCounts] = useEmitter(getMemory());
 | 
			
		||||
    const [_, setMarksOccurrence] = React.useState<boolean>(false);
 | 
			
		||||
    const [eventCounts, setEventCounts] = useEmitter(getMemory());
 | 
			
		||||
    const [marksOccurrence, setMarksOccurrence] = React.useState<boolean>(false);
 | 
			
		||||
    const [infoDataDialogAck, setInfoDataDialogAck] = React.useState<boolean>(
 | 
			
		||||
        localStorage.getItem('infoDataDialogAck') === null
 | 
			
		||||
            ? true
 | 
			
		||||
@ -45,7 +53,6 @@ const Sidebar = () => {
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    React.useEffect(() => {
 | 
			
		||||
        if (!origin) return;
 | 
			
		||||
        for (const cluster of Object.values(getMemory().getClustersForOrigin(origin))) {
 | 
			
		||||
            if (cluster.hasMarks()) {
 | 
			
		||||
                return setMarksOccurrence(true);
 | 
			
		||||
@ -55,7 +62,6 @@ const Sidebar = () => {
 | 
			
		||||
        return setMarksOccurrence(false);
 | 
			
		||||
    }, [eventCounts['*']]);
 | 
			
		||||
 | 
			
		||||
    if (!origin) return <div>Błąd: Brak parametru "origin"</div>;
 | 
			
		||||
    return (
 | 
			
		||||
        <div className="sidebar">
 | 
			
		||||
            <header className="header">
 | 
			
		||||
@ -198,7 +204,7 @@ const Sidebar = () => {
 | 
			
		||||
                        <StolenData
 | 
			
		||||
                            origin={origin}
 | 
			
		||||
                            eventCounts={eventCounts}
 | 
			
		||||
                            minValueLength={minValueLength === null ? 7 : minValueLength}
 | 
			
		||||
                            minValueLength={minValueLength}
 | 
			
		||||
                            cookiesOnly={cookiesOnly}
 | 
			
		||||
                            cookiesOrOriginOnly={cookiesOrOriginOnly}
 | 
			
		||||
                            detailsVisibility={detailsVisibility}
 | 
			
		||||
@ -206,7 +212,7 @@ const Sidebar = () => {
 | 
			
		||||
                    </>
 | 
			
		||||
                ) : (
 | 
			
		||||
                    <Options
 | 
			
		||||
                        minValueLength={minValueLength === null ? 7 : minValueLength}
 | 
			
		||||
                        minValueLength={minValueLength}
 | 
			
		||||
                        setMinValueLength={setMinValueLength}
 | 
			
		||||
                        cookiesOnly={cookiesOnly}
 | 
			
		||||
                        setCookiesOnly={setCookiesOnly}
 | 
			
		||||
 | 
			
		||||
@ -7,15 +7,6 @@
 | 
			
		||||
        flex-flow: column;
 | 
			
		||||
        border-bottom: none;
 | 
			
		||||
 | 
			
		||||
        &__header {
 | 
			
		||||
            display: flex;
 | 
			
		||||
            align-items: center;
 | 
			
		||||
 | 
			
		||||
            .icon.cookie-data {
 | 
			
		||||
                margin-left: 0.25rem;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .domain-checkbox {
 | 
			
		||||
            margin-right: 0.5rem;
 | 
			
		||||
            width: 0.875rem;
 | 
			
		||||
 | 
			
		||||
@ -2,10 +2,12 @@ import React from 'react';
 | 
			
		||||
import { getMemory } from '../../memory';
 | 
			
		||||
import { StolenDataEntry } from '../../stolen-data-entry';
 | 
			
		||||
 | 
			
		||||
import { useEmitter } from '../../util';
 | 
			
		||||
import { maskString, useEmitter } from '../../util';
 | 
			
		||||
 | 
			
		||||
import './stolen-data-cluster.scss';
 | 
			
		||||
 | 
			
		||||
const MAX_STRING_VALUE_LENGTH = 100;
 | 
			
		||||
 | 
			
		||||
function StolenDataValue({ entry }: { entry: StolenDataEntry; prefixKey?: string }) {
 | 
			
		||||
    const [version] = useEmitter(entry);
 | 
			
		||||
    let body = null;
 | 
			
		||||
@ -41,16 +43,13 @@ function StolenDataRow({ entry }: { entry: StolenDataEntry }) {
 | 
			
		||||
                <input
 | 
			
		||||
                    type="checkbox"
 | 
			
		||||
                    checked={entry.isMarked}
 | 
			
		||||
                    id={entry.id.toString()}
 | 
			
		||||
                    onChange={() => {
 | 
			
		||||
                        entry.toggleMark();
 | 
			
		||||
                        getMemory().emit('change', entry.request.shorthost);
 | 
			
		||||
                    }}
 | 
			
		||||
                />
 | 
			
		||||
            </td>
 | 
			
		||||
            <th title={`Nazwa: ${entry.name}\nŹródło: ${entry.source}`}>
 | 
			
		||||
                <label htmlFor={entry.id.toString()}>{entry.name}</label>
 | 
			
		||||
            </th>
 | 
			
		||||
            <th title={`Nazwa: ${entry.name}\nŹródło: ${entry.source}`}>{entry.name}</th>
 | 
			
		||||
            <td className="icons">
 | 
			
		||||
                {entry.source === 'cookie' ? (
 | 
			
		||||
                    <span title="Dane przechowywane w Cookies">
 | 
			
		||||
@ -104,11 +103,13 @@ export default function StolenDataCluster({
 | 
			
		||||
    shorthost,
 | 
			
		||||
    minValueLength,
 | 
			
		||||
    cookiesOnly,
 | 
			
		||||
    refreshToken,
 | 
			
		||||
    cookiesOrOriginOnly,
 | 
			
		||||
    detailsVisibility,
 | 
			
		||||
}: {
 | 
			
		||||
    origin: string;
 | 
			
		||||
    shorthost: string;
 | 
			
		||||
    refreshToken: number;
 | 
			
		||||
    minValueLength: number;
 | 
			
		||||
    cookiesOnly: boolean;
 | 
			
		||||
    cookiesOrOriginOnly: boolean;
 | 
			
		||||
@ -118,37 +119,27 @@ export default function StolenDataCluster({
 | 
			
		||||
    const fullHosts = cluster.getFullHosts();
 | 
			
		||||
    const [version] = useEmitter(cluster);
 | 
			
		||||
 | 
			
		||||
    /* console.log('Stolen data cluster!', shorthost, refreshToken); */
 | 
			
		||||
 | 
			
		||||
    console.log(cluster.getMarkedEntries());
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <div className="stolen-data-cluster-container">
 | 
			
		||||
            <header className="domains-container">
 | 
			
		||||
                <div className="domains-container__header">
 | 
			
		||||
                <div>
 | 
			
		||||
                    <input
 | 
			
		||||
                        type="checkbox"
 | 
			
		||||
                        className="domain-checkbox"
 | 
			
		||||
                        data-version={version}
 | 
			
		||||
                        checked={cluster.hasMarks()}
 | 
			
		||||
                        onChange={() => {
 | 
			
		||||
                            console.log('Clicked checkbox!', {
 | 
			
		||||
                                cluster_id: cluster.id,
 | 
			
		||||
                                has_marks: cluster.hasMarks(),
 | 
			
		||||
                            });
 | 
			
		||||
                            cluster.hasMarks() ? cluster.undoMark() : cluster.autoMark();
 | 
			
		||||
                            getMemory().emit('change', cluster.id);
 | 
			
		||||
                        }}
 | 
			
		||||
                    />
 | 
			
		||||
                    <a className="domain" href={'https://' + cluster.id} target="_blank">
 | 
			
		||||
                        {cluster.id}
 | 
			
		||||
                    </a>{' '}
 | 
			
		||||
                    {cluster.hasCookies() ? (
 | 
			
		||||
                        <img
 | 
			
		||||
                            src="/assets/icons/cookie.svg"
 | 
			
		||||
                            height={16}
 | 
			
		||||
                            width={16}
 | 
			
		||||
                            className="icon cookie-data"
 | 
			
		||||
                        />
 | 
			
		||||
                    ) : (
 | 
			
		||||
                        ''
 | 
			
		||||
                    )}
 | 
			
		||||
                    </a>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div className="subdomains-container">
 | 
			
		||||
                    {fullHosts.map((host, index) => (
 | 
			
		||||
 | 
			
		||||
@ -14,7 +14,7 @@ export function StolenData({
 | 
			
		||||
    detailsVisibility,
 | 
			
		||||
}: {
 | 
			
		||||
    origin: string;
 | 
			
		||||
    eventCounts: Record<string, number | undefined>;
 | 
			
		||||
    eventCounts: Record<string, number>;
 | 
			
		||||
    minValueLength: number;
 | 
			
		||||
    cookiesOnly: boolean;
 | 
			
		||||
    cookiesOrOriginOnly: boolean;
 | 
			
		||||
@ -43,7 +43,7 @@ export function StolenData({
 | 
			
		||||
                        origin={origin}
 | 
			
		||||
                        shorthost={cluster.id}
 | 
			
		||||
                        key={cluster.id + origin}
 | 
			
		||||
                        refreshToken={eventCounts[cluster.id] || 0}
 | 
			
		||||
                        refreshToken={eventCounts[cluster.id]}
 | 
			
		||||
                        minValueLength={minValueLength}
 | 
			
		||||
                        cookiesOnly={cookiesOnly}
 | 
			
		||||
                        cookiesOrOriginOnly={cookiesOrOriginOnly}
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,4 @@
 | 
			
		||||
import React from 'react';
 | 
			
		||||
import { Tab } from '../../util';
 | 
			
		||||
 | 
			
		||||
export default function TabDropdown({
 | 
			
		||||
    setPickedTab,
 | 
			
		||||
@ -8,7 +7,7 @@ export default function TabDropdown({
 | 
			
		||||
    setPickedTab: (tab_id: number) => void;
 | 
			
		||||
    pickedTab: number;
 | 
			
		||||
}) {
 | 
			
		||||
    const [tabs, setTabs] = React.useState<Tab[]>([]);
 | 
			
		||||
    const [tabs, setTabs] = React.useState([]);
 | 
			
		||||
    React.useEffect(() => {
 | 
			
		||||
        browser.tabs.query({ currentWindow: true }).then(setTabs);
 | 
			
		||||
    }, []);
 | 
			
		||||
 | 
			
		||||
@ -22,10 +22,6 @@ body {
 | 
			
		||||
        z-index: 1;
 | 
			
		||||
        user-select: none;
 | 
			
		||||
 | 
			
		||||
        &--no-page {
 | 
			
		||||
            border-bottom: 0;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .webpage-metadata {
 | 
			
		||||
            word-break: break-all;
 | 
			
		||||
            display: flex;
 | 
			
		||||
 | 
			
		||||
@ -14,20 +14,12 @@ async function getCurrentTab() {
 | 
			
		||||
import './../../styles/global.scss';
 | 
			
		||||
import './toolbar.scss';
 | 
			
		||||
 | 
			
		||||
function isDomainHighlySuspicious(domain: string): boolean {
 | 
			
		||||
    return (
 | 
			
		||||
        domain.includes('facebook') ||
 | 
			
		||||
        domain.includes('twitter') ||
 | 
			
		||||
        domain.includes('linkedin') ||
 | 
			
		||||
        false
 | 
			
		||||
    );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const Toolbar = () => {
 | 
			
		||||
    const [origin, setOrigin] = React.useState<string | null>(null);
 | 
			
		||||
    const [eventCounts] = useEmitter(getMemory());
 | 
			
		||||
    const [stolenDataView, setStolenDataView] = React.useState<boolean>(true);
 | 
			
		||||
    const [eventCounts, setEventCounts] = useEmitter(getMemory());
 | 
			
		||||
    const [cookieDomainCopy, setCookieDomainCopy] = React.useState<string | null>(null);
 | 
			
		||||
    const [_, setMarksOccurrence] = React.useState<boolean>(false);
 | 
			
		||||
    const [marksOccurrence, setMarksOccurrence] = React.useState<boolean>(false);
 | 
			
		||||
    const [exposedOriginDomainCopy, setExposedOriginDomainCopy] = React.useState<string | null>(
 | 
			
		||||
        null
 | 
			
		||||
    );
 | 
			
		||||
@ -40,7 +32,7 @@ const Toolbar = () => {
 | 
			
		||||
        const listener = async () => {
 | 
			
		||||
            const tab = await getCurrentTab();
 | 
			
		||||
 | 
			
		||||
            if (tab !== undefined && tab.url) {
 | 
			
		||||
            if (tab !== undefined) {
 | 
			
		||||
                const url = new URL(tab.url);
 | 
			
		||||
                if (url.origin.startsWith('moz-extension')) {
 | 
			
		||||
                    return;
 | 
			
		||||
@ -59,85 +51,60 @@ const Toolbar = () => {
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    React.useEffect(() => {
 | 
			
		||||
        if (!origin) return;
 | 
			
		||||
        const exposedOriginDomains = Object.values(getMemory().getClustersForOrigin(origin))
 | 
			
		||||
            .filter((cluster) => cluster.exposesOrigin())
 | 
			
		||||
            .sort((cluster1, cluster2) =>
 | 
			
		||||
                isDomainHighlySuspicious(cluster1.id)
 | 
			
		||||
                    ? -1
 | 
			
		||||
                    : isDomainHighlySuspicious(cluster2.id)
 | 
			
		||||
                      ? 1
 | 
			
		||||
                      : 0
 | 
			
		||||
            )
 | 
			
		||||
            .map((cluster) => cluster.id);
 | 
			
		||||
        setExposedOriginDomainCopy('');
 | 
			
		||||
 | 
			
		||||
        switch (exposedOriginDomains.length) {
 | 
			
		||||
            case 0:
 | 
			
		||||
                break;
 | 
			
		||||
                return null;
 | 
			
		||||
            case 1:
 | 
			
		||||
                setExposedOriginDomainCopy(`${exposedOriginDomains[0]}.`);
 | 
			
		||||
                break;
 | 
			
		||||
                return setExposedOriginDomainCopy(`${exposedOriginDomains[0]}.`);
 | 
			
		||||
            case 2:
 | 
			
		||||
                setExposedOriginDomainCopy(
 | 
			
		||||
                return setExposedOriginDomainCopy(
 | 
			
		||||
                    `${exposedOriginDomains[0]} oraz ${exposedOriginDomains[1]}.`
 | 
			
		||||
                );
 | 
			
		||||
                break;
 | 
			
		||||
            case 3:
 | 
			
		||||
                setExposedOriginDomainCopy(
 | 
			
		||||
                return setExposedOriginDomainCopy(
 | 
			
		||||
                    `${exposedOriginDomains[0]}, ${exposedOriginDomains[1]} oraz ${exposedOriginDomains[2]}.`
 | 
			
		||||
                );
 | 
			
		||||
                break;
 | 
			
		||||
            default:
 | 
			
		||||
                setExposedOriginDomainCopy(
 | 
			
		||||
                return setExposedOriginDomainCopy(
 | 
			
		||||
                    `${exposedOriginDomains[0]}, ${exposedOriginDomains[1]} (i ${
 | 
			
		||||
                        exposedOriginDomains.length - 2 < 2 ? 2 : exposedOriginDomains.length - 2
 | 
			
		||||
                    } innych).`
 | 
			
		||||
                );
 | 
			
		||||
                break;
 | 
			
		||||
        }
 | 
			
		||||
    }, [eventCounts['*'], origin]);
 | 
			
		||||
 | 
			
		||||
    React.useEffect(() => {
 | 
			
		||||
        if (!origin) return;
 | 
			
		||||
        const cookieDomains = Object.values(getMemory().getClustersForOrigin(origin))
 | 
			
		||||
            .filter((cluster) => cluster.hasCookies())
 | 
			
		||||
            .sort((cluster1, cluster2) =>
 | 
			
		||||
                isDomainHighlySuspicious(cluster1.id)
 | 
			
		||||
                    ? -1
 | 
			
		||||
                    : isDomainHighlySuspicious(cluster2.id)
 | 
			
		||||
                      ? 1
 | 
			
		||||
                      : 0
 | 
			
		||||
            )
 | 
			
		||||
            .map((cluster) => cluster.id);
 | 
			
		||||
        setCookieDomainCopy('');
 | 
			
		||||
 | 
			
		||||
        switch (cookieDomains.length) {
 | 
			
		||||
            case 0:
 | 
			
		||||
                break;
 | 
			
		||||
                return null;
 | 
			
		||||
            case 1:
 | 
			
		||||
                setCookieDomainCopy(`${cookieDomains[0]}.`);
 | 
			
		||||
                break;
 | 
			
		||||
                return setCookieDomainCopy(`${cookieDomains[0]}.`);
 | 
			
		||||
            case 2:
 | 
			
		||||
                setCookieDomainCopy(`${cookieDomains[0]} oraz ${cookieDomains[1]}.`);
 | 
			
		||||
                break;
 | 
			
		||||
                return setCookieDomainCopy(`${cookieDomains[0]} oraz ${cookieDomains[1]}.`);
 | 
			
		||||
            case 3:
 | 
			
		||||
                setCookieDomainCopy(
 | 
			
		||||
                return setCookieDomainCopy(
 | 
			
		||||
                    `${cookieDomains[0]}, ${cookieDomains[1]} oraz ${cookieDomains[2]}.`
 | 
			
		||||
                );
 | 
			
		||||
                break;
 | 
			
		||||
            default:
 | 
			
		||||
                setCookieDomainCopy(
 | 
			
		||||
                return setCookieDomainCopy(
 | 
			
		||||
                    `${cookieDomains[0]}, ${cookieDomains[1]} (i ${
 | 
			
		||||
                        cookieDomains.length - 2 < 2 ? 2 : cookieDomains.length - 2
 | 
			
		||||
                    } innych).`
 | 
			
		||||
                );
 | 
			
		||||
                break;
 | 
			
		||||
        }
 | 
			
		||||
    }, [eventCounts['*'], origin]);
 | 
			
		||||
 | 
			
		||||
    React.useEffect(() => {
 | 
			
		||||
        if (!origin) return;
 | 
			
		||||
        for (const cluster of Object.values(getMemory().getClustersForOrigin(origin))) {
 | 
			
		||||
            if (cluster.hasMarks()) {
 | 
			
		||||
                return setMarksOccurrence(true);
 | 
			
		||||
@ -148,7 +115,6 @@ const Toolbar = () => {
 | 
			
		||||
    }, [eventCounts['*']]);
 | 
			
		||||
 | 
			
		||||
    function autoMark() {
 | 
			
		||||
        if (!origin) return;
 | 
			
		||||
        for (const cluster of Object.values(getMemory().getClustersForOrigin(origin))) {
 | 
			
		||||
            cluster.autoMark();
 | 
			
		||||
        }
 | 
			
		||||
@ -157,7 +123,7 @@ const Toolbar = () => {
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <div className="toolbar">
 | 
			
		||||
            <header className={origin ? 'header' : 'header header--no-page'}>
 | 
			
		||||
            <header className="header">
 | 
			
		||||
                <img src="../../assets/icon-addon.svg" height={32}></img>
 | 
			
		||||
                <div className="webpage-metadata">
 | 
			
		||||
                    {origin ? (
 | 
			
		||||
@ -176,107 +142,95 @@ const Toolbar = () => {
 | 
			
		||||
                ) : null}
 | 
			
		||||
            </header>
 | 
			
		||||
 | 
			
		||||
            {origin ? (
 | 
			
		||||
                <Fragment>
 | 
			
		||||
                    {' '}
 | 
			
		||||
                    <section className="summary">
 | 
			
		||||
                        <div className="counters-wrapper">
 | 
			
		||||
                            <div className="counters">
 | 
			
		||||
                                <div className="counter counter--cookies">
 | 
			
		||||
                                    <img
 | 
			
		||||
                                        src="/assets/icons/cookie.svg#color"
 | 
			
		||||
                                        width="24"
 | 
			
		||||
                                        height="24"
 | 
			
		||||
                                    />
 | 
			
		||||
                                    <span data-event={`${eventCounts['*']}`}>
 | 
			
		||||
                                        {
 | 
			
		||||
                                            Object.values(
 | 
			
		||||
                                                getMemory().getClustersForOrigin(origin)
 | 
			
		||||
                                            ).filter((cluster) => cluster.hasCookies()).length
 | 
			
		||||
                                        }
 | 
			
		||||
                                    </span>
 | 
			
		||||
                                </div>
 | 
			
		||||
                                <div className="counter counter--browser-history">
 | 
			
		||||
                                    <img
 | 
			
		||||
                                        src="/assets/icons/warning.svg#color"
 | 
			
		||||
                                        width="24"
 | 
			
		||||
                                        height="24"
 | 
			
		||||
                                    />
 | 
			
		||||
                                    <span data-event={`${eventCounts['*']}`}>
 | 
			
		||||
                                        {
 | 
			
		||||
                                            Object.values(
 | 
			
		||||
                                                getMemory().getClustersForOrigin(origin)
 | 
			
		||||
                                            ).filter((cluster) => cluster.exposesOrigin()).length
 | 
			
		||||
                                        }
 | 
			
		||||
                                    </span>
 | 
			
		||||
                                </div>
 | 
			
		||||
                            </div>
 | 
			
		||||
                            <div className="big-counter" data-event={`${eventCounts['*']}`}>
 | 
			
		||||
                                {Object.values(getMemory().getClustersForOrigin(origin)).length}
 | 
			
		||||
                            </div>
 | 
			
		||||
            <section className="summary">
 | 
			
		||||
                <div className="counters-wrapper">
 | 
			
		||||
                    <div className="counters">
 | 
			
		||||
                        <div className="counter counter--browser-history">
 | 
			
		||||
                            <img src="/assets/icons/warning.svg#color" width="24" height="24" />
 | 
			
		||||
                            <span data-event={`${eventCounts['*']}`}>
 | 
			
		||||
                                {
 | 
			
		||||
                                    Object.values(getMemory().getClustersForOrigin(origin)).filter(
 | 
			
		||||
                                        (cluster) => cluster.exposesOrigin()
 | 
			
		||||
                                    ).length
 | 
			
		||||
                                }
 | 
			
		||||
                            </span>
 | 
			
		||||
                        </div>
 | 
			
		||||
                        <span className="notice">Liczba wykrytych domen podmiotów trzecich</span>
 | 
			
		||||
                        <div className="counter counter--cookies">
 | 
			
		||||
                            <img src="/assets/icons/cookie.svg#color" width="24" height="24" />
 | 
			
		||||
                            <span data-event={`${eventCounts['*']}`}>
 | 
			
		||||
                                {
 | 
			
		||||
                                    Object.values(getMemory().getClustersForOrigin(origin)).filter(
 | 
			
		||||
                                        (cluster) => cluster.hasCookies()
 | 
			
		||||
                                    ).length
 | 
			
		||||
                                }
 | 
			
		||||
                            </span>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div className="big-counter" data-event={`${eventCounts['*']}`}>
 | 
			
		||||
                        {Object.values(getMemory().getClustersForOrigin(origin)).length}
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
                <span className="notice">Liczba wykrytych domen podmiotów trzecich</span>
 | 
			
		||||
            </section>
 | 
			
		||||
 | 
			
		||||
            <section className="details">
 | 
			
		||||
                {exposedOriginDomainCopy ? (
 | 
			
		||||
                    <p
 | 
			
		||||
                        data-event={`${eventCounts['*']}`}
 | 
			
		||||
                        title={Object.values(getMemory().getClustersForOrigin(origin))
 | 
			
		||||
                            .filter((cluster) => cluster.exposesOrigin())
 | 
			
		||||
                            .map((domain) => domain.id)
 | 
			
		||||
                            .join(', ')}
 | 
			
		||||
                    >
 | 
			
		||||
                        {first_sentence_cookie}
 | 
			
		||||
                        <strong>{exposedOriginDomainCopy}</strong>
 | 
			
		||||
                    </p>
 | 
			
		||||
                ) : null}
 | 
			
		||||
                {cookieDomainCopy ? (
 | 
			
		||||
                    <p
 | 
			
		||||
                        data-event={`${eventCounts['*']}`}
 | 
			
		||||
                        title={Object.values(getMemory().getClustersForOrigin(origin))
 | 
			
		||||
                            .filter((cluster) => cluster.hasCookies())
 | 
			
		||||
                            .map((domain) => domain.id)
 | 
			
		||||
                            .join(', ')}
 | 
			
		||||
                    >
 | 
			
		||||
                        {first_sentence_history}
 | 
			
		||||
                        <strong>{cookieDomainCopy}</strong>
 | 
			
		||||
                    </p>
 | 
			
		||||
                ) : null}
 | 
			
		||||
            </section>
 | 
			
		||||
 | 
			
		||||
            {exposedOriginDomainCopy || cookieDomainCopy ? (
 | 
			
		||||
                <Fragment>
 | 
			
		||||
                    <section className="about">
 | 
			
		||||
                        <p>
 | 
			
		||||
                            Takie przetwarzanie danych może być niezgodne z prawem. Przejdź do
 | 
			
		||||
                            analizy aby pomóc ustalić, czy ta strona nie narusza RODO.
 | 
			
		||||
                        </p>
 | 
			
		||||
                    </section>
 | 
			
		||||
                    <section className="details">
 | 
			
		||||
                        {cookieDomainCopy ? (
 | 
			
		||||
                            <p
 | 
			
		||||
                                data-event={`${eventCounts['*']}`}
 | 
			
		||||
                                title={Object.values(getMemory().getClustersForOrigin(origin))
 | 
			
		||||
                                    .filter((cluster) => cluster.hasCookies())
 | 
			
		||||
                                    .map((domain) => domain.id)
 | 
			
		||||
                                    .join(', ')}
 | 
			
		||||
                            >
 | 
			
		||||
                                {first_sentence_cookie}
 | 
			
		||||
                                <strong>{cookieDomainCopy}</strong>
 | 
			
		||||
                            </p>
 | 
			
		||||
                        ) : null}
 | 
			
		||||
                        {exposedOriginDomainCopy ? (
 | 
			
		||||
                            <p
 | 
			
		||||
                                data-event={`${eventCounts['*']}`}
 | 
			
		||||
                                title={Object.values(getMemory().getClustersForOrigin(origin))
 | 
			
		||||
                                    .filter((cluster) => cluster.exposesOrigin())
 | 
			
		||||
                                    .map((domain) => domain.id)
 | 
			
		||||
                                    .join(', ')}
 | 
			
		||||
                            >
 | 
			
		||||
                                {first_sentence_history}
 | 
			
		||||
                                <strong>{exposedOriginDomainCopy}</strong>
 | 
			
		||||
                            </p>
 | 
			
		||||
                        ) : null}
 | 
			
		||||
                    <section className="actions">
 | 
			
		||||
                        <button
 | 
			
		||||
                            className="button button--report"
 | 
			
		||||
                            onClick={() => {
 | 
			
		||||
                                autoMark();
 | 
			
		||||
                                window.open(
 | 
			
		||||
                                    `/components/sidebar/sidebar.html?origin=${origin}`,
 | 
			
		||||
                                    'new_tab'
 | 
			
		||||
                                );
 | 
			
		||||
                                window.close(); // close toolbar popup
 | 
			
		||||
                            }}
 | 
			
		||||
                        >
 | 
			
		||||
                            Przejdź do analizy
 | 
			
		||||
                        </button>
 | 
			
		||||
                    </section>
 | 
			
		||||
                    {exposedOriginDomainCopy || cookieDomainCopy ? (
 | 
			
		||||
                        <Fragment>
 | 
			
		||||
                            <section className="about">
 | 
			
		||||
                                <p>
 | 
			
		||||
                                    Takie przetwarzanie danych może być niezgodne z prawem. Przejdź
 | 
			
		||||
                                    do analizy aby pomóc ustalić, czy ta strona nie narusza RODO lub
 | 
			
		||||
                                    ustawy Prawo Komunikacji Elektronicznej.
 | 
			
		||||
                                </p>
 | 
			
		||||
                            </section>
 | 
			
		||||
                            <section className="actions">
 | 
			
		||||
                                <button
 | 
			
		||||
                                    className="button button--report"
 | 
			
		||||
                                    onClick={() => {
 | 
			
		||||
                                        autoMark();
 | 
			
		||||
                                        window.open(
 | 
			
		||||
                                            `/components/sidebar/sidebar.html?origin=${origin}`,
 | 
			
		||||
                                            'new_tab'
 | 
			
		||||
                                        );
 | 
			
		||||
                                        window.close(); // close toolbar popup
 | 
			
		||||
                                    }}
 | 
			
		||||
                                >
 | 
			
		||||
                                    Przejdź do analizy
 | 
			
		||||
                                </button>
 | 
			
		||||
                            </section>
 | 
			
		||||
                        </Fragment>
 | 
			
		||||
                    ) : (
 | 
			
		||||
                        <Fragment>
 | 
			
		||||
                            <section className="about about__no-errors">
 | 
			
		||||
                                <p>Nie znaleziono problemów na tej stronie.</p>
 | 
			
		||||
                            </section>
 | 
			
		||||
                        </Fragment>
 | 
			
		||||
                    )}
 | 
			
		||||
                </Fragment>
 | 
			
		||||
            ) : null}
 | 
			
		||||
            ) : (
 | 
			
		||||
                <Fragment>
 | 
			
		||||
                    <section className="about about__no-errors">
 | 
			
		||||
                        <p>Nie znaleziono problemów na tej stronie.</p>
 | 
			
		||||
                    </section>
 | 
			
		||||
                </Fragment>
 | 
			
		||||
            )}
 | 
			
		||||
        </div>
 | 
			
		||||
    );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										13
									
								
								diag.html
									
									
									
									
									
								
							
							
						
						@ -1,13 +0,0 @@
 | 
			
		||||
<!DOCTYPE html>
 | 
			
		||||
<html>
 | 
			
		||||
    <head>
 | 
			
		||||
        <title>RENTGEN DIAG</title>
 | 
			
		||||
    </head>
 | 
			
		||||
    <body>
 | 
			
		||||
        <div id="app"></div>
 | 
			
		||||
    </body>
 | 
			
		||||
    <script src="/node_modules/react/umd/react.production.min.js"></script>
 | 
			
		||||
    <script src="/node_modules/react-dom/umd/react-dom.production.min.js"></script>
 | 
			
		||||
    <script src="/node_modules/survey-react/survey.react.min.js"></script>
 | 
			
		||||
    <script src="./lib/diag.js"></script>
 | 
			
		||||
</html>
 | 
			
		||||
							
								
								
									
										65
									
								
								diag.tsx
									
									
									
									
									
								
							
							
						
						@ -1,65 +0,0 @@
 | 
			
		||||
import React, { Fragment } from 'react';
 | 
			
		||||
import ReactDOM from 'react-dom';
 | 
			
		||||
import EmailContent from './components/report-window/email-content';
 | 
			
		||||
 | 
			
		||||
import { makeFakeClusters } from './components/report-window/fake-clusters';
 | 
			
		||||
 | 
			
		||||
class ErrorBoundary extends React.Component<any, { hasError: boolean; error: any }> {
 | 
			
		||||
    constructor(props: any) {
 | 
			
		||||
        super(props);
 | 
			
		||||
        this.state = { hasError: false, error: null };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static getDerivedStateFromError(error: any) {
 | 
			
		||||
        return { hasError: true, error };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    render() {
 | 
			
		||||
        if (this.state.hasError) {
 | 
			
		||||
            return <h1>Something went wrong.</h1>;
 | 
			
		||||
        }
 | 
			
		||||
        return this.props.children;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function Diag() {
 | 
			
		||||
    const [json, setjson] = React.useState(
 | 
			
		||||
        JSON.stringify({ answers: { hosts: {} }, visited_url: '', fake_clusters_data: {} })
 | 
			
		||||
    );
 | 
			
		||||
    const { answers, visited_url, fake_clusters_data } = JSON.parse(json);
 | 
			
		||||
    const fake_clusters = makeFakeClusters(fake_clusters_data);
 | 
			
		||||
    return (
 | 
			
		||||
        <div style={{ display: 'grid', gridTemplateColumns: '50% 50%', minHeight: '100vh' }}>
 | 
			
		||||
            <div>
 | 
			
		||||
                <textarea
 | 
			
		||||
                    style={{ width: 'calc(100% - 50px)', height: '100%' }}
 | 
			
		||||
                    value={json}
 | 
			
		||||
                    onChange={(e) => {
 | 
			
		||||
                        setjson(e.target.value);
 | 
			
		||||
                    }}
 | 
			
		||||
                ></textarea>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div>
 | 
			
		||||
                <EmailContent
 | 
			
		||||
                    {...{
 | 
			
		||||
                        answers,
 | 
			
		||||
                        visited_url,
 | 
			
		||||
                        clusters: fake_clusters,
 | 
			
		||||
                        scrRequestPath: '/screenshots',
 | 
			
		||||
                        downloadFiles: () => {
 | 
			
		||||
                            alert('download!');
 | 
			
		||||
                        },
 | 
			
		||||
                        user_role: 'user',
 | 
			
		||||
                    }}
 | 
			
		||||
                />
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ReactDOM.render(
 | 
			
		||||
    <ErrorBoundary>
 | 
			
		||||
        <Diag />
 | 
			
		||||
    </ErrorBoundary>,
 | 
			
		||||
    document.getElementById('app')
 | 
			
		||||
);
 | 
			
		||||
@ -12,32 +12,32 @@ const watch = process.argv.includes('--watch') && {
 | 
			
		||||
let skipReactImports = {
 | 
			
		||||
    name: 'skipReactImports',
 | 
			
		||||
    setup(build) {
 | 
			
		||||
        build.onResolve({ filter: /^(react(-dom)?|survey-react)$/ }, (args) => {
 | 
			
		||||
        build.onResolve({ filter: /^react(-dom)?$/ }, (args) => {
 | 
			
		||||
            return {
 | 
			
		||||
                path: args.path,
 | 
			
		||||
                namespace: `globalExternal_${args.path}`,
 | 
			
		||||
            };
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        build.onLoad({ filter: /.*/, namespace: 'globalExternal_react' }, () => {
 | 
			
		||||
            return {
 | 
			
		||||
                contents: `module.exports = globalThis.React`,
 | 
			
		||||
                loader: 'js',
 | 
			
		||||
            };
 | 
			
		||||
        });
 | 
			
		||||
        build.onLoad(
 | 
			
		||||
            { filter: /.*/, namespace: 'globalExternal_react' },
 | 
			
		||||
            () => {
 | 
			
		||||
                return {
 | 
			
		||||
                    contents: `module.exports = globalThis.React`,
 | 
			
		||||
                    loader: 'js',
 | 
			
		||||
                };
 | 
			
		||||
            }
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        build.onLoad({ filter: /.*/, namespace: 'globalExternal_react-dom' }, () => {
 | 
			
		||||
            return {
 | 
			
		||||
                contents: `module.exports = globalThis.ReactDOM`,
 | 
			
		||||
                loader: 'js',
 | 
			
		||||
            };
 | 
			
		||||
        });
 | 
			
		||||
        build.onLoad({ filter: /.*/, namespace: 'globalExternal_survey-react' }, () => {
 | 
			
		||||
            return {
 | 
			
		||||
                contents: `module.exports = globalThis.Survey`,
 | 
			
		||||
                loader: 'js',
 | 
			
		||||
            };
 | 
			
		||||
        });
 | 
			
		||||
        build.onLoad(
 | 
			
		||||
            { filter: /.*/, namespace: 'globalExternal_react-dom' },
 | 
			
		||||
            () => {
 | 
			
		||||
                return {
 | 
			
		||||
                    contents: `module.exports = globalThis.ReactDOM`,
 | 
			
		||||
                    loader: 'js',
 | 
			
		||||
                };
 | 
			
		||||
            }
 | 
			
		||||
        );
 | 
			
		||||
    },
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -46,11 +46,11 @@ esbuild
 | 
			
		||||
        entryPoints: [
 | 
			
		||||
            'components/toolbar/toolbar.tsx',
 | 
			
		||||
            'components/sidebar/sidebar.tsx',
 | 
			
		||||
            'test.ts',
 | 
			
		||||
            'components/report-window/report-window.tsx',
 | 
			
		||||
            'background.ts',
 | 
			
		||||
            'diag.tsx',
 | 
			
		||||
            'styles/global.scss',
 | 
			
		||||
            'styles/fonts.scss',
 | 
			
		||||
            'styles/fonts.scss'
 | 
			
		||||
        ],
 | 
			
		||||
        bundle: true,
 | 
			
		||||
        // minify: true,
 | 
			
		||||
@ -59,9 +59,9 @@ esbuild
 | 
			
		||||
        plugins: [scss(), skipReactImports],
 | 
			
		||||
        define: {
 | 
			
		||||
            PLUGIN_NAME: '"Rentgen"',
 | 
			
		||||
            PLUGIN_URL: '"https://addons.mozilla.org/pl/firefox/addon/rentgen/"',
 | 
			
		||||
            PLUGIN_URL: '"https://git.internet-czas-dzialac.pl/icd/rentgen"',
 | 
			
		||||
        },
 | 
			
		||||
        external: ['react', 'react-dom', 'survey-react'],
 | 
			
		||||
        external: ['react', 'react-dom'],
 | 
			
		||||
        watch,
 | 
			
		||||
    })
 | 
			
		||||
    .then(() => console.log('Add-on was built'))
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,4 @@
 | 
			
		||||
'use strict';
 | 
			
		||||
import { DataLocation, StolenDataEntry } from './stolen-data-entry';
 | 
			
		||||
import { StolenDataEntry } from './stolen-data-entry';
 | 
			
		||||
import {
 | 
			
		||||
    flattenObjectEntries,
 | 
			
		||||
    getshorthost,
 | 
			
		||||
@ -76,13 +75,12 @@ export default class ExtendedRequest {
 | 
			
		||||
    public tabId: number;
 | 
			
		||||
    public url: string;
 | 
			
		||||
    public shorthost: string;
 | 
			
		||||
    public requestHeaders: { name: string; value?: string; binaryValue?: number[] }[] = [];
 | 
			
		||||
    public requestHeaders: Request['requestHeaders'] = [];
 | 
			
		||||
    public originalURL: string;
 | 
			
		||||
    public origin: string;
 | 
			
		||||
    public initialized = false;
 | 
			
		||||
    public stolenData: StolenDataEntry[] = [];
 | 
			
		||||
    public originalURL: string | null = null; // sometimes we can only establish that the given request applied to a certain origin, not a full URL from the address bar - in case of service workers, for example. Hence the null
 | 
			
		||||
    public originalPathname: string | null = null; // same as above
 | 
			
		||||
    public originalHost: string;
 | 
			
		||||
    public stolenData: StolenDataEntry[];
 | 
			
		||||
    public originalPathname: string;
 | 
			
		||||
    public requestBody: RequestBody;
 | 
			
		||||
 | 
			
		||||
    static by_id = {} as Record<string, ExtendedRequest>;
 | 
			
		||||
@ -93,67 +91,67 @@ export default class ExtendedRequest {
 | 
			
		||||
        this.url = data.url;
 | 
			
		||||
        this.shorthost = getshorthost(data.url);
 | 
			
		||||
        this.requestBody = ((data as any).requestBody as undefined | RequestBody) || {};
 | 
			
		||||
        if (this.url.includes('criteo')) {
 | 
			
		||||
            console.log(this);
 | 
			
		||||
        }
 | 
			
		||||
        ExtendedRequest.by_id[data.requestId] = this;
 | 
			
		||||
 | 
			
		||||
        this.data = Object.assign({}, data);
 | 
			
		||||
        (this.data as any).frameAncestors = [
 | 
			
		||||
            ...((data as any)?.frameAncestors?.map((e: any) => ({ url: e.url })) || []),
 | 
			
		||||
        ]; // making a copy?
 | 
			
		||||
            ...(data as any).frameAncestors.map((e: any) => ({ url: e.url })),
 | 
			
		||||
        ];
 | 
			
		||||
 | 
			
		||||
        // console.log('→→→',(this.data as any).frameAncestors, (data as any).frameAncestors);
 | 
			
		||||
 | 
			
		||||
        let url: string;
 | 
			
		||||
        let is_full_url = true;
 | 
			
		||||
        let url_comes_from: string;
 | 
			
		||||
        if (this.data.type === 'main_frame') {
 | 
			
		||||
            url = this.data.url;
 | 
			
		||||
            url_comes_from = 'main_frame';
 | 
			
		||||
        } else if (this.data.frameId === 0 && this.data.documentUrl) {
 | 
			
		||||
            url = this.data.documentUrl;
 | 
			
		||||
            url_comes_from = 'documentUrl';
 | 
			
		||||
            if (this.data.tabId == -1) {
 | 
			
		||||
                //a service worker?
 | 
			
		||||
                url_comes_from = 'documentUrl (webworker)';
 | 
			
		||||
                is_full_url = false;
 | 
			
		||||
            }
 | 
			
		||||
        } else if (
 | 
			
		||||
            (this.data as any)?.frameAncestors &&
 | 
			
		||||
            (this.data as any).frameAncestors[0] !== undefined
 | 
			
		||||
        ) {
 | 
			
		||||
            url = (this.data as any).frameAncestors.at(-1).url || '';
 | 
			
		||||
            url_comes_from = 'frameAncestors';
 | 
			
		||||
        } else {
 | 
			
		||||
            url = this.data.documentUrl || this.data.originUrl;
 | 
			
		||||
            url_comes_from = 'last resort';
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.originalURL = is_full_url ? url : null;
 | 
			
		||||
        this.origin = new URL(url).origin;
 | 
			
		||||
 | 
			
		||||
        this.originalHost = new URL(url).host;
 | 
			
		||||
        this.originalPathname = is_full_url ? new URL(url).pathname : null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    addHeaders(headers: Request['requestHeaders']) {
 | 
			
		||||
        this.requestHeaders = headers || [];
 | 
			
		||||
        this.requestHeaders = headers;
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    init() {
 | 
			
		||||
    async init() {
 | 
			
		||||
        await this.cacheOrigin();
 | 
			
		||||
        this.initialized = true;
 | 
			
		||||
        this.stolenData = this.getAllStolenData();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async cacheOrigin(): Promise<void> {
 | 
			
		||||
        let url: string;
 | 
			
		||||
        if (this.data.tabId && this.data.tabId >= 0) {
 | 
			
		||||
            const tab = await browser.tabs.get(this.data.tabId);
 | 
			
		||||
            url = tab.url;
 | 
			
		||||
        } else if (
 | 
			
		||||
            (this.data as any)?.frameAncestors &&
 | 
			
		||||
            (this.data as any).frameAncestors[0] !== undefined
 | 
			
		||||
        ) {
 | 
			
		||||
            url = (this.data as any).frameAncestors[0].url || '';
 | 
			
		||||
        } else {
 | 
			
		||||
            const headers = Object.fromEntries(
 | 
			
		||||
                this.requestHeaders.map(({ name, value }) => [name, value])
 | 
			
		||||
            );
 | 
			
		||||
            if (headers.Referer) {
 | 
			
		||||
                url = headers.Referer;
 | 
			
		||||
            } else {
 | 
			
		||||
                url = this.data.url;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.originalURL = url;
 | 
			
		||||
        this.origin = new URL(url).origin;
 | 
			
		||||
        this.originalPathname = new URL(url).pathname;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    isThirdParty() {
 | 
			
		||||
        const request_url = new URL(this.data.url);
 | 
			
		||||
        if (request_url.host.includes(this.originalHost)) {
 | 
			
		||||
        const origin_url = new URL(this.originalURL);
 | 
			
		||||
        if (request_url.host.includes(origin_url.host)) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
        if (getshorthost(request_url.host) == getshorthost(this.originalHost)) {
 | 
			
		||||
        if (getshorthost(request_url.host) == getshorthost(origin_url.host)) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
        return (
 | 
			
		||||
            request_url.origin != this.origin ||
 | 
			
		||||
            request_url.origin != origin_url.origin ||
 | 
			
		||||
            (this.data as any).urlClassification.thirdParty.length > 0
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
@ -164,12 +162,13 @@ export default class ExtendedRequest {
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    exposesOriginWhere(): null | DataLocation {
 | 
			
		||||
        const host = this.originalHost;
 | 
			
		||||
        const path = this.originalPathname || '/';
 | 
			
		||||
    exposesOrigin() {
 | 
			
		||||
        const url = new URL(this.originalURL);
 | 
			
		||||
        const host = url.host;
 | 
			
		||||
        const path = url.pathname;
 | 
			
		||||
        const shorthost = getshorthost(host);
 | 
			
		||||
        if (this.getReferer().includes(shorthost)) {
 | 
			
		||||
            return { path: this.url, source: 'header', key: 'Referer' };
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
        for (const entry of this.stolenData) {
 | 
			
		||||
            if (
 | 
			
		||||
@ -177,14 +176,10 @@ export default class ExtendedRequest {
 | 
			
		||||
                entry.value.includes(path) ||
 | 
			
		||||
                entry.value.includes(shorthost)
 | 
			
		||||
            ) {
 | 
			
		||||
                return entry.toDataLocation();
 | 
			
		||||
                return true;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    exposesOrigin() {
 | 
			
		||||
        return this.exposesOriginWhere() !== null;
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private getAllStolenData(): StolenDataEntry[] {
 | 
			
		||||
@ -222,10 +217,7 @@ export default class ExtendedRequest {
 | 
			
		||||
                if ((Array.isArray(value) && value.length === 1 && !value[0]) || !value) {
 | 
			
		||||
                    return ['requestBody', key];
 | 
			
		||||
                } else if (!Array.isArray(value)) {
 | 
			
		||||
                    return [
 | 
			
		||||
                        'raw',
 | 
			
		||||
                        String.fromCharCode.apply(null, Array.from(new Uint8Array(value.bytes))),
 | 
			
		||||
                    ];
 | 
			
		||||
                    return ['raw', String.fromCharCode.apply(null, new Uint8Array(value.bytes))];
 | 
			
		||||
                } else {
 | 
			
		||||
                    return [key, value || ''];
 | 
			
		||||
                }
 | 
			
		||||
@ -244,7 +236,7 @@ export default class ExtendedRequest {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    getCookie(): string {
 | 
			
		||||
        return this.requestHeaders.find((h) => h.name == 'Cookie')?.value || '';
 | 
			
		||||
        return this.requestHeaders.find((h) => h.name == 'Cookie')?.value;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    getPathParams(): StolenDataEntry[] {
 | 
			
		||||
@ -267,8 +259,8 @@ export default class ExtendedRequest {
 | 
			
		||||
    getQueryParams(): StolenDataEntry[] {
 | 
			
		||||
        const url = new URL(this.data.url);
 | 
			
		||||
        return flattenObjectEntries(
 | 
			
		||||
            (Array.from((url.searchParams as any).entries()) as [string, string][])
 | 
			
		||||
                .map(([key, value]: [string, string]) => [key, value || ''])
 | 
			
		||||
            Array.from((url.searchParams as any).entries())
 | 
			
		||||
                .map(([key, value]) => [key, value || ''])
 | 
			
		||||
                .map(([key, value]) => {
 | 
			
		||||
                    return [key, StolenDataEntry.parseValue(safeDecodeURIComponent(value))];
 | 
			
		||||
                })
 | 
			
		||||
@ -291,7 +283,7 @@ export default class ExtendedRequest {
 | 
			
		||||
                .map((header) => {
 | 
			
		||||
                    return [
 | 
			
		||||
                        header.name,
 | 
			
		||||
                        StolenDataEntry.parseValue(safeDecodeURIComponent(header.value || '')),
 | 
			
		||||
                        StolenDataEntry.parseValue(safeDecodeURIComponent(header.value)),
 | 
			
		||||
                    ];
 | 
			
		||||
                })
 | 
			
		||||
        ).map(([key, value]) => new StolenDataEntry(this, 'header', key, value));
 | 
			
		||||
@ -305,10 +297,6 @@ export default class ExtendedRequest {
 | 
			
		||||
        return this.stolenData.filter((data) => data.isMarked);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    unmarkAllEntries() {
 | 
			
		||||
        this.stolenData.forEach((entry) => entry.unmark());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    getHost() {
 | 
			
		||||
        return new URL(this.url).host;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -1,54 +0,0 @@
 | 
			
		||||
/**
 | 
			
		||||
 * Chrome Browser API Implementation
 | 
			
		||||
 * 
 | 
			
		||||
 * Mapuje Chrome chrome.* API na nasze ujednolicone BrowserAPI
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
import type { BrowserAPI } from './types';
 | 
			
		||||
 | 
			
		||||
// Chrome używa globalnego obiektu `chrome`
 | 
			
		||||
declare const chrome: any;
 | 
			
		||||
 | 
			
		||||
export const chromeAPI: BrowserAPI = {
 | 
			
		||||
    // Tabs API - chrome.tabs.* → tabs.*
 | 
			
		||||
    tabs: {
 | 
			
		||||
        query: chrome.tabs.query,
 | 
			
		||||
        onUpdated: {
 | 
			
		||||
            addListener: chrome.tabs.onUpdated.addListener,
 | 
			
		||||
            removeListener: chrome.tabs.onUpdated.removeListener,
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    // Badge API - Chrome używa action (nie browserAction)
 | 
			
		||||
    badge: {
 | 
			
		||||
        setBadgeText: chrome.action.setBadgeText,
 | 
			
		||||
        setTitle: chrome.action.setTitle,
 | 
			
		||||
        setBadgeBackgroundColor: chrome.action.setBadgeBackgroundColor,
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    // WebRequest API - chrome.webRequest.* → webRequest.*
 | 
			
		||||
    webRequest: {
 | 
			
		||||
        onBeforeRequest: {
 | 
			
		||||
            addListener: chrome.webRequest.onBeforeRequest.addListener,
 | 
			
		||||
        },
 | 
			
		||||
        onBeforeSendHeaders: {
 | 
			
		||||
            addListener: chrome.webRequest.onBeforeSendHeaders.addListener,
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    // Cookies API - chrome.cookies.* → cookies.*
 | 
			
		||||
    cookies: {
 | 
			
		||||
        getAll: chrome.cookies.getAll,
 | 
			
		||||
        remove: chrome.cookies.remove,
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    // Extension API - chrome.extension.* → extension.*
 | 
			
		||||
    extension: {
 | 
			
		||||
        getBackgroundPage: chrome.extension.getBackgroundPage,
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    // Windows API - chrome.windows.* → windows.*  
 | 
			
		||||
    windows: {
 | 
			
		||||
        WINDOW_ID_CURRENT: chrome.windows.WINDOW_ID_CURRENT,
 | 
			
		||||
    },
 | 
			
		||||
};
 | 
			
		||||
@ -1,54 +0,0 @@
 | 
			
		||||
/**
 | 
			
		||||
 * Firefox Browser API Implementation
 | 
			
		||||
 * 
 | 
			
		||||
 * Mapuje Firefox browser.* API na nasze ujednolicone BrowserAPI
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
import type { BrowserAPI } from './types';
 | 
			
		||||
 | 
			
		||||
// Firefox używa globalnego obiektu `browser`
 | 
			
		||||
declare const browser: any;
 | 
			
		||||
 | 
			
		||||
export const firefoxAPI: BrowserAPI = {
 | 
			
		||||
    // Tabs API - direct mapping
 | 
			
		||||
    tabs: {
 | 
			
		||||
        query: browser.tabs.query,
 | 
			
		||||
        onUpdated: {
 | 
			
		||||
            addListener: browser.tabs.onUpdated.addListener,
 | 
			
		||||
            removeListener: browser.tabs.onUpdated.removeListener,
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    // Badge API - Firefox używa browserAction
 | 
			
		||||
    badge: {
 | 
			
		||||
        setBadgeText: browser.browserAction.setBadgeText,
 | 
			
		||||
        setTitle: browser.browserAction.setTitle,
 | 
			
		||||
        setBadgeBackgroundColor: browser.browserAction.setBadgeBackgroundColor,
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    // WebRequest API - direct mapping
 | 
			
		||||
    webRequest: {
 | 
			
		||||
        onBeforeRequest: {
 | 
			
		||||
            addListener: browser.webRequest.onBeforeRequest.addListener,
 | 
			
		||||
        },
 | 
			
		||||
        onBeforeSendHeaders: {
 | 
			
		||||
            addListener: browser.webRequest.onBeforeSendHeaders.addListener,
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    // Cookies API - direct mapping
 | 
			
		||||
    cookies: {
 | 
			
		||||
        getAll: browser.cookies.getAll,
 | 
			
		||||
        remove: browser.cookies.remove,
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    // Extension API - direct mapping
 | 
			
		||||
    extension: {
 | 
			
		||||
        getBackgroundPage: browser.extension.getBackgroundPage,
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    // Windows API - direct mapping
 | 
			
		||||
    windows: {
 | 
			
		||||
        WINDOW_ID_CURRENT: browser.windows.WINDOW_ID_CURRENT,
 | 
			
		||||
    },
 | 
			
		||||
};
 | 
			
		||||
@ -1,27 +0,0 @@
 | 
			
		||||
/**
 | 
			
		||||
 * Browser API Abstraction - Main Export
 | 
			
		||||
 * 
 | 
			
		||||
 * Eksportuje właściwą implementację na podstawie TARGET build variable
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
import type { BrowserAPI } from './types';
 | 
			
		||||
 | 
			
		||||
// Build-time selection of browser API implementation
 | 
			
		||||
let browserApi: BrowserAPI;
 | 
			
		||||
 | 
			
		||||
// TARGET jest ustawiane przez esbuild.config.js na podstawie npm script
 | 
			
		||||
if (process.env.TARGET === 'chrome') {
 | 
			
		||||
    // Chrome build - używamy chrome adapter
 | 
			
		||||
    const { chromeAPI } = require('./chrome');
 | 
			
		||||
    browserApi = chromeAPI;
 | 
			
		||||
} else {
 | 
			
		||||
    // Firefox build (default) - używamy firefox adapter  
 | 
			
		||||
    const { firefoxAPI } = require('./firefox');
 | 
			
		||||
    browserApi = firefoxAPI;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Eksportuj jako default export
 | 
			
		||||
export default browserApi;
 | 
			
		||||
 | 
			
		||||
// Re-export typów dla wygody
 | 
			
		||||
export * from './types';
 | 
			
		||||
@ -1,124 +0,0 @@
 | 
			
		||||
/**
 | 
			
		||||
 * Browser API Abstraction - Typy na podstawie faktycznego użycia w kodzie
 | 
			
		||||
 * 
 | 
			
		||||
 * Przeanalizowane pliki:
 | 
			
		||||
 * - util.ts: tabs.query, Tab.id
 | 
			
		||||
 * - tab-dropdown.tsx: tabs.query, Tab.id, Tab.title  
 | 
			
		||||
 * - toolbar.tsx: tabs.query, tabs.onUpdated, Tab.url, windows.WINDOW_ID_CURRENT
 | 
			
		||||
 * - memory.ts: browserAction.*, webRequest.*, cookies.*, extension.*
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
// === Tab API (util.ts, tab-dropdown.tsx, toolbar.tsx) ===
 | 
			
		||||
export interface Tab {
 | 
			
		||||
    id?: number;      // util.ts: tab.id, tab-dropdown.tsx: tab.id
 | 
			
		||||
    title?: string;   // tab-dropdown.tsx: tab.title
 | 
			
		||||
    url?: string;     // toolbar.tsx: tab.url
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface TabQuery {
 | 
			
		||||
    currentWindow?: boolean; // util.ts, tab-dropdown.tsx
 | 
			
		||||
    active?: boolean;        // toolbar.tsx
 | 
			
		||||
    windowId?: number;       // toolbar.tsx
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// === Badge/BrowserAction API (memory.ts) ===
 | 
			
		||||
export interface BadgeTextDetails {
 | 
			
		||||
    text: string;    // memory.ts: setBadgeText
 | 
			
		||||
    tabId?: number;  // memory.ts: setBadgeText (optional)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface BadgeTitleDetails {
 | 
			
		||||
    title: string;   // memory.ts: setTitle
 | 
			
		||||
    tabId?: number;  // memory.ts: setTitle (optional)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface BadgeColorDetails {
 | 
			
		||||
    color: string;   // memory.ts: setBadgeBackgroundColor
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// === WebRequest API (memory.ts) ===
 | 
			
		||||
export interface RequestDetails {
 | 
			
		||||
    requestId: string;                    // memory.ts: request.requestId
 | 
			
		||||
    requestHeaders?: RequestHeader[];     // memory.ts: request.requestHeaders
 | 
			
		||||
    // Note: ExtendedRequest konstruktor używa więcej pól, 
 | 
			
		||||
    // ale tu skupiamy się na tym co bezpośrednio używa browser API
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface RequestHeader {
 | 
			
		||||
    name: string;
 | 
			
		||||
    value?: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface RequestFilter {
 | 
			
		||||
    urls: string[];  // memory.ts: { urls: ['<all_urls>'] }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export type RequestListener = (details: RequestDetails) => void;
 | 
			
		||||
 | 
			
		||||
// === Cookies API (memory.ts) ===
 | 
			
		||||
export interface Cookie {
 | 
			
		||||
    name: string;    // memory.ts: cookie.name
 | 
			
		||||
    domain: string;  // memory.ts: cookie.domain
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface CookieQuery {
 | 
			
		||||
    domain?: string; // memory.ts: { domain: shorthost }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface CookieRemove {
 | 
			
		||||
    name: string;    // memory.ts: { name: cookie.name, url: ... }
 | 
			
		||||
    url: string;     // memory.ts: { url: `https://${cookie.domain}` }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// === Main Browser API Interface ===
 | 
			
		||||
export interface BrowserAPI {
 | 
			
		||||
    // Tabs API
 | 
			
		||||
    tabs: {
 | 
			
		||||
        query(queryInfo: TabQuery): Promise<Tab[]>;
 | 
			
		||||
        onUpdated: {
 | 
			
		||||
            addListener(listener: (tabId: number, changeInfo: any, tab: Tab) => void): void;
 | 
			
		||||
            removeListener(listener: (tabId: number, changeInfo: any, tab: Tab) => void): void;
 | 
			
		||||
        };
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    // Badge API (Firefox: browserAction, Chrome: action)
 | 
			
		||||
    badge: {
 | 
			
		||||
        setBadgeText(details: BadgeTextDetails): void;
 | 
			
		||||
        setTitle(details: BadgeTitleDetails): void;
 | 
			
		||||
        setBadgeBackgroundColor(details: BadgeColorDetails): void;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    // WebRequest API
 | 
			
		||||
    webRequest: {
 | 
			
		||||
        onBeforeRequest: {
 | 
			
		||||
            addListener(
 | 
			
		||||
                listener: RequestListener,
 | 
			
		||||
                filter: RequestFilter,
 | 
			
		||||
                extraInfoSpec?: string[]
 | 
			
		||||
            ): void;
 | 
			
		||||
        };
 | 
			
		||||
        onBeforeSendHeaders: {
 | 
			
		||||
            addListener(
 | 
			
		||||
                listener: RequestListener,
 | 
			
		||||
                filter: RequestFilter,
 | 
			
		||||
                extraInfoSpec?: string[]
 | 
			
		||||
            ): void;
 | 
			
		||||
        };
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    // Cookies API
 | 
			
		||||
    cookies: {
 | 
			
		||||
        getAll(details: CookieQuery): Promise<Cookie[]>;
 | 
			
		||||
        remove(details: CookieRemove): Promise<Cookie | null>;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    // Extension API
 | 
			
		||||
    extension: {
 | 
			
		||||
        getBackgroundPage(): Window | null;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    // Windows API
 | 
			
		||||
    windows: {
 | 
			
		||||
        WINDOW_ID_CURRENT: number;
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
@ -1,10 +1,10 @@
 | 
			
		||||
{
 | 
			
		||||
    "description": "Rentgen to wtyczka dla przeglądarek opartych o Firefoxa, która automatycznie wizualizuje, jakie dane zostały ~~wykradzione~~ wysłane do podmiotów trzecich przez odwiedzane strony.",
 | 
			
		||||
    "description": "Rentgen to wtyczka dla przeglądarek opartych o Firefoxa, która automatycznie wizualizuje, jakie dane zostały wykradzione wysłane do podmiotów trzecich przez odwiedzane strony.",
 | 
			
		||||
    "manifest_version": 2,
 | 
			
		||||
    "name": "Rentgen",
 | 
			
		||||
    "short_name": "Rentgen",
 | 
			
		||||
    "version": "0.2.1",
 | 
			
		||||
    "author": "Kuba Orlik, Arkadiusz Wieczorek (Internet. Time to act! Foundation)",
 | 
			
		||||
    "version": "0.0.3",
 | 
			
		||||
    "author": "Kuba Orlik, Arkadiusz Wieczorek (Internet. Czas działać!)",
 | 
			
		||||
    "homepage_url": "https://git.internet-czas-dzialac.pl/icd/rentgen",
 | 
			
		||||
    "background": {
 | 
			
		||||
        "scripts": ["lib/background.js"]
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										17
									
								
								memory.ts
									
									
									
									
									
								
							
							
						
						@ -1,9 +1,9 @@
 | 
			
		||||
import ExtendedRequest from './extended-request';
 | 
			
		||||
import { getshorthost } from './util';
 | 
			
		||||
import { getshorthost, makeThrottle } from './util';
 | 
			
		||||
import { RequestCluster } from './request-cluster';
 | 
			
		||||
import { SaferEmitter } from './safer-emitter';
 | 
			
		||||
 | 
			
		||||
function setDomainsCount(counter: number, tabId: number) {
 | 
			
		||||
function setDomainsNumber(counter: number, tabId: number) {
 | 
			
		||||
    browser.browserAction.setBadgeText({ text: counter < 0 ? '0' : counter.toString(), tabId });
 | 
			
		||||
    browser.browserAction.setTitle({
 | 
			
		||||
        title: 'Rentgen',
 | 
			
		||||
@ -35,12 +35,10 @@ export default class Memory extends SaferEmitter {
 | 
			
		||||
            ? browser.browserAction.setBadgeBackgroundColor({ color: '#ff726b' })
 | 
			
		||||
            : browser.browserAction.setBadgeBackgroundColor({ color: '#ffb900' });
 | 
			
		||||
 | 
			
		||||
        if (request.tabId >= 0) {
 | 
			
		||||
            setDomainsCount(
 | 
			
		||||
                Object.values(this.getClustersForOrigin(request.origin)).length,
 | 
			
		||||
                request.tabId
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
        setDomainsNumber(
 | 
			
		||||
            Object.values(this.getClustersForOrigin(request.origin)).length,
 | 
			
		||||
            request.tabId
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    constructor() {
 | 
			
		||||
@ -67,7 +65,7 @@ export default class Memory extends SaferEmitter {
 | 
			
		||||
 | 
			
		||||
    emit(eventName: string, data = 'any'): boolean {
 | 
			
		||||
        setTimeout(() => super.emit(eventName, data), 0);
 | 
			
		||||
        return true;
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    getClustersForOrigin(origin: string): Record<string, RequestCluster> {
 | 
			
		||||
@ -78,6 +76,7 @@ export default class Memory extends SaferEmitter {
 | 
			
		||||
        if (shorthost) {
 | 
			
		||||
            const cookies = await browser.cookies.getAll({ domain: shorthost });
 | 
			
		||||
            for (const cookie of cookies) {
 | 
			
		||||
                console.log('removing cookie', cookie.name, 'from', cookie.domain);
 | 
			
		||||
                await browser.cookies.remove({
 | 
			
		||||
                    name: cookie.name,
 | 
			
		||||
                    url: `https://${cookie.domain}`,
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										4763
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
							
								
								
									
										19
									
								
								package.json
									
									
									
									
									
								
							
							
						
						@ -1,25 +1,15 @@
 | 
			
		||||
{
 | 
			
		||||
    "name": "rentgen",
 | 
			
		||||
    "version": "0.2.1",
 | 
			
		||||
    "description": "Rentgen is an add-on prepared for both Firefox-based and Chromium-based browsers. This extension will automatically visualize all the data that a given website sends to third parties.",
 | 
			
		||||
    "version": "0.0.3",
 | 
			
		||||
    "description": "A simple Firefox extension that visualizes all the data that a given website sends to third parties.",
 | 
			
		||||
    "main": "esbuild.config.js",
 | 
			
		||||
    "type": "module",
 | 
			
		||||
    "scripts": {
 | 
			
		||||
        "build": "node esbuild.config.js",
 | 
			
		||||
        "build:firefox": "TARGET=firefox node esbuild.config.js",
 | 
			
		||||
        "build:chrome": "TARGET=chrome node esbuild.config.js",
 | 
			
		||||
        "watch": "node esbuild.config.js --watch",
 | 
			
		||||
        "watch:firefox": "TARGET=firefox node esbuild.config.js --watch",
 | 
			
		||||
        "watch:chrome": "TARGET=chrome node esbuild.config.js --watch",
 | 
			
		||||
        "ext-test": "web-ext run",
 | 
			
		||||
        "build-addon": "npm i && npm run build && npm run create-package",
 | 
			
		||||
        "build-addon:firefox": "npm i && npm run build:firefox && npm run create-package:firefox",
 | 
			
		||||
        "build-addon:chrome": "npm i && npm run build:chrome && npm run create-package:chrome",
 | 
			
		||||
        "create-package": "web-ext build --ignore-files '!**/node_modules' '!**/node_modules/**/react-dom' '!**/node_modules/**/react-dom/umd' '!**/node_modules/**/*/react-dom.production.min.js' '!**/node_modules/**/react' '!**/node_modules/**/react/umd' '!**/node_modules/**/*/react.production.min.js' '!**/node_modules/**/survey-react'  '!**/node_modules/**/survey-react/*.min.js' '!**/node_modules/**/survey-react/*.min.css' --overwrite-dest",
 | 
			
		||||
        "create-package:firefox": "web-ext build --overwrite-dest --artifacts-dir ../web-ext-artifacts",
 | 
			
		||||
        "create-package:chrome": "cd dist-chrome && 7z a -tzip ../web-ext-artifacts/rentgen-chrome-0.1.10.zip * && cd ..",
 | 
			
		||||
        "typecheck": "tsc --noEmit",
 | 
			
		||||
        "lint": "web-ext lint"
 | 
			
		||||
        "create-package": "web-ext build",
 | 
			
		||||
        "typecheck": "tsc --noEmit"
 | 
			
		||||
    },
 | 
			
		||||
    "repository": {
 | 
			
		||||
        "type": "git",
 | 
			
		||||
@ -51,7 +41,6 @@
 | 
			
		||||
        "tracking"
 | 
			
		||||
    ],
 | 
			
		||||
    "devDependencies": {
 | 
			
		||||
        "@types/chrome": "^0.1.3",
 | 
			
		||||
        "@types/events": "^3.0.0",
 | 
			
		||||
        "@types/react-dom": "^17.0.9",
 | 
			
		||||
        "addons-linter": "^4.7.0",
 | 
			
		||||
 | 
			
		||||
@ -1,7 +1,7 @@
 | 
			
		||||
import { FakeRequestClusterData } from './components/report-window/fake-clusters';
 | 
			
		||||
import { EventEmitter } from 'events';
 | 
			
		||||
import ExtendedRequest from './extended-request';
 | 
			
		||||
import { SaferEmitter } from './safer-emitter';
 | 
			
		||||
import { DataLocation, Sources, StolenDataEntry } from './stolen-data-entry';
 | 
			
		||||
import { Sources, StolenDataEntry } from './stolen-data-entry';
 | 
			
		||||
 | 
			
		||||
import { allSubhosts, isSameURL, reduceConcat, unique } from './util';
 | 
			
		||||
 | 
			
		||||
@ -10,20 +10,13 @@ const source_priority: Array<Sources> = ['cookie', 'pathname', 'queryparams', 'h
 | 
			
		||||
export class RequestCluster extends SaferEmitter {
 | 
			
		||||
    public requests: ExtendedRequest[] = [];
 | 
			
		||||
    public representativeStolenData: StolenDataEntry[] = [];
 | 
			
		||||
    public expanded: boolean = false;
 | 
			
		||||
    public lastModified: number = 0;
 | 
			
		||||
    public lastFullUrl: string | null = null;
 | 
			
		||||
    public expanded: boolean;
 | 
			
		||||
    constructor(public id: string) {
 | 
			
		||||
        super();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    add(request: ExtendedRequest) {
 | 
			
		||||
        this.requests.push(request);
 | 
			
		||||
        this.emit('change');
 | 
			
		||||
        this.lastModified = Date.now();
 | 
			
		||||
        if (request.originalURL) {
 | 
			
		||||
            this.lastFullUrl = request.originalURL;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    toggleExpanded(state: boolean) {
 | 
			
		||||
@ -172,13 +165,7 @@ export class RequestCluster extends SaferEmitter {
 | 
			
		||||
        return this.requests.map((request) => request.getMarkedEntries()).reduce(reduceConcat, []);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    exposesOriginWhere(): DataLocation[] {
 | 
			
		||||
        return this.requests
 | 
			
		||||
            .map((request) => request.exposesOriginWhere())
 | 
			
		||||
            .filter((l) => l !== null) as DataLocation[];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    exposesOrigin(): boolean {
 | 
			
		||||
    exposesOrigin() {
 | 
			
		||||
        return this.requests.some((request) => request.exposesOrigin());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -191,32 +178,8 @@ export class RequestCluster extends SaferEmitter {
 | 
			
		||||
 | 
			
		||||
    undoMark() {
 | 
			
		||||
        this.calculateRepresentativeStolenData();
 | 
			
		||||
        this.requests.forEach((request) => request.unmarkAllEntries());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    getDataTypeDescription(noun = 'Twojej') {
 | 
			
		||||
        let types_of_data: string[] = [];
 | 
			
		||||
        if (this.exposesOrigin()) {
 | 
			
		||||
            types_of_data.push(`część ${noun} historii przeglądania`);
 | 
			
		||||
        }
 | 
			
		||||
        if (this.hasMarkedCookies()) {
 | 
			
		||||
            types_of_data.push('unikalne ID z cookies');
 | 
			
		||||
        }
 | 
			
		||||
        if (types_of_data.length > 1) {
 | 
			
		||||
            types_of_data[types_of_data.length - 1] =
 | 
			
		||||
                'oraz ' + types_of_data[types_of_data.length - 1];
 | 
			
		||||
        }
 | 
			
		||||
        return types_of_data.join(', ');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    makeDataForFake(): FakeRequestClusterData {
 | 
			
		||||
        return {
 | 
			
		||||
            id: this.id,
 | 
			
		||||
            hasCookies: this.hasCookies(),
 | 
			
		||||
            hasMarkedCookies: this.hasMarkedCookies(),
 | 
			
		||||
            hasMarks: this.hasMarks(),
 | 
			
		||||
            exposesOriginWhere: this.exposesOriginWhere(),
 | 
			
		||||
            exposesOrigin: this.exposesOrigin(),
 | 
			
		||||
        };
 | 
			
		||||
        this.representativeStolenData.forEach((entry) => {
 | 
			
		||||
            entry.unmark();
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -48,6 +48,7 @@ export class SaferEmitter extends EventEmitter {
 | 
			
		||||
                        Reflect.apply(listener, this, args);
 | 
			
		||||
                    } catch (error) {
 | 
			
		||||
                        console.error(error);
 | 
			
		||||
                        debugger;
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -1,3 +1,4 @@
 | 
			
		||||
import { EventEmitter } from 'events';
 | 
			
		||||
import ExtendedRequest, { HAREntry } from './extended-request';
 | 
			
		||||
import { SaferEmitter } from './safer-emitter';
 | 
			
		||||
 | 
			
		||||
@ -33,12 +34,6 @@ const id = (function* id() {
 | 
			
		||||
 | 
			
		||||
export type DecodingSchema = 'base64' | 'raw';
 | 
			
		||||
 | 
			
		||||
export type DataLocation = {
 | 
			
		||||
    path: string;
 | 
			
		||||
    source: Sources;
 | 
			
		||||
    key: string;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export class StolenDataEntry extends SaferEmitter {
 | 
			
		||||
    public isIAB = false;
 | 
			
		||||
    public id: number;
 | 
			
		||||
@ -64,10 +59,11 @@ export class StolenDataEntry extends SaferEmitter {
 | 
			
		||||
    getPriority() {
 | 
			
		||||
        let priority = 0;
 | 
			
		||||
        priority += Math.min(this.value.length, 50);
 | 
			
		||||
        if (this.value.includes(this.request.originalHost)) {
 | 
			
		||||
        const url = new URL(this.request.originalURL);
 | 
			
		||||
        if (this.value.includes(url.host)) {
 | 
			
		||||
            priority += 100;
 | 
			
		||||
        }
 | 
			
		||||
        if (this.request.originalPathname && this.value.includes(this.request.originalPathname)) {
 | 
			
		||||
        if (this.value.includes(url.pathname)) {
 | 
			
		||||
            priority += 100;
 | 
			
		||||
        }
 | 
			
		||||
        if (this.source === 'cookie') {
 | 
			
		||||
@ -137,7 +133,7 @@ export class StolenDataEntry extends SaferEmitter {
 | 
			
		||||
        } else if (value === null) {
 | 
			
		||||
            return 'null';
 | 
			
		||||
        } else {
 | 
			
		||||
            return (value as any).toString();
 | 
			
		||||
            return value.toString();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -242,14 +238,10 @@ export class StolenDataEntry extends SaferEmitter {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    exposesPath() {
 | 
			
		||||
        const pathname = this.request.originalPathname;
 | 
			
		||||
        if (pathname === null) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
        return (
 | 
			
		||||
            this.request.originalPathname !== '/' &&
 | 
			
		||||
            [this.value, safeDecodeURIComponent(this.value)].some((haystack) =>
 | 
			
		||||
                haystack.includes(pathname)
 | 
			
		||||
                haystack.includes(this.request.originalPathname)
 | 
			
		||||
            )
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
@ -259,12 +251,4 @@ export class StolenDataEntry extends SaferEmitter {
 | 
			
		||||
            haystack.includes(getshorthost(this.request.origin))
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    toDataLocation(): DataLocation {
 | 
			
		||||
        return {
 | 
			
		||||
            path: this.request.url,
 | 
			
		||||
            source: this.source,
 | 
			
		||||
            key: this.name,
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										15
									
								
								test.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,15 @@
 | 
			
		||||
import ExtendedRequest from "./extended-request";
 | 
			
		||||
import { StolenDataEntry } from "./stolen-data-entry";
 | 
			
		||||
import { flattenObject, maskString } from "./util";
 | 
			
		||||
 | 
			
		||||
console.log(flattenObject({ a: { b: { c: [1, 2, 3] } } }));
 | 
			
		||||
 | 
			
		||||
console.log(maskString("abcdefghijklmnopqrstuvwxyz", 1 / 3, 5));
 | 
			
		||||
 | 
			
		||||
console.log(maskString("abcdefghijklmnopqrstuvwxyz", 1, 30));
 | 
			
		||||
 | 
			
		||||
console.log(
 | 
			
		||||
  StolenDataEntry.parseValue(
 | 
			
		||||
    `[{"@context":"https://schema.org/","@type":"Product","image":["/medias/sys_master/root/images/h95/h8b/10873724928030/oppo-reno6-black-front.png","/medias/sys_master/root/images/h15/hb9/10873725681694/oppo-reno6-black-side.png","/medias/sys_master/root/images/hf8/h81/10873728729118/oppo-reno6-black-back.png"],"description":"OPPO Reno6 5G bez umowy lub w abonamencie w sklepie Orange. Zamów do domu lub odbierz w salonie w 24h. Transport gratis!","sku":"1100027218","brand":{"@type":"Thing","name":"OPPO"},"offers":{"@type":"Offer","priceCurrency":"PLN","priceValidUntil":"2099-12-31T00:00:00","itemCondition":"https://schema.org/UsedCondition","availability":"https://schema.org/InStock","url":"/esklep/smartfony/oppo/oppo-reno6-5g","seller":{"@type":"Organization","name":"Orange Polska S.A"},"price":"2199"},"name":"OPPO Reno6 5G"},{"@context":"http://schema.org/","@type":"Product","name":"OPPO Reno6 5G","description":"null","offers":{"@type":"Offer","priceCurrency":"PLN","price":"1248.00"}}]`
 | 
			
		||||
  )
 | 
			
		||||
);
 | 
			
		||||
@ -8,9 +8,6 @@
 | 
			
		||||
        "typeRoots": ["node_modules/@types", "node_modules/web-ext-types"],
 | 
			
		||||
        "target": "es2019",
 | 
			
		||||
        "outDir": "lib",
 | 
			
		||||
        "skipLibCheck": true,
 | 
			
		||||
        "strictNullChecks": true,
 | 
			
		||||
        "strict": true,
 | 
			
		||||
        "alwaysStrict": true
 | 
			
		||||
        "skipLibCheck": true
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										65
									
								
								util.ts
									
									
									
									
									
								
							
							
						
						@ -1,6 +1,5 @@
 | 
			
		||||
import { EventEmitter } from 'events';
 | 
			
		||||
import React from 'react';
 | 
			
		||||
import { DataLocation, Sources } from './stolen-data-entry';
 | 
			
		||||
 | 
			
		||||
export type Unpromisify<T> = T extends Promise<infer R> ? R : T;
 | 
			
		||||
export type Unarray<T> = T extends Array<infer R> ? R : T;
 | 
			
		||||
@ -37,11 +36,7 @@ export function getshorthost(host: string) {
 | 
			
		||||
        .replace(/^.*:\/\//, '')
 | 
			
		||||
        .replace(/\/.*$/, '')
 | 
			
		||||
        .split('.');
 | 
			
		||||
    const second_last = parts.at(-2);
 | 
			
		||||
    if (!second_last) {
 | 
			
		||||
        throw new Error('url too short?');
 | 
			
		||||
    }
 | 
			
		||||
    let lookback = !['co', 'com'].includes(second_last) ? -2 : -3;
 | 
			
		||||
    let lookback = !['co', 'com'].includes(parts.at(-2)) ? -2 : -3;
 | 
			
		||||
    if (parts.at(-2) == 'doubleclick' || parts.at(-2) == 'google') {
 | 
			
		||||
        lookback = -4; // to distinguish between google ads and stats
 | 
			
		||||
    } else if (parts.at(-2) == 'google') {
 | 
			
		||||
@ -78,7 +73,7 @@ export function useEmitter(
 | 
			
		||||
export function parseCookie(cookie: string): Record<string, string> {
 | 
			
		||||
    return cookie
 | 
			
		||||
        .split(';')
 | 
			
		||||
        .map((l) => [l.slice(0, l.indexOf('=')), l.slice(l.indexOf('=') + 1)])
 | 
			
		||||
        .map((l) => l.split('='))
 | 
			
		||||
        .reduce(
 | 
			
		||||
            (acc, [key, value]) => ({
 | 
			
		||||
                ...acc,
 | 
			
		||||
@ -94,7 +89,7 @@ export async function getTabByID(id: number) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function parseToObject(str: unknown): Record<string | symbol, unknown> {
 | 
			
		||||
    let result: Record<string | symbol, unknown> = {};
 | 
			
		||||
    let result: Record<string | symbol, unknown>;
 | 
			
		||||
    let original_string: string;
 | 
			
		||||
    if (typeof str === 'string') {
 | 
			
		||||
        original_string = str;
 | 
			
		||||
@ -102,8 +97,6 @@ export function parseToObject(str: unknown): Record<string | symbol, unknown> {
 | 
			
		||||
    } else if (typeof str == 'object') {
 | 
			
		||||
        result = str as Record<string | symbol, unknown>;
 | 
			
		||||
        original_string = (result[Symbol.for('originalString')] as string) || JSON.stringify(str);
 | 
			
		||||
    } else {
 | 
			
		||||
        return result;
 | 
			
		||||
    }
 | 
			
		||||
    result[Symbol.for('originalString')] = original_string;
 | 
			
		||||
    return result;
 | 
			
		||||
@ -156,13 +149,9 @@ export function getDate() {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function toBase64(file: File): Promise<string> {
 | 
			
		||||
    return new Promise((resolve, reject) => {
 | 
			
		||||
    return new Promise((resolve) => {
 | 
			
		||||
        const FR = new FileReader();
 | 
			
		||||
        FR.addEventListener('load', (e) => {
 | 
			
		||||
            const target = e.target;
 | 
			
		||||
            if (!target) {
 | 
			
		||||
                return reject('File missing?');
 | 
			
		||||
            }
 | 
			
		||||
            resolve(e.target.result as string);
 | 
			
		||||
        });
 | 
			
		||||
        FR.readAsDataURL(file);
 | 
			
		||||
@ -210,8 +199,7 @@ export function isBase64JSON(s: unknown): s is string {
 | 
			
		||||
 | 
			
		||||
export function flattenObject(
 | 
			
		||||
    obj: unknown,
 | 
			
		||||
    parser: (to_parse: { toString: () => string }) => string | Record<string, unknown> = (id) =>
 | 
			
		||||
        id.toString(),
 | 
			
		||||
    parser: (to_parse: unknown) => string | Record<string, unknown> = (id) => id.toString(),
 | 
			
		||||
    key = '',
 | 
			
		||||
    ret = [] as [string, string][],
 | 
			
		||||
    parsed = false
 | 
			
		||||
@ -232,12 +220,7 @@ export function flattenObject(
 | 
			
		||||
            flattenObject(value, parser, prefix + subkey, ret);
 | 
			
		||||
        }
 | 
			
		||||
    } else if (!parsed) {
 | 
			
		||||
        try {
 | 
			
		||||
            flattenObject(parser(obj as { toString: () => string }), parser, key, ret, true);
 | 
			
		||||
        } catch (e) {
 | 
			
		||||
            //emergency case, mostly for just type safety
 | 
			
		||||
            ret.push([key, JSON.stringify(obj)]);
 | 
			
		||||
        }
 | 
			
		||||
        flattenObject(parser(obj), parser, key, ret, true);
 | 
			
		||||
    } else if (typeof obj === 'string') {
 | 
			
		||||
        ret.push([key, obj]);
 | 
			
		||||
    } else {
 | 
			
		||||
@ -248,8 +231,7 @@ export function flattenObject(
 | 
			
		||||
 | 
			
		||||
export function flattenObjectEntries(
 | 
			
		||||
    entries: [string, unknown][],
 | 
			
		||||
    parser: (to_parse: { toString: () => string }) => string | Record<string, unknown> = (id) =>
 | 
			
		||||
        id.toString()
 | 
			
		||||
    parser: (to_parse: unknown) => string | Record<string, unknown> = (id) => id.toString()
 | 
			
		||||
): [string, string][] {
 | 
			
		||||
    return flattenObject(Object.fromEntries(entries), parser);
 | 
			
		||||
}
 | 
			
		||||
@ -284,37 +266,8 @@ export function normalizeForClassname(string: string) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function wordlist(words: string[]) {
 | 
			
		||||
    return Array.from(new Set(words)).reduce(
 | 
			
		||||
        (acc, word, i) =>
 | 
			
		||||
            `${acc}${
 | 
			
		||||
                i > 0 ? (i < words.length - 1 ? ', ' : Math.random() > 0.5 ? ' i ' : ' oraz ') : ''
 | 
			
		||||
            }${word}`,
 | 
			
		||||
    return words.reduce(
 | 
			
		||||
        (acc, word, i) => `${acc}${i > 0 ? (i < words.length - 1 ? ',' : ' i') : ''} ${word}`,
 | 
			
		||||
        ''
 | 
			
		||||
    );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const source_to_word: Record<Sources, string> = {
 | 
			
		||||
    cookie: 'plik cookie o nazwie',
 | 
			
		||||
    pathname: 'fragment ścieżki w URL',
 | 
			
		||||
    queryparams: 'query params w URL o nazwie',
 | 
			
		||||
    header: 'nagłówek HTTP',
 | 
			
		||||
    request_body: 'body zapytania HTTP, pod kluczem',
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export function dataLocationToText(l: DataLocation) {
 | 
			
		||||
    return `${source_to_word[l.source]} ${l.key}`;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function downloadText(filename: string, text: string) {
 | 
			
		||||
    // https://stackoverflow.com/questions/45831191/generate-and-download-file-from-js
 | 
			
		||||
    var element = document.createElement('a');
 | 
			
		||||
    element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text));
 | 
			
		||||
    element.setAttribute('download', filename);
 | 
			
		||||
 | 
			
		||||
    element.style.display = 'none';
 | 
			
		||||
    document.body.appendChild(element);
 | 
			
		||||
 | 
			
		||||
    element.click();
 | 
			
		||||
 | 
			
		||||
    document.body.removeChild(element);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||