Compare commits

...

2 Commits

Author SHA1 Message Date
Kuba Orlik
894391aaa3 Zrobiłem już większość logiki formularza.
Trzeba jeszcze zrobić tak, aby pytania dla kolejnych hostów pojawiały
się po kolei - tzn dopiero jak dla poprzedniego hosta
hostNeedsQuestions jest false, to pokazujemy następnego. Potem trzeba
popracować nad samym generowaniem treści na podstawie wszystkich tych
odpowiedzi
2022-01-30 21:03:49 +01:00
Kuba Orlik
2c5e4f1005 Questionare checkpoint 2022-01-30 17:37:04 +01:00
15 changed files with 611 additions and 353 deletions

View File

@ -1,5 +1,5 @@
trailingComma: "es5"
tabWidth: 4
printWidth: 80
printWidth: 100
semi: true
singleQuote: true

View File

@ -54,6 +54,10 @@ esbuild
outdir: './lib',
loader: { '.woff': 'file', '.woff2': 'file' },
plugins: [scss(), skipReactImports],
define: {
PLUGIN_NAME: '"Rentgen"',
PLUGIN_URL: '"https://git.internet-czas-dzialac.pl/icd/rentgen"',
},
external: ['react', 'react-dom'],
watch,
})

View File

@ -0,0 +1,43 @@
import { EmailTemplate3Config } from './email-template-3';
import hostSettingsRadio from './host-settings-radio';
export default function ConsentProblems({
settings,
host_id,
setConfig,
pronoun,
}: {
host_id: string;
setConfig: React.Dispatch<React.SetStateAction<EmailTemplate3Config>>;
settings: EmailTemplate3Config['hosts_settings'][string];
pronoun: 0 | 1 | 2 | 3;
}) {
if (settings.legal_basis_type !== 'consent') {
return '';
}
const p = pronoun;
return (
<div>
{hostSettingsRadio({
host_id,
setConfig,
field: 'consent_problems' as const,
value: settings.consent_problems,
options: {
claims_consent_but_sends_before_consent: /* HTML */ `Strona wysłała
${p == 3 ? 'nasze' : 'moje'} dane do ${host_id} zanim
${['wyraziłem', 'wyraziłam', 'wyraziłom', 'wyraziliśmy'][p]} na to zgodę.`,
claims_consent_but_there_was_no_easy_refuse: /* HTML */ `${[
'Kliknąłem',
'Kliknęłam',
'Kliknęłom',
'Kliknęliśmy',
][p]}
przycisk od wyrażania zgody, ale w okienku o zgodę nie było natychmiastowo
dostępnego przycisku do niewyrażenia zgody jednym kliknięciem.`,
none: 'Żadne z powyższych.',
},
})}
</div>
);
}

View File

@ -0,0 +1,108 @@
import { EmailTemplate3Config } from './email-template-3';
import hostSettingsDropdown from './host-setting-dropdown';
import ConsentProblems from './consent-problems';
import LegitimateInteresProblems from './legitimate-interest-problems';
import { hostNeedsQuestions } from './host-needs-questions';
export function setHostSetting<
P1 extends keyof EmailTemplate3Config['hosts_settings'],
P2 extends keyof EmailTemplate3Config['hosts_settings'][P1]
>(
setConfig: React.Dispatch<React.SetStateAction<EmailTemplate3Config>>,
[p1, p2]: [P1, P2],
value: EmailTemplate3Config['hosts_settings'][P1][P2]
) {
setConfig((v) => {
console.log(v, {
...v,
hosts_settings: {
...v.hosts_settings,
[p1]: {
...v.hosts_settings[p2],
[p2]: value,
},
},
});
return {
...v,
hosts_settings: {
...v.hosts_settings,
[p1]: {
...v.hosts_settings[p1],
[p2]: value,
},
},
};
});
}
export default function emailHostSettings(
config: EmailTemplate3Config,
setConfig: React.Dispatch<React.SetStateAction<EmailTemplate3Config>>
) {
if (config.policy_readable == 'null') {
return '';
}
const p = config.pronoun;
return (
<div>
{Object.entries(config.hosts_settings).map(([host_id, settings]) => (
<div key={host_id}>
<h5>
{host_id}, {hostNeedsQuestions(settings).toString()}
</h5>
<p>
Cele przetwarzania danych przez właściciela domeny {host_id}{' '}
{hostSettingsDropdown({
host_id,
setConfig,
settings,
field: 'presence',
value: settings.presence,
options: {
not_mentioned: ['nie są nigdzie na stronie opisane'],
mentioned_in_policy: [
'są opisane w polityce prywatności',
config.policy_readable !== 'yes',
],
mentioned_in_popup: ['są opisane w okienku RODO'],
},
})}
</p>
{!['not_mentioned', 'null'].includes(settings.presence) ? (
<p>
Wskazana przez administratora podstawa prawna dla{' '}
<strong> tego konkretnego celu</strong>{' '}
{hostSettingsDropdown({
host_id,
setConfig,
settings,
field: 'legal_basis_type' as const,
value: settings.legal_basis_type,
options: {
consent: ['to zgoda.'],
legitimate_interest: ['to uzasadniony interes.'],
not_mentioned: ['nie jest wskazana nigdzie na stronie.'],
},
})}
</p>
) : (
''
)}
{!['not_mentioned', 'null'].includes(settings.legal_basis_type) ? (
<div>
{ConsentProblems({ settings, host_id, pronoun: p, setConfig })}
{LegitimateInteresProblems({
settings,
host_id,
setConfig,
})}
</div>
) : (
''
)}
</div>
))}
</div>
);
}

