Update designs

This commit is contained in:
Arkadiusz Wieczorek 2022-04-24 22:11:33 +02:00
parent 6faa078095
commit b92a472569
22 changed files with 1107 additions and 1042 deletions

View File

@ -1,3 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M15 22H9V20H15V22ZM15 19H9L8.777 17C8.65703 16.3385 8.45863 15.6936 8.186 15.079C7.832 14.579 7.463 14.152 7.106 13.735C5.79411 12.5053 5.03465 10.7978 5 9C5 5.13401 8.13401 2 12 2C15.866 2 19 5.13401 19 9C18.9593 10.7868 18.2057 12.4831 16.907 13.711L16.89 13.731C16.534 14.148 16.166 14.58 15.819 15.075C15.5466 15.6912 15.3476 16.3373 15.226 17L15 19ZM12 4C9.23995 4.00331 7.00331 6.23995 7 9C7 10.544 7.644 11.293 8.618 12.428C8.988 12.86 9.408 13.348 9.818 13.919C10.3156 14.8858 10.6555 15.9259 10.825 17H13.176C13.3499 15.929 13.6892 14.8916 14.182 13.925C14.582 13.354 15.001 12.863 15.37 12.431L15.385 12.413C16.357 11.273 17 10.52 17 9C16.9967 6.23995 14.7601 4.00331 12 4Z" fill="#2E3A59"/> <path d="M15 22H9V20H15V22ZM15 19H9L8.777 17C8.65703 16.3385 8.45863 15.6936 8.186 15.079C7.832 14.579 7.463 14.152 7.106 13.735C5.79411 12.5053 5.03465 10.7978 5 9C5 5.13401 8.13401 2 12 2C15.866 2 19 5.13401 19 9C18.9593 10.7868 18.2057 12.4831 16.907 13.711L16.89 13.731C16.534 14.148 16.166 14.58 15.819 15.075C15.5466 15.6912 15.3476 16.3373 15.226 17L15 19ZM12 4C9.23995 4.00331 7.00331 6.23995 7 9C7 10.544 7.644 11.293 8.618 12.428C8.988 12.86 9.408 13.348 9.818 13.919C10.3156 14.8858 10.6555 15.9259 10.825 17H13.176C13.3499 15.929 13.6892 14.8916 14.182 13.925C14.582 13.354 15.001 12.863 15.37 12.431L15.385 12.413C16.357 11.273 17 10.52 17 9C16.9967 6.23995 14.7601 4.00331 12 4Z" fill="#000000"/>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 814 B

After

Width:  |  Height:  |  Size: 814 B

View File

@ -1,3 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M18 22H6C4.89543 22 4 21.1046 4 20V4.00001C4 2.89544 4.89543 2.00001 6 2.00001H13C13.2654 1.99907 13.5201 2.10462 13.707 2.29301L19.707 8.29301C19.8954 8.47994 20.0009 8.73462 20 9.00001V20C20 21.1046 19.1046 22 18 22ZM6 4.00001V20H16.586L14.02 17.434C13.4101 17.8017 12.7121 17.9973 12 18C10.1612 18.0199 8.54049 16.7967 8.05545 15.0229C7.57041 13.2491 8.34318 11.3714 9.93625 10.4529C11.5293 9.53434 13.5415 9.80626 14.8337 11.1147C16.1258 12.4231 16.3724 14.4386 15.434 16.02L18 18.588V9.41401L12.586 4.00001H6ZM12 12C10.8954 12 10 12.8954 10 14C10 15.1046 10.8954 16 12 16C13.1046 16 14 15.1046 14 14C14 12.8954 13.1046 12 12 12Z" fill="#2E3A59"/> <path d="M18 22H6C4.89543 22 4 21.1046 4 20V4.00001C4 2.89544 4.89543 2.00001 6 2.00001H13C13.2654 1.99907 13.5201 2.10462 13.707 2.29301L19.707 8.29301C19.8954 8.47994 20.0009 8.73462 20 9.00001V20C20 21.1046 19.1046 22 18 22ZM6 4.00001V20H16.586L14.02 17.434C13.4101 17.8017 12.7121 17.9973 12 18C10.1612 18.0199 8.54049 16.7967 8.05545 15.0229C7.57041 13.2491 8.34318 11.3714 9.93625 10.4529C11.5293 9.53434 13.5415 9.80626 14.8337 11.1147C16.1258 12.4231 16.3724 14.4386 15.434 16.02L18 18.588V9.41401L12.586 4.00001H6ZM12 12C10.8954 12 10 12.8954 10 14C10 15.1046 10.8954 16 12 16C13.1046 16 14 15.1046 14 14C14 12.8954 13.1046 12 12 12Z" fill="#000000"/>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 764 B

After

Width:  |  Height:  |  Size: 764 B

View File

@ -1,3 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M18 22H6C4.89543 22 4 21.1046 4 20V4C4 2.89543 4.89543 2 6 2H13C13.0109 2.00047 13.0217 2.00249 13.032 2.006C13.0418 2.00902 13.0518 2.01103 13.062 2.012C13.1502 2.01765 13.2373 2.0348 13.321 2.063L13.349 2.072C13.3717 2.07968 13.3937 2.08904 13.415 2.1C13.5239 2.14842 13.6232 2.21618 13.708 2.3L19.708 8.3C19.7918 8.38479 19.8596 8.48406 19.908 8.593C19.918 8.615 19.925 8.638 19.933 8.661L19.942 8.687C19.9699 8.77039 19.9864 8.85718 19.991 8.945C19.9926 8.95418 19.9949 8.96322 19.998 8.972C19.9998 8.98122 20.0004 8.99062 20.0001 9V20C20.0001 21.1046 19.1046 22 18 22ZM6 4V20H18V10H13C12.4477 10 12 9.55228 12 9V4H6ZM14 5.414V8H16.586L14 5.414ZM15 16H9V14H15V16Z" fill="#2E3A59"/> <path d="M18 22H6C4.89543 22 4 21.1046 4 20V4C4 2.89543 4.89543 2 6 2H13C13.0109 2.00047 13.0217 2.00249 13.032 2.006C13.0418 2.00902 13.0518 2.01103 13.062 2.012C13.1502 2.01765 13.2373 2.0348 13.321 2.063L13.349 2.072C13.3717 2.07968 13.3937 2.08904 13.415 2.1C13.5239 2.14842 13.6232 2.21618 13.708 2.3L19.708 8.3C19.7918 8.38479 19.8596 8.48406 19.908 8.593C19.918 8.615 19.925 8.638 19.933 8.661L19.942 8.687C19.9699 8.77039 19.9864 8.85718 19.991 8.945C19.9926 8.95418 19.9949 8.96322 19.998 8.972C19.9998 8.98122 20.0004 8.99062 20.0001 9V20C20.0001 21.1046 19.1046 22 18 22ZM6 4V20H18V10H13C12.4477 10 12 9.55228 12 9V4H6ZM14 5.414V8H16.586L14 5.414ZM15 16H9V14H15V16Z" fill="#000000"/>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 798 B

After

Width:  |  Height:  |  Size: 798 B

View File

@ -5,15 +5,15 @@ import { Problem } from './problems/problem';
import { UnlawfulCookieAccess } from './problems/unlawful-cookies'; import { UnlawfulCookieAccess } from './problems/unlawful-cookies';
export default function deduceProblems( export default function deduceProblems(
answers: ParsedAnswers, answers: ParsedAnswers,
clusters: Record<string, RequestCluster> clusters: Record<string, RequestCluster>
): Problem[] { ): Problem[] {
const problems = []; const problems = [];
if (answers.popup_type === 'none') { if (answers.popup_type === 'none') {
problems.push(new NoInformationAtAllProblem(answers, clusters)); problems.push(new NoInformationAtAllProblem(answers, clusters));
} }
if (UnlawfulCookieAccess.qualifies(answers, Object.values(clusters))) { if (UnlawfulCookieAccess.qualifies(answers, Object.values(clusters))) {
problems.push(new UnlawfulCookieAccess(answers, clusters)); problems.push(new UnlawfulCookieAccess(answers, clusters));
} }
return problems; return problems;
} }

View File

