Doména .CZ slaví 25 let, začalo to souborem hosts.txt

21. 11. 2018
Doba čtení: 12 minut

Sdílet

Ilustrační obrázek
Autor: Depositphotos – stori
Ilustrační obrázek
Na tradiční konfrerenci sdružení CZ.NIC se letos slavilo 20 let jeho existence. Na programu ale nechyběla ani technická témata z oblasti programování a kybernetické bezpečnosti.

Čtyři pětiletky CZ.NIC

Na začátku nebylo nic, potom vznikl Internet a soubor hosts.txt, který si všechny počítače každý týden stahují. Ten soubor dodnes funguje, ve Windows jej najdete v  C:\Windows\System32\drivers\etc\hosts, na unixech /etc/hosts. Soubor spravovala organizace InterNIC, od jejíhož jména je odvozeno i jméno sdružení CZ.NIC, které právě slaví 20 let. U této příležitosti připravil Ondřej Filip shrnutí vývoje správce české domény ve čtyřech pětiletkách. „Zajímavé je, že každá pětiletka se vyznačuje také změnou loga sdružení.“

Systém DNS měl prvních několik let na starost Jon Postel, vymyslel například použití dvoupísmenných domén nejvyšší úrovně podle zkratek jednotlivých států. Na formální procesy si příliš nepotrpěl, pokud znal osobně někoho z dané země, prostě na něj delegoval doménu. Takto jednoduše se doména .cz octla ve správě Jiřího Orsága, zaměstnance výpočetního centra VŠCHT. Později přešel do soukromé společnosti a správa domény .cz s ním. V té době byl také iniciován vznik CZ-NIC, protože správa domény byla tehdy náročná a nevýdělečná činnost. Sdružení vzniklo 27. května 1998. První valnou hromadu svolával Jan Gruntorád, který dodnes působí jako ředitel sdružení CESNET.

V první pětiletce, mezi roky 1998 a 2002, se sdružení CZ-NIC teprve rodilo. Bylo zároveň technickým správcem i jediným registrátorem, vlastní provoz registru byl ale outsourcován organizaci EUnet, která později několikrát změnila jméno. Na přelomu roku 1999 bylo rozhodnuto domény zpoplatnit částkou 1600 Kč bez DPH. To vedlo v únoru 2000 k rušení sedmi z celkového počtu 41 tisíc domén, ty se však brzy objevily znovu. Postupně docházelo k uvolňování pravidel. S tím souvisí dražba později uvolněných jmen v březnu 2002. Nejzajímavější byl souboj o doménu cd.cz, o kterou měly zájem jak České dráhy, tak i jakýsi obchod s kompaktními disky.

První pětiletka se vyznačovala velkým množstvím ruční práce, outsourcingem správy externí firmě a třemi zaměstnanci, ze kterých dva jsou stále v CZ.NIC – Martin Peterka a Zuzana Durajová. Velkou roli mělo tehdy představenstvo, které rozhodovalo spory o doménová jména. To na konci éry, kdy měl registr 170 tisíc domén, rozhodlo po vzoru zahraničních registrů přejít na systém DSD – Distribuovaná Správa Domény.

Druhá pětiletka, mezi lety 2003 a 2008, by se mohla nazývat dobou přerodu. Byla poměrně neklidná, docházelo k intenzivnímu lobbingu. Spoustě členů se nelíbilo, že provoz registračního systému je stále outsourcován. Na valné hromadě v 25. listopadu 2003 došlo k nepříliš povedenému převratu – odvolání dvou členů pětičlenného představenstva, jedno uvolněné místo obsadil Ondřej Filip. Na druhé byl v březnu 2004 dovolen Karel Taft, takže reformisté, mezi které patřil i stávající člen představenstva Tomáš Maršálek, začali mít mírnou převahu. V prosinci 2004 došlo k rezignaci Jiřího Orsága a Petra Krále, nahradil je Michal Krsek a Marek Antoš. Posléze rezignoval i ředitel sdružení Jiří Dohnal, řízením byl pověřen Ondřej Filip. Ten byl v červnu 2005 potvrzen ve funkci ředitele. Na valné hromadě bylo dohodnuto změnit strategii, vytvořit vlastní registrační systém a upravit organizační strukturu sdružení – založit trojici komor a kolegium.

V dubnu 2006 bylo také podepsáno memorandum s ministerstvem informatiky, čímž stát uznal roli sdružení CZ.NIC ve správě domény .cz. Začal se rodit registrační systém DSDng, později pojmenovaný FRED. Ten byl otestován v roce 2006 na systému ENUM, což se ukázalo jako výhodné pro beta test. K migraci domény .cz na FRED došlo o prodlouženém víkendu 28. – 30. září 2007. Byly dva migrační programy, oba musely doběhnout a dávat stejná data. Nový systém byl spuštěn prvního října 2007 v 10:00, cena domén klesla na polovinu. Na konci druhé pětiletky měl CZ.NIC 21 zaměstnanců, všechny služby si zajišťoval sám.

