Martin Mesršmíd (DIA): eDoklady shodily Identitu občana, musíme ji zásadně předělat

22. 1. 2024
Doba čtení: 11 minut

Sdílet

Martin Mesršmíd
Autor: Úřad vlády ČR
Martin Mesršmíd
Proč start aplikace eDoklady provázely problémy? Jaké možnosti má stát při testování svých aplikací před jejich startem? A jak plánuje eDoklady rozvíjet?

Na 130 tisíc lidí se od pátku zaregistrovalo do nové státní aplikace eDoklady, a má tak v mobilu k dispozici digitální kopii svého občanského průkazu. Uznávat jej sice zatím povinně musí jen vybrané státní úřady, ale v průběhu následujícího roku se má její využitelnost výrazně rozšířit.

Start aplikace, která se v aplikačních obchodech objevila už v pátek, den před oficiálním spuštěním, provázely problémy. eDoklady generovaly zvýšené množství požadavků na systém Identity občana (NIA), přes který se uživatelé v aplikaci registrují, přihlašují a autorizují požadavky na údaje ze základních registrů. Část uživatelů se tak registrovat nemohla a aplikace vykazovala chybová hlášení i při pokusech o ověření dokladů. Potíže přetrvávají i v pondělí. 

O problémech při startu eDokladů, nedostatcích NIA i plánech na nové funkce aplikace jsme po pondělní tiskové konferenci mluvili s ředitelem DIA Martinem Mesršmídem:

eDoklady přicházejí. Proč je používat a co naopak neumí? Přečtěte si také:

eDoklady přicházejí. Proč je používat a co naopak neumí?

O co konkrétně šlo při víkendových potížích eDokladů a výpadku Identity občana (NIA)? „Jen“ o to, že aplikace generovala na NIA příliš moc požadavků?

Ano, přesně tak.

Takže se problém týkal jen NIA, a ne základních registrů?

Informační systém základních registrů v některých situacích také vykazuje dílčí zpoždění, ale hlavní problém byl opravdu s NIA. Byť jsme ji v prosinci posilovali, povýšili jsme záložní lokalitu na plnohodnotnou lokalitu, šli jsme do režimu active-active, a ještě jsme navyšovali paměť, přesto to na ten nápor nestačilo. Šlo jednak o registrace, protože proces registrace jako takový vyžaduje několik postupných volání NIA. Každá registrace tak představovala zátěž a zcela pochopitelně lidé, kterým to nefungovalo, to zkoušeli znova a znova a znova, takže se dalo z problému velmi obtížně dostat. I samotné přihlašování a každé ověřování v aplikaci pokaždé volalo NIA.

Snažili jsme se NIA optimalizovat na backendu, nějakým způsobem začít requesty cachovat a i aplikaci trošku upravujeme tak, aby volání bylo o něco méně. Tak, jak to bylo navrženo, to bylo z bezpečnostních důvodů, aby byla identita uživatele neustále dokonale prokázaná a vědělo se, že nedošlo k žádným změnám. Vzhledem k tomu, že aplikace funguje velmi dobře i v offline režimu, kde možnost neustálého ověřování není, jsme přesvědčení o tom, že nějaké cachování, pozdržení dotazování na NIA třeba v řádu desítek minut, je naprosto korektní a uchráníme tak NIA od zátěže.

Nicméně, dospěli jsme už definitivně k tomu, co jsme dřív tušili a připravovali jsme se na to: NIA skutečně bude potřeba zásadnějším způsobem updatovat a bude s ní opravdu potřeba v první řadě přejít do cloudu. Teď funguje na cloud-like architektuře, je to kontejnerizované řešení, nicméně stejně musíme jít do plnohodnotného cloudu, který bude možné volně škálovat. Myslím, že je to naprosto nezbytné.

Už před několika měsíci jsme také naspecifikovali řešení frontování požadavků, které jsme poptali taky s předstihem, nicméně ještě není implementováno, pracuje se na něm. Má zajistit, aby – když je NIA přetížená – ji další requesty neshazovaly, ale aby před NIA byla předřazená fronta, která ji ochrání. To nicméně neřeší problém uživatele, protože ten by jen čekal o to déle. Není to optimální řešení, nicméně ochrání to NIA jako takovou.

Roman Vrba (SPCSS): Státní cloud přinese úklid, přepisování starých aplikací a rozšíření datacentra Přečtěte si také:

