diff --git a/components/report-window/email-content.tsx b/components/report-window/email-content.tsx index b6aad6e..eb99062 100644 --- a/components/report-window/email-content.tsx +++ b/components/report-window/email-content.tsx @@ -39,10 +39,12 @@ export default function EmailContent({ function copyTextToClipboard() { // Should be changed in the future to Clipboard API (https://developer.mozilla.org/en-US/docs/Web/API/Clipboard/write#browser_compatibility) let r = document.createRange(); - r.selectNode(document.querySelector('.mail-container__content')); - window.getSelection().addRange(r); + const container = document.querySelector('.mail-container__content'); + if (!container) return; + r.selectNode(container); + window.getSelection()?.addRange(r); document.execCommand('copy'); - window.getSelection().removeAllRanges(); + window.getSelection()?.removeAllRanges(); setCopy(true); } diff --git a/components/report-window/generate-survey-questions.ts b/components/report-window/generate-survey-questions.ts index 9c3c44e..c0b3cd4 100644 --- a/components/report-window/generate-survey-questions.ts +++ b/components/report-window/generate-survey-questions.ts @@ -122,7 +122,10 @@ function generateHostPage( defaultValueExpression: index == 0 ? 'marketing' - : `{${f('legitimate_interest_description', previous_cluster)}}`, + : `{${f( + 'legitimate_interest_description', + previous_cluster || undefined + )}}`, }, { type: 'radiogroup', diff --git a/components/report-window/har-converter.tsx b/components/report-window/har-converter.tsx index 16fe731..ed0e873 100644 --- a/components/report-window/har-converter.tsx +++ b/components/report-window/har-converter.tsx @@ -16,7 +16,9 @@ function handleNewFile( ); setFiltered(new Blob([JSON.stringify(content)], { type: 'application/json' })); }); - reader.readAsText(element.files[0]); + const file = element?.files?.[0]; + if (!file) throw new Error('file empty?'); + reader.readAsText(file); } function generateFakeHAR(entries: StolenDataEntry[]) { @@ -80,8 +82,11 @@ export default function HARConverter({ entries }: { entries: StolenDataEntry[] } type="file" accept=".har" onChange={(e) => { - setFilename(e.target.files[0].name); - handleNewFile(e.target, entries, setFiltered); + const file = e.target?.files?.[0]; + if (file) { + setFilename(file.name); + handleNewFile(e.target, entries, setFiltered); + } }} /> {(filtered && ( diff --git a/components/report-window/problems/unlawful-data.tsx b/components/report-window/problems/unlawful-data.tsx deleted file mode 100644 index 8bd8e99..0000000 --- a/components/report-window/problems/unlawful-data.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import { RequestCluster } from '../../../request-cluster'; -import { ExplainerKey } from '../explainers'; -import { ParsedAnswers, ParsedHostAnswers } from '../parse-answers'; -import { v } from '../verbs'; -import { Problem } from './problem'; - -type UnlawfulDataClassification = 'no_purpose'; - -export function classifyUnlawfulData( - hostAnswers: ParsedHostAnswers, - cluster: RequestCluster -): UnlawfulDataClassification { - if (hostAnswers.present == 'not_mentioned' && hostAnswers.was_processing_necessary == 'no') { - return 'no_purpose'; - } -} - -export class UnlawfulData extends Problem { - static qualifies(answers: ParsedAnswers, clusters: RequestCluster[]): boolean {} - getEmailContent() { - const _ = (key: string) => v(key, this.answers.zaimek); - return ( - <> -

Przetwarzanie danych osobowych bez ważnej podsawy prawnej

-

- {_('Moje')} dane osobowe zostały ujawnione podmiotom, które są właścicielami - domen: -

- {this.getRangeDescription()} - - ); - } - getNecessaryExplainers() { - return [] as ExplainerKey[]; - } -} diff --git a/components/report-window/report-window.tsx b/components/report-window/report-window.tsx index 1872825..a9b1db3 100644 --- a/components/report-window/report-window.tsx +++ b/components/report-window/report-window.tsx @@ -22,15 +22,17 @@ function Report() { const url = new URL(document.location.toString()); const origin = url.searchParams.get('origin'); const [counter] = useEmitter(getMemory()); + const rawAnswers = url.searchParams.get('answers'); const [answers, setAnswers] = React.useState( - url.searchParams.get('answers') ? JSON.parse(url.searchParams.get('answers')) : null + rawAnswers ? JSON.parse(rawAnswers) : null ); const [mode, setMode] = React.useState(url.searchParams.get('mode') || 'survey'); const [scrRequestPath, setScrRequestPath] = React.useState(''); - const clusters = getMemory().getClustersForOrigin(origin); + const clusters = getMemory().getClustersForOrigin(origin || ''); React.useEffect(() => { + if (!origin) return; const url = new URL(document.location.toString()); url.searchParams.set('origin', origin); url.searchParams.set('answers', JSON.stringify(answers)); @@ -38,8 +40,12 @@ function Report() { history.pushState({}, 'Rentgen', url.toString()); }, [mode, answers, origin]); const visited_url = Object.values(clusters) - .find((cluster) => cluster.getMarkedRequests().length > 0) - ?.getMarkedRequests()[0].originalURL; + .sort((clusterA, clusterB) => (clusterA.lastModified > clusterB.lastModified ? -1 : 1)) + .find((cluster) => !!cluster.lastFullUrl)?.lastFullUrl; + + if (!visited_url) { + return
Wczytywanie...
; + } const result = (
diff --git a/components/report-window/screenshot-generator.tsx b/components/report-window/screenshot-generator.tsx index f8c6cf3..b5dd5aa 100644 --- a/components/report-window/screenshot-generator.tsx +++ b/components/report-window/screenshot-generator.tsx @@ -66,7 +66,7 @@ export default function ScreenshotGenerator({ }) { const [mode, setMode] = React.useState('idle'); const [images, setImages] = React.useState([]); - const [taskId, setTaskId] = React.useState(null); + const [taskId, setTaskId] = React.useState(null); const [output, setOutput] = React.useState({}); async function subscribeTask(path: string): Promise { @@ -75,8 +75,8 @@ export default function ScreenshotGenerator({ await new Promise((resolve) => setTimeout(resolve, 1000)); response = await (await pollTask(path)).json(); setImages((response as screenshotTask)?.images); - document.querySelector('.images').scrollTo({ - top: document.querySelector('.images').scrollHeight, + document.querySelector('.images')?.scrollTo({ + top: document.querySelector('.images')?.scrollHeight, behavior: 'smooth', }); } diff --git a/components/report-window/use-survey.ts b/components/report-window/use-survey.ts index 39ef13b..4a1a85f 100644 --- a/components/report-window/use-survey.ts +++ b/components/report-window/use-survey.ts @@ -8,8 +8,8 @@ import verbs, { v } from './verbs'; export default function useSurvey( clusters: RequestCluster[], { onComplete }: { onComplete: (sender: { data: RawAnswers }) => void } -): Survey.ReactSurveyModel { - const [survey, setSurvey] = React.useState(null); +): Survey.ReactSurveyModel | null { + const [survey, setSurvey] = React.useState(null); React.useEffect(() => { const model = generateSurveyQuestions(clusters); const survey = new Survey.Model(model); diff --git a/components/sidebar/sidebar.tsx b/components/sidebar/sidebar.tsx index f3155cc..36744a9 100644 --- a/components/sidebar/sidebar.tsx +++ b/components/sidebar/sidebar.tsx @@ -1,20 +1,11 @@ import React from 'react'; import ReactDOM from 'react-dom'; -import Options from '../../options'; -import { StolenData } from './stolen-data'; -import { getshorthost, useEmitter } from '../../util'; import { getMemory } from '../../memory'; - -async function getCurrentTab() { - const [tab] = await browser.tabs.query({ - active: true, - windowId: browser.windows.WINDOW_ID_CURRENT, - }); - return tab; -} - +import Options from '../../options'; +import { useEmitter } from '../../util'; import './../../styles/global.scss'; import './sidebar.scss'; +import { StolenData } from './stolen-data'; const Sidebar = () => { const url = new URL(document.location.toString()); @@ -28,8 +19,8 @@ const Sidebar = () => { const [cookiesOnly, setCookiesOnly] = React.useState(false); const [stolenDataView, setStolenDataView] = React.useState(true); const [cookiesOrOriginOnly, setCookiesOrOriginOnly] = React.useState(false); - const [eventCounts, setEventCounts] = useEmitter(getMemory()); - const [marksOccurrence, setMarksOccurrence] = React.useState(false); + const [eventCounts] = useEmitter(getMemory()); + const [_, setMarksOccurrence] = React.useState(false); const [infoDataDialogAck, setInfoDataDialogAck] = React.useState( localStorage.getItem('infoDataDialogAck') === null ? true @@ -53,6 +44,7 @@ const Sidebar = () => { ); React.useEffect(() => { + if (!origin) return; for (const cluster of Object.values(getMemory().getClustersForOrigin(origin))) { if (cluster.hasMarks()) { return setMarksOccurrence(true); @@ -62,6 +54,7 @@ const Sidebar = () => { return setMarksOccurrence(false); }, [eventCounts['*']]); + if (!origin) return
Błąd: Brak parametru "origin"
; return (
@@ -204,7 +197,7 @@ const Sidebar = () => { { ) : ( ; + eventCounts: Record; minValueLength: number; cookiesOnly: boolean; cookiesOrOriginOnly: boolean; @@ -43,7 +43,7 @@ export function StolenData({ origin={origin} shorthost={cluster.id} key={cluster.id + origin} - refreshToken={eventCounts[cluster.id]} + refreshToken={eventCounts[cluster.id] || 0} minValueLength={minValueLength} cookiesOnly={cookiesOnly} cookiesOrOriginOnly={cookiesOrOriginOnly} diff --git a/components/sidebar/tab-dropdown.tsx b/components/sidebar/tab-dropdown.tsx index 4729dc7..1034b67 100644 --- a/components/sidebar/tab-dropdown.tsx +++ b/components/sidebar/tab-dropdown.tsx @@ -1,4 +1,5 @@ import React from 'react'; +import { Tab } from '../../util'; export default function TabDropdown({ setPickedTab, @@ -7,7 +8,7 @@ export default function TabDropdown({ setPickedTab: (tab_id: number) => void; pickedTab: number; }) { - const [tabs, setTabs] = React.useState([]); + const [tabs, setTabs] = React.useState([]); React.useEffect(() => { browser.tabs.query({ currentWindow: true }).then(setTabs); }, []); diff --git a/components/toolbar/toolbar.tsx b/components/toolbar/toolbar.tsx index cbde180..73342fd 100644 --- a/components/toolbar/toolbar.tsx +++ b/components/toolbar/toolbar.tsx @@ -16,10 +16,9 @@ import './toolbar.scss'; const Toolbar = () => { const [origin, setOrigin] = React.useState(null); - const [stolenDataView, setStolenDataView] = React.useState(true); - const [eventCounts, setEventCounts] = useEmitter(getMemory()); + const [eventCounts] = useEmitter(getMemory()); const [cookieDomainCopy, setCookieDomainCopy] = React.useState(null); - const [marksOccurrence, setMarksOccurrence] = React.useState(false); + const [_, setMarksOccurrence] = React.useState(false); const [exposedOriginDomainCopy, setExposedOriginDomainCopy] = React.useState( null ); @@ -33,6 +32,7 @@ const Toolbar = () => { const tab = await getCurrentTab(); if (tab !== undefined) { + if (!tab.url) return; const url = new URL(tab.url); if (url.origin.startsWith('moz-extension')) { return; @@ -58,23 +58,27 @@ const Toolbar = () => { switch (exposedOriginDomains.length) { case 0: - return null; + break; case 1: - return setExposedOriginDomainCopy(`${exposedOriginDomains[0]}.`); + setExposedOriginDomainCopy(`${exposedOriginDomains[0]}.`); + break; case 2: - return setExposedOriginDomainCopy( + setExposedOriginDomainCopy( `${exposedOriginDomains[0]} oraz ${exposedOriginDomains[1]}.` ); + break; case 3: - return setExposedOriginDomainCopy( + setExposedOriginDomainCopy( `${exposedOriginDomains[0]}, ${exposedOriginDomains[1]} oraz ${exposedOriginDomains[2]}.` ); + break; default: - return setExposedOriginDomainCopy( + setExposedOriginDomainCopy( `${exposedOriginDomains[0]}, ${exposedOriginDomains[1]} (i ${ exposedOriginDomains.length - 2 < 2 ? 2 : exposedOriginDomains.length - 2 } innych).` ); + break; } }, [eventCounts['*'], origin]); @@ -86,25 +90,30 @@ const Toolbar = () => { switch (cookieDomains.length) { case 0: - return null; + null; case 1: - return setCookieDomainCopy(`${cookieDomains[0]}.`); + setCookieDomainCopy(`${cookieDomains[0]}.`); + break; case 2: - return setCookieDomainCopy(`${cookieDomains[0]} oraz ${cookieDomains[1]}.`); + setCookieDomainCopy(`${cookieDomains[0]} oraz ${cookieDomains[1]}.`); + break; case 3: - return setCookieDomainCopy( + setCookieDomainCopy( `${cookieDomains[0]}, ${cookieDomains[1]} oraz ${cookieDomains[2]}.` ); + break; default: - return setCookieDomainCopy( + setCookieDomainCopy( `${cookieDomains[0]}, ${cookieDomains[1]} (i ${ cookieDomains.length - 2 < 2 ? 2 : cookieDomains.length - 2 } innych).` ); + break; } }, [eventCounts['*'], origin]); React.useEffect(() => { + if (!origin) return; for (const cluster of Object.values(getMemory().getClustersForOrigin(origin))) { if (cluster.hasMarks()) { return setMarksOccurrence(true); @@ -115,12 +124,17 @@ const Toolbar = () => { }, [eventCounts['*']]); function autoMark() { + if (!origin) return; for (const cluster of Object.values(getMemory().getClustersForOrigin(origin))) { cluster.autoMark(); } return setMarksOccurrence(true); } + if (!origin) { + return
Wczytywanie...
; + } + return (
diff --git a/extended-request.ts b/extended-request.ts index 8a9d90c..ac9efa9 100644 --- a/extended-request.ts +++ b/extended-request.ts @@ -75,12 +75,12 @@ export default class ExtendedRequest { public tabId: number; public url: string; public shorthost: string; - public requestHeaders: Request['requestHeaders'] = []; - public originalURL: string; + public requestHeaders: { name: string; value?: string; binaryValue?: number[] }[] = []; + public originalURL: string | null = null; public origin: string; public initialized = false; - public stolenData: StolenDataEntry[]; - public originalPathname: string; + public stolenData: StolenDataEntry[] = []; + public originalPathname: string | null = null; public requestBody: RequestBody; static by_id = {} as Record; @@ -97,20 +97,21 @@ export default class ExtendedRequest { (this.data as any).frameAncestors = [ ...(data as any).frameAncestors.map((e: any) => ({ url: e.url })), ]; + this.origin = this.cacheOrigin(); } addHeaders(headers: Request['requestHeaders']) { - this.requestHeaders = headers; + this.requestHeaders = headers || []; return this; } - async init() { - await this.cacheOrigin(); + init() { + this.cacheOrigin(); this.initialized = true; this.stolenData = this.getAllStolenData(); } - async cacheOrigin(): Promise { + cacheOrigin(): string { let url: string; if (this.data.type === 'main_frame') { url = this.data.url; @@ -135,6 +136,7 @@ export default class ExtendedRequest { this.originalURL = url; this.origin = new URL(url).origin; this.originalPathname = new URL(url).pathname; + return this.origin; } isThirdParty() { diff --git a/request-cluster.ts b/request-cluster.ts index 8264804..ca4e4c6 100644 --- a/request-cluster.ts +++ b/request-cluster.ts @@ -1,4 +1,3 @@ -import { EventEmitter } from 'events'; import ExtendedRequest from './extended-request'; import { SaferEmitter } from './safer-emitter'; import { Sources, StolenDataEntry } from './stolen-data-entry'; @@ -11,12 +10,18 @@ export class RequestCluster extends SaferEmitter { public requests: ExtendedRequest[] = []; public representativeStolenData: StolenDataEntry[] = []; public expanded: boolean; + public lastModified: number = 0; + public lastFullUrl: string | null = null; constructor(public id: string) { super(); } add(request: ExtendedRequest) { this.requests.push(request); this.emit('change'); + this.lastModified = Date.now(); + if (request.originalURL) { + this.lastFullUrl = request.originalURL; + } } toggleExpanded(state: boolean) { diff --git a/tsconfig.json b/tsconfig.json index 50d873e..f2b501f 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -8,6 +8,8 @@ "typeRoots": ["node_modules/@types", "node_modules/web-ext-types"], "target": "es2019", "outDir": "lib", - "skipLibCheck": true + "skipLibCheck": true, + "strictNullChecks": true, + "strict": true } } diff --git a/util.ts b/util.ts index caae362..ffb976b 100644 --- a/util.ts +++ b/util.ts @@ -36,7 +36,11 @@ export function getshorthost(host: string) { .replace(/^.*:\/\//, '') .replace(/\/.*$/, '') .split('.'); - let lookback = !['co', 'com'].includes(parts.at(-2)) ? -2 : -3; + const second_last = parts.at(-2); + if (!second_last) { + throw new Error('url too short?'); + } + let lookback = !['co', 'com'].includes(second_last) ? -2 : -3; if (parts.at(-2) == 'doubleclick' || parts.at(-2) == 'google') { lookback = -4; // to distinguish between google ads and stats } else if (parts.at(-2) == 'google') { @@ -89,7 +93,7 @@ export async function getTabByID(id: number) { } export function parseToObject(str: unknown): Record { - let result: Record; + let result: Record = {}; let original_string: string; if (typeof str === 'string') { original_string = str; @@ -97,6 +101,8 @@ export function parseToObject(str: unknown): Record { } else if (typeof str == 'object') { result = str as Record; original_string = (result[Symbol.for('originalString')] as string) || JSON.stringify(str); + } else { + return {}; } result[Symbol.for('originalString')] = original_string; return result; @@ -149,10 +155,11 @@ export function getDate() { } export function toBase64(file: File): Promise { - return new Promise((resolve) => { + return new Promise((resolve, reject) => { const FR = new FileReader(); FR.addEventListener('load', (e) => { - resolve(e.target.result as string); + const target = e.target; + target ? resolve(target.result as string) : reject('empty file?'); }); FR.readAsDataURL(file); }); @@ -199,7 +206,8 @@ export function isBase64JSON(s: unknown): s is string { export function flattenObject( obj: unknown, - parser: (to_parse: unknown) => string | Record = (id) => id.toString(), + parser: (to_parse: { toString: () => string }) => string | Record = (id) => + id.toString(), key = '', ret = [] as [string, string][], parsed = false @@ -220,7 +228,7 @@ export function flattenObject( flattenObject(value, parser, prefix + subkey, ret); } } else if (!parsed) { - flattenObject(parser(obj), parser, key, ret, true); + flattenObject(parser(obj as { toString: () => string }), parser, key, ret, true); } else if (typeof obj === 'string') { ret.push([key, obj]); } else { @@ -231,7 +239,8 @@ export function flattenObject( export function flattenObjectEntries( entries: [string, unknown][], - parser: (to_parse: unknown) => string | Record = (id) => id.toString() + parser: (to_parse: { toString: () => string }) => string | Record = (id) => + id.toString() ): [string, string][] { return flattenObject(Object.fromEntries(entries), parser); }