1// Service worker: пÑÑ-ÑÐ²ÐµÐ´Ð¾Ð¼Ð»ÐµÐ½Ð¸Ñ Ð¸ оÑлайн-Ñежим пÑиложениÑ. 2// 3// ÐÐÐÐÐ ÐÐ Ð Ð ÐÐÐУС: ÑÑÐ¾Ñ worker ÑегиÑÑÑиÑÑеÑÑÑ Ñ ÐÐÐÐÐÐРпоÑеÑиÑÐµÐ»Ñ ÑайÑа 4// (PushNotifications в коÑневой ÑаÑкладке), а не ÑолÑко Ñ Ð¿Ð¾ÑÑавивÑÐ¸Ñ 5// пÑиложение. ÐÑÑ, ÑÑо здеÑÑ Ð½Ð°Ð¿Ð¸Ñано, менÑÐµÑ ÑпоÑоб доÑÑавки ÑайÑа вÑей 6// аÑдиÑоÑии. Ð ÑломаннÑй worker не оÑкаÑÑваеÑÑÑ Ð²Ñкладкой пÑежнего обÑаза â он 7// живÑÑ Ð² бÑаÑзеÑе Ñеловека, оÑмениÑÑ ÐµÐ³Ð¾ Ð¼Ð¾Ð¶ÐµÑ ÑолÑко новÑй sw.js. 8// 9// ÐÑо кÑÑ Ð·Ð´ÐµÑÑ Ð¾Ð´Ð½Ð° опаÑноÑÑÑ â неÑвежеÑÑÑ Ð³Ð»Ð°Ð²Ð½Ð¾Ð¹, коÑоÑÑÑ Ñинили в авгÑÑÑе. 10// ÐоÑÑÐ¾Ð¼Ñ ÑÑÑаниÑÑ Ð±ÐµÑÑÑÑÑ Ð¢ÐÐЬÐÐ ÑÑÑаÑегией «ÑнаÑала ÑеÑÑ»: пока ÑеÑÑ ÐµÑÑÑ, 11// Ñеловек вÑегда Ð²Ð¸Ð´Ð¸Ñ Ð¶Ð¸Ð²ÑÑ Ð²ÐµÑÑиÑ, а кÑÑ Ð´Ð¾ÑÑаÑÑÑÑ Ð»Ð¸ÑÑ ÐºÐ¾Ð³Ð´Ð° ÑеÑи неÑ, и 12// ÑолÑко еÑли ÐºÐ¾Ð¿Ð¸Ñ Ð¼Ð¾Ð»Ð¾Ð¶Ðµ ÑÑÑок. ÐÑÑиÑÑеÑÑÑ Ñо, ÑÑо не ÑÑÑаÑÐµÐ²Ð°ÐµÑ Ð½ÐµÐ·Ð°Ð¼ÐµÑно: 13// ÑÐ°Ð¹Ð»Ñ ÑбоÑки (Ñ ÑÑ Ð² имени) и каÑÑинки новоÑÑей (ÑÐ°Ð·Ð¼ÐµÑ Ð¸ каÑеÑÑво в адÑеÑе). 14// 15// ÐРпеÑÐµÑ Ð²Ð°ÑÑваем ÑлÑжебнÑе оÑвеÑÑ App Router (`?_rsc=`), `/api/**` и вÑÑ 16// пÑоÑее. RSC â ÑÐ¾Ñ Ð¶Ðµ конÑÐµÐ½Ñ ÑÑÑаниÑÑ Ð¾ÑделÑнÑм запÑоÑом; закÑÑиÑовав его, Ð¼Ñ 17// веÑнÑли Ð±Ñ ÑÑ Ð¶Ðµ неÑвежеÑÑÑ, ÑолÑко незамеÑнее. 18 19// ÐоднимаÑÑ Ð¿Ñи изменении ÐÐ ÐÐÐРкÑÑиÑованиÑ, а не пÑи каждом вÑкаÑе: имена 20// Ñайлов ÑбоÑки Ñ ÑÑиÑованÑ, ÑÑÑаниÑÑ Ð¸ Ñак пÑовеÑÑÑÑÑÑ ÑеÑÑÑ, а ÑоÑÑ Ñ ÑанилиÑа 21// ÑдеÑживаÑÑ Ð¿Ð¾Ñолки ниже. 22const VERSION = 'v1' 23const CACHE_PREFIX = 'omskinform-' 24// ÐаглÑÑке нÑжен Ñвой кÑÑ: лежи она ÑÑеди Ñайлов ÑбоÑки, поÑолок вÑÑеÑнил Ð±Ñ 25// именно ÐµÑ â она кладÑÑÑÑ Ð¿ÐµÑвой, а вÑÑеÑнÑÑÑÑÑ ÑамÑе Ñанние запиÑи. 26// ÐÑделÑнÑй кÑÑ Ð¿Ð¾Ð´ Ð¾Ð´Ð½Ñ ÑлÑжебнÑÑ Ð·Ð°Ð¿Ð¸ÑÑ: ÑÑÑаниÑа не Ð¼Ð¾Ð¶ÐµÑ Ð¿ÑоÑиÑаÑÑ 27// заголовки навигаÑионного оÑвеÑа и не Ð¼Ð¾Ð¶ÐµÑ ÑпÑоÑиÑÑ worker'а до Ñого, как 28// загÑÑзиÑÑÑ. ÐоÑÑÐ¾Ð¼Ñ worker оÑÑавлÑÐµÑ Ð·Ð´ÐµÑÑ Ð¾ÑмеÑÐºÑ Â«ÑÑа ÑÑÑаниÑа оÑдана из 29// копии», а ÑÑÑаниÑа ÑиÑÐ°ÐµÑ ÐµÑ Ð¿Ñи ÑÑаÑÑе и говоÑÐ¸Ñ Ð¾Ð± ÑÑом ÑеловекÑ. 30const STATE_CACHE = `${CACHE_PREFIX}state-${VERSION}` 31const LAST_NAVIGATION = '/__last-navigation' 32 33const SHELL_CACHE = `${CACHE_PREFIX}shell-${VERSION}` 34const PAGES_CACHE = `${CACHE_PREFIX}pages-${VERSION}` 35const STATIC_CACHE = `${CACHE_PREFIX}static-${VERSION}` 36const IMAGES_CACHE = `${CACHE_PREFIX}images-${VERSION}` 37const OWN_CACHES = [SHELL_CACHE, PAGES_CACHE, STATIC_CACHE, IMAGES_CACHE, STATE_CACHE] 38 39const OFFLINE_URL = '/offline' 40const CACHED_AT = 'x-oi-cached-at' 41 42// СÑÑки: новоÑÑÑ ÑÑаÑÑе ÑÑÑок Ñеловек пÑÐ¸Ð¼ÐµÑ Ð·Ð° ÑегоднÑÑнÑÑ Ð¸ не поймÑÑ, ÑÑо 43// ÑиÑÐ°ÐµÑ Ð²ÑеÑаÑний Ð´ÐµÐ½Ñ â ÑеÑÑÐ½Ð°Ñ Ð·Ð°Ð³Ð»ÑÑка лÑÑÑе. 44const PAGE_MAX_AGE = 24 * 60 * 60 * 1000 45 46// СколÑко ждаÑÑ ÑеÑÑ, пÑежде Ñем показаÑÑ ÐºÐ¾Ð¿Ð¸Ñ Ð¸Ð· кÑÑа. 47// 48// ÐпаÑен не обÑÑв ÑвÑзи â пÑи нÑм fetch Ð¿Ð°Ð´Ð°ÐµÑ ÑÑазÑ, и ÐºÐ¾Ð¿Ð¸Ñ Ð¾ÑдаÑÑÑÑ Ð¼Ð³Ð½Ð¾Ð²ÐµÐ½Ð½Ð¾. 49// ÐпаÑна ÑеÑÑ, коÑоÑÐ°Ñ ÐСТЬ, но молÑиÑ: Ñак бÑваеÑ, пока пÑоÑÑпаеÑÑÑ Ñадио 50// ÑелеÑона поÑле Ñна. Ðез пÑедела worker ждал ÐµÑ ÑколÑко Ñгодно, и Ñеловек 51// ÑмоÑÑел на пÑÑÑой ÑкÑан много ÑекÑнд â жалоба владелÑÑа 09.09.2026: 52// «запÑÑÐºÐ°Ñ Ñ Ð¸ÐºÐ¾Ð½ÐºÐ¸, белÑй ÑкÑан, вообÑе ниÑего не гÑÑзиÑ, пеÑÐµÐ·Ð°Ñ Ð¾Ð¶Ñ â ноÑм». 53// ÐÑкÑда ÑÑи ÑекÑндÑ. ÐÑо бÑÐ´Ð¶ÐµÑ Ð½Ð° ÐÐÐÐÐÐÐÐРоÑвеÑа, а не на вÑÑ ÑÑÑаниÑÑ: 54// fetch ÑазÑеÑаеÑÑÑ, как ÑолÑко пÑиÑли заголовки. Ðо ÑмоÑк-замеÑам пÑода пеÑвÑй 55// Ð±Ð°Ð¹Ñ Ð³Ð»Ð°Ð²Ð½Ð¾Ð¹ пÑÐ¸Ñ Ð¾Ð´Ð¸Ñ Ð·Ð° 0,31â0,46 Ñ, Ñо еÑÑÑ ÑÑи ÑекÑÐ½Ð´Ñ â ÑÑо ÑемÑ-деÑÑÑÑ 56// обÑÑнÑÑ Ð¾ÑвеÑов подÑÑд. ÐеÑÑ Ð½ÑÑ Ð³ÑаниÑа взÑÑа из наблÑдавÑейÑÑ Ð¿Ð°Ñологии: 32 57// ÑекÑÐ½Ð´Ñ Ð½Ð° пеÑвÑй запÑÐ¾Ñ Ðº главной поÑле пеÑеÑÐ¾Ð·Ð´Ð°Ð½Ð¸Ñ ÐºÐ¾Ð½ÑейнеÑа; Ñакой ÑлÑÑай 58// пÑедел и оÑÑекаеÑ. УÑоÑнение ÑеÑÑии omskinform. 59const NETWORK_TIMEOUT = 3000 60 61// ÐоÑолки ÑÑиÑÐ°Ð½Ñ Ð¿Ð¾ веÑÑ Ð¶Ð¸Ð²ÑÑ ÑÑÑаниÑ: HTML маÑеÑиала около 210 ÐÐ 62// неÑжаÑÑм, Ð³Ð»Ð°Ð²Ð½Ð°Ñ â 530 ÐÐ, каÑÑинка â оÐ
62ºÐ¾Ð»Ð¾ 200 ÐÐ. ТÑидÑаÑÑ ÑÑÑÐ°Ð½Ð¸Ñ Ð¸ 63// ÑеÑÑÑдеÑÑÑ ÐºÐ°ÑÑинок даÑÑ Ð¿Ð¾ÑÑдка 18 ÐÐ; на Ñакой велиÑине бÑаÑÐ·ÐµÑ Ñ ÑанилиÑе 64// не вÑбÑаÑÑваеÑ. Ð¤Ð°Ð¹Ð»Ñ ÑбоÑки огÑаниÑÐµÐ½Ñ Ð¾ÑделÑно: Ð¸Ñ Ð¸Ð¼ÐµÐ½Ð° менÑÑÑÑÑ Ñ ÐºÐ°Ð¶Ð´Ñм 65// вÑкаÑом, поÑÑÐ¾Ð¼Ñ Ð±ÐµÐ· поÑолка ÐºÐ¾Ð¼Ð¿Ð»ÐµÐºÑ Ñанков копилÑÑ Ð±Ñ Ð²ÐµÑно. 66const LIMITS = { 67 [PAGES_CACHE]: 30, 68 [STATIC_CACHE]: 120, 69 [IMAGES_CACHE]: 60, 70} 71 72const isOwnRequest = (request) => { 73 if (request.method !== 'GET') { 74 return false 75 } 76 77 const url = new URL(request.url) 78 79 return url.origin === self.location.origin && !url.searchParams.has('_rsc') && !url.pathname.startsWith('/api/') 80} 81 82// Ðмена Ñайлов ÑбоÑки ÑодеÑÐ¶Ð°Ñ Ñ ÑÑ: поÑле вÑкаÑа менÑеÑÑÑ Ñамо имÑ, и ÑÑаÑÑй 83// Ñайл новÑй подмениÑÑ Ð½Ðµ можеÑ. Ðконки и ÑÑиÑÑÑ Ð¼ÐµÐ½ÑÑÑÑÑ Ñаз в год. 84const isBuildAsset = (url) => 85 url.pathname.startsWith('/_next/static/') || 86 url.pathname.startsWith('/icon-') || 87 url.pathname === '/apple-touch-icon.png' || 88 url.pathname.endsWith('.woff2') 89 90const isArticleImage = (url) => url.pathname === '/_next/image' 91 92const dropOldest = async (cache, limit) => { 93 const keys = await cache.keys() 94 95 for (const key of keys.slice(0, keys.length - limit)) { 96 await cache.delete(key) 97 } 98} 99 100// ÐÑмеÑка вÑемени нÑжна ÑолÑко ÑÑÑаниÑам: Ñ Ð½Ð¸Ñ ÐµÑÑÑ ÑÑок годноÑÑи, Ñ Ñайлов 101// ÑбоÑки и каÑÑинок его Ð½ÐµÑ â Ð¸Ñ Ð°Ð´ÑÐµÑ Ð¼ÐµÐ½ÑеÑÑÑ Ð²Ð¼ÐµÑÑе Ñ ÑодеÑжимÑм. 102const stamped = async (response) => { 103 const headers = new Headers(response.headers) 104 headers.set(CACHED_AT, String(Date.now())) 105 106 return new Response(await response.blob(), { 107 status: response.status, 108 statusText: response.statusText, 109 headers, 110 }) 111} 112 113// ÐÑÐ²ÐµÑ Ð¿Ð¾Ñле пеÑенапÑÐ°Ð²Ð»ÐµÐ½Ð¸Ñ ÐºÐ»Ð°ÑÑÑ Ð½ÐµÐ»ÑзÑ: Ñ Ð½Ð°Ð²Ð¸Ð³Ð°Ñии redirect === 'manual', 114// и оÑданнÑй из кÑÑа оÑÐ²ÐµÑ Ñ redirected === true ÑонÑÐµÑ respondWith â Ñеловек 115// ÑÐ²Ð¸Ð´Ð¸Ñ Ð¾ÑÐ¸Ð±ÐºÑ ÑеÑи вмеÑÑо заглÑÑки. 116const keep = async (cacheName, request, response) => { 117 if (!response.ok || response.redirected) { 118 return 119 } 120 121 const cache = await caches.open(cacheName) 122 await cache.put(request, cacheName === PAGES_CACHE ? await stamped(response) : response) 123 await dropOldest(cache, LIMITS[cacheName]) 124} 125 126const isFresh = (response) => { 127 const savedAt = Number(response.headers.get(CACHED_AT)) 128 129 return Boolean(savedAt) && Date.now() - savedAt < PAGE_MAX_AGE 130} 131 132// УÑÑановка бÑÐ²Ð°ÐµÑ Ð¸ без ÑеÑи, поÑÑÐ¾Ð¼Ñ Ð·Ð°Ð³Ð»ÑÑÐºÑ Ð´Ð¾ÐºÐ»Ð°Ð´Ñваем пÑи пеÑвом ÑдаÑном 133// пеÑÐµÑ Ð¾Ð´Ðµ: ждаÑÑ ÑледÑÑÑей ÑÑÑановки нелÑзÑ, она Ð¼Ð¾Ð¶ÐµÑ Ð½Ðµ ÑлÑÑиÑÑÑÑ Ð¼ÐµÑÑÑами. 134const ensureOfflinePage = async () => { 135 const shell = await caches.open(SHELL_CACHE) 136 137 if (await shell.match(OFFLINE_URL)) { 138 return 139 } 140 141 try { 142 await shell.addAll([OFFLINE_URL]) 143 } catch (precacheError) { 144 // РазбиÑаÑÑ Ð½ÐµÑего: ÑеÑи Ð¼Ð¾Ð¶ÐµÑ Ð½Ðµ бÑÑÑ, ÑÑо не поломка. 145 } 146} 147 148/** 149 * ÐÑмеÑиÑÑ, ÑÑо ÑÐµÐ»Ð¾Ð²ÐµÐºÑ Ð¿Ð¾ÐºÐ°Ð·Ð°Ð½Ð° ÑÐ¾Ñ ÑанÑÐ½Ð½Ð°Ñ ÐºÐ¾Ð¿Ð¸Ñ, â или ÑнÑÑÑ Ð¾ÑмеÑкÑ, когда 150 * пÑиÑла Ð¶Ð¸Ð²Ð°Ñ ÑÑÑаниÑа. СнимаÑÑ Ð¾Ð±ÑзаÑелÑно: инаÑе полоÑка «показано 151 * ÑÐ¾Ñ ÑанÑнное» оÑÑалаÑÑ Ð±Ñ Ð²Ð¸ÑеÑÑ Ð¿Ð¾Ñле возвÑаÑÐµÐ½Ð¸Ñ ÑвÑзи. 152 */ 153const markNavigation = async (request, cachedAt) => { 154 const state = await caches.open(STATE_CACHE) 155 const key = new URL(LAST_NAVIGATION, self.location.origin).href 156 157 if (!cachedAt) { 158 await state.delete(key) 159 160 return 161 } 162 163 const telo = JSON.stringify({ url: request.url, cachedAt }) 164 165 await state.put(key, new Response(telo, { headers: { 'content-type': 'application/json' } })) 166} 167 168const offlineFallback = async (request) => { 169 const pages = await caches.open(PAGES_CACHE) 170 const seenBefore = await pages.match(request) 171 172 if (seenBefore && isFresh(seenBefore)) { 173 // Ð¢Ð¾Ñ Ð¶Ðµ ÑаÑÑÑÑ, ÑÑо и Ð´Ð»Ñ Ð¶Ð¸Ð²Ð¾Ð¹ ÑÑÑаниÑÑ: оÑказ Ñ ÑанилиÑа не должен 174 // оÑнимаÑÑ Ñ Ñеловека копиÑ, коÑоÑÐ°Ñ Ñже наÑлаÑÑ. 175 await markNavigation(request, Number(seenBefore.headers.get(CACHED_AT))).catch(() => undefined) 176 177 return seenBefore 178 } 179 180 const shell = await caches.open(SHELL_CACHE) 181 182 return (await shell.match(OFFLINE_URL)) || new Response('ÐÐµÑ ÑвÑзи', { status: 503 }) 183} 184 185const freshCopy = async (request) => { 186 const pages = await caches.open(PAGES_CACHE) 187 const stored = await pages.match(request) 188 189 return stored && isFresh(stored) ? stored : undefined 190} 191 192// ÐозвÑаÑÐ°ÐµÑ Ð¸ обеÑание, и ÑпоÑоб ÑнÑÑÑ ÑаймеÑ: без ÑнÑÑÐ¸Ñ Ð¾Ð½ доживал Ð±Ñ Ð´Ð¾ 193// ÑÑабаÑÑÐ²Ð°Ð½Ð¸Ñ Ñже поÑле Ñого, как ÑеÑÑ Ð¾ÑвеÑила, â мелоÑÑ, но виÑÑÑÐ¸Ñ ÑаймеÑов 194// в worker'е бÑÑÑ Ð½Ðµ должно. 195const afterTimeout = (ms) => { 196 let timer 197 198 return { 199 promise: new Promise((resolve) => { 200 timer = setTimeout(() => resolve(undefined), ms) 201 }), 202 cancel: () => clearTimeout(timer), 203 } 204} 205 206// ÐÑÐ²ÐµÑ ÑÑ Ð¾Ð´Ð¸Ñ ÑÐµÐ»Ð¾Ð²ÐµÐºÑ ÑÑазÑ, запиÑÑ Ð¸Ð´ÑÑ Ñледом в waitUntil. ÐождиÑÑ Ð¼Ñ
207// запиÑи â поÑÐ¾ÐºÐ¾Ð²Ð°Ñ Ð¾ÑÑиÑовка App Router вÑклÑÑилаÑÑ Ð±Ñ Ð½Ð° каждом пеÑÐµÑ Ð¾Ð´Ðµ Ñ 208// каждого поÑеÑиÑелÑ: клон ÑиÑаеÑÑÑ Ð´Ð¾ конÑа, а Ð³Ð»Ð°Ð²Ð½Ð°Ñ Ð²ÐµÑÐ¸Ñ Ð¿Ð¾Ð»Ð¼ÐµÐ³Ð°Ð±Ð°Ð¹Ñа. 209const keepPage = (event, response) => { 210 event.waitUntil(keep(PAGES_CACHE, event.request, response.clone())) 211 event.waitUntil(ensureOfflinePage()) 212} 213 214const pageFromNetwork = async (event) => { 215 const fromNetwork = fetch(event.request) 216 const copy = await freshCopy(event.request) 217 218 // Ðопии Ð½ÐµÑ â ждÑм ÑеÑÑ ÑколÑко поÑÑебÑеÑÑÑ: показаÑÑ Â«Ð½ÐµÑ ÑвÑзи» пÑи живом 219 // инÑеÑнеÑе Ñ Ñже, Ñем подождаÑÑ. 220 if (!copy) { 221 try { 222 const response = await fromNetwork 223 224 keepPage(event, response) 225 226 return response 227 } catch (networkError) { 228 return offlineFallback(event.request) 229 } 230 } 231 232 const predel = afterTimeout(NETWORK_TIMEOUT) 233 const response = await Promise.race([fromNetwork.catch(() => undefined), predel.promise]) 234 235 predel.cancel() 236 237 if (response) { 238 keepPage(event, response) 239 240 // СнÑÑие оÑмеÑки ждÑм ÐРоÑвеÑа, а не в Ñоне: ÑÑÑаниÑа ÑиÑÐ°ÐµÑ ÐµÑ ÑÑÐ°Ð·Ñ Ð¿Ð¾Ñле 241 // загÑÑзки и ÑÑпела Ð±Ñ ÑвидеÑÑ Ð¿ÑоÑлÑÑ â полоÑка «показано ÑÐ¾Ñ ÑанÑнное» 242 // виÑела Ð±Ñ Ð½Ð° живой ÑÑÑаниÑе. Ðовод «не блокиÑоваÑÑ Ð¾ÑвеÑ» напиÑан пÑо 243 // keep(): ÑÐ¾Ñ ÑиÑÐ°ÐµÑ Ñело Ñеликом, а здеÑÑ Ð¾Ð´Ð½Ð° коÑоÑÐºÐ°Ñ Ð·Ð°Ð¿Ð¸ÑÑ. 244 // 245 // ÐÑÐ¸Ð±ÐºÑ Ð³Ð°Ñим намеÑенно, и ÑÑо не ÑкÑаÑаÑелÑÑÑво: Cache Storage оÑказÑÐ²Ð°ÐµÑ 246 // в пÑиваÑном Ñежиме и когда конÑилоÑÑ Ð¼ÐµÑÑо. Ðез catch Ñакой оÑказ вÑлеÑел 247 // Ð±Ñ Ð² обÑий блок ниже, и Ñеловек полÑÑил Ð±Ñ ÐÐÐÐЮ вмеÑÑо живой ÑÑÑаниÑÑ, 248 // коÑоÑÐ°Ñ Ñже Ñ Ð½Ð°Ñ Ð² ÑÑÐºÐ°Ñ , â да еÑÑ Ñ Ð¿Ð¾Ð»Ð¾Ñкой повеÑÑ , Ð²ÐµÐ´Ñ Ð¾ÑмеÑка не 249 // ÑнÑлаÑÑ. Ðве оÑибки за один Ñбой Ñ ÑанилиÑа. УÑловие ÑеÑÑии omskinform. 250 await markNavigation(event.request, 0).catch(() => undefined) 251 252 return response 253 } 254 255 // СеÑÑ Ð½Ðµ оÑвеÑила вовÑемÑ: Ñеловек полÑÑÐ°ÐµÑ Ð²ÑеÑаÑнÑÑ-ÑегоднÑÑнÑÑ ÐºÐ¾Ð¿Ð¸Ñ 256 // ÑÑазÑ, а ÑÐ²ÐµÐ¶Ð°Ñ Ð´Ð¾Ð³ÑÑжаеÑÑÑ Ð² Ñоне и лÑÐ¶ÐµÑ Ð² кÑÑ Ðº ÑледÑÑÑÐµÐ¼Ñ Ð·Ð°Ð¿ÑÑкÑ. 257 // 258 // TODO(adminoi, 2026-10-09): добавиÑÑ ÑÑеÑÑе ÑоÑÑоÑние «ÑвÑÐ·Ñ ÐµÑÑÑ, оÑвеÑа 259 // неÑ» Ð´Ð»Ñ ÑÐµÑ , Ñ ÐºÐ¾Ð³Ð¾ копии Ð½ÐµÑ Ð²Ð¾Ð²Ñе. ÐокÑÑÑие ÑÑой пÑавки обÑаÑно 260 // ÑооÑноÑиÑÑÑ Ñ Ð±ÐµÐ´Ð¾Ð¹: ежедневнÑй ÑиÑаÑÐµÐ»Ñ ÑпаÑÑн Ñвежей копией, а Ñедкий 261 // ÑÑ Ð¾Ð´Ð¸Ñ Ð² веÑÐºÑ !copy и Ð²Ð¸Ð´Ð¸Ñ ÑÐ¾Ñ Ð¶Ðµ белÑй ÑкÑан â пÑи Ñом, ÑÑо Ñ Ð½ÐµÐ³Ð¾ Ñадио 262 // Ñпало долÑÑе и молÑаÑÐ°Ñ ÑеÑÑ Ð²ÐµÑоÑÑнее. То еÑÑÑ Ð±ÐµÐ· ÑÑеÑÑего ÑоÑÑоÑÐ½Ð¸Ñ 263 // незакÑÑÑÑм оÑÑаÑÑÑÑ ÑамÑй ÑаÑÑÑй иÑÑоÑник жалобÑ. ÐамеÑание ÑеÑÑии 264 // omskinform. 265 // 266 // ÐÑоÑой иÑÑоÑник Ñой же Ð±ÐµÐ´Ñ Ð½Ð°Ñла ÑеÑÑÐ¸Ñ oi на Ñвоей панели: ÑÑÐ°Ð·Ñ Ð¿Ð¾Ñле 267 // пеÑезапÑÑка ÑеÑвеÑа Ñоединение не молÑиÑ, а ÐТÐÐÐ ÐÐÐТСЯ â и Ñакой оÑказ 268 // легко пÑинÑÑÑ Ð·Ð° оÑлайн. У Ð½ÐµÑ ÑÑо длилоÑÑ 1,6 ÑекÑндÑ, поÑле Ñего пеÑвÑй 269 // оÑÐ²ÐµÑ Ð¿ÑиÑÑл ÑеÑез 36 ÑекÑнд. ÐнаÑÐ¸Ñ ÑÑеÑÑе ÑоÑÑоÑние нÑжно и Ð´Ð»Ñ Ð¾Ñказа 270 // ÑÐ¾ÐµÐ´Ð¸Ð½ÐµÐ½Ð¸Ñ Ð¿Ñи живом инÑеÑнеÑе, а не ÑолÑко Ð´Ð»Ñ Ð¼Ð¾Ð»ÑÐ°Ð½Ð¸Ñ ÑеÑи. 271 event.waitUntil(fromNetwork.then((late) => keep(PAGES_CACHE, event.request, late.clone())).catch(() => undefined)) 272 273 // ÐÑмеÑка «показана копиÑ» обÑзана ÑÑоÑÑÑ Ð¸ здеÑÑ. ÐÑа веÑка поÑвилаÑÑ Ð²Ð¼ÐµÑÑе 274 // Ñ Ð¿Ñеделом Ð¾Ð¶Ð¸Ð´Ð°Ð½Ð¸Ñ Ð¸ забиÑÐ°ÐµÑ Ñ offlineFallback главнÑй ÑлÑÑай оÑдаÑи 275 // копии: ÑеÑÑ Ð¾Ñказала или пÑомолÑала, а ÑÐ²ÐµÐ¶Ð°Ñ ÐºÐ¾Ð¿Ð¸Ñ ÐµÑÑÑ. Ðез оÑмеÑки 276 // полоÑка «Ðоказано ÑÐ¾Ñ ÑанÑнное» не покажеÑÑÑ Ð¸Ð¼ÐµÐ½Ð½Ð¾ Ñогда, когда нÑжна. 277 await markNavigation(event.request, Number(copy.headers.get(CACHED_AT))).catch(() => undefined) 278 279 return copy 280} 281 282const fromCacheFirst = async (event, cacheName) => { 283 const cache = await caches.open(cacheName) 284 const cached = await cache.match(event.request) 285 286 if (cached) { 287 return cached 288 } 289 290 const response = await fetch(event.request) 291 event.waitUntil(keep(cacheName, event.request, response.clone())) 292 293 return response 294} 295 296const dropForeignCaches = async () => { 297 const names = await caches.keys() 298 const stale = names.filter((name) => name.startsWith(CACHE_PREFIX) && !OWN_CACHES.includes(name)) 299 300 await Promise.all(stale.map((name) => caches.delete(name))) 301} 302 303self.addEventListener('install', (event) => { 304 event.waitUntil(ensureOfflinePage().then(() =>
304 self.skipWaiting())) 305}) 306 307self.addEventListener('activate', (event) => { 308 event.waitUntil(dropForeignCaches().then(() => self.clients.claim())) 309}) 310 311self.addEventListener('fetch', (event) => { 312 if (!isOwnRequest(event.request)) { 313 return 314 } 315 316 if (event.request.mode === 'navigate') { 317 event.respondWith(pageFromNetwork(event)) 318 319 return 320 } 321 322 const url = new URL(event.request.url) 323 324 if (isBuildAsset(url)) { 325 event.respondWith(fromCacheFirst(event, STATIC_CACHE)) 326 327 return 328 } 329 330 if (isArticleImage(url)) { 331 event.respondWith(fromCacheFirst(event, IMAGES_CACHE)) 332 } 333}) 334 335self.addEventListener('push', (event) => { 336 let data = {} 337 try { 338 data = event.data ? event.data.json() : {} 339 } catch (error) { 340 data = {} 341 } 342 343 const title = data.title || 'ÐмÑк-инÑоÑм' 344 const options = { 345 body: data.body || '', 346 icon: '/icon-192.png', 347 badge: '/icon-192.png', 348 tag: data.tag, 349 data: { url: data.url || '/' }, 350 } 351 352 event.waitUntil(self.registration.showNotification(title, options)) 353}) 354 355self.addEventListener('notificationclick', (event) => { 356 event.notification.close() 357 const url = (event.notification.data && event.notification.data.url) || '/' 358 359 event.waitUntil( 360 self.clients.matchAll({ type: 'window', includeUncontrolled: true }).then((clientList) => { 361 for (const client of clientList) { 362 if ('focus' in client) { 363 client.navigate(url) 364 return client.focus() 365 } 366 } 367 return self.clients.openWindow(url) 368 }), 369 ) 370})
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.