Reveal URLs — Arhitektura

Ovaj dokument opisuje kako je Reveal URLs strukturiran i kako radi njegova osnovna logika, za saradnike i tehničke čitatelje. Kroz cijeli tekst navode se stvarne datoteke i simboli u obliku path symbol, tako da se svaka tvrdnja može provjeriti u izvornom kodu. Korisnički opis nalazi se u manual, a koraci ručne provjere u manual test plan.

Pregled

Reveal URLs je jedna MV3 WebExtension koja se iz jedne zajedničke, čiste jezgre koda gradi za Chrome, Edge, Opera, Firefox i Thunderbird. Safari je odgođen za kasniju fazu: ima manifest i može se izgraditi kada se eksplicitno navede, ali nije uključen u podrazumijevani build ni u CI. Proširenje prikazuje odredišni URL svakog linka pored samog linka u rendered email sadržaju i označava link kada vidljivi tekst upućuje na drugu registrable domenu od one iz href.

Repozitorij je pnpm i TypeScript monorepo opisan datotekama pnpm-workspace.yaml i package.json, a "private": true sprječava njegovo objavljivanje kao paketa. Bundling vodi tooling/build.mjs, koji kompajlira zajedničke pakete i tanke entry point-e svakog engine-a u učitljivi direktorij dist/<target>/. webextension-polyfill se bundla u svaki script umjesto da se oslanja na runtime global.

Dva native dodatka za e-poštu proširuju istu jezgru detekcije na površine do kojih WebExtension ne može doći: Outlook Add-in na Office.js i Gmail Add-on na Apps Script CardService. Oba ponovo koriste samo PURE analizu iz packages/core/src/findings.ts, zajedno sa analyseAnchors, analyseHtml, neutralnim modelom Finding[], logikom hostMismatch i izvođenjem registrable domena preko tldts. Razlikuju se samo host adapter i prezentacija. Outlook koristi DOMParser i radi client-side. Gmail koristi node-html-parser i radi server-side na Google Apps Script V8 infrastrukturi. Pošto nijedan od ta dva okvira ne dozvoljava mijenjanje DOM-a prikazane poruke, oba prikazuju panel ili karticu s nalazima, a ne inline annotation. Putanja DOM mutacije iz linkProcessor.ts i REVEAL_URLS_CSS tu se ne koriste. Relativni href razrješava se samo prema pouzdanom <base href> unutar same poruke, a u suprotnom se preskače.

Kodna baza jasno razdvaja odgovornosti:

Raspored repozitorija

packages/core — čista jezgra

Nijedan modul ovdje ne importuje extension API kao što su browser.*, chrome.* ili messenger.*, i nijedan ne koristi string-to-markup DOM sinkove kao što su innerHTML ili insertAdjacentHTML. Taj ugovor se mehanički provodi u packages/core/test/purity.test.ts, koji prolazi kroz src/**/*.ts, uklanja tijela komentara pomoću stripComments i provjerava da ni EXTENSION_API_PATTERN ni UNSAFE_DOM_PATTERN ne odgovaraju nijednom modulu. Javna površina se reeksportuje iz packages/core/src/index.ts, a jedina runtime zavisnost je tldts.

Glavni moduli su:

packages/webext — omotači browser API-ja

Ovi moduli SMIJU koristiti browser.* i to je jedino mjesto gdje se dotiču WebExtension API-ji. Javna površina se reeksportuje iz packages/webext/src/index.ts. Tu se nalaze content.ts, contentRegistration.ts, storage.ts, background.ts, toolbarAction.ts, messageDisplay.ts, messageDisplayBackground.ts i options/options.ts.

extensions/<engine> — tanki entry point-i i manifesti

