1// Simple PWA JavaScript 2// console.log('PWA: JavaScript file loaded'); 3// console.log('PWA: simplePWA config:', typeof simplePWA !== 'undefined' ? simplePWA : 'NOT DEFINED'); 4(function () { 5 'use strict'; 6 7 let deferredPrompt = null; 8 let installButton; 9 let beforeInstallPromptFired = false; 10 11 // Listen for beforeinstallprompt IMMEDIATELY - before DOMContentLoaded 12 // This is critical because Chrome can fire this event very early 13 window.addEventListener('beforeinstallprompt', function (e) { 14 // console.log('PWA: beforeinstallprompt fired EARLY - native install available'); 15 e.preventDefault(); 16 deferredPrompt = e; 17 beforeInstallPromptFired = true; 18 19 // If DOM is already loaded and prompt exists, update it 20 if (document.readyState !== 'loading') { 21 const existingPrompt = document.getElementById('pwa-install-prompt'); 22 if (existingPrompt && !existingPrompt.classList.contains('hidden')) { 23 updateInstallButtonForNative(); 24 } 25 } 26 }); 27 28 // Initialize PWA functionality when DOM is loaded 29 document.addEventListener('DOMContentLoaded', function () { 30 // console.log('PWA: DOM loaded, initializing...'); 31 32 // Track PWA pageview only for standalone mode (web pageviews tracked elsewhere) 33 if (isStandalone()) { 34 // Mark as installed so the regular website won't show the install prompt 35 localStorage.setItem('pwa-installed', '1'); 36 37 trackPWAEvent('Pageview', 'pwa', { 38 'pwa_display_mode': 'pwa' 39 }); 40 41 // Track PWA open (once per session) 42 if (!sessionStorage.getItem('pwa-open-tracked')) { 43 sessionStorage.setItem('pwa-open-tracked', '1'); 44 const platform = isAndroidDevice() ? 'android' : (isIOSSafari() ? 'ios' : 'other'); 45 trackPWAEvent('Open', 'standalone', { 46 'pwa_platform': platform, 47 'pwa_display_mode': 'standalone' 48 }); 49 } 50 } 51 52 // Don't initialize PWA prompt in in-app browsers 53 if (isInAppBrowser()) { 54 // console.log('PWA: In-app browser detected, skipping PWA prompt initialization'); 55 // Still register service worker but don't show prompts 56 initServiceWorker(); 57 return; 58 } 59 60 initServiceWorker(); 61 createInstallPrompt(); 62 setupInstallPromptListeners(); 63 64 // If beforeinstallprompt already fired before DOM loaded, update button 65 if (beforeInstallPromptFired && deferredPrompt) { 66 updateInstallButtonForNative(); 67 } 68 69 // Add global function for testing 70 window.testPWAPrompt = function () { 71 // console.log('PWA: Manual test triggered'); 72 showInstallPrompt(); 73 }; 74 75 // Also add debug functions 76 window.pwaDEBUG = { 77 showPrompt: showInstallPrompt, 78 hidePrompt: hideInstallPrompt, 79 showAndroidInstructions: showAndroidInstallInstructions, 80 showGuidedInstall: showAndroidGuidedInstall, 81 checkConditions: function () { 82 // console.log('PWA Conditions Check:'); 83 // console.log('- Is Mobile:', isMobileDevice()); 84 // console.log('- Is In-App Browser:', isInAppBrowser()); 85 // console.log('- Is Android:', isAndroidDevice()); 86 // console.log('- Is Android Chrome:', isAndroidChrome()); 87 // console.log('- Is Android Edge:', isAndroidEdge()); 88 // console.log('- Supports Native Install:', supportsNativeInstall()); 89 // console.log('- Browser Name:', getBrowserName()); 90 // console.log('- Is Standalone:', isStandalone()); 91 // console.log('- Is Dismissed:', isDismissed()); 92 // console.log('- Deferred Prompt:', deferredPrompt); 93 } 94 }; 95 96 97 // console.log('PWA: You can test the prompt manually by running: testPWAPrompt()'); 98 99 // Additional check for PWA installability criteria 100 setTimeout(function () { 101 checkPWAInstallCriteria(); 102 }, 2000); 103 }); 104 105 // Register service worker 106 function initServiceWorker() { 107 if ('serviceWorker' in navigator) { 108 // Register with explicit scope to ensure PWA installability 109 navigator.serviceWorker.register(simplePWA.serviceWorkerUrl, { 110 scope: '/' 111 }) 112 .then(function (registration) { 113 // console.log('Service Worker registered successfully:', registration.scope); 114 115 // Check for updates periodically 116 registration.update(); 117 118 // When service worker is active, Chrome may fire beforeinstallprompt 119 if (registration.active) { 120 // console.log('PWA: Service worker is active'); 121 } 122 123 // Listen for service worker state changes 124 registration.addEventListener('updatefound', function () { 125 const newWorker = registration.installing; 126 if (newWorker) { 127 newWorker.addEventListener('statechange', function () { 128 if (newWorker.state === 'activated') { 129 // console.log('PWA: New service worker activated'); 130 } 131 }); 132 } 133 }); 134 }) 135 .catch(function (error) { 136 // console.log('Service Worker registration failed:', error); 137 }); 138 } 139 } 140 141 // Create install prompt UI 142 function createInstallPrompt() { 143 // console.log('PWA: Creating install prompt UI'); 144 const promptHTML = ` 145 <div id="pwa-install-prompt" class="pwa-install-prompt hidden"> 146 <div class="pwa-prompt-content"> 147 <div class="pwa-prompt-icon" id="pwa-prompt-icon"></div> 148 <div class="pwa-prompt-text"> 149 <h3>${simplePWA.siteName || simplePWA.appName}</h3> 150 <p>${simplePWA.promptDescription || 'Voeg toe aan je startscherm!'}</p> 151 </div> 152 <div class="pwa-prompt-buttons"> 153 <button id="pwa-install-btn" class="pwa-btn pwa-btn-primary">${simplePWA.installCta || 'Installeren'}</button> 154 <button id="pwa-close-btn" class="pwa-close-btn">×</button> 155 </div> 156 </div> 157 </div> 158 `; 159 160 document.body.insertAdjacentHTML('beforeend', promptHTML); 161 installButton = document.getElementById('pwa-install-btn'); 162 163 // console.log('PWA: Prompt HTML inserted into DOM'); 164 // console.log('PWA: Install button element:', installButton); 165 166 // Verify the prompt was added 167 const addedPrompt = document.getElementById('pwa-install-prompt'); 168 if (addedPrompt) { 169 // console.log('PWA: Prompt element successfully added to DOM'); 170 } else { 171 // console.log('PWA: ERROR - Failed to add prompt element to DOM'); 172 } 173 174 // Load the appropriate icon 175 loadPromptIcon(); 176 } 177 178 // Setup event listeners for install prompt 179 function setupInstallPromptListeners() { 180 const installPrompt = document.getElementById('pwa-install-prompt'); 181 const closeBtn = document.getElementById('pwa-close-btn'); 182 183 // Note: Main beforeinstallprompt listener is registered at the top level (before DOMContentLoaded) 184 // This secondary listener handles late-firing scenarios 185 window.addEventListener('beforeinstallprompt', function (e) { 186 // console.log('PWA: beforeinstallprompt fired (secondary listener) - native install available'); 187 e.preventDefault(); 188 deferredPrompt = e; 189 beforeInstallPromptFired = true; 190 191 // Update existing prompt if it's already showing 192 if (document.getElementById('pwa-install-prompt') && !document.getElementById('pwa-install-prompt').classList.contains('hidden')) { 193 // console.log('PWA: Updating existing prompt with native functionality'); 194 updateInstallButtonForNative(); 195 } 196 // Show install prompt if not dismissed and not already showing 197 else if (!isDismissed() && !isStandalone()) { 198 setTimeout(function () { 199 // console.log('PWA: Showing prompt with native install capability'); 200 showInstallPrompt(); 201 updateInstallButtonForNative(); 202 }, 2000); 203 } 204 }); 205 206 // Install button click 207 if (installButton) { 208 installButton.addEventListener('click', async function () { 209 // console.log('PWA: Install button clicked, deferredPrompt available:', !!deferredPrompt); 210 211 if (isAndroidDevice()) { 212 const platform = 'android'; 213 trackPWAEvent('Install Click', 'install_click', { 214 'pwa_platform': platform, 215 'pwa_browser': getBrowserName() 216 }); 217 } 218 219 if (deferredPrompt) { 220 // console.log('PWA: Using native install prompt'); 221 try { 222 deferredPrompt.prompt(); 223 const choiceResult = await deferredPrompt.userChoice; 224 if (choiceResult.outcome === 'accepted') { 225 // console.log('User accepted the install prompt'); 226 // Track successful install 227 const platform = isAndroidDevice() ? 'android' : (isIOSSafari() ? 'ios' : 'other'); 228 trackPWAEvent('Install Success', 'install_success', { 229 'pwa_platform': platform, 230 'pwa_browser': getBrowserName() 231 }); 232 } else { 233 // console.log('User dismissed the install prompt'); 234 } 235 deferredPrompt = null; 236 hideInstallPrompt(); 237 } catch (err) { 238 // console.log('PWA: Native prompt error:', err); 239 // Prompt may have been used already, fall back to guided install 240 deferredPrompt = null; 241 showFallbackInstall(); 242 } 243 } else { 244 // console.log('PWA: No native prompt available, checking if installed...'); 245 246 // Check if app is already installed 247 if ('getInstalledRelatedApps' in navigator) { 248 try { 249 const relatedApps = await navigator.getInstalledRelatedApps(); 250 // If we find any related app (since we added ourselves to manifest), assume it's installed 251 if (relatedApps.length > 0) { 252 // App is installed - try to open it by navigating to start_url 253 // console.log('PWA: App detected as installed, attempting to open...', relatedApps); 254 const openUrl = simplePWA.startUrl || '/?utm_source=pwa&utm_medium=standalone'; 255 window.location.href = openUrl; 256 return; 257 } 258 } catch (err) { 259 // console.log('PWA: Error checking installed apps:', err); 260 } 261 } 262 263 // console.log('PWA: No native prompt available, showing guided installation'); 264 showFallbackInstall(); 265 } 266 }); 267 } 268 269 // Helper function to show fallback installation 270 function showFallbackInstall() { 271 // For Android devices, show a guided approach 272 if (isAndroidDevice()) { 273 // console.log('PWA: Android detected, showing guided installation'); 274 hideInstallPrompt(); 275 showAndroidGuidedInstall(); 276 } 277 // Handle different scenarios based on device type 278 else if (isIOSSafari()) { 279 // Fallback for iOS Safari 280 showIOSInstallInstructions(); 281 } else { 282 // Generic fallback 283 showGenericInstallInstructions(); 284 } 285 } 286 287 // Close button click 288 if (closeBtn) { 289 closeBtn.addEventListener('click', function () { 290 const platform = isAndroidDevice() ? 'android' : (isIOSSafari() ? 'ios' : 'other'); 291 trackPWAEvent('Popup Closed', 'dismissed', { 292 'pwa_platform': platform, 293 'pwa_browser': getBrowserName() 294 }); 295 hideInstallPrompt(); 296 // Don't show again for 30 days 297 localStorage.setItem('pwa-dismissed', Date.now() + (30 * 24 * 60 * 60 * 1000)); 298 }); 299 } 300 301 // Listen for app installed event 302 window.addEventListener('appinstalled', function (e) { 303 // console.log('PWA was installed'); 304 hideInstallPrompt(); 305 localStorage.setItem('pwa-installed', '1'); 306 }); 307 308 // Debug information 309 // console.log('PWA Debug Info:');
310 // console.log('- Is Mobile:', isMobileDevice()); 311 // console.log('- Is Standalone:', isStandalone()); 312 // console.log('- Is Dismissed:', isDismissed()); 313 // console.log('- Is iOS Safari:', isIOSSafari()); 314 // console.log('- Is Android:', isAndroidDevice()); 315 // console.log('- User Agent:', navigator.userAgent); 316 317 // Check if we should show prompt on mobile devices 318 if (isMobileDevice() && !isStandalone() && !isDismissed()) { 319 // console.log('PWA: Conditions met, will check if already installed'); 320 321 // Check if app is already installed before showing prompt 322 isAppAlreadyInstalled().then(function (installed) { 323 if (installed) { 324 // console.log('PWA: App already installed, not showing prompt'); 325 return; 326 } 327 328 // For Android devices - prioritize native install prompt 329 if (isAndroidDevice()) { 330 // console.log('PWA: Android detected, setting up install functionality'); 331 332 // If we already have the native prompt, show immediately with a small delay 333 if (deferredPrompt) { 334 // console.log('PWA: Native prompt already available, showing promptly'); 335 setTimeout(function () { 336 showInstallPrompt(); 337 updateInstallButtonForNative(); 338 }, 2000); 339 } else { 340 // Wait a bit longer for beforeinstallprompt to potentially fire 341 // Chrome needs the service worker to be active first 342 // console.log('PWA: Waiting for native prompt...'); 343 setTimeout(function () { 344 if (deferredPrompt) { 345 // console.log('PWA: Native prompt became available'); 346 showInstallPrompt(); 347 updateInstallButtonForNative(); 348 } else { 349 // console.log('PWA: No native prompt after wait, showing anyway'); 350 // Show prompt anyway - button click will handle fallback 351 showInstallPrompt(); 352 } 353 }, 4000); // Wait 4 seconds to give Chrome time to fire beforeinstallprompt 354 } 355 } 356 // Show prompt for iOS Safari users (no beforeinstallprompt event) 357 else if (isIOSSafari()) { 358 // console.log('PWA: iOS Safari detected, showing prompt in 3 seconds'); 359 setTimeout(function () { 360 showInstallPrompt(); 361 }, 3000); 362 } 363 // Also show for other mobile devices 364 else { 365 // console.log('PWA: Other mobile device, showing prompt in 4 seconds'); 366 setTimeout(function () { 367 showInstallPrompt(); 368 }, 4000); 369 } 370 }); 371 } else { 372 // console.log('PWA: Conditions not met for showing prompt'); 373 // if (!isMobileDevice()) console.log('- Not a mobile device'); 374 // if (isStandalone()) console.log('- Already running in standalone mode'); 375 // if (isDismissed()) console.log('- User previously dismissed the prompt'); 376 } 377 } 378 379 // Show install prompt 380 function showInstallPrompt() { 381 // console.log('PWA: showInstallPrompt() called'); 382 383 // Don't show prompt in in-app browsers 384 if (isInAppBrowser()) { 385 // console.log('PWA: In-app browser detected, not showing prompt'); 386 return; 387 } 388 389 // Don't show prompt if already in standalone mode 390 if (isStandalone()) { 391 // console.log('PWA: Already in standalone mode, not showing prompt'); 392 return; 393 } 394
395 // Don't show prompt on desktop 396 if (!isMobileDevice()) { 397 // console.log('PWA: Desktop detected, not showing prompt'); 398 return; 399 } 400 401 if (!canShowPromptThisSession()) { 402 // console.log('PWA: Session limit reached, not showing prompt'); 403 return; 404 } 405 const prompt = document.getElementById('pwa-install-prompt'); 406 if (prompt) { 407 // console.log('PWA: Prompt element found, showing prompt'); 408 prompt.classList.remove('hidden'); 409 prompt.classList.add('show'); 410 incrementSessionShowCounter(); 411 } else { 412 // console.log('PWA: ERROR - Prompt element not found!'); 413 } 414 } 415 416 // Hide install prompt 417 function hideInstallPrompt() { 418 const prompt = document.getElementById('pwa-install-prompt'); 419 if (prompt) { 420 prompt.classList.remove('show'); 421 prompt.classList.add('hidden'); 422 } 423 } 424 425 // Session limit helpers 426 function getSessionCounterKey() { 427 return 'pwa-session-shows'; 428 } 429 function getSessionExpiryKey() { 430 return 'pwa-session-expires'; 431 } 432 function getNow() { return Date.now(); } 433 function getLimit() { return Number(simplePWA.maxShowsPerSession || 0); } 434 function getTtl() { return Number(simplePWA.sessionLimitLifetimeMs || 0); } 435 436 function ensureSessionWindow() { 437 const expiryKey = getSessionExpiryKey(); 438 const expiresAt = Number(localStorage.getItem(expiryKey) || 0); 439 const now = getNow(); 440 if (!expiresAt || now > expiresAt) { 441 // Reset window 442 localStorage.setItem(getSessionCounterKey(), '0'); 443 const ttl = getTtl(); 444 if (ttl > 0) { 445 localStorage.setItem(expiryKey, String(now + ttl)); 446 } else { 447 // If no ttl provided, default to 1 hour 448 localStorage.setItem(expiryKey, String(now + 60 * 60 * 1000)); 449 } 450 } 451 } 452 453 function canShowPromptThisSession() { 454 const limit = getLimit(); 455 if (limit <= 0) return false; // never show 456 ensureSessionWindow(); 457 const count = Number(localStorage.getItem(getSessionCounterKey()) || 0); 458 return count < limit; 459 } 460 461 function incrementSessionShowCounter() { 462 ensureSessionWindow(); 463 const key = getSessionCounterKey(); 464 const count = Number(localStorage.getItem(key) || 0) + 1; 465 localStorage.setItem(key, String(count)); 466 } 467 468 // Load app icon for iOS modal 469 function loadIOSModalIcon() { 470 const iconElements = document.querySelectorAll('.ios-modal-app-icon'); 471 if (!iconElements || iconElements.length === 0) return; 472 473 // Priority 1: admin-provided icon via localized settings 474 if (simplePWA && simplePWA.customIcon192) { 475 iconElements.forEach(el => { 476 el.src = simplePWA.customIcon192; 477 }); 478 return; 479 } 480 481 // Priority 2: WP app manifest 482 fetch('/wp-json/app/manifest') 483 .then(response => { 484 if (!response.ok) throw new Error('Manifest not available'); 485 return response.json(); 486 }) 487 .then(manifest => { 488 if (manifest.icons && manifest.icons.length > 0) { 489 // Find the best icon (prefer 192x192 or closest) 490 let bestIcon = manifest.icons[0]; 491 for (const icon of manifest.icons) { 492 if (icon.sizes === '192x192' || icon.sizes === '192') { 493 bestIcon = icon; 494 break; 495 } 496 } 497 498 iconElements.forEach(el => { 499 el.src = bestIcon.src; 500 }); 501 } else { 502 // Fallback to site manifest 503 loadIOSModalIconFromSiteManifest(iconElements); 504 } 505 }) 506 .catch(() => { 507 // Fallback to site manifest 508 loadIOSModalIconFromSiteManifest(iconElements); 509 }); 510 } 511 512 // Load icon from site's manifest.json for iOS modal 513 function loadIOSModalIconFromSiteManifest(iconElements) { 514 fetch('/?simple_pwa_manifest=1') 515 .then(response => { 516 if (!response.ok) throw new Error('Site manifest not available'); 517 return response.json(); 518 }) 519 .then(manifest => { 520 if (manifest.icons && manifest.icons.length > 0) { 521 // Find the best icon (prefer 192x192 or closest) 522 let bestIcon = manifest.icons[0]; 523 for (const icon of manifest.icons) { 524 if (icon.sizes === '192x192') { 525 bestIcon = icon; 526 break; 527 } 528 } 529
530 iconElements.forEach(el => { 531 el.src = bestIcon.src; 532 }); 533 } 534 // If no icons found, use fallback (already set in HTML) 535 }) 536 .catch(() => { 537 // Keep fallback icon if everything fails 538 // console.log('PWA: Using fallback icon for iOS modal'); 539 }); 540 } 541 542 // Show iOS install instructions 543 function showIOSInstallInstructions() { 544 const custom = (simplePWA.instructions && simplePWA.instructions.ios) ? simplePWA.instructions.ios : ''; 545 546 // Get plugin URL for icon assets 547 const pluginUrl = simplePWA.pluginUrl || (simplePWA.homeUrl + '/wp-content/plugins/simple-pwa'); 548 549 // Fallback app icon 550 const fallbackIconUrl = pluginUrl + '/assets/icon-192x192.svg'; 551 552 // Get the actual site name (not the prompt title) 553 const appName = simplePWA.appName || simplePWA.siteName; 554 555 const instructions = (custom && custom.trim().length > 0) ? custom : ` 556 <div id="ios-install-modal" class="pwa-modal no-blurring"> 557 <div class="pwa-modal-content-ios"> 558 <div class="pwa-modal-header-ios"> 559 <div class="pwa-modal-title-ios">Toevoegen aan startscherm</div> 560 <button class="pwa-modal-close-ios" id="ios-modal-close"> 561 <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> 562 <line x1="18" y1="6" x2="6" y2="18"></line> 563 <line x1="6" y1="6" x2="18" y2="18"></line> 564 </svg> 565 </button> 566 </div> 567 568 <div class="pwa-modal-details-ios"> 569 <div class="pwa-modal-img-ios bordered"> 570 <img alt="app icon" class="ios-modal-app-icon" style="color:transparent !important;" src="${fallbackIconUrl}"> 571 </div> 572 <div class="pwa-modal-text-ios"> 573 <div>${appName}</div> 574 <div>${simplePWA.homeUrl.replace(/^https?:\/\//, '')}</div> 575 </div> 576 </div> 577 578 <div class="pwa-modal-steps-ios"> 579 <div class="pwa-modal-step-ios" data-step="1."> 580 <div> 581 Klik op 582 <div class="apple-share-sheet-icon"> 583 <img src="${pluginUrl}/assets/icons/ios-share.svg" alt="Share" width="24" height="24"> 584 </div> 585 in de werkbalk van de browser 586 </div> 587 </div> 588 589 <div class="pwa-modal-step-ios" data-step="2."> 590 <div> 591 Scroll en selecteer 592 <span class="add-to-homescreen-text"> 593 <img src="${pluginUrl}/assets/icons/plus-square.svg" alt="Add" width="18" height="18"> 594 Zet op beginscherm 595 </span> 596 </div> 597 </div> 598 599 <div class="pwa-modal-step-ios" data-step="3."> 600 <div> 601 Ga naar het 602 <img alt="app icon" class="pwa-inline-logo ios-modal-app-icon" src="${fallbackIconUrl}" style="color:transparent !important;"> 603 icoon op je startscherm 604 </div> 605 </div> 606 </div> 607 </div> 608 </div> 609 `; 610 611 document.body.insertAdjacentHTML('beforeend', instructions); 612 613 // Load the proper app icon 614 loadIOSModalIcon(); 615 616 const modal = document.getElementById('ios-install-modal'); 617 const closeBtn = document.getElementById('ios-modal-close'); 618 619 if (closeBtn) { 620 closeBtn.addEventListener('click', function () { 621 modal.remove(); 622 hideInstallPrompt(); 623 }); 624 } 625 626 // Close modal when clicking outside 627 if (modal) { 628 modal.addEventListener('click', function (e) { 629 if (e.target === modal) { 630 modal.remove(); 631 hideInstallPrompt(); 632 } 633 }); 634 } 635 } 636 637 // Show Android install instructions 638 function showAndroidInstallInstructions() { 639 const browserName = getBrowserName(); 640 const custom = (simplePWA.instructions && simplePWA.instructions.android) ? simplePWA.instructions.android : ''; 641 const appName = simplePWA.appName || simplePWA.siteName; 642 const instructions = (custom && custom.trim().length > 0) ? custom : ` 643 <div id="android-install-modal" class="pwa-modal"> 644 <div class="pwa-modal-content"> 645 <h3>${appName}</h3>
646 <div class="android-instructions"> 647 <p>${simplePWA.promptDescription || 'Voeg toe aan je startscherm!'}</p> 648 ${getAndroidInstructions(browserName)} 649 </div> 650 <button id="android-modal-close" class="pwa-btn pwa-btn-primary">Begrepen</button> 651 </div> 652 </div> 653 `; 654 655 document.body.insertAdjacentHTML('beforeend', instructions); 656 657 const modal = document.getElementById('android-install-modal'); 658 const closeBtn = document.getElementById('android-modal-close'); 659 660 if (closeBtn) { 661 closeBtn.addEventListener('click', function () { 662 modal.remove(); 663 hideInstallPrompt(); 664 }); 665 } 666 } 667 668 // Show generic install instructions 669 function showGenericInstallInstructions() { 670 const custom = (simplePWA.instructions && simplePWA.instructions.generic) ? simplePWA.instructions.generic : ''; 671 const appName = simplePWA.appName || simplePWA.siteName; 672 const instructions = (custom && custom.trim().length > 0) ? custom : ` 673 <div id="generic-install-modal" class="pwa-modal"> 674 <div class="pwa-modal-content"> 675 <h3>${appName}</h3> 676 <div class="generic-instructions"> 677 <p>${simplePWA.promptDescription || 'Voeg toe aan je startscherm!'}</p> 678 <ul> 679 <li>Zoek naar een "Toevoegen aan startscherm" optie in je browser menu</li> 680 <li>Of voeg deze pagina toe aan je bladwijzers voor snelle toegang</li> 681 </ul> 682 </div> 683 <button id="generic-modal-close" class="pwa-btn pwa-btn-primary">Begrepen</button> 684 </div> 685 </div> 686 `; 687 688 document.body.insertAdjacentHTML('beforeend', instructions); 689 690 const modal = document.getElementById('generic-install-modal'); 691 const closeBtn = document.getElementById('generic-modal-close'); 692 693 if (closeBtn) { 694 closeBtn.addEventListener('click', function () { 695 modal.remove(); 696 hideInstallPrompt(); 697 }); 698 } 699 } 700 701 // Show waiting message for delayed install 702 function showWaitingForInstall() { 703 const waitingHTML = ` 704 <div id="pwa-waiting-modal" class="pwa-modal"> 705 <div class="pwa-modal-content"> 706 <div class="pwa-waiting-content"> 707 <div class="pwa-spinner"></div> 708 <h3>Even wachten...</h3> 709 <p>We controleren of de app direct geïnstalleerd kan worden.</p> 710 </div> 711 </div> 712 </div> 713 `; 714 715 document.body.insertAdjacentHTML('beforeend', waitingHTML); 716 } 717 718 // Hide waiting message 719 function hideWaitingMessage() { 720 const waitingModal = document.getElementById('pwa-waiting-modal'); 721 if (waitingModal) { 722 waitingModal.remove(); 723 } 724 } 725 726 // Show Android guided installation that actually works 727 function showAndroidGuidedInstall() { 728 // console.log('PWA: Showing Android guided installation'); 729 730 const browserName = getBrowserName(); 731 const isChrome = browserName.includes('Chrome'); 732 733 // Get the actual site name from wp-json/app/manifest or fallback 734 const appName = simplePWA.appName || simplePWA.siteName; 735 736 // IMPORTANT: Guide users to "Install app" instead of "Add to Home screen" when possible 737 // "Install app" creates a full PWA, "Add to Home screen" may create just a shortcut 738 const guidedHTML = ` 739 <div id="pwa-guided-modal" class="pwa-modal"> 740 <div class="pwa-modal-content pwa-modal-content-android"> 741 <div class="pwa-modal-header-android"> 742 <div class="pwa-modal-title-android">Installeer ${appName}</div> 743 <button class="pwa-modal-close-android" onclick="document.getElementById('pwa-guided-modal').remove()"> 744 <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> 745 <line x1="18" y1="6" x2="6" y2="18"></line> 746 <line x1="6" y1="6" x2="18" y2="18"></line> 747 </svg> 748 </button> 749 </div> 750 751 <div class="pwa-guided-content-android"> 752 <div class="pwa-guided-steps-android"> 753 ${isChrome ? ` 754 <div class="pwa-step-android"> 755 <div class="pwa-step-number-android">1</div> 756 <div class="pwa-step-content-android"> 757 <div class="pwa-step-title-android">Tik op het menu rechtsboven</div> 758 <div class="pwa-step-visual-android"> 759 <div class="pwa-browser-bar-android"> 760 <div class="pwa-address-bar-android">${window.location.hostname}</div> 761 <div class="pwa-menu-dots-android"> 762 <svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor"><circle cx="12" cy="5" r="2"/><circle cx="12" cy="12" r="2"/><circle cx="12" cy="19" r="2"/></svg> 763 </div> 764 </div> 765 </div> 766 </div> 767 </div> 768 769 <div class="pwa-step-android"> 770 <div class="pwa-step-number-android">2</div> 771 <div class="pwa-step-content-android"> 772 <div class="pwa-step-title-android">Kies <strong>"App installeren"</strong></div> 773 <div class="pwa-step-description-android">Als je "App installeren" niet ziet, kies dan "Toevoegen aan startscherm"</div> 774 <div class="pwa-step-visual-android"> 775 <div class="pwa-menu-item-android pwa-menu-item-preferred"> 776 <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>
777 <span>App installeren</span> 778 </div> 779 </div> 780 </div> 781 </div> 782 783 <div class="pwa-step-android"> 784 <div class="pwa-step-number-android">3</div> 785 <div class="pwa-step-content-android"> 786 <div class="pwa-step-title-android">Bevestig met "Installeren"</div> 787 </div> 788 </div> 789 ` : ` 790 <div class="pwa-step-android"> 791 <div class="pwa-step-number-android">1</div> 792 <div class="pwa-step-content-android"> 793 <div class="pwa-step-title-android">Tik op het menu van je browser</div> 794 <div class="pwa-step-visual-android"> 795 <div class="pwa-browser-bar-android"> 796 <div class="pwa-address-bar-android">${window.location.hostname}</div> 797 <div class="pwa-menu-icon-android"> 798 <svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor"><rect x="3" y="5" width="18" height="2"/><rect x="3" y="11" width="18" height="2"/><rect x="3" y="17" width="18" height="2"/></svg> 799 </div> 800 </div> 801 </div> 802 </div> 803 </div> 804 805 <div class="pwa-step-android"> 806 <div class="pwa-step-number-android">2</div> 807 <div class="pwa-step-content-android"> 808 <div class="pwa-step-title-android">Kies <strong>"App installeren"</strong> of "Toevoegen aan startscherm"</div> 809 <div class="pwa-step-visual-android"> 810 <div class="pwa-menu-item-android pwa-menu-item-preferred"> 811 <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg> 812 <span>App installeren</span> 813 </div> 814 </div> 815 </div> 816 </div> 817 `} 818 </div> 819 </div> 820 </div> 821 </div> 822 `; 823 824 document.body.insertAdjacentHTML('beforeend', guidedHTML); 825 826 // Close on backdrop click 827 const modal = document.getElementById('pwa-guided-modal'); 828 if (modal) { 829 modal.addEventListener('click', function (e) { 830 if (e.target === modal) { 831 modal.remove(); 832 } 833 }); 834 } 835 } 836 837 838 // Create direct homescreen shortcut for Android 839 function createHomescreenShortcut() { 840 // console.log('PWA: Attempting to create direct homescreen shortcut'); 841 842 // Show success message 843 showShortcutSuccess(); 844 845 // Try to trigger browser's "Add to Home Screen" functionality 846 // This uses a combination of techniques 847 848 // Method 1: Try to programmatically trigger the menu 849 if (navigator.userAgent.includes('Chrome')) { 850 // For Chrome, we can try to guide the user more directly 851 setTimeout(function () { 852 // Create a more direct approach 853 triggerChromeAddToHomeScreen(); 854 }, 1000); 855 } else { 856 // For other browsers, show a simple success message 857 setTimeout(function () { 858 showGenericShortcutMessage(); 859 }, 1000); 860 } 861 } 862 863 // Trigger Chrome's Add to Home Screen 864 function triggerChromeAddToHomeScreen() { 865 // console.log('PWA: Triggering Chrome Add to Home Screen'); 866 867 try { 868 // Method 1: Try to create a virtual link click to trigger browser menu 869 const link = document.createElement('a'); 870 link.href = window.location.href; 871 link.setAttribute('data-turbo', 'false'); 872 link.click(); 873 874 // Method 2: Try to trigger browser's context menu programmatically 875 const contextMenuEvent = new MouseEvent('contextmenu', { 876 bubbles: true, 877 cancelable: true, 878 view: window, 879 clientX: window.innerWidth / 2, 880 clientY: window.innerHeight / 2 881 }); 882 document.body.dispatchEvent(contextMenuEvent); 883 884 // Method 3: Try to simulate key combinations that might trigger browser menu
885 const keyEvent = new KeyboardEvent('keydown', { 886 key: 'F10', 887 shiftKey: true, 888 bubbles: true 889 }); 890 document.dispatchEvent(keyEvent); 891 892 // Method 4: Force refresh manifest link 893 const existingManifest = document.querySelector('link[rel="manifest"]'); 894 if (existingManifest) { 895 existingManifest.remove(); 896 } 897 const newManifest = document.createElement('link'); 898 newManifest.rel = 'manifest'; 899 newManifest.href = simplePWA.manifestUrl + '?v=' + Date.now(); 900 document.head.appendChild(newManifest); 901 902 // Method 5: Try to trigger through window.open with special parameters 903 setTimeout(function () { 904 try { 905 const popup = window.open('', '_self'); 906 if (popup) { 907 popup.close(); 908 } 909 } catch (e) { 910 // console.log('PWA: Popup method failed:', e); 911 } 912 }, 100); 913 914 // Show success message after attempts 915 setTimeout(function () { 916 showShortcutCreatedMessage(); 917 }, 2000); 918 919 } catch (error) { 920 // console.log('PWA: Direct shortcut creation failed:', error); 921 showShortcutCreatedMessage(); 922 } 923 } 924 925 // Show shortcut success message 926 function showShortcutSuccess() { 927 const successHTML = ` 928 <div id="pwa-shortcut-modal" class="pwa-modal"> 929 <div class="pwa-modal-content"> 930 <div class="pwa-shortcut-content"> 931 <div class="pwa-success-icon"> 932 <svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="#34A853" stroke-width="2"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/></svg> 933 </div> 934 <h3>Shortcut wordt aangemaakt...</h3> 935 <p>We maken een snelkoppeling naar je startscherm.</p> 936 <div class="pwa-spinner"></div> 937 </div> 938 </div> 939 </div> 940 `; 941 942 document.body.insertAdjacentHTML('beforeend', successHTML); 943 } 944 945 // Show final shortcut created message 946 function showShortcutCreatedMessage() { 947 const modal = document.getElementById('pwa-shortcut-modal'); 948 const appName = simplePWA.appName || simplePWA.siteName; 949 if (modal) { 950 const content = modal.querySelector('.pwa-shortcut-content'); 951 content.innerHTML = ` 952 <div class="pwa-success-icon"> 953 <svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="#34A853" stroke-width="2"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/></svg> 954 </div> 955 <h3>Klaar</h3> 956 <p>De app is toegevoegd aan je startscherm.<br>Kijk naar het icoon van <strong>${appName}</strong>.</p> 957 <button class="pwa-btn pwa-btn-primary" onclick="this.closest('.pwa-modal').remove()">Sluiten</button> 958 `; 959 } 960 961 // Auto-close after 4 seconds 962 setTimeout(function () { 963 const modal = document.getElementById('pwa-shortcut-modal'); 964 if (modal) { 965 modal.remove(); 966 } 967 }, 4000); 968 } 969 970 // Show generic shortcut message for non-Chrome browsers 971 function showGenericShortcutMessage() { 972 const modal = document.getElementById('pwa-shortcut-modal'); 973 const appName = simplePWA.appName || simplePWA.siteName; 974 if (modal) { 975 const content = modal.querySelector('.pwa-shortcut-content'); 976 content.innerHTML = ` 977 <div class="pwa-success-icon"> 978 <svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="#34A853" stroke-width="2"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/></svg> 979 </div> 980 <h3>Shortcut aangemaakt</h3> 981 <p>De website is toegevoegd aan je startscherm als <strong>${appName}</strong>.</p> 982 <button class="pwa-btn pwa-btn-primary" onclick="this.closest('.pwa-modal').remove()">Sluiten</button> 983 `; 984 } 985 986 // Auto-close after 3 seconds 987 setTimeout(function () { 988 const modal = document.getElementById('pwa-shortcut-modal'); 989 if (modal) { 990 modal.remove(); 991 } 992 }, 3000); 993 } 994 995 // Update install button when native prompt becomes available 996 function updateInstallButtonForNative() { 997 const installBtn = document.getElementById('pwa-install-btn'); 998 if (installBtn && deferredPrompt) { 999 // console.log('PWA: Updating install button for native Android prompt'); 1000 installBtn.textContent = (simplePWA.installCta || 'Installeren'); 1001 installBtn.style.background = '#34A853'; // Google green 1002 1003 // Keep plain text label (no emoji/icon) 1004 installBtn.innerHTML = (simplePWA.installCta || 'Installeren'); 1005 } 1006 } 1007 1008 // Enhanced Android device detection 1009 function isAndroidChrome() { 1010 const ua = navigator.userAgent; 1011 return /Android/i.test(ua) && /Chrome/i.test(ua) && !/Edge/i.test(ua); 1012 } 1013 1014 function isAndroidEdge() { 1015 const ua = navigator.userAgent; 1016 return /Android/i.test(ua) && /Edge/i.test(ua); 1017 } 1018 1019 function supportsNativeInstall() {
1020 return isAndroidChrome() || isAndroidEdge(); 1021 } 1022 1023 // Get browser name for Android instructions 1024 function getBrowserName() { 1025 const ua = navigator.userAgent; 1026 if (/Chrome/i.test(ua) && !/Edge/i.test(ua)) return 'Chrome'; 1027 if (/Edge/i.test(ua)) return 'Edge'; 1028 if (/Firefox/i.test(ua)) return 'Firefox'; 1029 if (/Samsung/i.test(ua)) return 'Samsung Internet'; 1030 return 'Browser'; 1031 } 1032 1033 // Get specific Android installation instructions based on browser 1034 function getAndroidInstructions(browserName) { 1035 switch (browserName) { 1036 case 'Chrome': 1037 return ` 1038 <ol> 1039 <li>Tik op het menu rechtsboven</li> 1040 <li>Selecteer "Toevoegen aan startscherm"</li> 1041 <li>Tik op "Toevoegen" om te bevestigen</li> 1042 </ol> 1043 `; 1044 case 'Edge': 1045 return ` 1046 <ol> 1047 <li>Tik op het menu onderaan</li> 1048 <li>Selecteer "Toevoegen aan startscherm"</li> 1049 <li>Tik op "Toevoegen" om te bevestigen</li> 1050 </ol> 1051 `; 1052 case 'Firefox': 1053 return ` 1054 <ol> 1055 <li>Tik op het menu rechtsboven</li> 1056 <li>Selecteer "Toevoegen aan startscherm"</li> 1057 <li>Tik op "Toevoegen"</li> 1058 </ol> 1059 `; 1060 case 'Samsung Internet': 1061 return ` 1062 <ol> 1063 <li>Tik op het menu onderaan</li> 1064 <li>Selecteer "Toevoegen aan startscherm"</li> 1065 <li>Bevestig de installatie</li> 1066 </ol> 1067 `; 1068 default: 1069 return ` 1070 <ol> 1071 <li>Zoek naar het menu van je browser</li> 1072 <li>Zoek naar "Toevoegen aan startscherm"</li> 1073 <li>Volg de instructies om de app te installeren</li> 1074 </ol> 1075 `; 1076 } 1077 } 1078 1079 // Check PWA installation criteria 1080 function checkPWAInstallCriteria() { 1081 // console.log('PWA: Checking installation criteria...'); 1082 1083 // Check service worker 1084 if ('serviceWorker' in navigator) { 1085 navigator.serviceWorker.getRegistration().then(function (registration) { 1086 // console.log('PWA Criteria - Service Worker:', registration ? 'â Registered' : 'â Not registered'); 1087 }); 1088 } else { 1089 // console.log('PWA Criteria - Service Worker: â Not supported'); 1090 } 1091 1092 // Check manifest 1093 const manifestLink = document.querySelector('link[rel="manifest"]'); 1094 // console.log('PWA Criteria - Manifest:', manifestLink ? 'â Present' : 'â Missing'); 1095 1096 // Check HTTPS 1097 // console.log('PWA Criteria - HTTPS:', location.protocol === 'https:' ? 'â Secure' : 'â Not secure'); 1098 1099 // Check if already installed 1100 // console.log('PWA Criteria - Not installed:', !isStandalone() ? 'â Not installed' : 'â Already installed'); 1101 1102 // Check beforeinstallprompt availability 1103 setTimeout(function () { 1104 // console.log('PWA Criteria - beforeinstallprompt:', deferredPrompt ? 'â Available' : 'â Not available'); 1105 1106 if (!deferredPrompt && isAndroidChrome()) { 1107 // console.log('PWA: Chrome detected but no beforeinstallprompt. Possible reasons:'); 1108 // console.log('- User previously dismissed the prompt'); 1109 // console.log('- App is already installed'); 1110 // console.log('- PWA criteria not fully met'); 1111 // console.log('- User engagement score too low'); 1112 } 1113 }, 5000); 1114 } 1115 1116 // Check if device is mobile 1117 function isMobileDevice() { 1118 return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent); 1119 } 1120 1121 // Check if PWA is already installed on the device 1122 async function isAppAlreadyInstalled() { 1123 // Check localStorage flag (set when appinstalled event fires) 1124 if (localStorage.getItem('pwa-installed') === '1') { 1125 return true; 1126 } 1127 1128 // Check via getInstalledRelatedApps API (Android Chrome) 1129 if ('getInstalledRelatedApps' in navigator) { 1130 try { 1131 const relatedApps = await navigator.getInstalledRelatedApps(); 1132 if (relatedApps.length > 0) { 1133 localStorage.setItem('pwa-installed', '1'); 1134 return true; 1135 } 1136 } catch (e) { 1137 // API not supported or failed, continue with normal flow 1138 } 1139 } 1140 1141 return false;
1142 } 1143 1144 // Check if app is running in standalone mode 1145 function isStandalone() { 1146 return window.matchMedia('(display-mode: standalone)').matches || 1147 window.navigator.standalone === true; 1148 } 1149 1150 // Check if prompt was recently dismissed 1151 function isDismissed() { 1152 const dismissed = localStorage.getItem('pwa-dismissed'); 1153 return dismissed && parseInt(dismissed) > Date.now(); 1154 } 1155 1156 // Check if browser is iOS Safari 1157 function isIOSSafari() { 1158 const ua = window.navigator.userAgent; 1159 const iOS = !!ua.match(/iPad|iPhone|iPod/); 1160 const webkit = !!ua.match(/WebKit/); 1161 const chrome = !!ua.match(/CriOS|Chrome/); 1162 return iOS && webkit && !chrome; 1163 } 1164 1165 // Check if device is Android 1166 function isAndroidDevice() { 1167 const ua = window.navigator.userAgent; 1168 return /Android/i.test(ua); 1169 } 1170 1171 // Detect if user is in an in-app browser 1172 function isInAppBrowser() { 1173 const ua = window.navigator.userAgent || window.navigator.vendor || window.opera; 1174
1175 // Check for common in-app browsers 1176 const inAppBrowsers = [ 1177 'FBAN', // Facebook App 1178 'FBAV', // Facebook App 1179 'Instagram', // Instagram 1180 'LinkedInApp', // LinkedIn 1181 'Twitter', // Twitter / X 1182 'TwitterAndroid', // Twitter Android App 1183 'Line', // Line 1184 'Snapchat', // Snapchat 1185 'Pinterest', // Pinterest 1186 'WhatsApp', // WhatsApp 1187 'Telegram', // Telegram 1188 'messenger', // Facebook Messenger 1189 'MicroMessenger', // WeChat 1190 'GSA', // Google App (iOS) 1191 ]; 1192 1193 // Check if any in-app browser identifier is in user agent 1194 for (let i = 0; i < inAppBrowsers.length; i++) { 1195 if (ua.indexOf(inAppBrowsers[i]) > -1) { 1196 // console.log('PWA: In-app browser detected: ' + inAppBrowsers[i]); 1197 return true; 1198 } 1199 } 1200 1201 // Additional check for iOS WebView 1202 if ((/iPad|iPhone|iPod/.test(ua)) && !window.MSStream) { 1203 // Check if it's not Safari 1204 const isSafari = /Safari/i.test(ua) && !/CriOS|FxiOS|OPiOS|mercury/i.test(ua); 1205 const isWebView = !isSafari && /AppleWebKit/i.test(ua); 1206 1207 if (isWebView) { 1208 // console.log('PWA: iOS WebView/In-app browser detected'); 1209 return true; 1210 } 1211 } 1212 1213 return false; 1214 } 1215 1216 // Generic PWA event tracking function 1217 function trackPWAEvent(action, label, extras) { 1218 if (typeof window.dataLayer !== 'undefined') { 1219 window.dataLayer.push({ 1220 'event': 'FireEvent', 1221 'eventCategory': 'PWA', 1222 'eventAction': action, 1223 'eventLabel': label || '', 1224 'eventValue': 1, 1225 ...(extras || {}) 1226 }); 1227 } 1228 } 1229 1230 // Load appropriate icon for the install prompt 1231 function loadPromptIcon() { 1232 const iconElement = document.getElementById('pwa-prompt-icon'); 1233 if (!iconElement) return; 1234 1235 // Set a default SVG icon first 1236 iconElement.innerHTML = '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="5" y="2" width="14" height="20" rx="2" ry="2"/><line x1="12" y1="18" x2="12.01" y2="18"/></svg>'; 1237 1238 // Priority 1: admin-provided icon via localized settings 1239 if (simplePWA && simplePWA.customIcon192) { 1240 const img = document.createElement('img'); 1241 img.src = simplePWA.customIcon192; 1242 img.alt = 'App Icon'; 1243 img.style.width = '24px'; 1244 img.style.height = '24px'; 1245 img.style.borderRadius = '4px'; 1246 img.style.objectFit = 'cover'; 1247 iconElement.innerHTML = ''; 1248 iconElement.appendChild(img); 1249 return; 1250 } 1251 1252 // Priority 2: WP app manifest 1253 fetch('/wp-json/app/manifest') 1254 .then(response => { 1255 if (!response.ok) throw new Error('Manifest not available'); 1256 return response.json(); 1257 }) 1258 .then(manifest => { 1259 if (manifest.icons && manifest.icons.length > 0) { 1260 // Find the best icon (prefer 192x192 or closest) 1261 let bestIcon = manifest.icons[0]; 1262 for (const icon of manifest.icons) { 1263 if (icon.sizes === '192x192' || icon.sizes === '192') { 1264 bestIcon = icon; 1265 break; 1266 } 1267 } 1268 1269 // Create img element to replace SVG 1270 const img = document.createElement('img'); 1271 img.src = bestIcon.src; 1272 img.alt = 'App Icon'; 1273 img.style.width = '24px'; 1274 img.style.height = '24px'; 1275 img.style.borderRadius = '4px'; 1276 img.style.objectFit = 'cover'; 1277 1278 // Replace the SVG with the image 1279 iconElement.innerHTML = ''; 1280 iconElement.appendChild(img); 1281 } else { 1282 // Fallback to trying site manifest 1283 loadManifestIcon(); 1284 } 1285 }) 1286 .catch(() => { 1287 // Fallback to trying site manifest 1288 loadManifestIcon(); 1289 }); 1290 } 1291 1292 // Load icon from site's manifest.json 1293 function loadManifestIcon() { 1294 const iconElement = document.getElementById('pwa-prompt-icon'); 1295 if (!iconElement) return; 1296 1297 fetch('/?simple_pwa_manifest=1') 1298 .then(response => { 1299 if (!response.ok) throw new Error('Site manifest not available'); 1300 return response.json(); 1301 }) 1302 .then(manifest => { 1303 if (manifest.icons && manifest.icons.length > 0) { 1304 // Find the best icon (prefer 192x192 or closest) 1305 let bestIcon = manifest.icons[0]; 1306 for (const icon of manifest.icons) { 1307 if (icon.sizes === '192x192') { 1308 bestIcon = icon; 1309 break; 1310 } 1311 } 1312 1313 // Create img element to replace SVG 1314 const img = document.createElement('img'); 1315 img.src = bestIcon.src; 1316 img.alt = 'App Icon'; 1317 img.style.width = '24px'; 1318 img.style.height = '24px'; 1319 img.style.borderRadius = '4px'; 1320 img.style.objectFit = 'cover'; 1321 1322 // Replace the SVG with the image 1323 iconElement.innerHTML = ''; 1324 iconElement.appendChild(img); 1325 }
1326 // If no icons found, keep the SVG fallback 1327 }) 1328 .catch(() => { 1329 // Keep SVG fallback if everything fails 1330 // console.log('PWA: Using SVG fallback for install prompt icon'); 1331 }); 1332 } 1333 1334})();
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.