1/** 2 * WHAT: Decides whether the Android "get the app" bar appears, and what it says. 3 * WHY: The markup ships to every visitor because the page HTML is cached and 4 * shared (see includes/app-banner.php) â so the platform check, the 5 * install check and the reader's dismissal all have to happen here. 6 * INPUT: window.LP_GETAPP (localized by app-banner.php) + #lp-getapp. 7 * OUTPUT: the bar unhidden with one of two wordings, or nothing at all. 8 * DEPENDS ON: nothing â no framework, no build step. 9 * TESTED BY: tests/banner.mjs â it drives THIS file in a real browser on both 10 * platforms, which is where every decision below actually happens, plus 11 * by hand on an Android phone. The PHP side is covered by the same suite 12 * and by dwlatinprayer/tests/test-app-links.php (which pages qualify). 13 * â This line used to name tests/test-app-banner.php "the PHP side". No 14 * such file exists; includes/app-banner.php names the real ones. 15 */ 16(function () { 17 'use strict'; 18 19 var KEY = 'lp-getapp-dismissed'; 20 var el = document.getElementById('lp-getapp'); 21 var cfg = window.LP_GETAPP; 22 if (!el || !cfg) return; 23 24 // iOS is deliberately not handled here: Safari draws Apple's own Smart App 25 // Banner from the <meta> tag, and unlike anything we can write it KNOWS 26 // whether the app is installed. Two banners on one page would be worse than 27 // either alone. 28 if (!/Android/i.test(navigator.userAgent)) return; 29 30 // A reader who closed it once has answered the question. 31 try { 32 if (localStorage.getItem(KEY)) return; 33 } catch (e) { 34 // Private mode / storage disabled. Showing the bar is the safe side of 35 // this: worst case it reappears, where a thrown error would take the 36 // whole script down before it ever ran. 37 } 38 39 /** 40 * Show the bar in one of its two states. 41 * @param {boolean} installed Does this device already have the app? 42 */ 43 function render(installed) { 44 // No Android route configured and no app installed: there is nothing to 45 // offer, so say nothing. 46 if (!installed && !cfg.getUrl) return; 47 48 var cta = el.querySelector('.lp-getapp__cta'); 49 el.querySelector('.lp-getapp__lead').textContent = installed ? cfg.openLead : cfg.getLead; 50 // The button shows one word and announces the whole sentence. 51 cta.textContent = installed ? cfg.openCta : cfg.getCta; 52 cta.setAttribute('aria-label', installed ? cfg.openAria : cfg.getAria); 53 cta.href = installed ? cfg.openUrl : cfg.getUrl; 54 if (!installed) cta.rel = 'noopener'; 55 el.hidden = false; 56 } 57 58 el.querySelector('.lp-getapp__close').addEventListener('click', function () { 59 el.hidden = true; 60 // The date is stored rather than a bare flag purely so the value can be 61 // read later; any value at all counts as dismissed. 62 try { localStorage.setItem(KEY, new Date().toISOString().slice(0, 10)); } catch (e) {} 63 }); 64 65 // The one honest install check the web offers, and only Chrome on Android 66 // has it. It answers from the site manifest's `related_applications` and the 67 // app's assetlinks.json TOGETHER â both are served by dwlatinprayer, so a 68 // false "not installed" here means one of those two stopped resolving. 69 // Where the API is missing we assume not-installed: offering the app to 70 // someone who has it costs one dismissal; hiding it from someone who does 71 // not costs the install. 72 if (typeof navigator.getInstalledRelatedApps === 'function' && cfg.package) { 73 navigator.getInstalledRelatedApps().then(function (apps) { 74 render(apps.some(function (app) { return app.id === cfg.package; })); 75 }, function () { 76 render(false); 77 }); 78 } else { 79 render(false); 80 } 81})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.