Svaki engine ima vlastiti manifest.json i vlastiti icons/. Chrome, Firefox i Thunderbird dodatno imaju direktorij src/ s entry point-ima. Edge, Opera i Safari imaju samo manifest i ikone i ponovo koriste Chrome src/. Na primjer, extensions/chrome/src/content.ts sadrži samo void createContentController().bootstrap();, a extensions/chrome/src/background.ts poziva registerBackground, registerContentReconciliation i registerToolbarToggle. extensions/thunderbird/src/background.ts poziva registerBackground i registerMessageDisplay. extensions/_template je scaffold, a ne build target.

tooling, features i smoke testovi

tooling obuhvata build.mjs, version.mjs i icons.mjs. features obuhvata Gherkin scenarije, step definitions i vjerne doubles datoteke features/support/webext.ts i features/support/world.ts. Svaki engine također ima extensions/<engine>/test/manifest.smoke.test.mjs, dok _template koristi template.smoke.test.mjs.

Tok otkrivanja

Content putanja počinje u entry point-u engine-a kao što je extensions/chrome/src/content.ts, koji poziva createContentController().bootstrap() iz packages/webext/src/content.ts.

Bootstrap

createContentController bootstrap radi jednom po frame-u. On preuzima frame prije svakog await preko claimBootstrap, učitava config preko loadConfigWithRetry, ubacuje stylesheet kroz injectStyles tek nakon uspješnog čitanja i vraća marker pomoću releaseBootstrap ako učitavanje konačno ne uspije. Kada je config.enabled aktivan, poziva start, a u svakom slučaju registruje listener onConfigChanged.

Određivanje aktivnog pravila za sajt

start i applyConfig koriste resolveSiteRule da odluče da li i gdje treba izvršiti annotation. Prvo se pokušava lokacija samog dokumenta preko resolveForHref, koji filtrira config.sites na uključena pravila, a konačan izbor prepušta funkciji selectMostSpecific. Ako se dokument ne poklapa, a njegovo porijeklo je opaque ili inherited, kako to provjerava hasOpaqueOrigin, tada slijedi fallback preko readTopHref, readOpenerHref i readReferrer, uz zaštitu od cross-origin čitanja.

Ograničavanje na korijen sadržaja i praćenje promjena

start pamti contentRoot, kreira annotator preko createAnnotator(config) i obrađuje svaki korijen iz selectRoots(doc, contentRoot), koji omotava querySelectorAll u try/catch kako bi neispravan selector sigurno vratio []. Nakon toga posmatra doc.body koristeći OBSERVER_OPTIONS. processMutations filtrira svaku promjenu prema root-u, a processAddedNode obuhvata čvorove unutar root-a, sam root i čvorove koji ga sadrže. applyConfig radi teardown, restart ili reflow zavisno od promjene aktivnog pravila i contentRoot.

Označavanje pojedinačnog linka

Annotator se nalazi u packages/core/src/linkProcessor.ts. createAnnotator vraća Annotator s Map, data-ru tokenom iz generateToken i aktivnim config-om. Njegov process(root) čisti odspojene zapise, skuplja a[href] elemente pomoću collectAnchors, razrješava URL putem resolveAnnotatableUrl, koristi brzu putanju isAnnotationIntact kada se render nije promijenio i računa mismatch pomoću hostMismatch prije nego što pozove annotateAnchor.

annotateAnchor renderira samo SAFE-DOM. U režimu "inline" dodaje <span class="reveal-urls-url"> sa textContent i <br>. U režimu "title" čuva postojeći title, mijenja ga u URL i, kada je mismatch naglašen, dodaje <span class="reveal-urls-warn"> sa REVEAL_URLS_WARN_LABEL. Kreirani čvorovi dobijaju data-ru="<token>", a configFingerprint služi za prisilni reflow. truncateUrl radi nad [...displayHref] i koristi URL_ELLIPSIS.

Nepodudaranje hosta

packages/core/src/hostMismatch.ts hostMismatch poredi vidljivi tekst linka s njegovim href na nivou registrable domene, a ne sirovog hosta. extractHostCandidates dijeli tekst na tokene i svodi ih pomoću hostCandidate, koji traži tačku i public suffix koji tldts prepoznaje. Href host i kandidati svode se preko tldts.getDomain. Svaki kandidat sa drugom domenom proizvodi mismatch.

