1
0
forked from icd/rentgen

Compare commits

..

2 Commits

20 changed files with 924 additions and 2312 deletions

View File

@ -1,18 +1,3 @@
import { init } from './memory'; import { init } from "./memory";
console.log('🔴 [DIAGNOSTYKA] Wczytywanie background.ts, TARGET =', process.env.TARGET); init();
// Inicjalizacja pamięci storage
try {
init();
console.log('✅ init() zakończone pomyślnie');
} catch (error) {
console.error('❌ init() nie powiodło się:', error);
}
// Log zakończenia inicjalizacji
if (process.env.TARGET === 'chrome') {
console.log('🔵 Service worker Chrome Rentgen zainicjalizowany (używa chrome.storage.session)');
} else {
console.log('🦊 Strona tła Firefox Rentgen zainicjalizowana');
}

View File

@ -24,24 +24,6 @@ function Report() {
if (!origin) { if (!origin) {
return <div>Błąd: brak parametru "origin"</div>; return <div>Błąd: brak parametru "origin"</div>;
} }
// Oczekiwanie na gotowość pamięci Chrome
const [memoryReady, setMemoryReady] = React.useState(process.env.TARGET !== 'chrome');
React.useEffect(() => {
if (process.env.TARGET === 'chrome') {
const memory = getMemory();
if (typeof (memory as any).waitUntilReady === 'function') {
(memory as any).waitUntilReady().then(() => {
setMemoryReady(true);
console.log('✅ Memory gotowa dla okna raportu');
});
} else {
setMemoryReady(true);
}
}
}, []);
const [counter] = useEmitter(getMemory()); const [counter] = useEmitter(getMemory());
const rawAnswers = url.searchParams.get('answers'); const rawAnswers = url.searchParams.get('answers');
const [answers, setAnswers] = React.useState<ParsedAnswers>( const [answers, setAnswers] = React.useState<ParsedAnswers>(
@ -50,32 +32,22 @@ function Report() {
const [mode, setMode] = React.useState(url.searchParams.get('mode') || 'survey'); const [mode, setMode] = React.useState(url.searchParams.get('mode') || 'survey');
const [scrRequestPath, setScrRequestPath] = React.useState(''); const [scrRequestPath, setScrRequestPath] = React.useState('');
// Pobieranie klastrów tylko gdy pamięć jest gotowa const clusters = getMemory().getClustersForOrigin(origin || '');
const clusters = memoryReady ? getMemory().getClustersForOrigin(origin || '') : {};
React.useEffect(() => { React.useEffect(() => {
if (!origin || !memoryReady) return; if (!origin) return;
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, memoryReady]); }, [mode, answers, origin]);
// Wyświetlanie wczytywania w trakcie oczekiwania na pamięć
if (!memoryReady) {
return <div>Wczytywanie danych z rozszerzenia...</div>;
}
const visited_url = Object.values(clusters) const visited_url = Object.values(clusters)
.sort((a, b) => (a.lastModified > b.lastModified ? -1 : 1)) .sort((a, b) => (a.lastModified > b.lastModified ? -1 : 1))
.find((cluster) => !!cluster.lastFullUrl)?.lastFullUrl; .find((cluster) => !!cluster.lastFullUrl)?.lastFullUrl;
// Jeśli nie znaleziono visited_url, próba skonstruowania z origin if (!visited_url) {
const finalVisitedUrl = visited_url || origin; return <div>Wczytywanie...</div>;
if (!finalVisitedUrl) {
return <div>Błąd: nie można znaleźć adresu strony</div>;
} }
const result = ( const result = (
@ -83,7 +55,7 @@ function Report() {
{mode === 'survey' ? ( {mode === 'survey' ? (
<Questions <Questions
clusters={Object.values(clusters).filter( clusters={Object.values(clusters).filter(
(cluster) => cluster.hasMarks() (cluster) => cluster.getMarkedRequests().length > 0
)} )}
onComplete={(answers) => { onComplete={(answers) => {
setAnswers(parseAnswers(answers)); setAnswers(parseAnswers(answers));
@ -96,11 +68,11 @@ function Report() {
{mode === 'screenshots' ? ( {mode === 'screenshots' ? (
<ScreenshotGenerator <ScreenshotGenerator
{...{ {...{
visited_url: finalVisitedUrl, visited_url,
clusters, clusters,
setReportWindowMode: setMode, setReportWindowMode: setMode,
setRequestPath: setScrRequestPath, setRequestPath: setScrRequestPath,
downloadFiles, downloadFiles: downloadFiles,
user_role: answers.user_role, user_role: answers.user_role,
}} }}
/> />
@ -111,10 +83,10 @@ function Report() {
<EmailContent <EmailContent
{...{ {...{
answers, answers,
visited_url: finalVisitedUrl, visited_url,
clusters, clusters,
scrRequestPath, scrRequestPath,
downloadFiles, downloadFiles: downloadFiles,
user_role: answers.user_role, user_role: answers.user_role,
}} }}
/> />
@ -123,19 +95,28 @@ function Report() {
)} )}
</div> </div>
); );
return result; return (
<Fragment>
<header className="header">
<img src="../../assets/icon-addon.svg" height={32}></img>
<div className="webpage-metadata">
{origin ? (
<>
<span>Generowanie raportu </span>
<span className="webpage-metadata--hyperlink">{origin}</span>
</>
) : (
<span>Przejdź do wybranej strony internetowej</span>
)}
</div>
</header>
<section id="main-section">{result}</section>
</Fragment>
);
} catch (e) { } catch (e) {
console.error(e); console.error(e);
return ( return <div>ERROR! {JSON.stringify(e)}</div>;
<div style={{ padding: 30 }}>
<p>
<strong>Wystąpił błąd</strong>
</p>
<p>Najprawdopodobniej Rentgen napotkał stronę, której nie jest w stanie obsłużyć.</p>
<p>{(e as Error).toString()}</p>
</div>
);
} }
} }
ReactDOM.render(<Report />, document.getElementById('app')); ReactDOM.render(<Report />, document.getElementById('app'));

View File

@ -12,8 +12,6 @@ const Sidebar = () => {
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');
// Chrome: Oczekiwanie na gotowość pamięci
const [memoryReady, setMemoryReady] = React.useState(process.env.TARGET !== 'chrome');
const [minValueLength, setMinValueLength] = React.useState<number | null>( const [minValueLength, setMinValueLength] = React.useState<number | null>(
localStorage.getItem('minValueLength') === null localStorage.getItem('minValueLength') === null
? 7 ? 7
@ -46,24 +44,8 @@ const Sidebar = () => {
: false : false
); );
// Oczekiwanie na gotowość pamięci Chrome
React.useEffect(() => { React.useEffect(() => {
if (process.env.TARGET === 'chrome') { if (!origin) return;
const memory = getMemory();
if (typeof (memory as any).waitUntilReady === 'function') {
(memory as any).waitUntilReady().then(() => {
setMemoryReady(true);
console.log('✅ Memory gotowa dla sidebara');
});
} else {
setMemoryReady(true);
}
}
}, []);
React.useEffect(() => {
if (!origin || !memoryReady) return;
for (const cluster of Object.values(getMemory().getClustersForOrigin(origin))) { for (const cluster of Object.values(getMemory().getClustersForOrigin(origin))) {
if (cluster.hasMarks()) { if (cluster.hasMarks()) {
return setMarksOccurrence(true); return setMarksOccurrence(true);
@ -71,24 +53,9 @@ const Sidebar = () => {
} }
return setMarksOccurrence(false); return setMarksOccurrence(false);
}, [eventCounts['*'], memoryReady]); }, [eventCounts['*']]);
if (!origin) return <div>Błąd: Brak parametru "origin"</div>; if (!origin) return <div>Błąd: Brak parametru "origin"</div>;
// Wyświetlanie stanu wczytywania dla Chrome
if (!memoryReady) {
return (
<div className="sidebar">
<header className="header">
<img src="../../assets/icon-addon.svg" height={32}></img>
<div className="webpage-metadata">
<span>Ładowanie danych...</span>
</div>
</header>
</div>
);
}
return ( return (
<div className="sidebar"> <div className="sidebar">
<header className="header"> <header className="header">
@ -154,12 +121,12 @@ const Sidebar = () => {
</button> </button>
{localStorage.getItem('blottingBrowser') === {localStorage.getItem('blottingBrowser') ===
'nikttakniesplamitwojejprzeglądarki jakspidersweb' ? ( 'nikttakniesplamitwojejprzeglądarkijakspidersweb' ? (
<button <button
onClick={() => { onClick={() => {
if ( if (
window.confirm( 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)' '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( let deep_copy = JSON.parse(
@ -211,8 +178,8 @@ const Sidebar = () => {
<section className="dialog-container dialog-container--warning"> <section className="dialog-container dialog-container--warning">
<span> <span>
<strong>Uwaga!</strong> Niekoniecznie każda przesłana poniżej <strong>Uwaga!</strong> Niekoniecznie każda przesłana poniżej
informacja jest daną osobową. Niektóre z podanych domen mogą informacja jest daną osobową. Niektóre z podanych domen mogą
należeć do właściciela strony i nie reprezentować podmiotów należeć do właściciela strony i nie reprezentować podmiotów
trzecich. trzecich.
</span> </span>
<button <button
@ -267,4 +234,4 @@ const Sidebar = () => {
); );
}; };
ReactDOM.render(<Sidebar />, document.getElementById('app')); ReactDOM.render(<Sidebar />, document.getElementById('app'));

View File

@ -102,7 +102,6 @@ function StolenDataRow({ entry }: { entry: StolenDataEntry }) {
export default function StolenDataCluster({ export default function StolenDataCluster({
origin, origin,
shorthost, shorthost,
refreshToken,
minValueLength, minValueLength,
cookiesOnly, cookiesOnly,
cookiesOrOriginOnly, cookiesOrOriginOnly,
@ -110,7 +109,6 @@ export default function StolenDataCluster({
}: { }: {
origin: string; origin: string;
shorthost: string; shorthost: string;
refreshToken: number;
minValueLength: number; minValueLength: number;
cookiesOnly: boolean; cookiesOnly: boolean;
cookiesOrOriginOnly: boolean; cookiesOrOriginOnly: boolean;

View File

@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import browserAPI, { Tab } from '../../lib/browser-api'; import { Tab } from '../../util';
export default function TabDropdown({ export default function TabDropdown({
setPickedTab, setPickedTab,
@ -10,7 +10,7 @@ export default function TabDropdown({
}) { }) {
const [tabs, setTabs] = React.useState<Tab[]>([]); const [tabs, setTabs] = React.useState<Tab[]>([]);
React.useEffect(() => { React.useEffect(() => {
browserAPI.tabs.query({ currentWindow: true }).then(setTabs); browser.tabs.query({ currentWindow: true }).then(setTabs);
}, []); }, []);
return ( return (
<select <select
@ -27,4 +27,4 @@ export default function TabDropdown({
))} ))}
</select> </select>
); );
} }

View File

@ -7,10 +7,6 @@
rel="stylesheet" rel="stylesheet"
href="/lib/styles/global.css" href="/lib/styles/global.css"
> >
<link
rel="stylesheet"
href="/lib/styles/fonts.css"
>
<link <link
rel="stylesheet" rel="stylesheet"
href="/lib/components/toolbar/toolbar.css" href="/lib/components/toolbar/toolbar.css"

View File

@ -1,46 +1,20 @@
import React, { Fragment } from 'react'; import React, { Fragment } from 'react';
import ReactDOM from 'react-dom'; import ReactDOM from 'react-dom';
import { useEmitter } from '../../util';
import { getMemory } from '../../memory'; import { getMemory } from '../../memory';
import { useEmitter, getshorthost } from '../../util';
import browserAPI from '../../lib/browser-api';
// Niezawodne pobieranie zakładki z ponawianiem prób async function getCurrentTab() {
async function getCurrentTab(retries = 3, delay = 100): Promise<any> { const [tab] = await browser.tabs.query({
for (let i = 0; i < retries; i++) { active: true,
try { windowId: browser.windows.WINDOW_ID_CURRENT,
// Metoda 1: Zapytanie o aktywną zakładkę });
const tabs = await browserAPI.tabs.query({ return tab;
active: true,
currentWindow: true,
});
if (tabs && tabs[0] && tabs[0].url) {
return tabs[0];
}
// Metoda 2: Użycie lastFocusedWindow
const tabsLastFocused = await browserAPI.tabs.query({
active: true,
lastFocusedWindow: true,
});
if (tabsLastFocused && tabsLastFocused[0] && tabsLastFocused[0].url) {
return tabsLastFocused[0];
}
} catch (error) {
console.warn(`Próba ${i + 1} zapytania o zakładkę nie powiodła się:`, error);
}
// Czekanie przed ponowieniem próby
if (i < retries - 1) {
await new Promise(resolve => setTimeout(resolve, delay));
}
}
return undefined;
} }
function isDomainHighlySuspicious(domain: string) { import './../../styles/global.scss';
import './toolbar.scss';
function isDomainHighlySuspicious(domain: string): boolean {
return ( return (
domain.includes('facebook') || domain.includes('facebook') ||
domain.includes('twitter') || domain.includes('twitter') ||
@ -51,99 +25,51 @@ function isDomainHighlySuspicious(domain: string) {
const Toolbar = () => { const Toolbar = () => {
const [origin, setOrigin] = React.useState<string | null>(null); const [origin, setOrigin] = React.useState<string | null>(null);
const [memoryReady, setMemoryReady] = React.useState(process.env.TARGET !== 'chrome');
const [eventCounts] = useEmitter(getMemory()); const [eventCounts] = useEmitter(getMemory());
const [cookieDomainCopy, setCookieDomainCopy] = React.useState<string | null>(null); const [cookieDomainCopy, setCookieDomainCopy] = React.useState<string | null>(null);
const [_, setMarksOccurrence] = React.useState(false); const [_, setMarksOccurrence] = React.useState<boolean>(false);
const [exposedOriginDomainCopy, setExposedOriginDomainCopy] = React.useState<string | null>( const [exposedOriginDomainCopy, setExposedOriginDomainCopy] = React.useState<string | null>(
null null
); );
const first_sentence_cookie = const first_sentence_cookie = 'Strona dokonała zapisu i odczytu plików Cookie dla domen ';
'Strona dokonała zapisu i odczytu plików Cookie dla domen ';
const first_sentence_history = const first_sentence_history =
'Część informacji o Twojej historii przeglądania została wysłana do '; 'Część informacji o Twojej historii przeglądania została wysłana do ';
// Oczekiwanie na gotowość pamięci Chrome
React.useEffect(() => { React.useEffect(() => {
if (process.env.TARGET === 'chrome') {
const memory = getMemory();
if (typeof (memory as any).waitUntilReady === 'function') {
(memory as any).waitUntilReady().then(() => {
setMemoryReady(true);
console.log('✅ Memory gotowa, popup może wyświetlać dane');
});
} else {
setMemoryReady(true);
}
}
}, []);
React.useEffect(() => {
let isMounted = true;
const listener = async () => { const listener = async () => {
if (!isMounted) return;
const tab = await getCurrentTab(); const tab = await getCurrentTab();
if (!isMounted) return; if (tab !== undefined && tab.url) {
const url = new URL(tab.url);
if (tab && tab.url) { if (url.origin.startsWith('moz-extension')) {
try { return;
const url = new URL(tab.url);
// Pomijanie stron rozszerzenia
if (url.origin.startsWith('moz-extension') ||
url.origin.startsWith('chrome-extension') ||
url.protocol === 'chrome:' ||
url.protocol === 'about:') {
return;
}
setOrigin(url.origin);
} catch (error) {
console.warn('Nie udało się sparsować URL zakładki:', tab.url, error);
} }
setOrigin(url.origin);
} else { } else {
// Tylko ostrzeżenie w trybie debug, nie błąd console.warn('Out of the tab scope');
if (process.env.NODE_ENV === 'development') {
console.debug('Popup otwarty bez kontekstu aktywnej zakładki');
}
} }
}; };
browserAPI.tabs.onUpdated.addListener(listener); browser.tabs.onUpdated.addListener(listener);
listener();
// Początkowe wczytywanie z odpowiednim opóźnieniem
if (process.env.TARGET === 'chrome') {
// Chrome potrzebuje więcej czasu dla service worker + storage
setTimeout(listener, 200);
} else {
// Firefox jest gotowy natychmiast
listener();
}
return () => { return () => {
isMounted = false; browser.tabs.onUpdated.removeListener(listener);
browserAPI.tabs.onUpdated.removeListener(listener);
}; };
}, []); });
React.useEffect(() => { React.useEffect(() => {
if (!origin || !memoryReady) return; if (!origin) return;
const exposedOriginDomains = Object.values(getMemory().getClustersForOrigin(origin)) const exposedOriginDomains = Object.values(getMemory().getClustersForOrigin(origin))
.filter((cluster) => cluster.exposesOrigin()) .filter((cluster) => cluster.exposesOrigin())
.sort((cluster1, cluster2) => .sort((cluster1, cluster2) =>
isDomainHighlySuspicious(cluster1.id) isDomainHighlySuspicious(cluster1.id)
? -1 ? -1
: isDomainHighlySuspicious(cluster2.id) : isDomainHighlySuspicious(cluster2.id)
? 1 ? 1
: 0 : 0
) )
.map((cluster) => cluster.id); .map((cluster) => cluster.id);
setExposedOriginDomainCopy(''); setExposedOriginDomainCopy('');
switch (exposedOriginDomains.length) { switch (exposedOriginDomains.length) {
@ -170,22 +96,20 @@ const Toolbar = () => {
); );
break; break;
} }
}, [eventCounts['*'], origin, memoryReady]); }, [eventCounts['*'], origin]);
React.useEffect(() => { React.useEffect(() => {
if (!origin || !memoryReady) return; if (!origin) return;
const cookieDomains = Object.values(getMemory().getClustersForOrigin(origin)) const cookieDomains = Object.values(getMemory().getClustersForOrigin(origin))
.filter((cluster) => cluster.hasCookies()) .filter((cluster) => cluster.hasCookies())
.sort((cluster1, cluster2) => .sort((cluster1, cluster2) =>
isDomainHighlySuspicious(cluster1.id) isDomainHighlySuspicious(cluster1.id)
? -1 ? -1
: isDomainHighlySuspicious(cluster2.id) : isDomainHighlySuspicious(cluster2.id)
? 1 ? 1
: 0 : 0
) )
.map((cluster) => cluster.id); .map((cluster) => cluster.id);
setCookieDomainCopy(''); setCookieDomainCopy('');
switch (cookieDomains.length) { switch (cookieDomains.length) {
@ -204,45 +128,52 @@ const Toolbar = () => {
break; break;
default: default:
setCookieDomainCopy( setCookieDomainCopy(
`${cookieDomains[0]}, ${cookieDomains[1]} (i ${ `${cookieDomains[0]}, ${cookieDomains[1]} (i ${
cookieDomains.length - 2 < 2 ? 2 : cookieDomains.length - 2 cookieDomains.length - 2 < 2 ? 2 : cookieDomains.length - 2
} innych).` } innych).`
); );
break; break;
} }
}, [eventCounts['*'], origin, memoryReady]); }, [eventCounts['*'], origin]);
const autoMark = () => { React.useEffect(() => {
Object.values(getMemory().getClustersForOrigin(origin || '')).forEach((cluster) => if (!origin) return;
cluster.autoMark() for (const cluster of Object.values(getMemory().getClustersForOrigin(origin))) {
); if (cluster.hasMarks()) {
setMarksOccurrence(true); return setMarksOccurrence(true);
}; }
}
return setMarksOccurrence(false);
}, [eventCounts['*']]);
function autoMark() {
if (!origin) return;
for (const cluster of Object.values(getMemory().getClustersForOrigin(origin))) {
cluster.autoMark();
}
return setMarksOccurrence(true);
}
return ( return (
<div className="toolbar"> <div className="toolbar">
<header className={origin ? 'header' : 'header header--no-page'}> <header className={origin ? 'header' : 'header header--no-page'}>
<img src="../../assets/icon-addon.svg" height="24" /> <img src="../../assets/icon-addon.svg" height={32}></img>
<div className="webpage-metadata"> <div className="webpage-metadata">
{origin ? ( {origin ? (
<div className="webpage-metadata--hyperlink">{origin}</div> <>
<span>Analiza strony</span>
<span className="webpage-metadata--hyperlink">{origin}</span>
</>
) : ( ) : (
<div>Rentgen - wtyczka do przeglądania</div> <span>Przejdź do wybranej strony internetowej</span>
)} )}
</div> </div>
{origin ? ( {origin ? (
<button
onClick={() => {
window.close();
}}
>
<img src="../../assets/icons/x_thick.svg" width="12" height="12" />
</button>
) : (
<a href="https://internet-czas-dzialac.pl"> <a href="https://internet-czas-dzialac.pl">
<img src="/assets/icons/info_circle_outline.svg" width="20" height="20" /> <img src="/assets/icons/info_circle_outline.svg" width="20" height="20" />
</a> </a>
)} ) : null}
</header> </header>
{origin ? ( {origin ? (
@ -252,22 +183,30 @@ const Toolbar = () => {
<div className="counters-wrapper"> <div className="counters-wrapper">
<div className="counters"> <div className="counters">
<div className="counter counter--cookies"> <div className="counter counter--cookies">
<img src="/assets/icons/cookie.svg#color" width="24" height="24" /> <img
src="/assets/icons/cookie.svg#color"
width="24"
height="24"
/>
<span data-event={`${eventCounts['*']}`}> <span data-event={`${eventCounts['*']}`}>
{ {
Object.values(getMemory().getClustersForOrigin(origin)).filter( Object.values(
(cluster) => cluster.hasCookies() getMemory().getClustersForOrigin(origin)
).length ).filter((cluster) => cluster.hasCookies()).length
} }
</span> </span>
</div> </div>
<div className="counter counter--browser-history"> <div className="counter counter--browser-history">
<img src="/assets/icons/warning.svg#color" width="24" height="24" /> <img
src="/assets/icons/warning.svg#color"
width="24"
height="24"
/>
<span data-event={`${eventCounts['*']}`}> <span data-event={`${eventCounts['*']}`}>
{ {
Object.values(getMemory().getClustersForOrigin(origin)).filter( Object.values(
(cluster) => cluster.exposesOrigin() getMemory().getClustersForOrigin(origin)
).length ).filter((cluster) => cluster.exposesOrigin()).length
} }
</span> </span>
</div> </div>
@ -308,9 +247,9 @@ const Toolbar = () => {
<Fragment> <Fragment>
<section className="about"> <section className="about">
<p> <p>
Takie przetwarzanie danych może być niezgodne z&nbsp;prawem. Takie przetwarzanie danych może być niezgodne z prawem. Przejdź
Przejdź do analizy aby pomóc ustalić, czy ta strona nie narusza do analizy aby pomóc ustalić, czy ta strona nie narusza RODO lub
RODO lub ustawy Prawo Komunikacji Elektronicznej. ustawy Prawo Komunikacji Elektronicznej.
</p> </p>
</section> </section>
<section className="actions"> <section className="actions">
@ -322,7 +261,7 @@ const Toolbar = () => {
`/components/sidebar/sidebar.html?origin=${origin}`, `/components/sidebar/sidebar.html?origin=${origin}`,
'new_tab' 'new_tab'
); );
window.close(); window.close(); // close toolbar popup
}} }}
> >
Przejdź do analizy Przejdź do analizy
@ -342,4 +281,4 @@ const Toolbar = () => {
); );
}; };
ReactDOM.render(<Toolbar />, document.getElementById('toolbar')); ReactDOM.render(<Toolbar />, document.getElementById('toolbar'));

View File

@ -1,179 +1,68 @@
import esbuild from 'esbuild'; import esbuild from 'esbuild';
import scss from 'esbuild-plugin-sass'; import scss from 'esbuild-plugin-sass';
import { copyFileSync, mkdirSync, readdirSync, existsSync } from 'fs';
import { join, dirname } from 'path'; const watch = process.argv.includes('--watch') && {
onRebuild(error) {
// Określenie platformy docelowej: firefox (domyślnie) lub chrome if (error) console.error('[watch] build failed', error);
const TARGET = process.env.TARGET || 'firefox'; else console.log('[watch] build finished');
const IS_FIREFOX = TARGET === 'firefox'; },
const IS_CHROME = TARGET === 'chrome'; };
// Katalogi wyjściowe // see https://github.com/evanw/esbuild/issues/806#issuecomment-779138268
const DIST_DIR = IS_FIREFOX ? './dist-firefox' : './dist-chrome'; let skipReactImports = {
const LIB_DIR = join(DIST_DIR, 'lib'); name: 'skipReactImports',
setup(build) {
console.log(`🎯 Budowanie dla: ${TARGET.toUpperCase()}`); build.onResolve({ filter: /^(react(-dom)?|survey-react)$/ }, (args) => {
console.log(`📁 Katalog wyjściowy: ${DIST_DIR}`); return {
path: args.path,
const watch = process.argv.includes('--watch') && { namespace: `globalExternal_${args.path}`,
onRebuild(error) { };
if (error) console.error('[watch] budowanie nie powiodło się', error); });
else console.log('[watch] budowanie zakończone');
}, build.onLoad({ filter: /.*/, namespace: 'globalExternal_react' }, () => {
}; return {
contents: `module.exports = globalThis.React`,
// Funkcja pomocnicza: rekurencyjne kopiowanie katalogów loader: 'js',
function copyDir(src, dest) { };
if (!existsSync(dest)) { });
mkdirSync(dest, { recursive: true });
} build.onLoad({ filter: /.*/, namespace: 'globalExternal_react-dom' }, () => {
return {
const entries = readdirSync(src, { withFileTypes: true }); contents: `module.exports = globalThis.ReactDOM`,
loader: 'js',
for (const entry of entries) { };
const srcPath = join(src, entry.name); });
const destPath = join(dest, entry.name); build.onLoad({ filter: /.*/, namespace: 'globalExternal_survey-react' }, () => {
return {
if (entry.isDirectory()) { contents: `module.exports = globalThis.Survey`,
copyDir(srcPath, destPath); loader: 'js',
} else { };
copyFileSync(srcPath, destPath); });
} },
} };
}
esbuild
// Plugin: kopiowanie plików statycznych po zakończeniu budowania .build({
const copyStaticFiles = { entryPoints: [
name: 'copy-static-files', 'components/toolbar/toolbar.tsx',
setup(build) { 'components/sidebar/sidebar.tsx',
build.onEnd(() => { 'components/report-window/report-window.tsx',
console.log('📋 Kopiowanie plików statycznych...'); 'background.ts',
'diag.tsx',
// Kopiowanie manifestu (wybór na podstawie platformy docelowej) 'styles/global.scss',
const manifestSrc = IS_FIREFOX ? './manifest.json' : './manifest-chrome.json'; 'styles/fonts.scss',
const manifestDest = join(DIST_DIR, 'manifest.json'); ],
mkdirSync(dirname(manifestDest), { recursive: true }); bundle: true,
copyFileSync(manifestSrc, manifestDest); // minify: true,
console.log(` ✓ Skopiowano ${manifestSrc}${manifestDest}`); outdir: './lib',
loader: { '.woff': 'file', '.woff2': 'file' },
// Kopiowanie katalogu components plugins: [scss(), skipReactImports],
if (existsSync('./components')) { define: {
copyDir('./components', join(DIST_DIR, 'components')); PLUGIN_NAME: '"Rentgen"',
console.log(' ✓ Skopiowano components/'); PLUGIN_URL: '"https://addons.mozilla.org/pl/firefox/addon/rentgen/"',
} },
external: ['react', 'react-dom', 'survey-react'],
// Kopiowanie katalogu assets watch,
if (existsSync('./assets')) { })
copyDir('./assets', join(DIST_DIR, 'assets')); .then(() => console.log('Add-on was built'))
console.log(' ✓ Skopiowano assets/'); .catch(() => process.exit(1));
}
// Kopiowanie wymaganych bibliotek z node_modules (potrzebne dla plików HTML z UMD React)
const nodeModulesDest = join(DIST_DIR, 'node_modules');
// React
const reactUmdSrc = './node_modules/react/umd';
const reactUmdDest = join(nodeModulesDest, 'react/umd');
if (existsSync(reactUmdSrc)) {
copyDir(reactUmdSrc, reactUmdDest);
console.log(' ✓ Skopiowano node_modules/react/umd/');
}
// React-DOM
const reactDomUmdSrc = './node_modules/react-dom/umd';
const reactDomUmdDest = join(nodeModulesDest, 'react-dom/umd');
if (existsSync(reactDomUmdSrc)) {
copyDir(reactDomUmdSrc, reactDomUmdDest);
console.log(' ✓ Skopiowano node_modules/react-dom/umd/');
}
// Survey-React
const surveyReactSrc = './node_modules/survey-react';
const surveyReactDest = join(nodeModulesDest, 'survey-react');
if (existsSync(surveyReactSrc)) {
// Kopiowanie tylko niezbędnych plików
mkdirSync(surveyReactDest, { recursive: true });
const surveyFiles = ['survey.react.min.js', 'survey.react.min.css'];
surveyFiles.forEach(file => {
const src = join(surveyReactSrc, file);
if (existsSync(src)) {
copyFileSync(src, join(surveyReactDest, file));
}
});
console.log(' ✓ Skopiowano node_modules/survey-react/');
}
console.log(`✅ Budowanie dla ${TARGET.toUpperCase()} zakończone!`);
});
},
};
// Zobacz: https://github.com/evanw/esbuild/issues/806#issuecomment-779138268
// Plugin pomijający importy React (używamy globalnych obiektów z UMD)
let skipReactImports = {
name: 'skipReactImports',
setup(build) {
build.onResolve({ filter: /^(react(-dom)?|survey-react)$/ }, (args) => {
return {
path: args.path,
namespace: `globalExternal_${args.path}`,
};
});
build.onLoad({ filter: /.*/, namespace: 'globalExternal_react' }, () => {
return {
contents: `module.exports = globalThis.React`,
loader: 'js',
};
});
build.onLoad({ filter: /.*/, namespace: 'globalExternal_react-dom' }, () => {
return {
contents: `module.exports = globalThis.ReactDOM`,
loader: 'js',
};
});
build.onLoad({ filter: /.*/, namespace: 'globalExternal_survey-react' }, () => {
return {
contents: `module.exports = globalThis.Survey`,
loader: 'js',
};
});
},
};
esbuild
.build({
entryPoints: [
// JavaScript/TypeScript
'components/toolbar/toolbar.tsx',
'components/sidebar/sidebar.tsx',
'components/report-window/report-window.tsx',
'background.ts',
'diag.tsx',
// Globalne style
'styles/global.scss',
'styles/fonts.scss',
// Style komponentów (kompilowane osobno)
'components/toolbar/toolbar.scss',
// 'components/sidebar/sidebar.scss',
// 'components/report-window/report-window.scss',
],
bundle: true,
// minify: true,
outdir: LIB_DIR,
loader: { '.woff': 'file', '.woff2': 'file' },
plugins: [scss(), skipReactImports, copyStaticFiles],
define: {
PLUGIN_NAME: '"Rentgen"',
PLUGIN_URL: '"https://addons.mozilla.org/pl/firefox/addon/rentgen/"',
'process.env.TARGET': JSON.stringify(TARGET),
},
external: ['react', 'react-dom', 'survey-react'],
watch,
})
.then(() => console.log(`\n🎉 Dodatek dla ${TARGET.toUpperCase()} zbudowany pomyślnie!\n`))
.catch(() => process.exit(1));

View File

@ -8,27 +8,6 @@ import {
safeDecodeURIComponent, safeDecodeURIComponent,
} from './util'; } from './util';
/**
* Bezpieczna konwersja Uint8Array na string, obsługująca duże tablice
* które mogłyby spowodować stack overflow przy użyciu String.fromCharCode.apply()
*/
function uint8ArrayToString(bytes: Uint8Array): string {
const CHUNK_SIZE = 8192; // Przetwarzanie w kawałkach 8KB aby uniknąć stack overflow
if (bytes.length <= CHUNK_SIZE) {
// Mała tablica - używamy szybkiej metody
return String.fromCharCode.apply(null, Array.from(bytes) as any);
}
// Duża tablica - przetwarzamy w kawałkach
let result = '';
for (let i = 0; i < bytes.length; i += CHUNK_SIZE) {
const chunk = bytes.subarray(i, i + CHUNK_SIZE);
result += String.fromCharCode.apply(null, Array.from(chunk) as any);
}
return result;
}
type NameValue = { name: string; value: string }; type NameValue = { name: string; value: string };
export type HAREntry = { export type HAREntry = {
@ -83,7 +62,7 @@ const whitelisted_cookies = [
/^Connection$/, /^Connection$/,
/^Sec-Fetch-.*$/, /^Sec-Fetch-.*$/,
/^Content-Type$/, /^Content-Type$/,
/^Cookie$/, // wyodrębniamy to w getCookie() osobno /^Cookie$/, // we're extracting it in getCookie separately anyway
/^User-Agent$/, /^User-Agent$/,
]; ];
@ -101,8 +80,8 @@ export default class ExtendedRequest {
public origin: string; public origin: string;
public initialized = false; public initialized = false;
public stolenData: StolenDataEntry[] = []; public stolenData: StolenDataEntry[] = [];
public originalURL: string | null = null; // czasami możemy ustalić tylko origin, a nie pełny URL z paska adresu - np. w przypadku service workerów public originalURL: string | null = null; // sometimes we can only establish that the given request applied to a certain origin, not a full URL from the address bar - in case of service workers, for example. Hence the null
public originalPathname: string | null = null; // tak samo jak powyżej public originalPathname: string | null = null; // same as above
public originalHost: string; public originalHost: string;
public requestBody: RequestBody; public requestBody: RequestBody;
@ -112,45 +91,20 @@ export default class ExtendedRequest {
constructor(data: Request) { constructor(data: Request) {
this.tabId = data.tabId; this.tabId = data.tabId;
this.url = data.url; this.url = data.url;
this.shorthost = getshorthost(data.url);
this.requestBody = ((data as any).requestBody as undefined | RequestBody) || {}; this.requestBody = ((data as any).requestBody as undefined | RequestBody) || {};
ExtendedRequest.by_id[data.requestId] = this;
this.data = Object.assign({}, data); this.data = Object.assign({}, data);
(this.data as any).frameAncestors = [ (this.data as any).frameAncestors = [
...((data as any)?.frameAncestors?.map((e: any) => ({ url: e.url })) || []), ...((data as any)?.frameAncestors?.map((e: any) => ({ url: e.url })) || []),
]; ]; // making a copy?
ExtendedRequest.by_id[data.requestId] = this; // console.log('→→→',(this.data as any).frameAncestors, (data as any).frameAncestors);
// ========================================
// Parsowanie URL - obsługa różnych przeglądarek
// ========================================
// Funkcja pomocnicza: sprawdzenie czy URL jest poprawnym HTTP(S)
const isValidHttpUrl = (urlString: string): boolean => {
if (!urlString || urlString.length < 8) return false;
const lower = urlString.toLowerCase();
return lower.startsWith('http://') || lower.startsWith('https://');
};
// Funkcja pomocnicza: bezpieczne parsowanie URL
const safeParseUrl = (urlString: string) => {
try {
if (!isValidHttpUrl(urlString)) return null;
const parsed = new URL(urlString);
return {
origin: parsed.origin,
host: parsed.host,
pathname: parsed.pathname
};
} catch (error) {
return null;
}
};
// Określenie URL kandydata z kontekstu żądania
let url: string; let url: string;
let is_full_url = true; let is_full_url = true;
let url_comes_from: string; let url_comes_from: string;
if (this.data.type === 'main_frame') { if (this.data.type === 'main_frame') {
url = this.data.url; url = this.data.url;
url_comes_from = 'main_frame'; url_comes_from = 'main_frame';
@ -158,6 +112,7 @@ export default class ExtendedRequest {
url = this.data.documentUrl; url = this.data.documentUrl;
url_comes_from = 'documentUrl'; url_comes_from = 'documentUrl';
if (this.data.tabId == -1) { if (this.data.tabId == -1) {
//a service worker?
url_comes_from = 'documentUrl (webworker)'; url_comes_from = 'documentUrl (webworker)';
is_full_url = false; is_full_url = false;
} }
@ -167,58 +122,16 @@ export default class ExtendedRequest {
) { ) {
url = (this.data as any).frameAncestors.at(-1).url || ''; url = (this.data as any).frameAncestors.at(-1).url || '';
url_comes_from = 'frameAncestors'; url_comes_from = 'frameAncestors';
} else if (process.env.TARGET === 'chrome' && (this.data as any).initiator) {
// Chrome MV3: Używamy właściwości initiator
url = (this.data as any).initiator;
url_comes_from = 'initiator (Chrome MV3)';
is_full_url = false;
} else { } else {
url = this.data.documentUrl || this.data.originUrl; url = this.data.documentUrl || this.data.originUrl;
url_comes_from = 'ostatnia deska ratunku'; url_comes_from = 'last resort';
} }
// Próba parsowania URLi w kolejności preferencji this.originalURL = is_full_url ? url : null;
const urlsToTry = [ this.origin = new URL(url).origin;
url,
this.data.documentUrl,
this.data.originUrl,
this.data.url
].filter(Boolean);
let parsedUrl: { origin: string; host: string; pathname: string } | null = null; this.originalHost = new URL(url).host;
this.originalPathname = is_full_url ? new URL(url).pathname : null;
for (const urlToTry of urlsToTry) {
parsedUrl = safeParseUrl(urlToTry as string);
if (parsedUrl) {
url = urlToTry as string;
break;
}
}
// Ustawienie właściwości z bezpiecznymi wartościami domyślnymi
if (parsedUrl) {
// Pomyślnie sparsowano
this.originalURL = is_full_url ? url : null;
this.origin = parsedUrl.origin;
this.originalHost = parsedUrl.host;
this.originalPathname = is_full_url ? parsedUrl.pathname : null;
// Bezpieczne ustawienie shorthost
try {
this.shorthost = getshorthost(parsedUrl.host);
} catch (error) {
console.warn('Nie udało się uzyskać shorthost:', parsedUrl.host, error);
this.shorthost = parsedUrl.host;
}
} else {
// Nie udało się sparsować - używamy bezpiecznych wartości domyślnych
// Te żądania zostaną odfiltrowane przez isThirdParty() później
this.originalURL = null;
this.origin = 'unknown://unknown';
this.originalHost = 'unknown';
this.originalPathname = '/';
this.shorthost = 'unknown';
}
} }
addHeaders(headers: Request['requestHeaders']) { addHeaders(headers: Request['requestHeaders']) {
@ -232,30 +145,17 @@ export default class ExtendedRequest {
} }
isThirdParty() { isThirdParty() {
// Pomijanie żądań z nieznanym origin (nieparsowalny URL) const request_url = new URL(this.data.url);
if (this.origin === 'unknown://unknown' || this.originalHost === 'unknown') { if (request_url.host.includes(this.originalHost)) {
return false; // Nie śledzimy tych
}
try {
const request_url = new URL(this.data.url);
if (request_url.host.includes(this.originalHost)) {
return false;
}
if (getshorthost(request_url.host) == getshorthost(this.originalHost)) {
return false;
}
return (
request_url.origin != this.origin ||
(this.data as any).urlClassification.thirdParty.length > 0
);
} catch (error) {
// Jeśli nie możemy sparsować URL żądania, nie jest śledzalne
return false; return false;
} }
if (getshorthost(request_url.host) == getshorthost(this.originalHost)) {
return false;
}
return (
request_url.origin != this.origin ||
(this.data as any).urlClassification.thirdParty.length > 0
);
} }
getReferer() { getReferer() {
@ -318,19 +218,14 @@ export default class ExtendedRequest {
]) ])
), ),
}).map(([key, value]) => { }).map(([key, value]) => {
// Obsługa szyfrowanego POST body (jak na ocdn.eu na businessinsider.com.pl) // to handle how ocdn.eu encrypts POST body on https://businessinsider.com.pl/
if ((Array.isArray(value) && value.length === 1 && !value[0]) || !value) { if ((Array.isArray(value) && value.length === 1 && !value[0]) || !value) {
return ['requestBody', key]; return ['requestBody', key];
} else if (!Array.isArray(value)) { } else if (!Array.isArray(value)) {
// POPRAWKA: Używamy bezpiecznej konwersji w kawałkach zamiast apply() return [
try { 'raw',
const uint8Array = new Uint8Array(value.bytes); String.fromCharCode.apply(null, Array.from(new Uint8Array(value.bytes))),
const stringValue = uint8ArrayToString(uint8Array); ];
return ['raw', stringValue];
} catch (e) {
console.warn('Nie udało się przetworzyć bajtów body żądania:', e);
return ['raw', '[Dane binarne - nie udało się przetworzyć]'];
}
} else { } else {
return [key, value || '']; return [key, value || ''];
} }

View File

@ -14,63 +14,40 @@ export const chromeAPI: BrowserAPI = {
tabs: { tabs: {
query: chrome.tabs.query, query: chrome.tabs.query,
onUpdated: { onUpdated: {
addListener: chrome.tabs.onUpdated.addListener.bind(chrome.tabs.onUpdated), addListener: chrome.tabs.onUpdated.addListener,
removeListener: chrome.tabs.onUpdated.removeListener.bind(chrome.tabs.onUpdated), removeListener: chrome.tabs.onUpdated.removeListener,
}, },
onRemoved: chrome.tabs.onRemoved ? {
addListener: chrome.tabs.onRemoved.addListener.bind(chrome.tabs.onRemoved),
removeListener: chrome.tabs.onRemoved.removeListener.bind(chrome.tabs.onRemoved),
} : undefined,
}, },
// Badge API - Chrome używa action (nie browserAction jak Firefox) // Badge API - Chrome używa action (nie browserAction)
// Owinięte w try-catch aby obsłużyć zamknięte zakładki
badge: { badge: {
setBadgeText: (details: any) => { setBadgeText: chrome.action.setBadgeText,
try { setTitle: chrome.action.setTitle,
chrome.action.setBadgeText(details); setBadgeBackgroundColor: chrome.action.setBadgeBackgroundColor,
} catch (e) {
// Zakładka zamknięta - ignorujemy
}
},
setTitle: (details: any) => {
try {
chrome.action.setTitle(details);
} catch (e) {
// Zakładka zamknięta - ignorujemy
}
},
setBadgeBackgroundColor: (details: any) => {
try {
chrome.action.setBadgeBackgroundColor(details);
} catch (e) {
// Zakładka zamknięta - ignorujemy
}
},
}, },
// WebRequest API - chrome.webRequest.* → webRequest.* // WebRequest API - chrome.webRequest.* → webRequest.*
webRequest: { webRequest: {
onBeforeRequest: { onBeforeRequest: {
addListener: chrome.webRequest.onBeforeRequest.addListener.bind(chrome.webRequest.onBeforeRequest), addListener: chrome.webRequest.onBeforeRequest.addListener,
}, },
onBeforeSendHeaders: { onBeforeSendHeaders: {
addListener: chrome.webRequest.onBeforeSendHeaders.addListener.bind(chrome.webRequest.onBeforeSendHeaders), addListener: chrome.webRequest.onBeforeSendHeaders.addListener,
}, },
}, },
// Cookies API - chrome.cookies.* → cookies.* // Cookies API - chrome.cookies.* → cookies.*
cookies: { cookies: {
getAll: chrome.cookies.getAll.bind(chrome.cookies), getAll: chrome.cookies.getAll,
remove: chrome.cookies.remove.bind(chrome.cookies), remove: chrome.cookies.remove,
}, },
// Extension API - chrome.extension.* → extension.* // Extension API - chrome.extension.* → extension.*
extension: { extension: {
getBackgroundPage: chrome.extension?.getBackgroundPage?.bind(chrome.extension) || (() => null), getBackgroundPage: chrome.extension.getBackgroundPage,
}, },
// Windows API - chrome.windows.* → windows.* // Windows API - chrome.windows.* → windows.*
windows: { windows: {
WINDOW_ID_CURRENT: chrome.windows.WINDOW_ID_CURRENT, WINDOW_ID_CURRENT: chrome.windows.WINDOW_ID_CURRENT,
}, },

View File

@ -6,102 +6,49 @@
import type { BrowserAPI } from './types'; import type { BrowserAPI } from './types';
// Bezpieczny dostęp do globalnego obiektu browser - sprawdzamy czy istnieje w runtime // Firefox używa globalnego obiektu `browser`
function getBrowser() { declare const browser: any;
// @ts-ignore - dostęp do potencjalnie niezdefiniowanego globalnego obiektu
if (typeof globalThis.browser !== 'undefined') {
// @ts-ignore
return globalThis.browser;
}
return null;
}
export const firefoxAPI: BrowserAPI = { export const firefoxAPI: BrowserAPI = {
// Tabs API - leniwy dostęp z odpowiednimi typami zwracanymi // Tabs API - direct mapping
tabs: { tabs: {
query: (queryInfo: any) => { query: browser.tabs.query,
const b = getBrowser();
if (b) {
return b.tabs.query(queryInfo);
}
return Promise.resolve([]);
},
onUpdated: { onUpdated: {
addListener: (listener: any) => { addListener: browser.tabs.onUpdated.addListener,
getBrowser()?.tabs.onUpdated.addListener(listener); removeListener: browser.tabs.onUpdated.removeListener,
},
removeListener: (listener: any) => {
getBrowser()?.tabs.onUpdated.removeListener(listener);
},
},
onRemoved: {
addListener: (listener: any) => {
getBrowser()?.tabs.onRemoved?.addListener(listener);
},
removeListener: (listener: any) => {
getBrowser()?.tabs.onRemoved?.removeListener(listener);
},
}, },
}, },
// Badge API - Firefox używa browserAction (nie action jak Chrome) // Badge API - Firefox używa browserAction
badge: { badge: {
setBadgeText: (details: any) => { setBadgeText: browser.browserAction.setBadgeText,
getBrowser()?.browserAction.setBadgeText(details); setTitle: browser.browserAction.setTitle,
}, setBadgeBackgroundColor: browser.browserAction.setBadgeBackgroundColor,
setTitle: (details: any) => {
getBrowser()?.browserAction.setTitle(details);
},
setBadgeBackgroundColor: (details: any) => {
getBrowser()?.browserAction.setBadgeBackgroundColor(details);
},
}, },
// WebRequest API - leniwy dostęp // WebRequest API - direct mapping
webRequest: { webRequest: {
onBeforeRequest: { onBeforeRequest: {
addListener: (listener: any, filter: any, extraInfoSpec?: any) => { addListener: browser.webRequest.onBeforeRequest.addListener,
getBrowser()?.webRequest.onBeforeRequest.addListener(listener, filter, extraInfoSpec);
},
}, },
onBeforeSendHeaders: { onBeforeSendHeaders: {
addListener: (listener: any, filter: any, extraInfoSpec?: any) => { addListener: browser.webRequest.onBeforeSendHeaders.addListener,
getBrowser()?.webRequest.onBeforeSendHeaders.addListener(listener, filter, extraInfoSpec);
},
}, },
}, },
// Cookies API - leniwy dostęp z odpowiednimi typami zwracanymi // Cookies API - direct mapping
cookies: { cookies: {
getAll: (details: any) => { getAll: browser.cookies.getAll,
const b = getBrowser(); remove: browser.cookies.remove,
if (b) {
return b.cookies.getAll(details);
}
return Promise.resolve([]);
},
remove: (details: any) => {
const b = getBrowser();
if (b) {
return b.cookies.remove(details);
}
return Promise.resolve(null);
},
}, },
// Extension API - leniwy dostęp // Extension API - direct mapping
extension: { extension: {
getBackgroundPage: () => { getBackgroundPage: browser.extension.getBackgroundPage,
const b = getBrowser();
return b ? b.extension.getBackgroundPage() : null;
},
}, },
// Windows API - leniwy dostęp // Windows API - direct mapping
windows: { windows: {
get WINDOW_ID_CURRENT() { WINDOW_ID_CURRENT: browser.windows.WINDOW_ID_CURRENT,
const b = getBrowser();
return b ? b.windows.WINDOW_ID_CURRENT : -2;
},
}, },
}; };

View File

@ -1,28 +1,27 @@
/** /**
* Browser API Abstraction - Główny eksport * Browser API Abstraction - Main Export
* *
* Eksportuje właściwą implementację na podstawie zmiennej TARGET z procesu budowania * Eksportuje właściwą implementację na podstawie TARGET build variable
* Używa statycznych importów dla kompatybilności z Chrome service worker
*/ */
import type { BrowserAPI } from './types'; import type { BrowserAPI } from './types';
import { chromeAPI } from './chrome';
import { firefoxAPI } from './firefox';
// Wybór implementacji API przeglądarki w czasie budowania // Build-time selection of browser API implementation
let browserApi: BrowserAPI; let browserApi: BrowserAPI;
// TARGET jest ustawiane przez esbuild.config.js na podstawie npm script // TARGET jest ustawiane przez esbuild.config.js na podstawie npm script
if (process.env.TARGET === 'chrome') { if (process.env.TARGET === 'chrome') {
// Build dla Chrome - używamy adaptera Chrome // Chrome build - używamy chrome adapter
const { chromeAPI } = require('./chrome');
browserApi = chromeAPI; browserApi = chromeAPI;
} else { } else {
// Build dla Firefox (domyślny) - używamy adaptera Firefox // Firefox build (default) - używamy firefox adapter
const { firefoxAPI } = require('./firefox');
browserApi = firefoxAPI; browserApi = firefoxAPI;
} }
// Eksport jako default export // Eksportuj jako default export
export default browserApi; export default browserApi;
// Re-eksport typów dla wygody // Re-export typów dla wygody
export * from './types'; export * from './types';

View File

@ -1,86 +1,76 @@
/** /**
* Browser API Abstraction - Typy na podstawie faktycznego użycia w kodzie * Browser API Abstraction - Typy na podstawie faktycznego użycia w kodzie
*
* Przeanalizowane pliki:
* - util.ts: tabs.query, Tab.id
* - tab-dropdown.tsx: tabs.query, Tab.id, Tab.title
* - toolbar.tsx: tabs.query, tabs.onUpdated, Tab.url, windows.WINDOW_ID_CURRENT
* - memory.ts: browserAction.*, webRequest.*, cookies.*, extension.*
*/ */
// Import pełnego typu Request z util.ts // === Tab API (util.ts, tab-dropdown.tsx, toolbar.tsx) ===
export type Request = {
cookieStoreId?: string;
documentUrl?: string;
frameId: number;
incognito?: boolean;
method: string;
originUrl: string;
parentFrameId: number;
proxyInfo?: {
host: string;
port: number;
type: string;
username: string;
proxyDNS: boolean;
failoverTimeout: number;
};
requestHeaders?: { name: string; value?: string; binaryValue?: number[] }[];
requestId: string;
tabId: number;
thirdParty?: boolean;
timeStamp: number;
type: string;
url: string;
urlClassification?: { firstParty: string[]; thirdParty: string[] };
};
// === Tab API ===
export interface Tab { export interface Tab {
id?: number; id?: number; // util.ts: tab.id, tab-dropdown.tsx: tab.id
title?: string; title?: string; // tab-dropdown.tsx: tab.title
url?: string; url?: string; // toolbar.tsx: tab.url
} }
export interface TabQuery { export interface TabQuery {
currentWindow?: boolean; currentWindow?: boolean; // util.ts, tab-dropdown.tsx
active?: boolean; active?: boolean; // toolbar.tsx
windowId?: number; windowId?: number; // toolbar.tsx
lastFocusedWindow?: boolean; // Chrome używa tego zamiast currentWindow czasami
} }
// === Badge/BrowserAction API === // === Badge/BrowserAction API (memory.ts) ===
export interface BadgeTextDetails { export interface BadgeTextDetails {
text: string; text: string; // memory.ts: setBadgeText
tabId?: number; tabId?: number; // memory.ts: setBadgeText (optional)
} }
export interface BadgeTitleDetails { export interface BadgeTitleDetails {
title: string; title: string; // memory.ts: setTitle
tabId?: number; tabId?: number; // memory.ts: setTitle (optional)
} }
export interface BadgeColorDetails { export interface BadgeColorDetails {
color: string; color: string; // memory.ts: setBadgeBackgroundColor
} }
// === WebRequest API === // === WebRequest API (memory.ts) ===
export interface RequestFilter { export interface RequestDetails {
urls: string[]; requestId: string; // memory.ts: request.requestId
requestHeaders?: RequestHeader[]; // memory.ts: request.requestHeaders
// Note: ExtendedRequest konstruktor używa więcej pól,
// ale tu skupiamy się na tym co bezpośrednio używa browser API
} }
export type RequestListener = (details: Request) => void; export interface RequestHeader {
// === Cookies API ===
export interface Cookie {
name: string; name: string;
domain: string; value?: string;
}
export interface RequestFilter {
urls: string[]; // memory.ts: { urls: ['<all_urls>'] }
}
export type RequestListener = (details: RequestDetails) => void;
// === Cookies API (memory.ts) ===
export interface Cookie {
name: string; // memory.ts: cookie.name
domain: string; // memory.ts: cookie.domain
} }
export interface CookieQuery { export interface CookieQuery {
domain?: string; domain?: string; // memory.ts: { domain: shorthost }
} }
export interface CookieRemove { export interface CookieRemove {
name: string; name: string; // memory.ts: { name: cookie.name, url: ... }
url: string; url: string; // memory.ts: { url: `https://${cookie.domain}` }
} }
// === Główny interfejs Browser API === // === Main Browser API Interface ===
export interface BrowserAPI { export interface BrowserAPI {
// Tabs API // Tabs API
tabs: { tabs: {
@ -89,10 +79,6 @@ export interface BrowserAPI {
addListener(listener: (tabId: number, changeInfo: any, tab: Tab) => void): void; addListener(listener: (tabId: number, changeInfo: any, tab: Tab) => void): void;
removeListener(listener: (tabId: number, changeInfo: any, tab: Tab) => void): void; removeListener(listener: (tabId: number, changeInfo: any, tab: Tab) => void): void;
}; };
onRemoved?: {
addListener(listener: (tabId: number, removeInfo: any) => void): void;
removeListener(listener: (tabId: number, removeInfo: any) => void): void;
};
}; };
// Badge API (Firefox: browserAction, Chrome: action) // Badge API (Firefox: browserAction, Chrome: action)

View File

@ -1,35 +0,0 @@
{
"description": "Rentgen is an add-on that automatically visualizes all the data that a given website sends to third parties.",
"manifest_version": 3,
"name": "Rentgen",
"short_name": "Rentgen",
"version": "0.1.10",
"author": "Kuba Orlik, Arkadiusz Wieczorek (Internet. Czas działać!)",
"homepage_url": "https://git.internet-czas-dzialac.pl/icd/rentgen",
"background": {
"service_worker": "lib/background.js"
},
"action": {
"default_icon": {
"16": "assets/icon-16.png",
"32": "assets/icon-32.png",
"48": "assets/icon-48.png"
},
"default_title": "Rentgen",
"default_popup": "components/toolbar/toolbar.html"
},
"icons": {
"16": "assets/icon-16.png",
"32": "assets/icon-32.png",
"48": "assets/icon-48.png",
"128": "assets/icon-128.png"
},
"permissions": [
"storage",
"webRequest",
"cookies"
],
"host_permissions": [
"<all_urls>"
]
}

View File

@ -3,8 +3,8 @@
"manifest_version": 2, "manifest_version": 2,
"name": "Rentgen", "name": "Rentgen",
"short_name": "Rentgen", "short_name": "Rentgen",
"version": "0.1.10", "version": "0.2.1",
"author": "Kuba Orlik, Arkadiusz Wieczorek (Internet. Czas działać!)", "author": "Kuba Orlik, Arkadiusz Wieczorek (Internet. Time to act! Foundation)",
"homepage_url": "https://git.internet-czas-dzialac.pl/icd/rentgen", "homepage_url": "https://git.internet-czas-dzialac.pl/icd/rentgen",
"background": { "background": {
"scripts": ["lib/background.js"] "scripts": ["lib/background.js"]

363
memory.ts
View File

@ -2,134 +2,19 @@ import ExtendedRequest from './extended-request';
import { getshorthost } from './util'; import { getshorthost } from './util';
import { RequestCluster } from './request-cluster'; import { RequestCluster } from './request-cluster';
import { SaferEmitter } from './safer-emitter'; import { SaferEmitter } from './safer-emitter';
import browserAPI from './lib/browser-api';
// Deklaracja Chrome API dla TypeScript
declare const chrome: any;
function setDomainsCount(counter: number, tabId: number) { function setDomainsCount(counter: number, tabId: number) {
// Ochrona przed próbą ustawienia badge dla zamkniętej zakładki browser.browserAction.setBadgeText({ text: counter < 0 ? '0' : counter.toString(), tabId });
try { browser.browserAction.setTitle({
browserAPI.badge.setBadgeText({ text: counter < 0 ? '0' : counter.toString(), tabId }); title: 'Rentgen',
browserAPI.badge.setTitle({ tabId,
title: 'Rentgen', });
tabId,
});
} catch (e) {
// Zakładka została zamknięta - ignorujemy błąd
console.debug(`Zakładka ${tabId} już nie istnieje, pomijanie aktualizacji badge`);
}
}
// Cachowany RequestCluster dla popupu Chrome (ma metody, ale używa danych z cache)
class CachedRequestCluster extends RequestCluster {
private _hasCookies: boolean = false;
private _exposesOrigin: boolean = false;
private _hasMarks: boolean = false;
public lastFullUrl: string | null = null;
public lastModified: number = 0;
constructor(id: string, cached: any) {
super(id);
this._hasCookies = cached.hasCookies || false;
this._exposesOrigin = cached.exposesOrigin || false;
this._hasMarks = cached.hasMarks || false;
this.lastFullUrl = cached.lastFullUrl || null;
this.lastModified = cached.lastModified || 0;
this.requests = []; // Pusta tablica zapobiegająca błędom
}
hasCookies(): boolean {
return this._hasCookies;
}
exposesOrigin(): boolean {
return this._exposesOrigin;
}
hasMarks(): boolean {
return this._hasMarks;
}
// Automatyczne zaznaczenie dla cachowanego clustra
autoMark(): void {
this._hasMarks = true;
// ✅ Trigger storage sync
if (process.env.TARGET === 'chrome') {
const memory = getMemory();
if (memory instanceof Memory) {
memory.scheduleSyncToStorage?.();
}
}
}
// Cofnięcie zaznaczenia dla cachowanego clustra
undoMark(): void {
this._hasMarks = false;
// ✅ Trigger storage sync
if (process.env.TARGET === 'chrome') {
const memory = getMemory();
if (memory instanceof Memory) {
memory.scheduleSyncToStorage?.();
}
}
}
// Nadpisanie metody - zwraca pustą tablicę
calculateRepresentativeStolenData(): any[] {
return [];
}
// Nadpisanie metody - zwraca tylko główną domenę
getFullHosts(): string[] {
return [this.id];
}
// Metody wymagane przez report-window
hasMarkedCookies(): boolean {
return this._hasCookies && this._hasMarks;
}
getMarkedRequests(): any[] {
return [];
}
getMarkedEntries(): any[] {
return [];
}
exposesOriginWhere(): any[] {
return this._exposesOrigin ? [{ path: '', source: 'cached', key: '' }] : [];
}
getDataTypeDescription(noun = 'Twojej'): string {
let types_of_data: string[] = [];
if (this.exposesOrigin()) {
types_of_data.push(`część ${noun} historii przeglądania`);
}
if (this.hasMarkedCookies()) {
types_of_data.push('unikalne ID z cookies');
}
if (types_of_data.length > 1) {
types_of_data[types_of_data.length - 1] = 'oraz ' + types_of_data[types_of_data.length - 1];
}
return types_of_data.join(', ');
}
} }
export default class Memory extends SaferEmitter { export default class Memory extends SaferEmitter {
origin_to_history = {} as Record<string, Record<string, RequestCluster>>; origin_to_history = {} as Record<string, Record<string, RequestCluster>>;
isReady: boolean = true; // Firefox jest zawsze gotowy
private readyPromise: Promise<void> | null = null;
private syncScheduled: boolean = false;
private lastSyncTime: number = 0;
// Chrome: Śledzenie pełnych URLi dla zakładek (do generowania screenshotów)
private tabUrls: Map<number, string> = new Map();
async register(request: ExtendedRequest) { async register(request: ExtendedRequest) {
await request.init(); await request.init();
if (!request.isThirdParty()) { if (!request.isThirdParty()) {
return; return;
} }
@ -137,75 +22,38 @@ export default class Memory extends SaferEmitter {
this.origin_to_history[request.origin] = {}; this.origin_to_history[request.origin] = {};
} }
const shorthost = getshorthost(new URL(request.url).host); const shorthost = getshorthost(new URL(request.url).host);
let isNewCluster = false;
if (!this.origin_to_history[request.origin][shorthost]) { if (!this.origin_to_history[request.origin][shorthost]) {
const cluster = new RequestCluster(shorthost); const cluster = new RequestCluster(shorthost);
this.origin_to_history[request.origin][shorthost] = cluster; this.origin_to_history[request.origin][shorthost] = cluster;
isNewCluster = true;
} }
this.origin_to_history[request.origin][shorthost].add(request); this.origin_to_history[request.origin][shorthost].add(request);
// Chrome: Automatyczne zaznaczanie podejrzanych domen w service workerze
if (process.env.TARGET === 'chrome' && isNewCluster) {
// Zaznacz od razu po utworzeniu clustra
this.origin_to_history[request.origin][shorthost].autoMark();
}
this.emit('change', shorthost); this.emit('change', shorthost);
// Owinięcie operacji badge w try-catch Object.values(this.getClustersForOrigin(request.origin)).some((cluster) =>
try { cluster.hasCookies()
Object.values(this.getClustersForOrigin(request.origin)).some((cluster) => )
cluster.hasCookies() ? browser.browserAction.setBadgeBackgroundColor({ color: '#ff726b' })
) : browser.browserAction.setBadgeBackgroundColor({ color: '#ffb900' });
? browserAPI.badge.setBadgeBackgroundColor({ color: '#ff726b' })
: browserAPI.badge.setBadgeBackgroundColor({ color: '#ffb900' });
if (request.tabId >= 0) { if (request.tabId >= 0) {
setDomainsCount( setDomainsCount(
Object.values(this.getClustersForOrigin(request.origin)).length, Object.values(this.getClustersForOrigin(request.origin)).length,
request.tabId request.tabId
); );
}
} catch (e) {
// Zakładka zamknięta - ignorujemy błędy badge
console.debug('Aktualizacja badge nie powiodła się - zakładka prawdopodobnie zamknięta');
}
// Chrome: Throttlowana synchronizacja do storage (max co 500ms)
if (process.env.TARGET === 'chrome') {
this.scheduleSyncToStorage();
} }
} }
constructor() { constructor() {
super(); super();
browserAPI.webRequest.onBeforeRequest.addListener( browser.webRequest.onBeforeRequest.addListener(
async (request) => { async (request) => {
// Chrome: Śledzenie nawigacji main_frame dla pełnego URL new ExtendedRequest(request);
if (process.env.TARGET === 'chrome' && request.type === 'main_frame' && request.tabId >= 0) {
this.tabUrls.set(request.tabId, request.url);
console.log(`📍 Zapamiętano URL zakładki ${request.tabId}:`, request.url);
}
const extReq = new ExtendedRequest(request);
// Chrome: Wstrzyknięcie pełnego URL ze śledzonych zakładek
if (process.env.TARGET === 'chrome' && request.tabId >= 0) {
const fullUrl = this.tabUrls.get(request.tabId);
if (fullUrl && !extReq.originalURL) {
extReq.originalURL = fullUrl;
}
}
}, },
{ urls: ['<all_urls>'] }, { urls: ['<all_urls>'] },
['requestBody'] ['requestBody']
); );
browser.webRequest.onBeforeSendHeaders.addListener(
browserAPI.webRequest.onBeforeSendHeaders.addListener(
async (request) => { async (request) => {
const extendedRequest = ExtendedRequest.by_id[request.requestId].addHeaders( const extendedRequest = ExtendedRequest.by_id[request.requestId].addHeaders(
request.requestHeaders || [] request.requestHeaders || []
@ -215,34 +63,10 @@ export default class Memory extends SaferEmitter {
{ urls: ['<all_urls>'] }, { urls: ['<all_urls>'] },
['requestHeaders'] ['requestHeaders']
); );
// Chrome: Czyszczenie URLi zakładek po zamknięciu
if (process.env.TARGET === 'chrome') {
browserAPI.tabs.onRemoved?.addListener?.((tabId: number) => {
this.tabUrls.delete(tabId);
});
}
// Chrome: Wczytywanie z storage przy starcie (dla popupu)
if (process.env.TARGET === 'chrome') {
this.isReady = false;
this.readyPromise = this.loadFromStorage().then(() => {
this.isReady = true;
this.emit('ready');
console.log('🔵 Memory gotowa do użycia przez popup');
});
}
} }
emit(eventName: string, data = 'any'): boolean { emit(eventName: string, data = 'any'): boolean {
setTimeout(() => { setTimeout(() => super.emit(eventName, data), 0);
super.emit(eventName, data);
// ✅ Sync to storage when marks change (Chrome only)
if (process.env.TARGET === 'chrome' && eventName === 'change') {
this.scheduleSyncToStorage();
}
}, 0);
return true; return true;
} }
@ -250,19 +74,11 @@ export default class Memory extends SaferEmitter {
return this.origin_to_history[origin] || {}; return this.origin_to_history[origin] || {};
} }
// Chrome: Oczekiwanie na zakończenie wczytywania ze storage
async waitUntilReady(): Promise<void> {
if (this.isReady) return;
if (this.readyPromise) {
await this.readyPromise;
}
}
async removeCookiesFor(origin: string, shorthost?: string): Promise<void> { async removeCookiesFor(origin: string, shorthost?: string): Promise<void> {
if (shorthost) { if (shorthost) {
const cookies = await browserAPI.cookies.getAll({ domain: shorthost }); const cookies = await browser.cookies.getAll({ domain: shorthost });
for (const cookie of cookies) { for (const cookie of cookies) {
await browserAPI.cookies.remove({ await browser.cookies.remove({
name: cookie.name, name: cookie.name,
url: `https://${cookie.domain}`, url: `https://${cookie.domain}`,
}); });
@ -276,150 +92,19 @@ export default class Memory extends SaferEmitter {
.map((cluster) => this.removeCookiesFor(origin, cluster.id)) .map((cluster) => this.removeCookiesFor(origin, cluster.id))
); );
} }
// Chrome: Throttlowana synchronizacja do storage
if (process.env.TARGET === 'chrome') {
this.scheduleSyncToStorage();
}
} }
async removeRequestsFor(origin: string) { async removeRequestsFor(origin: string) {
this.origin_to_history[origin] = {}; this.origin_to_history[origin] = {};
// Chrome: Throttlowana synchronizacja do storage
if (process.env.TARGET === 'chrome') {
this.scheduleSyncToStorage();
}
}
// ========================================
// CHROME: Metody synchronizacji ze storage (THROTTLED)
// ========================================
public scheduleSyncToStorage(): void {
if (this.syncScheduled) return;
this.syncScheduled = true;
setTimeout(() => {
this.syncToStorage();
this.syncScheduled = false;
}, 500); // Synchronizacja max co 500ms
}
private syncToStorage(): void {
if (typeof chrome !== 'undefined' && chrome.storage?.session) {
const now = Date.now();
// Pomijamy jeśli synchronizowano niedawno (w ciągu 300ms)
if (now - this.lastSyncTime < 300) {
return;
}
this.lastSyncTime = now;
const serializable: Record<string, any> = {};
for (const [origin, clusters] of Object.entries(this.origin_to_history)) {
serializable[origin] = {};
for (const [shorthost, cluster] of Object.entries(clusters)) {
// Zapisujemy tylko niezbędne dane dla UI
serializable[origin][shorthost] = {
id: cluster.id,
hasCookies: cluster.hasCookies(),
exposesOrigin: cluster.exposesOrigin(),
hasMarks: cluster.hasMarks(),
requestCount: cluster.requests?.length || 0,
lastFullUrl: cluster.lastFullUrl || null,
lastModified: cluster.lastModified || 0,
};
}
}
chrome.storage.session.set({ rentgen_memory: serializable }).catch((err: any) => {
console.error('Nie udało się zsynchronizować pamięci do storage:', err);
});
}
}
private async loadFromStorage(): Promise<void> {
if (typeof chrome !== 'undefined' && chrome.storage?.session) {
try {
const result = await chrome.storage.session.get('rentgen_memory');
if (result.rentgen_memory) {
const serialized = result.rentgen_memory;
for (const [origin, clusters] of Object.entries(serialized)) {
this.origin_to_history[origin] = {};
for (const [shorthost, cached] of Object.entries(clusters as Record<string, any>)) {
// Tworzenie CachedRequestCluster ze wszystkimi potrzebnymi danymi
const clusterData = cached as {
id: string;
hasCookies: boolean;
exposesOrigin: boolean;
hasMarks: boolean;
requestCount: number;
lastFullUrl: string | null;
lastModified: number;
};
this.origin_to_history[origin][shorthost] = new CachedRequestCluster(
clusterData.id,
clusterData
);
}
}
console.log('🔵 Wczytano pamięć z chrome.storage.session:', Object.keys(this.origin_to_history).length, 'origins');
}
} catch (err) {
console.error('Nie udało się wczytać pamięci ze storage:', err);
}
}
} }
} }
// ========================================
// INICJALIZACJA: Firefox vs Chrome
// ========================================
export function init() { export function init() {
const memory = new Memory(); const memory = new Memory();
if (process.env.TARGET === 'chrome') { (window as any).memory = memory;
// Chrome MV3: Service worker używa 'self' zamiast 'window'
(self as any).memory = memory;
console.log('🔵 Memory zainicjalizowana w service workerze Chrome (self.memory + chrome.storage.session)');
} else {
// Firefox: Standardowa strona tła z 'window'
(window as any).memory = memory;
console.log('🦊 Memory zainicjalizowana w stronie tła Firefox (window.memory)');
}
} }
// ========================================
// DOSTĘP DO MEMORY: Firefox vs Chrome
// ========================================
// Cachowana instancja memory dla popupu (tylko Chrome)
let popupMemoryInstance: Memory | null = null;
export function getMemory(): Memory { export function getMemory(): Memory {
if (process.env.TARGET === 'chrome') { return (browser.extension.getBackgroundPage().window as any).memory as Memory;
// Chrome: Najpierw próba pobrania z service workera }
if (typeof self !== 'undefined' && (self as any).memory) {
// Jesteśmy W service workerze - bezpośredni dostęp
return (self as any).memory as Memory;
} else {
// Jesteśmy w popupie/contencie - tworzymy RAZ i cachujemy
if (!popupMemoryInstance) {
console.log('🔵 Tworzenie instancji Chrome memory dla popupu (czytanie z chrome.storage.session)');
popupMemoryInstance = new Memory();
}
return popupMemoryInstance;
}
} else {
// Firefox: Używamy tradycyjnego getBackgroundPage()
const backgroundPage = browserAPI.extension.getBackgroundPage();
if (!backgroundPage) {
throw new Error('Strona tła nie jest dostępna');
}
return (backgroundPage.window as any).memory as Memory;
}
}

1695
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,23 +1,22 @@
{ {
"name": "rentgen", "name": "rentgen",
"version": "0.1.10", "version": "0.2.1",
"description": "Rentgen is an add-on prepared for both Firefox-based and Chromium-based browsers. This extension will automatically visualize all the data that a given website sends to third parties.", "description": "Rentgen is an add-on prepared for both Firefox-based and Chromium-based browsers. This extension will automatically visualize all the data that a given website sends to third parties.",
"main": "esbuild.config.js", "main": "esbuild.config.js",
"type": "module", "type": "module",
"scripts": { "scripts": {
"convert-icons": "node scripts/convert-icons.js",
"build": "node esbuild.config.js", "build": "node esbuild.config.js",
"build:firefox": "TARGET=firefox node esbuild.config.js", "build:firefox": "TARGET=firefox node esbuild.config.js",
"build:chrome": "npm run convert-icons && TARGET=chrome node esbuild.config.js", "build:chrome": "TARGET=chrome node esbuild.config.js",
"watch": "node esbuild.config.js --watch", "watch": "node esbuild.config.js --watch",
"watch:firefox": "TARGET=firefox node esbuild.config.js --watch", "watch:firefox": "TARGET=firefox node esbuild.config.js --watch",
"watch:chrome": "TARGET=chrome node esbuild.config.js --watch", "watch:chrome": "TARGET=chrome node esbuild.config.js --watch",
"ext-test": "web-ext run", "ext-test": "web-ext run",
"build-addon": "npm i && npm run build && npm run create-package", "build-addon": "npm i && npm run build && npm run create-package",
"build-addon:firefox": "npm i && npm run build && npm run create-package:firefox", "build-addon:firefox": "npm i && npm run build:firefox && npm run create-package:firefox",
"build-addon:chrome": "npm i && npm run build:chrome && npm run create-package:chrome", "build-addon:chrome": "npm i && npm run build:chrome && npm run create-package:chrome",
"create-package": "web-ext build --ignore-files '!**/node_modules' '!**/node_modules/**/react-dom' '!**/node_modules/**/react-dom/umd' '!**/node_modules/**/*/react-dom.production.min.js' '!**/node_modules/**/react' '!**/node_modules/**/react/umd' '!**/node_modules/**/*/react.production.min.js' '!**/node_modules/**/survey-react' '!**/node_modules/**/survey-react/*.min.js' '!**/node_modules/**/survey-react/*.min.css' --overwrite-dest", "create-package": "web-ext build --ignore-files '!**/node_modules' '!**/node_modules/**/react-dom' '!**/node_modules/**/react-dom/umd' '!**/node_modules/**/*/react-dom.production.min.js' '!**/node_modules/**/react' '!**/node_modules/**/react/umd' '!**/node_modules/**/*/react.production.min.js' '!**/node_modules/**/survey-react' '!**/node_modules/**/survey-react/*.min.js' '!**/node_modules/**/survey-react/*.min.css' --overwrite-dest",
"create-package:firefox": "cd dist-firefox && web-ext build --overwrite-dest --artifacts-dir ../web-ext-artifacts --ignore-files '!**/node_modules' '!**/node_modules/**/react-dom' '!**/node_modules/**/react-dom/umd' '!**/node_modules/**/*/react-dom.production.min.js' '!**/node_modules/**/react' '!**/node_modules/**/react/umd' '!**/node_modules/**/*/react.production.min.js' '!**/node_modules/**/survey-react' '!**/node_modules/**/survey-react/*.min.js' '!**/node_modules/**/survey-react/*.min.css'", "create-package:firefox": "web-ext build --overwrite-dest --artifacts-dir ../web-ext-artifacts",
"create-package:chrome": "cd dist-chrome && 7z a -tzip ../web-ext-artifacts/rentgen-chrome-0.1.10.zip * && cd ..", "create-package:chrome": "cd dist-chrome && 7z a -tzip ../web-ext-artifacts/rentgen-chrome-0.1.10.zip * && cd ..",
"typecheck": "tsc --noEmit", "typecheck": "tsc --noEmit",
"lint": "web-ext lint" "lint": "web-ext lint"
@ -58,7 +57,6 @@
"addons-linter": "^4.7.0", "addons-linter": "^4.7.0",
"esbuild": "^0.14.14", "esbuild": "^0.14.14",
"esbuild-plugin-sass": "^1.0.1", "esbuild-plugin-sass": "^1.0.1",
"sharp": "^0.34.4",
"typescript": "^4.6.4", "typescript": "^4.6.4",
"web-ext": "^6.7.0", "web-ext": "^6.7.0",
"web-ext-types": "^3.2.1" "web-ext-types": "^3.2.1"

View File

@ -1,32 +0,0 @@
import sharp from 'sharp';
import { mkdirSync } from 'fs';
const sizes = [16, 32, 48, 128];
const svgPath = 'assets/icon-addon.svg';
const outputDir = 'dist-chrome/assets';
async function convertIcons() {
try {
// Upewnienie się, że katalog wyjściowy istnieje
mkdirSync(outputDir, { recursive: true });
console.log('🎨 Konwersja ikon SVG do PNG dla Chrome...');
// Konwersja do każdego rozmiaru
for (const size of sizes) {
await sharp(svgPath)
.resize(size, size)
.png()
.toFile(`${outputDir}/icon-${size}.png`);
console.log(`✓ Utworzono icon-${size}.png`);
}
console.log('✅ Wszystkie ikony Chrome wygenerowane pomyślnie');
} catch (error) {
console.error('Błąd konwersji ikon:', error);
process.exit(1);
}
}
convertIcons();

52
util.ts
View File

@ -1,12 +1,11 @@
import { EventEmitter } from 'events'; import { EventEmitter } from 'events';
import React from 'react'; import React from 'react';
import { DataLocation, Sources } from './stolen-data-entry'; import { DataLocation, Sources } from './stolen-data-entry';
import browserAPI from './lib/browser-api';
export type Unpromisify<T> = T extends Promise<infer R> ? R : T; export type Unpromisify<T> = T extends Promise<infer R> ? R : T;
export type Unarray<T> = T extends Array<infer R> ? R : T; export type Unarray<T> = T extends Array<infer R> ? R : T;
export type Tab = Unarray<Unpromisify<ReturnType<typeof browserAPI.tabs.query>>>; export type Tab = Unarray<Unpromisify<ReturnType<typeof browser.tabs.query>>>;
export type Request = { export type Request = {
cookieStoreId?: string; cookieStoreId?: string;
documentUrl?: string; // RL of the document in which the resource will be loaded. For example, if the web page at "https://example.com" contains an image or an iframe, then the documentUrl for the image or iframe will be "https://example.com". For a top-level document, documentUrl is undefined. documentUrl?: string; // RL of the document in which the resource will be loaded. For example, if the web page at "https://example.com" contains an image or an iframe, then the documentUrl for the image or iframe will be "https://example.com". For a top-level document, documentUrl is undefined.
@ -34,50 +33,21 @@ export type Request = {
}; };
export function getshorthost(host: string) { export function getshorthost(host: string) {
// Obsługa przypadków brzegowych const parts = host
if (!host || typeof host !== 'string') { .replace(/^.*:\/\//, '')
console.warn('getshorthost: nieprawidłowy host:', host); .replace(/\/.*$/, '')
return 'unknown'; .split('.');
}
// Czyszczenie stringa hosta
const cleanHost = host
.replace(/^.*:\/\//, '') // Usunięcie protokołu
.replace(/\/.*$/, '') // Usunięcie ścieżki
.replace(/:\d+$/, ''); // Usunięcie portu
const parts = cleanHost.split('.');
// Obsługa przypadków specjalnych
if (parts.length === 0 || !cleanHost) {
console.warn('getshorthost: pusty host po czyszczeniu');
return 'unknown';
}
if (parts.length === 1) {
// Pojedyncze słowo jak "localhost" lub "unknown"
return parts[0];
}
const second_last = parts.at(-2); const second_last = parts.at(-2);
// Bezpieczny fallback jeśli wciąż nieprawidłowy
if (!second_last) { if (!second_last) {
console.warn('getshorthost: nie można określić domeny dla:', host); throw new Error('url too short?');
return cleanHost; // Zwracamy jak jest zamiast crashować
} }
let lookback = !['co', 'com'].includes(second_last) ? -2 : -3; let lookback = !['co', 'com'].includes(second_last) ? -2 : -3;
if (parts.at(-2) == 'doubleclick' || parts.at(-2) == 'google') { if (parts.at(-2) == 'doubleclick' || parts.at(-2) == 'google') {
lookback = -4; // aby rozróżnić google ads i stats lookback = -4; // to distinguish between google ads and stats
} else if (parts.at(-2) == 'google') { } else if (parts.at(-2) == 'google') {
lookback = -3; // aby rozróżnić różne usługi google lookback = -3; // to distinguish various google services
} }
return parts.slice(lookback).join('.');
// Upewnienie się, że nie wycinamy poza granice tablicy
const sliceStart = Math.max(0, parts.length + lookback);
return parts.slice(sliceStart).join('.');
} }
export function useEmitter( export function useEmitter(
@ -119,7 +89,7 @@ export function parseCookie(cookie: string): Record<string, string> {
} }
export async function getTabByID(id: number) { export async function getTabByID(id: number) {
const tabs = await browserAPI.tabs.query({ currentWindow: true }); const tabs = await browser.tabs.query({ currentWindow: true });
return tabs.find((tab) => tab.id == id); return tabs.find((tab) => tab.id == id);
} }
@ -347,4 +317,4 @@ export function downloadText(filename: string, text: string) {
element.click(); element.click();
document.body.removeChild(element); document.body.removeChild(element);
} }