1var DOMAIN_FALLBACK_CACHE = 'domain-fallback-v3'; 2var DOMAIN_FALLBACK_SPLASH = '/domain-fallback/splash.html'; 3var DOMAIN_FALLBACK_UNAVAILABLE = '/domain-fallback/unavailable.html'; 4var DOMAIN_FALLBACK_LOGO = '/media/assets/app/src/img/logo.png'; 5var DOMAIN_FALLBACK_ASSETS = [ 6 DOMAIN_FALLBACK_SPLASH, 7 DOMAIN_FALLBACK_UNAVAILABLE, 8 DOMAIN_FALLBACK_LOGO, 9]; 10var DOMAIN_FALLBACK_SPLASH_DELAY_MS = 8000; 11var DOMAIN_FALLBACK_FETCH_HARD_TIMEOUT_MS = 60000; 12var DOMAIN_FALLBACK_PROBE_TIMEOUT_MS = 5000; 13var DOMAIN_FALLBACK_TECH_TIMEOUT_MS = 8000; 14var DOMAIN_FALLBACK_TECH_NEXT_DOMAIN_URL = 'https://tech.taxi1.ws/system/api/next-domain'; 15var DOMAIN_FALLBACK_NAVIGATE_FAILURE_THRESHOLD = 2; 16var DOMAIN_FALLBACK_NAVIGATE_FAILURE_WINDOW_MS = 5 * 60 * 1000; 17var DOMAIN_FALLBACK_IDB_NAME = 'txm-domain-fallback'; 18var DOMAIN_FALLBACK_IDB_STORE = 'state'; 19var DOMAIN_FALLBACK_IDB_KEY = 'fallback'; 20var DOMAIN_FALLBACK_IDB_KEY_NAVIGATE_FAILURES = 'navigate-failures'; 21 22self.addEventListener('install', function (event) { 23 event.waitUntil( 24 caches.open(DOMAIN_FALLBACK_CACHE).then(function (cache) { 25 return cache.addAll(DOMAIN_FALLBACK_ASSETS); 26 }).then(function () { 27 return self.skipWaiting(); 28 }) 29 ); 30}); 31 32self.addEventListener('activate', function (event) { 33 event.waitUntil( 34 caches.keys().then(function (keys) { 35 return Promise.all(keys.filter(function (key) { 36 return key.indexOf('domain-fallback-') === 0 && key !== DOMAIN_FALLBACK_CACHE; 37 }).map(function (key) { 38 return caches.delete(key); 39 })); 40 }).then(function () { 41 return self.clients.claim(); 42 }) 43 ); 44}); 45 46self.addEventListener('message', function (event) { 47 if (!event.data || event.data.type !== 'CLEAR_FALLBACK') { 48 return; 49 } 50 51 event.waitUntil(clearFallbackState()); 52}); 53 54self.addEventListener('push', function (event) { 55 if (!(self.Notification && self.Notification.permission === 'granted')) { 56 return; 57 } 58 59 const sendNotification = payload => { 60 if (payload.data.hasOwnProperty('token') && payload.data.token) { 61 fetch('/push-notifications/delivered', { 62 method: 'POST', 63 headers: { 64 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' 65 }, 66 body: 'token=' + payload.data.token 67 }) 68 } 69 70 return self.registration.showNotification(payload.title, { 71 body: payload.body, 72 icon: payload.icon, 73 actions: payload.actions ? payload.actions : [], 74 data: payload.data 75 }); 76 }; 77 78 if (event.data) { 79 const payload = JSON.parse(event.data.text()); 80 event.waitUntil(sendNotification(payload)); 81 } 82}); 83 84self.addEventListener('notificationclick', function(event) { 85 const clickedNotification = event.notification; 86 clickedNotification.close(); 87 88 const clients = self.clients; 89 const notification = event.notification; 90 91 if (notification.data.hasOwnProperty('token') && notification.data.token) { 92 fetch('/push-notifications/clicked', { 93 method: 'POST', 94 headers: { 95 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' 96 }, 97 body: 'token=' + notification.data.token 98 }) 99 } 100 101 if (notification.data.hasOwnProperty('url') && notification.data.url) { 102 return clients.openWindow(notification.data.url); 103 } else { 104 return clients.openWindow('/'); 105 } 106}); 107 108self.addEventListener('fetch', function (event) { 109 var url = new URL(event.request.url); 110 111 if (event.request.mode === 'navigate') { 112 if (event.request.method !== 'GET' || isAdminPath(url.pathname)) { 113 return; 114 } 115 116 event.respondWith(handleNavigateRequest(event)); 117 return; 118 } 119 120 if (url.origin === self.location.origin && isFallbackAsset(url.pathname)) { 121 event.respondWith(getCachedPage(url.pathname)); 122 } 123}); 124 125function isAdminPath(pathname) { 126 return pathname === '/admin' || pathname.indexOf('/admin/') === 0; 127} 128 129function isFallbackAsset(pathname) { 130 return DOMAIN_FALLBACK_ASSETS.indexOf(pathname) !== -1; 131} 132 133function handleNavigateRequest(event) { 134 var request = event.request; 135 var pending = startNavigateFetch(request); 136 var splashTimer = null; 137 var splashDelay = new Promise(function (resolve) { 138 splashTimer = setTimeout(function () { 139 resolve({splash: true}); 140 }, DOMAIN_FALLBACK_SPLASH_DELAY_MS); 141 }); 142 var settled = pending.promise.then(function (response) { 143 return {response: response}; 144 }, function (error) { 145 return {error: error || new Error('fetch failed')}; 146 }); 147 148 return Promise.race([settled, splashDelay]).then(function (outcome) { 149 clearTimeout(splashTimer); 150 151 if (outcome.response) { 152 return clearNavigateFailures().then(function () { 153 return outcome.response; 154 }); 155 } 156 157 if (outcome.error) { 158 return respondNavigateFailure(request); 159 } 160 161 if (typeof ReadableStream === 'undefined' || typeof TextEncoder === 'undefined') { 162 pending.abort(); 163 164 return respondNavigateFailure(request); 165 } 166 167 return respondSplashStream(event, request, pending.promise); 168 }); 169} 170 171function startNavigateFetch(request) { 172 var controller = typeof AbortController !== 'undefined' ? new AbortController() : null;
173 var fetchRequest = controller ? new Request(request, {signal: controller.signal}) : request; 174 var hardTimer = null; 175 var abort = function () { 176 if (controller) { 177 controller.abort(); 178 } 179 }; 180 var hardTimeout = new Promise(function (resolve, reject) { 181 hardTimer = setTimeout(function () { 182 abort(); 183 reject(new Error('timeout')); 184 }, DOMAIN_FALLBACK_FETCH_HARD_TIMEOUT_MS); 185 }); 186 var promise = Promise.race([fetch(fetchRequest), hardTimeout]).then(function (response) { 187 clearTimeout(hardTimer); 188 189 return response; 190 }, function (error) { 191 clearTimeout(hardTimer); 192 throw error; 193 }); 194 195 return {promise: promise, abort: abort}; 196} 197 198function respondNavigateFailure(request) { 199 return recordNavigateFailure().then(function (failureCount) { 200 return runDeadDomainFallback(request, failureCount >= DOMAIN_FALLBACK_NAVIGATE_FAILURE_THRESHOLD); 201 }); 202} 203 204function respondSplashStream(event, request, fetchPromise) { 205 return getSplashHtml().then(function (splashHtml) { 206 var encoder = new TextEncoder(); 207 var parts = splitSplashHtml(splashHtml); 208 var stream = new ReadableStream({ 209 start: function (controller) { 210 controller.enqueue(encoder.encode(parts.open + buildRenderPadding())); 211 212 var finish = fetchPromise.then(function (response) { 213 return clearNavigateFailures().then(function () { 214 return buildLateResponseScript(response, request); 215 }); 216 }, function () { 217 return recordNavigateFailure().then(function (failureCount) { 218 return buildFallbackRouterConfig(request, failureCount >= DOMAIN_FALLBACK_NAVIGATE_FAILURE_THRESHOLD); 219 }).then(buildFallbackRouterScript); 220 }).catch(function () { 221 return buildReloadScript(request.url); 222 }).then(function (script) { 223 controller.enqueue(encoder.encode(script + parts.close)); 224 controller.close(); 225 }).catch(function () { 226 try { 227 controller.close(); 228 } catch (error) { 229 } 230 }); 231 232 event.waitUntil(finish); 233 }, 234 }); 235 236 return new Response(stream, { 237 headers: { 238 'Content-Type': 'text/html; charset=UTF-8', 239 'Cache-Control': 'no-store', 240 }, 241 }); 242 }); 243} 244 245function getSplashHtml() { 246 return getCachedPage(DOMAIN_FALLBACK_SPLASH).then(function (response) { 247 return response.text(); 248 }); 249} 250 251function splitSplashHtml(splashHtml) { 252 var bodyEnd = splashHtml.lastIndexOf('</body>'); 253 if (bodyEnd === -1) { 254 return {open: splashHtml, close: ''}; 255 } 256 257 return { 258 open: splashHtml.slice(0, bodyEnd), 259 close: splashHtml.slice(bodyEnd), 260 }; 261} 262 263function buildRenderPadding() { 264 return '<!--' + ' '.repeat(1024) + '-->'; 265} 266 267function htmlResponse(html) { 268 return new Response(html, { 269 headers: {'Content-Type': 'text/html; charset=UTF-8'}, 270 }); 271} 272 273function encodeForInlineScript(value) { 274 return JSON.stringify(value) 275 .replace(/<\//g, '<\\/') 276 .replace(/<!--/g, '<\\!--'); 277} 278 279function buildReloadScript(url) { 280 return '<script>window.location.replace(' + encodeForInlineScript(url) + ');</script>'; 281} 282 283function buildLateResponseScript(response, request) { 284 if (!response 285 || response.type === 'opaqueredirect' 286 || response.redirected 287 || (response.status >= 300 && response.status < 400) 288 ) { 289 return buildReloadScript(request.url); 290 } 291 292 var contentType = (response.headers.get('Content-Type') || '').toLowerCase(); 293 if (contentType.indexOf('text/html') === -1) { 294 return buildReloadScript(request.url); 295 } 296 297 return response.text().then(function (html) { 298 return '<script>(function(){' 299 + 'var html=' + encodeForInlineScript(html) + ';' 300 + 'function replace(){document.open();document.write(html);document.close();}' 301 + 'function schedule(){setTimeout(replace,0);}' 302 + 'if(document.readyState==="loading"){document.addEventListener("DOMContentLoaded",schedule);}else{schedule();}' 303 + '})();</script>'; 304 }); 305} 306 307function buildFallbackRouterConfig(request, confirmedDead) { 308 var base = { 309 originalUrl: request.url, 310 fromDomain: self.location.hostname, 311 token: '', 312 shouldLookupNext: confirmedDead, 313 probeTimeoutMs: DOMAIN_FALLBACK_PROBE_TIMEOUT_MS, 314 techTimeoutMs: DOMAIN_FALLBACK_TECH_TIMEOUT_MS, 315 techNextDomainUrl: DOMAIN_FALLBACK_TECH_NEXT_DOMAIN_URL, 316 }; 317 318 return getFallbackState().then(function (state) { 319 if (state && state.currentDomain === self.location.hostname && state.token) { 320 base.token = state.token; 321 } 322 323 return base; 324 }).catch(function () { 325 return base; 326 }); 327} 328 329function runDeadDomainFallback(request, confirmedDead) { 330 return Promise.all([
331 getSplashHtml(), 332 buildFallbackRouterConfig(request, confirmedDead), 333 ]).then(function (parts) { 334 var split = splitSplashHtml(parts[0]); 335 336 return htmlResponse(split.open + buildFallbackRouterScript(parts[1]) + split.close); 337 }).catch(function () { 338 return getCachedPage(DOMAIN_FALLBACK_SPLASH); 339 }); 340} 341 342function buildFallbackRouterScript(cfg) { 343 var payload = JSON.stringify({ 344 originalUrl: cfg.originalUrl, 345 fromDomain: cfg.fromDomain, 346 token: cfg.token, 347 shouldLookupNext: cfg.shouldLookupNext, 348 probeTimeoutMs: cfg.probeTimeoutMs, 349 techTimeoutMs: cfg.techTimeoutMs, 350 techNextDomainUrl: cfg.techNextDomainUrl, 351 }); 352 353 var script = '<script>(function(){' 354 + 'var cfg=' + payload + ';' 355 + 'function probeDomain(domain){' 356 + 'var controller=typeof AbortController!=="undefined"?new AbortController():null;' 357 + 'var timer=controller?setTimeout(function(){controller.abort();},cfg.probeTimeoutMs):null;' 358 + 'var opts={method:"GET",mode:"no-cors",cache:"no-store",credentials:"omit"};' 359 + 'if(controller){opts.signal=controller.signal;}' 360 + 'return fetch("https://"+domain+"/system/api/domain?domain="+encodeURIComponent(domain),opts)' 361 + '.then(function(){if(timer){clearTimeout(timer);}return true;})' 362 + '.catch(function(){if(timer){clearTimeout(timer);}return false;});' 363 + '}' 364 + 'function buildRedirectUrl(domain){' 365 + 'var source=new URL(cfg.originalUrl);' 366 + 'var target=new URL(source.pathname+source.search,"https://"+domain);' 367 + 'if(!cfg.token){return target.toString();}' 368 + 'var params=new URLSearchParams(target.search);' 369 + 'params.set("token",cfg.token);' 370 + 'params.set("relogin","1");' 371 + 'params.set("domain_fallback","1");' 372 + 'params.set("from_domain",cfg.fromDomain||"");' 373 + 'params.set("from_url",cfg.originalUrl||"");' 374 + 'target.search=params.toString();' 375 + 'return target.toString();' 376 + '}' 377 + 'function showUnavailable(){' 378 + 'document.body.innerHTML="<div style=\\"text-align:center;padding:24px;font-family:sans-serif;color:#e8ecf4;background:#1a1f2e;min-height:100vh;display:flex;align-items:center;justify-content:center\\"><div><img src=\\"/media/assets/app/src/img/logo.png\\" alt=\\"Taxi Money\\" width=\\"180\\" style=\\"display:block;width:180px;height:auto;margin:0 auto 28px\\"><h1 style=\\"font-size:20px;margin-bottom:12px\\">Ð¡Ð°Ð¹Ñ Ð²Ñеменно недоÑÑÑпен</h1><p style=\\"opacity:0.85;line-height:1.5\\">ÐопÑобÑйÑе зайÑи позже или обÑаÑиÑеÑÑ Ð² поддеÑжкÑ.</p><div style=\\"margin-top:24px;display:flex;flex-direction:column;gap:12px\\"><a href=\\"https://a.taxi-money.games/login?aid=1394\\" rel=\\"noopener noreferrer\\" style=\\"color:#6b9fff;font-size:15px;text-decoration:none\\">Ðазовое зеÑкало</a><a href=\\"https://t.me/txmoney\\" rel=\\"noopener noreferrer\\" target=\\"_blank\\" style=\\"color:#6b9fff;font-size:15px;text-decoration:none\\">Telegram-гÑÑппа</a></div></div></div>";' 379 + '}' 380 + 'function fetchNextDomains(){' 381 + 'var controller=typeof AbortController!=="undefined"?new AbortController():null;' 382 + 'var timer=controller?setTimeout(function(){controller.abort();},cfg.techTimeoutMs):null;' 383 + 'var url=cfg.techNextDomainUrl+"?domain="+encodeURIComponent(cfg.fromDomain)+"&_="+Date.now();' 384 + 'var opts={method:"GET",cache:"no-store",credentials:"omit",mode:"cors"};' 385 + 'if(controller){opts.signal=controller.signal;}' 386 + 'return fetch(url,opts).then(function(response){' 387 + 'if(timer){clearTimeout(timer);}' 388 + 'if(!response||!response.ok){return[];}' 389 + 'return response.json();' 390 + '}).then(function(data){' 391 + 'if(!data){return[];}' 392 + 'if(data.next_domains&&data.next_domains.length){return data.next_domains;}' 393 + 'if(data.next_domain){return[data.next_domain];}' 394 + 'return[];' 395 + '}).catch(function(){if(timer){clearTimeout(timer);}return[];});' 396 + '}' 397 + 'function redirectToFirstAlive(domains){' 398 + 'var chain=Promise.resolve(null);' 399 + 'domains.forEach(function(domain){' 400 + 'chain=chain.then(function(found){' 401 + 'if(found){return found;}' 402 + 'return probeDomain(domain).then(function(alive){return alive?domain:null;});' 403 + '});' 404 + '});' 405 + 'chain.then(function(aliveDomain){' 406 + 'if(aliveDomain){window.location.replace(buildRedirectUrl(aliveDomain));return;}' 407 + 'showUnavailable();' 408 + '});' 409 + '}' 410 + 'function run(){' 411 + 'if(!cfg.shouldLookupNext){window.location.replace(cfg.originalUrl);return;}' 412 + 'fetchNextDomains().then(function(domains){' 413 + 'if(!domains.length){showUnavailable();return;}' 414 + 'redirectToFirstAlive(domains);' 415 + '});' 416 + '}' 417 + 'run();' 418 + '})();</script>'; 419 420 return script; 421} 422 423function getCachedPage(path) { 424 return caches.match(path).then(function (cached) { 425 if (cached) { 426 return cached; 427 } 428 429 return new Response('Service unavailable', { 430 status: 503, 431 headers: {'Content-Type': 'text/plain; charset=UTF-8'}, 432 }); 433 }); 434} 435 436function openFallbackDb() { 437 return new Promise(function (resolve, reject) { 438 var request = indexedDB.open(DOMAIN_FALLBACK_IDB_NAME, 1); 439
440 request.onupgradeneeded = function () { 441 var db = request.result; 442 if (!db.objectStoreNames.contains(DOMAIN_FALLBACK_IDB_STORE)) { 443 db.createObjectStore(DOMAIN_FALLBACK_IDB_STORE); 444 } 445 }; 446 447 request.onsuccess = function () { 448 resolve(request.result); 449 }; 450 451 request.onerror = function () { 452 reject(request.error); 453 }; 454 }); 455} 456 457function idbGet(store, key) { 458 return new Promise(function (resolve, reject) { 459 var request = store.get(key); 460 461 request.onsuccess = function () { 462 resolve(request.result); 463 }; 464 465 request.onerror = function () { 466 reject(request.error); 467 }; 468 }); 469} 470 471function idbPut(store, key, value) { 472 return new Promise(function (resolve, reject) { 473 var request = store.put(value, key); 474 475 request.onsuccess = function () { 476 resolve(value); 477 }; 478 479 request.onerror = function () { 480 reject(request.error); 481 }; 482 }); 483} 484 485function idbDelete(store, key) { 486 return new Promise(function (resolve, reject) { 487 var request = store.delete(key); 488 489 request.onsuccess = function () { 490 resolve(); 491 }; 492 493 request.onerror = function () { 494 reject(request.error); 495 }; 496 }); 497} 498 499function pruneNavigateFailures(timestamps) { 500 var minTs = Date.now() - DOMAIN_FALLBACK_NAVIGATE_FAILURE_WINDOW_MS; 501 502 return timestamps.filter(function (ts) { 503 return ts >= minTs; 504 }); 505} 506 507function getNavigateFailures() { 508 return openFallbackDb().then(function (db) { 509 var transaction = db.transaction(DOMAIN_FALLBACK_IDB_STORE, 'readonly'); 510 var store = transaction.objectStore(DOMAIN_FALLBACK_IDB_STORE); 511 512 return idbGet(store, DOMAIN_FALLBACK_IDB_KEY_NAVIGATE_FAILURES).then(function (value) { 513 if (!Array.isArray(value)) { 514 return []; 515 } 516 517 return pruneNavigateFailures(value); 518 }); 519 }); 520} 521 522function recordNavigateFailure() { 523 return getNavigateFailures().then(function (timestamps) { 524 timestamps.push(Date.now()); 525 timestamps = pruneNavigateFailures(timestamps); 526 527 return openFallbackDb().then(function (db) { 528 var transaction = db.transaction(DOMAIN_FALLBACK_IDB_STORE, 'readwrite'); 529 var store = transaction.objectStore(DOMAIN_FALLBACK_IDB_STORE); 530 531 return idbPut(store, DOMAIN_FALLBACK_IDB_KEY_NAVIGATE_FAILURES, timestamps).then(function () { 532 return timestamps.length; 533 }); 534 }); 535 }); 536} 537 538function clearNavigateFailures() { 539 return openFallbackDb().then(function (db) { 540 var transaction = db.transaction(DOMAIN_FALLBACK_IDB_STORE, 'readwrite'); 541 var store = transaction.objectStore(DOMAIN_FALLBACK_IDB_STORE); 542 543 return idbDelete(store, DOMAIN_FALLBACK_IDB_KEY_NAVIGATE_FAILURES); 544 }).catch(function () { 545 return null; 546 }); 547} 548 549function getFallbackState() { 550 return openFallbackDb().then(function (db) { 551 var transaction = db.transaction(DOMAIN_FALLBACK_IDB_STORE, 'readonly'); 552 var store = transaction.objectStore(DOMAIN_FALLBACK_IDB_STORE); 553 554 return idbGet(store, DOMAIN_FALLBACK_IDB_KEY); 555 }); 556} 557 558function clearFallbackState() { 559 return openFallbackDb().then(function (db) { 560 var transaction = db.transaction(DOMAIN_FALLBACK_IDB_STORE, 'readwrite'); 561 var store = transaction.objectStore(DOMAIN_FALLBACK_IDB_STORE); 562 563 return Promise.all([ 564 idbDelete(store, DOMAIN_FALLBACK_IDB_KEY), 565 idbDelete(store, DOMAIN_FALLBACK_IDB_KEY_NAVIGATE_FAILURES), 566 ]); 567 }); 568}
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.