Třetí pětiletku, období let 2008 – 2012, je možné nazvat dobou softwaru. Počet domén rychle rostl, jejich správa byla automatizovaná, a tak v rozpočtu sdružení začalo přebývat velké množství peněz. V roce 2008 bylo rozhodnuto o změně strategie sdružení, které se nově mělo věnovat podpoře internetové infrastruktury, bezpečnosti a stability internetu, podpoře nových technologií a osvětové činnosti. Výsledkem je vznik laboratoří CZ.NIC, které jako první převzaly open source směrovací démon BIRD, zavádění technologie DNSSEC, nebo třeba spuštění Edice, Akademie a konference Internet a technologie. V roce 2010 přišlo rozšíření do Brna, bylo spuštěno mojeID. Prvního listopadu 2011 přešel ze sdružení CESNET pod správu sdružení CZ.NIC národní bezpečnostní tým CSIRT.cz. Na konci roku 2012 mělo sdružení 54 zaměstnanců a milion registrovaných domén.

Čtvrtá pětiletka, mezi roky 2013 a 2018, by se dala nazvat dobou hardware. Projekty vyspěly, některé se daly komercializovat. V prosinci 2012 byl schválen projekt sdílené kybernetické ochrany, ze kterého se stal router Turris, také začal vývoj hry Tablexia či kampaň Jak na Internet. Projekt Turris se dočkal pokračování v podobě úspěšných crowdfundingových kampaní na Turris Omnia a Turris Mox.

Došlo také na politické aktivity, boj proti návrhu ACTA, akci Přichází cenzor a kampaň proti novému zákonu o vojenském zpravodajství. CZ.NIC má nyní 120 zaměstnanců a 1,3 milionu domén a správa domény jde trochu do pozadí. Víc než pětinu příjmů sdružení už netvoří poplatky z registrace domén, ale například komerční podpora open source projektů, které ve sdružení vyvíjejí.

Cesta do pravěku domény .cz

„Taky jsem se ucházel o místo v CZ.NIC a nakonec jsem rád, že jsem to tehdy nevyhrál,“ řekl na úvod Jakub Ditrich, zakladatel společnosti Globe Internet, kterou později prodal společnosti Active 24. Vzpomněl na okamžik první změny pravidel v listopadu 1997, kdy byla registrace domén z velké míry uvolněna. Společnost spustila portál domeny.cz, spustila velkou kampaň a zaregistrovala i některá slovníková slova za účelem spekulace. Nebyly ale zdaleka sami, stejný nápad mělo i spousta dalších internetových podnikatelů. „Nejvíce jsme vydělali na prodeji domény sex.cz,“ vzpomíná na Jakub Ditrich.

Další známý internetový podnikatel, Josef Grill, se k registraci domén dostal vlastně omylem: „Během studií na právnické fakultě jsem chtěl registrovat doménu pro sebe, ale než jsem stihl vyřídit všechny papíry, zaregistroval ji někdo jiný. To se opakovalo vícekrát, až jsem se naučil registrovat domény sám. Pak to po mně chtěli i přátelé a na konci studií už jsem provozoval webhostingovou firmu.“ Ve svém příspěvku připomněl zajímavé momenty, které zažil s českou doménou ve společnostech Forpsi a WEDOS.

„Jeden kolega testoval funkčnost registračního systému skriptem, který se neustále pokoušel registrovat doménu nic.cz . Kvůli nějaké krátkodobé chybě na straně registru toto prošlo a stali jsme se tak na chvíli jejím držitelem.“ Situaci se však podařilo velmi rychle napravit.

O něco horší situace byla s doménou dpmb.cz. Z důvodu technické chyby ji její registrátor v září 2011 zrušil registraci. Když si toho všiml náhodný návštěvník, okamžitě doménu zaregistroval právě u hostingu WEDOS. „Začaly se dít věci. Na doménu byly navázány jízdní řády, zastávkové informační panely a podobně. Původní registrátor začal vyvíjet nátlak, že doménu musíme zákazníkovi sebrat a vrátit zpět.“ Nakonec došlo pouze k nastavení původních DNS serverů a vlastní převod domény na brněnský dopravní podnik byl posvěcen dohodou s novým držitelem. Mimochodem, právě tento incident pomohl otevření dat o poloze vozidel MHD v Brně, jak prozradil Martin Čežíkpřednášce na konferenci OpenAlt 2014.

Josef Grill zmínil i vlastní průšvih s DNSSEC z dubna 2017, kdy se nepodařilo prodloužit platnost podpisů. „Opravu jsme měli během hodiny, ale dalších několik hodin trvalo přegenerovat 170 tisíc zón.“

Milan Behro přidal vzpomínky z pohledu společnosti Zoner. Ta byla založena v roce 1993 a už v roce 1996 se začala seznamovat s internetem. „Asi jsme byly první komerční specializovaná webhostingová firma v ČR.“ První problémy byly s umístěním serverů. „První servery jsme měli v USA. Vzhledem k tomu, že v ČR neexistoval peering a většina provozu šla přes USA, byla to výhodná pozice.“ Až do roku 2000 šlo o hosting na Windows, teprve později byly přidány linuxové servery.