Roman Vrba (SPCSS): Státní cloud přinese úklid, přepisování starých aplikací a rozšíření datacentra

Takže by to mělo pomoci zmírnit problém, že jedna aplikace dokáže shodit celou NIA.

Ano, je to tak.

Ministr Ivan Bartoš na pondělní tiskové konferenci říkal, že chcete eDoklady vyzkoušet už letos v některých okrskových volebních komisích a příští rok už naostro při volbách do Poslanecké sněmovny. Máte tedy nějaký harmonogram přesunu NIA do cloudu, aby systém zvládal větší zátěž, která bude s volbami nejspíš spojena?

Myslím, že letos to určitě možné nebude. Zvažujeme, jestli by NIA měla, nebo neměla být ve státním cloudu, protože jsou tam přece jenom citlivé operace.

Ale státní cloud ještě plně nefunguje.

Přesně tak. Budeme teď hodně analyzovat, co si můžeme dovolit pustit volně do cloudu, aby to škálovalo, a co si musíme podržet my. Je dost dobře možné, že se nám podaří řešení rozdělit tak, aby část byla v komerčním cloudu a část třeba zůstala na stávající infrastruktuře až do doby, než bude k dispozici státní cloud. Letošní rok, myslím, strávíme většinu času analýzou, návrhy řešení, jejich testováním a příští rok by podle mě měla přijít větší změna NIA. Souvisí to i s rozpočtem. Přepracování NIA momentálně nemáme v rozpočtu na letošní rok, takže teď nemůžeme do zásadního předělání zainvestovat spoustu peněz, které nemáme v kapse.

Testovali jste před spuštěním eDokladů zátěž na NIA?

Ano, ale na reálných základních registrech nemůžete dělat dotazy, pokud k tomu nemáte zákonné zmocnění. Všechny dotazy jsou zaprotokolovány, že se na vás někdo ptal. Nemůžeme bombardovat skutečně produkční NIA. Museli jsme to všechno dělat v testovacím prostředí, kde jsme se snažili reálný provoz co nejvíc nasimulovat. Nicméně reálný provoz vždycky je jiný než při testování.

Nestálo by za to systémy upravit tak, aby se to testování dalo dělat plnohodnotněji? 

Je to tak a určitě to budeme zvažovat.

Tak třeba základní registry momentálně procházejí upgradem…

Posilujeme tam hardware, ale nikoli zatím jejich filozofii nebo princip jejich fungování, i když už máme projekt, kterému se říká Základní registry nové generace, kde se snažíme přemyslet to, jak základní registry aktuálně fungují, na základě zkušeností, které říkají, že by se někde něco mohlo změnit.

Proč při spouštění nového systému, jako jsou eDoklady, tu aplikaci neuvolňujete postupně, třeba pro menší skupiny uživatelů? Nebo neumožňujete například jen aplikaci nainstalovat a pak uživatelům postupně posílat zprávu, že už se mohou registrovat?

Digitální občanský průkaz v mobilu se blíží aneb Jak budou fungovat české eDoklady Přečtěte si také:

Digitální občanský průkaz v mobilu se blíží aneb Jak budou fungovat české eDoklady

Bylo by to naprosto skvělé, v IT je to běžná dobrá praxe. Bohužel narážíme na legislativní limity a momentálně to přesně takhle udělat nesmíme. Protože buď to právo občana existuje, nebo neexistuje. Nemůžeme občany rozdělovat do nějakých kategorií a říct, vy jste si vylosoval, že můžete, a tady paní si nevylosovala, protože má třeba liché rodné číslo, a my jsme to spustili jen na sudá rodná čísla nebo jenom po okresech. Muselo by to přesně takhle být napsáno v zákoně.

Do budoucna, až budeme dělat nějakou další podobnou akci, si možná na nějaké přechodné období zkusíme do příslušného zákona propašovat možnost testování. Bylo by to velice dobré a bylo by skvělé, abychom třeba mohli využít dobrovolné testery, takže by se ještě před platností zákona mohla nějaká skupina lidí, early adopters, přihlásit do testu. Mohly by jich možná být i desítky tisíc, protože lidí, kteří mají zájem, je opravdu hodně. 

K tomu bychom ale potřebovali do zákona dostat, že naše oprávnění existuje ještě před zákonným termínem spuštění služby a že třeba dva měsíce před tím už můžeme zapojit občany a můžeme s nimi aplikace testovat. Bylo by to skvělé, ale je to opravdu práce v první řadě pro legislativce, aby nám do zákona propašovali, že to smíme udělat.

