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) { |         if (!origin) { | ||||||
|             return <div>Błąd: brak parametru "origin"</div>; |             return <div>Błąd: brak parametru "origin"</div>; | ||||||
|         } |         } | ||||||
|  |          | ||||||
|  |         // Oczekiwanie na gotowość pamięci Chrome
 | ||||||
|  |         const [memoryReady, setMemoryReady] = React.useState(process.env.TARGET !== 'chrome'); | ||||||
| 
					
					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 [counter] = useEmitter(getMemory()); | ||||||
|         const rawAnswers = url.searchParams.get('answers'); |         const rawAnswers = url.searchParams.get('answers'); | ||||||
|         const [answers, setAnswers] = React.useState<ParsedAnswers>( |         const [answers, setAnswers] = React.useState<ParsedAnswers>( | ||||||
| @ -32,22 +50,32 @@ function Report() { | |||||||
|         const [mode, setMode] = React.useState(url.searchParams.get('mode') || 'survey'); |         const [mode, setMode] = React.useState(url.searchParams.get('mode') || 'survey'); | ||||||
|         const [scrRequestPath, setScrRequestPath] = React.useState(''); |         const [scrRequestPath, setScrRequestPath] = React.useState(''); | ||||||
| 
 | 
 | ||||||
|         const clusters = getMemory().getClustersForOrigin(origin || ''); |         // Pobieranie klastrów tylko gdy pamięć jest gotowa
 | ||||||
|  |         const clusters = memoryReady ? getMemory().getClustersForOrigin(origin || '') : {}; | ||||||
| 
 | 
 | ||||||
|         React.useEffect(() => { |         React.useEffect(() => { | ||||||
|             if (!origin) return; |             if (!origin || !memoryReady) return; | ||||||
|             const url = new URL(document.location.toString()); |             const url = new URL(document.location.toString()); | ||||||
|             url.searchParams.set('origin', origin); |             url.searchParams.set('origin', origin); | ||||||
|             url.searchParams.set('answers', JSON.stringify(answers)); |             url.searchParams.set('answers', JSON.stringify(answers)); | ||||||
|             url.searchParams.set('mode', mode); |             url.searchParams.set('mode', mode); | ||||||
|             history.pushState({}, 'Rentgen', url.toString()); |             history.pushState({}, 'Rentgen', url.toString()); | ||||||
|         }, [mode, answers, origin]); |         }, [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) |         const visited_url = Object.values(clusters) | ||||||
|             .sort((a, b) => (a.lastModified > b.lastModified ? -1 : 1)) |             .sort((a, b) => (a.lastModified > b.lastModified ? -1 : 1)) | ||||||
|             .find((cluster) => !!cluster.lastFullUrl)?.lastFullUrl; |             .find((cluster) => !!cluster.lastFullUrl)?.lastFullUrl; | ||||||
| 
 | 
 | ||||||
|         if (!visited_url) { |         // Jeśli nie znaleziono visited_url, próba skonstruowania z origin
 | ||||||
|             return <div>Wczytywanie...</div>; |         const finalVisitedUrl = visited_url || origin; | ||||||
|  |          | ||||||
|  |         if (!finalVisitedUrl) { | ||||||
|  |             return <div>Błąd: nie można znaleźć adresu strony</div>; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         const result = ( |         const result = ( | ||||||
| @ -55,7 +83,7 @@ function Report() { | |||||||
|                 {mode === 'survey' ? ( |                 {mode === 'survey' ? ( | ||||||
|                     <Questions |                     <Questions | ||||||
|                         clusters={Object.values(clusters).filter( |                         clusters={Object.values(clusters).filter( | ||||||
|                             (cluster) => cluster.getMarkedRequests().length > 0 |                             (cluster) => cluster.hasMarks() | ||||||
|                         )} |                         )} | ||||||
|                         onComplete={(answers) => { |                         onComplete={(answers) => { | ||||||
|                             setAnswers(parseAnswers(answers)); |                             setAnswers(parseAnswers(answers)); | ||||||
| @ -68,11 +96,11 @@ function Report() { | |||||||
|                 {mode === 'screenshots' ? ( |                 {mode === 'screenshots' ? ( | ||||||
|                     <ScreenshotGenerator |                     <ScreenshotGenerator | ||||||
|                         {...{ |                         {...{ | ||||||
|                             visited_url, |                             visited_url: finalVisitedUrl, | ||||||
|                             clusters, |                             clusters, | ||||||
|                             setReportWindowMode: setMode, |                             setReportWindowMode: setMode, | ||||||
|                             setRequestPath: setScrRequestPath, |                             setRequestPath: setScrRequestPath, | ||||||
|                             downloadFiles: downloadFiles, |                             downloadFiles, | ||||||
|                             user_role: answers.user_role, |                             user_role: answers.user_role, | ||||||
|                         }} |                         }} | ||||||
|                     /> |                     /> | ||||||
| @ -83,10 +111,10 @@ function Report() { | |||||||
|                     <EmailContent |                     <EmailContent | ||||||
|                         {...{ |                         {...{ | ||||||
|                             answers, |                             answers, | ||||||
|                             visited_url, |                             visited_url: finalVisitedUrl, | ||||||
|                             clusters, |                             clusters, | ||||||
|                             scrRequestPath, |                             scrRequestPath, | ||||||
|                             downloadFiles: downloadFiles, |                             downloadFiles, | ||||||
|                             user_role: answers.user_role, |                             user_role: answers.user_role, | ||||||
|                         }} |                         }} | ||||||
|                     /> |                     /> | ||||||
| @ -95,28 +123,19 @@ function Report() { | |||||||
|                 )} |                 )} | ||||||
|             </div> |             </div> | ||||||
|         ); |         ); | ||||||
|         return ( |         return result; | ||||||
|             <Fragment> |  | ||||||
|                 <header className="header"> |  | ||||||
|                     <img src="../../assets/icon-addon.svg" height={32}></img> |  | ||||||
|                     <div className="webpage-metadata"> |  | ||||||
|                         {origin ? ( |  | ||||||
|                             <> |  | ||||||
|                                 <span>Generowanie raportu </span> |  | ||||||
|                                 <span className="webpage-metadata--hyperlink">{origin}</span> |  | ||||||
|                             </> |  | ||||||
|                         ) : ( |  | ||||||
|                             <span>Przejdź do wybranej strony internetowej</span> |  | ||||||
|                         )} |  | ||||||
|                     </div> |  | ||||||
|                 </header> |  | ||||||
|                 <section id="main-section">{result}</section> |  | ||||||
|             </Fragment> |  | ||||||
|         ); |  | ||||||
|     } catch (e) { |     } catch (e) { | ||||||
|         console.error(e); |         console.error(e); | ||||||
|         return <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 url = new URL(document.location.toString()); | ||||||
|     const origin = url.searchParams.get('origin'); |     const origin = url.searchParams.get('origin'); | ||||||
| 
 | 
 | ||||||
|  |     // Chrome: Oczekiwanie na gotowość pamięci
 | ||||||
|  |     const [memoryReady, setMemoryReady] = React.useState(process.env.TARGET !== 'chrome'); | ||||||
|     const [minValueLength, setMinValueLength] = React.useState<number | null>( |     const [minValueLength, setMinValueLength] = React.useState<number | null>( | ||||||
|         localStorage.getItem('minValueLength') === null |         localStorage.getItem('minValueLength') === null | ||||||
|             ? 7 |             ? 7 | ||||||
| @ -44,8 +46,24 @@ const Sidebar = () => { | |||||||
|             : false |             : false | ||||||
|     ); |     ); | ||||||
| 
 | 
 | ||||||
|  |     // Oczekiwanie na gotowość pamięci Chrome
 | ||||||
|     React.useEffect(() => { |     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))) { |         for (const cluster of Object.values(getMemory().getClustersForOrigin(origin))) { | ||||||
|             if (cluster.hasMarks()) { |             if (cluster.hasMarks()) { | ||||||
|                 return setMarksOccurrence(true); |                 return setMarksOccurrence(true); | ||||||
| @ -53,9 +71,24 @@ const Sidebar = () => { | |||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         return setMarksOccurrence(false); |         return setMarksOccurrence(false); | ||||||
|     }, [eventCounts['*']]); |     }, [eventCounts['*'], memoryReady]); | ||||||
| 
 | 
 | ||||||
|     if (!origin) return <div>Błąd: Brak parametru "origin"</div>; |     if (!origin) return <div>Błąd: Brak parametru "origin"</div>; | ||||||
|  |      | ||||||
|  |     // Wyświetlanie stanu wczytywania dla Chrome
 | ||||||
|  |     if (!memoryReady) { | ||||||
|  |         return ( | ||||||
|  |             <div className="sidebar"> | ||||||
|  |                 <header className="header"> | ||||||
|  |                     <img src="../../assets/icon-addon.svg" height={32}></img> | ||||||
|  |                     <div className="webpage-metadata"> | ||||||
|  |                         <span>Ładowanie danych...</span> | ||||||
|  |                     </div> | ||||||
|  |                 </header> | ||||||
|  |             </div> | ||||||
|  |         ); | ||||||
|  |     } | ||||||
|  |      | ||||||
|     return ( |     return ( | ||||||
|         <div className="sidebar"> |         <div className="sidebar"> | ||||||
|             <header className="header"> |             <header className="header"> | ||||||
| @ -121,12 +154,12 @@ const Sidebar = () => { | |||||||
|                     </button> |                     </button> | ||||||
| 
 | 
 | ||||||
|                     {localStorage.getItem('blottingBrowser') === |                     {localStorage.getItem('blottingBrowser') === | ||||||
|                     'nikttakniesplamitwojejprzeglądarkijakspidersweb' ? ( |                     'nikttakniesplamitwojejprzeglądarki jakspidersweb' ? ( | ||||||
|                         <button |                         <button | ||||||
|                             onClick={() => { |                             onClick={() => { | ||||||
|                                 if ( |                                 if ( | ||||||
|                                     window.confirm( |                                     window.confirm( | ||||||
|                                         'Czy chcesz wczytać wszystkie domeny w celu „splamienia” twojej przeglądarki? Uwaga przeglądarka może zablokować otwieranie nowych kart. (Ten krok jest opcjonalny)' |                                         'Czy chcesz wczytać wszystkie domeny w celu „splamienia" twojej przeglądarki? Uwaga przeglądarka może zablokować otwieranie nowych kart. (Ten krok jest opcjonalny)' | ||||||
|                                     ) |                                     ) | ||||||
|                                 ) { |                                 ) { | ||||||
|                                     let deep_copy = JSON.parse( |                                     let deep_copy = JSON.parse( | ||||||
| @ -178,8 +211,8 @@ const Sidebar = () => { | |||||||
|                             <section className="dialog-container dialog-container--warning"> |                             <section className="dialog-container dialog-container--warning"> | ||||||
|                                 <span> |                                 <span> | ||||||
|                                     <strong>Uwaga!</strong> Niekoniecznie każda przesłana poniżej |                                     <strong>Uwaga!</strong> Niekoniecznie każda przesłana poniżej | ||||||
|                                     informacja jest daną osobową. Niektóre z podanych domen mogą |                                     informacja jest daną osobową. Niektóre z podanych domen mogą | ||||||
|                                     należeć do właściciela strony i nie reprezentować podmiotów |                                     należeć do właściciela strony i nie reprezentować podmiotów | ||||||
|                                     trzecich. |                                     trzecich. | ||||||
|                                 </span> |                                 </span> | ||||||
|                                 <button |                                 <button | ||||||
| @ -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({ | export default function StolenDataCluster({ | ||||||
|     origin, |     origin, | ||||||
|     shorthost, |     shorthost, | ||||||
|  |     refreshToken, | ||||||
|     minValueLength, |     minValueLength, | ||||||
|     cookiesOnly, |     cookiesOnly, | ||||||
|     cookiesOrOriginOnly, |     cookiesOrOriginOnly, | ||||||
| @ -109,6 +110,7 @@ export default function StolenDataCluster({ | |||||||
| }: { | }: { | ||||||
|     origin: string; |     origin: string; | ||||||
|     shorthost: string; |     shorthost: string; | ||||||
|  |     refreshToken: number; | ||||||
|     minValueLength: number; |     minValueLength: number; | ||||||
|     cookiesOnly: boolean; |     cookiesOnly: boolean; | ||||||
|     cookiesOrOriginOnly: boolean; |     cookiesOrOriginOnly: boolean; | ||||||
|  | |||||||
| @ -7,6 +7,10 @@ | |||||||
|         rel="stylesheet" |         rel="stylesheet" | ||||||
|         href="/lib/styles/global.css" |         href="/lib/styles/global.css" | ||||||
|     > |     > | ||||||
|  |     <link | ||||||
|  |         rel="stylesheet" | ||||||
|  |         href="/lib/styles/fonts.css" | ||||||
|  |     > | ||||||
|     <link |     <link | ||||||
|         rel="stylesheet" |         rel="stylesheet" | ||||||
|         href="/lib/components/toolbar/toolbar.css" |         href="/lib/components/toolbar/toolbar.css" | ||||||
|  | |||||||
| @ -4,12 +4,40 @@ import { getMemory } from '../../memory'; | |||||||
| import { useEmitter, getshorthost } from '../../util'; | import { useEmitter, getshorthost } from '../../util'; | ||||||
| import browserAPI from '../../lib/browser-api'; | import browserAPI from '../../lib/browser-api'; | ||||||
| 
 | 
 | ||||||
| async function getCurrentTab() { | // Niezawodne pobieranie zakładki z ponawianiem prób
 | ||||||
|     const [tab] = await browserAPI.tabs.query({ | async function getCurrentTab(retries = 3, delay = 100): Promise<any> { | ||||||
|         active: true, |     for (let i = 0; i < retries; i++) { | ||||||
|         windowId: browserAPI.windows.WINDOW_ID_CURRENT, |         try { | ||||||
|     }); |             // Metoda 1: Zapytanie o aktywną zakładkę
 | ||||||
|     return tab; |             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) { | function isDomainHighlySuspicious(domain: string) { | ||||||
| @ -23,6 +51,7 @@ function isDomainHighlySuspicious(domain: string) { | |||||||
| 
 | 
 | ||||||
| const Toolbar = () => { | const Toolbar = () => { | ||||||
|     const [origin, setOrigin] = React.useState<string | null>(null); |     const [origin, setOrigin] = React.useState<string | null>(null); | ||||||
|  |     const [memoryReady, setMemoryReady] = React.useState(process.env.TARGET !== 'chrome'); | ||||||
|     const [eventCounts] = useEmitter(getMemory()); |     const [eventCounts] = useEmitter(getMemory()); | ||||||
|     const [cookieDomainCopy, setCookieDomainCopy] = React.useState<string | null>(null); |     const [cookieDomainCopy, setCookieDomainCopy] = React.useState<string | null>(null); | ||||||
|     const [_, setMarksOccurrence] = React.useState(false); |     const [_, setMarksOccurrence] = React.useState(false); | ||||||
| @ -35,28 +64,74 @@ const Toolbar = () => { | |||||||
|     const first_sentence_history = |     const first_sentence_history = | ||||||
|         'Część informacji o Twojej historii przeglądania została wysłana do '; |         'Część informacji o Twojej historii przeglądania została wysłana do '; | ||||||
| 
 | 
 | ||||||
|  |     // Oczekiwanie na gotowość pamięci Chrome
 | ||||||
|     React.useEffect(() => { |     React.useEffect(() => { | ||||||
|         const listener = async () => { |         if (process.env.TARGET === 'chrome') { | ||||||
|             const tab = await getCurrentTab(); |             const memory = getMemory(); | ||||||
|             if (tab !== undefined && tab.url) { |             if (typeof (memory as any).waitUntilReady === 'function') { | ||||||
|                 const url = new URL(tab.url); |                 (memory as any).waitUntilReady().then(() => { | ||||||
|                 if (url.origin.startsWith('moz-extension')) { |                     setMemoryReady(true); | ||||||
|                     return; |                     console.log('✅ Memory gotowa, popup może wyświetlać dane'); | ||||||
|                 } |                 }); | ||||||
|                 setOrigin(url.origin); |  | ||||||
|             } else { |             } else { | ||||||
|                 console.warn('Out of the tab scope'); |                 setMemoryReady(true); | ||||||
|             } |             } | ||||||
|         }; |         } | ||||||
|         browserAPI.tabs.onUpdated.addListener(listener); |     }, []); | ||||||
|         listener(); |  | ||||||
|         return () => { |  | ||||||
|             browserAPI.tabs.onUpdated.removeListener(listener); |  | ||||||
|         }; |  | ||||||
|     }); |  | ||||||
| 
 | 
 | ||||||
|     React.useEffect(() => { |     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)) |         const exposedOriginDomains = Object.values(getMemory().getClustersForOrigin(origin)) | ||||||
|             .filter((cluster) => cluster.exposesOrigin()) |             .filter((cluster) => cluster.exposesOrigin()) | ||||||
| @ -95,10 +170,10 @@ const Toolbar = () => { | |||||||
|                 ); |                 ); | ||||||
|                 break; |                 break; | ||||||
|         } |         } | ||||||
|     }, [eventCounts['*'], origin]); |     }, [eventCounts['*'], origin, memoryReady]); | ||||||
| 
 | 
 | ||||||
|     React.useEffect(() => { |     React.useEffect(() => { | ||||||
|         if (!origin) return; |         if (!origin || !memoryReady) return; | ||||||
| 
 | 
 | ||||||
|         const cookieDomains = Object.values(getMemory().getClustersForOrigin(origin)) |         const cookieDomains = Object.values(getMemory().getClustersForOrigin(origin)) | ||||||
|             .filter((cluster) => cluster.hasCookies()) |             .filter((cluster) => cluster.hasCookies()) | ||||||
| @ -135,7 +210,7 @@ const Toolbar = () => { | |||||||
|                 ); |                 ); | ||||||
|                 break; |                 break; | ||||||
|         } |         } | ||||||
|     }, [eventCounts['*'], origin]); |     }, [eventCounts['*'], origin, memoryReady]); | ||||||
| 
 | 
 | ||||||
|     const autoMark = () => { |     const autoMark = () => { | ||||||
|         Object.values(getMemory().getClustersForOrigin(origin || '')).forEach((cluster) => |         Object.values(getMemory().getClustersForOrigin(origin || '')).forEach((cluster) => | ||||||
|  | |||||||
| @ -1,68 +1,179 @@ | |||||||
| import esbuild from 'esbuild'; | import esbuild from 'esbuild'; | ||||||
| import scss from 'esbuild-plugin-sass'; | import scss from 'esbuild-plugin-sass'; | ||||||
| 
 | import { copyFileSync, mkdirSync, readdirSync, existsSync } from 'fs'; | ||||||
| const watch = process.argv.includes('--watch') && { | import { join, dirname } from 'path'; | ||||||
|     onRebuild(error) { | 
 | ||||||
|         if (error) console.error('[watch] build failed', error); | // Określenie platformy docelowej: firefox (domyślnie) lub chrome
 | ||||||
|         else console.log('[watch] build finished'); | const TARGET = process.env.TARGET || 'firefox'; | ||||||
|     }, | const IS_FIREFOX = TARGET === 'firefox'; | ||||||
| }; | const IS_CHROME = TARGET === 'chrome'; | ||||||
| 
 | 
 | ||||||
| // see https://github.com/evanw/esbuild/issues/806#issuecomment-779138268
 | // Katalogi wyjściowe
 | ||||||
| let skipReactImports = { | const DIST_DIR = IS_FIREFOX ? './dist-firefox' : './dist-chrome'; | ||||||
|     name: 'skipReactImports', | const LIB_DIR = join(DIST_DIR, 'lib'); | ||||||
|     setup(build) { | 
 | ||||||
|         build.onResolve({ filter: /^(react(-dom)?|survey-react)$/ }, (args) => { | console.log(`🎯 Budowanie dla: ${TARGET.toUpperCase()}`); | ||||||
|             return { | console.log(`📁 Katalog wyjściowy: ${DIST_DIR}`); | ||||||
|                 path: args.path, | 
 | ||||||
|                 namespace: `globalExternal_${args.path}`, | 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'); | ||||||
|         build.onLoad({ filter: /.*/, namespace: 'globalExternal_react' }, () => { |     }, | ||||||
|             return { | }; | ||||||
|                 contents: `module.exports = globalThis.React`, | 
 | ||||||
|                 loader: 'js', | // Funkcja pomocnicza: rekurencyjne kopiowanie katalogów
 | ||||||
|             }; | function copyDir(src, dest) { | ||||||
|         }); |     if (!existsSync(dest)) { | ||||||
| 
 |         mkdirSync(dest, { recursive: true }); | ||||||
|         build.onLoad({ filter: /.*/, namespace: 'globalExternal_react-dom' }, () => { |     } | ||||||
|             return { |      | ||||||
|                 contents: `module.exports = globalThis.ReactDOM`, |     const entries = readdirSync(src, { withFileTypes: true }); | ||||||
|                 loader: 'js', |      | ||||||
|             }; |     for (const entry of entries) { | ||||||
|         }); |         const srcPath = join(src, entry.name); | ||||||
|         build.onLoad({ filter: /.*/, namespace: 'globalExternal_survey-react' }, () => { |         const destPath = join(dest, entry.name); | ||||||
|             return { |          | ||||||
|                 contents: `module.exports = globalThis.Survey`, |         if (entry.isDirectory()) { | ||||||
|                 loader: 'js', |             copyDir(srcPath, destPath); | ||||||
|             }; |         } else { | ||||||
|         }); |             copyFileSync(srcPath, destPath); | ||||||
|     }, |         } | ||||||
| }; |     } | ||||||
| 
 | } | ||||||
| esbuild | 
 | ||||||
|     .build({ | // Plugin: kopiowanie plików statycznych po zakończeniu budowania
 | ||||||
|         entryPoints: [ | const copyStaticFiles = { | ||||||
|             'components/toolbar/toolbar.tsx', |     name: 'copy-static-files', | ||||||
|             'components/sidebar/sidebar.tsx', |     setup(build) { | ||||||
|             'components/report-window/report-window.tsx', |         build.onEnd(() => { | ||||||
|             'background.ts', |             console.log('📋 Kopiowanie plików statycznych...'); | ||||||
|             'diag.tsx', |              | ||||||
|             'styles/global.scss', |             // Kopiowanie manifestu (wybór na podstawie platformy docelowej)
 | ||||||
|             'styles/fonts.scss', |             const manifestSrc = IS_FIREFOX ? './manifest.json' : './manifest-chrome.json'; | ||||||
|         ], |             const manifestDest = join(DIST_DIR, 'manifest.json'); | ||||||
|         bundle: true, |             mkdirSync(dirname(manifestDest), { recursive: true }); | ||||||
|         // minify: true,
 |             copyFileSync(manifestSrc, manifestDest); | ||||||
|         outdir: './lib', |             console.log(`   ✓ Skopiowano ${manifestSrc} → ${manifestDest}`); | ||||||
|         loader: { '.woff': 'file', '.woff2': 'file' }, |              | ||||||
|         plugins: [scss(), skipReactImports], |             // Kopiowanie katalogu components
 | ||||||
|         define: { |             if (existsSync('./components')) { | ||||||
|             PLUGIN_NAME: '"Rentgen"', |                 copyDir('./components', join(DIST_DIR, 'components')); | ||||||
|             PLUGIN_URL: '"https://addons.mozilla.org/pl/firefox/addon/rentgen/"', |                 console.log('   ✓ Skopiowano components/'); | ||||||
|         }, |             } | ||||||
|         external: ['react', 'react-dom', 'survey-react'], |              | ||||||
|         watch, |             // Kopiowanie katalogu assets
 | ||||||
|     }) |             if (existsSync('./assets')) { | ||||||
|     .then(() => console.log('Add-on was built')) |                 copyDir('./assets', join(DIST_DIR, 'assets')); | ||||||
|     .catch(() => process.exit(1)); |                 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, |     safeDecodeURIComponent, | ||||||
| } from './util'; | } from './util'; | ||||||
| 
 | 
 | ||||||
|  | /** | ||||||
|  |  * Bezpieczna konwersja Uint8Array na string, obsługująca duże tablice | ||||||
|  |  * które mogłyby spowodować stack overflow przy użyciu String.fromCharCode.apply() | ||||||
|  |  */ | ||||||
|  | function uint8ArrayToString(bytes: Uint8Array): string { | ||||||
|  |     const CHUNK_SIZE = 8192; // Przetwarzanie w kawałkach 8KB aby uniknąć stack overflow
 | ||||||
|  |      | ||||||
|  |     if (bytes.length <= CHUNK_SIZE) { | ||||||
|  |         // Mała tablica - używamy szybkiej metody
 | ||||||
|  |         return String.fromCharCode.apply(null, Array.from(bytes) as any); | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     // Duża tablica - przetwarzamy w kawałkach
 | ||||||
|  |     let result = ''; | ||||||
|  |     for (let i = 0; i < bytes.length; i += CHUNK_SIZE) { | ||||||
|  |         const chunk = bytes.subarray(i, i + CHUNK_SIZE); | ||||||
|  |         result += String.fromCharCode.apply(null, Array.from(chunk) as any); | ||||||
|  |     } | ||||||
|  |     return result; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| type NameValue = { name: string; value: string }; | type NameValue = { name: string; value: string }; | ||||||
| 
 | 
 | ||||||
| export type HAREntry = { | export type HAREntry = { | ||||||
| @ -62,7 +83,7 @@ const whitelisted_cookies = [ | |||||||
|     /^Connection$/, |     /^Connection$/, | ||||||
|     /^Sec-Fetch-.*$/, |     /^Sec-Fetch-.*$/, | ||||||
|     /^Content-Type$/, |     /^Content-Type$/, | ||||||
|     /^Cookie$/, // we're extracting it in getCookie separately anyway
 |     /^Cookie$/, // wyodrębniamy to w getCookie() osobno
 | ||||||
|     /^User-Agent$/, |     /^User-Agent$/, | ||||||
| ]; | ]; | ||||||
| 
 | 
 | ||||||
| @ -80,8 +101,8 @@ export default class ExtendedRequest { | |||||||
|     public origin: string; |     public origin: string; | ||||||
|     public initialized = false; |     public initialized = false; | ||||||
|     public stolenData: StolenDataEntry[] = []; |     public stolenData: StolenDataEntry[] = []; | ||||||
|     public originalURL: string | null = null; // 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 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; // same as above
 |     public originalPathname: string | null = null; // tak samo jak powyżej
 | ||||||
|     public originalHost: string; |     public originalHost: string; | ||||||
|     public requestBody: RequestBody; |     public requestBody: RequestBody; | ||||||
| 
 | 
 | ||||||
| @ -91,20 +112,45 @@ export default class ExtendedRequest { | |||||||
|     constructor(data: Request) { |     constructor(data: Request) { | ||||||
|         this.tabId = data.tabId; |         this.tabId = data.tabId; | ||||||
|         this.url = data.url; |         this.url = data.url; | ||||||
|         this.shorthost = getshorthost(data.url); |  | ||||||
|         this.requestBody = ((data as any).requestBody as undefined | RequestBody) || {}; |         this.requestBody = ((data as any).requestBody as undefined | RequestBody) || {}; | ||||||
|         ExtendedRequest.by_id[data.requestId] = this; |  | ||||||
| 
 |  | ||||||
|         this.data = Object.assign({}, data); |         this.data = Object.assign({}, data); | ||||||
|         (this.data as any).frameAncestors = [ |         (this.data as any).frameAncestors = [ | ||||||
|             ...((data as any)?.frameAncestors?.map((e: any) => ({ url: e.url })) || []), |             ...((data as any)?.frameAncestors?.map((e: any) => ({ url: e.url })) || []), | ||||||
|         ]; // making a copy?
 |         ]; | ||||||
| 
 | 
 | ||||||
|         // 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 url: string; | ||||||
|         let is_full_url = true; |         let is_full_url = true; | ||||||
|         let url_comes_from: string; |         let url_comes_from: string; | ||||||
|  | 
 | ||||||
|         if (this.data.type === 'main_frame') { |         if (this.data.type === 'main_frame') { | ||||||
|             url = this.data.url; |             url = this.data.url; | ||||||
|             url_comes_from = 'main_frame'; |             url_comes_from = 'main_frame'; | ||||||
| @ -112,7 +158,6 @@ export default class ExtendedRequest { | |||||||
|             url = this.data.documentUrl; |             url = this.data.documentUrl; | ||||||
|             url_comes_from = 'documentUrl'; |             url_comes_from = 'documentUrl'; | ||||||
|             if (this.data.tabId == -1) { |             if (this.data.tabId == -1) { | ||||||
|                 //a service worker?
 |  | ||||||
|                 url_comes_from = 'documentUrl (webworker)'; |                 url_comes_from = 'documentUrl (webworker)'; | ||||||
|                 is_full_url = false; |                 is_full_url = false; | ||||||
|             } |             } | ||||||
| @ -122,16 +167,58 @@ export default class ExtendedRequest { | |||||||
|         ) { |         ) { | ||||||
|             url = (this.data as any).frameAncestors.at(-1).url || ''; |             url = (this.data as any).frameAncestors.at(-1).url || ''; | ||||||
|             url_comes_from = 'frameAncestors'; |             url_comes_from = 'frameAncestors'; | ||||||
|  |         } else if (process.env.TARGET === 'chrome' && (this.data as any).initiator) { | ||||||
|  |             // Chrome MV3: Używamy właściwości initiator
 | ||||||
|  |             url = (this.data as any).initiator; | ||||||
|  |             url_comes_from = 'initiator (Chrome MV3)'; | ||||||
|  |             is_full_url = false; | ||||||
|         } else { |         } else { | ||||||
|             url = this.data.documentUrl || this.data.originUrl; |             url = this.data.documentUrl || this.data.originUrl; | ||||||
|             url_comes_from = 'last resort'; |             url_comes_from = 'ostatnia deska ratunku'; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         this.originalURL = is_full_url ? url : null; |         // Próba parsowania URLi w kolejności preferencji
 | ||||||
|         this.origin = new URL(url).origin; |         const urlsToTry = [ | ||||||
|  |             url, | ||||||
|  |             this.data.documentUrl, | ||||||
|  |             this.data.originUrl, | ||||||
|  |             this.data.url | ||||||
|  |         ].filter(Boolean); | ||||||
| 
 | 
 | ||||||
|         this.originalHost = new URL(url).host; |         let parsedUrl: { origin: string; host: string; pathname: string } | null = null; | ||||||
|         this.originalPathname = is_full_url ? new URL(url).pathname : null; | 
 | ||||||
|  |         for (const urlToTry of urlsToTry) { | ||||||
|  |             parsedUrl = safeParseUrl(urlToTry as string); | ||||||
|  |             if (parsedUrl) { | ||||||
|  |                 url = urlToTry as string; | ||||||
|  |                 break; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // Ustawienie właściwości z bezpiecznymi wartościami domyślnymi
 | ||||||
|  |         if (parsedUrl) { | ||||||
|  |             // Pomyślnie sparsowano
 | ||||||
|  |             this.originalURL = is_full_url ? url : null; | ||||||
|  |             this.origin = parsedUrl.origin; | ||||||
|  |             this.originalHost = parsedUrl.host; | ||||||
|  |             this.originalPathname = is_full_url ? parsedUrl.pathname : null; | ||||||
|  |              | ||||||
|  |             // Bezpieczne ustawienie shorthost
 | ||||||
|  |             try { | ||||||
|  |                 this.shorthost = getshorthost(parsedUrl.host); | ||||||
|  |             } catch (error) { | ||||||
|  |                 console.warn('Nie udało się uzyskać shorthost:', parsedUrl.host, error); | ||||||
|  |                 this.shorthost = parsedUrl.host; | ||||||
|  |             } | ||||||
|  |         } else { | ||||||
|  |             // Nie udało się sparsować - używamy bezpiecznych wartości domyślnych
 | ||||||
|  |             // Te żądania zostaną odfiltrowane przez isThirdParty() później
 | ||||||
|  |             this.originalURL = null; | ||||||
|  |             this.origin = 'unknown://unknown'; | ||||||
|  |             this.originalHost = 'unknown'; | ||||||
|  |             this.originalPathname = '/'; | ||||||
|  |             this.shorthost = 'unknown'; | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     addHeaders(headers: Request['requestHeaders']) { |     addHeaders(headers: Request['requestHeaders']) { | ||||||
| @ -145,17 +232,30 @@ export default class ExtendedRequest { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     isThirdParty() { |     isThirdParty() { | ||||||
|         const request_url = new URL(this.data.url); |         // Pomijanie żądań z nieznanym origin (nieparsowalny URL)
 | ||||||
|         if (request_url.host.includes(this.originalHost)) { |         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; |             return false; | ||||||
|         } |         } | ||||||
|         if (getshorthost(request_url.host) == getshorthost(this.originalHost)) { |  | ||||||
|             return false; |  | ||||||
|         } |  | ||||||
|         return ( |  | ||||||
|             request_url.origin != this.origin || |  | ||||||
|             (this.data as any).urlClassification.thirdParty.length > 0 |  | ||||||
|         ); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     getReferer() { |     getReferer() { | ||||||
| @ -218,14 +318,19 @@ export default class ExtendedRequest { | |||||||
|                     ]) |                     ]) | ||||||
|                 ), |                 ), | ||||||
|             }).map(([key, value]) => { |             }).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) { |                 if ((Array.isArray(value) && value.length === 1 && !value[0]) || !value) { | ||||||
|                     return ['requestBody', key]; |                     return ['requestBody', key]; | ||||||
|                 } else if (!Array.isArray(value)) { |                 } else if (!Array.isArray(value)) { | ||||||
|                     return [ |                     // POPRAWKA: Używamy bezpiecznej konwersji w kawałkach zamiast apply()
 | ||||||
|                         'raw', |                     try { | ||||||
|                         String.fromCharCode.apply(null, Array.from(new Uint8Array(value.bytes))), |                         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 { |                 } else { | ||||||
|                     return [key, value || '']; |                     return [key, value || '']; | ||||||
|                 } |                 } | ||||||
|  | |||||||
| @ -14,40 +14,63 @@ export const chromeAPI: BrowserAPI = { | |||||||
|     tabs: { |     tabs: { | ||||||
|         query: chrome.tabs.query, |         query: chrome.tabs.query, | ||||||
|         onUpdated: { |         onUpdated: { | ||||||
|             addListener: chrome.tabs.onUpdated.addListener, |             addListener: chrome.tabs.onUpdated.addListener.bind(chrome.tabs.onUpdated), | ||||||
|             removeListener: chrome.tabs.onUpdated.removeListener, |             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: { |     badge: { | ||||||
|         setBadgeText: chrome.action.setBadgeText, |         setBadgeText: (details: any) => { | ||||||
|         setTitle: chrome.action.setTitle, |             try { | ||||||
|         setBadgeBackgroundColor: chrome.action.setBadgeBackgroundColor, |                 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 API - chrome.webRequest.* → webRequest.*
 | ||||||
|     webRequest: { |     webRequest: { | ||||||
|         onBeforeRequest: { |         onBeforeRequest: { | ||||||
|             addListener: chrome.webRequest.onBeforeRequest.addListener, |             addListener: chrome.webRequest.onBeforeRequest.addListener.bind(chrome.webRequest.onBeforeRequest), | ||||||
|         }, |         }, | ||||||
|         onBeforeSendHeaders: { |         onBeforeSendHeaders: { | ||||||
|             addListener: chrome.webRequest.onBeforeSendHeaders.addListener, |             addListener: chrome.webRequest.onBeforeSendHeaders.addListener.bind(chrome.webRequest.onBeforeSendHeaders), | ||||||
|         }, |         }, | ||||||
|     }, |     }, | ||||||
| 
 | 
 | ||||||
|     // Cookies API - chrome.cookies.* → cookies.*
 |     // Cookies API - chrome.cookies.* → cookies.*
 | ||||||
|     cookies: { |     cookies: { | ||||||
|         getAll: chrome.cookies.getAll, |         getAll: chrome.cookies.getAll.bind(chrome.cookies), | ||||||
|         remove: chrome.cookies.remove, |         remove: chrome.cookies.remove.bind(chrome.cookies), | ||||||
|     }, |     }, | ||||||
| 
 | 
 | ||||||
|     // Extension API - chrome.extension.* → extension.*
 |     // Extension API - chrome.extension.* → 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: { |     windows: { | ||||||
|         WINDOW_ID_CURRENT: chrome.windows.WINDOW_ID_CURRENT, |         WINDOW_ID_CURRENT: chrome.windows.WINDOW_ID_CURRENT, | ||||||
|     }, |     }, | ||||||
|  | |||||||
| @ -6,49 +6,102 @@ | |||||||
| 
 | 
 | ||||||
| import type { BrowserAPI } from './types'; | import type { BrowserAPI } from './types'; | ||||||
| 
 | 
 | ||||||
| // Firefox używa globalnego obiektu `browser`
 | // Bezpieczny dostęp do globalnego obiektu browser - sprawdzamy czy istnieje w runtime
 | ||||||
| declare const browser: any; | 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 = { | export const firefoxAPI: BrowserAPI = { | ||||||
|     // Tabs API - direct mapping
 |     // Tabs API - leniwy dostęp z odpowiednimi typami zwracanymi
 | ||||||
|     tabs: { |     tabs: { | ||||||
|         query: browser.tabs.query, |         query: (queryInfo: any) => { | ||||||
|  |             const b = getBrowser(); | ||||||
|  |             if (b) { | ||||||
|  |                 return b.tabs.query(queryInfo); | ||||||
|  |             } | ||||||
|  |             return Promise.resolve([]); | ||||||
|  |         }, | ||||||
|         onUpdated: { |         onUpdated: { | ||||||
|             addListener: browser.tabs.onUpdated.addListener, |             addListener: (listener: any) => { | ||||||
|             removeListener: browser.tabs.onUpdated.removeListener, |                 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: { |     badge: { | ||||||
|         setBadgeText: browser.browserAction.setBadgeText, |         setBadgeText: (details: any) => { | ||||||
|         setTitle: browser.browserAction.setTitle, |             getBrowser()?.browserAction.setBadgeText(details); | ||||||
|         setBadgeBackgroundColor: browser.browserAction.setBadgeBackgroundColor, |         }, | ||||||
|  |         setTitle: (details: any) => { | ||||||
|  |             getBrowser()?.browserAction.setTitle(details); | ||||||
|  |         }, | ||||||
|  |         setBadgeBackgroundColor: (details: any) => { | ||||||
|  |             getBrowser()?.browserAction.setBadgeBackgroundColor(details); | ||||||
|  |         }, | ||||||
|     }, |     }, | ||||||
| 
 | 
 | ||||||
|     // WebRequest API - direct mapping
 |     // WebRequest API - leniwy dostęp
 | ||||||
|     webRequest: { |     webRequest: { | ||||||
|         onBeforeRequest: { |         onBeforeRequest: { | ||||||
|             addListener: browser.webRequest.onBeforeRequest.addListener, |             addListener: (listener: any, filter: any, extraInfoSpec?: any) => { | ||||||
|  |                 getBrowser()?.webRequest.onBeforeRequest.addListener(listener, filter, extraInfoSpec); | ||||||
|  |             }, | ||||||
|         }, |         }, | ||||||
|         onBeforeSendHeaders: { |         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: { |     cookies: { | ||||||
|         getAll: browser.cookies.getAll, |         getAll: (details: any) => { | ||||||
|         remove: browser.cookies.remove, |             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: { |     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: { |     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 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; | let browserApi: BrowserAPI; | ||||||
| 
 | 
 | ||||||
| // TARGET jest ustawiane przez esbuild.config.js na podstawie npm script
 | // TARGET jest ustawiane przez esbuild.config.js na podstawie npm script
 | ||||||
| if (process.env.TARGET === 'chrome') { | if (process.env.TARGET === 'chrome') { | ||||||
|     // Chrome build - używamy chrome adapter
 |     // Build dla Chrome - używamy adaptera Chrome
 | ||||||
|     const { chromeAPI } = require('./chrome'); |  | ||||||
|     browserApi = chromeAPI; |     browserApi = chromeAPI; | ||||||
| } else { | } else { | ||||||
|     // Firefox build (default) - używamy firefox adapter  
 |     // Build dla Firefox (domyślny) - używamy adaptera Firefox
 | ||||||
|     const { firefoxAPI } = require('./firefox'); |  | ||||||
|     browserApi = firefoxAPI; |     browserApi = firefoxAPI; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Eksportuj jako default export
 | // Eksport jako default export
 | ||||||
| export default browserApi; | export default browserApi; | ||||||
| 
 | 
 | ||||||
| // Re-export typów dla wygody
 | // Re-eksport typów dla wygody
 | ||||||
| export * from './types'; | export * from './types'; | ||||||
| @ -1,14 +1,8 @@ | |||||||
| /** | /** | ||||||
|  * Browser API Abstraction - Typy na podstawie faktycznego użycia w kodzie |  * Browser API Abstraction - Typy na podstawie faktycznego użycia w kodzie | ||||||
|  *  |  | ||||||
|  * Przeanalizowane pliki: |  | ||||||
|  * - util.ts: tabs.query, Tab.id |  | ||||||
|  * - tab-dropdown.tsx: tabs.query, Tab.id, Tab.title   |  | ||||||
|  * - toolbar.tsx: tabs.query, tabs.onUpdated, Tab.url, windows.WINDOW_ID_CURRENT |  | ||||||
|  * - memory.ts: browserAction.*, webRequest.*, cookies.*, extension.* |  | ||||||
|  */ |  */ | ||||||
| 
 | 
 | ||||||
| // Import full Request type from util.ts
 | // Import pełnego typu Request z util.ts
 | ||||||
| export type Request = { | export type Request = { | ||||||
|     cookieStoreId?: string; |     cookieStoreId?: string; | ||||||
|     documentUrl?: string; |     documentUrl?: string; | ||||||
| @ -35,57 +29,58 @@ export type Request = { | |||||||
|     urlClassification?: { firstParty: string[]; thirdParty: string[] }; |     urlClassification?: { firstParty: string[]; thirdParty: string[] }; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| // === Tab API (util.ts, tab-dropdown.tsx, toolbar.tsx) ===
 | // === Tab API ===
 | ||||||
| export interface Tab { | export interface Tab { | ||||||
|     id?: number;      // util.ts: tab.id, tab-dropdown.tsx: tab.id
 |     id?: number; | ||||||
|     title?: string;   // tab-dropdown.tsx: tab.title
 |     title?: string; | ||||||
|     url?: string;     // toolbar.tsx: tab.url
 |     url?: string; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export interface TabQuery { | export interface TabQuery { | ||||||
|     currentWindow?: boolean; // util.ts, tab-dropdown.tsx
 |     currentWindow?: boolean; | ||||||
|     active?: boolean;        // toolbar.tsx
 |     active?: boolean; | ||||||
|     windowId?: number;       // toolbar.tsx
 |     windowId?: number; | ||||||
|  |     lastFocusedWindow?: boolean;  // Chrome używa tego zamiast currentWindow czasami
 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // === Badge/BrowserAction API (memory.ts) ===
 | // === Badge/BrowserAction API ===
 | ||||||
| export interface BadgeTextDetails { | export interface BadgeTextDetails { | ||||||
|     text: string;    // memory.ts: setBadgeText
 |     text: string; | ||||||
|     tabId?: number;  // memory.ts: setBadgeText (optional)
 |     tabId?: number; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export interface BadgeTitleDetails { | export interface BadgeTitleDetails { | ||||||
|     title: string;   // memory.ts: setTitle
 |     title: string; | ||||||
|     tabId?: number;  // memory.ts: setTitle (optional)
 |     tabId?: number; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export interface BadgeColorDetails { | export interface BadgeColorDetails { | ||||||
|     color: string;   // memory.ts: setBadgeBackgroundColor
 |     color: string; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // === WebRequest API (memory.ts) ===
 | // === WebRequest API ===
 | ||||||
| export interface RequestFilter { | export interface RequestFilter { | ||||||
|     urls: string[];  // memory.ts: { urls: ['<all_urls>'] }
 |     urls: string[]; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export type RequestListener = (details: Request) => void; | export type RequestListener = (details: Request) => void; | ||||||
| 
 | 
 | ||||||
| // === Cookies API (memory.ts) ===
 | // === Cookies API ===
 | ||||||
| export interface Cookie { | export interface Cookie { | ||||||
|     name: string;    // memory.ts: cookie.name
 |     name: string; | ||||||
|     domain: string;  // memory.ts: cookie.domain
 |     domain: string; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export interface CookieQuery { | export interface CookieQuery { | ||||||
|     domain?: string; // memory.ts: { domain: shorthost }
 |     domain?: string; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export interface CookieRemove { | export interface CookieRemove { | ||||||
|     name: string;    // memory.ts: { name: cookie.name, url: ... }
 |     name: string; | ||||||
|     url: string;     // memory.ts: { url: `https://${cookie.domain}` }
 |     url: string; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // === Main Browser API Interface ===
 | // === Główny interfejs Browser API ===
 | ||||||
| export interface BrowserAPI { | export interface BrowserAPI { | ||||||
|     // Tabs API
 |     // Tabs API
 | ||||||
|     tabs: { |     tabs: { | ||||||
| @ -94,6 +89,10 @@ export interface BrowserAPI { | |||||||
|             addListener(listener: (tabId: number, changeInfo: any, tab: Tab) => void): void; |             addListener(listener: (tabId: number, changeInfo: any, tab: Tab) => void): void; | ||||||
|             removeListener(listener: (tabId: number, changeInfo: any, tab: Tab) => void): void; |             removeListener(listener: (tabId: number, changeInfo: any, tab: Tab) => void): void; | ||||||
|         }; |         }; | ||||||
|  |         onRemoved?: { | ||||||
|  |             addListener(listener: (tabId: number, removeInfo: any) => void): void; | ||||||
|  |             removeListener(listener: (tabId: number, removeInfo: any) => void): void; | ||||||
|  |         }; | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|     // Badge API (Firefox: browserAction, Chrome: action)
 |     // Badge API (Firefox: browserAction, Chrome: action)
 | ||||||
|  | |||||||
							
								
								
									
										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 { SaferEmitter } from './safer-emitter'; | ||||||
| import browserAPI from './lib/browser-api'; | import browserAPI from './lib/browser-api'; | ||||||
| 
 | 
 | ||||||
|  | // Deklaracja Chrome API dla TypeScript
 | ||||||
|  | declare const chrome: any; | ||||||
|  | 
 | ||||||
| function setDomainsCount(counter: number, tabId: number) { | function setDomainsCount(counter: number, tabId: number) { | ||||||
|     browserAPI.badge.setBadgeText({ text: counter < 0 ? '0' : counter.toString(), tabId }); |     // Ochrona przed próbą ustawienia badge dla zamkniętej zakładki
 | ||||||
|     browserAPI.badge.setTitle({ |     try { | ||||||
|         title: 'Rentgen', |         browserAPI.badge.setBadgeText({ text: counter < 0 ? '0' : counter.toString(), tabId }); | ||||||
|         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 { | export default class Memory extends SaferEmitter { | ||||||
|     origin_to_history = {} as Record<string, Record<string, RequestCluster>>; |     origin_to_history = {} as Record<string, Record<string, RequestCluster>>; | ||||||
|  |     isReady: boolean = true; // Firefox jest zawsze gotowy
 | ||||||
|  |     private readyPromise: Promise<void> | null = null; | ||||||
|  |     private syncScheduled: boolean = false; | ||||||
|  |     private lastSyncTime: number = 0; | ||||||
|  |      | ||||||
|  |     // Chrome: Śledzenie pełnych URLi dla zakładek (do generowania screenshotów)
 | ||||||
|  |     private tabUrls: Map<number, string> = new Map(); | ||||||
|  |      | ||||||
|     async register(request: ExtendedRequest) { |     async register(request: ExtendedRequest) { | ||||||
|         await request.init(); |         await request.init(); | ||||||
|  |          | ||||||
|         if (!request.isThirdParty()) { |         if (!request.isThirdParty()) { | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
| @ -23,24 +137,46 @@ export default class Memory extends SaferEmitter { | |||||||
|             this.origin_to_history[request.origin] = {}; |             this.origin_to_history[request.origin] = {}; | ||||||
|         } |         } | ||||||
|         const shorthost = getshorthost(new URL(request.url).host); |         const shorthost = getshorthost(new URL(request.url).host); | ||||||
|  |          | ||||||
|  |         let isNewCluster = false; | ||||||
|         if (!this.origin_to_history[request.origin][shorthost]) { |         if (!this.origin_to_history[request.origin][shorthost]) { | ||||||
|             const cluster = new RequestCluster(shorthost); |             const cluster = new RequestCluster(shorthost); | ||||||
|             this.origin_to_history[request.origin][shorthost] = cluster; |             this.origin_to_history[request.origin][shorthost] = cluster; | ||||||
|  |             isNewCluster = true; | ||||||
|         } |         } | ||||||
|  |          | ||||||
|         this.origin_to_history[request.origin][shorthost].add(request); |         this.origin_to_history[request.origin][shorthost].add(request); | ||||||
|  |          | ||||||
|  |         // Chrome: Automatyczne zaznaczanie podejrzanych domen w service workerze
 | ||||||
|  |         if (process.env.TARGET === 'chrome' && isNewCluster) { | ||||||
|  |             // Zaznacz od razu po utworzeniu clustra
 | ||||||
|  |             this.origin_to_history[request.origin][shorthost].autoMark(); | ||||||
|  |         } | ||||||
|  |          | ||||||
|         this.emit('change', shorthost); |         this.emit('change', shorthost); | ||||||
| 
 | 
 | ||||||
|         Object.values(this.getClustersForOrigin(request.origin)).some((cluster) => |         // Owinięcie operacji badge w try-catch
 | ||||||
|             cluster.hasCookies() |         try { | ||||||
|         ) |             Object.values(this.getClustersForOrigin(request.origin)).some((cluster) => | ||||||
|             ? browserAPI.badge.setBadgeBackgroundColor({ color: '#ff726b' }) |                 cluster.hasCookies() | ||||||
|             : browserAPI.badge.setBadgeBackgroundColor({ color: '#ffb900' }); |             ) | ||||||
|  |                 ? browserAPI.badge.setBadgeBackgroundColor({ color: '#ff726b' }) | ||||||
|  |                 : browserAPI.badge.setBadgeBackgroundColor({ color: '#ffb900' }); | ||||||
| 
 | 
 | ||||||
|         if (request.tabId >= 0) { |             if (request.tabId >= 0) { | ||||||
|             setDomainsCount( |                 setDomainsCount( | ||||||
|                 Object.values(this.getClustersForOrigin(request.origin)).length, |                     Object.values(this.getClustersForOrigin(request.origin)).length, | ||||||
|                 request.tabId |                     request.tabId | ||||||
|             ); |                 ); | ||||||
|  |             } | ||||||
|  |         } catch (e) { | ||||||
|  |             // Zakładka zamknięta - ignorujemy błędy badge
 | ||||||
|  |             console.debug('Aktualizacja badge nie powiodła się - zakładka prawdopodobnie zamknięta'); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // Chrome: Throttlowana synchronizacja do storage (max co 500ms)
 | ||||||
|  |         if (process.env.TARGET === 'chrome') { | ||||||
|  |             this.scheduleSyncToStorage(); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -49,11 +185,26 @@ export default class Memory extends SaferEmitter { | |||||||
| 
 | 
 | ||||||
|         browserAPI.webRequest.onBeforeRequest.addListener( |         browserAPI.webRequest.onBeforeRequest.addListener( | ||||||
|             async (request) => { |             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>'] }, |             { urls: ['<all_urls>'] }, | ||||||
|             ['requestBody'] |             ['requestBody'] | ||||||
|         ); |         ); | ||||||
|  |          | ||||||
|         browserAPI.webRequest.onBeforeSendHeaders.addListener( |         browserAPI.webRequest.onBeforeSendHeaders.addListener( | ||||||
|             async (request) => { |             async (request) => { | ||||||
|                 const extendedRequest = ExtendedRequest.by_id[request.requestId].addHeaders( |                 const extendedRequest = ExtendedRequest.by_id[request.requestId].addHeaders( | ||||||
| @ -64,10 +215,34 @@ export default class Memory extends SaferEmitter { | |||||||
|             { urls: ['<all_urls>'] }, |             { urls: ['<all_urls>'] }, | ||||||
|             ['requestHeaders'] |             ['requestHeaders'] | ||||||
|         ); |         ); | ||||||
|  | 
 | ||||||
|  |         // Chrome: Czyszczenie URLi zakładek po zamknięciu
 | ||||||
|  |         if (process.env.TARGET === 'chrome') { | ||||||
|  |             browserAPI.tabs.onRemoved?.addListener?.((tabId: number) => { | ||||||
|  |                 this.tabUrls.delete(tabId); | ||||||
|  |             }); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // Chrome: Wczytywanie z storage przy starcie (dla popupu)
 | ||||||
|  |         if (process.env.TARGET === 'chrome') { | ||||||
|  |             this.isReady = false; | ||||||
|  |             this.readyPromise = this.loadFromStorage().then(() => { | ||||||
|  |                 this.isReady = true; | ||||||
|  |                 this.emit('ready'); | ||||||
|  |                 console.log('🔵 Memory gotowa do użycia przez popup'); | ||||||
|  |             }); | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     emit(eventName: string, data = 'any'): boolean { |     emit(eventName: string, data = 'any'): boolean { | ||||||
|         setTimeout(() => 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; |         return true; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -75,6 +250,14 @@ export default class Memory extends SaferEmitter { | |||||||
|         return this.origin_to_history[origin] || {}; |         return this.origin_to_history[origin] || {}; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     // Chrome: Oczekiwanie na zakończenie wczytywania ze storage
 | ||||||
|  |     async waitUntilReady(): Promise<void> { | ||||||
|  |         if (this.isReady) return; | ||||||
|  |         if (this.readyPromise) { | ||||||
|  |             await this.readyPromise; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     async removeCookiesFor(origin: string, shorthost?: string): Promise<void> { |     async removeCookiesFor(origin: string, shorthost?: string): Promise<void> { | ||||||
|         if (shorthost) { |         if (shorthost) { | ||||||
|             const cookies = await browserAPI.cookies.getAll({ domain: shorthost }); |             const cookies = await browserAPI.cookies.getAll({ domain: shorthost }); | ||||||
| @ -93,23 +276,150 @@ export default class Memory extends SaferEmitter { | |||||||
|                     .map((cluster) => this.removeCookiesFor(origin, cluster.id)) |                     .map((cluster) => this.removeCookiesFor(origin, cluster.id)) | ||||||
|             ); |             ); | ||||||
|         } |         } | ||||||
|  | 
 | ||||||
|  |         // Chrome: Throttlowana synchronizacja do storage
 | ||||||
|  |         if (process.env.TARGET === 'chrome') { | ||||||
|  |             this.scheduleSyncToStorage(); | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     async removeRequestsFor(origin: string) { |     async removeRequestsFor(origin: string) { | ||||||
|         this.origin_to_history[origin] = {}; |         this.origin_to_history[origin] = {}; | ||||||
|  | 
 | ||||||
|  |         // Chrome: Throttlowana synchronizacja do storage
 | ||||||
|  |         if (process.env.TARGET === 'chrome') { | ||||||
|  |             this.scheduleSyncToStorage(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // ========================================
 | ||||||
|  |     // CHROME: Metody synchronizacji ze storage (THROTTLED)
 | ||||||
|  |     // ========================================
 | ||||||
|  | 
 | ||||||
|  |     public scheduleSyncToStorage(): void { | ||||||
|  |         if (this.syncScheduled) return; | ||||||
|  |          | ||||||
|  |         this.syncScheduled = true; | ||||||
|  |         setTimeout(() => { | ||||||
|  |             this.syncToStorage(); | ||||||
|  |             this.syncScheduled = false; | ||||||
|  |         }, 500); // Synchronizacja max co 500ms
 | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private syncToStorage(): void { | ||||||
|  |         if (typeof chrome !== 'undefined' && chrome.storage?.session) { | ||||||
|  |             const now = Date.now(); | ||||||
|  |             // Pomijamy jeśli synchronizowano niedawno (w ciągu 300ms)
 | ||||||
|  |             if (now - this.lastSyncTime < 300) { | ||||||
|  |                 return; | ||||||
|  |             } | ||||||
|  |             this.lastSyncTime = now; | ||||||
|  |              | ||||||
|  |             const serializable: Record<string, any> = {}; | ||||||
|  |              | ||||||
|  |             for (const [origin, clusters] of Object.entries(this.origin_to_history)) { | ||||||
|  |                 serializable[origin] = {}; | ||||||
|  |                 for (const [shorthost, cluster] of Object.entries(clusters)) { | ||||||
|  |                     // Zapisujemy tylko niezbędne dane dla UI
 | ||||||
|  |                     serializable[origin][shorthost] = { | ||||||
|  |                         id: cluster.id, | ||||||
|  |                         hasCookies: cluster.hasCookies(), | ||||||
|  |                         exposesOrigin: cluster.exposesOrigin(), | ||||||
|  |                         hasMarks: cluster.hasMarks(), | ||||||
|  |                         requestCount: cluster.requests?.length || 0, | ||||||
|  |                         lastFullUrl: cluster.lastFullUrl || null, | ||||||
|  |                         lastModified: cluster.lastModified || 0, | ||||||
|  |                     }; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |              | ||||||
|  |             chrome.storage.session.set({ rentgen_memory: serializable }).catch((err: any) => { | ||||||
|  |                 console.error('Nie udało się zsynchronizować pamięci do storage:', err); | ||||||
|  |             }); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private async loadFromStorage(): Promise<void> { | ||||||
|  |         if (typeof chrome !== 'undefined' && chrome.storage?.session) { | ||||||
|  |             try { | ||||||
|  |                 const result = await chrome.storage.session.get('rentgen_memory'); | ||||||
|  |                 if (result.rentgen_memory) { | ||||||
|  |                     const serialized = result.rentgen_memory; | ||||||
|  |                      | ||||||
|  |                     for (const [origin, clusters] of Object.entries(serialized)) { | ||||||
|  |                         this.origin_to_history[origin] = {}; | ||||||
|  |                         for (const [shorthost, cached] of Object.entries(clusters as Record<string, any>)) { | ||||||
|  |                             // Tworzenie CachedRequestCluster ze wszystkimi potrzebnymi danymi
 | ||||||
|  |                             const clusterData = cached as {  | ||||||
|  |                                 id: string;  | ||||||
|  |                                 hasCookies: boolean;  | ||||||
|  |                                 exposesOrigin: boolean; | ||||||
|  |                                 hasMarks: boolean; | ||||||
|  |                                 requestCount: number; | ||||||
|  |                                 lastFullUrl: string | null; | ||||||
|  |                                 lastModified: number; | ||||||
|  |                             }; | ||||||
|  |                             this.origin_to_history[origin][shorthost] = new CachedRequestCluster( | ||||||
|  |                                 clusterData.id, | ||||||
|  |                                 clusterData | ||||||
|  |                             ); | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                      | ||||||
|  |                     console.log('🔵 Wczytano pamięć z chrome.storage.session:', Object.keys(this.origin_to_history).length, 'origins'); | ||||||
|  |                 } | ||||||
|  |             } catch (err) { | ||||||
|  |                 console.error('Nie udało się wczytać pamięci ze storage:', err); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // ========================================
 | ||||||
|  | // INICJALIZACJA: Firefox vs Chrome
 | ||||||
|  | // ========================================
 | ||||||
|  | 
 | ||||||
| export function init() { | export function init() { | ||||||
|     const memory = new Memory(); |     const memory = new Memory(); | ||||||
| 
 | 
 | ||||||
|     (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 { | export function getMemory(): Memory { | ||||||
|     const backgroundPage = browserAPI.extension.getBackgroundPage(); |     if (process.env.TARGET === 'chrome') { | ||||||
|     if (!backgroundPage) { |         // Chrome: Najpierw próba pobrania z service workera
 | ||||||
|         throw new Error('Background page not available'); |         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", |                 "addons-linter": "^4.7.0", | ||||||
|                 "esbuild": "^0.14.14", |                 "esbuild": "^0.14.14", | ||||||
|                 "esbuild-plugin-sass": "^1.0.1", |                 "esbuild-plugin-sass": "^1.0.1", | ||||||
|  |                 "sharp": "^0.34.4", | ||||||
|                 "typescript": "^4.6.4", |                 "typescript": "^4.6.4", | ||||||
|                 "web-ext": "^6.7.0", |                 "web-ext": "^6.7.0", | ||||||
|                 "web-ext-types": "^3.2.1" |                 "web-ext-types": "^3.2.1" | ||||||
| @ -187,6 +188,17 @@ | |||||||
|                 "node": ">= 0.10.4" |                 "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": { |         "node_modules/@eslint/eslintrc": { | ||||||
|             "version": "1.3.0", |             "version": "1.3.0", | ||||||
|             "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.0.tgz", |             "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", |             "resolved": "https://registry.npmjs.org/@iabtcf/core/-/core-1.5.3.tgz", | ||||||
|             "integrity": "sha512-DZsenDL6uz/jULc/PlWs9HA2eDHkAcL+JiZ5TtB1O5ZSwZ2BMyJc4I5qC+j/7BZFPXqaHRbGQ7QpbxmcHYLw1Q==" |             "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": { |         "node_modules/@mdn/browser-compat-data": { | ||||||
|             "version": "4.1.13", |             "version": "4.1.13", | ||||||
|             "resolved": "https://registry.npmjs.org/@mdn/browser-compat-data/-/browser-compat-data-4.1.13.tgz", |             "resolved": "https://registry.npmjs.org/@mdn/browser-compat-data/-/browser-compat-data-4.1.13.tgz", | ||||||
| @ -1832,6 +2294,16 @@ | |||||||
|             "dev": true, |             "dev": true, | ||||||
|             "peer": 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": { |         "node_modules/doctrine": { | ||||||
|             "version": "3.0.0", |             "version": "3.0.0", | ||||||
|             "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", |             "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", | ||||||
| @ -5699,6 +6171,62 @@ | |||||||
|                 "sha.js": "bin.js" |                 "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": { |         "node_modules/shebang-command": { | ||||||
|             "version": "2.0.0", |             "version": "2.0.0", | ||||||
|             "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", |             "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", | ||||||
| @ -7316,6 +7844,16 @@ | |||||||
|                 "async": "~0.2.9" |                 "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": { |         "@eslint/eslintrc": { | ||||||
|             "version": "1.3.0", |             "version": "1.3.0", | ||||||
|             "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.0.tgz", |             "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", |             "resolved": "https://registry.npmjs.org/@iabtcf/core/-/core-1.5.3.tgz", | ||||||
|             "integrity": "sha512-DZsenDL6uz/jULc/PlWs9HA2eDHkAcL+JiZ5TtB1O5ZSwZ2BMyJc4I5qC+j/7BZFPXqaHRbGQ7QpbxmcHYLw1Q==" |             "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": { |         "@mdn/browser-compat-data": { | ||||||
|             "version": "4.1.13", |             "version": "4.1.13", | ||||||
|             "resolved": "https://registry.npmjs.org/@mdn/browser-compat-data/-/browser-compat-data-4.1.13.tgz", |             "resolved": "https://registry.npmjs.org/@mdn/browser-compat-data/-/browser-compat-data-4.1.13.tgz", | ||||||
| @ -8626,6 +9354,12 @@ | |||||||
|             "dev": true, |             "dev": true, | ||||||
|             "peer": 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": { |         "doctrine": { | ||||||
|             "version": "3.0.0", |             "version": "3.0.0", | ||||||
|             "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", |             "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", | ||||||
| @ -11633,6 +12367,47 @@ | |||||||
|                 "safe-buffer": "^5.0.1" |                 "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": { |         "shebang-command": { | ||||||
|             "version": "2.0.0", |             "version": "2.0.0", | ||||||
|             "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", |             "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", | ||||||
|  | |||||||
| @ -5,18 +5,19 @@ | |||||||
|     "main": "esbuild.config.js", |     "main": "esbuild.config.js", | ||||||
|     "type": "module", |     "type": "module", | ||||||
|     "scripts": { |     "scripts": { | ||||||
|  |         "convert-icons": "node scripts/convert-icons.js", | ||||||
|         "build": "node esbuild.config.js", |         "build": "node esbuild.config.js", | ||||||
|         "build:firefox": "TARGET=firefox node esbuild.config.js", |         "build:firefox": "TARGET=firefox node esbuild.config.js", | ||||||
|         "build:chrome": "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": "node esbuild.config.js --watch", | ||||||
|         "watch:firefox": "TARGET=firefox node esbuild.config.js --watch", |         "watch:firefox": "TARGET=firefox node esbuild.config.js --watch", | ||||||
|         "watch:chrome": "TARGET=chrome node esbuild.config.js --watch", |         "watch:chrome": "TARGET=chrome node esbuild.config.js --watch", | ||||||
|         "ext-test": "web-ext run", |         "ext-test": "web-ext run", | ||||||
|         "build-addon": "npm i && npm run build && npm run create-package", |         "build-addon": "npm i && npm run build && npm run create-package", | ||||||
|         "build-addon:firefox": "npm i && npm run build: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", |         "build-addon:chrome": "npm i && npm run build:chrome && npm run create-package:chrome", | ||||||
|         "create-package": "web-ext build --ignore-files '!**/node_modules' '!**/node_modules/**/react-dom' '!**/node_modules/**/react-dom/umd' '!**/node_modules/**/*/react-dom.production.min.js' '!**/node_modules/**/react' '!**/node_modules/**/react/umd' '!**/node_modules/**/*/react.production.min.js' '!**/node_modules/**/survey-react'  '!**/node_modules/**/survey-react/*.min.js' '!**/node_modules/**/survey-react/*.min.css' --overwrite-dest", |         "create-package": "web-ext build --ignore-files '!**/node_modules' '!**/node_modules/**/react-dom' '!**/node_modules/**/react-dom/umd' '!**/node_modules/**/*/react-dom.production.min.js' '!**/node_modules/**/react' '!**/node_modules/**/react/umd' '!**/node_modules/**/*/react.production.min.js' '!**/node_modules/**/survey-react'  '!**/node_modules/**/survey-react/*.min.js' '!**/node_modules/**/survey-react/*.min.css' --overwrite-dest", | ||||||
|         "create-package:firefox": "cd dist-firefox && web-ext build --overwrite-dest --artifacts-dir ../web-ext-artifacts", |         "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 ..", |         "create-package:chrome": "cd dist-chrome && 7z a -tzip ../web-ext-artifacts/rentgen-chrome-0.1.10.zip * && cd ..", | ||||||
|         "typecheck": "tsc --noEmit", |         "typecheck": "tsc --noEmit", | ||||||
|         "lint": "web-ext lint" |         "lint": "web-ext lint" | ||||||
| @ -57,6 +58,7 @@ | |||||||
|         "addons-linter": "^4.7.0", |         "addons-linter": "^4.7.0", | ||||||
|         "esbuild": "^0.14.14", |         "esbuild": "^0.14.14", | ||||||
|         "esbuild-plugin-sass": "^1.0.1", |         "esbuild-plugin-sass": "^1.0.1", | ||||||
|  |         "sharp": "^0.34.4", | ||||||
|         "typescript": "^4.6.4", |         "typescript": "^4.6.4", | ||||||
|         "web-ext": "^6.7.0", |         "web-ext": "^6.7.0", | ||||||
|         "web-ext-types": "^3.2.1" |         "web-ext-types": "^3.2.1" | ||||||
|  | |||||||
							
								
								
									
										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) { | export function getshorthost(host: string) { | ||||||
|     const parts = host |     // Obsługa przypadków brzegowych
 | ||||||
|         .replace(/^.*:\/\//, '') |     if (!host || typeof host !== 'string') { | ||||||
|         .replace(/\/.*$/, '') |         console.warn('getshorthost: nieprawidłowy host:', host); | ||||||
|         .split('.'); |         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); |     const second_last = parts.at(-2); | ||||||
|  |      | ||||||
|  |     // Bezpieczny fallback jeśli wciąż nieprawidłowy
 | ||||||
|     if (!second_last) { |     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; |     let lookback = !['co', 'com'].includes(second_last) ? -2 : -3; | ||||||
|  |      | ||||||
|     if (parts.at(-2) == 'doubleclick' || parts.at(-2) == 'google') { |     if (parts.at(-2) == 'doubleclick' || parts.at(-2) == 'google') { | ||||||
|         lookback = -4; // to distinguish between google ads and stats
 |         lookback = -4; // aby rozróżnić google ads i stats
 | ||||||
|     } else if (parts.at(-2) == 'google') { |     } 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( | 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