Přidal i vzpomínku na časté spory o doménová jména v devadesátých letech: „Náš zákazník například měl doménu radegast.cz , nějak se nepohodl s Nomurou (tehdejší majitel pivovaru) a Jiří Orság jednoho dne doménu předelegoval, bez vědomí nás či našeho zákazníka.“

V roce 1996 založili v Zoneru také český informační server na doméně www.czechia.com, který měl sloužit jako univerzální webová stránka pro české podniky. „Lidé nevěděli, co internet je, běžně jsme registrovali domény a zřizovali hostingové služby pomocí papírových formulářů. Také jsme vydávali osvětové knihy, abychom lidem vysvětlovali, co vlastně internet je, a proč by se na něm lidé měli prezentovat.“

Česká veřejná správa a její domény

Zjistit, které všechny domény má veřejná správa, je netriviální úkol i s přístupem k registru. Jiří Průša objevil celkem asi 7200 doménových jmen veřejné správy. K hledání použil především databázi datových schránek. Nejoblíbenější koncovkou je .cz, oblíbená je i .eu. Zajímavým příkladem je obec Babice. Těch je v ČR celkem šest, ale ani jedna nepoužívá doménu  babice.cz.

Co se týká ministerstev, nejvíce domén má ministerstvo vnitra – celkem 158. Z toho 77 domén je pouze variací domény mojedatovaschranka.cz s nejrůznějšími překlepy. Zřejmě jde o ochranu před typosquattingem. Zajímavé je, že státní správě nechybí kreativita. Třeba doména kupsilup.cz nabízí zboží zabavené z trestné činnosti.

Převod webových aplikací na Python 3

Python 3 je s námi už od roku 2007, verze pro produkční nasazení od roku 2008. V roce 2020 skončí podpora Pythonu 2.7, je tedy dobré do té doby přejít. Pro automatickou konverzi jsou k dispozici nástroje jako 2to3, futurize, modernize, knihovny jako six, futurepast.

Důležitá je podpora Python 3 v podpůrných knihovnách. To byl dříve problém, situace se ale výrazně zlepšuje. 349 z 360 nejpoužívanějších aplikací má podporu pro Python 3. Svůj projekt můžete ověřit utilitkou caniusepython3. Klíčové je také testování. Dobré testy odhalí případné problémy při konverzi kódu. Rozhodně je dobré také předtím provést refactoring a odstranit zastaralé a nepotřebné kusy kódu.

CZ.NIC má zhruba 29 projektů, celkem asi sto tisíc řádků kódu. Projekty mají na sobě netriviální závislosti, také zhruba 50 externích závislostí. Z toho důvodu nešlo snadno přejít na Python 3 ostrým střihem, místo toho bylo potřeba postupně upravit všechny balíčky, aby fungovaly s Pythonem 2 i 3.

Nejdřív pomocí utilityisort hromadně zapnuli podporu unicode_literals . Tím se všechny řetězce staly unicodovými. Nástrojautopep8 pak opravil syntaxi tak, aby byla kompatibilní s oběma verzemi Pythonu. Testování obstaral nástroj tox , který dokáže automaticky otestovat kód v mnoha různých verzích Pythonu. Vlastní převedený kód pak používá knihovnu six . Všechny výskytyunicode astr je třeba nahradit zasix.text_type a six.binary_type . Taky je potřeba všechny binární soubory důsledně otevírat v binárním režimu.

Při psaní univerzálních zdrojových kódů by vždy měl být Python 2 speciální případ, Python 3 výchozí volba, ne naopak. To proto, že až/pokud vyjde Python verze 4, měl by běžet kód pro Python 3, nikoli ten pro Python 2; dá se předpokládat, že Python 4 bude syntakticky bližší Pythonu 3. „Konverze mezi textem a bajty by měla probíhat na hranicích kódu a vždy je dobré detailně dokumentovat rozhraní jednotlivých částí kódu,“ uzavřel svou přednášku Tomáš Pazderka.

Novinky v Knot DNS a Knot Resolver

U každého software je po čase potřeba provést refactoring a údržbu kódu. „Ve verzi 2.7 jsme provedli velkou údržbu knihoven, odstranili jsme přežitý kód. Snažili jsme se taky kód zrychlit,“ otevřel svou přednášku Daniel Salzman. Knihovny kromě Knot DNS používá i projekt Knot Resolver. Při refactoringu byly také zohledněny výsledky nedávného bezpečnostního auditu, který sice dopadl dobře, ale přesto měl několik doporučení. Bylo také odstraněno několik vrstev kompatibility se starými verzemi. „Doporučujeme proto přemigrovat nejméně na verzi 2.6. Odtud už je upgrade přímočarý.“

Knot také lépe využívá Linux Capabilities, namísto používání účtu root. Může tak být spuštěn pod neprivilegovaným uživatelem a ihned po otevření portu 53 se vzdát i této pravomoci. Jde však o čistě linuxovou funkci, na ostatních operačních systémech není k dispozici.