View File

@ -1,232 +0,0 @@
import React from 'react';
import { RequestCluster } from '../request-cluster';
import { StolenDataEntry } from '../stolen-data-entry';
import { getDate, toBase64 } from '../util';
import DomainSummary from './domain-summary';
type PopupState = 'not_clicked' | 'clicked_but_no_reject_all';
export default function EmailTemplate1({
entries,
clusters,
}: {
entries: StolenDataEntry[];
clusters: Record<string, RequestCluster>;
version: number;
}): JSX.Element {
const [popupState, setPopupState] =
React.useState<PopupState>('not_clicked');
const [acceptAllName, setAcceptAllName] = React.useState<string>(
'Zaakceptuj wszystkie'
);
const [popupScreenshotBase64, setPopupScreenshotBase64] =
React.useState<string>(null);
return (
<div>
<label htmlFor="popupState">Status okienka o rodo:</label>
<select
id="popupState"
value={popupState}
onChange={(e) => setPopupState(e.target.value as PopupState)}
>
<option value="not_clicked">Nic nie kliknięte</option>
<option value="clicked_but_no_reject_all">
Kliknięte "akceptuj wszystkie", ale nie było opcji "Odrzuć
wszystkie"
</option>
</select>
{popupState === 'clicked_but_no_reject_all' ? (
<>
<div>
<label htmlFor="acceptAllName">
Tekst na przycisku do zatwierdzania wszystkich zgód:
</label>
<input
{...{
type: 'text',
value: acceptAllName,
onChange: (e) =>
setAcceptAllName(e.target.value),
}}
/>
</div>
<div>
<label htmlFor="popup-screenshot">
Zrzut ekranu z tego, jak wyglądał popup przed
kliknięciem {acceptAllName}:
</label>
<input
{...{
type: 'file',
id: 'popup-screenshot',
onChange: async (e) => {
setPopupScreenshotBase64(
await toBase64(e.target.files[0])
);
},
}}
/>
</div>
</>
) : null}
<p>
Dzień dobry, w dniu {getDate()} odwiedziłem stronę{' '}
{entries[0].request.originalURL}. Strona ta wysłała moje dane
osobowe do podmiotów trzecich - bez mojej zgody.{' '}
</p>
<ul>
{Object.values(clusters)
.filter((cluster) => cluster.hasMarks())
.map((cluster) => (
<DomainSummary cluster={cluster} />
))}
</ul>
<p>
{' '}
Dane te zostały wysłane przez Państwa stronę - a mówiąc
dokładniej, przez zamieszczone przez Państwa na tej stronie
skrypty.
</p>
{popupState === 'not_clicked' ? (
<p>
Nastąpiło to, zanim zdążyłem w ogóle przeczytać treść
wyskakującego okienka ze zgodami i zanim miałem szansę
wyrazić sprzeciw takiemu przetwarzaniu danych osobowych.
</p>
) : null}
{popupState === 'clicked_but_no_reject_all' ? (
<p>
O ile po wejściu na stronę wcisnąłem w wyskakującym okienku
przycisk {acceptAllName}, o tyle nie stanowi to według
mnie ważnej w świetle RODO zgody, gdyż brakowało w tym
okienku równie łatwo osiągalnego przycisku, którego
kliknięcie skutkowałoby zasygnalizowaniem braku mojej zgody
na takie przetwarzanie moich danych. Mówiąc wprost -
wyrażenie zgody było łatwiejsze niż jej niewyrażenie.
Niewyrażenie zgody wiąże się z negatywną konsekwencją
konieczności przechodzenia przez dodatkowe kroki w
wyskakującym okienku. Zatem tak otrzymana przez Państwo moja
zgoda nie jest poprawną podstawą prawną do przetwarzania
moich danych osobowych, gdyż nie spełnia warunku
dobrowolności wspomnianego w Art. 4. pkt 11{' '}
<em>
rozporządzenia Parlamentu Europejskiego i Rady (UE)
2016/679 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
</em>
.{<img {...{ src: popupScreenshotBase64 }} />}
</p>
) : null}
<p>
Udokumentowałem to na zrzutach ekranu z mojej przeglądarki
internetowej, które to zrzuty przesyłam w załączeniu.
</p>
<p>
Wiem, że nie wszystkie rodzaje przetwarzania danych wymagają
zgody użytkownika. W kontekście stron internetowych z
wymienionych w Art. 6. pkt 1. RODO mogą mieć zastosowanie albo
zgoda (Art. 6. pkt 1. lit. a)), albo niezbędność tego
przetwarzania do wykonania umowy (Art. 6. pkt 1. lit. b)), albo
uzasadniony interes (Art. 6. pkt 1. lit. f)). Wiem też, że każda
z tych podstaw prawnych ma moc dopiero po spełnieniu określonych
warunków.
</p>
<p>
Nie widzę ważnej podstawy prawnej legalizującej procesy
przetwarzania moich danych osobowych, jakie wymieniłem powyżej
(na pewno nie jest to przetwarzanie konieczne do wyświetlenia
strony z technicznego punktu widzenia). Jeżeli takie przesłanki
legalizujące jednak występują, proszę o ich wskazanie,
<strong>
{' '}
dla każdego z celów i podmiotów z <em>osobna</em>
</strong>
.
</p>
<p>
Jeżeli wskazaną przez Państwa przesłanką legalizującą dany
element procesu przetwarzania danych osobowych przez Państwa
stronę jest Art 6. pkt 1 lit. a) RODO (zgoda), na mocy Art. 7
pkt 1 RODO proszę o wykazanie, że udzieliłem Państwu zgodę na
takie przetwarzanie moich danych osobowych zanim to
przetwarzanie nastąpiło, oraz że ta zgoda jest ważna w świetle
RODO (odnosząc się w szczególności do art. 7 ust. 3 RODO). Z
góry zaznaczam, że ustawienia przeglądarki nie stanowią ważnej
w świetle RODO zgody.
</p>
<p>
Jeżeli wskazaną przez Państwa przesłanką legalizującą dany
element procesu przetwarzania danych osobowych przez Państwa
stronę jest Art 6. pkt 1 lit. b) RODO (niezbędność takiego
przetwarzania do wykonania umowy), proszę o wskazanie, w jaki
sposób ta konieczność zachodzi, oraz co sprawia, że Państwa
zdaniem nie można wykonać umowy związanej z wyświetleniem
Państwa strony bez przekazywania identyfikatora internetowego z
plików Cookies lub historii przeglądania w nagłówku Referer do
wskazanych podmiotów trzecich.
</p>
<p>
Jeżeli wskazaną przez Państwa przesłanką legalizującą dany
element procesu przetwarzania danych osobowych przez Państwa
stronę jest Art 6. pkt 1 lit. f) RODO (uzasadniony interes),
proszę o wskazanie, jaki to jest{' '}
<strong>konkretny interes</strong> (prosze o bardziej dokładny
opis niż np. tylko "marketing"), oraz o wynik testu równowagi
pomiędzy Państwa interesem a moimi podstawowymi wolnościami i
prawami - ze wskazaniem tego, co sprawia, że w Państwa ocenie
Państwa uzasadniony interes przeważa moje prawa i interesy w
kontekście wspomnianych powyżej procesów przetwarzania danych.
Proszę też pamiętać, że aby w ramach danego celu przetwarzania
powołać się na prawnie uzasadniony interes, powinni mi Państo
umożliwić wyrażenie sprzeciwu wobec przetwarzania moich danych w
tym celu <em>przed</em> rozpoczęciem przetwarzania - zob.{' '}
<a href="https://edpb.europa.eu/system/files/2021-11/edpb_guidelines_082020_on_the_targeting_of_social_media_users_pl_0.pdf">
Wytyczne 8/2020 Europejskiej Rady Ochrony Danych dotyczące
targetowania użytkowników mediów społecznościowych
</a>
</p>
<p>
Niniejszym zwracam się także z żądaniem ujawnienia tożsamości
podmiotów, które właścicielami wyżej wymienionych domen, abym
mógł zapoznać się z ich politykami prywatności i zwrócić się do
tych podmiotów o usunięcie z ich baz wysłanych przez Państwa
stronę moich danych.
</p>
<p>
Proszę też o wysłanie kopii danych zebranych na mój temat i
wysłanych do wyżej wymienionych podmiotów.
</p>
<p>
W odpowiedzi proszę się nie powoływać na IAB Europe i ich
rzekomą renomę w tworzeniu rozwiązań zgodnych z RODO. IAB chroni
interes reklamodawców, a nie Użytkowników, i ich rozwiązania
(np. TCF) {' '}
<a href="https://panoptykon.org/search/site/IAB">
notorycznie niezgodne z RODO i pozbawione szacunku dla
Użytkowników
</a>
.
</p>
<p>
Apeluję także o wprowadzenie stosownych zmian na stronie tak,
aby nie pozostawiać cienia wątpliwości odnośnie tego, na mocy
jakiej przesłanki legalizującej dane przetwarzane przez
wspomniane podmioty trzecie, lub tak, aby te dane po prostu nie
były wysyłane. Pomoże to zachować prywatność innym użytkownikom
Państwa strony. Polecam Państwa uwadze oficjalne wytyczne EROD
dotyczące zgody w kontekście RODO:
https://edpb.europa.eu/sites/default/files/files/file1/edpb_guidelines_202005_consent_pl.pdf
). Aby na przykład zapobiec automatycznemu wysyłaniu historii
przeglądania do podmiotów trzecich przez Państwa stronę, można
po prostu ustawić odpowiednio treść nagłówka{' '}
<a href="https://developer.mozilla.org/pl/docs/Web/HTTP/Headers/Referrer-Policy">
Referrer-Policy{' '}
</a>
.
</p>
</div>
);
}

