refactor: migracja wywołań API przeglądarki do abstrakcji browserAPI #127
| @ -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'); | ||||
| } | ||||
| @ -24,6 +24,24 @@ function Report() { | ||||
|         if (!origin) { | ||||
|             return <div>Błąd: brak parametru "origin"</div>; | ||||
|         } | ||||
|          | ||||
|         // Oczekiwanie na gotowość pamięci Chrome
 | ||||
|         const [memoryReady, setMemoryReady] = React.useState(process.env.TARGET !== 'chrome'); | ||||
| 
					
					kuba marked this conversation as resolved
					
				 | ||||
|          | ||||
|         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<ParsedAnswers>( | ||||
| @ -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 <div>Wczytywanie danych z rozszerzenia...</div>; | ||||
|         } | ||||
|          | ||||
|         const visited_url = Object.values(clusters) | ||||
|             .sort((a, b) => (a.lastModified > b.lastModified ? -1 : 1)) | ||||
|             .find((cluster) => !!cluster.lastFullUrl)?.lastFullUrl; | ||||
| 
 | ||||
|         if (!visited_url) { | ||||
|             return <div>Wczytywanie...</div>; | ||||
|         // Jeśli nie znaleziono visited_url, próba skonstruowania z origin
 | ||||
|         const finalVisitedUrl = visited_url || origin; | ||||
|          | ||||
|         if (!finalVisitedUrl) { | ||||
|             return <div>Błąd: nie można znaleźć adresu strony</div>; | ||||
|         } | ||||
| 
 | ||||
|         const result = ( | ||||
| @ -55,7 +83,7 @@ function Report() { | ||||
|                 {mode === 'survey' ? ( | ||||
|                     <Questions | ||||
|                         clusters={Object.values(clusters).filter( | ||||
|                             (cluster) => cluster.getMarkedRequests().length > 0 | ||||
|                             (cluster) => cluster.hasMarks() | ||||
|                         )} | ||||
|                         onComplete={(answers) => { | ||||
|                             setAnswers(parseAnswers(answers)); | ||||
| @ -68,11 +96,11 @@ function Report() { | ||||
|                 {mode === 'screenshots' ? ( | ||||
|                     <ScreenshotGenerator | ||||
|                         {...{ | ||||
|                             visited_url, | ||||
|                             visited_url: finalVisitedUrl, | ||||
|                             clusters, | ||||
|                             setReportWindowMode: setMode, | ||||
|                             setRequestPath: setScrRequestPath, | ||||
|                             downloadFiles: downloadFiles, | ||||
|                             downloadFiles, | ||||
|                             user_role: answers.user_role, | ||||
|                         }} | ||||
|                     /> | ||||
| @ -83,10 +111,10 @@ function Report() { | ||||
|                     <EmailContent | ||||
|                         {...{ | ||||
|                             answers, | ||||
|                             visited_url, | ||||
|                             visited_url: finalVisitedUrl, | ||||
|                             clusters, | ||||
|                             scrRequestPath, | ||||
|                             downloadFiles: downloadFiles, | ||||
|                             downloadFiles, | ||||
|                             user_role: answers.user_role, | ||||
|                         }} | ||||
|                     /> | ||||
| @ -95,28 +123,19 @@ function Report() { | ||||
|                 )} | ||||
|             </div> | ||||
|         ); | ||||
|         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> | ||||
|         ); | ||||
|         return result; | ||||
|     } catch (e) { | ||||
|         console.error(e); | ||||
|         return <div>ERROR! {JSON.stringify(e)}</div>; | ||||
|         return ( | ||||
|             <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')); | ||||
| @ -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<number | null>( | ||||
|         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 <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 ( | ||||
|         <div className="sidebar"> | ||||
|             <header className="header"> | ||||
| @ -121,12 +154,12 @@ const Sidebar = () => { | ||||
|                     </button> | ||||
| 
 | ||||
|                     {localStorage.getItem('blottingBrowser') === | ||||
|                     'nikttakniesplamitwojejprzeglądarkijakspidersweb' ? ( | ||||
|                     'nikttakniesplamitwojejprzeglądarki jakspidersweb' ? ( | ||||
|                         <button | ||||
|                             onClick={() => { | ||||
|                                 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 = () => { | ||||
|                             <section className="dialog-container dialog-container--warning"> | ||||
|                                 <span> | ||||
|                                     <strong>Uwaga!</strong> 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. | ||||
|                                 </span> | ||||
|                                 <button | ||||
| @ -234,4 +267,4 @@ const Sidebar = () => { | ||||
|     ); | ||||
| }; | ||||
| 
 | ||||
| ReactDOM.render(<Sidebar />, document.getElementById('app')); | ||||
| ReactDOM.render(<Sidebar />, document.getElementById('app')); | ||||
| @ -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; | ||||
|  | ||||
| @ -7,6 +7,10 @@ | ||||
|         rel="stylesheet" | ||||
|         href="/lib/styles/global.css" | ||||
|     > | ||||
|     <link | ||||
|         rel="stylesheet" | ||||
|         href="/lib/styles/fonts.css" | ||||
|     > | ||||
|     <link | ||||
|         rel="stylesheet" | ||||
|         href="/lib/components/toolbar/toolbar.css" | ||||
|  | ||||
| @ -4,12 +4,40 @@ import { getMemory } from '../../memory'; | ||||
| import { useEmitter, getshorthost } from '../../util'; | ||||
| import browserAPI from '../../lib/browser-api'; | ||||
| 
 | ||||
| async function getCurrentTab() { | ||||
|     const [tab] = await browserAPI.tabs.query({ | ||||
|         active: true, | ||||
|         windowId: browserAPI.windows.WINDOW_ID_CURRENT, | ||||
|     }); | ||||
|     return tab; | ||||
| // Niezawodne pobieranie zakładki z ponawianiem prób
 | ||||
| async function getCurrentTab(retries = 3, delay = 100): Promise<any> { | ||||
|     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<string | null>(null); | ||||
|     const [memoryReady, setMemoryReady] = React.useState(process.env.TARGET !== 'chrome'); | ||||
|     const [eventCounts] = useEmitter(getMemory()); | ||||
|     const [cookieDomainCopy, setCookieDomainCopy] = React.useState<string | null>(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) => | ||||
|  | ||||
| @ -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)); | ||||
| @ -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 || '']; | ||||
|                 } | ||||
|  | ||||
| @ -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, | ||||
|     }, | ||||
|  | ||||
| @ -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; | ||||
|         }, | ||||
|     }, | ||||
| }; | ||||
| @ -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'; | ||||
| @ -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: ['<all_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)
 | ||||
|  | ||||
							
								
								
									
										35
									
								
								manifest-chrome.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								manifest-chrome.json
									
									
									
									
									
										Normal file
									
								
							| @ -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": [ | ||||
|         "<all_urls>" | ||||
|     ] | ||||
| } | ||||
							
								
								
									
										354
									
								
								memory.ts
									
									
									
									
									
								
							
							
						
						
									
										354
									
								
								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<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) { | ||||
|         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: ['<all_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: ['<all_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<void> { | ||||
|         if (this.isReady) return; | ||||
|         if (this.readyPromise) { | ||||
|             await this.readyPromise; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     async removeCookiesFor(origin: string, shorthost?: string): Promise<void> { | ||||
|         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<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() { | ||||
|     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; | ||||
| } | ||||
							
								
								
									
										775
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										775
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							| @ -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", | ||||
|  | ||||
| @ -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" | ||||
|  | ||||
							
								
								
									
										32
									
								
								scripts/convert-icons.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								scripts/convert-icons.js
									
									
									
									
									
										Normal file
									
								
							| @ -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(); | ||||
							
								
								
									
										45
									
								
								util.ts
									
									
									
									
									
								
							
							
						
						
									
										45
									
								
								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( | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	
Jak to działa? Bo w przeglądarce AFAIK nie mamy dostępu do process.env 🤔
Aaa już widzę, że to jest dodawane, oki