diff --git a/background.ts b/background.ts
index 4e2e800..7529424 100644
--- a/background.ts
+++ b/background.ts
@@ -1,3 +1,18 @@
-import { init } from "./memory";
+import { init } from './memory';
-init();
+console.log('🔴 [DIAGNOSTYKA] Wczytywanie background.ts, TARGET =', process.env.TARGET);
+
+// 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');
+}
\ No newline at end of file
diff --git a/components/report-window/report-window.tsx b/components/report-window/report-window.tsx
index 5dc58e8..d870c76 100644
--- a/components/report-window/report-window.tsx
+++ b/components/report-window/report-window.tsx
@@ -24,6 +24,24 @@ function Report() {
if (!origin) {
return
Błąd: brak parametru "origin"
;
}
+
+ // 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 rawAnswers = url.searchParams.get('answers');
const [answers, setAnswers] = React.useState(
@@ -32,22 +50,32 @@ function Report() {
const [mode, setMode] = React.useState(url.searchParams.get('mode') || 'survey');
const [scrRequestPath, setScrRequestPath] = React.useState('');
- const clusters = getMemory().getClustersForOrigin(origin || '');
+ // Pobieranie klastrów tylko gdy pamięć jest gotowa
+ const clusters = memoryReady ? getMemory().getClustersForOrigin(origin || '') : {};
React.useEffect(() => {
- if (!origin) return;
+ if (!origin || !memoryReady) return;
const url = new URL(document.location.toString());
url.searchParams.set('origin', origin);
url.searchParams.set('answers', JSON.stringify(answers));
url.searchParams.set('mode', mode);
history.pushState({}, 'Rentgen', url.toString());
- }, [mode, answers, origin]);
+ }, [mode, answers, origin, memoryReady]);
+
+ // Wyświetlanie wczytywania w trakcie oczekiwania na pamięć
+ if (!memoryReady) {
+ return Wczytywanie danych z rozszerzenia...
;
+ }
+
const visited_url = Object.values(clusters)
.sort((a, b) => (a.lastModified > b.lastModified ? -1 : 1))
.find((cluster) => !!cluster.lastFullUrl)?.lastFullUrl;
- if (!visited_url) {
- return Wczytywanie...
;
+ // Jeśli nie znaleziono visited_url, próba skonstruowania z origin
+ const finalVisitedUrl = visited_url || origin;
+
+ if (!finalVisitedUrl) {
+ return Błąd: nie można znaleźć adresu strony
;
}
const result = (
@@ -55,7 +83,7 @@ function Report() {
{mode === 'survey' ? (
cluster.getMarkedRequests().length > 0
+ (cluster) => cluster.hasMarks()
)}
onComplete={(answers) => {
setAnswers(parseAnswers(answers));
@@ -68,11 +96,11 @@ function Report() {
{mode === 'screenshots' ? (
@@ -83,10 +111,10 @@ function Report() {
@@ -95,28 +123,19 @@ function Report() {
)}
);
- return (
-
-
-
-
- );
+ return result;
} catch (e) {
console.error(e);
- return ERROR! {JSON.stringify(e)}
;
+ return (
+
+
+ Wystąpił błąd
+
+
Najprawdopodobniej Rentgen napotkał stronę, której nie jest w stanie obsłużyć.
+
{(e as Error).toString()}
+
+ );
}
}
-ReactDOM.render( , document.getElementById('app'));
+ReactDOM.render( , document.getElementById('app'));
\ No newline at end of file
diff --git a/components/sidebar/sidebar.tsx b/components/sidebar/sidebar.tsx
index 6fdd0de..d4709ff 100644
--- a/components/sidebar/sidebar.tsx
+++ b/components/sidebar/sidebar.tsx
@@ -12,6 +12,8 @@ const Sidebar = () => {
const url = new URL(document.location.toString());
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(
localStorage.getItem('minValueLength') === null
? 7
@@ -44,8 +46,24 @@ const Sidebar = () => {
: false
);
+ // Oczekiwanie na gotowość pamięci Chrome
React.useEffect(() => {
- if (!origin) return;
+ 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 sidebara');
+ });
+ } else {
+ setMemoryReady(true);
+ }
+ }
+ }, []);
+
+ React.useEffect(() => {
+ if (!origin || !memoryReady) return;
+
for (const cluster of Object.values(getMemory().getClustersForOrigin(origin))) {
if (cluster.hasMarks()) {
return setMarksOccurrence(true);
@@ -53,9 +71,24 @@ const Sidebar = () => {
}
return setMarksOccurrence(false);
- }, [eventCounts['*']]);
+ }, [eventCounts['*'], memoryReady]);
if (!origin) return Błąd: Brak parametru "origin"
;
+
+ // Wyświetlanie stanu wczytywania dla Chrome
+ if (!memoryReady) {
+ return (
+
+
+
+
+ Ładowanie danych...
+
+
+
+ );
+ }
+
return (
@@ -121,12 +154,12 @@ const Sidebar = () => {
{localStorage.getItem('blottingBrowser') ===
- 'nikttakniesplamitwojejprzeglądarkijakspidersweb' ? (
+ 'nikttakniesplamitwojejprzeglądarki jakspidersweb' ? (
{
if (
window.confirm(
- 'Czy chcesz wczytać wszystkie domeny w celu „splamienia” twojej przeglądarki? Uwaga przeglądarka może zablokować otwieranie nowych kart. (Ten krok jest opcjonalny)'
+ '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(
@@ -178,8 +211,8 @@ const Sidebar = () => {
Uwaga! Niekoniecznie każda przesłana poniżej
- informacja jest daną osobową. Niektóre z podanych domen mogą
- należeć do właściciela strony i nie reprezentować podmiotów
+ informacja jest daną osobową. Niektóre z podanych domen mogą
+ należeć do właściciela strony i nie reprezentować podmiotów
trzecich.
{
);
};
-ReactDOM.render( , document.getElementById('app'));
+ReactDOM.render( , document.getElementById('app'));
\ No newline at end of file
diff --git a/components/sidebar/stolen-data-cluster.tsx b/components/sidebar/stolen-data-cluster.tsx
index 2fa64bb..a6e67a3 100644
--- a/components/sidebar/stolen-data-cluster.tsx
+++ b/components/sidebar/stolen-data-cluster.tsx
@@ -102,6 +102,7 @@ function StolenDataRow({ entry }: { entry: StolenDataEntry }) {
export default function StolenDataCluster({
origin,
shorthost,
+ refreshToken,
minValueLength,
cookiesOnly,
cookiesOrOriginOnly,
@@ -109,6 +110,7 @@ export default function StolenDataCluster({
}: {
origin: string;
shorthost: string;
+ refreshToken: number;
minValueLength: number;
cookiesOnly: boolean;
cookiesOrOriginOnly: boolean;
diff --git a/components/toolbar/toolbar.html b/components/toolbar/toolbar.html
index 53e478a..42f7e94 100644
--- a/components/toolbar/toolbar.html
+++ b/components/toolbar/toolbar.html
@@ -7,6 +7,10 @@
rel="stylesheet"
href="/lib/styles/global.css"
>
+
{
+ for (let i = 0; i < retries; i++) {
+ try {
+ // Metoda 1: Zapytanie o aktywną zakładkę
+ const tabs = await browserAPI.tabs.query({
+ 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) {
@@ -23,6 +51,7 @@ function isDomainHighlySuspicious(domain: string) {
const Toolbar = () => {
const [origin, setOrigin] = React.useState(null);
+ const [memoryReady, setMemoryReady] = React.useState(process.env.TARGET !== 'chrome');
const [eventCounts] = useEmitter(getMemory());
const [cookieDomainCopy, setCookieDomainCopy] = React.useState(null);
const [_, setMarksOccurrence] = React.useState(false);
@@ -35,28 +64,74 @@ const Toolbar = () => {
const first_sentence_history =
'Część informacji o Twojej historii przeglądania została wysłana do ';
+ // Oczekiwanie na gotowość pamięci Chrome
React.useEffect(() => {
- const listener = async () => {
- const tab = await getCurrentTab();
- if (tab !== undefined && tab.url) {
- const url = new URL(tab.url);
- if (url.origin.startsWith('moz-extension')) {
- return;
- }
- setOrigin(url.origin);
+ 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 {
- console.warn('Out of the tab scope');
+ setMemoryReady(true);
}
- };
- browserAPI.tabs.onUpdated.addListener(listener);
- listener();
- return () => {
- browserAPI.tabs.onUpdated.removeListener(listener);
- };
- });
+ }
+ }, []);
React.useEffect(() => {
- if (!origin) return;
+ let isMounted = true;
+
+ const listener = async () => {
+ if (!isMounted) return;
+
+ const tab = await getCurrentTab();
+
+ if (!isMounted) return;
+
+ if (tab && tab.url) {
+ try {
+ 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);
+ }
+ } else {
+ // Tylko ostrzeżenie w trybie debug, nie błąd
+ if (process.env.NODE_ENV === 'development') {
+ console.debug('Popup otwarty bez kontekstu aktywnej zakładki');
+ }
+ }
+ };
+
+ browserAPI.tabs.onUpdated.addListener(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 () => {
+ isMounted = false;
+ browserAPI.tabs.onUpdated.removeListener(listener);
+ };
+ }, []);
+
+ React.useEffect(() => {
+ if (!origin || !memoryReady) return;
const exposedOriginDomains = Object.values(getMemory().getClustersForOrigin(origin))
.filter((cluster) => cluster.exposesOrigin())
@@ -95,10 +170,10 @@ const Toolbar = () => {
);
break;
}
- }, [eventCounts['*'], origin]);
+ }, [eventCounts['*'], origin, memoryReady]);
React.useEffect(() => {
- if (!origin) return;
+ if (!origin || !memoryReady) return;
const cookieDomains = Object.values(getMemory().getClustersForOrigin(origin))
.filter((cluster) => cluster.hasCookies())
@@ -135,7 +210,7 @@ const Toolbar = () => {
);
break;
}
- }, [eventCounts['*'], origin]);
+ }, [eventCounts['*'], origin, memoryReady]);
const autoMark = () => {
Object.values(getMemory().getClustersForOrigin(origin || '')).forEach((cluster) =>
diff --git a/esbuild.config.js b/esbuild.config.js
index 873010c..6967c4d 100644
--- a/esbuild.config.js
+++ b/esbuild.config.js
@@ -1,68 +1,179 @@
-import esbuild from 'esbuild';
-import scss from 'esbuild-plugin-sass';
-
-const watch = process.argv.includes('--watch') && {
- onRebuild(error) {
- if (error) console.error('[watch] build failed', error);
- else console.log('[watch] build finished');
- },
-};
-
-// see https://github.com/evanw/esbuild/issues/806#issuecomment-779138268
-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: [
- 'components/toolbar/toolbar.tsx',
- 'components/sidebar/sidebar.tsx',
- 'components/report-window/report-window.tsx',
- 'background.ts',
- 'diag.tsx',
- 'styles/global.scss',
- 'styles/fonts.scss',
- ],
- bundle: true,
- // minify: true,
- outdir: './lib',
- loader: { '.woff': 'file', '.woff2': 'file' },
- plugins: [scss(), skipReactImports],
- define: {
- PLUGIN_NAME: '"Rentgen"',
- PLUGIN_URL: '"https://addons.mozilla.org/pl/firefox/addon/rentgen/"',
- },
- external: ['react', 'react-dom', 'survey-react'],
- watch,
- })
- .then(() => console.log('Add-on was built'))
- .catch(() => process.exit(1));
+import esbuild from 'esbuild';
+import scss from 'esbuild-plugin-sass';
+import { copyFileSync, mkdirSync, readdirSync, existsSync } from 'fs';
+import { join, dirname } from 'path';
+
+// Określenie platformy docelowej: firefox (domyślnie) lub chrome
+const TARGET = process.env.TARGET || 'firefox';
+const IS_FIREFOX = TARGET === 'firefox';
+const IS_CHROME = TARGET === 'chrome';
+
+// Katalogi wyjściowe
+const DIST_DIR = IS_FIREFOX ? './dist-firefox' : './dist-chrome';
+const LIB_DIR = join(DIST_DIR, 'lib');
+
+console.log(`🎯 Budowanie dla: ${TARGET.toUpperCase()}`);
+console.log(`📁 Katalog wyjściowy: ${DIST_DIR}`);
+
+const watch = process.argv.includes('--watch') && {
+ onRebuild(error) {
+ if (error) console.error('[watch] budowanie nie powiodło się', error);
+ else console.log('[watch] budowanie zakończone');
+ },
+};
+
+// Funkcja pomocnicza: rekurencyjne kopiowanie katalogów
+function copyDir(src, dest) {
+ if (!existsSync(dest)) {
+ mkdirSync(dest, { recursive: true });
+ }
+
+ const entries = readdirSync(src, { withFileTypes: true });
+
+ for (const entry of entries) {
+ const srcPath = join(src, entry.name);
+ const destPath = join(dest, entry.name);
+
+ if (entry.isDirectory()) {
+ copyDir(srcPath, destPath);
+ } else {
+ copyFileSync(srcPath, destPath);
+ }
+ }
+}
+
+// Plugin: kopiowanie plików statycznych po zakończeniu budowania
+const copyStaticFiles = {
+ name: 'copy-static-files',
+ setup(build) {
+ build.onEnd(() => {
+ console.log('📋 Kopiowanie plików statycznych...');
+
+ // Kopiowanie manifestu (wybór na podstawie platformy docelowej)
+ const manifestSrc = IS_FIREFOX ? './manifest.json' : './manifest-chrome.json';
+ const manifestDest = join(DIST_DIR, 'manifest.json');
+ mkdirSync(dirname(manifestDest), { recursive: true });
+ copyFileSync(manifestSrc, manifestDest);
+ console.log(` ✓ Skopiowano ${manifestSrc} → ${manifestDest}`);
+
+ // Kopiowanie katalogu components
+ if (existsSync('./components')) {
+ copyDir('./components', join(DIST_DIR, 'components'));
+ console.log(' ✓ Skopiowano components/');
+ }
+
+ // Kopiowanie katalogu assets
+ if (existsSync('./assets')) {
+ copyDir('./assets', join(DIST_DIR, 'assets'));
+ console.log(' ✓ Skopiowano assets/');
+ }
+
+ // 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));
\ No newline at end of file
diff --git a/extended-request.ts b/extended-request.ts
index e8ea713..e06fd31 100644
--- a/extended-request.ts
+++ b/extended-request.ts
@@ -8,6 +8,27 @@ import {
safeDecodeURIComponent,
} 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 };
export type HAREntry = {
@@ -62,7 +83,7 @@ const whitelisted_cookies = [
/^Connection$/,
/^Sec-Fetch-.*$/,
/^Content-Type$/,
- /^Cookie$/, // we're extracting it in getCookie separately anyway
+ /^Cookie$/, // wyodrębniamy to w getCookie() osobno
/^User-Agent$/,
];
@@ -80,8 +101,8 @@ export default class ExtendedRequest {
public origin: string;
public initialized = false;
public stolenData: StolenDataEntry[] = [];
- public originalURL: string | null = null; // sometimes we can only establish that the given request applied to a certain origin, not a full URL from the address bar - in case of service workers, for example. Hence the null
- public originalPathname: string | null = null; // same as above
+ public 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 originalPathname: string | null = null; // tak samo jak powyżej
public originalHost: string;
public requestBody: RequestBody;
@@ -91,20 +112,45 @@ export default class ExtendedRequest {
constructor(data: Request) {
this.tabId = data.tabId;
this.url = data.url;
- this.shorthost = getshorthost(data.url);
this.requestBody = ((data as any).requestBody as undefined | RequestBody) || {};
- ExtendedRequest.by_id[data.requestId] = this;
-
this.data = Object.assign({}, data);
(this.data as any).frameAncestors = [
...((data as any)?.frameAncestors?.map((e: any) => ({ url: e.url })) || []),
- ]; // making a copy?
+ ];
- // console.log('→→→',(this.data as any).frameAncestors, (data as any).frameAncestors);
+ ExtendedRequest.by_id[data.requestId] = this;
+ // ========================================
+ // 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 is_full_url = true;
let url_comes_from: string;
+
if (this.data.type === 'main_frame') {
url = this.data.url;
url_comes_from = 'main_frame';
@@ -112,7 +158,6 @@ export default class ExtendedRequest {
url = this.data.documentUrl;
url_comes_from = 'documentUrl';
if (this.data.tabId == -1) {
- //a service worker?
url_comes_from = 'documentUrl (webworker)';
is_full_url = false;
}
@@ -122,16 +167,58 @@ export default class ExtendedRequest {
) {
url = (this.data as any).frameAncestors.at(-1).url || '';
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 {
url = this.data.documentUrl || this.data.originUrl;
- url_comes_from = 'last resort';
+ url_comes_from = 'ostatnia deska ratunku';
}
- this.originalURL = is_full_url ? url : null;
- this.origin = new URL(url).origin;
+ // Próba parsowania URLi w kolejności preferencji
+ const urlsToTry = [
+ url,
+ this.data.documentUrl,
+ this.data.originUrl,
+ this.data.url
+ ].filter(Boolean);
- this.originalHost = new URL(url).host;
- this.originalPathname = is_full_url ? new URL(url).pathname : null;
+ let parsedUrl: { origin: string; host: string; pathname: string } | null = 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']) {
@@ -145,17 +232,30 @@ export default class ExtendedRequest {
}
isThirdParty() {
- const request_url = new URL(this.data.url);
- if (request_url.host.includes(this.originalHost)) {
+ // Pomijanie żądań z nieznanym origin (nieparsowalny URL)
+ if (this.origin === 'unknown://unknown' || this.originalHost === 'unknown') {
+ 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;
}
- 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() {
@@ -218,14 +318,19 @@ export default class ExtendedRequest {
])
),
}).map(([key, value]) => {
- // to handle how ocdn.eu encrypts POST body on https://businessinsider.com.pl/
+ // Obsługa szyfrowanego POST body (jak na ocdn.eu na businessinsider.com.pl)
if ((Array.isArray(value) && value.length === 1 && !value[0]) || !value) {
return ['requestBody', key];
} else if (!Array.isArray(value)) {
- return [
- 'raw',
- String.fromCharCode.apply(null, Array.from(new Uint8Array(value.bytes))),
- ];
+ // POPRAWKA: Używamy bezpiecznej konwersji w kawałkach zamiast apply()
+ try {
+ const uint8Array = 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 {
return [key, value || ''];
}
diff --git a/lib/browser-api/chrome.ts b/lib/browser-api/chrome.ts
index 5431819..419a42a 100644
--- a/lib/browser-api/chrome.ts
+++ b/lib/browser-api/chrome.ts
@@ -14,40 +14,63 @@ export const chromeAPI: BrowserAPI = {
tabs: {
query: chrome.tabs.query,
onUpdated: {
- addListener: chrome.tabs.onUpdated.addListener,
- removeListener: chrome.tabs.onUpdated.removeListener,
+ addListener: chrome.tabs.onUpdated.addListener.bind(chrome.tabs.onUpdated),
+ removeListener: chrome.tabs.onUpdated.removeListener.bind(chrome.tabs.onUpdated),
},
+ 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)
+ // Badge API - Chrome używa action (nie browserAction jak Firefox)
+ // Owinięte w try-catch aby obsłużyć zamknięte zakładki
badge: {
- setBadgeText: chrome.action.setBadgeText,
- setTitle: chrome.action.setTitle,
- setBadgeBackgroundColor: chrome.action.setBadgeBackgroundColor,
+ setBadgeText: (details: any) => {
+ try {
+ chrome.action.setBadgeText(details);
+ } 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: {
onBeforeRequest: {
- addListener: chrome.webRequest.onBeforeRequest.addListener,
+ addListener: chrome.webRequest.onBeforeRequest.addListener.bind(chrome.webRequest.onBeforeRequest),
},
onBeforeSendHeaders: {
- addListener: chrome.webRequest.onBeforeSendHeaders.addListener,
+ addListener: chrome.webRequest.onBeforeSendHeaders.addListener.bind(chrome.webRequest.onBeforeSendHeaders),
},
},
// Cookies API - chrome.cookies.* → cookies.*
cookies: {
- getAll: chrome.cookies.getAll,
- remove: chrome.cookies.remove,
+ getAll: chrome.cookies.getAll.bind(chrome.cookies),
+ remove: chrome.cookies.remove.bind(chrome.cookies),
},
// Extension API - chrome.extension.* → extension.*
extension: {
- getBackgroundPage: chrome.extension.getBackgroundPage,
+ getBackgroundPage: chrome.extension?.getBackgroundPage?.bind(chrome.extension) || (() => null),
},
- // Windows API - chrome.windows.* → windows.*
+ // Windows API - chrome.windows.* → windows.*
windows: {
WINDOW_ID_CURRENT: chrome.windows.WINDOW_ID_CURRENT,
},
diff --git a/lib/browser-api/firefox.ts b/lib/browser-api/firefox.ts
index 911f0a7..cdbe765 100644
--- a/lib/browser-api/firefox.ts
+++ b/lib/browser-api/firefox.ts
@@ -6,49 +6,102 @@
import type { BrowserAPI } from './types';
-// Firefox używa globalnego obiektu `browser`
-declare const browser: any;
+// Bezpieczny dostęp do globalnego obiektu browser - sprawdzamy czy istnieje w runtime
+function getBrowser() {
+ // @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 = {
- // Tabs API - direct mapping
+ // Tabs API - leniwy dostęp z odpowiednimi typami zwracanymi
tabs: {
- query: browser.tabs.query,
+ query: (queryInfo: any) => {
+ const b = getBrowser();
+ if (b) {
+ return b.tabs.query(queryInfo);
+ }
+ return Promise.resolve([]);
+ },
onUpdated: {
- addListener: browser.tabs.onUpdated.addListener,
- removeListener: browser.tabs.onUpdated.removeListener,
+ addListener: (listener: any) => {
+ getBrowser()?.tabs.onUpdated.addListener(listener);
+ },
+ 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
+ // Badge API - Firefox używa browserAction (nie action jak Chrome)
badge: {
- setBadgeText: browser.browserAction.setBadgeText,
- setTitle: browser.browserAction.setTitle,
- setBadgeBackgroundColor: browser.browserAction.setBadgeBackgroundColor,
+ setBadgeText: (details: any) => {
+ getBrowser()?.browserAction.setBadgeText(details);
+ },
+ setTitle: (details: any) => {
+ getBrowser()?.browserAction.setTitle(details);
+ },
+ setBadgeBackgroundColor: (details: any) => {
+ getBrowser()?.browserAction.setBadgeBackgroundColor(details);
+ },
},
- // WebRequest API - direct mapping
+ // WebRequest API - leniwy dostęp
webRequest: {
onBeforeRequest: {
- addListener: browser.webRequest.onBeforeRequest.addListener,
+ addListener: (listener: any, filter: any, extraInfoSpec?: any) => {
+ getBrowser()?.webRequest.onBeforeRequest.addListener(listener, filter, extraInfoSpec);
+ },
},
onBeforeSendHeaders: {
- addListener: browser.webRequest.onBeforeSendHeaders.addListener,
+ addListener: (listener: any, filter: any, extraInfoSpec?: any) => {
+ getBrowser()?.webRequest.onBeforeSendHeaders.addListener(listener, filter, extraInfoSpec);
+ },
},
},
- // Cookies API - direct mapping
+ // Cookies API - leniwy dostęp z odpowiednimi typami zwracanymi
cookies: {
- getAll: browser.cookies.getAll,
- remove: browser.cookies.remove,
+ getAll: (details: any) => {
+ const b = getBrowser();
+ 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 - direct mapping
+ // Extension API - leniwy dostęp
extension: {
- getBackgroundPage: browser.extension.getBackgroundPage,
+ getBackgroundPage: () => {
+ const b = getBrowser();
+ return b ? b.extension.getBackgroundPage() : null;
+ },
},
- // Windows API - direct mapping
+ // Windows API - leniwy dostęp
windows: {
- WINDOW_ID_CURRENT: browser.windows.WINDOW_ID_CURRENT,
+ get WINDOW_ID_CURRENT() {
+ const b = getBrowser();
+ return b ? b.windows.WINDOW_ID_CURRENT : -2;
+ },
},
};
\ No newline at end of file
diff --git a/lib/browser-api/index.ts b/lib/browser-api/index.ts
index 119d6c1..b8cfc40 100644
--- a/lib/browser-api/index.ts
+++ b/lib/browser-api/index.ts
@@ -1,27 +1,28 @@
/**
- * Browser API Abstraction - Main Export
+ * Browser API Abstraction - Główny eksport
*
- * Eksportuje właściwą implementację na podstawie TARGET build variable
+ * Eksportuje właściwą implementację na podstawie zmiennej TARGET z procesu budowania
+ * Używa statycznych importów dla kompatybilności z Chrome service worker
*/
import type { BrowserAPI } from './types';
+import { chromeAPI } from './chrome';
+import { firefoxAPI } from './firefox';
-// Build-time selection of browser API implementation
+// Wybór implementacji API przeglądarki w czasie budowania
let browserApi: BrowserAPI;
// TARGET jest ustawiane przez esbuild.config.js na podstawie npm script
if (process.env.TARGET === 'chrome') {
- // Chrome build - używamy chrome adapter
- const { chromeAPI } = require('./chrome');
+ // Build dla Chrome - używamy adaptera Chrome
browserApi = chromeAPI;
} else {
- // Firefox build (default) - używamy firefox adapter
- const { firefoxAPI } = require('./firefox');
+ // Build dla Firefox (domyślny) - używamy adaptera Firefox
browserApi = firefoxAPI;
}
-// Eksportuj jako default export
+// Eksport jako default export
export default browserApi;
-// Re-export typów dla wygody
+// Re-eksport typów dla wygody
export * from './types';
\ No newline at end of file
diff --git a/lib/browser-api/types.ts b/lib/browser-api/types.ts
index 4723242..ed3fd0f 100644
--- a/lib/browser-api/types.ts
+++ b/lib/browser-api/types.ts
@@ -1,14 +1,8 @@
/**
* 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 full Request type from util.ts
+// Import pełnego typu Request z util.ts
export type Request = {
cookieStoreId?: string;
documentUrl?: string;
@@ -35,57 +29,58 @@ export type Request = {
urlClassification?: { firstParty: string[]; thirdParty: string[] };
};
-// === Tab API (util.ts, tab-dropdown.tsx, toolbar.tsx) ===
+// === Tab API ===
export interface Tab {
- id?: number; // util.ts: tab.id, tab-dropdown.tsx: tab.id
- title?: string; // tab-dropdown.tsx: tab.title
- url?: string; // toolbar.tsx: tab.url
+ id?: number;
+ title?: string;
+ url?: string;
}
export interface TabQuery {
- currentWindow?: boolean; // util.ts, tab-dropdown.tsx
- active?: boolean; // toolbar.tsx
- windowId?: number; // toolbar.tsx
+ currentWindow?: boolean;
+ active?: boolean;
+ windowId?: number;
+ lastFocusedWindow?: boolean; // Chrome używa tego zamiast currentWindow czasami
}
-// === Badge/BrowserAction API (memory.ts) ===
+// === Badge/BrowserAction API ===
export interface BadgeTextDetails {
- text: string; // memory.ts: setBadgeText
- tabId?: number; // memory.ts: setBadgeText (optional)
+ text: string;
+ tabId?: number;
}
export interface BadgeTitleDetails {
- title: string; // memory.ts: setTitle
- tabId?: number; // memory.ts: setTitle (optional)
+ title: string;
+ tabId?: number;
}
export interface BadgeColorDetails {
- color: string; // memory.ts: setBadgeBackgroundColor
+ color: string;
}
-// === WebRequest API (memory.ts) ===
+// === WebRequest API ===
export interface RequestFilter {
- urls: string[]; // memory.ts: { urls: [''] }
+ urls: string[];
}
export type RequestListener = (details: Request) => void;
-// === Cookies API (memory.ts) ===
+// === Cookies API ===
export interface Cookie {
- name: string; // memory.ts: cookie.name
- domain: string; // memory.ts: cookie.domain
+ name: string;
+ domain: string;
}
export interface CookieQuery {
- domain?: string; // memory.ts: { domain: shorthost }
+ domain?: string;
}
export interface CookieRemove {
- name: string; // memory.ts: { name: cookie.name, url: ... }
- url: string; // memory.ts: { url: `https://${cookie.domain}` }
+ name: string;
+ url: string;
}
-// === Main Browser API Interface ===
+// === Główny interfejs Browser API ===
export interface BrowserAPI {
// Tabs API
tabs: {
@@ -94,6 +89,10 @@ export interface BrowserAPI {
addListener(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)
diff --git a/manifest-chrome.json b/manifest-chrome.json
new file mode 100644
index 0000000..3320c80
--- /dev/null
+++ b/manifest-chrome.json
@@ -0,0 +1,35 @@
+{
+ "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": [
+ ""
+ ]
+}
\ No newline at end of file
diff --git a/memory.ts b/memory.ts
index ed904a4..af73e82 100644
--- a/memory.ts
+++ b/memory.ts
@@ -4,18 +4,132 @@ import { RequestCluster } from './request-cluster';
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) {
- browserAPI.badge.setBadgeText({ text: counter < 0 ? '0' : counter.toString(), tabId });
- browserAPI.badge.setTitle({
- title: 'Rentgen',
- tabId,
- });
+ // Ochrona przed próbą ustawienia badge dla zamkniętej zakładki
+ try {
+ browserAPI.badge.setBadgeText({ text: counter < 0 ? '0' : counter.toString(), tabId });
+ browserAPI.badge.setTitle({
+ 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 {
origin_to_history = {} as Record>;
+ isReady: boolean = true; // Firefox jest zawsze gotowy
+ private readyPromise: Promise | 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 = new Map();
+
async register(request: ExtendedRequest) {
await request.init();
+
if (!request.isThirdParty()) {
return;
}
@@ -23,24 +137,46 @@ export default class Memory extends SaferEmitter {
this.origin_to_history[request.origin] = {};
}
const shorthost = getshorthost(new URL(request.url).host);
+
+ let isNewCluster = false;
if (!this.origin_to_history[request.origin][shorthost]) {
const cluster = new RequestCluster(shorthost);
this.origin_to_history[request.origin][shorthost] = cluster;
+ isNewCluster = true;
}
+
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);
- Object.values(this.getClustersForOrigin(request.origin)).some((cluster) =>
- cluster.hasCookies()
- )
- ? browserAPI.badge.setBadgeBackgroundColor({ color: '#ff726b' })
- : browserAPI.badge.setBadgeBackgroundColor({ color: '#ffb900' });
+ // Owinięcie operacji badge w try-catch
+ try {
+ Object.values(this.getClustersForOrigin(request.origin)).some((cluster) =>
+ cluster.hasCookies()
+ )
+ ? browserAPI.badge.setBadgeBackgroundColor({ color: '#ff726b' })
+ : browserAPI.badge.setBadgeBackgroundColor({ color: '#ffb900' });
- if (request.tabId >= 0) {
- setDomainsCount(
- Object.values(this.getClustersForOrigin(request.origin)).length,
- request.tabId
- );
+ if (request.tabId >= 0) {
+ setDomainsCount(
+ Object.values(this.getClustersForOrigin(request.origin)).length,
+ 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();
}
}
@@ -49,11 +185,26 @@ export default class Memory extends SaferEmitter {
browserAPI.webRequest.onBeforeRequest.addListener(
async (request) => {
- new ExtendedRequest(request);
+ // Chrome: Śledzenie nawigacji main_frame dla pełnego URL
+ 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: [''] },
['requestBody']
);
+
browserAPI.webRequest.onBeforeSendHeaders.addListener(
async (request) => {
const extendedRequest = ExtendedRequest.by_id[request.requestId].addHeaders(
@@ -64,10 +215,34 @@ export default class Memory extends SaferEmitter {
{ urls: [''] },
['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 {
- setTimeout(() => super.emit(eventName, data), 0);
+ setTimeout(() => {
+ super.emit(eventName, data);
+
+ // ✅ Sync to storage when marks change (Chrome only)
+ if (process.env.TARGET === 'chrome' && eventName === 'change') {
+ this.scheduleSyncToStorage();
+ }
+ }, 0);
return true;
}
@@ -75,6 +250,14 @@ export default class Memory extends SaferEmitter {
return this.origin_to_history[origin] || {};
}
+ // Chrome: Oczekiwanie na zakończenie wczytywania ze storage
+ async waitUntilReady(): Promise {
+ if (this.isReady) return;
+ if (this.readyPromise) {
+ await this.readyPromise;
+ }
+ }
+
async removeCookiesFor(origin: string, shorthost?: string): Promise {
if (shorthost) {
const cookies = await browserAPI.cookies.getAll({ domain: shorthost });
@@ -93,23 +276,150 @@ export default class Memory extends SaferEmitter {
.map((cluster) => this.removeCookiesFor(origin, cluster.id))
);
}
+
+ // Chrome: Throttlowana synchronizacja do storage
+ if (process.env.TARGET === 'chrome') {
+ this.scheduleSyncToStorage();
+ }
}
async removeRequestsFor(origin: string) {
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 = {};
+
+ 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 {
+ 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)) {
+ // 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() {
const memory = new Memory();
- (window as any).memory = memory;
+ if (process.env.TARGET === 'chrome') {
+ // 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 {
- const backgroundPage = browserAPI.extension.getBackgroundPage();
- if (!backgroundPage) {
- throw new Error('Background page not available');
+ if (process.env.TARGET === 'chrome') {
+ // 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;
}
- return (backgroundPage.window as any).memory as Memory;
}
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index b4185d8..306cc67 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -25,6 +25,7 @@
"addons-linter": "^4.7.0",
"esbuild": "^0.14.14",
"esbuild-plugin-sass": "^1.0.1",
+ "sharp": "^0.34.4",
"typescript": "^4.6.4",
"web-ext": "^6.7.0",
"web-ext-types": "^3.2.1"
@@ -187,6 +188,17 @@
"node": ">= 0.10.4"
}
},
+ "node_modules/@emnapi/runtime": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.5.0.tgz",
+ "integrity": "sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
"node_modules/@eslint/eslintrc": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.0.tgz",
@@ -292,6 +304,456 @@
"resolved": "https://registry.npmjs.org/@iabtcf/core/-/core-1.5.3.tgz",
"integrity": "sha512-DZsenDL6uz/jULc/PlWs9HA2eDHkAcL+JiZ5TtB1O5ZSwZ2BMyJc4I5qC+j/7BZFPXqaHRbGQ7QpbxmcHYLw1Q=="
},
+ "node_modules/@img/colour": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.0.0.tgz",
+ "integrity": "sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@img/sharp-darwin-arm64": {
+ "version": "0.34.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.4.tgz",
+ "integrity": "sha512-sitdlPzDVyvmINUdJle3TNHl+AG9QcwiAMsXmccqsCOMZNIdW2/7S26w0LyU8euiLVzFBL3dXPwVCq/ODnf2vA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-darwin-arm64": "1.2.3"
+ }
+ },
+ "node_modules/@img/sharp-darwin-x64": {
+ "version": "0.34.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.4.tgz",
+ "integrity": "sha512-rZheupWIoa3+SOdF/IcUe1ah4ZDpKBGWcsPX6MT0lYniH9micvIU7HQkYTfrx5Xi8u+YqwLtxC/3vl8TQN6rMg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-darwin-x64": "1.2.3"
+ }
+ },
+ "node_modules/@img/sharp-libvips-darwin-arm64": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.3.tgz",
+ "integrity": "sha512-QzWAKo7kpHxbuHqUC28DZ9pIKpSi2ts2OJnoIGI26+HMgq92ZZ4vk8iJd4XsxN+tYfNJxzH6W62X5eTcsBymHw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-darwin-x64": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.3.tgz",
+ "integrity": "sha512-Ju+g2xn1E2AKO6YBhxjj+ACcsPQRHT0bhpglxcEf+3uyPY+/gL8veniKoo96335ZaPo03bdDXMv0t+BBFAbmRA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-arm": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.3.tgz",
+ "integrity": "sha512-x1uE93lyP6wEwGvgAIV0gP6zmaL/a0tGzJs/BIDDG0zeBhMnuUPm7ptxGhUbcGs4okDJrk4nxgrmxpib9g6HpA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-arm64": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.3.tgz",
+ "integrity": "sha512-I4RxkXU90cpufazhGPyVujYwfIm9Nk1QDEmiIsaPwdnm013F7RIceaCc87kAH+oUB1ezqEvC6ga4m7MSlqsJvQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-ppc64": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.3.tgz",
+ "integrity": "sha512-Y2T7IsQvJLMCBM+pmPbM3bKT/yYJvVtLJGfCs4Sp95SjvnFIjynbjzsa7dY1fRJX45FTSfDksbTp6AGWudiyCg==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-s390x": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.3.tgz",
+ "integrity": "sha512-RgWrs/gVU7f+K7P+KeHFaBAJlNkD1nIZuVXdQv6S+fNA6syCcoboNjsV2Pou7zNlVdNQoQUpQTk8SWDHUA3y/w==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-x64": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.3.tgz",
+ "integrity": "sha512-3JU7LmR85K6bBiRzSUc/Ff9JBVIFVvq6bomKE0e63UXGeRw2HPVEjoJke1Yx+iU4rL7/7kUjES4dZ/81Qjhyxg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linuxmusl-arm64": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.3.tgz",
+ "integrity": "sha512-F9q83RZ8yaCwENw1GieztSfj5msz7GGykG/BA+MOUefvER69K/ubgFHNeSyUu64amHIYKGDs4sRCMzXVj8sEyw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linuxmusl-x64": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.3.tgz",
+ "integrity": "sha512-U5PUY5jbc45ANM6tSJpsgqmBF/VsL6LnxJmIf11kB7J5DctHgqm0SkuXzVWtIY90GnJxKnC/JT251TDnk1fu/g==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-linux-arm": {
+ "version": "0.34.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.4.tgz",
+ "integrity": "sha512-Xyam4mlqM0KkTHYVSuc6wXRmM7LGN0P12li03jAnZ3EJWZqj83+hi8Y9UxZUbxsgsK1qOEwg7O0Bc0LjqQVtxA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-arm": "1.2.3"
+ }
+ },
+ "node_modules/@img/sharp-linux-arm64": {
+ "version": "0.34.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.4.tgz",
+ "integrity": "sha512-YXU1F/mN/Wu786tl72CyJjP/Ngl8mGHN1hST4BGl+hiW5jhCnV2uRVTNOcaYPs73NeT/H8Upm3y9582JVuZHrQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-arm64": "1.2.3"
+ }
+ },
+ "node_modules/@img/sharp-linux-ppc64": {
+ "version": "0.34.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.4.tgz",
+ "integrity": "sha512-F4PDtF4Cy8L8hXA2p3TO6s4aDt93v+LKmpcYFLAVdkkD3hSxZzee0rh6/+94FpAynsuMpLX5h+LRsSG3rIciUQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-ppc64": "1.2.3"
+ }
+ },
+ "node_modules/@img/sharp-linux-s390x": {
+ "version": "0.34.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.4.tgz",
+ "integrity": "sha512-qVrZKE9Bsnzy+myf7lFKvng6bQzhNUAYcVORq2P7bDlvmF6u2sCmK2KyEQEBdYk+u3T01pVsPrkj943T1aJAsw==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-s390x": "1.2.3"
+ }
+ },
+ "node_modules/@img/sharp-linux-x64": {
+ "version": "0.34.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.4.tgz",
+ "integrity": "sha512-ZfGtcp2xS51iG79c6Vhw9CWqQC8l2Ot8dygxoDoIQPTat/Ov3qAa8qpxSrtAEAJW+UjTXc4yxCjNfxm4h6Xm2A==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-x64": "1.2.3"
+ }
+ },
+ "node_modules/@img/sharp-linuxmusl-arm64": {
+ "version": "0.34.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.4.tgz",
+ "integrity": "sha512-8hDVvW9eu4yHWnjaOOR8kHVrew1iIX+MUgwxSuH2XyYeNRtLUe4VNioSqbNkB7ZYQJj9rUTT4PyRscyk2PXFKA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linuxmusl-arm64": "1.2.3"
+ }
+ },
+ "node_modules/@img/sharp-linuxmusl-x64": {
+ "version": "0.34.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.4.tgz",
+ "integrity": "sha512-lU0aA5L8QTlfKjpDCEFOZsTYGn3AEiO6db8W5aQDxj0nQkVrZWmN3ZP9sYKWJdtq3PWPhUNlqehWyXpYDcI9Sg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linuxmusl-x64": "1.2.3"
+ }
+ },
+ "node_modules/@img/sharp-wasm32": {
+ "version": "0.34.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.4.tgz",
+ "integrity": "sha512-33QL6ZO/qpRyG7woB/HUALz28WnTMI2W1jgX3Nu2bypqLIKx/QKMILLJzJjI+SIbvXdG9fUnmrxR7vbi1sTBeA==",
+ "cpu": [
+ "wasm32"
+ ],
+ "dev": true,
+ "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/runtime": "^1.5.0"
+ },
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-win32-arm64": {
+ "version": "0.34.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.4.tgz",
+ "integrity": "sha512-2Q250do/5WXTwxW3zjsEuMSv5sUU4Tq9VThWKlU2EYLm4MB7ZeMwF+SFJutldYODXF6jzc6YEOC+VfX0SZQPqA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0 AND LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-win32-ia32": {
+ "version": "0.34.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.4.tgz",
+ "integrity": "sha512-3ZeLue5V82dT92CNL6rsal6I2weKw1cYu+rGKm8fOCCtJTR2gYeUfY3FqUnIJsMUPIH68oS5jmZ0NiJ508YpEw==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "Apache-2.0 AND LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-win32-x64": {
+ "version": "0.34.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.4.tgz",
+ "integrity": "sha512-xIyj4wpYs8J18sVN3mSQjwrw7fKUqRw+Z5rnHNCy5fYTxigBz81u5mOMPmFumwjcn8+ld1ppptMBCLic1nz6ig==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0 AND LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
"node_modules/@mdn/browser-compat-data": {
"version": "4.1.13",
"resolved": "https://registry.npmjs.org/@mdn/browser-compat-data/-/browser-compat-data-4.1.13.tgz",
@@ -1832,6 +2294,16 @@
"dev": true,
"peer": true
},
+ "node_modules/detect-libc": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
+ "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/doctrine": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
@@ -5699,6 +6171,62 @@
"sha.js": "bin.js"
}
},
+ "node_modules/sharp": {
+ "version": "0.34.4",
+ "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.4.tgz",
+ "integrity": "sha512-FUH39xp3SBPnxWvd5iib1X8XY7J0K0X7d93sie9CJg2PO8/7gmg89Nve6OjItK53/MlAushNNxteBYfM6DEuoA==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@img/colour": "^1.0.0",
+ "detect-libc": "^2.1.0",
+ "semver": "^7.7.2"
+ },
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-darwin-arm64": "0.34.4",
+ "@img/sharp-darwin-x64": "0.34.4",
+ "@img/sharp-libvips-darwin-arm64": "1.2.3",
+ "@img/sharp-libvips-darwin-x64": "1.2.3",
+ "@img/sharp-libvips-linux-arm": "1.2.3",
+ "@img/sharp-libvips-linux-arm64": "1.2.3",
+ "@img/sharp-libvips-linux-ppc64": "1.2.3",
+ "@img/sharp-libvips-linux-s390x": "1.2.3",
+ "@img/sharp-libvips-linux-x64": "1.2.3",
+ "@img/sharp-libvips-linuxmusl-arm64": "1.2.3",
+ "@img/sharp-libvips-linuxmusl-x64": "1.2.3",
+ "@img/sharp-linux-arm": "0.34.4",
+ "@img/sharp-linux-arm64": "0.34.4",
+ "@img/sharp-linux-ppc64": "0.34.4",
+ "@img/sharp-linux-s390x": "0.34.4",
+ "@img/sharp-linux-x64": "0.34.4",
+ "@img/sharp-linuxmusl-arm64": "0.34.4",
+ "@img/sharp-linuxmusl-x64": "0.34.4",
+ "@img/sharp-wasm32": "0.34.4",
+ "@img/sharp-win32-arm64": "0.34.4",
+ "@img/sharp-win32-ia32": "0.34.4",
+ "@img/sharp-win32-x64": "0.34.4"
+ }
+ },
+ "node_modules/sharp/node_modules/semver": {
+ "version": "7.7.3",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz",
+ "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
@@ -7316,6 +7844,16 @@
"async": "~0.2.9"
}
},
+ "@emnapi/runtime": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.5.0.tgz",
+ "integrity": "sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "tslib": "^2.4.0"
+ }
+ },
"@eslint/eslintrc": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.0.tgz",
@@ -7400,6 +7938,196 @@
"resolved": "https://registry.npmjs.org/@iabtcf/core/-/core-1.5.3.tgz",
"integrity": "sha512-DZsenDL6uz/jULc/PlWs9HA2eDHkAcL+JiZ5TtB1O5ZSwZ2BMyJc4I5qC+j/7BZFPXqaHRbGQ7QpbxmcHYLw1Q=="
},
+ "@img/colour": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.0.0.tgz",
+ "integrity": "sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==",
+ "dev": true
+ },
+ "@img/sharp-darwin-arm64": {
+ "version": "0.34.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.4.tgz",
+ "integrity": "sha512-sitdlPzDVyvmINUdJle3TNHl+AG9QcwiAMsXmccqsCOMZNIdW2/7S26w0LyU8euiLVzFBL3dXPwVCq/ODnf2vA==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "@img/sharp-libvips-darwin-arm64": "1.2.3"
+ }
+ },
+ "@img/sharp-darwin-x64": {
+ "version": "0.34.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.4.tgz",
+ "integrity": "sha512-rZheupWIoa3+SOdF/IcUe1ah4ZDpKBGWcsPX6MT0lYniH9micvIU7HQkYTfrx5Xi8u+YqwLtxC/3vl8TQN6rMg==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "@img/sharp-libvips-darwin-x64": "1.2.3"
+ }
+ },
+ "@img/sharp-libvips-darwin-arm64": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.3.tgz",
+ "integrity": "sha512-QzWAKo7kpHxbuHqUC28DZ9pIKpSi2ts2OJnoIGI26+HMgq92ZZ4vk8iJd4XsxN+tYfNJxzH6W62X5eTcsBymHw==",
+ "dev": true,
+ "optional": true
+ },
+ "@img/sharp-libvips-darwin-x64": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.3.tgz",
+ "integrity": "sha512-Ju+g2xn1E2AKO6YBhxjj+ACcsPQRHT0bhpglxcEf+3uyPY+/gL8veniKoo96335ZaPo03bdDXMv0t+BBFAbmRA==",
+ "dev": true,
+ "optional": true
+ },
+ "@img/sharp-libvips-linux-arm": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.3.tgz",
+ "integrity": "sha512-x1uE93lyP6wEwGvgAIV0gP6zmaL/a0tGzJs/BIDDG0zeBhMnuUPm7ptxGhUbcGs4okDJrk4nxgrmxpib9g6HpA==",
+ "dev": true,
+ "optional": true
+ },
+ "@img/sharp-libvips-linux-arm64": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.3.tgz",
+ "integrity": "sha512-I4RxkXU90cpufazhGPyVujYwfIm9Nk1QDEmiIsaPwdnm013F7RIceaCc87kAH+oUB1ezqEvC6ga4m7MSlqsJvQ==",
+ "dev": true,
+ "optional": true
+ },
+ "@img/sharp-libvips-linux-ppc64": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.3.tgz",
+ "integrity": "sha512-Y2T7IsQvJLMCBM+pmPbM3bKT/yYJvVtLJGfCs4Sp95SjvnFIjynbjzsa7dY1fRJX45FTSfDksbTp6AGWudiyCg==",
+ "dev": true,
+ "optional": true
+ },
+ "@img/sharp-libvips-linux-s390x": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.3.tgz",
+ "integrity": "sha512-RgWrs/gVU7f+K7P+KeHFaBAJlNkD1nIZuVXdQv6S+fNA6syCcoboNjsV2Pou7zNlVdNQoQUpQTk8SWDHUA3y/w==",
+ "dev": true,
+ "optional": true
+ },
+ "@img/sharp-libvips-linux-x64": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.3.tgz",
+ "integrity": "sha512-3JU7LmR85K6bBiRzSUc/Ff9JBVIFVvq6bomKE0e63UXGeRw2HPVEjoJke1Yx+iU4rL7/7kUjES4dZ/81Qjhyxg==",
+ "dev": true,
+ "optional": true
+ },
+ "@img/sharp-libvips-linuxmusl-arm64": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.3.tgz",
+ "integrity": "sha512-F9q83RZ8yaCwENw1GieztSfj5msz7GGykG/BA+MOUefvER69K/ubgFHNeSyUu64amHIYKGDs4sRCMzXVj8sEyw==",
+ "dev": true,
+ "optional": true
+ },
+ "@img/sharp-libvips-linuxmusl-x64": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.3.tgz",
+ "integrity": "sha512-U5PUY5jbc45ANM6tSJpsgqmBF/VsL6LnxJmIf11kB7J5DctHgqm0SkuXzVWtIY90GnJxKnC/JT251TDnk1fu/g==",
+ "dev": true,
+ "optional": true
+ },
+ "@img/sharp-linux-arm": {
+ "version": "0.34.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.4.tgz",
+ "integrity": "sha512-Xyam4mlqM0KkTHYVSuc6wXRmM7LGN0P12li03jAnZ3EJWZqj83+hi8Y9UxZUbxsgsK1qOEwg7O0Bc0LjqQVtxA==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "@img/sharp-libvips-linux-arm": "1.2.3"
+ }
+ },
+ "@img/sharp-linux-arm64": {
+ "version": "0.34.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.4.tgz",
+ "integrity": "sha512-YXU1F/mN/Wu786tl72CyJjP/Ngl8mGHN1hST4BGl+hiW5jhCnV2uRVTNOcaYPs73NeT/H8Upm3y9582JVuZHrQ==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "@img/sharp-libvips-linux-arm64": "1.2.3"
+ }
+ },
+ "@img/sharp-linux-ppc64": {
+ "version": "0.34.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.4.tgz",
+ "integrity": "sha512-F4PDtF4Cy8L8hXA2p3TO6s4aDt93v+LKmpcYFLAVdkkD3hSxZzee0rh6/+94FpAynsuMpLX5h+LRsSG3rIciUQ==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "@img/sharp-libvips-linux-ppc64": "1.2.3"
+ }
+ },
+ "@img/sharp-linux-s390x": {
+ "version": "0.34.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.4.tgz",
+ "integrity": "sha512-qVrZKE9Bsnzy+myf7lFKvng6bQzhNUAYcVORq2P7bDlvmF6u2sCmK2KyEQEBdYk+u3T01pVsPrkj943T1aJAsw==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "@img/sharp-libvips-linux-s390x": "1.2.3"
+ }
+ },
+ "@img/sharp-linux-x64": {
+ "version": "0.34.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.4.tgz",
+ "integrity": "sha512-ZfGtcp2xS51iG79c6Vhw9CWqQC8l2Ot8dygxoDoIQPTat/Ov3qAa8qpxSrtAEAJW+UjTXc4yxCjNfxm4h6Xm2A==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "@img/sharp-libvips-linux-x64": "1.2.3"
+ }
+ },
+ "@img/sharp-linuxmusl-arm64": {
+ "version": "0.34.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.4.tgz",
+ "integrity": "sha512-8hDVvW9eu4yHWnjaOOR8kHVrew1iIX+MUgwxSuH2XyYeNRtLUe4VNioSqbNkB7ZYQJj9rUTT4PyRscyk2PXFKA==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "@img/sharp-libvips-linuxmusl-arm64": "1.2.3"
+ }
+ },
+ "@img/sharp-linuxmusl-x64": {
+ "version": "0.34.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.4.tgz",
+ "integrity": "sha512-lU0aA5L8QTlfKjpDCEFOZsTYGn3AEiO6db8W5aQDxj0nQkVrZWmN3ZP9sYKWJdtq3PWPhUNlqehWyXpYDcI9Sg==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "@img/sharp-libvips-linuxmusl-x64": "1.2.3"
+ }
+ },
+ "@img/sharp-wasm32": {
+ "version": "0.34.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.4.tgz",
+ "integrity": "sha512-33QL6ZO/qpRyG7woB/HUALz28WnTMI2W1jgX3Nu2bypqLIKx/QKMILLJzJjI+SIbvXdG9fUnmrxR7vbi1sTBeA==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "@emnapi/runtime": "^1.5.0"
+ }
+ },
+ "@img/sharp-win32-arm64": {
+ "version": "0.34.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.4.tgz",
+ "integrity": "sha512-2Q250do/5WXTwxW3zjsEuMSv5sUU4Tq9VThWKlU2EYLm4MB7ZeMwF+SFJutldYODXF6jzc6YEOC+VfX0SZQPqA==",
+ "dev": true,
+ "optional": true
+ },
+ "@img/sharp-win32-ia32": {
+ "version": "0.34.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.4.tgz",
+ "integrity": "sha512-3ZeLue5V82dT92CNL6rsal6I2weKw1cYu+rGKm8fOCCtJTR2gYeUfY3FqUnIJsMUPIH68oS5jmZ0NiJ508YpEw==",
+ "dev": true,
+ "optional": true
+ },
+ "@img/sharp-win32-x64": {
+ "version": "0.34.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.4.tgz",
+ "integrity": "sha512-xIyj4wpYs8J18sVN3mSQjwrw7fKUqRw+Z5rnHNCy5fYTxigBz81u5mOMPmFumwjcn8+ld1ppptMBCLic1nz6ig==",
+ "dev": true,
+ "optional": true
+ },
"@mdn/browser-compat-data": {
"version": "4.1.13",
"resolved": "https://registry.npmjs.org/@mdn/browser-compat-data/-/browser-compat-data-4.1.13.tgz",
@@ -8626,6 +9354,12 @@
"dev": true,
"peer": true
},
+ "detect-libc": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
+ "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
+ "dev": true
+ },
"doctrine": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
@@ -11633,6 +12367,47 @@
"safe-buffer": "^5.0.1"
}
},
+ "sharp": {
+ "version": "0.34.4",
+ "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.4.tgz",
+ "integrity": "sha512-FUH39xp3SBPnxWvd5iib1X8XY7J0K0X7d93sie9CJg2PO8/7gmg89Nve6OjItK53/MlAushNNxteBYfM6DEuoA==",
+ "dev": true,
+ "requires": {
+ "@img/colour": "^1.0.0",
+ "@img/sharp-darwin-arm64": "0.34.4",
+ "@img/sharp-darwin-x64": "0.34.4",
+ "@img/sharp-libvips-darwin-arm64": "1.2.3",
+ "@img/sharp-libvips-darwin-x64": "1.2.3",
+ "@img/sharp-libvips-linux-arm": "1.2.3",
+ "@img/sharp-libvips-linux-arm64": "1.2.3",
+ "@img/sharp-libvips-linux-ppc64": "1.2.3",
+ "@img/sharp-libvips-linux-s390x": "1.2.3",
+ "@img/sharp-libvips-linux-x64": "1.2.3",
+ "@img/sharp-libvips-linuxmusl-arm64": "1.2.3",
+ "@img/sharp-libvips-linuxmusl-x64": "1.2.3",
+ "@img/sharp-linux-arm": "0.34.4",
+ "@img/sharp-linux-arm64": "0.34.4",
+ "@img/sharp-linux-ppc64": "0.34.4",
+ "@img/sharp-linux-s390x": "0.34.4",
+ "@img/sharp-linux-x64": "0.34.4",
+ "@img/sharp-linuxmusl-arm64": "0.34.4",
+ "@img/sharp-linuxmusl-x64": "0.34.4",
+ "@img/sharp-wasm32": "0.34.4",
+ "@img/sharp-win32-arm64": "0.34.4",
+ "@img/sharp-win32-ia32": "0.34.4",
+ "@img/sharp-win32-x64": "0.34.4",
+ "detect-libc": "^2.1.0",
+ "semver": "^7.7.2"
+ },
+ "dependencies": {
+ "semver": {
+ "version": "7.7.3",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz",
+ "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==",
+ "dev": true
+ }
+ }
+ },
"shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
diff --git a/package.json b/package.json
index f1362be..93a00c9 100644
--- a/package.json
+++ b/package.json
@@ -5,18 +5,19 @@
"main": "esbuild.config.js",
"type": "module",
"scripts": {
+ "convert-icons": "node scripts/convert-icons.js",
"build": "node esbuild.config.js",
"build:firefox": "TARGET=firefox node esbuild.config.js",
- "build:chrome": "TARGET=chrome node esbuild.config.js",
+ "build:chrome": "npm run convert-icons && TARGET=chrome node esbuild.config.js",
"watch": "node esbuild.config.js --watch",
"watch:firefox": "TARGET=firefox node esbuild.config.js --watch",
"watch:chrome": "TARGET=chrome node esbuild.config.js --watch",
"ext-test": "web-ext run",
"build-addon": "npm i && npm run build && npm run create-package",
- "build-addon:firefox": "npm i && npm run build:firefox && npm run create-package:firefox",
+ "build-addon:firefox": "npm i && npm run build && npm run create-package:firefox",
"build-addon:chrome": "npm i && npm run build:chrome && npm run create-package:chrome",
"create-package": "web-ext build --ignore-files '!**/node_modules' '!**/node_modules/**/react-dom' '!**/node_modules/**/react-dom/umd' '!**/node_modules/**/*/react-dom.production.min.js' '!**/node_modules/**/react' '!**/node_modules/**/react/umd' '!**/node_modules/**/*/react.production.min.js' '!**/node_modules/**/survey-react' '!**/node_modules/**/survey-react/*.min.js' '!**/node_modules/**/survey-react/*.min.css' --overwrite-dest",
- "create-package:firefox": "cd dist-firefox && web-ext build --overwrite-dest --artifacts-dir ../web-ext-artifacts",
+ "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:chrome": "cd dist-chrome && 7z a -tzip ../web-ext-artifacts/rentgen-chrome-0.1.10.zip * && cd ..",
"typecheck": "tsc --noEmit",
"lint": "web-ext lint"
@@ -57,6 +58,7 @@
"addons-linter": "^4.7.0",
"esbuild": "^0.14.14",
"esbuild-plugin-sass": "^1.0.1",
+ "sharp": "^0.34.4",
"typescript": "^4.6.4",
"web-ext": "^6.7.0",
"web-ext-types": "^3.2.1"
diff --git a/scripts/convert-icons.js b/scripts/convert-icons.js
new file mode 100644
index 0000000..0c14869
--- /dev/null
+++ b/scripts/convert-icons.js
@@ -0,0 +1,32 @@
+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();
\ No newline at end of file
diff --git a/util.ts b/util.ts
index 2f87878..3d07579 100644
--- a/util.ts
+++ b/util.ts
@@ -34,21 +34,50 @@ export type Request = {
};
export function getshorthost(host: string) {
- const parts = host
- .replace(/^.*:\/\//, '')
- .replace(/\/.*$/, '')
- .split('.');
+ // Obsługa przypadków brzegowych
+ if (!host || typeof host !== 'string') {
+ console.warn('getshorthost: nieprawidłowy host:', host);
+ return 'unknown';
+ }
+
+ // 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);
+
+ // Bezpieczny fallback jeśli wciąż nieprawidłowy
if (!second_last) {
- throw new Error('url too short?');
+ console.warn('getshorthost: nie można określić domeny dla:', host);
+ return cleanHost; // Zwracamy jak jest zamiast crashować
}
+
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
+ lookback = -4; // aby rozróżnić google ads i stats
} else if (parts.at(-2) == 'google') {
- lookback = -3; // to distinguish various google services
+ lookback = -3; // aby rozróżnić różne usługi google
}
- 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(