View File

@ -1,16 +1,68 @@
import React from 'react';
import { toBase64 } from '../util';
import { EmailTemplate2Config } from './email-template-2';
import ConsentProblems from './consent-problems';
import emailHostSettings from './email-host-settings';
import { EmailTemplate3Config } from './email-template-3';
export default function EmailTemplate2Controls({
export default function EmailTemplate3Controls({
config,
setConfig,
}: {
config: EmailTemplate2Config;
setConfig: React.Dispatch<React.SetStateAction<EmailTemplate2Config>>;
config: EmailTemplate3Config;
setConfig: React.Dispatch<React.SetStateAction<EmailTemplate3Config>>;
}): JSX.Element {
const p = config.pronoun;
return (
<div>
<div>
<label htmlFor="pronoun">Forma czasownika:</label>
<select
id="pronoun"
value={config.pronoun}
onChange={(e) =>
setConfig((v) => ({
...v,
pronoun: parseInt(e.target.value) as EmailTemplate3Config['pronoun'],
}))
}
>
<option value="0">Wysłałem</option>
<option value="1">Wysłałam</option>
<option value="2">Wysłałom</option>
<option value="3">Wysłaliśmy</option>
</select>
</div>
<div>
<label htmlFor="policy_readable">
Czy polityka prywatności jest dostępna i czytelna?
</label>
<select
id="policy_readable"
value={config.policy_readable}
onChange={(e) =>
setConfig((v) => ({
...v,
policy_readable: e.target
.value as EmailTemplate3Config['policy_readable'],
}))
}
>
<option value="null" disabled>
wybierz opcję
</option>
<option value="yes">dostępna i czytelna</option>
<option value="entirely_obscured_by_popup">
dostępna, ale nieczytelna. Zasłania popup o RODO
</option>
<option value="cant_find">
Niedostępna. {['Szukałem', 'Szukałam', 'Szukałom', 'Szukaliśmy'][p]}, ale
nie {['znalazłem', 'znalazłam', 'znalazłom', 'znaleźliśmy'][p]} jej na
stronie
</option>
</select>
</div>
{emailHostSettings(config, setConfig)}
<div>
<label htmlFor="poup_type">Typ okienka o RODO: </label>
<select
@ -19,8 +71,7 @@ export default function EmailTemplate2Controls({
onChange={(e) =>
setConfig((v) => ({
...v,
popup_type: e.target
.value as EmailTemplate2Config['popup_type'],
popup_type: e.target.value as EmailTemplate3Config['popup_type'],
}))
}
>
@ -33,17 +84,13 @@ export default function EmailTemplate2Controls({
</div>
{config.popup_type !== 'none' ? (
<div>
<label htmlFor="popup_screenshot">
Zrzut ekranu okienka o RODO:
</label>
<label htmlFor="popup_screenshot">Zrzut ekranu okienka o RODO:</label>
<input
{...{
type: 'file',
id: 'popup_screenshot',
onChange: async (e) => {
const popup_screenshot_base64 = await toBase64(
e.target.files[0]
);
const popup_screenshot_base64 = await toBase64(e.target.files[0]);
setConfig((v) => ({
...v,
popup_screenshot_base64,
@ -55,55 +102,26 @@ export default function EmailTemplate2Controls({
) : (
''
)}
{config.popup_type === 'consent' ? (
<div>
<label htmlFor="acceptAllName">
Tekst na przycisku do zatwierdzania wszystkich zgód:
</label>
<input
{...{
type: 'text',
value: config.popup_accept_all_text,
onChange: (e) =>
setConfig((v) => ({
...v,
popup_accept_all_text: e.target.value,
})),
}}
/>
</div>
) : (
''
)}
<div>
<label htmlFor="popup_action">
Czy coś klikn*ł*m w informacjach o RODO?
</label>
<label htmlFor="popup_action">Czy coś klikn*ł*m w informacjach o RODO?</label>
<select
id="popup_action"
value={config.popup_type}
onChange={(e) =>
setConfig((v) => ({
...v,
popup_action: e.target
.value as EmailTemplate2Config['popup_action'],
popup_action: e.target.value as EmailTemplate3Config['popup_action'],
}))
}
>
<option value="ignored">Nic nie klin*ł*m</option>
<option value="accepted">
Kliknięte {config.popup_accept_all_text}
</option>
<option value="closed">
Zamkn*ł*m okienko (np. przyciskiem "X")
</option>
<option value="accepted">Kliknięte {config.popup_accept_all_text}</option>
<option value="closed">Zamkn*ł*m okienko (np. przyciskiem "X")</option>
</select>
</div>
{config.popup_action === 'closed' ? (
<div>
<label htmlFor="popup_closed_how">
Jak okienko zostało zamknięte? Poprzez
</label>
<label htmlFor="popup_closed_how">Jak okienko zostało zamknięte? Poprzez</label>
<input
id="popup_closed_how"
type="text"
@ -130,14 +148,13 @@ export default function EmailTemplate2Controls({
onChange={(e) =>
setConfig((v) => ({
...v,
popup_mentions_passive_consent:
e.target.checked,
popup_mentions_passive_consent: e.target.checked,
}))
}
/>
<label htmlFor="popup_mentions_passive_consent">
okienko wspomina o pasywnej zgodzie (np. korzystając ze
strony wyrażasz zgodę)
okienko wspomina o pasywnej zgodzie (np. korzystając ze strony wyrażasz
zgodę)
</label>
</div>
) : (
@ -146,8 +163,7 @@ export default function EmailTemplate2Controls({
{config.popup_mentions_passive_consent ? (
<div>
<label htmlFor="popup_passive_consent_text">
Jak okienko próbuje wmówić Ci, że wyrażasz zgodę?
Przeklej z treści okienka:
Jak okienko próbuje wmówić Ci, że wyrażasz zgodę? Przeklej z treści okienka:
</label>
<input
id="popup_passive_consent_text"

View File

@ -0,0 +1,147 @@
import React from 'react';
import { RequestCluster } from '../request-cluster';
import { StolenDataEntry } from '../stolen-data-entry';
import { getDate } from '../util';
import EmailTemplate3Controls from './email-template-3-controls';
declare var PLUGIN_NAME: string;
declare var PLUGIN_URL: string;
export type EmailTemplate3Config = {
pronoun: 0 | 1 | 2 | 3; // masc, fem, neutral, plural
policy_readable: 'null' | 'yes' | 'entirely_obscured_by_popup' | 'cant_find';
hosts_settings: Record<
string,
{
presence: 'null' | 'not_mentioned' | 'mentioned_in_popup' | 'mentioned_in_policy';
legal_basis_type: 'null' | 'consent' | 'legitimate_interest' | 'not_mentioned';
consent_problems?:
| 'claims_consent_but_sends_before_consent'
| 'claims_consent_but_there_was_no_easy_refuse'
| 'none'
| 'null';
legitimate_interest_activity_specified: 'null' | 'no' | 'precise' | 'vague';
legitimate_interest_activity_description: string;
}
>;
popup_type: 'none' | 'passive_cookie_banner' | 'consent';
popup_action: 'ignored' | 'accepted' | 'closed';
popup_closed_how: string;
popup_screenshot_base64: string | null;
popup_accept_all_text: string;
popup_mentions_passive_consent: boolean;
popup_passive_consent_text: string;
};
export default function EmailTemplate3({
entries,
clusters,
}: {
entries: StolenDataEntry[];
clusters: Record<string, RequestCluster>;
version: number;
}): JSX.Element {
const all_host_ids = Array.from(new Set(entries.map((entry) => entry.request.shorthost)));
const [config, setConfig] = React.useState<EmailTemplate3Config>({
pronoun: Math.round(Math.random()) as 0 | 1,
policy_readable: 'null',
hosts_settings: Object.fromEntries(
all_host_ids.map((cluster_id) => [
cluster_id,
{
presence: 'null',
legal_basis_presence: 'null',
legal_basis_type: 'null',
consent_problems: 'null',
legitimate_interest_activity_specified: 'null',
legitimate_interest_activity_description: '',
},
])
),
popup_type: 'none',
popup_action: 'ignored',
popup_screenshot_base64: null,
popup_accept_all_text: 'Zaakceptuj wszystkie',
popup_mentions_passive_consent: false,
popup_passive_consent_text: '',
popup_closed_how: 'kliknięcie przycisku „X”',
});
const visited_url = entries[0].request.originalURL;
const p = config.pronoun;
return (
<div style={{ display: 'flex', flexFlow: 'row wrap', margin: '-1rem' }}>
<div style={{ flexBasis: '50rem', margin: '1rem' }}>
<EmailTemplate3Controls {...{ config, setConfig }} />
</div>
<article
style={{
boxShadow: 'rgba(0, 0, 0, 0.2) 5px 3px 8px',
padding: '4rem 3rem',
margin: '1rem',
borderRadius: '0.25rem',
color: 'hsl(240, 5.7%, 15.8%)',
flexBasis: '50rem',
}}
>
<p>
Dzień dobry, w dniu {getDate()}{' '}
{['odwiedziłem', 'odwiedziłam', 'odwiedziłom', 'odwiedziliśmy'][p]} stronę{' '}
{visited_url} i {['zbadałem', 'zbadałam', 'zbadałom', 'zbadaliśmy'][p]} za
pomocą wtyczki <a href={PLUGIN_URL}>{PLUGIN_NAME}</a> w celu zbadania, jakie
informacje o {['mnie', 'mnie', 'mnie', 'nas'][p]} wysyła ta strona do podmiotów
trzecich.
</p>
<p>
{['Moją', 'Moją', 'Moją', 'Naszą'][p]} szczególną uwagę przykuło: WYFILTROWANE
WZGLĘDEM TEGO, CZY DANEGO PODMIOTU NIE MA W POLITYCE PRYWATNOŚCI LUB
POWIADOMIENIU O COOKIESACH{' '}
<ul>
<li>
- wysyłanie mojego identyfikatora internetowego [z Cookie] (value) oraz
części mojej historii przeglądania do właściciela domeny (domain);
</li>
<li> - (...).</li>
</ul>
</p>
<p>
Dane te zostały wysłane zanim {['miałem', 'miałam', 'miałom', 'mieliśmy'][p]}{' '}
szansę przeczytać Państwa politykę prywatności i w jakikolwiek czynny i
jednoznaczny sposób wyrazić zgodę na takie przetwarzanie moich danych osobowych.
</p>
{!['yes', 'null'].includes(config.policy_readable) ? (
<p>
{['Chciałem', 'Chciałam', 'Chciałom', 'Chcieliśmy'][p]} przeczytać Państwa
politykę prywatności przed akceptacją, ale{' '}
{config.policy_readable == 'cant_find' ? (
<>nie mogę znaleźć jej nigdzie na Państwa stronie.</>
) : (
''
)}{' '}
{config.policy_readable == 'entirely_obscured_by_popup' ? (
<>jest ona przesłonięta przez okienko o RODO.</>
) : (
''
)}
</p>
) : (
''
)}
<p>
Dane zostały udostępnione podmiotom, o których nie{' '}
{['znalazłem', 'znalazłam', 'znalazłom', 'znaleźliśmy'][p]} informacji ani w
Państwa polityce prywatności, ani w żadnym wyskakującym okienku na Państwa
stronie. Z tego powodu zwracam{p == 3 ? 'y' : ''} się do Państwa z pytaniem:
jakie były podstawy prawne takiego ujawnienia{' '}
{['moich', 'moich', 'moich', 'naszych'][p]} danych osobowych wyżej wymienionym
podmiotom? Uprzejmie {['proszę', 'proszę', 'proszę', 'prosimy'][p]} o wskazanie
podstawy prawnej dla każdego z tych podmiotów z osobna.
</p>
</article>
</div>
);
}

View File

@ -1,8 +1,6 @@
import React from 'react';
import { RequestCluster } from '../request-cluster';
import { StolenDataEntry } from '../stolen-data-entry';
import EmailTemplate1 from './email-template-1';
import EmailTemplate2 from './email-template-2';
import EmailTemplate3 from './email-template-3';
export default function EmailTemplate({
entries,
@ -13,21 +11,9 @@ export default function EmailTemplate({
clusters: Record<string, RequestCluster>;
version: number;
}) {
const [templateVersion, setTemplateVersion] = React.useState('2');
return (
<div>
<select
value={templateVersion}
onChange={(e) => setTemplateVersion(e.target.value)}
>
<option value="1">wersja 1</option>
<option value="2">wersja 2</option>
</select>
{templateVersion === '1' ? (
<EmailTemplate1 {...{ entries, clusters, version }} />
) : (
<EmailTemplate2 {...{ entries, clusters, version }} />
)}
<EmailTemplate3 {...{ entries, clusters, version }} />
</div>
);
}

View File

@ -0,0 +1,32 @@
import { EmailTemplate3Config } from './email-template-3';
export function hostNeedsQuestions({
presence,
legal_basis_type,
consent_problems,
legitimate_interest_activity_description,
legitimate_interest_activity_specified,
}: EmailTemplate3Config['hosts_settings'][string]) {
if (presence == 'not_mentioned') {
return false;
}
if (legal_basis_type == 'not_mentioned') {
return false;
}
if (legal_basis_type == 'consent' && consent_problems !== 'null') {
return false;
}
if (
legitimate_interest_activity_specified !== 'null' &&
legitimate_interest_activity_specified !== 'vague'
) {
return false;
}
if (
legal_basis_type == 'legitimate_interest' &&
legitimate_interest_activity_description != ''
) {
return false;
}
return true;
}

View File

@ -0,0 +1,46 @@
import { setHostSetting } from './email-host-settings';
import { EmailTemplate3Config } from './email-template-3';
export default function hostSettingsDropdown<
Field extends keyof EmailTemplate3Config['hosts_settings'][string]
>({
host_id,
setConfig,
value,
field,
options,
}: {
host_id: string;
setConfig: React.Dispatch<React.SetStateAction<EmailTemplate3Config>>;
settings: EmailTemplate3Config['hosts_settings'][string];
field: Field;
value: string;
options: Record<
Exclude<EmailTemplate3Config['hosts_settings'][string][Field], 'null'>,
[string, boolean?]
>;
}) {
return (
<select
value={value}
onChange={(e) =>
setHostSetting(
setConfig,
[host_id, field],
e.target.value as EmailTemplate3Config['hosts_settings'][string][typeof field]
)
}
>
<option value="null" disabled>
wybierz opcję
</option>
{Object.entries(options).map(
([value, [display, disabled]]: [string, [string, boolean]]) => (
<option value={value} disabled={disabled}>
{display}
</option>
)
)}
</select>
);
}

View File

@ -0,0 +1,47 @@
import { normalizeForClassname } from '../util';
import { EmailTemplate3Config } from './email-template-3';
export default function hostSettingsRadio<
Field extends keyof EmailTemplate3Config['hosts_settings'][string]
>({
options,
host_id,
field,
setConfig,
value,
}: {
host_id: string;
setConfig: React.Dispatch<React.SetStateAction<EmailTemplate3Config>>;
field: Field;
options: Record<Exclude<EmailTemplate3Config['hosts_settings'][string][Field], 'null'>, string>;
value: EmailTemplate3Config['hosts_settings'][string][Field];
}) {
return (
<div>
{Object.entries(options).map(([option_value, display]) => (
<div>
<input
type="radio"
name={normalizeForClassname(host_id + '_consent_problems')}
value={option_value}
checked={value == option_value}
id={normalizeForClassname(option_value + host_id)}
onChange={(e) => {
setConfig((v) => ({
...v,
hosts_settings: {
...v.hosts_settings,
[host_id]: {
...v.hosts_settings[host_id],
[field]: e.target.value,
},
},
}));
}}
/>
<label htmlFor={normalizeForClassname(option_value + host_id)}>{display}</label>
</div>
))}
</div>
);
}

View File

@ -0,0 +1,66 @@
import React from 'react';
import { setHostSetting } from './email-host-settings';
import { EmailTemplate3Config } from './email-template-3';
import hostSettingsDropdown from './host-setting-dropdown';
export default function LegitimateInteresProblems({
settings,
host_id,
pronoun,
setConfig,
}: {
host_id: string;
settings: EmailTemplate3Config['hosts_settings'][string];
setConfig: React.Dispatch<React.SetStateAction<EmailTemplate3Config>>;
}) {
if (settings.legal_basis_type !== 'legitimate_interest') {
return '';
}
const [description, setDescription] = React.useState('marketing');
return (
<div>
<div>
Czy administrator strony opisał szczegółowo, na czym polega uzasadniony interes w
kontekście tego celu?
{hostSettingsDropdown({
settings,
host_id,
setConfig,
field: 'legitimate_interest_activity_specified' as const,
value: settings.legitimate_interest_activity_specified,
options: {
precise: [
'Tak, wskazuje jasno na bieżące działania lub korzyści wynikające z takiego przetwarzania danych.',
],
vague: ['Wskazuje tylko ogólnie, jak np. „marketing” czy „statystyki”.'],
no: ['Nie. Nie wiadomo, na czym ten uzasadniony interes polega.'],
},
})}
</div>
{settings.legitimate_interest_activity_specified === 'vague' ? (
<div>
Jak administrator opisał to, na czym polega uzasadniony interes w kontekście{' '}
{host_id}?{' '}
<input
type="text"
value={description}
onChange={(e) => setDescription(e.target.value)}
/>
<button
onClick={(e) =>
setHostSetting(
setConfig,
[host_id, 'legitimate_interest_activity_description'],
description
)
}
>
zatwierdź
</button>
</div>
) : (
''
)}
</div>
);
}

View File

@ -9,14 +9,16 @@
<body>
<div
id="app"
style="max-width: 50rem; margin: 0 auto; padding: 1rem 0rem 2rem;"
style=" margin: 0 auto; padding: 1rem 0rem 2rem;"
></div>
<style>
tr:hover {
background-color: hsla(0, 0%, 0%, 0.1);
}
</style>
<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/report-window/report-window.js"></script>
</body>
</html>
</html>

View File

@ -80,44 +80,39 @@ function DataPreview({
}
function Report() {
console.time('getOrigin');
const origin = new URL(document.location.toString()).searchParams.get(
'origin'
);
console.timeEnd('getOrigin');
console.time('useMemory');
const [counter, setCounter] = useEmitter(getMemory());
console.timeEnd('useMemory');
console.time('getClustersForOrigin');
const clusters = getMemory().getClustersForOrigin(origin);
console.timeEnd('getClustersForOrigin');
const [entries, setEntries] = React.useState<StolenDataEntry[]>([]);
console.time('useEffect report-window');
React.useEffect(() => {
setEntries(
Object.values(clusters)
.map((cluster) => {
cluster.calculateRepresentativeStolenData();
return cluster.representativeStolenData;
})
.reduce(reduceConcat, [])
.filter((entry) => entry.isMarked)
try {
const origin = new URL(document.location.toString()).searchParams.get(
'origin'
);
}, []);
console.timeEnd('useEffect report-window');
if (entries.length == 0) {
return <>Wczytywanie...</>;
const [counter, setCounter] = useEmitter(getMemory());
const clusters = getMemory().getClustersForOrigin(origin);
const [entries, setEntries] = React.useState<StolenDataEntry[]>([]);
React.useEffect(() => {
setEntries(
Object.values(clusters)
.map((cluster) => {
cluster.calculateRepresentativeStolenData();
return cluster.representativeStolenData;
})
.reduce(reduceConcat, [])
.filter((entry) => entry.isMarked)
);
}, []);
if (entries.length == 0) {
return <>Wczytywanie...</>;
}
const result = (
<div {...{ 'data-version': counter }}>
<h1>Generuj treść maila dla {origin}</h1>
<EmailTemplate {...{ entries, clusters, version: counter }} />
{/* <HARConverter {...{ entries }} /> */}
</div>
);
return result;
} catch (e) {
console.error(e);
return <div>ERRO! {JSON.stringify(e)}</div>;
}
console.time('rendering template');
const result = (
<div {...{ 'data-version': counter }}>
<h1>Generuj treść maila dla {origin}</h1>
<EmailTemplate {...{ entries, clusters, version: counter }} />
<HARConverter {...{ entries }} />
</div>
);
console.timeEnd('rendering template');
return result;
}
ReactDOM.render(<Report />, document.getElementById('app'));

26
util.ts
View File

@ -87,17 +87,13 @@ export function parseToObject(str: unknown): Record<string | symbol, unknown> {
result = JSON.parse(str);
} else if (typeof str == 'object') {
result = str as Record<string | symbol, unknown>;
original_string =
(result[Symbol.for('originalString')] as string) ||
JSON.stringify(str);
original_string = (result[Symbol.for('originalString')] as string) || JSON.stringify(str);
}
result[Symbol.for('originalString')] = original_string;
return result;
}
export function isJSONObject(
str: unknown
): str is Record<string, unknown> | string | number {
export function isJSONObject(str: unknown): str is Record<string, unknown> | string | number {
try {
const firstChar = JSON.stringify(parseToObject(str))[0];
return ['{', '['].includes(firstChar);
@ -137,9 +133,10 @@ export function reduceConcat<T>(a: T[], b: T[]): T[] {
export function getDate() {
const d = new Date();
return `${d.getFullYear()}-${(d.getMonth() + 1)
return `${d.getFullYear()}-${(d.getMonth() + 1).toString().padStart(2, '0')}-${d
.getDate()
.toString()
.padStart(2, '0')}-${d.getDate().toString().padStart(2, '0')}`;
.padStart(2, '0')}`;
}
export function toBase64(file: File): Promise<string> {
@ -193,8 +190,7 @@ export function isBase64JSON(s: unknown): s is string {
export function flattenObject(
obj: unknown,
parser: (to_parse: unknown) => 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
@ -224,8 +220,7 @@ export function flattenObject(
export function flattenObjectEntries(
entries: [string, unknown][],
parser: (to_parse: unknown) => 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);
}
@ -236,8 +231,7 @@ export function maskString(
max_chars_total: number
): string {
const amount_of_chars_to_cut =
str.length -
Math.min(str.length * max_fraction_remaining, max_chars_total);
str.length - Math.min(str.length * max_fraction_remaining, max_chars_total);
if (amount_of_chars_to_cut == 0) {
return str;
}
@ -255,3 +249,7 @@ export function safeDecodeURIComponent(s: string) {
return s;
}
}
export function normalizeForClassname(string: string) {
return string.replace(/[^a-z0-9]/gi, '-');
}