1// Firebase initialization and Push notifications 2// PWA и Push пÑомпÑÑ Ð¾ÑобÑажаÑÑÑÑ Ð¢ÐÐЬÐРна мобилÑнÑÑ ÑÑÑÑойÑÑÐ²Ð°Ñ (ÑиÑина <= 768px) 3// Ðа деÑкÑопе пÑомпÑÑ ÑкÑÑÑÑ, но push ÑÐ²ÐµÐ´Ð¾Ð¼Ð»ÐµÐ½Ð¸Ñ Ð²Ñе Ñавно ÑабоÑаÑÑ ÐµÑли полÑзоваÑÐµÐ»Ñ Ð´Ð°ÑÑ ÑазÑеÑение 4 5import { initializeApp } from 'https://www.gstatic.com/firebasejs/10.7.1/firebase-app.js'; 6import { getMessaging, getToken, onMessage } from 'https://www.gstatic.com/firebasejs/10.7.1/firebase-messaging.js'; 7 8// Firebase configuration 9const firebaseConfig = { 10 apiKey: "AIzaSyArheUgsyG77li-6ckmCjE3g4TrbhQdytY", 11 authDomain: "antique-auction.firebaseapp.com", 12 projectId: "antique-auction", 13 storageBucket: "antique-auction.firebasestorage.app", 14 messagingSenderId: "418020751608", 15 appId: "1:418020751608:web:63ef350c59e0f17997e30d", 16 measurementId: "G-Y6YCBES7HF" 17}; 18 19// VAPID Key 20const VAPID_KEY = "BN47KhW0kBj8O-hZLPCXclO4F6Y38Y9OTDRKcpxyZTAiLbC7dd-Llo1zuUNCSdXH5FVsx3bmseUUT2XbTZRoPic"; 21 22// Initialize Firebase 23const app = initializeApp(firebaseConfig); 24const messaging = getMessaging(app); 25 26// ФÑнкÑÐ¸Ñ Ð¿ÑовеÑки мобилÑного ÑÑÑÑойÑÑва 27function isMobileDevice() { 28 const userAgent = navigator.userAgent || navigator.vendor || window.opera; 29 30 // ÐÑовеÑка ÑиÑÐ¸Ð½Ñ ÑкÑана 31 const isMobileWidth = window.innerWidth <= 768; 32 33 // ÐÑовеÑка User Agent 34 const isMobileUA = /android|webos|iphone|ipad|ipod|blackberry|iemobile|opera mini/i.test(userAgent.toLowerCase()); 35 36 // ÐÑовеÑка touch events 37 const hasTouch = 'ontouchstart' in window || navigator.maxTouchPoints > 0; 38 39 return isMobileWidth || (isMobileUA && hasTouch); 40} 41 42// ФÑнкÑÐ¸Ñ Ð¿ÑовеÑки iOS ÑÑÑÑойÑÑва 43function isIOSDevice() { 44 const userAgent = navigator.userAgent || navigator.vendor || window.opera; 45 const isIOS = /iPad|iPhone|iPod/.test(userAgent) && !window.MSStream; 46 const isIPadOS = navigator.maxTouchPoints && navigator.maxTouchPoints > 2 && /MacIntel/.test(navigator.platform); 47 return isIOS || isIPadOS; 48} 49 50// ФÑнкÑÐ¸Ñ Ð¿ÑовеÑки ÑÑÑановленного PWA на iOS 51function isIOSPWAInstalled() { 52 if (window.navigator.standalone === true) return true; 53 if (isIOSDevice() && window.matchMedia('(display-mode: standalone)').matches) return true; 54 if (localStorage.getItem('pwa-install-started') === 'true') return true; 55 return false; 56} 57 58// ÐбÑÐ°Ñ ÑÑнкÑÐ¸Ñ Ð¿ÑовеÑки PWA Ñежима 59function isPWAMode() { 60 if (window.navigator.standalone === true) return true; 61 if (window.matchMedia('(display-mode: standalone)').matches) return true; 62 return false; 63} 64 65// ÐоказÑваем Ð±Ð°Ð½Ð½ÐµÑ ÑÑÑановки PWA 66let deferredPrompt; 67let installPromptShown = false; 68let iosPromptShown = false; 69 70// Android/Chrome - иÑполÑзÑем beforeinstallprompt 71window.addEventListener('beforeinstallprompt', (e) => { 72 e.preventDefault(); 73 deferredPrompt = e; 74 75 if (isPWAMode()) { 76 console.log('Already in PWA mode, skipping install prompt'); 77 return; 78 } 79 80 if (!installPromptShown && isMobileDevice()) { 81 setTimeout(() => { 82 showInstallPrompt(); 83 }, 5000); 84 } 85}); 86 87// iOS - показÑваем инÑÑÑÑкÑÐ¸Ñ 88if (isIOSDevice() && !isIOSPWAInstalled() && !iosPromptShown && !isPWAMode()) { 89 setTimeout(() => { 90 showIOSInstallInstructions(); 91 iosPromptShown = true; 92 }, 5000); 93} 94 95function showInstallPrompt() { 96 const installBanner = document.createElement('div'); 97 installBanner.id = 'pwa-install-banner'; 98 installBanner.className = 'install-app-cto-panel'; 99 installBanner.style.cssText = ` 100 background: #252930; 101 position: fixed; 102 left: 0; 103 right: 0; 104 bottom: 0; 105 z-index: 9999; 106 padding: 20px; 107 display: flex; 108 justify-content: center; 109 align-items: center; 110 gap: 8px; 111 box-shadow: 0 -2px 10px rgba(0,0,0,0.3); 112 font-family: Arial, sans-serif; 113 `; 114 115 installBanner.innerHTML = ` 116 <button id="pwa-dismiss-btn" style="border: none; background: transparent; padding: 0; cursor: pointer; flex-shrink: 0;"> 117 <svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="#e74b3d" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> 118 <line x1="18" y1="6" x2="6" y2="18"></line> 119 <line x1="6" y1="6" x2="18" y2="18"></line> 120 </svg> 121 </button> 122 123 <button id="pwa-install-btn" style=" 124 border: 2px solid white; 125 padding: 10px 12px; 126 flex-grow: 1; 127 max-width: 300px; 128 box-sizing: border-box; 129 color: white; 130 background: transparent; 131 display: flex; 132 justify-content: center; 133 align-items: center; 134 font-size: 14px; 135 font-weight: bold; 136 border-radius: 12px; 137 text-align: center; 138 cursor: pointer; 139 transition: all 0.2s; 140 " onmouseover="this.style.background='white'; this.style.color='black';" onmouseout="this.style.background='transparent'; this.style.color='white';"> 141 УÑÑановиÑÑ Ð¿Ñиложение 142 </button> 143 144 <div style="flex-shrink: 0;"> 145 <img src="/img/icon-192x192.png" alt="Install PWA" style="width: 60px; height: 60px; border-radius: 8px;"> 146 </div> 147 `; 148 149 document.body.appendChild(installBanner); 150 installPromptShown = true; 151 152 // УÑÑановка пÑÐ¸Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ 153 document.getElementById('pwa-install-btn').addEventListener('click', async () => { 154 localStorage.setItem('pwa-install-started', 'true'); 155 156 if (deferredPrompt) { 157 deferredPrompt.prompt(); 158 const { outcome } = await deferredPrompt.userChoice; 159 console.log(`User response: ${outcome}`); 160 deferredPrompt = null; 161 } 162 installBanner.remove(); 163 }); 164 165 // ÐакÑÑÑие баннеÑа 166 document.getElementById('pwa-dismiss-btn').addEventListener('click', () => { 167 installBanner.remove(); 168 }); 169} 170 171// iOS инÑÑÑÑкÑии по ÑÑÑановке PWA (плаÑка как на art-critic.ru) 172function showIOSInstallInstructions() { 173 if (isIOSPWAInstalled()) { 174 console.log('PWA Ñже ÑÑÑановлено на iOS'); 175 return; 176 } 177 178 if (localStorage.getItem('ios-install-dismissed')) { 179 return; 180 } 181 182 const iosPanel = document.createElement('div'); 183 iosPanel.id = 'ios-install-panel'; 184 iosPanel.className = 'install-app-cto-panel'; 185 iosPanel.style.cssText = ` 186 background: #252930; 187 position: fixed; 188 left: 0; 189 right: 0; 190 bottom: 0; 191 z-index: 9999; 192 padding: 20px; 193 display: flex; 194 justify-content: center;
195 align-items: center; 196 gap: 8px; 197 box-shadow: 0 -2px 10px rgba(0,0,0,0.3); 198 font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Arial, sans-serif; 199 `; 200 201 iosPanel.innerHTML = ` 202 <button id="ios-close-panel" style="border: none; background: transparent; padding: 0; cursor: pointer; flex-shrink: 0;"> 203 <svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="#e74b3d" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> 204 <line x1="18" y1="6" x2="6" y2="18"></line> 205 <line x1="6" y1="6" x2="18" y2="18"></line> 206 </svg> 207 </button> 208 209 <button id="ios-install-link" style=" 210 border: 2px solid white; 211 padding: 10px 12px; 212 flex-grow: 1; 213 max-width: 300px; 214 box-sizing: border-box; 215 color: white; 216 background: transparent; 217 display: flex; 218 justify-content: center; 219 align-items: center; 220 font-size: 14px; 221 font-weight: bold; 222 border-radius: 12px; 223 text-align: center; 224 cursor: pointer; 225 transition: all 0.2s; 226 " onmouseover="this.style.background='white'; this.style.color='black';" onmouseout="this.style.background='transparent'; this.style.color='white';"> 227 УÑÑановиÑÑ Ð¿Ñиложение 228 </button> 229 230 <div style="flex-shrink: 0;"> 231 <img src="/img/icon-192x192.png" alt="Install PWA" style="width: 60px; height: 60px; border-radius: 8px;"> 232 </div> 233 `; 234 235 document.body.appendChild(iosPanel); 236 237 // ÐбÑабоÑÑик закÑÑÑÐ¸Ñ 238 document.getElementById('ios-close-panel').addEventListener('click', () => { 239 iosPanel.remove(); 240 localStorage.setItem('ios-install-dismissed', 'true'); 241 }); 242 243 // ÐбÑабоÑÑик клика на ÐºÐ½Ð¾Ð¿ÐºÑ ÑÑÑановки - показÑваем инÑÑÑÑкÑÐ¸Ñ 244 document.getElementById('ios-install-link').addEventListener('click', () => { 245 localStorage.setItem('pwa-install-started', 'true'); 246 showIOSInstallModal(); 247 }); 248 249 console.log('iOS install panel shown'); 250} 251 252// ÐодалÑное окно Ñ Ð¸Ð½ÑÑÑÑкÑией Ð´Ð»Ñ iOS 253function showIOSInstallModal() { 254 const modal = document.createElement('div'); 255 modal.id = 'ios-install-modal'; 256 modal.style.cssText = ` 257 position: fixed; 258 top: 0; 259 left: 0; 260 right: 0; 261 bottom: 0; 262 background: rgba(0, 0, 0, 0.85); 263 z-index: 10000; 264 display: flex;
265 align-items: center; 266 justify-content: center; 267 padding: 20px; 268 font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Arial, sans-serif; 269 overflow-y: auto; 270 `; 271 272 modal.innerHTML = ` 273 <div style="background: white; border-radius: 16px; max-width: 500px; width: 100%; max-height: 90vh; overflow-y: auto; position: relative;"> 274 <button id="modal-close-btn" style="position: sticky; top: 0; right: 0; float: right; background: none; border: none; font-size: 32px; color: #999; cursor: pointer; padding: 12px 16px; z-index: 1;">×</button> 275 276 <div style="padding: 20px 24px 24px;"> 277 <div style="text-align: center; margin-bottom: 24px; clear: both;"> 278 <div style="width: 80px; height: 80px; background: #32363a; border-radius: 16px; margin: 0 auto 12px; display: flex; align-items: center; justify-content: center; overflow: hidden;"> 279 <img src="/img/icon-192x192.png" alt="ART 1" style="width: 100%; height: 100%; object-fit: cover;"> 280 </div> 281 <h2 style="margin: 0 0 4px 0; font-size: 18px; color: #333; font-weight: 600;">ART 1</h2> 282 <p style="margin: 0; font-size: 14px; color: #666;">УÑÑановиÑе пÑиложение на главнÑй ÑкÑан</p> 283 </div> 284 285 <div style="background: #f8f9fa; border-radius: 12px; padding: 20px; margin-bottom: 20px;"> 286 <h3 style="margin: 0 0 16px 0; font-size: 16px; color: #333; font-weight: 600;">Ðак ÑÑÑановиÑÑ Ð¿Ñиложение:</h3> 287 288 <ol style="margin: 0; padding: 0; list-style: none; counter-reset: step-counter;"> 289 <li style="counter-increment: step-counter; margin-bottom: 16px; padding-left: 36px; position: relative; font-size: 14px; line-height: 1.5; color: #333;"> 290 <span style="position: absolute; left: 0; top: 0; width: 24px; height: 24px; background: white; color: #162b82; border: 2px solid #162b82; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-weight: bold; font-size: 12px;">1</span> 291 ÐÑкÑойÑе пÑиложение <strong>Safari</strong> 292 <img src="https://cdn-icons-png.flaticon.com/512/564/564442.png" alt="Safari" style="width: 20px; height: 20px; vertical-align: middle; margin: 0 2px;"> 293 на ÑÑÑÑойÑÑве iPhone или iPad. 294 </li> 295 296 <li style="counter-increment: step-counter; margin-bottom: 16px; padding-left: 36px; position: relative; font-size: 14px; line-height: 1.5; color: #333;"> 297 <span style="position: absolute; left: 0; top: 0; width: 24px; height: 24px; background: white; color: #162b82; border: 2px solid #162b82; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-weight: bold; font-size: 12px;">2</span> 298 ÐеÑейдиÑе на ÑайÑ, Ð´Ð»Ñ ÐºÐ¾ÑоÑого Ð²Ñ Ñ Ð¾ÑиÑе ÑоздаÑÑ ÑÑлÑк. 299 </li> 300 301 <li style="counter-increment: step-counter; margin-bottom: 16px; padding-left: 36px; position: relative; font-size: 14px; line-height: 1.5; color: #333;"> 302 <span style="position: absolute; left: 0; top: 0; width: 24px; height: 24px; background: white; color: #162b82; border: 2px solid #162b82; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-weight: bold; font-size: 12px;">3</span> 303 Рнижней панели Safari нажмиÑе на знаÑок «ÐоделиÑÑÑÑ» 304 <img src="https://cdn-icons-png.flaticon.com/512/10495/10495796.png" alt="ÐоделиÑÑÑÑ" style="width: 20px; height: 20px; vertical-align: middle; margin: 0 2px;"> 305 </li> 306 307 <li style="counter-increment: step-counter; margin-bottom: 16px; padding-left: 36px; position: relative; font-size: 14px; line-height: 1.5; color: #333;"> 308 <span style="position: absolute; left: 0; top: 0; width: 24px; height: 24px; background: white; color: #162b82; border: 2px solid #162b82; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-weight: bold; font-size: 12px;">4</span> 309 ÐÑбеÑиÑе <strong>«ÐобавиÑÑ Ð½Ð° главнÑй ÑкÑан»</strong> 310 <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="vertical-align: middle; margin: 0 2px;"> 311 <rect x="3" y="3" width="18" height="18" rx="2"></rect> 312 <path d="M12 8v8M8 12h8"></path> 313 </svg> 314 </li> 315 316 <li style="counter-increment: step-counter; padding-left: 36px; position: relative; font-size: 14px; line-height: 1.5; color: #333;">
317 <span style="position: absolute; left: 0; top: 0; width: 24px; height: 24px; background: white; color: #162b82; border: 2px solid #162b82; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-weight: bold; font-size: 12px;">5</span> 318 ÐзмениÑе или подÑвеÑдиÑе ÑÐ²ÐµÐ´ÐµÐ½Ð¸Ñ Ð¾ ÑайÑе и нажмиÑе <strong>«ÐобавиÑÑ»</strong>. 319 </li> 320 </ol> 321 </div> 322 323 <div style="background: #e8f5e9; border: 1px solid #4caf50; border-radius: 8px; padding: 12px; margin-bottom: 20px;"> 324 <p style="margin: 0; font-size: 13px; color: #2e7d32; line-height: 1.5;"> 325 <strong>â ÐоÑово!</strong> Ðа главнÑй ÑкÑан добавлен ÑÑлÑк Ð´Ð»Ñ Ð±ÑÑÑÑого доÑÑÑпа к ÑайÑÑ. 326 </p> 327 </div> 328 329 <div style="background: #fff3cd; border: 1px solid #ffc107; border-radius: 8px; padding: 12px;"> 330 <p style="margin: 0; font-size: 13px; color: #856404; line-height: 1.5;"> 331 <strong>ð¡ Ðажно:</strong> Push-ÑÐ²ÐµÐ´Ð¾Ð¼Ð»ÐµÐ½Ð¸Ñ Ð´Ð¾ÑÑÑÐ¿Ð½Ñ ÑолÑко поÑле ÑÑÑановки пÑÐ¸Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ Ð½Ð° главнÑй ÑкÑан (iOS 16.4+). 332 </p> 333 </div> 334 </div> 335 </div> 336 `; 337 338 document.body.appendChild(modal); 339 340 // ÐакÑÑÑие модалки 341 const closeModal = () => modal.remove(); 342 343 document.getElementById('modal-close-btn').addEventListener('click', closeModal); 344 345 // ÐакÑÑÑие по ÐºÐ»Ð¸ÐºÑ Ð½Ð° Ñон 346 modal.addEventListener('click', (e) => { 347 if (e.target === modal) { 348 closeModal(); 349 } 350 }); 351 352 console.log('iOS install modal shown'); 353} 354 355// РегиÑÑÑиÑÑем Service Worker пÑи загÑÑзке 356if ('serviceWorker' in navigator) { 357 navigator.serviceWorker.register('/firebase-messaging-sw.js') 358 .then(async registration => { 359 console.log('Firebase SW registered:', registration.scope); 360 361 // Ðдем акÑиваÑии Service Worker 362 if (registration.installing) { 363 console.log('Service Worker installing, waiting for activation...'); 364 await waitForServiceWorkerActivation(registration); 365 } else if (registration.waiting) { 366 console.log('Service Worker waiting, activating...'); 367 await waitForServiceWorkerActivation(registration); 368 } else if (registration.active) { 369 console.log('Service Worker already active'); 370 } 371 }) 372 .catch(err => { 373 console.error('SW registration failed:', err); 374 }); 375} 376 377// ФÑнкÑÐ¸Ñ Ð¾Ð¶Ð¸Ð´Ð°Ð½Ð¸Ñ Ð°ÐºÑиваÑии Service Worker 378function waitForServiceWorkerActivation(registration) { 379 return new Promise((resolve) => { 380 const worker = registration.installing || registration.waiting || registration.active; 381 382 if (!worker) { 383 resolve(); 384 return; 385 } 386 387 if (worker.state === 'activated') { 388 resolve(); 389 return; 390 } 391 392 worker.addEventListener('statechange', (e) => { 393 console.log('Service Worker state changed to:', e.target.state); 394 if (e.target.state === 'activated') { 395 resolve(); 396 } 397 }); 398 }); 399} 400 401// ÐÑÑлеживание ÑÑпеÑной ÑÑÑановки 402window.addEventListener('appinstalled', async (evt) => { 403 console.log('PWA ÑÑпеÑно ÑÑÑановлено!'); 404 405 // Ð¡Ð¾Ñ ÑанÑем Ñлаг ÑÑÑановки 406 localStorage.setItem('pwa-install-started', 'true'); 407 localStorage.setItem('pwa-installed', 'true'); 408 409 // СкÑÑваем вÑе баннеÑÑ ÑÑÑановки 410 const installBanner = document.getElementById('pwa-install-banner'); 411 const iosPanel = document.getElementById('ios-install-panel'); 412 if (installBanner) installBanner.remove(); 413 if (iosPanel) iosPanel.remove(); 414 415 // ÐÐÐÐÐЫÐÐÐРУÐÐÐÐÐÐÐÐÐÐ ÐРУСÐÐШÐÐРУСТÐÐÐÐÐÐ 416 showInstallSuccessNotification(); 417 418 // ÐвÑомаÑиÑеÑки запÑаÑиваем ÑазÑеÑение на push поÑле ÑÑÑановки 419 setTimeout(() => { 420 requestNotificationPermission(); 421 }, 3000); 422}); 423 424// ФÑнкÑÐ¸Ñ Ð¿Ð¾ÐºÐ°Ð·Ð° ÑÐ²ÐµÐ´Ð¾Ð¼Ð»ÐµÐ½Ð¸Ñ Ð¾Ð± ÑÑпеÑной ÑÑÑановке 425async function showInstallSuccessNotification() { 426 // СпоÑоб 1: ЧеÑез Service Worker (еÑли еÑÑÑ ÑазÑеÑение) 427 if ('serviceWorker' in navigator && Notification.permission === 'granted') { 428 try { 429 const registration = await navigator.serviceWorker.getRegistration(); 430 if (registration) { 431 await registration.showNotification('ART 1', { 432 body: 'ÐÑиложение ÑÑпеÑно ÑÑÑановлено! ТепеÑÑ Ð²Ñ Ð¼Ð¾Ð¶ÐµÑе полÑÑаÑÑ ÑÐ²ÐµÐ´Ð¾Ð¼Ð»ÐµÐ½Ð¸Ñ Ð¾ новÑÑ Ð»Ð¾ÑÐ°Ñ Ð¸ ÑоÑÐ³Ð°Ñ .', 433 icon: '/img/icon-192x192.png',
434 badge: '/img/icon-96x96.png', 435 tag: 'install-success', 436 vibrate: [200, 100, 200], 437 requireInteraction: false 438 }); 439 console.log('Install success notification shown via SW'); 440 return; 441 } 442 } catch (e) { 443 console.log('SW notification failed:', e); 444 } 445 } 446 447 // СпоÑоб 2: ЧеÑез Notification API напÑÑмÑÑ 448 if ('Notification' in window && Notification.permission === 'granted') { 449 try { 450 new Notification('ART 1', { 451 body: 'ÐÑиложение ÑÑпеÑно ÑÑÑановлено!', 452 icon: '/img/icon-192x192.png', 453 tag: 'install-success' 454 }); 455 console.log('Install success notification shown via Notification API'); 456 return; 457 } catch (e) { 458 console.log('Notification API failed:', e); 459 } 460 } 461 462 // СпоÑоб 3: ÐоказÑваем кÑаÑивÑй Ð±Ð°Ð½Ð½ÐµÑ Ð²Ð½ÑÑÑи ÑÑÑаниÑÑ (fallback) 463 showInstallSuccessBanner(); 464} 465 466// ÐÑаÑивÑй Ð±Ð°Ð½Ð½ÐµÑ Ð¾Ð± ÑÑпеÑной ÑÑÑановке (еÑли ÑÐ²ÐµÐ´Ð¾Ð¼Ð»ÐµÐ½Ð¸Ñ Ð½Ðµ доÑÑÑпнÑ) 467function showInstallSuccessBanner() { 468 const banner = document.createElement('div'); 469 banner.id = 'install-success-banner'; 470 banner.style.cssText = ` 471 position: fixed; 472 top: 20px; 473 left: 50%; 474 transform: translateX(-50%); 475 background: linear-gradient(135deg, #4CAF50 0%, #45a049 100%); 476 color: white; 477 padding: 16px 24px; 478 border-radius: 12px; 479 box-shadow: 0 4px 20px rgba(0,0,0,0.3); 480 z-index: 10001; 481 display: flex;
482 align-items: center; 483 gap: 12px; 484 font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Arial, sans-serif; 485 animation: slideDown 0.3s ease; 486 max-width: 90%; 487 `; 488 489 banner.innerHTML = ` 490 <svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> 491 <path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/> 492 <polyline points="22 4 12 14.01 9 11.01"/> 493 </svg> 494 <div> 495 <div style="font-weight: 600; font-size: 16px;">ÐÑиложение ÑÑÑановлено!</div> 496 <div style="font-size: 13px; opacity: 0.9;">ТепеÑÑ Ð´Ð¾ÑÑÑпно на главном ÑкÑане</div> 497 </div> 498 `; 499 500 // ÐобавлÑем анимаÑÐ¸Ñ 501 const style = document.createElement('style'); 502 style.textContent = ` 503 @keyframes slideDown { 504 from { opacity: 0; transform: translateX(-50%) translateY(-20px); } 505 to { opacity: 1; transform: translateX(-50%) translateY(0); } 506 } 507 `; 508 document.head.appendChild(style); 509 510 document.body.appendChild(banner); 511 512 // УбиÑаем ÑеÑез 5 ÑекÑнд 513 setTimeout(() => { 514 banner.style.animation = 'slideDown 0.3s ease reverse'; 515 setTimeout(() => banner.remove(), 300); 516 }, 5000); 517 518 console.log('Install success banner shown'); 519} 520 521// ÐапÑÐ¾Ñ ÑазÑеÑÐµÐ½Ð¸Ñ Ð½Ð° ÑÐ²ÐµÐ´Ð¾Ð¼Ð»ÐµÐ½Ð¸Ñ 522async function requestNotificationPermission() { 523 try { 524 const permission = await Notification.requestPermission(); 525 526 if (permission === 'granted') { 527 console.log('Notification permission granted.'); 528 await subscribeToNotifications(); 529 } else { 530 console.log('Notification permission denied.'); 531 } 532 } catch (error) { 533 console.error('Error requesting notification permission:', error); 534 } 535} 536 537// ÐодпиÑка на push ÑÐ²ÐµÐ´Ð¾Ð¼Ð»ÐµÐ½Ð¸Ñ 538async function subscribeToNotifications() { 539 try { 540 console.log('Starting subscription process...'); 541 542 // ÐолÑÑаем заÑегиÑÑÑиÑованнÑй Service Worker Ð´Ð»Ñ Firebase 543 const registration = await navigator.serviceWorker.getRegistration('/'); 544 if (!registration) { 545 console.error('No service worker registered!'); 546 return; 547 } 548 549 // ÐÑовеÑÑем ÑÑо Service Worker акÑивен 550 if (!registration.active) { 551 console.log('Service Worker not active, waiting for activation...'); 552 await waitForServiceWorkerActivation(registration); 553 } 554 555 console.log('â Using Service Worker registration:', registration.scope); 556 console.log('â Service Worker state:', registration.active?.state); 557 558 // ÐолÑÑаем FCM Ñокен 559 console.log('Getting FCM token...'); 560 const currentToken = await getToken(messaging, { 561 vapidKey: VAPID_KEY, 562 serviceWorkerRegistration: registration 563 }); 564 565 if (currentToken) { 566 console.log('â FCM Token received:', currentToken); 567 // ÐÑпÑавлÑем Ñокен на ÑеÑÐ²ÐµÑ Ð´Ð»Ñ ÑÐ¾Ñ ÑÐ°Ð½ÐµÐ½Ð¸Ñ 568 await saveTokenToServer(currentToken); 569 console.log('â Subscription complete!'); 570 } else { 571 console.log('No registration token available.'); 572 } 573 } catch (error) { 574 console.error('â Error subscribing to notifications:', error); 575 } 576} 577 578// Ð¡Ð¾Ñ Ñанение Ñокена на ÑеÑвеÑе 579async function saveTokenToServer(token) { 580 try { 581 const response = await fetch('/wp-admin/admin-ajax.php', { 582 method: 'POST', 583 headers: { 584 'Content-Type': 'application/x-www-form-urlencoded', 585 }, 586 body: new URLSearchParams({ 587 action: 'save_fcm_token', 588 token: token, 589 user_agent: navigator.userAgent, 590 subscribed_at: new Date().toISOString() 591 }) 592 }); 593 594 const data = await response.json(); 595 console.log('Token saved to server:', data); 596 } catch (error) { 597 console.error('Error saving token to server:', error); 598 } 599} 600 601// ÐбÑабоÑка Ð²Ñ Ð¾Ð´ÑÑÐ¸Ñ ÑообÑений (когда пÑиложение оÑкÑÑÑо) 602onMessage(messaging, async (payload) => { 603 console.log('Message received in foreground:', payload); 604 605 const notificationTitle = payload.notification?.title || 'ART 1'; 606 const notificationOptions = { 607 body: payload.notification?.body || 'Ðовое Ñведомление', 608 icon: payload.notification?.icon || '/img/icon-192x192.png',
609 badge: '/img/icon-96x96.png', 610 tag: 'antique-auction-foreground', 611 requireInteraction: false, 612 data: { 613 url: payload.data?.url || payload.fcmOptions?.link || '/' 614 } 615 }; 616 617 // ÐоказÑваем Ñведомление ÑеÑез Service Worker (ÑабоÑÐ°ÐµÑ Ð½Ð° Android) 618 if (Notification.permission === 'granted') { 619 try { 620 const registration = await navigator.serviceWorker.getRegistration(); 621 if (registration) { 622 await registration.showNotification(notificationTitle, notificationOptions); 623 console.log('Notification shown via Service Worker'); 624 } 625 } catch (error) { 626 console.error('Error showing notification:', error); 627 } 628 } 629}); 630 631// Флаг гоÑовноÑÑи Service Worker 632let serviceWorkerReady = false; 633 634// ÐвÑомаÑиÑеÑÐºÐ°Ñ Ð¿Ð¾Ð´Ð¿Ð¸Ñка пÑи загÑÑзке (ТÐÐЬÐРпоÑле гоÑовноÑÑи SW) 635window.addEventListener('load', async () => { 636 if ('Notification' in window) { 637 // ÐÐÐÐ ÑегиÑÑÑаÑии Service Worker 638 console.log('Waiting for Service Worker to be ready...'); 639 await navigator.serviceWorker.ready; 640 serviceWorkerReady = true; 641 console.log('Service Worker is ready, checking permissions...'); 642 643 if (Notification.permission === 'granted') { 644 // Уже еÑÑÑ ÑазÑеÑение - подпиÑÑваемÑÑ 645 console.log('Permission already granted, subscribing...'); 646 subscribeToNotifications(); 647 } else if (Notification.permission === 'default' && isMobileDevice()) { 648 // ÐоказÑваем пÑÐ¾Ð¼Ð¿Ñ ÑеÑез 10 ÑекÑнд ТÐÐЬÐРна мобилÑнÑÑ 649 setTimeout(() => { 650 showNotificationPrompt(); 651 }, 10000); 652 } 653 } 654}); 655 656// ÐÑовеÑка PWA Ñежима (ÐÐСÐРзагÑÑзки) 657window.addEventListener('load', async () => { 658 if (window.matchMedia('(display-mode: standalone)').matches) { 659 console.log('Running as PWA, waiting for SW...'); 660 await navigator.serviceWorker.ready; 661 serviceWorkerReady = true; 662 console.log('SW ready in PWA mode'); 663 664 if (Notification.permission === 'granted') { 665 subscribeToNotifications(); 666 } else if (Notification.permission === 'default' && isMobileDevice()) { 667 // Ð PWA ÑÑÐ°Ð·Ñ Ð·Ð°Ð¿ÑаÑиваем ÑазÑеÑение ТÐÐЬÐРна мобилÑнÑÑ 668 setTimeout(() => { 669 requestNotificationPermission(); 670 }, 2000); 671 } 672 } 673}); 674 675function showNotificationPrompt() { 676 const promptBanner = document.createElement('div'); 677 promptBanner.id = 'notification-prompt-banner'; 678 promptBanner.className = 'notification-cto-panel'; 679 promptBanner.style.cssText = ` 680 background: #252930; 681 position: fixed; 682 left: 0; 683 right: 0; 684 bottom: 0; 685 z-index: 9999; 686 padding: 20px; 687 display: flex; 688 justify-content: center; 689 align-items: center; 690 gap: 8px; 691 box-shadow: 0 -2px 10px rgba(0,0,0,0.3); 692 font-family: Arial, sans-serif; 693 `; 694 695 promptBanner.innerHTML = ` 696 <button id="notification-close-btn" style="border: none; background: transparent; padding: 0; cursor: pointer; flex-shrink: 0;"> 697 <svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="#e74b3d" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> 698 <line x1="18" y1="6" x2="6" y2="18"></line> 699 <line x1="6" y1="6" x2="18" y2="18"></line> 700 </svg> 701 </button> 702 703 <button id="notification-allow-btn" style=" 704 border: 2px solid white; 705 padding: 10px 12px; 706 flex-grow: 1; 707 max-width: 300px; 708 box-sizing: border-box; 709 color: white; 710 background: transparent; 711 display: flex; 712 justify-content: center; 713 align-items: center; 714 font-size: 14px; 715 font-weight: bold; 716 border-radius: 12px; 717 text-align: center; 718 cursor: pointer; 719 transition: all 0.2s; 720 " onmouseover="this.style.background='white'; this.style.color='black';" onmouseout="this.style.background='transparent'; this.style.color='white';"> 721 РазÑеÑиÑÑ ÑÐ²ÐµÐ´Ð¾Ð¼Ð»ÐµÐ½Ð¸Ñ 722 </button> 723 724 <div style="flex-shrink: 0; width: 60px; height: 60px; background: #8b0000; border-radius: 8px; display: flex; align-items: center; justify-content: center; font-size: 28px;"> 725 ð 726 </div> 727 `; 728 729 document.body.appendChild(promptBanner); 730 731 document.getElementById('notification-allow-btn').addEventListener('click', () => { 732 requestNotificationPermission(); 733 promptBanner.remove(); 734 }); 735 736 document.getElementById('notification-close-btn').addEventListener('click', () => { 737 promptBanner.remove(); 738 }); 739} 740 741console.log('Firebase initialized successfully!'); 742
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.