7. listopadu 2018 Facebook, JavaScript, History API

Přibližně před dvěma týdny začal Facebook přidávat „sledovací“ parameter fbclid (Facebook click id?) do všech externích odkazů, které uživatelé sdílí. A mě se to nelíbilo, tak ten parametr skrývám.

Tenhle nový parametr byl nejspíš přidán proto, aby Facebook mohl obcházet omezení sledování třetími stranami a evidentně to funguje. Mozilla musela opravit jejich Facebook Container, který má právě bránit Facebooku ve sledování uživatelů skrz celý web. Mimo jiné to také například rozbíjí Cloudflare cachování.

Úprava historie

Já naštěstí takové problémy řešit nemusím, ale stejně se mi nelíbilo, že návštěvníci přicházející z Facebooku ten parametr v mých URL viděli. Chtěl jsem, aby jejich prohlížeče ukazovaly hezké a „čisté“ adresy, které by mohli kopírovat a sdílet dále. Ten parametr jsem mohl odstranit na serveru a poté návštěvníka přesměrovat na tu samou stránku. To by ale zabralo nějaký čas navíc a lidi přicházející z Facebooku by za to platili delším časem nahrávání stránek.

Místo toho jsem udělal něco jiného: tu část URL, která se mi nelíbí, prostě schovám. Žádný reloady, žádný přesměrování, žádný požadavky sem a odpovědi tam. Ke skrytí se dá použít history.replaceState() Pokud bych to měl fakt hodně zjednodušit, tak to změní některé části URL (ne, doménu ne), které uživatelé vidí v prohlížeči, aniž by se (znovu) načítala stránka. Metoda replaceState() má kámoše, pushState(), která dělá to samé, ale navíc vloží nový záznam do historie prohlížení stránek. Obě tyto metody mohou přidat „stav“, který bude poté dostupný v události popstate. Víc se dozvíte např. v MDN.

Odstranění fbclid 🚮

S těmito znalostmi jsem vytvořil kratičký kód v JavaScriptu, který se spustí co nejdříve a prostě jen změní URL v prohlížeči, aniž by se posílal nějaký požadavek nebo se někam přesměrovávalo:

(function() {
        var param = 'fbclid';
        if (location.search.indexOf(param + '=') !== -1) {
                var replace = '';
                try {
                        var url = new URL(location);
                        url.searchParams.delete(param);
                        replace = url.href;
                } catch (ex) {
                        var regExp = new RegExp('[?&]' + param + '=.*$');
                        replace = location.search.replace(regExp, '');
                        replace = location.pathname + replace + location.hash;
                }
                history.replaceState(null, '', replace);
        }
})();

Funguje to takto: pokud je v adrese parametr fbclid, tak se pomocí hezkého URL API odstraní. Pokud to selže, primárně kvůli tomu, že to API nepodporuje Internet Explorer, tak se parametr a jeho hodnota odstraní z konce URL pomocí regulárních výrazů. Nikde není psáno, že ten parametr bude vždy na konci URL, takže ten regulár by mohl odstranit i další parametry, ale poněvadž ten kód se použije jen pro Internet Explorer a poněvadž jsem nechtěl používat „dospělý“ parser toho za otazníčkem, tak je to akceptovatelné riziko, alespoň pro mě. Ve finále se stará adresa nahradí novou pomocí history.replaceState().

Šlo by to celé udělat jen pomocí regulárních výrazů, ale to URL API se mi líbí a rád bych ho používal v prohlížečích, které ho podporují. Další možností je Internet Explorer ignorovat a parametr fbclid v něm neskrývat, což mi přijde jako řešení s výborným poměrem cena/výkon. Pokud byste to tak chtěli udělat, tak kód v bloku if () {} nahraďte tímto fragmentem:

try {
        var url = new URL(location);
        url.searchParams.delete(param);
        history.replaceState(null, '', url.href);
} catch (ex) {
        // nic
}

Kód uložte do souboru pojmenovaného třeba remove-fbclid.js a nahrávejte ho s atributy async a defer, aby to nezdržovalo vykreslování stránky:

<script src="remove-fbclid.js" async defer></script>

Můžete to celé vyzkoušet přímo na tomto článku, do adresy přidejte ?fbclid=1337 a načtěte stránku. Kdybyste chtěli vidět úpravu URL a historie v akci na jakékoliv stránce, tak otevřete „developer tools“, jděte do konzole a spusťte např.:

history.replaceState(null, '', '/admin');

Řádek s adresou bude ukazovat jiné URL, ale obsah stránky zůstane beze změny. Ta nová adresa v podstatě nahradila tu starou. Když ve vašem browseru zmáčknete tlačítko „zpět“, tak se dostanete na adresu, ze které jste přišli na stránku, na které jste spustili history.replaceState(). Metoda history.pushState() funguje trochu jinak, ta přidá nový záznam do historie prohlížení. Využíváme toho např. na Report URI, když měníme URL podle toho, jaké vyberete filtry v seznamu reportů, což se hodí pro ukládání do záložek apod.

Nikomu nevěř

Mimochodem, dnes jste se tu měli naučit ještě jednu lekci: URL v řádku s adresou nemusí být to, na které prohlížeč poslal požadavek a které se ve skutečnosti načetlo. v kombinaci s útoky jako je Cross-Site Scripting je s tím je docela dost srandy. Na debugování stránek raději tedy používejte „developer tools“ a adrese, kterou vám prohlížeč ukazuje, moc nevěřte.

Analytika

Pokud máte aplikaci postavenou jako single-page application a změny URL po kliknutí jsou řešeny právě pomocí History API, tak by se mohlo stát, že po přidání mého kousku kódu se začnou návštěvy v přehledech duplikovat. Standardní analytics.js to tak dělat nebude, jiný nástroj by mohl. Řešení webové analytiky ale rád přenechám profíkům.

Zkuste alespoň přidat značku <link>atributem rel=canonical, která bude ukazovat na aktuální stránku, ale bez všech nepotřebných parametrů a tedy i bez fbclid. Říkají tomu self-referencing canonical tag a vyhledávačům to prý pomáhá pochopit, která adresa je ta správná pro aktuálně načtenou stránku. V mém oblíbeném frameworku Nette jsem v šabloně do <head> přidal tento řádek, který používá šikovné makro n:href:

<link rel="canonical" n:href="//this">

Přičemž //this nahrazuju něčím jiným, pokud je to potřeba, jako např. u přednášek, kde URL ukazuje přímo na jednotlivé slajdy. Ty jsou ale pořád jen částí jedné a té samé stránky.

Aktualizace článku

15.11. Přidán odstavec o analytice a canonical tagu

8.11. Kód v anonymní funkci, abychom si hráli jen na svým písečku

Michal Špaček

Vyvíjím webové aplikace, zajímá mě jejich bezpečnost. Nebojím se o tom mluvit veřejně, hledám hranice tak, že je posouvám. Chci naučit webové vývojáře stavět bezpečnější a výkonnější weby a aplikace.

Veřejná školení

Zvu vás na následující školení, která pořádám a vedu:

Bezpečnost PHP aplikací
(11.–12. prosince 2018 Praha)

HTTPS pro vývojáře a správce
(13. prosince 2018 Praha)