Stilovi i kontrast

packages/core/src/styles.ts čuva REVEAL_URLS_CSS i pomoćne funkcije applyColour, applyFontOverrides i applyContrastBackdrop. applyContrastBackdrop čita izračunatu boju elementa i prvu opaque pozadinu koju pronađe kroz getComputedStyle, a zatim koristi needsWhiteBackdrop iz packages/core/src/contrast.ts da odluči treba li postaviti style.backgroundColor = "white". contrast.ts pruža parseColour, relativeLuminance i contrastRatio.

Podesivi hostovi i ograničavanje sadržaja

SiteRule u packages/core/src/config.ts određuje GDJE annotation radi putem match i allFrames, te KOJI container je ograničava kroz contentRoot, zajedno s enabled i oznakom builtin. DEFAULT_SITES obuhvata Gmail, Proton sa allFrames i oba Outlook hosta. Config objedinjuje globalne toggle-ove, colours, font overrides i listu sites.

Mehanizam match patterna

packages/core/src/matchPattern.ts sadrži restriktivnu gramatiku MATCH_PATTERN: samo http i https, opcionalni *. wildcard host, glob path, bez porta, bez * scheme-a i bez <all_urls>. matchesPattern poredi URL s validiranim pattern-om i delegira path dio na pathGlobMatches. parsePatternParts dijeli pattern na host, path, scheme i wildcardHost.

Kada se pravila preklapaju, primjenjuje se pravilo "most specific wins". compareSiteSpecificity daje prednost tačnom hostu nad wildcard hostom, zatim dužem literal hostu, zatim literalnijem path-u preko literalPathLength, a na kraju koristi ASCII tiebreak. selectMostSpecific vraća najspecifičnije uključeno pravilo koje odgovara URL-u.

permissions.getAll() može prijaviti grant-ove u punoj WebExtension gramatici, kao što su <all_urls>, *://*/*, https://*/*, *://*.host/* i https://host/*) koje bi MATCH_PATTERN odbio. parseGrantedOrigin ih pretvara u GrantedOriginParts, originCovers provjerava pokrivenost preko match, a matchAllowsOriginFallback govori da li je path tačno ORIGIN_FALLBACK_PATH, odnosno /*.

Dinamička registracija

packages/webext/src/contentRegistration.ts registruje jedan dynamic content script po svakom pravilu koje je dodao korisnik. Built-in pravila opslužuju se preko static content_scripts unosa i nikada se ne registruju dinamički. desiredContentScripts filtrira config.sites, koristi isOriginGranted, originCovers i contentScriptId, te postavlja matchOriginAsFallback: true samo kada to dozvoljava matchAllowsOriginFallback(rule.match).

reconcileContentScriptsOnce čita dodijeljena porijekla, pohranjeni config i aktivne registracije te ih usklađuje kroz property-aware diff. sameRegistration normalizira i željeni descriptor i live read-back. Pošto ne postoji updateContentScripts, izmijenjeni script se prvo uklanja pa ponovo registruje. reconcileContentScripts koristi promise inFlight kako bi serijalizirao triggere koji se mogu dogoditi gotovo istovremeno.

Tok eksplicitne dozvole

