feat/refactor: Chrome MV3 Support + Refaktoryzacja architektury dla kompatybilności obu przeglądarek
## 🎯 Cel Dodanie pełnego wsparcia dla Chrome Manifest V3 przy zachowaniu kompatybilności z Firefox. Główne wyzwanie: Chrome MV3 używa service workerów zamiast persistent background pages, co wymaga nowej architektury zarządzania pamięcią. --- ## 🏗️ Architektura ### Nowa warstwa abstrakcji: lib/browser-api/ **lib/browser-api/index.ts** - Główny punkt wejścia do zunifikowanego API - Wybiera właściwą implementację na podstawie zmiennej TARGET (build-time) - Eksportuje jeden interfejs dla całej aplikacji **lib/browser-api/types.ts** - Wspólne definicje typów dla obu przeglądarek - Interfejs BrowserAPI definiujący wszystkie potrzebne metody - Typy dla tabs, badge, webRequest, cookies, extension, windows **lib/browser-api/firefox.ts** - Adapter dla Firefox browser.* API - Lazy access do globalnego obiektu browser (bezpieczne dla środowisk bez Firefox API) - Wszystkie metody zwracają Promise lub są no-op jeśli API niedostępne **lib/browser-api/chrome.ts** - Adapter dla Chrome chrome.* API - Mapowanie chrome.action → badge (różnica nazewnictwa) - Ochrona przed błędami gdy karta zostaje zamknięta (try-catch w operacjach badge) --- ## 🔧 Build System ### esbuild.config.js - **Dodano**: Rozpoznawanie zmiennej środowiskowej TARGET (firefox/chrome) - **Dodano**: Różne katalogi wyjściowe (dist-firefox, dist-chrome) - **Dodano**: Kopiowanie odpowiedniego manifestu na podstawie TARGET - **Dodano**: Plugin do konwersji ikon SVG → PNG dla Chrome (wymaga PNG w MV3) - **Zmieniono**: define zawiera teraz process.env.TARGET dostępny w runtime ### package.json - **Dodano**: Skrypty build:firefox, build:chrome, watch:firefox, watch:chrome - **Dodano**: Skrypty build-addon:firefox, build-addon:chrome do tworzenia paczek - **Dodano**: Skrypt convert-icons do generowania PNG z SVG - **Dodano**: Zależność sharp do konwersji obrazów ### scripts/convert-icons.js (NOWY PLIK) - Konwertuje assets/icon-addon.svg → PNG w rozmiarach 16, 32, 48, 128px - Wymagane dla Chrome (MV3 nie akceptuje SVG w manifestach) ### manifest-chrome.json (NOWY PLIK) - Manifest V3 dla Chrome - background.service_worker zamiast background.scripts - action zamiast browser_action - Ikony PNG zamiast SVG - host_permissions zamiast embedowanych w permissions --- ## 🧠 Pamięć i Stan (NAJWIĘKSZA ZMIANA) ### memory.ts - Kompletna refaktoryzacja **Problem Chrome MV3:** - Service worker może być wyładowany w dowolnym momencie - Brak dostępu do window.memory z popup/sidebar - chrome.extension.getBackgroundPage() zwraca null w MV3 **Rozwiązanie:** 1. **Service Worker**: Trzyma pełne dane, synchronizuje do chrome.storage.session 2. **Popup/Sidebar**: Tworzy własną instancję Memory czytając z storage 3. **Throttled sync**: Maksymalnie co 500ms zapisy do storage (wydajność) **Nowa klasa: CachedRequestCluster** - Dziedziczy po RequestCluster ale NIE ma rzeczywistych requestów - Przechowuje tylko metadane: hasCookies, exposesOrigin, hasMarks - Implementuje wszystkie wymagane metody zwracając cached state - Używana TYLKO w popup/report window w Chrome **Zmiany w Memory klasie:** - **Dodano**: isReady flag i readyPromise dla async inicjalizacji (Chrome) - **Dodano**: waitUntilReady() - popup musi poczekać na załadowanie danych - **Dodano**: tabUrls: Map<number, string> - tracking pełnych URL dla Chrome (service worker nie dostaje documentUrl) - **Dodano**: scheduleSyncToStorage() - throttled sync do storage - **Dodano**: syncToStorage() - serializacja clustrów do JSON - **Dodano**: loadFromStorage() - deserializacja przy starcie popup - **Zmieniono**: register() śledzi main_frame URL i synuje po każdej zmianie - **Zmieniono**: Badge operacje w try-catch (karta może być zamknięta) **Funkcja getMemory():** - **Firefox**: browserAPI.extension.getBackgroundPage().memory (tradycyjnie) - **Chrome Service Worker**: self.memory (jesteśmy W service workerze) - **Chrome Popup**: Tworzy NOWĄ instancję czytając z storage (cachowana jako popupMemoryInstance) --- ## 🔒 Bezpieczeństwo i Obsługa Błędów ### util.ts - getshorthost() - **Dodano**: Walidacja wejścia (null, undefined, pusty string) - **Dodano**: Czyszczenie URL (protokół, ścieżka, port) - **Dodano**: Obsługa edge cases (localhost, single word domains) - **Dodano**: Bezpieczne fallbacki zamiast crashowania - **Dodano**: Console.warn zamiast milczących błędów ### extended-request.ts - **MASYWNE POPRAWKI** parsowania URL w konstruktorze - **Dodano**: isValidHttpUrl() helper - sprawdza czy URL zaczyna się od http(s) - **Dodano**: safeParseUrl() helper - try-catch wokół new URL() - **Dodano**: Próba parsowania wielu URL w kolejności priorytetów - **Dodano**: Obsługa Chrome MV3 initiator property - **Dodano**: Bezpieczne defaulty gdy parsowanie się nie uda (unknown://unknown) - **Zmieniono**: isThirdParty() pomija requesty z unparseable URLs - **Dodano**: uint8ArrayToString() - chunked konwersja dużych arrayów (zapobiega stack overflow) - **Zmieniono**: Request body processing używa chunked konwersji --- ## 🎨 UI Components ### toolbar.tsx (popup) - **Dodano**: getCurrentTab() z retry mechanism (Chrome czasem nie zwraca karty od razu) - **Dodano**: Sprawdzanie memoryReady przed renderowaniem danych - **Dodano**: Wywołanie waitUntilReady() w useEffect - **Dodano**: Opóźnienie 200ms dla Chrome przy inicjalizacji (service worker + storage delay) - **Dodano**: Graceful handling gdy popup otwarty bez active tab ### sidebar.tsx - **Dodano**: Stan memoryReady i loading screen dla Chrome - **Dodano**: Wywołanie waitUntilReady() przed dostępem do danych - **Dodano**: Conditional rendering - pokazuje Ładowanie... gdy pamięć nie gotowa ### report-window.tsx - **Dodano**: Stan memoryReady i loading message - **Dodano**: Wywołanie waitUntilReady() przed generowaniem raportu - **Dodano**: Fallback konstruowania URL z origin gdy brak visited_url - **Zmieniono**: Filtr clustrów używa hasMarks() zamiast getMarkedRequests().length ### stolen-data-cluster.tsx - **Bez znaczących zmian** - działa z abstrakcją RequestCluster --- ## 🐛 Poprawki Bugów ### background.ts - **Dodano**: Diagnostic logging do debugowania inicjalizacji - **Dodano**: Try-catch wokół init() z error logging - **Dodano**: Różne logi dla Firefox vs Chrome ### memory.ts - badge operations - **Dodano**: Try-catch wokół wszystkich operacji badge - Zapobiega crashowaniu gdy użytkownik zamknie kartę podczas operacji ### chrome.ts - badge adapter - **Dodano**: Try-catch w setBadgeText, setTitle, setBadgeBackgroundColor - Chrome rzuca błędy gdy operujemy na zamkniętych kartach --- ## 📝 Workflow Użytkownika (Chrome) 1. **Użytkownik odwiedza stronę** → Service worker rejestruje requesty → Auto-mark podejrzanych → Sync do storage 2. **Użytkownik otwiera popup** → Tworzy Memory → Czyta ze storage → Pokazuje dane z flagami 3. **Użytkownik (od)zaznacza domeny** → Zmienia flagi → Sync do storage 4. **Użytkownik generuje raport** → Otwiera report-window → Czyta ze storage → Filtruje według hasMarks() --- ## ✅ Rezultat - ✅ **Firefox**: Działa jak wcześniej (background page + window.memory) - ✅ **Chrome**: Pełne wsparcie MV3 (service worker + storage.session) - ✅ **Wspólny kod**: 95% kodu jest shared, tylko warstwa dostępu do API różni się - ✅ **Bezpieczeństwo**: Obsługa wszystkich edge cases w parsowaniu URL - ✅ **Wydajność**: Throttled sync do storage (max co 500ms) - ✅ **UX**: Loading states w popup/sidebar dla Chrome - ✅ **Build**: npm run build-addon:firefox lub npm build-addon dla firefox / npm run build:chrome dla chrome
This commit is contained in:
parent
3512386b2b
commit
ffb37f9d61
@ -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');
|
||||||
|
|
||||||
|
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,27 +123,18 @@ 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>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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,14 +1,115 @@
|
|||||||
import esbuild from 'esbuild';
|
import esbuild from 'esbuild';
|
||||||
import scss from 'esbuild-plugin-sass';
|
import scss from 'esbuild-plugin-sass';
|
||||||
|
import { copyFileSync, mkdirSync, readdirSync, existsSync } from 'fs';
|
||||||
|
import { join, dirname } from 'path';
|
||||||
|
|
||||||
|
// Określenie platformy docelowej: firefox (domyślnie) lub chrome
|
||||||
|
const TARGET = process.env.TARGET || 'firefox';
|
||||||
|
const IS_FIREFOX = TARGET === 'firefox';
|
||||||
|
const IS_CHROME = TARGET === 'chrome';
|
||||||
|
|
||||||
|
// Katalogi wyjściowe
|
||||||
|
const DIST_DIR = IS_FIREFOX ? './dist-firefox' : './dist-chrome';
|
||||||
|
const LIB_DIR = join(DIST_DIR, 'lib');
|
||||||
|
|
||||||
|
console.log(`🎯 Budowanie dla: ${TARGET.toUpperCase()}`);
|
||||||
|
console.log(`📁 Katalog wyjściowy: ${DIST_DIR}`);
|
||||||
|
|
||||||
const watch = process.argv.includes('--watch') && {
|
const watch = process.argv.includes('--watch') && {
|
||||||
onRebuild(error) {
|
onRebuild(error) {
|
||||||
if (error) console.error('[watch] build failed', error);
|
if (error) console.error('[watch] budowanie nie powiodło się', error);
|
||||||
else console.log('[watch] build finished');
|
else console.log('[watch] budowanie zakończone');
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// see https://github.com/evanw/esbuild/issues/806#issuecomment-779138268
|
// Funkcja pomocnicza: rekurencyjne kopiowanie katalogów
|
||||||
|
function copyDir(src, dest) {
|
||||||
|
if (!existsSync(dest)) {
|
||||||
|
mkdirSync(dest, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
const entries = readdirSync(src, { withFileTypes: true });
|
||||||
|
|
||||||
|
for (const entry of entries) {
|
||||||
|
const srcPath = join(src, entry.name);
|
||||||
|
const destPath = join(dest, entry.name);
|
||||||
|
|
||||||
|
if (entry.isDirectory()) {
|
||||||
|
copyDir(srcPath, destPath);
|
||||||
|
} else {
|
||||||
|
copyFileSync(srcPath, destPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Plugin: kopiowanie plików statycznych po zakończeniu budowania
|
||||||
|
const copyStaticFiles = {
|
||||||
|
name: 'copy-static-files',
|
||||||
|
setup(build) {
|
||||||
|
build.onEnd(() => {
|
||||||
|
console.log('📋 Kopiowanie plików statycznych...');
|
||||||
|
|
||||||
|
// Kopiowanie manifestu (wybór na podstawie platformy docelowej)
|
||||||
|
const manifestSrc = IS_FIREFOX ? './manifest.json' : './manifest-chrome.json';
|
||||||
|
const manifestDest = join(DIST_DIR, 'manifest.json');
|
||||||
|
mkdirSync(dirname(manifestDest), { recursive: true });
|
||||||
|
copyFileSync(manifestSrc, manifestDest);
|
||||||
|
console.log(` ✓ Skopiowano ${manifestSrc} → ${manifestDest}`);
|
||||||
|
|
||||||
|
// Kopiowanie katalogu components
|
||||||
|
if (existsSync('./components')) {
|
||||||
|
copyDir('./components', join(DIST_DIR, 'components'));
|
||||||
|
console.log(' ✓ Skopiowano components/');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Kopiowanie katalogu assets
|
||||||
|
if (existsSync('./assets')) {
|
||||||
|
copyDir('./assets', join(DIST_DIR, 'assets'));
|
||||||
|
console.log(' ✓ Skopiowano assets/');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Kopiowanie wymaganych bibliotek z node_modules (potrzebne dla plików HTML z UMD React)
|
||||||
|
const nodeModulesDest = join(DIST_DIR, 'node_modules');
|
||||||
|
|
||||||
|
// React
|
||||||
|
const reactUmdSrc = './node_modules/react/umd';
|
||||||
|
const reactUmdDest = join(nodeModulesDest, 'react/umd');
|
||||||
|
if (existsSync(reactUmdSrc)) {
|
||||||
|
copyDir(reactUmdSrc, reactUmdDest);
|
||||||
|
console.log(' ✓ Skopiowano node_modules/react/umd/');
|
||||||
|
}
|
||||||
|
|
||||||
|
// React-DOM
|
||||||
|
const reactDomUmdSrc = './node_modules/react-dom/umd';
|
||||||
|
const reactDomUmdDest = join(nodeModulesDest, 'react-dom/umd');
|
||||||
|
if (existsSync(reactDomUmdSrc)) {
|
||||||
|
copyDir(reactDomUmdSrc, reactDomUmdDest);
|
||||||
|
console.log(' ✓ Skopiowano node_modules/react-dom/umd/');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Survey-React
|
||||||
|
const surveyReactSrc = './node_modules/survey-react';
|
||||||
|
const surveyReactDest = join(nodeModulesDest, 'survey-react');
|
||||||
|
if (existsSync(surveyReactSrc)) {
|
||||||
|
// Kopiowanie tylko niezbędnych plików
|
||||||
|
mkdirSync(surveyReactDest, { recursive: true });
|
||||||
|
const surveyFiles = ['survey.react.min.js', 'survey.react.min.css'];
|
||||||
|
surveyFiles.forEach(file => {
|
||||||
|
const src = join(surveyReactSrc, file);
|
||||||
|
if (existsSync(src)) {
|
||||||
|
copyFileSync(src, join(surveyReactDest, file));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
console.log(' ✓ Skopiowano node_modules/survey-react/');
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`✅ Budowanie dla ${TARGET.toUpperCase()} zakończone!`);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Zobacz: https://github.com/evanw/esbuild/issues/806#issuecomment-779138268
|
||||||
|
// Plugin pomijający importy React (używamy globalnych obiektów z UMD)
|
||||||
let skipReactImports = {
|
let skipReactImports = {
|
||||||
name: 'skipReactImports',
|
name: 'skipReactImports',
|
||||||
setup(build) {
|
setup(build) {
|
||||||
@ -32,6 +133,7 @@ let skipReactImports = {
|
|||||||
loader: 'js',
|
loader: 'js',
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
build.onLoad({ filter: /.*/, namespace: 'globalExternal_survey-react' }, () => {
|
build.onLoad({ filter: /.*/, namespace: 'globalExternal_survey-react' }, () => {
|
||||||
return {
|
return {
|
||||||
contents: `module.exports = globalThis.Survey`,
|
contents: `module.exports = globalThis.Survey`,
|
||||||
@ -44,25 +146,34 @@ let skipReactImports = {
|
|||||||
esbuild
|
esbuild
|
||||||
.build({
|
.build({
|
||||||
entryPoints: [
|
entryPoints: [
|
||||||
|
// JavaScript/TypeScript
|
||||||
'components/toolbar/toolbar.tsx',
|
'components/toolbar/toolbar.tsx',
|
||||||
'components/sidebar/sidebar.tsx',
|
'components/sidebar/sidebar.tsx',
|
||||||
'components/report-window/report-window.tsx',
|
'components/report-window/report-window.tsx',
|
||||||
'background.ts',
|
'background.ts',
|
||||||
'diag.tsx',
|
'diag.tsx',
|
||||||
|
|
||||||
|
// Globalne style
|
||||||
'styles/global.scss',
|
'styles/global.scss',
|
||||||
'styles/fonts.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,
|
bundle: true,
|
||||||
// minify: true,
|
// minify: true,
|
||||||
outdir: './lib',
|
outdir: LIB_DIR,
|
||||||
loader: { '.woff': 'file', '.woff2': 'file' },
|
loader: { '.woff': 'file', '.woff2': 'file' },
|
||||||
plugins: [scss(), skipReactImports],
|
plugins: [scss(), skipReactImports, copyStaticFiles],
|
||||||
define: {
|
define: {
|
||||||
PLUGIN_NAME: '"Rentgen"',
|
PLUGIN_NAME: '"Rentgen"',
|
||||||
PLUGIN_URL: '"https://addons.mozilla.org/pl/firefox/addon/rentgen/"',
|
PLUGIN_URL: '"https://addons.mozilla.org/pl/firefox/addon/rentgen/"',
|
||||||
|
'process.env.TARGET': JSON.stringify(TARGET),
|
||||||
},
|
},
|
||||||
external: ['react', 'react-dom', 'survey-react'],
|
external: ['react', 'react-dom', 'survey-react'],
|
||||||
watch,
|
watch,
|
||||||
})
|
})
|
||||||
.then(() => console.log('Add-on was built'))
|
.then(() => console.log(`\n🎉 Dodatek dla ${TARGET.toUpperCase()} zbudowany pomyślnie!\n`))
|
||||||
.catch(() => process.exit(1));
|
.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,37 +14,60 @@ 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.*
|
||||||
|
|||||||
@ -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