Knot 2.7 nabízí nové moduly: Query ACL slouží pro omezování dostupnosti zóny z konkrétních IP adres, modul GeoIP umožňuje úpravy DNS odpovědí na základě IP adresy nebo geografické polohy klienta. Nový modul implementuje protokol DNS Cookies. Ty se snaží do DNS přinést transakce. „V naší implementaci se při použití Cookies vypíná RRL, protože je zaručené, že zdrojová adresa není zfalšovaná.“ Změny se také dočkal modul pro on-line podepisování, umí rotovat klíče stejně jako při běžném podepisování.

Při obrovském množství malých zón se objevil problém s funkcí malloc v Glibc. Paměť se fragmentuje a může dojít až k jejímu vyčerpání. Pro náročné nasazení proto vývojáři nedoporučují malloc, ale například jemalloc, nebo  TCMalloc.

Knot se také stal součástí projektu OSS-Fuzz. Ten provádí kontinuální fuzzing open source softwaru. Objevené chyby jsou zveřejněné nejpozději po 90 dnech – to motivuje vývojáře se o svůj projekt dobře starat. „Když jsme testování spustili, objevily se pouze tři malé problémy v knihovně libzscanner, které však neměly ani velký význam z pohledu bezpečnosti.“

Knot Resolver má nově podporu agresivního kešování pro zóny s NSEC3. Nový je také modul pro přednačtení kořenové zóny, který si přáli velcí provozovatelé. Naopak byly odstraněny moduly DNS Cookies a Version – šlo o velmi ranou implementaci DNS cookies, která nepracovala v souladu s aktuálním standardem a je třeba ji přepsat.

Hledání anomálií v DNS provozu

Projekt Advanced DNS Analysis and Measurements představil Maciej Andziński. Cílem projektu je objevit anomálie v DNS provozu autoritativních serverů domény .cz. Data se sbírají pomocí PCAP a posílají se do Hadoop clusteru, kde se nad nimi provádí analýzy různými metodami strojového učení. Data se agregují, následně se na nich počítají nejrůznější statistiky, například amplifikační faktor, entropie zdrojových portů a hodnoty query id, použité příznaky a další. Celkem se počítá 18 parametrů.

Vyhodnocení anomálního provozu probíhá porovnáním vzdálenosti určitých parametrů od průměrné hodnoty; pro analýzu dvou parametrů je možné hodnoty vynést do grafu a následně jako anomální vyhodnotit každý provoz, který vybočí z normálních hodnot stanovených kruhem kolem většinové hodnoty. Pro 18 hodnocených parametrů systém pracuje obdobně, jen jde o 18rozměrný prostor, kde hranici mezi normálním a anomálním provozem tvoří 18rozměrné těleso.

Pro získání dat z reálných DNS resolverů byl použit systém RIPE Atlas. Po naučení systému drtivá většina reálných DNS resolverů nebyla vyhodnocena jako anomálie, naopak známé skenery typu zonemaster byly okamžitě detekovány jako skenery. Jedinou výjimkou byl nástroj DNSviz, který zřejmě velmi věrně imituje chování skutečného resolveru.

Ukázalo se, že systém má smysl, hned první výsledky ukázaly na bezpečnostní problém u jednoho DNS operátora, což zároveň brání všechny výsledky ihned zveřejnit. Bylo ale možné zveřejnit výsledky pro autonomní systém 25192, který patří sdružení CZ.NIC, které je samo o sobě pátým největším konzumentem dat z .cz  domény. Jako anomálie byly vyhodnoceny monitorovací systémy jako Icinga, také RIPE Atlas Anchor hostovaný v CZ.NIC. Naopak zkušební resolvery hostované v CZ.NIC byly stoprocentně vyhodnoceny jako resolvery.

Autor článku

Ondřej Caletka vystudoval obor Telekomunikační technika na ČVUT a dnes pracuje ve vzdělávacím oddělení RIPE NCC, mezinárodní asociaci koordinující internetové sítě.