Už dříve jste říkali, že výhledově plánujete umožnit do eDokladů ukládat i další doklady (třeba řidičské průkazy, průkazy pojištěnce a podobně). A co třeba možnost uložit si do aplikace občanské průkazy nezletilých dětí – pokud je tedy mají vydané?

Momentálně to v plánu nemáme, protože úplně neladí s filozofií aplikace tak, jak byla navržená. Nicméně by to mohlo být užitečné a byl bych rád, kdyby se nám to podařilo změnit. Možná by k tomu byla potřeba nějaká úprava legislativy, ale možná ne složitá. V případě, že děti mají vydaný občanský průkaz, by jejich zákonný zástupce mohl mít možnost držet v aplikaci více průkazů. Architektura aplikace to zatím vůbec nepodporuje. Ale dává to perfektní smysl, byl bych rád, kdyby se nám aplikaci podařilo tímto směrem rozvíjet. Na druhou stranu víme, že se budeme postupně víc a víc soustředit na evropskou digitální peněženku. Proto je otázkou, jestli bychom tuto filozofii nepromítli až do ní.

Roadmapu eDokladů teď máme docela plnou. Plánujeme rozhraní na registrace pro soukromé subjekty, aplikace pro policii, rozhraní pro úřady, aby si mohly ověřené údaje integrovat do svých informačních systémů tak, aby se do nich propisovaly automaticky. A potom také rozhraní, které umožní třetím stranám, soukromým subjektům, vydávat si vlastní aplikace eDoklady, což bude hotové v polovině roku. Je toho hrozně moc a třeba rozšíření na více dokladů už letos do roadmapy úplně nedostaneme.

Co znamená možnost pro soukromé subjekty, aby si mohly vydávat vlastní aplikace eDoklady?

To znamená, že si v principu kdokoliv, firma nebo konsorcium, bude moci vydat vlastní aplikaci eDoklady. My se tím přibližujeme té evropské digitální peněžence, která také předpokládá, že providerů bude více. Vytvořili jsme otevřený systém, ve kterém backend, který je samozřejmě v gesci státu, vydává kryptograficky ošetřený materiál, který potom kdokoliv může uložit v nějaké aplikaci a pak ho prostě vydat. Ta aplikace je svým způsobem jenom průtokový ohřívač dat. Nemůže s nimi nic dělat, protože je všechno orazítkované, podepsané.

Takže si eDoklady bude moci vytvořit jakýkoli další poskytovatel identit?

Ano, my je budeme certifikovat. A příprava tohoto řešení zabere docela dost práce.

Když jsme u té evropské peněženky, rýsuje se už nějaký termín jejího spuštění?

Už existuje politická shoda na příslušném evropském nařízení a teď se ladí text a čeká se na jeho překlad. My jsme se k tomu překladu vyjadřovali a odsouhlasili jsme některé pasáže. Teď čekáme na prováděcí předpisy. Očekáváme, že řekněme do poloviny letošního roku budou hotové. Od toho se začne počítat čas k tomu, že členské státy budou muset evropskou peněženku digitální identity implementovat. Ten proces je náročný a neustále se na úrovni Evropy protahuje, takže se teď nedá přesně říct, kdy to bude. Právě proto si myslím, že je lepší sbírat zkušenosti s naším řešením, mít něco v ruce a být pak připraveni.

Jak jsme na Lupě psali, budoucí evropská peněženka má v prozatímních specifikacích závazek zveřejnění zdrojového kódu aplikace jako open source. Plánujete to i s eDoklady?

Ne, to jsme vůbec neplánovali. Velmi by nám to zkomplikovalo situaci. Jsme si ale vědomi toho, že evropská digitální peněženka bude muset mít kód zveřejněný jako open source.

Jaké byly náklady na vývoj eDokladů? Mluvili jste o 10 milionech Kč…

Náklady za loňský rok a letošní rok, včetně uprav, o kterých jsem mluvil, budou kolem 50–60 milionů korun. Těch 10 milionů je to, co už bylo fakticky vyfakturováno a zaplaceno. Nemáme ale ještě všechny faktury pohromadě, takže dosavadní náklady odhadujeme kolem 20 milionů Kč, což zahrnuje úpravy backendu, Portálu občana a samotnou aplikaci. V letošním roce naskakují náklady na rozhraní, na otevření systému, na zmíněné certifikace a tak dále.