@ -9,50 +9,50 @@ declare var PLUGIN_NAME: string;
declare var PLUGIN_URL: string; declare var PLUGIN_URL: string;
export default function EmailContent({ export default function EmailContent({
answers, answers,
visited_url, visited_url,
clusters, clusters,
}: { }: {
answers: ParsedAnswers; answers: ParsedAnswers;
visited_url: string; visited_url: string;
clusters: Record<string, RequestCluster>; clusters: Record<string, RequestCluster>;
}) { }) {
const _ = (key: string) => v(key, answers.zaimek); const _ = (key: string) => v(key, answers.zaimek);
const problems = deduceProblems(answers, clusters); const problems = deduceProblems(answers, clusters);
const explainers = Array.from( const explainers = Array.from(
new Set( new Set(
problems problems
.map((problem) => problem.getNecessaryExplainers()) .map((problem) => problem.getNecessaryExplainers())
.reduce((a, b) => a.concat(b), []) .reduce((a, b) => a.concat(b), [])
) )
).map((explainer_key) => Explainers[explainer_key]); ).map((explainer_key) => Explainers[explainer_key]);
return ( return (
<div style={{ padding: '1rem' }}> <div style={{ padding: '1rem' }}>
<pre>{JSON.stringify(answers, null, 3)}</pre> <pre>{JSON.stringify(answers, null, 3)}</pre>
<p>Dzień dobry,</p> <p>Dzień dobry,</p>
<p> <p>
w dniu {getDate()} {_('odwiedziłem')} stronę {visited_url}. Po podejrzeniu ruchu w dniu {getDate()} {_('odwiedziłem')} stronę {visited_url}. Po podejrzeniu ruchu
sieciowego generowanego przez stronę za pomocą wtyczki{' '} sieciowego generowanego przez stronę za pomocą wtyczki{' '}
<a href={PLUGIN_URL}>{PLUGIN_NAME}</a> w przeglądarce Firefox {_('mam')} pytania <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')}{' '} dotyczące przetwarzania {_('moich')} danych osobowych, na które nie {_('znalazłem')}{' '}
odpowiedzi nigdzie na Państwa stronie. odpowiedzi nigdzie na Państwa stronie.
</p> </p>
{problems.map((problem) => problem.getEmailContent())} {problems.map((problem) => problem.getEmailContent())}
{explainers.map((explainer) => explainer(answers.zaimek))} {explainers.map((explainer) => explainer(answers.zaimek))}
<h2>Państwa rola jako współadministratora danych osobowych</h2> <h2>Państwa rola jako współadministratora danych osobowych</h2>
<p> <p>
{_('Zwracam')} Państwa uwagę na fakt, że w myśl{' '} {_('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"> <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 treści wyroku TSUE w sprawie C-40/17
</a>{' '} </a>{' '}
poprzez wysyłanie moich danych w wyżej opisanym zakresie stają się Państwo 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 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 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 Europejskiego i Rady (UE) z dnia 27 kwietnia 2016 r. w sprawie ochrony osób
związku z przetwarzaniem danych osobowych i w sprawie swobodnego przepływu takich danych fizycznych w związku z przetwarzaniem danych osobowych i w sprawie swobodnego
oraz uchylenia dyrektywy 95/46/WE (ogólne rozporządzenie o ochronie danych, dalej: przepływu takich danych oraz uchylenia dyrektywy 95/46/WE (ogólne rozporządzenie o
RODO). ochronie danych, dalej: RODO).
</p> </p>
</div> </div>
); );
} }

View File

@ -1,14 +1,14 @@
export type ExplainerKey = 'cookies_are_pii'; export type ExplainerKey = 'cookies_are_pii';
export const Explainers: Record<ExplainerKey, (zaimek_index: 0 | 1 | 2 | 3) => JSX.Element> = { export const Explainers: Record<ExplainerKey, (zaimek_index: 0 | 1 | 2 | 3) => JSX.Element> = {
cookies_are_pii: () => ( cookies_are_pii: () => (
<> <>
<h2>Ciasteczka stanowią dane osobowe</h2> <h2>Ciasteczka stanowią dane osobowe</h2>
<p> <p>
Sztucznie wygenerowane identyfikatory przechowywane w plikach Cookies stanowią dane Sztucznie wygenerowane identyfikatory przechowywane w plikach Cookies stanowią dane
osobowe. Wskazuje na to wprost Art. 4. pkt 1. RODO, wymieniając identyfikator osobowe. Wskazuje na to wprost Art. 4. pkt 1. RODO, wymieniając identyfikator
internetowy i numer identyfikacyjny jako przykłady danych osobowych. internetowy i numer identyfikacyjny jako przykłady danych osobowych.
</p> </p>
</> </>
), ),
}; };

View File

@ -1,382 +1,382 @@
function generateHostPage( function generateHostPage(
host: string, host: string,
index: number, index: number,
all_hosts: string[] all_hosts: string[]
): { title: string; elements: any[]; visibleIf: string } { ): { title: string; elements: any[]; visibleIf: string } {
function f(name: string, h = host) { function f(name: string, h = host) {
return `${h.replace(/\./g, '_')}|${name}`; return `${h.replace(/\./g, '_')}|${name}`;
} }
const previous_host: string | null = index > 0 ? all_hosts[index - 1] : null; const previous_host: string | null = index > 0 ? all_hosts[index - 1] : null;
function defaultValue(name: string) { function defaultValue(name: string) {
if (!previous_host) { if (!previous_host) {
return {}; return {};
} }
return { defaultValueExpression: `{${f(name, previous_host)}}` }; return { defaultValueExpression: `{${f(name, previous_host)}}` };
} }
return { return {
title: host, title: host,
elements: [ elements: [
{ {
type: 'radiogroup', type: 'radiogroup',
name: f('present'), name: f('present'),
isRequired: true, isRequired: true,
title: `Cel ujawnienia danych właścicielowi domeny ${host}`, title: `Cel ujawnienia danych właścicielowi domeny ${host}`,
...defaultValue('present'), ...defaultValue('present'),
visibleIf: '{popup_type} != "none"', visibleIf: '{popup_type} != "none"',
choices: [ choices: [
{ {
value: 'not_mentioned', value: 'not_mentioned',
text: 'nie jest podany nigdzie na stronie', text: 'nie jest podany nigdzie na stronie',
visibleIf: "{policy_readable} = 'yes' ", visibleIf: "{policy_readable} = 'yes' ",
}, },
{ {
value: 'not_before_making_a_choice', value: 'not_before_making_a_choice',
text: 'nie jest podany w żadnym miejscu na stronie, do którego można się dostać bez podejmowania wyboru dotyczącego przetwarzania danych osobowych', text: 'nie jest podany w żadnym miejscu na stronie, do którego można się dostać bez podejmowania wyboru dotyczącego przetwarzania danych osobowych',
}, },
{ {
value: 'mentioned_in_policy', value: 'mentioned_in_policy',
text: 'jest podany w polityce prywatności', text: 'jest podany w polityce prywatności',
visibleIf: "{policy_readable} = 'yes' ", visibleIf: "{policy_readable} = 'yes' ",
}, },
{ {
value: 'mentioned_in_popup', value: 'mentioned_in_popup',
text: 'jest podany w okienku RODO', text: 'jest podany w okienku RODO',
visibleIf: "{popup_type} != 'none' ", visibleIf: "{popup_type} != 'none' ",
}, },
], ],
}, },
{ {
type: 'radiogroup', type: 'radiogroup',
name: f('legal_basis_type'), name: f('legal_basis_type'),
...defaultValue('legal_basis_type'), ...defaultValue('legal_basis_type'),
isRequired: true, isRequired: true,
title: `Podstawa prawna dla tego konkretnego celu`, title: `Podstawa prawna dla tego konkretnego celu`,
visibleIf: `{${f('present')}} notempty and {${f( visibleIf: `{${f('present')}} notempty and {${f(
'present' 'present'
)}} != "not_mentioned" and {${f('present')}} != "not_before_making_a_choice"`, )}} != "not_mentioned" and {${f('present')}} != "not_before_making_a_choice"`,
choices: [ choices: [
{ value: 'consent', text: 'to zgoda.' }, { value: 'consent', text: 'to zgoda.' },
{ {
value: 'legitimate_interest', value: 'legitimate_interest',
text: 'to uzasadniony interes.', text: 'to uzasadniony interes.',
}, },
{ value: 'not_mentioned', text: 'nie jest wskazana nigdzie na stronie.' }, { value: 'not_mentioned', text: 'nie jest wskazana nigdzie na stronie.' },
], ],
}, },
{ {
type: 'radiogroup', type: 'radiogroup',
name: f('consent_problems'), name: f('consent_problems'),
...defaultValue('consent_problems'), ...defaultValue('consent_problems'),
isRequired: true, isRequired: true,
title: `Jak ma się ta podstawa prawna do stanu faktycznego?`, title: `Jak ma się ta podstawa prawna do stanu faktycznego?`,
visibleIf: `{${f('legal_basis_type')}} = "consent"`, visibleIf: `{${f('legal_basis_type')}} = "consent"`,
defaultValueExpression: defaultValueExpression:
'iif({popup_action} = "none" or {popup_action} = "closed_popup", "claims_consent_but_sends_before_consent", iif({popup_action} = "accept_all" and {rejection_is_hard} = "yes", "claims_consent_but_there_was_no_easy_refuse", ""))', 'iif({popup_action} = "none" or {popup_action} = "closed_popup", "claims_consent_but_sends_before_consent", iif({popup_action} = "accept_all" and {rejection_is_hard} = "yes", "claims_consent_but_there_was_no_easy_refuse", ""))',
choices: [ choices: [
{ {
value: 'claims_consent_but_sends_before_consent', value: 'claims_consent_but_sends_before_consent',
text: `Strona wysłała {moje} dane do ${host} 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', value: 'claims_consent_but_there_was_no_easy_refuse',
text: '{Kliknąłem} przycisk od wyrażania zgody, ale w okienku o zgodę nie było natychmiastowo dostępnego przycisku do niewyrażenia zgody jednym kliknięciem', text: '{Kliknąłem} przycisk od wyrażania zgody, ale w okienku o zgodę nie było natychmiastowo dostępnego przycisku do niewyrażenia zgody jednym kliknięciem',
}, },
{ value: 'none', text: 'żadne z powyższych.' }, { value: 'none', text: 'żadne z powyższych.' },
], ],
}, },
{ {
type: 'radiogroup', type: 'radiogroup',
name: f('legitimate_interest_activity_specified'), name: f('legitimate_interest_activity_specified'),
...defaultValue('legitimate_interest_activity_specified'), ...defaultValue('legitimate_interest_activity_specified'),
isRequired: true, isRequired: true,
title: /* HTML */ `Czy administrator strony opisał szczegółowo, na czym polega title: /* HTML */ `Czy administrator strony opisał szczegółowo, na czym polega
uzasadniony interes w kontekście tego celu?`, uzasadniony interes w kontekście tego celu?`,
visibleIf: `{${f('legal_basis_type')}} = "legitimate_interest"`, visibleIf: `{${f('legal_basis_type')}} = "legitimate_interest"`,
choices: [ choices: [
{ {
value: 'precise', value: 'precise',
text: /* HTML */ `Tak, wskazuje jasno na bieżące działania lub korzyści wynikające text: /* HTML */ `Tak, wskazuje jasno na bieżące działania lub korzyści
z takiego przetwarzania danych.`, wynikające z takiego przetwarzania danych.`,
}, },
{ {
value: 'vague', value: 'vague',
text: `Wskazuje tylko ogólnie, jak np. „marketing” czy „statystyki”.`, text: `Wskazuje tylko ogólnie, jak np. „marketing” czy „statystyki”.`,
}, },
{ {
value: 'no', value: 'no',
text: `Nie. Nie wiadomo, na czym ten uzasadniony interes polega.`, text: `Nie. Nie wiadomo, na czym ten uzasadniony interes polega.`,
}, },
], ],
}, },
{ {
type: 'text', type: 'text',
title: `Jak administrator opisał to, na czym polega uzasadniony interes w kontekście ${host}?`, title: `Jak administrator opisał to, na czym polega uzasadniony interes w kontekście ${host}?`,
name: f('legitimate_interest_description'), name: f('legitimate_interest_description'),
visibleIf: `{${f('legitimate_interest_activity_specified')}} = 'vague'`, visibleIf: `{${f('legitimate_interest_activity_specified')}} = 'vague'`,
placeholder: 'marketing', placeholder: 'marketing',
defaultValueExpression: defaultValueExpression:
index == 0 index == 0
? 'marketing' ? 'marketing'
: `{${f('legitimate_interest_description', previous_host)}}`, : `{${f('legitimate_interest_description', previous_host)}}`,
}, },
{ {
type: 'radiogroup', type: 'radiogroup',
title: `Czy domena ${host} 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'), name: f('outside_eu'),
...defaultValue('outside_eu'), ...defaultValue('outside_eu'),
visibleIf: `{${f('legitimate_interest_activity_specified')}} = "precise" or {${f( visibleIf: `{${f('legitimate_interest_activity_specified')}} = "precise" or {${f(
'consent_problems' 'consent_problems'
)}} = "none"`, )}} = "none"`,
choices: [ choices: [
{ value: 'yes', text: 'Tak' }, { value: 'yes', text: 'Tak' },
{ value: 'no', text: 'Nie' }, { value: 'no', text: 'Nie' },
{ value: 'not_sure', text: 'Nie wiem' }, { value: 'not_sure', text: 'Nie wiem' },
], ],
}, },
{ {
type: 'radiogroup', type: 'radiogroup',
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ą?`, 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'), name: f('was_processing_necessary'),
...defaultValue('was_processing_necessary'), ...defaultValue('was_processing_necessary'),
visibleIf: `{${f('legal_basis_type')}} = "legitimate_interest" or {${f( visibleIf: `{${f('legal_basis_type')}} = "legitimate_interest" or {${f(
'present' 'present'
)}} = "not_mentioned" or {popup_type} = "none"`, )}} = "not_mentioned" or {popup_type} = "none"`,
choices: [ choices: [
{ value: 'yes', text: 'Tak, było konieczne' }, { value: 'yes', text: 'Tak, było konieczne' },
{ value: 'no', text: 'Nie, nie było konieczne' }, { value: 'no', text: 'Nie, nie było konieczne' },
{ value: 'not_sure', text: 'Nie mam zdania' }, { value: 'not_sure', text: 'Nie mam zdania' },
], ],
}, },
], ],
}; };
} }
export default function generateSurveyQuestions(hosts: string[]) { export default function generateSurveyQuestions(hosts: string[]) {
return { return {
showQuestionNumbers: 'off', showQuestionNumbers: 'off',
showProgressBar: 'top', showProgressBar: 'top',
clearInvisibleValues: 'onHidden', clearInvisibleValues: 'onHidden',
pages: [ pages: [
{ {
title: 'Zaimki', title: 'Zaimki',
elements: [ elements: [
{ {
type: 'radiogroup', type: 'radiogroup',
name: 'zaimek', name: 'zaimek',
title: 'Forma czasownika:', title: 'Forma czasownika:',
isRequired: true, isRequired: true,
choices: [ choices: [
{ value: 0, text: 'Wysłałem' }, { value: 0, text: 'Wysłałem' },
{ value: 1, text: 'Wysłałam' }, { value: 1, text: 'Wysłałam' },
{ value: 2, text: 'Wysłałom' }, { value: 2, text: 'Wysłałom' },
{ value: 3, text: 'Wysłaliśmy' }, { value: 3, text: 'Wysłaliśmy' },
], ],
}, },
], ],
}, },
{ {
title: 'Obowiązek informacyjny i machanizm pozyskiwania zgody', title: 'Obowiązek informacyjny i machanizm pozyskiwania zgody',
elements: [ elements: [
{ {
type: 'radiogroup', type: 'radiogroup',
title: 'Jaką formę informacji o przetwarzaniu danych osobowych stosuje ta strona?', title: 'Jaką formę informacji o przetwarzaniu danych osobowych stosuje ta strona?',
name: 'popup_type', name: 'popup_type',
isRequired: true, isRequired: true,
choices: [ choices: [
{ value: 'none', text: 'Brak informacji' }, { value: 'none', text: 'Brak informacji' },
{ {
value: 'page', value: 'page',
text: 'Tylko w postaci tekstu na podstronie np. "prywatność" lub "polityka cookies"', text: 'Tylko w postaci tekstu na podstronie np. "prywatność" lub "polityka cookies"',
}, },
{ {
value: 'passive_popup', value: 'passive_popup',
text: /* HTML */ `Okienko o cookiesach, bez możliwości podjęcia żadnego text: /* HTML */ `Okienko o cookiesach, bez możliwości podjęcia
wyboru (np. tylko opcja zamknij)`, żadnego wyboru (np. tylko opcja zamknij)`,
}, },
{ {
value: 'some_choice', value: 'some_choice',
text: 'Okienko o cookiesach, z możliwością podjęcia wyboru', text: 'Okienko o cookiesach, z możliwością podjęcia wyboru',
}, },
], ],
}, },
{ {
type: 'checkbox', type: 'checkbox',
title: /* HTML */ `Istnieje możliwość, że okienko z informacjami i wyborami title: /* HTML */ `Istnieje możliwość, że okienko z informacjami i wyborami
dotyczącymi przetwarzania {Twoich} danych osobowych ukazało się dawno temu w dotyczącymi przetwarzania {Twoich} danych osobowych ukazało się dawno temu w
trakcie {twojej} wcześniejszej wizyty i wtedy je {odkliknąłeś}. {Otwórz} samą trakcie {twojej} wcześniejszej wizyty i wtedy je {odkliknąłeś}. {Otwórz}
stronę w Trybie Prywatnym (Incognito). Co {widzisz}?`, samą stronę w Trybie Prywatnym (Incognito). Co {widzisz}?`,
visibleIf: "{popup_type} = 'none' or {popup_type} = 'page'", visibleIf: "{popup_type} = 'none' or {popup_type} = 'page'",
name: 'is_incognito_different', name: 'is_incognito_different',
isRequired: true, isRequired: true,
choices: [ choices: [
{ {
value: 'incognito_is_the_same', value: 'incognito_is_the_same',
text: 'W Trybie prywatnym {widzę} to samo, co {widziałem} w normalnym trybie', text: 'W Trybie prywatnym {widzę} to samo, co {widziałem} w normalnym trybie',
}, },
], ],
}, },
{ {
type: 'html', type: 'html',
visibleIf: visibleIf:
'{is_incognito_different} != "no" and ({popup_type} = "none" or {popup_type} = "page") ', '{is_incognito_different} != "no" and ({popup_type} = "none" or {popup_type} = "page") ',
html: /* HTML */ `Jeżeli w trybie incognito widzisz więcej okienek z informacjami html: /* HTML */ `Jeżeli w trybie incognito widzisz więcej okienek z
o przetwarzaniu danych osobowych, wykonaj analizę w normalnym trybie ponownie - informacjami o przetwarzaniu danych osobowych, wykonaj analizę w
ale najpierw usuń pliki cookies tej strony. normalnym trybie ponownie - ale najpierw usuń pliki cookies tej strony.
<a <a
href="https://support.mozilla.org/pl/kb/usuwanie-ciasteczek-i-danych-stron-firefox?redirectslug=usuwanie-ciasteczek&redirectlocale=pl" href="https://support.mozilla.org/pl/kb/usuwanie-ciasteczek-i-danych-stron-firefox?redirectslug=usuwanie-ciasteczek&redirectlocale=pl"
target="_blank" target="_blank"
> >
Zobacz, jak to zrobić Zobacz, jak to zrobić
</a>`, </a>`,
}, },
{ {
type: 'radiogroup', type: 'radiogroup',
name: 'mentions_passive_consent', name: 'mentions_passive_consent',
isRequired: true, isRequired: true,
visibleIf: '{popup_type} = "passive_popup"', visibleIf: '{popup_type} = "passive_popup"',
title: 'Czy treść okienka wskazuje na zgodę wyrażoną pasywnie, np. „Korzystając z naszej strony wyrażasz zgodę”, „Brak zmiany ustawień przeglądarki oznacza zgodę”, „Klikając przycisk "X" (zamknij) wyrażasz zgodę”?', title: 'Czy treść okienka wskazuje na zgodę wyrażoną pasywnie, np. „Korzystając z naszej strony wyrażasz zgodę”, „Brak zmiany ustawień przeglądarki oznacza zgodę”, „Klikając przycisk "X" (zamknij) wyrażasz zgodę”?',
choices: [ choices: [
{ {
value: 'yes', value: 'yes',
text: 'Tak', text: 'Tak',
}, },
{ {
value: 'no', value: 'no',
text: 'Nie', text: 'Nie',
}, },
], ],
}, },
{ {
type: 'text', type: 'text',
name: 'passive_consent_description', name: 'passive_consent_description',
isRequired: true, isRequired: true,
visibleIf: '{mentions_passive_consent} = "yes"', visibleIf: '{mentions_passive_consent} = "yes"',
title: 'Jakimi słowami administrator opisuje to pasywne wyrażenie zgody? Zacytuj wprost. Na przykład: „Korzystając ze strony wyrażasz zgodę”, albo „Pozostawiając ustawienia przeglądarki bez zmian (..) wyrażasz zgodę”', title: 'Jakimi słowami administrator opisuje to pasywne wyrażenie zgody? Zacytuj wprost. Na przykład: „Korzystając ze strony wyrażasz zgodę”, albo „Pozostawiając ustawienia przeglądarki bez zmian (..) wyrażasz zgodę”',
defaultValue: 'Korzystając ze strony wyrażasz zgodę', defaultValue: 'Korzystając ze strony wyrażasz zgodę',
}, },
{ {
type: 'radiogroup', type: 'radiogroup',
name: 'cookie_wall', name: 'cookie_wall',
isRequired: true, isRequired: true,
visibleIf: '{popup_type} = "passive_popup"', visibleIf: '{popup_type} = "passive_popup"',
title: 'Czy treść strony jest wygodnie czytelna bez odkliknięcia tego okienka o RODO?', title: 'Czy treść strony jest wygodnie czytelna bez odkliknięcia tego okienka o RODO?',
choices: [ choices: [
{ {
value: 'no', // wiem, że tu jest "no", a odpowiedź brzmi "tak" - ale nazwa pytania dotyczy obecności cookie walla value: 'no', // wiem, że tu jest "no", a odpowiedź brzmi "tak" - ale nazwa pytania dotyczy obecności cookie walla
text: 'Tak, jest czytelna', text: 'Tak, jest czytelna',
}, },
{ {
value: 'yes', value: 'yes',
text: 'Nie. Jest zupełnie niewidoczna albo jest przesłonięta w stopniu uniemożliwiającym lub znacznie utrudniającym czytanie treści strony.', text: 'Nie. Jest zupełnie niewidoczna albo jest przesłonięta w stopniu uniemożliwiającym lub znacznie utrudniającym czytanie treści strony.',
}, },
], ],
}, },
{ {
type: 'radiogroup', type: 'radiogroup',
name: 'rejection_is_hard', name: 'rejection_is_hard',
isRequired: true, isRequired: true,
visibleIf: '{popup_type} = "some_choice"', visibleIf: '{popup_type} = "some_choice"',
title: 'Czy wyrażenie zgody na wszystkie cele jest dokładnie tak samo łatwe, jak odmowa zgody na wszystkie cele?', title: 'Czy wyrażenie zgody na wszystkie cele jest dokładnie tak samo łatwe, jak odmowa zgody na wszystkie cele?',
choices: [ choices: [
{ {
value: 'no', // wiem, że tu jest "no", a odpowiedź brzmi "tak" - ale nazwa pytania dotyczy braku równowagi value: 'no', // wiem, że tu jest "no", a odpowiedź brzmi "tak" - ale nazwa pytania dotyczy braku równowagi
text: 'Tak. Opcja odmowy zgody na wszystkie cele jest równie widoczna i łatwo dostępna, co opcja wyrażenia zgody.', text: 'Tak. Opcja odmowy zgody na wszystkie cele jest równie widoczna i łatwo dostępna, co opcja wyrażenia zgody.',
}, },
{ {
value: 'yes', value: 'yes',
text: 'Nie. Muszę wykonać więcej czynności 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.',
}, },
], ],
}, },
{ {
type: 'radiogroup', type: 'radiogroup',
name: 'popup_action', name: 'popup_action',
isRequired: true, isRequired: true,
visibleIf: '{popup_type} = "some_choice" or {popup_type} = "passive_popup"', visibleIf: '{popup_type} = "some_choice" or {popup_type} = "passive_popup"',
title: 'Jaką akcję {podjąłeś} w ramach wyskakującego okienka?', title: 'Jaką akcję {podjąłeś} w ramach wyskakującego okienka?',
choices: [ choices: [
{ {
value: 'none', value: 'none',
text: 'Nic nie {kliknąłem}', text: 'Nic nie {kliknąłem}',
}, },
{ {
value: 'closed_popup', value: 'closed_popup',
text: '{Zamknąłem} okienko za pomocą przycisku „X” lub „Zamknij”, lub podobnego', text: '{Zamknąłem} okienko za pomocą przycisku „X” lub „Zamknij”, lub podobnego',
}, },
{ {
value: 'accept_all', value: 'accept_all',
text: '{Kliknąłem} przycisk od akceptacji wszystkich zgód', text: '{Kliknąłem} przycisk od akceptacji wszystkich zgód',
}, },
{ {
value: 'deny_all', value: 'deny_all',
text: '{Odmówiłem} wyrażenia zgody na wszystkie cele', text: '{Odmówiłem} wyrażenia zgody na wszystkie cele',
}, },
{ {
value: 'other', value: 'other',
text: 'Coś innego', text: 'Coś innego',
}, },
], ],
}, },
{ {
type: 'text', type: 'text',
name: 'popup_closed_how', name: 'popup_closed_how',
isRequired: true, isRequired: true,
visibleIf: '{popup_action} = "closed_popup"', visibleIf: '{popup_action} = "closed_popup"',
title: 'W jaki sposób {zamknąłeś} okienko o zgodę? Opisz pełnym zdaniem', title: 'W jaki sposób {zamknąłeś} okienko o zgodę? Opisz pełnym zdaniem',
defaultValueExpression: '{Kliknąłem} przycisk „X”.', defaultValueExpression: '{Kliknąłem} przycisk „X”.',
}, },
{ {
type: 'text', type: 'text',
name: 'popup_deny_all_how', name: 'popup_deny_all_how',
isRequired: true, isRequired: true,
visibleIf: '{popup_action} = "deny_all"', visibleIf: '{popup_action} = "deny_all"',
title: 'W jaki sposób {zamknąłeś} okienko o zgodę? Opisz pełnym zdaniem, np.: „{Kliknąłem} przycisk <Odrzuć wszystkie>” lub „{Odznaczyłem} wszystkie opcje w ustawieniach zaawansowanych”', title: 'W jaki sposób {zamknąłeś} okienko o zgodę? Opisz pełnym zdaniem, np.: „{Kliknąłem} przycisk <Odrzuć wszystkie>” lub „{Odznaczyłem} wszystkie opcje w ustawieniach zaawansowanych”',
defaultValueExpression: '{Kliknąłem} przycisk „odmawiam wyrażenia zgody”.', defaultValueExpression: '{Kliknąłem} przycisk „odmawiam wyrażenia zgody”.',
}, },
{ {
type: 'radiogroup', type: 'radiogroup',
name: 'administrator_identity_available_before_choice', name: 'administrator_identity_available_before_choice',
isRequired: true, isRequired: true,
visibleIf: '{popup_type} = "some_choice"', visibleIf: '{popup_type} = "some_choice"',
title: 'Czy przed podjęciem wyboru dot. {Twoich} danych masz możliwość poznać tożsamość administratora strony?', title: 'Czy przed podjęciem wyboru dot. {Twoich} danych masz możliwość poznać tożsamość administratora strony?',
choices: [ choices: [
{ {
value: 'yes', value: 'yes',
text: 'Tak.', text: 'Tak.',
}, },
{ {
value: 'no', value: 'no',
text: 'Nie.', text: 'Nie.',
}, },
], ],
}, },
], ],
}, },
{ {
title: 'Obowiązek informacyjny, polityka prywatności', title: 'Obowiązek informacyjny, polityka prywatności',
visibleIf: "{popup_type} != 'none'", visibleIf: "{popup_type} != 'none'",
elements: [ elements: [
{ {
type: 'radiogroup', type: 'radiogroup',
title: 'Czy polityka prywatności jest dostępna i czytelna?', title: 'Czy polityka prywatności jest dostępna i czytelna?',
name: 'policy_readable', name: 'policy_readable',
isRequired: true, isRequired: true,
choices: [ choices: [
{ value: 'yes', text: 'dostępna i czytelna' }, { value: 'yes', text: 'dostępna i czytelna' },
{ {
value: 'entirely_obscured_by_popup', value: 'entirely_obscured_by_popup',
text: 'dostępna, ale nieczytelna. Zasłania ją całkowicie lub prawie całkowicie popup o RODO lub nie można się do niej doklikać bez podjęcia wyboru w okienku', text: 'dostępna, ale nieczytelna. Zasłania ją całkowicie lub prawie całkowicie popup o RODO lub nie można się do niej doklikać bez podjęcia wyboru w okienku',
}, },
{ {
value: 'cant_find', value: 'cant_find',
text: `Niedostępna. {Szukałem}, ale nie {znalazłem} jej na stronie`, text: `Niedostępna. {Szukałem}, ale nie {znalazłem} jej na stronie`,
}, },
], ],
}, },
], ],
}, },
...hosts.map(generateHostPage), ...hosts.map(generateHostPage),
], ],
}; };
} }

View File

@ -11,13 +11,10 @@ function handleNewFile(
const reader = new FileReader(); const reader = new FileReader();
reader.addEventListener('load', () => { reader.addEventListener('load', () => {
const content = JSON.parse(reader.result as string); const content = JSON.parse(reader.result as string);
content.log.entries = content.log.entries.filter( content.log.entries = content.log.entries.filter((har_entry: HAREntry) =>
(har_entry: HAREntry) => entries.some((entry) => entry.matchesHAREntry(har_entry))
entries.some((entry) => entry.matchesHAREntry(har_entry))
);
setFiltered(
new Blob([JSON.stringify(content)], { type: 'application/json' })
); );
setFiltered(new Blob([JSON.stringify(content)], { type: 'application/json' }));
}); });
reader.readAsText(element.files[0]); reader.readAsText(element.files[0]);
} }
@ -30,10 +27,7 @@ function generateFakeHAR(entries: StolenDataEntry[]) {
} else if (request1.shorthost > request2.shorthost) { } else if (request1.shorthost > request2.shorthost) {
return 1; return 1;
} else { } else {
return ( return request2.getBalancedPriority() - request1.getBalancedPriority();
request2.getBalancedPriority() -
request1.getBalancedPriority()
);
} }
}) })
.filter((_, index, array) => { .filter((_, index, array) => {
@ -43,10 +37,7 @@ function generateFakeHAR(entries: StolenDataEntry[]) {
} }
return true; return true;
}) })
.sort( .sort((entry1, entry2) => entry2.getBalancedPriority() - entry1.getBalancedPriority());
(entry1, entry2) =>
entry2.getBalancedPriority() - entry1.getBalancedPriority()
);
return { return {
log: { log: {
@ -75,15 +66,10 @@ function generateFakeHAR(entries: StolenDataEntry[]) {
}; };
} }
export default function HARConverter({ export default function HARConverter({ entries }: { entries: StolenDataEntry[] }) {
entries,
}: {
entries: StolenDataEntry[];
}) {
const [filtered, setFiltered] = React.useState<Blob | null>(null); const [filtered, setFiltered] = React.useState<Blob | null>(null);
const [filename, setFilename] = React.useState(''); const [filename, setFilename] = React.useState('');
const [fakeHAR, setFakeHAR] = const [fakeHAR, setFakeHAR] = React.useState<ReturnType<typeof generateFakeHAR>>();
React.useState<ReturnType<typeof generateFakeHAR>>();
React.useEffect(() => { React.useEffect(() => {
setFakeHAR(generateFakeHAR(entries)); setFakeHAR(generateFakeHAR(entries));
}, []); }, []);

View File

@ -3,65 +3,65 @@ import RawAnswers, { BasicRawAnswers, HostRawAnswers } from './raw-answers';
export type RecordValue<T> = T extends Record<any, infer R> ? R : any; export type RecordValue<T> = T extends Record<any, infer R> ? R : any;
export type ParsedHostAnswers = ({ export type ParsedHostAnswers = ({
present: present:
| 'not_mentioned' | 'not_mentioned'
| 'not_before_making_a_choice' | 'not_before_making_a_choice'
| 'mentioned_in_policy' | 'mentioned_in_policy'
| 'mentioned_in_popup'; | 'mentioned_in_popup';
legal_basis_type: 'consent' | 'legitimate_interes' | 'not_mentioned'; legal_basis_type: 'consent' | 'legitimate_interes' | 'not_mentioned';
was_processing_necessary: 'yes' | 'no' | 'not_sure'; was_processing_necessary: 'yes' | 'no' | 'not_sure';
} & ( } & (
| { | {
consent_problems: consent_problems:
| 'claims_consent_but_sends_before_consent' | 'claims_consent_but_sends_before_consent'
| 'claims_consent_but_there_was_no_easy_refuse'; | 'claims_consent_but_there_was_no_easy_refuse';
} }
| { consent_problems: 'none'; outside_eu: 'yes' | 'no' | 'not_sure' } | { consent_problems: 'none'; outside_eu: 'yes' | 'no' | 'not_sure' }
)) & { )) & {
legitimate_interest_activity_specified: 'no' | 'precise' | 'vague'; legitimate_interest_activity_specified: 'no' | 'precise' | 'vague';
outside_eu: 'yes' | 'no' | 'not_sure'; outside_eu: 'yes' | 'no' | 'not_sure';
legitimate_interest_description?: string; legitimate_interest_description?: string;
}; };
export type ParsedAnswers = BasicRawAnswers & { hosts: Record<string, ParsedHostAnswers> }; export type ParsedAnswers = BasicRawAnswers & { hosts: Record<string, ParsedHostAnswers> };
function parseHostAnswers( function parseHostAnswers(
raw_answers: Record<keyof HostRawAnswers, string> raw_answers: Record<keyof HostRawAnswers, string>
): Record<string, ParsedHostAnswers> { ): Record<string, ParsedHostAnswers> {
const result: Record<string, Record<string, string>> = {}; const result: Record<string, Record<string, string>> = {};
for (const [key, value] of Object.entries(raw_answers)) { for (const [key, value] of Object.entries(raw_answers)) {
const [masked_host, attr] = key.split('|'); const [masked_host, attr] = key.split('|');
const host = masked_host.replace(/_/g, '.'); const host = masked_host.replace(/_/g, '.');
if (!result[host]) { if (!result[host]) {
result[host] = {} as ParsedHostAnswers; result[host] = {} as ParsedHostAnswers;
} }
result[host][attr] = value; result[host][attr] = value;
} }
return result as Record<string, ParsedHostAnswers>; return result as Record<string, ParsedHostAnswers>;
} }
export function parseAnswers({ export function parseAnswers({
zaimek, zaimek,
is_incognito_different, is_incognito_different,
policy_readable, policy_readable,
popup_type, popup_type,
cookie_wall, cookie_wall,
passive_consent_description, passive_consent_description,
mentions_passive_consent, mentions_passive_consent,
rejection_is_hard, rejection_is_hard,
administrator_identity_available_before_choice, administrator_identity_available_before_choice,
...rest ...rest
}: RawAnswers): ParsedAnswers { }: RawAnswers): ParsedAnswers {
return { return {
zaimek, zaimek,
is_incognito_different, is_incognito_different,
policy_readable, policy_readable,
popup_type, popup_type,
cookie_wall, cookie_wall,
passive_consent_description, passive_consent_description,
mentions_passive_consent, mentions_passive_consent,
rejection_is_hard, rejection_is_hard,
administrator_identity_available_before_choice, administrator_identity_available_before_choice,
hosts: parseHostAnswers(rest), hosts: parseHostAnswers(rest),
} as ParsedAnswers; } as ParsedAnswers;
} }

View File

@ -3,42 +3,43 @@ import { v } from '../verbs';
import { Problem } from './problem'; import { Problem } from './problem';
export default class NoInformationAtAllProblem extends Problem { export default class NoInformationAtAllProblem extends Problem {
getEmailContent() { getEmailContent() {
const _ = (word: string) => v(word, this.answers.zaimek); const _ = (word: string) => v(word, this.answers.zaimek);
return ( return (
<> <>
<h2>Brak informacji na temat przetwarzania danych osobowych</h2> <h2>Brak informacji na temat przetwarzania danych osobowych</h2>
<p> <p>
{_('Moje')} dane osobowe zostały ujawnione podmiotom, które właścicielami domen: {_('Moje')} dane osobowe zostały ujawnione podmiotom, które właścicielami
</p> domen:
{this.getRangeDescription()} </p>
<p> {this.getRangeDescription()}
Na stronie brakuje jednak jakichkolwiek informacji o tym, jakie cele przetwarzania <p>
takich danych oraz jakie podstawy prawne takiego przetwarzania. Na stronie brakuje jednak jakichkolwiek informacji o tym, jakie cele
</p> przetwarzania takich danych oraz jakie podstawy prawne takiego przetwarzania.
<p>Zwracam się zatem do Państwa z następującymi pytaniami:</p> </p>
<ul> <p>Zwracam się zatem do Państwa z następującymi pytaniami:</p>
<li>Jaka jest tożsamość właścicieli tych domen?</li> <ul>
<li>Jaki jest cel takiego przetwarzania danych przez Państwa stronę?</li> <li>Jaka jest tożsamość właścicieli tych domen?</li>
<li> <li>Jaki jest cel takiego przetwarzania danych przez Państwa stronę?</li>
Jaka jest podstawa prawna takiego przetwarzania moich danych osobowych przez <li>
Państwa stronę? Jaka jest podstawa prawna takiego przetwarzania moich danych osobowych przez
</li> Państwa stronę?
</ul> </li>
</> </ul>
); </>
} );
getNecessaryExplainers() { }
const explainers = [] as Array<ExplainerKey>; getNecessaryExplainers() {
const explainers = [] as Array<ExplainerKey>;
if ( if (
this.getMarkedClusters().some((cluster) => { this.getMarkedClusters().some((cluster) => {
console.log(cluster); console.log(cluster);
return cluster.hasMarkedCookies(); return cluster.hasMarkedCookies();
}) })
) { ) {
explainers.push('cookies_are_pii'); explainers.push('cookies_are_pii');
} }
return explainers; return explainers;
} }
} }

View File

@ -3,36 +3,36 @@ import { ExplainerKey } from '../explainers';
import { ParsedAnswers } from '../parse-answers'; import { ParsedAnswers } from '../parse-answers';
function formatRange(cluster: RequestCluster) { function formatRange(cluster: RequestCluster) {
const parts = [] as string[]; const parts = [] as string[];
console.log(cluster); console.log(cluster);
if (cluster.hasMarkedCookies()) { if (cluster.hasMarkedCookies()) {
parts.push('mojego identyfikatora internetowego pozyskanego z Cookie'); parts.push('mojego identyfikatora internetowego pozyskanego z Cookie');
} }
if (cluster.exposesOrigin()) { if (cluster.exposesOrigin()) {
parts.push('części mojej historii przeglądania'); parts.push('części mojej historii przeglądania');
} }
return parts.join(' oraz '); return parts.join(' oraz ');
} }
export abstract class Problem { export abstract class Problem {
constructor(public answers: ParsedAnswers, public clusters: Record<string, RequestCluster>) {} constructor(public answers: ParsedAnswers, public clusters: Record<string, RequestCluster>) {}
abstract getEmailContent(): JSX.Element; abstract getEmailContent(): JSX.Element;
abstract getNecessaryExplainers(): ExplainerKey[]; abstract getNecessaryExplainers(): ExplainerKey[];
getMarkedClusters() { getMarkedClusters() {
return Object.values(this.clusters).filter((c) => c.hasMarks()); return Object.values(this.clusters).filter((c) => c.hasMarks());
} }
getRangeDescription() { getRangeDescription() {
return ( return (
<ul> <ul>
{this.getMarkedClusters().map((cluster) => ( {this.getMarkedClusters().map((cluster) => (
<li key={cluster.id}> <li key={cluster.id}>
{cluster.id} (w zakresie: {formatRange(cluster)}) {cluster.id} (w zakresie: {formatRange(cluster)})
</li> </li>
))} ))}
</ul> </ul>
); );
} }
} }

View File

@ -6,171 +6,178 @@ import { v } from '../verbs';
import { Problem } from './problem'; import { Problem } from './problem';
export class UnlawfulCookieAccess extends Problem { export class UnlawfulCookieAccess extends Problem {
getNecessaryExplainers(): ExplainerKey[] { getNecessaryExplainers(): ExplainerKey[] {
return []; return [];
} }
static qualifies(answers: ParsedAnswers, clusters: RequestCluster[]): boolean { static qualifies(answers: ParsedAnswers, clusters: RequestCluster[]): boolean {
// są cookiesy, nie było zgody, nie są konieczne do działania strony // są cookiesy, nie było zgody, nie są konieczne do działania strony
const cookie_clusters = Object.values(clusters).filter((c) => c.hasMarkedCookies()); const cookie_clusters = Object.values(clusters).filter((c) => c.hasMarkedCookies());
return cookie_clusters.some((cluster) => { return cookie_clusters.some((cluster) => {
const hostAnswers = answers.hosts[cluster.id]; const hostAnswers = answers.hosts[cluster.id];
return ( return (
(hostAnswers.present == 'not_mentioned' || (hostAnswers.present == 'not_mentioned' ||
hostAnswers.present == 'not_before_making_a_choice' || hostAnswers.present == 'not_before_making_a_choice' ||
['none', 'closed_popup', 'deny_all'].includes(answers.popup_action) || ['none', 'closed_popup', 'deny_all'].includes(answers.popup_action) ||
answers.popup_type === 'none') && answers.popup_type === 'none') &&
hostAnswers.was_processing_necessary != 'yes' hostAnswers.was_processing_necessary != 'yes'
); );
}); });
} }
getEmailContent() { getEmailContent() {
const cookie_clusters = Object.values(this.clusters).filter((c) => c.hasMarkedCookies()); const cookie_clusters = Object.values(this.clusters).filter((c) => c.hasMarkedCookies());
const unnecessary_hosts = Object.entries(this.answers.hosts) const unnecessary_hosts = Object.entries(this.answers.hosts)
.filter(([, answers]) => answers.was_processing_necessary === 'no') .filter(([, answers]) => answers.was_processing_necessary === 'no')
.map(([host]) => host); .map(([host]) => host);
const maybe_unnecessary_hosts = Object.entries(this.answers.hosts) const maybe_unnecessary_hosts = Object.entries(this.answers.hosts)
.filter(([, answers]) => answers.was_processing_necessary === 'not_sure') .filter(([, answers]) => answers.was_processing_necessary === 'not_sure')
.map(([host]) => host); .map(([host]) => host);
const _ = (key: string) => v(key, this.answers.zaimek); const _ = (key: string) => v(key, this.answers.zaimek);
return ( return (
<> <>
<h2>Dostęp do cookies niezgodny z ustawą Prawo Telekomunikacyjne</h2> <h2>Dostęp do cookies niezgodny z ustawą Prawo Telekomunikacyjne</h2>
<p> <p>
Państwa strona dokonała odczytu plików Cookie zapisanych na dysku twardym mojego Państwa strona dokonała odczytu plików Cookie zapisanych na dysku twardym mojego
komputera. Dotyczy to plików cookie przypisanych do domen: komputera. Dotyczy to plików cookie przypisanych do domen:
</p> </p>
<ul> <ul>
{cookie_clusters.map((cluster, index) => { {cookie_clusters.map((cluster, index) => {
const names = cluster const names = cluster
.getMarkedEntries() .getMarkedEntries()
.filter((e) => e.source === 'cookie') .filter((e) => e.source === 'cookie')
.map((e) => e.name); .map((e) => e.name);
return ( return (
<li> <li>
{cluster.id} ({names.length > 1 ? 'pliki' : 'plik'}{' '} {cluster.id} ({names.length > 1 ? 'pliki' : 'plik'}{' '}
{names.map((name, index) => { {names.map((name, index) => {
return ( return (
<> <>
{index > 0 ? ', ' : ''} {index > 0 ? ', ' : ''}
{name} {name}
</> </>
); );
})} })}
){index === cookie_clusters.length - 1 ? '.' : ';'} ){index === cookie_clusters.length - 1 ? '.' : ';'}
</li> </li>
); );
})} })}
</ul> </ul>
<p> <p>
Zgodnie z treścią Art. 173.{' '} Zgodnie z treścią Art. 173.{' '}
<a href="https://isap.sejm.gov.pl/isap.nsf/download.xsp/WDU20041711800/U/D20041800Lj.pdf"> <a href="https://isap.sejm.gov.pl/isap.nsf/download.xsp/WDU20041711800/U/D20041800Lj.pdf">
ustawy Prawo Telekomunikacyjne ustawy Prawo Telekomunikacyjne
</a> </a>
, strona może pozyskać dostęp do treści plików cookies pod warunkiem spełnienia , strona może pozyskać dostęp do treści plików cookies pod warunkiem spełnienia
jednego z następujących warunków: jednego z następujących warunków:
</p> </p>
<ol> <ol>
<li> <li>
Użytkownik wyraził zgodę na takie przetwarzanie danych <em>po</em> tym, jak został Użytkownik wyraził zgodę na takie przetwarzanie danych <em>po</em> tym, jak
poinformowany bezpośrednio o celu uzyskania dostępu do tej informacji; został poinformowany bezpośrednio o celu uzyskania dostępu do tej
</li> informacji;
<li> </li>
Dostęp do treści plików cookies jest konieczny do dostarczania usługi świadczonej <li>
drogą elektroniczną zażądanej przez użytkownika. Dostęp do treści plików cookies jest konieczny do dostarczania usługi
</li> świadczonej drogą elektroniczną zażądanej przez użytkownika.
</ol> </li>
{(() => { </ol>
if (this.answers.popup_type == 'none' || this.answers.popup_type == 'page') { {(() => {
return ( if (this.answers.popup_type == 'none' || this.answers.popup_type == 'page') {
<p> return (
Jako, że strona nie pytała {_('mnie')} nigdy o zgodę, nie jest spełniony <p>
warunek 1. Jako, że strona nie pytała {_('mnie')} nigdy o zgodę, nie jest
</p> spełniony warunek 1.
); </p>
} else if (this.answers.popup_type === 'passive_popup') { );
return ( } else if (this.answers.popup_type === 'passive_popup') {
<p> return (
Państwa strona nie dała mi nigdy faktycznego wyboru dotyczącego wyrażenia <p>
lub odmówienia zgody na takie przetwarzanie danych osobowych, dlatego nie Państwa strona nie dała mi nigdy faktycznego wyboru dotyczącego
jest spełniony warunek 1.{' '} wyrażenia lub odmówienia zgody na takie przetwarzanie danych
{this.answers.mentions_passive_consent ? ( osobowych, dlatego nie jest spełniony warunek 1.{' '}
<> {this.answers.mentions_passive_consent ? (
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 Zgody wyrażonej w sposób bierny lub milczący nie można uznać
zaniechanie zmiany ustawień przeglądarki lub po prostu korzystanie ze za ważną w świetle obowiązujących przepisów rozporządzenia
strony nie stanowi ważnej zgody. Takie jest{' '} 2016/679. Dlatego zaniechanie zmiany ustawień przeglądarki
<a href="https://assets.midline.pl/pisma/2021-12-16%20odpowiedz%20UODO%20na%20skarg%C4%99%20i(n)Secure.pdf"> lub po prostu korzystanie ze strony nie stanowi ważnej
stanowisko polskiego UODO zgody. Takie jest{' '}
</a> <a href="https://assets.midline.pl/pisma/2021-12-16%20odpowiedz%20UODO%20na%20skarg%C4%99%20i(n)Secure.pdf">
. stanowisko polskiego UODO
</> </a>
) : ( .
'' </>
)} ) : (
</p> ''
); )}
} else if (this.answers.popup_type === 'some_choice') { </p>
if (this.answers.popup_action === 'none') { );
return ( } else if (this.answers.popup_type === 'some_choice') {
<p> if (this.answers.popup_action === 'none') {
Nie {_('wyraziłem')} zgody na takie przetwarzanie {_('moich')} danych return (
osobowych. W okienku pytającym o zgodję nic nie {_('kliknąłem')}. Nie <p>
jest zatem spełniony warunek 1. Nie {_('wyraziłem')} zgody na takie przetwarzanie {_('moich')}{' '}
</p> danych osobowych. W okienku pytającym o zgodję nic nie{' '}
); {_('kliknąłem')}. Nie jest zatem spełniony warunek 1.
} else if (this.answers.popup_action === 'closed_popup') { </p>
return ( );
<p> } else if (this.answers.popup_action === 'closed_popup') {
Nie {_('wyraziłem')} zgody na takie przetwarzanie {_('moich')} danych return (
osobowych. {this.answers.popup_closed_how.trim()} <p>
{this.answers.popup_closed_how.trim().at(-1) != '.' ? '.' : ''} Takiego Nie {_('wyraziłem')} zgody na takie przetwarzanie {_('moich')}{' '}
działania nie można uznać za ważną zgodę na przetwarzanie danych danych osobowych. {this.answers.popup_closed_how.trim()}
osobowych, gdyż nie spełnia warunku jednoznaczności opisanego w Art. 4, {this.answers.popup_closed_how.trim().at(-1) != '.'
pkt 11 RODO. Nie jest zatem spełniony warunek 1. ? '.'
</p> : ''}{' '}
); Takiego działania nie można uznać za ważną zgodę na
} else if (this.answers.popup_action == 'deny_all') { przetwarzanie danych osobowych, gdyż nie spełnia warunku
return ( jednoznaczności opisanego w Art. 4, pkt 11 RODO. Nie jest zatem
<p> spełniony warunek 1.
{this.answers.popup_deny_all_how.trim()} </p>
{this.answers.popup_closed_how.trim().at(-1) != '.' ? '.' : ''} Zatem nie );
jest spełniony warunek 1. } else if (this.answers.popup_action == 'deny_all') {
</p> return (
); <p>
} {this.answers.popup_deny_all_how.trim()}
} {this.answers.popup_closed_how.trim().at(-1) != '.'
})()} ? '.'
{unnecessary_hosts.length > 0 ? ( : ''}{' '}
<p> Zatem nie jest spełniony warunek 1.
W {_('mojej')} ocenie odczytywanie przez Państwa stronę treści plików cookies z{' '} </p>
{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. {unnecessary_hosts.length > 0 ? (
</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
<p> Państwa zdaniem jest inaczej, {_('proszę')} o wskazanie, co jest źródłem tej
{_('Proszę')} o wskazanie, czy być może stosowali Państwo inną podstawę prawną do konieczności i co odróżnia Państwa stronę od wielu innych stron, które
takiego przetwarzania {_('moich')} danych osobowych, czy przetwarzali je państwo bez realizują te same funkcjonalności <em>bez</em> korzystania z plików Cookie.
ważnej podstawy prawnej? </p>
</p> ) : (
{maybe_unnecessary_hosts.length > 1 ? ( ''
<p> )}
{_('Proszę')} też o wskazanie, czy dostęp do treści plików cookie z <p>
{wordlist(maybe_unnecessary_hosts)} jest konieczny do poprawnego działania strony? {_('Proszę')} o wskazanie, czy być może stosowali Państwo inną podstawę prawną
Jeżeli tak, to {_('proszę')} wskazać, w jaki sposób. Co sprawia, że strona nie do takiego przetwarzania {_('moich')} danych osobowych, czy przetwarzali je
może działać bez nich? państwo bez ważnej podstawy prawnej?
</p> </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
); strony? Jeżeli tak, to {_('proszę')} wskazać, w jaki sposób. Co sprawia, że
} strona nie może działać bez nich?
</p>
) : (
''
)}
</>
);
}
} }

View File

@ -7,29 +7,30 @@ import { Problem } from './problem';
type UnlawfulDataClassification = 'no_purpose'; type UnlawfulDataClassification = 'no_purpose';
export function classifyUnlawfulData( export function classifyUnlawfulData(
hostAnswers: ParsedHostAnswers, hostAnswers: ParsedHostAnswers,
cluster: RequestCluster cluster: RequestCluster
): UnlawfulDataClassification { ): UnlawfulDataClassification {
if (hostAnswers.present == 'not_mentioned' && hostAnswers.was_processing_necessary == 'no') { if (hostAnswers.present == 'not_mentioned' && hostAnswers.was_processing_necessary == 'no') {
return 'no_purpose'; return 'no_purpose';
} }
} }
export class UnlawfulData extends Problem { export class UnlawfulData extends Problem {
static qualifies(answers: ParsedAnswers, clusters: RequestCluster[]): boolean {} static qualifies(answers: ParsedAnswers, clusters: RequestCluster[]): boolean {}
getEmailContent() { getEmailContent() {
const _ = (key: string) => v(key, this.answers.zaimek); const _ = (key: string) => v(key, this.answers.zaimek);
return ( return (
<> <>
<h2>Przetwarzanie danych osobowych bez ważnej podsawy prawnej</h2> <h2>Przetwarzanie danych osobowych bez ważnej podsawy prawnej</h2>
<p> <p>
{_('Moje')} dane osobowe zostały ujawnione podmiotom, które właścicielami domen: {_('Moje')} dane osobowe zostały ujawnione podmiotom, które właścicielami
</p> domen:
{this.getRangeDescription()} </p>
</> {this.getRangeDescription()}
); </>
} );
getNecessaryExplainers() { }
return [] as ExplainerKey[]; getNecessaryExplainers() {
} return [] as ExplainerKey[];
}
} }

View File

@ -1,58 +1,58 @@
export type HostRawAnswers = { export type HostRawAnswers = {
[key: `${string}|present`]: [key: `${string}|present`]:
| 'not_mentioned' | 'not_mentioned'
| 'not_before_making_a_choice' | 'not_before_making_a_choice'
| 'mentioned_in_policy' | 'mentioned_in_policy'
| 'mentioned_in_popup'; | 'mentioned_in_popup';
[key: `${string}|legal_basis_type`]: 'consent' | 'legitimate_interest' | 'not_mentioned'; [key: `${string}|legal_basis_type`]: 'consent' | 'legitimate_interest' | 'not_mentioned';
[key: `${string}|consent`]: [key: `${string}|consent`]:
| 'claims_consent_but_sends_before_consent' | 'claims_consent_but_sends_before_consent'
| 'claims_consent_but_there_was_no_easy_refuse' | 'claims_consent_but_there_was_no_easy_refuse'
| 'none'; | 'none';
[key: `${string}|legitimate_interest_activity_specified`]: 'precise' | 'vague' | 'no'; [key: `${string}|legitimate_interest_activity_specified`]: 'precise' | 'vague' | 'no';
[key: `${string}|legitimate_interest_description`]: string; [key: `${string}|legitimate_interest_description`]: string;
[key: `${string}|outside_eu`]: 'yes' | 'no' | 'not_sure'; [key: `${string}|outside_eu`]: 'yes' | 'no' | 'not_sure';
}; };
export type BasicRawAnswers = { export type BasicRawAnswers = {
zaimek: 0 | 1 | 2 | 3; zaimek: 0 | 1 | 2 | 3;
is_incognito_different: [] | ['incognito_is_the_same']; is_incognito_different: [] | ['incognito_is_the_same'];
policy_readable: 'yes' | 'vague' | 'cant_find'; policy_readable: 'yes' | 'vague' | 'cant_find';
popup_action: 'none' | 'closed_popup' | 'accept_all' | 'deny_all' | 'other'; popup_action: 'none' | 'closed_popup' | 'accept_all' | 'deny_all' | 'other';
popup_closed_how: string; popup_closed_how: string;
popup_deny_all_how: string; popup_deny_all_how: string;
} & ( } & (
| ({ | ({
popup_type: 'passive_popup'; popup_type: 'passive_popup';
cookie_wall: 'yes' | 'no'; cookie_wall: 'yes' | 'no';
rejection_is_hard: undefined; rejection_is_hard: undefined;
administrator_identity_available_before_choice: undefined; administrator_identity_available_before_choice: undefined;
} & ( } & (
| { | {
mentions_passive_consent?: 'yes'; mentions_passive_consent?: 'yes';
passive_consent_description: string; passive_consent_description: string;
} }
| { | {
mentions_passive_consent?: 'no'; mentions_passive_consent?: 'no';
passive_consent_description: undefined; passive_consent_description: undefined;
} }
)) ))
| { | {
popup_type: 'some_choice'; popup_type: 'some_choice';
rejection_is_hard: 'yes' | 'no'; rejection_is_hard: 'yes' | 'no';
administrator_identity_available_before_choice: 'yes' | 'no'; administrator_identity_available_before_choice: 'yes' | 'no';
cookie_wall: undefined; cookie_wall: undefined;
passive_consent_description: undefined; passive_consent_description: undefined;
mentions_passive_consent: undefined; mentions_passive_consent: undefined;
} }
| { | {
popup_type: 'none' | 'page'; popup_type: 'none' | 'page';
cookie_wall: undefined; cookie_wall: undefined;
passive_consent_description: undefined; passive_consent_description: undefined;
mentions_passive_consent: undefined; mentions_passive_consent: undefined;
rejection_is_hard: undefined; rejection_is_hard: undefined;
administrator_identity_available_before_choice: undefined; administrator_identity_available_before_choice: undefined;
} }
); );
type RawAnswers = BasicRawAnswers & HostRawAnswers; type RawAnswers = BasicRawAnswers & HostRawAnswers;

View File

@ -9,70 +9,70 @@ import EmailContent from './email-content';
import { parseAnswers, ParsedAnswers } from './parse-answers'; import { parseAnswers, ParsedAnswers } from './parse-answers';
function Report() { function Report() {
try { try {
const url = new URL(document.location.toString()); const url = new URL(document.location.toString());
const origin = url.searchParams.get('origin'); const origin = url.searchParams.get('origin');
const [counter] = useEmitter(getMemory()); const [counter] = useEmitter(getMemory());
const [answers, setAnswers] = React.useState<ParsedAnswers>( const [answers, setAnswers] = React.useState<ParsedAnswers>(
url.searchParams.get('answers') ? JSON.parse(url.searchParams.get('answers')) : null url.searchParams.get('answers') ? JSON.parse(url.searchParams.get('answers')) : null
); );
const [mode, setMode] = React.useState(url.searchParams.get('mode') || 'survey'); const [mode, setMode] = React.useState(url.searchParams.get('mode') || 'survey');
const clusters = getMemory().getClustersForOrigin(origin); const clusters = getMemory().getClustersForOrigin(origin);
/* const [entries, setEntries] = React.useState<StolenDataEntry[]>([]); */ /* const [entries, setEntries] = React.useState<StolenDataEntry[]>([]); */
/* React.useEffect(() => { /* React.useEffect(() => {
* setEntries( * setEntries(
* Object.values(clusters) * Object.values(clusters)
* .map((cluster) => { * .map((cluster) => {
* cluster.calculateRepresentativeStolenData(); * cluster.calculateRepresentativeStolenData();
* return cluster.representativeStolenData; * return cluster.representativeStolenData;
* }) * })
* .reduce(reduceConcat, []) * .reduce(reduceConcat, [])
* .filter((entry) => entry.isMarked) * .filter((entry) => entry.isMarked)
* ); * );
* }, []); */ * }, []); */
/* if (entries.length == 0) { /* if (entries.length == 0) {
* return <>Wczytywanie...</>; * return <>Wczytywanie...</>;
* } */ * } */
React.useEffect(() => { React.useEffect(() => {
const url = new URL(document.location.toString()); const url = new URL(document.location.toString());
url.searchParams.set('origin', origin); url.searchParams.set('origin', origin);
url.searchParams.set('answers', JSON.stringify(answers)); url.searchParams.set('answers', JSON.stringify(answers));
url.searchParams.set('mode', mode); url.searchParams.set('mode', mode);
history.pushState({}, 'Rentgen', url.toString()); history.pushState({}, 'Rentgen', url.toString());
}, [mode, answers, origin]); }, [mode, answers, origin]);
const visited_url = Object.values(clusters) const visited_url = Object.values(clusters)
.find((cluster) => cluster.getMarkedRequests().length > 0) .find((cluster) => cluster.getMarkedRequests().length > 0)
?.getMarkedRequests()[0].originalURL; ?.getMarkedRequests()[0].originalURL;
const result = ( const result = (
<div {...{ 'data-version': counter }}> <div {...{ 'data-version': counter }}>
<nav> <nav>
<img src="../assets/icon-addon.svg" width={48} height={48}></img>{' '} <img src="../assets/icon-addon.svg" width={48} height={48}></img>{' '}
<h1>Rentgen - Generuj treść maila dla {origin}</h1> <h1>Rentgen - Generuj treść maila dla {origin}</h1>
</nav> </nav>
{mode === 'survey' ? ( {mode === 'survey' ? (
<Questions <Questions
hosts={Object.values(clusters) hosts={Object.values(clusters)
.filter((cluster) => cluster.getMarkedRequests().length > 0) .filter((cluster) => cluster.getMarkedRequests().length > 0)
.map((cluster) => cluster.id)} .map((cluster) => cluster.id)}
onComplete={(answers) => { onComplete={(answers) => {
setAnswers(parseAnswers(answers)); setAnswers(parseAnswers(answers));
setMode('preview'); setMode('preview');
}} }}
></Questions> ></Questions>
) : ( ) : (
'' ''
)} )}
{mode === 'preview' ? <EmailContent {...{ answers, visited_url, clusters }} /> : ''} {mode === 'preview' ? <EmailContent {...{ answers, visited_url, clusters }} /> : ''}
{/* <HARConverter {...{ entries }} /> */} {/* <HARConverter {...{ entries }} /> */}
</div> </div>
); );
return result; return result;
} catch (e) { } catch (e) {
console.error(e); console.error(e);
return <div>ERROR! {JSON.stringify(e)}</div>; return <div>ERROR! {JSON.stringify(e)}</div>;
} }
} }
ReactDOM.render(<Report />, document.getElementById('app')); ReactDOM.render(<Report />, document.getElementById('app'));

View File

@ -1,45 +1,45 @@
const words = { const words = {
zrobiłem: ['zrobiłem', 'zrobiłam', 'zrobiłom', 'zrobiliśmy'], zrobiłem: ['zrobiłem', 'zrobiłam', 'zrobiłom', 'zrobiliśmy'],
szukałem: ['szukałem', 'szukałam', 'szukałom', 'szukaliśmy'], szukałem: ['szukałem', 'szukałam', 'szukałom', 'szukaliśmy'],
znalazłem: ['znalazłem', 'znalazłam', 'znalazłom', 'znaleźliśmy'], znalazłem: ['znalazłem', 'znalazłam', 'znalazłom', 'znaleźliśmy'],
moje: ['moje', 'moje', 'moje', 'nasze'], moje: ['moje', 'moje', 'moje', 'nasze'],
mojej: ['mojej', 'mojej', 'mojej', 'naszej'], mojej: ['mojej', 'mojej', 'mojej', 'naszej'],
wyraziłem: ['wyraziłem', 'wyraziłam', 'wyraziłom', 'wyraziliśmy'], wyraziłem: ['wyraziłem', 'wyraziłam', 'wyraziłom', 'wyraziliśmy'],
kliknąłem: ['kliknąłem', 'kliknęłam', 'klinkęłom', 'kliknęliśmy'], kliknąłem: ['kliknąłem', 'kliknęłam', 'klinkęłom', 'kliknęliśmy'],
odwiedzałeś: ['odwiedzałeś', 'odwiedzałaś', 'odwiedzałoś', 'odwiedzaliście'], odwiedzałeś: ['odwiedzałeś', 'odwiedzałaś', 'odwiedzałoś', 'odwiedzaliście'],
wyraziłeś: ['wyraziłeś', 'wyraziłaś', 'wyraziłoś', 'wyraziliście'], wyraziłeś: ['wyraziłeś', 'wyraziłaś', 'wyraziłoś', 'wyraziliście'],
jesteś: ['jesteś', 'jesteś', 'jesteś', 'jesteście'], jesteś: ['jesteś', 'jesteś', 'jesteś', 'jesteście'],
twoich: ['twoich', 'twoich', 'twoich', 'waszych'], twoich: ['twoich', 'twoich', 'twoich', 'waszych'],
tobie: ['tobie', 'tobie', 'tobie', 'wam'], tobie: ['tobie', 'tobie', 'tobie', 'wam'],
twojej: ['twojej', 'twojej', 'twojej', 'waszej'], twojej: ['twojej', 'twojej', 'twojej', 'waszej'],
odkliknąłeś: ['odkliknąłeś', 'odkliknęłaś', 'odklikęłoś', 'odkliknęliście'], odkliknąłeś: ['odkliknąłeś', 'odkliknęłaś', 'odklikęłoś', 'odkliknęliście'],
otwórz: ['otwórz', 'otwórz', 'otwórz', 'otwórzcie'], otwórz: ['otwórz', 'otwórz', 'otwórz', 'otwórzcie'],
widzisz: ['widzisz', 'widzisz', 'widzisz', 'widzicie'], widzisz: ['widzisz', 'widzisz', 'widzisz', 'widzicie'],
widzę: ['widzę', 'widzę', 'widzę', 'widzimy'], widzę: ['widzę', 'widzę', 'widzę', 'widzimy'],
widziałem: ['widziałem', 'widziałam', 'widziałom', 'widzieliśmy'], widziałem: ['widziałem', 'widziałam', 'widziałom', 'widzieliśmy'],
odwiedziłem: ['odwiedziłem', 'odwiedziłam', 'odwiedziłom', 'odwiedziliśmy'], odwiedziłem: ['odwiedziłem', 'odwiedziłam', 'odwiedziłom', 'odwiedziliśmy'],
mam: ['mam', 'mam', 'mam', 'mamy'], mam: ['mam', 'mam', 'mam', 'mamy'],
podjąłeś: ['podjąłeś', 'podjęłaś', 'podjęłoś', 'podjęliście'], podjąłeś: ['podjąłeś', 'podjęłaś', 'podjęłoś', 'podjęliście'],
zamknąłem: ['zamknąłem', 'zamknęłom', 'zamknęłom', 'zamknęliśmy'], zamknąłem: ['zamknąłem', 'zamknęłom', 'zamknęłom', 'zamknęliśmy'],
zamknąłeś: ['zamknąłeś', 'zamknęłaś', 'zamknęłoś', 'zamknęliście'], zamknąłeś: ['zamknąłeś', 'zamknęłaś', 'zamknęłoś', 'zamknęliście'],
zwracam: ['zwracam', 'zwracam', 'zwracam', 'zwracamy'], zwracam: ['zwracam', 'zwracam', 'zwracam', 'zwracamy'],
moich: ['moich', 'moich', 'moich', 'naszych'], moich: ['moich', 'moich', 'moich', 'naszych'],
ciebie: ['ciebie', 'ciebie', 'ciebie', 'was'], ciebie: ['ciebie', 'ciebie', 'ciebie', 'was'],
mnie: ['mnie', 'mnie', 'mnie', 'nas'], mnie: ['mnie', 'mnie', 'mnie', 'nas'],
podjąłem: ['podjąłem', 'podjęłam', 'podjęłom', 'podjęliśmy'], podjąłem: ['podjąłem', 'podjęłam', 'podjęłom', 'podjęliśmy'],
dokonałeś: ['dokonałeś', 'dokonałaś', 'dokonałoś', 'dokonaliście'], dokonałeś: ['dokonałeś', 'dokonałaś', 'dokonałoś', 'dokonaliście'],
odmówiłeś: ['odmówiłeś', 'odmówiłaś', 'odmówiłoś', 'odmówiliście'], odmówiłeś: ['odmówiłeś', 'odmówiłaś', 'odmówiłoś', 'odmówiliście'],
odznaczyłem: ['odznaczyłem', 'odznaczyłam', 'odznaczyłom', 'odznaczyliśmy'], odznaczyłem: ['odznaczyłem', 'odznaczyłam', 'odznaczyłom', 'odznaczyliśmy'],
proszę: ['proszę', 'proszę', 'proszę', 'prosimy'], proszę: ['proszę', 'proszę', 'proszę', 'prosimy'],
odmówiłem: ['odmówiłem', 'odmówiłam', 'odmówiłom', 'odmówiliśmy'], odmówiłem: ['odmówiłem', 'odmówiłam', 'odmówiłom', 'odmówiliśmy'],
} as { [key: string]: string[] }; } as { [key: string]: string[] };
export default words; export default words;
export function v(key: string, index: number) { export function v(key: string, index: number) {
let result = words[key.toLowerCase()]?.[index] || key; let result = words[key.toLowerCase()]?.[index] || key;
if (key[0] == key[0].toUpperCase()) { if (key[0] == key[0].toUpperCase()) {
result = [result[0].toUpperCase(), ...result.slice(1)].join(''); result = [result[0].toUpperCase(), ...result.slice(1)].join('');
} }
return result; return result;
} }

View File

@ -3,7 +3,7 @@
.sidebar { .sidebar {
.header { .header {
display: grid; display: grid;
grid-template-columns: 1.75rem 1fr 1.25rem; grid-template-columns: 1.75rem 1fr 10rem;
align-items: center; align-items: center;
max-height: 3.5rem; max-height: 3.5rem;
min-height: 3.5rem; min-height: 3.5rem;
@ -13,10 +13,6 @@
background: #ffffff; background: #ffffff;
z-index: 1; z-index: 1;
&--without-logo {
grid-template-columns: 1fr 1.25rem;
}
.webpage-metadata { .webpage-metadata {
word-break: break-all; word-break: break-all;
display: flex; display: flex;
@ -112,4 +108,41 @@
align-self: flex-start; align-self: flex-start;
} }
} }
.button {
border: 0;
outline: 0;
height: 3rem;
font-size: 0.875rem;
line-height: 0.875rem;
height: 2.5rem;
cursor: pointer;
background-color: #000 !important;
&--report {
font-weight: 800;
padding: 0 1.5rem;
margin-left: 0.5rem;
background-color: #000;
color: $icd-rentgen-color;
&:hover {
background-image: linear-gradient(
to right,
$icd-rentgen-color 0%,
$icd-rentgen-color 4%,
#000 4%,
#000 100%
);
animation: slidebg 1s cubic-bezier(0.19, 1, 0.22, 1) infinite;
}
}
@keyframes slidebg {
to {
background-position: 155px;
}
}
}
} }

View File

@ -17,7 +17,6 @@ import './../../styles/global.scss';
import './sidebar.scss'; import './sidebar.scss';
const Sidebar = () => { const Sidebar = () => {
// const [origin, setOrigin] = React.useState<string | null>(null);
const url = new URL(document.location.toString()); const url = new URL(document.location.toString());
const origin = url.searchParams.get('origin'); const origin = url.searchParams.get('origin');
@ -70,15 +69,23 @@ const Sidebar = () => {
<span>Przejdź do wybranej strony internetowej</span> <span>Przejdź do wybranej strony internetowej</span>
)} )}
</div> </div>
{stolenDataView ? ( <button
<a href="https://internet-czas-dzialac.pl"> className="button button--report"
<img src="/assets/icons/info_circle_outline.svg" width="20" height="20" /> onClick={() => {
</a> const params = [
) : ( 'height=' + screen.height,
<button onClick={() => setStolenDataView(true)}> 'width=' + screen.width,
<img src="/assets/icons/short_left.svg" width="20" height="20" /> 'fullscreen=yes',
</button> ].join(',');
)} window.open(
`/components/report-window/report-window.html?origin=${origin}`,
'new_window',
params
);
}}
>
Generuj raport
</button>
</header> </header>
{stolenDataView ? ( {stolenDataView ? (
@ -126,34 +133,32 @@ const Sidebar = () => {
{detailsVisibility ? 'Ukryj szczegóły' : 'Wyświetlaj szczegóły'} {detailsVisibility ? 'Ukryj szczegóły' : 'Wyświetlaj szczegóły'}
</span> </span>
</button> </button>
<button {localStorage.getItem('blottingBrowser') ===
onClick={async () => { 'nikttakniesplamitwojejprzeglądarkijakspidersweb' ? (
if ( <button
window.confirm( onClick={() => {
'Czy chcesz wczytać wszystkie domeny w celu „splamienia” twojej przeglądarki? (Ten krok jest opcjonalny)' if (
) window.confirm(
) { 'Czy chcesz wczytać wszystkie domeny w celu „splamienia” twojej przeglądarki? Uwaga przeglądarka może zablokować otwieranie nowych kart. (Ten krok jest opcjonalny)'
let deep_copy = JSON.parse(
JSON.stringify(
Object.values(getMemory().getClustersForOrigin(origin)).map(
(domain) => domain.id
)
) )
); ) {
console.log('deep_copy', deep_copy); let deep_copy = JSON.parse(
for (const domain of deep_copy) { JSON.stringify(
const win = window.open( Object.values(
`https://${domain}`, getMemory().getClustersForOrigin(origin)
'_blank', ).map((domain) => domain.id)
`width=400,height=450,screenX=300,screenY=200` )
); );
for (const domain of deep_copy) {
window.open(`https://${domain}`);
}
} }
} }}
}} >
> <img src="/assets/icons/bulb.svg" width="20" height="20" />
<img src="/assets/icons/bulb.svg" width="20" height="20" /> <span>Odwiedź wszystkie domeny</span>
<span>Odwiedź wszystkie domeny</span> </button>
</button> ) : null}
</nav> </nav>
) : null} ) : null}
@ -202,6 +207,7 @@ const Sidebar = () => {
setWarningDataDialogAck={setWarningDataDialogAck} setWarningDataDialogAck={setWarningDataDialogAck}
detailsVisibility={detailsVisibility} detailsVisibility={detailsVisibility}
setDetailsVisibility={setDetailsVisibility} setDetailsVisibility={setDetailsVisibility}
setStolenDataView={setStolenDataView}
/> />
)} )}
</section> </section>