'; document.getElementById('preroll-iframe').onload = function () { setupIframe(); } prerollContainer = document.getElementsByClassName('preroll-container-iframe')[0]; } function setupIframe() { prerollDocument = document.getElementById('preroll-iframe').contentWindow.document; let el = prerollDocument.createElement('style'); prerollDocument.head.appendChild(el); el.innerText = "#adContainer>div:nth-of-type(1),#adContainer>div:nth-of-type(1) > iframe { width: 99% !important;height: 99% !important;max-width: 100%;}#videoContent,body{ width:100vw;height:100vh}body{ font-family:'Helvetica Neue',Arial,sans-serif}#videoContent{ overflow:hidden;background:#000}#adMuteBtn{ width:35px;height:35px;border:0;background:0 0;display:none;position:absolute;fill:rgba(230,230,230,1);bottom:20px;right:25px}"; videoContent = prerollDocument.getElementById('contentElement'); videoContent.style.display = 'none'; videoContent.volume = 1; videoContent.muted = false; const playPromise = videoContent.play(); if (playPromise !== undefined) { playPromise.then(function () { console.log('PREROLL sound allowed'); // setUpIMA(true); videoContent.volume = 1; videoContent.muted = false; setUpIMA(); }).catch(function () { console.log('PREROLL sound forbidden'); videoContent.volume = 0; videoContent.muted = true; setUpIMA(); }); } } function setupDimensions() { prerollWidth = Math.min(iinfoPrerollPosition.offsetWidth, 480); prerollHeight = Math.min(iinfoPrerollPosition.offsetHeight, 320); } function setUpIMA() { google.ima.settings.setDisableCustomPlaybackForIOS10Plus(true); google.ima.settings.setLocale('cs'); google.ima.settings.setNumRedirects(10); // Create the ad display container. createAdDisplayContainer(); // Create ads loader. adsLoader = new google.ima.AdsLoader(adDisplayContainer); // Listen and respond to ads loaded and error events. adsLoader.addEventListener( google.ima.AdsManagerLoadedEvent.Type.ADS_MANAGER_LOADED, onAdsManagerLoaded, false); adsLoader.addEventListener( google.ima.AdErrorEvent.Type.AD_ERROR, onAdError, false); // An event listener to tell the SDK that our content video // is completed so the SDK can play any post-roll ads. const contentEndedListener = function () { adsLoader.contentComplete(); }; videoContent.onended = contentEndedListener; // Request video ads. const adsRequest = new google.ima.AdsRequest(); adsRequest.adTagUrl = iinfoVastUrls[iinfoVastUrlIndex]; console.log('Preroll advert: ' + iinfoVastUrls[iinfoVastUrlIndex]); videoContent.muted = false; videoContent.volume = 1; // Specify the linear and nonlinear slot sizes. This helps the SDK to // select the correct creative if multiple are returned. // adsRequest.linearAdSlotWidth = prerollWidth; // adsRequest.linearAdSlotHeight = prerollHeight; adsRequest.nonLinearAdSlotWidth = 0; adsRequest.nonLinearAdSlotHeight = 0; adsLoader.requestAds(adsRequest); } function createAdDisplayContainer() { // We assume the adContainer is the DOM id of the element that will house // the ads. prerollDocument.getElementById('videoContent').style.display = 'none'; adDisplayContainer = new google.ima.AdDisplayContainer( prerollDocument.getElementById('adContainer'), videoContent); } function unmutePrerollAdvert() { adVolume = !adVolume; if (adVolume) { adsManager.setVolume(0.3); prerollDocument.getElementById('adMuteBtn').innerHTML = ''; } else { adsManager.setVolume(0); prerollDocument.getElementById('adMuteBtn').innerHTML = ''; } } function onAdsManagerLoaded(adsManagerLoadedEvent) { // Get the ads manager. const adsRenderingSettings = new google.ima.AdsRenderingSettings(); adsRenderingSettings.restoreCustomPlaybackStateOnAdBreakComplete = true; adsRenderingSettings.loadVideoTimeout = 12000; // videoContent should be set to the content video element. adsManager = adsManagerLoadedEvent.getAdsManager(videoContent, adsRenderingSettings); // Add listeners to the required events. adsManager.addEventListener(google.ima.AdErrorEvent.Type.AD_ERROR, onAdError); adsManager.addEventListener( google.ima.AdEvent.Type.CONTENT_PAUSE_REQUESTED, onContentPauseRequested); adsManager.addEventListener( google.ima.AdEvent.Type.CONTENT_RESUME_REQUESTED, onContentResumeRequested); adsManager.addEventListener( google.ima.AdEvent.Type.ALL_ADS_COMPLETED, onAdEvent); // Listen to any additional events, if necessary. adsManager.addEventListener(google.ima.AdEvent.Type.LOADED, onAdEvent); adsManager.addEventListener(google.ima.AdEvent.Type.STARTED, onAdEvent); adsManager.addEventListener(google.ima.AdEvent.Type.COMPLETE, onAdEvent); playAds(); } function playAds() { // Initialize the container. Must be done through a user action on mobile // devices. videoContent.load(); adDisplayContainer.initialize(); // setupDimensions(); try { // Initialize the ads manager. Ad rules playlist will start at this time. adsManager.init(1920, 1080, google.ima.ViewMode.NORMAL); // Call play to start showing the ad. Single video and overlay ads will // start at this time; the call will be ignored for ad rules. adsManager.start(); // window.addEventListener('resize', function (event) { // if (adsManager) { // setupDimensions(); // adsManager.resize(prerollWidth, prerollHeight, google.ima.ViewMode.NORMAL); // } // }); } catch (adError) { // An error may be thrown if there was a problem with the VAST response. // videoContent.play(); } } function onAdEvent(adEvent) { const ad = adEvent.getAd(); console.log('Preroll event: ' + adEvent.type); switch (adEvent.type) { case google.ima.AdEvent.Type.LOADED: if (!ad.isLinear()) { videoContent.play(); } prerollDocument.getElementById('adContainer').style.width = '100%'; prerollDocument.getElementById('adContainer').style.maxWidth = '640px'; prerollDocument.getElementById('adContainer').style.height = '360px'; break; case google.ima.AdEvent.Type.STARTED: window.addEventListener('scroll', onActiveView); if (ad.isLinear()) { intervalTimer = setInterval( function () { // Example: const remainingTime = adsManager.getRemainingTime(); // adsManager.pause(); }, 300); // every 300ms } prerollDocument.getElementById('adMuteBtn').style.display = 'block'; break; case google.ima.AdEvent.Type.ALL_ADS_COMPLETED: if (ad.isLinear()) { clearInterval(intervalTimer); } if (prerollLastError === 303) { playYtVideo(); } break; case google.ima.AdEvent.Type.COMPLETE: if (ad.isLinear()) { clearInterval(intervalTimer); } playYtVideo(); break; } } function onAdError(adErrorEvent) { console.log(adErrorEvent.getError()); prerollLastError = adErrorEvent.getError().getErrorCode(); if (!loadNext()) { playYtVideo(); } } function loadNext() { iinfoVastUrlIndex++; if (iinfoVastUrlIndex < iinfoVastUrls.length) { iinfoPrerollPosition.remove(); playPrerollAd(); } else { return false; } adVolume = 1; return true; } function onContentPauseRequested() { videoContent.pause(); } function onContentResumeRequested() { videoContent.play(); } function onActiveView() { if (prerollContainer) { const containerOffset = prerollContainer.getBoundingClientRect(); const windowHeight = window.innerHeight; if (containerOffset.top < windowHeight/1 && containerOffset.bottom > 0.0) { if (prerollPaused) { adsManager.resume(); prerollPaused = false; } return true; } else { if (!prerollPaused) { adsManager.pause(); prerollPaused = true; } } } return false; } function playYtVideo() { iinfoPrerollPosition.remove(); youtubeIframe.style.display = 'block'; youtubeIframe.src += '&autoplay=1&mute=1'; } }
'; document.getElementById('outstream-iframe').onload = function () { setupIframe(); } replayScreen = document.getElementById('iinfoOutstreamReplay'); iinfoOutstreamPosition = document.getElementById('iinfoOutstreamPosition'); outstreamContainer = document.getElementsByClassName('outstream-container')[0]; setupReplayScreen(); } function setupIframe() { outstreamDocument = document.getElementById('outstream-iframe').contentWindow.document; let el = outstreamDocument.createElement('style'); outstreamDocument.head.appendChild(el); el.innerText = "#adContainer>div:nth-of-type(1),#adContainer>div:nth-of-type(1) > iframe { width: 99% !important;height: 99% !important;max-width: 100%;}#videoContent,body{ width:100vw;height:100vh}body{ font-family:'Helvetica Neue',Arial,sans-serif}#videoContent{ overflow:hidden;background:#000}#adMuteBtn{ width:35px;height:35px;border:0;background:0 0;display:none;position:absolute;fill:rgba(230,230,230,1);bottom:-5px;right:25px}"; videoContent = outstreamDocument.getElementById('contentElement'); videoContent.style.display = 'none'; videoContent.volume = 1; videoContent.muted = false; if ( location.href.indexOf('rejstriky.finance.cz') !== -1 || location.href.indexOf('finance-rejstrik') !== -1 || location.href.indexOf('firmy.euro.cz') !== -1 || location.href.indexOf('euro-rejstrik') !== -1 || location.href.indexOf('/rejstrik/') !== -1 || location.href.indexOf('/rejstrik-firem/') !== -1) { outstreamDirectPlayed = true; soundAllowed = true; iinfoVastUrlIndex = 0; } if (!outstreamDirectPlayed) { console.log('OUTSTREAM direct'); setUpIMA(true); } else { if (soundAllowed) { const playPromise = videoContent.play(); if (playPromise !== undefined) { playPromise.then(function () { console.log('OUTSTREAM sound allowed'); setUpIMA(false); }).catch(function () { console.log('OUTSTREAM sound forbidden'); renderBanner(); }); } } else { renderBanner(); } } } function getWrapper() { let articleWrapper = document.querySelector('.rs-outstream-placeholder'); // Outstream Placeholder from RedSys manipulation if (articleWrapper && articleWrapper.style.display !== 'block') { articleWrapper.innerHTML = ""; articleWrapper.style.display = 'block'; } // Don't render OutStream on homepages if (articleWrapper === null) { if (document.querySelector('body.p-index')) { return null; } } if (articleWrapper === null) { articleWrapper = document.getElementById('iinfo-outstream'); } if (articleWrapper === null) { articleWrapper = document.querySelector('.layout-main__content .detail__article p:nth-of-type(6)'); } if (articleWrapper === null) { // Euro, Autobible, Zdravi articleWrapper = document.querySelector('.o-article .o-article__text p:nth-of-type(6)'); } if (articleWrapper === null) { articleWrapper = document.getElementById('sidebar'); } if (!articleWrapper) { console.error("Outstream wrapper of article was not found."); } return articleWrapper; } function setupDimensions() { outstreamWidth = Math.min(iinfoOutstreamPosition.offsetWidth, 480); outstreamHeight = Math.min(iinfoOutstreamPosition.offsetHeight, 320); } /** * Sets up IMA ad display container, ads loader, and makes an ad request. */ function setUpIMA(direct) { google.ima.settings.setDisableCustomPlaybackForIOS10Plus(true); google.ima.settings.setLocale('cs'); google.ima.settings.setNumRedirects(10); // Create the ad display container. createAdDisplayContainer(); // Create ads loader. adsLoader = new google.ima.AdsLoader(adDisplayContainer); // Listen and respond to ads loaded and error events. adsLoader.addEventListener( google.ima.AdsManagerLoadedEvent.Type.ADS_MANAGER_LOADED, onAdsManagerLoaded, false); adsLoader.addEventListener( google.ima.AdErrorEvent.Type.AD_ERROR, onAdError, false); // An event listener to tell the SDK that our content video // is completed so the SDK can play any post-roll ads. const contentEndedListener = function () { adsLoader.contentComplete(); }; videoContent.onended = contentEndedListener; // Request video ads. const adsRequest = new google.ima.AdsRequest(); if (direct) { adsRequest.adTagUrl = directVast; console.log('Outstream DIRECT CAMPAING advert: ' + directVast); videoContent.muted = true; videoContent.volume = 0; outstreamDirectPlayed = true; } else { adsRequest.adTagUrl = iinfoVastUrls[iinfoVastUrlIndex]; console.log('Outstream advert: ' + iinfoVastUrls[iinfoVastUrlIndex]); videoContent.muted = false; videoContent.volume = 1; } // Specify the linear and nonlinear slot sizes. This helps the SDK to // select the correct creative if multiple are returned. // adsRequest.linearAdSlotWidth = outstreamWidth; // adsRequest.linearAdSlotHeight = outstreamHeight; adsRequest.nonLinearAdSlotWidth = 0; adsRequest.nonLinearAdSlotHeight = 0; adsLoader.requestAds(adsRequest); } function setupReplayScreen() { replayScreen.addEventListener('click', function () { iinfoOutstreamPosition.remove(); iinfoVastUrlIndex = 0; outstreamInit(); }); } /** * Sets the 'adContainer' div as the IMA ad display container. */ function createAdDisplayContainer() { // We assume the adContainer is the DOM id of the element that will house // the ads. outstreamDocument.getElementById('videoContent').style.display = 'none'; adDisplayContainer = new google.ima.AdDisplayContainer( outstreamDocument.getElementById('adContainer'), videoContent); } function unmuteAdvert() { adVolume = !adVolume; if (adVolume) { adsManager.setVolume(0.3); outstreamDocument.getElementById('adMuteBtn').innerHTML = ''; } else { adsManager.setVolume(0); outstreamDocument.getElementById('adMuteBtn').innerHTML = ''; } } /** * Loads the video content and initializes IMA ad playback. */ function playAds() { // Initialize the container. Must be done through a user action on mobile // devices. videoContent.load(); adDisplayContainer.initialize(); // setupDimensions(); try { // Initialize the ads manager. Ad rules playlist will start at this time. adsManager.init(1920, 1080, google.ima.ViewMode.NORMAL); // Call play to start showing the ad. Single video and overlay ads will // start at this time; the call will be ignored for ad rules. adsManager.start(); // window.addEventListener('resize', function (event) { // if (adsManager) { // setupDimensions(); // adsManager.resize(outstreamWidth, outstreamHeight, google.ima.ViewMode.NORMAL); // } // }); } catch (adError) { // An error may be thrown if there was a problem with the VAST response. // videoContent.play(); } } /** * Handles the ad manager loading and sets ad event listeners. * @param { !google.ima.AdsManagerLoadedEvent } adsManagerLoadedEvent */ function onAdsManagerLoaded(adsManagerLoadedEvent) { // Get the ads manager. const adsRenderingSettings = new google.ima.AdsRenderingSettings(); adsRenderingSettings.restoreCustomPlaybackStateOnAdBreakComplete = true; adsRenderingSettings.loadVideoTimeout = 12000; // videoContent should be set to the content video element. adsManager = adsManagerLoadedEvent.getAdsManager(videoContent, adsRenderingSettings); // Add listeners to the required events. adsManager.addEventListener(google.ima.AdErrorEvent.Type.AD_ERROR, onAdError); adsManager.addEventListener( google.ima.AdEvent.Type.CONTENT_PAUSE_REQUESTED, onContentPauseRequested); adsManager.addEventListener( google.ima.AdEvent.Type.CONTENT_RESUME_REQUESTED, onContentResumeRequested); adsManager.addEventListener( google.ima.AdEvent.Type.ALL_ADS_COMPLETED, onAdEvent); // Listen to any additional events, if necessary. adsManager.addEventListener(google.ima.AdEvent.Type.LOADED, onAdEvent); adsManager.addEventListener(google.ima.AdEvent.Type.STARTED, onAdEvent); adsManager.addEventListener(google.ima.AdEvent.Type.COMPLETE, onAdEvent); playAds(); } /** * Handles actions taken in response to ad events. * @param { !google.ima.AdEvent } adEvent */ function onAdEvent(adEvent) { // Retrieve the ad from the event. Some events (for example, // ALL_ADS_COMPLETED) don't have ad object associated. const ad = adEvent.getAd(); console.log('Outstream event: ' + adEvent.type); switch (adEvent.type) { case google.ima.AdEvent.Type.LOADED: // This is the first event sent for an ad - it is possible to // determine whether the ad is a video ad or an overlay. if (!ad.isLinear()) { // Position AdDisplayContainer correctly for overlay. // Use ad.width and ad.height. videoContent.play(); } outstreamDocument.getElementById('adContainer').style.width = '100%'; outstreamDocument.getElementById('adContainer').style.maxWidth = '640px'; outstreamDocument.getElementById('adContainer').style.height = '360px'; break; case google.ima.AdEvent.Type.STARTED: window.addEventListener('scroll', onActiveView); // This event indicates the ad has started - the video player // can adjust the UI, for example display a pause button and // remaining time. if (ad.isLinear()) { // For a linear ad, a timer can be started to poll for // the remaining time. intervalTimer = setInterval( function () { // Example: const remainingTime = adsManager.getRemainingTime(); // adsManager.pause(); }, 300); // every 300ms } outstreamDocument.getElementById('adMuteBtn').style.display = 'block'; break; case google.ima.AdEvent.Type.ALL_ADS_COMPLETED: if (ad.isLinear()) { clearInterval(intervalTimer); } if (outstreamLastError === 303) { if (isBanner) { renderBanner(); } else { replayScreen.style.display = 'flex'; } } break; case google.ima.AdEvent.Type.COMPLETE: // This event indicates the ad has finished - the video player // can perform appropriate UI actions, such as removing the timer for // remaining time detection. if (ad.isLinear()) { clearInterval(intervalTimer); } if (isBanner) { renderBanner(); } else { replayScreen.style.display = 'flex'; } break; } } /** * Handles ad errors. * @param { !google.ima.AdErrorEvent } adErrorEvent */ function onAdError(adErrorEvent) { // Handle the error logging. console.log(adErrorEvent.getError()); outstreamLastError = adErrorEvent.getError().getErrorCode(); if (!loadNext()) { renderBanner(); } } function renderBanner() { if (isBanner) { console.log('Outstream: Render Banner'); iinfoOutstreamPosition.innerHTML = ""; iinfoOutstreamPosition.style.height = "330px"; iinfoOutstreamPosition.appendChild(bannerDiv); } else { console.log('Outstream: Banner is not set'); } } function loadNext() { iinfoVastUrlIndex++; if (iinfoVastUrlIndex < iinfoVastUrls.length) { iinfoOutstreamPosition.remove(); outstreamInit(); } else { return false; } adVolume = 1; return true; } /** * Pauses video content and sets up ad UI. */ function onContentPauseRequested() { videoContent.pause(); // This function is where you should setup UI for showing ads (for example, // display ad timer countdown, disable seeking and more.) // setupUIForAds(); } /** * Resumes video content and removes ad UI. */ function onContentResumeRequested() { videoContent.play(); // This function is where you should ensure that your UI is ready // to play content. It is the responsibility of the Publisher to // implement this function when necessary. // setupUIForContent(); } function onActiveView() { if (outstreamContainer) { const containerOffset = outstreamContainer.getBoundingClientRect(); const windowHeight = window.innerHeight; if (containerOffset.top < windowHeight/1 && containerOffset.bottom > 0.0) { if (outstreamPaused) { adsManager.resume(); outstreamPaused = false; } return true; } else { if (!outstreamPaused) { adsManager.pause(); outstreamPaused = true; } } } return false; } let outstreamInitInterval; if (typeof cpexPackage !== "undefined") { outstreamInitInterval = setInterval(tryToInitializeOutstream, 100); } else { const wrapper = getWrapper(); if (wrapper) { let outstreamInitialized = false; window.addEventListener('scroll', () => { if (!outstreamInitialized) { const containerOffset = wrapper.getBoundingClientRect(); const windowHeight = window.innerHeight; if (containerOffset.top < windowHeight / 1 && containerOffset.bottom > 0.0) { outstreamInit(); outstreamInitialized = true; } } }); } } function tryToInitializeOutstream() { const wrapper = getWrapper(); if (wrapper) { const containerOffset = wrapper.getBoundingClientRect(); const windowHeight = window.innerHeight; if (containerOffset.top < windowHeight / 1 && containerOffset.bottom > 0.0) { if (cpexPackage.adserver.displayed) { clearInterval(outstreamInitInterval); outstreamInit(); } } } else { clearInterval(outstreamInitInterval); } } }
OSZAR »