Promítne se část těchto nákladů i od implementace budoucí evropské peněženky?

Řekl bych, že nějaká část určitě, rozhodně náklady na úpravu backendové části na Portálu občana. Velmi pravděpodobně třeba i část rozhraní aplikace a jejího UI. A asi i část, která se týká výměnných protokolů, jako je MDL nebo přepážkový protokol, který je založen na statickém kódu. 

Vnitřek aplikace ale bude potřeba hodně předělat, protože evropská peněženka není založená na dokladech, ale na atributových certifikátech a na prezentaci atributových certifikátů, která je sestavená na míru danému informačnímu požadavku. To znamená, že ověřovatel (tzv. relying party) posílá komplexní dotaz, jaká data by potřeboval, evropská digitální peněženka se podívá na to, jaké atributové certifikáty má k dispozici, sestaví jejich prezentaci (tzv. verifiable presentation) a uživatel ji odsouhlasí. Navíc do ní bude případně zakomponován zero knowledge proof jako kryptografická operace, která umožní skrýt hodnoty atributových certifikátů, a jen ty skutečnosti prokázat. Není ale ještě jisté, jestli to takhle bude implementováno. 

Každopádně ten mechanismus je daleko složitější než to, co je v eDokladech, takže se velká část samotné aplikace vymění. Ale řekl bych, že hlavní hodnota spočívá v naší zkušenosti, v tom, že už to máme prošlápnuto. Zkušenost a čas, který jsme vývojem eDokladů získali, to jsou v podstatě desítky milionů.

Proč tedy eDoklady nevznikly více podle architektury, která se předpokládá pro evropsku peněženku, aby se dalo přepoužít více částí aplikace?

Protože to není vhodné. Architektura evropské peněženky není vhodná pro doklady jako takové. Atributové certifikáty fungují jinak. Doklad je uzavřená sada dat, ale evropská digitální peněženka nebude o dokladech, ale o jednotlivých skutečnostech, které jsou o člověku známy. Snažili jsme se o to, čím jsme si jistí a co je v Architectural Referential Framework, vydaném Evropskou unií, zejména šlo o komunikační protokoly, registr ověřovatelů a podobně. Tyto komponenty jsme použili a zbytek, u kterého není jisté, jak bude fungovat, jsme si zjednodušili pro účely toho, aby eDoklady byly hotové rychle, aby byly šité na doklady a abychom nestavěli nějaký vzdušný zámek, o kterém nevíme, jak vlastně bude ve výsledku fungovat.

WT100_25_SE

Poslední dotaz: existuje eObčanka, teď přibyly eDoklady a ty názvy se dost pletou. Neuvažujete o nějakých změnách, abyste to zmatení eliminovali? 

Pletlo se to i nám, dokonce jsme, myslím, v nějaké komunikaci chybně použili slovo eObčanka. Bojujeme s historií, kdy vzniklo několik projektů, které se jmenují podobně. Abych ten zmatek ještě zvětšil, upozorním také na eDokladovku od Státní tiskárny cenin, což je prototyp z roku 2019. Je velice povedený a my jsme z něj převzali protokol MDL, který ta aplikace už tehdy vizionářsky obsahovala. Myslím si, že by bylo dobré ten zmatek vyřešit. Uvidíme, jak si občané budou na název eDoklady zvykat, jak bude populární a jak se zažije. Je možné, že třeba za půl roku si na eObčanku a eDokladovku nikdo nevzpomene a všichni budou mít zafixovány eDoklady tak, že se toho názvu podržíme a budeme s ním dál pracovat. Pokud by to tak nebylo, je i možnost vymyslet pro Evropskou digitální peněženku nějaký nový název.

  • Chcete mít Lupu bez bannerů?
  • Chcete dostávat speciální týdenní newsletter o zákulisí českého internetu?
  • Chcete mít k dispozici strojové přepisy podcastů?
  • Chcete získat slevu 1 000 Kč na jednu z našich konferencí?

Staňte se naším podporovatelem

Autor článku

Šéfredaktor Lupa.cz a externí spolupracovník Českého rozhlasu Plus. Dříve editor IHNED.cz, předtím Aktuálně.cz a Českého rozhlasu. Najdete mě na Twitteru nebo na LinkedIn

'; 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'; } }
Upozorníme vás na články, které by vám neměly uniknout (maximálně 2x týdně).
'; 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 »