View File

@ -83,6 +83,7 @@
word-break: break-all; word-break: break-all;
text-overflow: clip; text-overflow: clip;
padding-left: 0.25rem; padding-left: 0.25rem;
padding-right: 0.25rem;
} }
.icons { .icons {

View File

@ -15,7 +15,7 @@ function StolenDataValue({ entry }: { entry: StolenDataEntry; prefixKey?: string
body = <></>; body = <></>;
} else { } else {
body = ( body = (
<div data-version={version}>{maskString(entry.value, 1, MAX_STRING_VALUE_LENGTH)}</div> <div data-version={version}>{entry.value}</div>
); );
} }
return ( return (
@ -26,7 +26,7 @@ function StolenDataValue({ entry }: { entry: StolenDataEntry; prefixKey?: string
getMemory().emit('change', false, entry.request.shorthost, 'clicked value'); getMemory().emit('change', false, entry.request.shorthost, 'clicked value');
e.stopPropagation(); e.stopPropagation();
}} }}
title={maskString(entry.value, 1, MAX_STRING_VALUE_LENGTH)} title={entry.value}
> >
{body} {body}
</td> </td>
@ -41,7 +41,7 @@ function StolenDataRow({ entry }: { entry: StolenDataEntry }) {
data-version={version} data-version={version}
className={`${entry.isMarked ? 'toggled' : 'untoggled'}`} className={`${entry.isMarked ? 'toggled' : 'untoggled'}`}
> >
<td className="checkbox"> {/* <td className="checkbox">
<input <input
type="checkbox" type="checkbox"
checked={entry.isMarked} checked={entry.isMarked}
@ -55,18 +55,18 @@ function StolenDataRow({ entry }: { entry: StolenDataEntry }) {
); );
}} }}
/> />
</td> </td> */}
<th <th
title={`Nazwa: ${entry.name}\nŹródło: ${entry.source}`} title={`Nazwa: ${entry.name}\nŹródło: ${entry.source}`}
onClick={() => { // onClick={() => {
entry.toggleMark(); // entry.toggleMark();
getMemory().emit( // getMemory().emit(
'change', // 'change',
false, // false,
entry.request.shorthost, // entry.request.shorthost,
'Clicked entry name' // 'Clicked entry name'
); // );
}} // }}
> >
{entry.name} {entry.name}
</th> </th>

View File

@ -19,6 +19,7 @@ const Toolbar = () => {
const [stolenDataView, setStolenDataView] = React.useState<boolean>(true); const [stolenDataView, setStolenDataView] = React.useState<boolean>(true);
const [eventCounts, setEventCounts] = useEmitter(getMemory()); const [eventCounts, setEventCounts] = useEmitter(getMemory());
const [cookieDomainCopy, setCookieDomainCopy] = React.useState<string | null>(null); const [cookieDomainCopy, setCookieDomainCopy] = React.useState<string | null>(null);
const [marksOccurrence, setMarksOccurrence] = React.useState<boolean>(false);
const [exposedOriginDomainCopy, setExposedOriginDomainCopy] = React.useState<string | null>( const [exposedOriginDomainCopy, setExposedOriginDomainCopy] = React.useState<string | null>(
null null
); );
@ -109,6 +110,23 @@ const Toolbar = () => {
} }
}, [eventCounts['*'], origin]); }, [eventCounts['*'], origin]);
React.useEffect(() => {
for (const cluster of Object.values(getMemory().getClustersForOrigin(origin))) {
if (cluster.hasMarks()) {
return setMarksOccurrence(true);
}
}
return setMarksOccurrence(false);
}, [eventCounts['*']]);
function autoMark() {
for (const cluster of Object.values(getMemory().getClustersForOrigin(origin))) {
cluster.autoMark();
}
return setMarksOccurrence(true);
}
return ( return (
<div className="toolbar"> <div className="toolbar">
<header className="header"> <header className="header">
@ -204,6 +222,7 @@ const Toolbar = () => {
'width=' + screen.width, 'width=' + screen.width,
'fullscreen=yes', 'fullscreen=yes',
].join(','); ].join(',');
autoMark();
window.open( window.open(
`/components/sidebar/sidebar.html?origin=${origin}`, `/components/sidebar/sidebar.html?origin=${origin}`,
'new_window', 'new_window',
@ -221,6 +240,7 @@ const Toolbar = () => {
'width=' + screen.width, 'width=' + screen.width,
'fullscreen=yes', 'fullscreen=yes',
].join(','); ].join(',');
autoMark();
window.open( window.open(
`/components/report-window/report-window.html?origin=${origin}`, `/components/report-window/report-window.html?origin=${origin}`,
'new_window', 'new_window',

View File

@ -1,4 +1,4 @@
import React from 'react'; import React, { Fragment } from 'react';
import './options.scss'; import './options.scss';
export default function Options({ export default function Options({
@ -12,6 +12,7 @@ export default function Options({
setWarningDataDialogAck, setWarningDataDialogAck,
detailsVisibility, detailsVisibility,
setDetailsVisibility, setDetailsVisibility,
setStolenDataView,
}: { }: {
minValueLength: number; minValueLength: number;
setMinValueLength: (n: number) => void; setMinValueLength: (n: number) => void;
@ -23,85 +24,94 @@ export default function Options({
setWarningDataDialogAck: (b: boolean) => void; setWarningDataDialogAck: (b: boolean) => void;
detailsVisibility: boolean; detailsVisibility: boolean;
setDetailsVisibility: (b: boolean) => void; setDetailsVisibility: (b: boolean) => void;
setStolenDataView: (b: boolean) => void;
}) { }) {
return ( return (
<div className="options-container"> <Fragment>
<span>Interfejs</span> <nav>
<fieldset> <button onClick={() => setStolenDataView(true)}>
<div className="input-container"> <img src="/assets/icons/short_left.svg" width="20" height="20" />
<input <span>Wróć do szczegółów</span>
type="checkbox" </button>
id="detailsVisibility" </nav>
checked={detailsVisibility} <div className="options-container">
onChange={(e) => { <span>Interfejs</span>
setDetailsVisibility(e.target.checked); <fieldset>
localStorage.setItem( <div className="input-container">
'detailsVisibility', <input
e.target.checked as unknown as string type="checkbox"
); id="detailsVisibility"
}} checked={detailsVisibility}
/> onChange={(e) => {
<label className="label-checkbox" htmlFor="detailsVisibility"> setDetailsVisibility(e.target.checked);
Wyświetlaj szczegóły pozyskanych danych localStorage.setItem(
</label> 'detailsVisibility',
</div> e.target.checked as unknown as string
<div className="input-container"> );
<input }}
type="checkbox" />
id="warningDataDialogAck" <label className="label-checkbox" htmlFor="detailsVisibility">
checked={warningDataDialogAck} Wyświetlaj szczegóły pozyskanych danych
onChange={(e) => { </label>
setWarningDataDialogAck(e.target.checked); </div>
localStorage.setItem( <div className="input-container">
'warningDataDialogAck', <input
e.target.checked as unknown as string type="checkbox"
); id="warningDataDialogAck"
}} checked={warningDataDialogAck}
/> onChange={(e) => {
<label className="label-checkbox" htmlFor="warningDataDialogAck"> setWarningDataDialogAck(e.target.checked);
Wyświetlaj komunikat o pozyskiwanych danych localStorage.setItem(
</label> 'warningDataDialogAck',
</div> e.target.checked as unknown as string
</fieldset> );
<span>Ustawienia zaawansowane</span> }}
<fieldset> />
<div className="input-container"> <label className="label-checkbox" htmlFor="warningDataDialogAck">
<label htmlFor="minValueLength"> Wyświetlaj komunikat o pozyskiwanych danych
Pokazuj tylko wartości o długości co najmniej{' '} </label>
</label> </div>
<input </fieldset>
type="number" <span>Ustawienia zaawansowane</span>
id="minValueLength" <fieldset>
value={minValueLength} <div className="input-container">
onChange={(e) => { <label htmlFor="minValueLength">
setMinValueLength(parseInt(e.target.value)); Pokazuj tylko wartości o długości co najmniej{' '}
localStorage.setItem('minValueLength', e.target.value); </label>
}} <input
/> type="number"
</div> id="minValueLength"
<div className="input-container"> value={minValueLength}
<input onChange={(e) => {
type="checkbox" setMinValueLength(parseInt(e.target.value));
id="cookiesOnly" localStorage.setItem('minValueLength', e.target.value);
checked={cookiesOnly} }}
onChange={(e) => setCookiesOnly(e.target.checked)} />
/> </div>
<label className="label-checkbox" htmlFor="cookiesOnly"> <div className="input-container">
Pokazuj tylko dane z cookiesów <input
</label> type="checkbox"
</div> id="cookiesOnly"
<div className="input-container"> checked={cookiesOnly}
<input onChange={(e) => setCookiesOnly(e.target.checked)}
type="checkbox" />
id="cookiesOrOriginOnly" <label className="label-checkbox" htmlFor="cookiesOnly">
checked={cookiesOrOriginOnly} Pokazuj tylko dane z cookiesów
onChange={(e) => setCookiesOrOriginOnly(e.target.checked)} </label>
/> </div>
<label className="label-checkbox" htmlFor="cookiesOrOriginOnly"> <div className="input-container">
Pokazuj tylko dane z cookiesów lub z częścią historii przeglądania <input
</label> type="checkbox"
</div> id="cookiesOrOriginOnly"
</fieldset> checked={cookiesOrOriginOnly}
</div> onChange={(e) => setCookiesOrOriginOnly(e.target.checked)}
/>
<label className="label-checkbox" htmlFor="cookiesOrOriginOnly">
Pokazuj tylko dane z cookiesów lub z częścią historii przeglądania
</label>
</div>
</fieldset>
</div>
</Fragment>
); );
} }