Static manifesti deklarišu optional_host_permissions (http://*/*, https://*/*) kako bi korisnik u runtime-u mogao dodijeliti dozvolu za dodatni host. packages/webext/src/options/options.ts addSite validira cijelo kandidat-pravilo preko normaliseSiteRule, izvodi host origin preko matchOrigin i poziva browser.permissions.request iz click gesture-a na Add dugmetu. Tek nakon odobrenja dodaje red builtin:false.

Konfiguracija, pohrana i kanonski obrazac

Jedinstveni validacijski lijevak

normaliseConfig u packages/core/src/config.ts je jedina kanonska read putanja. On prisilno pretvara i ograničava svako polje, vraća se na zadanu vrijednost kada je unos neispravan i nikada ne baca exception. Sastavljen je od validatora po poljima kao što su normaliseBoolean, normaliseRenderMode, normaliseMaxLength, normaliseIgnoreHosts, normaliseMatchColour, normaliseMismatchColour, normaliseCssSize, normaliseFontWeight, normaliseContentRoot, normaliseMatchPattern i normaliseSites. normaliseSites izbacuje neispravne unose, uklanja duplikate po match, prisiljava builtin: true kada se pravilo poklapa s built-in pravilom i vraća sve ugrađene stavke koje nedostaju.

Pohrana

packages/webext/src/storage.ts obavija WebExtension storage API. configArea koristi browser.storage.sync kada je dostupan, a prelazi na browser.storage.local, na primjer u Thunderbirdu. getConfig, setConfig i onConfigChanged uvijek provlače vrijednosti kroz normaliseConfig, tako da pokvarena pohrana nikada ne može vratiti neispravan Config. Oba add-on oblika koriste isti lijevak nad vlastitim host storage-om.

Kanonska konfiguracija i ciljane izmjene

Tri write putanje čitaju kanonski config, mijenjaju tačno jednu stvar i zapisuju ga nazad, bez spremanja nedovršenih form edits:

toggleEnabled u packages/webext/src/toolbarAction.ts slijedi isti obrazac sa background strane.

Build po engine-u i ugovor manifesta

tooling/build.mjs pokreće jedan zajednički esbuild build za svaki WebExtension target. Descriptor TARGETS navodi SOURCE TARGET za svaki target. Chrome, Firefox i Thunderbird imaju vlastiti src/. Edge, Opera i Safari koriste chrome kao izvor. Outlook add-in ima poseban izvor. ACTIVE_TARGETS, odnosno Chrome, Edge, Firefox, Opera i Thunderbird, jesu oni koji se grade preko --all i --package. Safari i Outlook grade se samo kada su eksplicitno navedeni, a Gmail koristi make build-gmail.

Gmail bundle cilja Apps Script V8 runtime, koji nema ni ES modules ni native URL. Zato se gradi kao ESM, a zatim mu se uklanja završni export {…} kako bi trigger funkcije ostale globalne. Mali URL polyfill uključuje se samo kada je potreban. buildTarget čisti dist/<target>/, kopira manifest.json i icons/, bundla script-ove kao IIFE, options stranicu kao ESM i kopira options.html i options.css.

tooling/version.mjs utiskuje verzije. Uzima MAJOR.MINOR iz korijenskog package.json, pronalazi najveći BUILD broj i dodaje ga svakom target manifestu. Outlook add-in ima i manifest.json i manifest.xml. tooling/icons.mjs, pokrenut kroz make icons, rasterizira assets/icon.svg u icon48.png i icon128.png.

Manifest ugovor definiran je u datotekama extensions/<engine>/test/manifest.smoke.test.mjs, gdje se provjerava oblik svakog manifesta i postojanje svakog referenced file-a.

Internacionalizacija (i18n)

Reveal URLs je lokaliziran na mnogo jezika osim engleskog. Jedini izvor za taj skup je packages/core/src/locales.json, koji sadrži SUPPORTED_LOCALES, lokalna imena i zadani jezik. Svaki neengleski string je machine-translated i čeka ljudsku provjeru. Oznaka porijekla zavisi od formata: _locales je nose u polju description, site catalogues je bilježe u site/i18n/README.md, a prevedeni docs u prvoj liniji nose HTML komentar.

Postoje tri nezavisne lokalizacijske površine:

Potiskivanje „Active sites” u klijentu e-pošte (AD-3)

Mail client target kao što je Thunderbird već vidi sve rendered messages, pa je host-based editor za „Active sites” tu suvišan. Odluka se prenosi kroz polje mailClient: true u tooling/build.mjs.

Sigurnosna i privatnosna pozicija

Testiranje