PageSourceSearch

https://radostmamina.ru/service-worker.js

js radostmamina.ru collected 2026-09-26 11:11:36 UTC 5,804 bytes, 172 lines download raw bytes

1// Service worker for smart-mirror failover.
2//
3// Generated per-request by node/server.js (route GET /service-worker).
4// The backup domain is picked from the same auto-increment group of 10
5// in the `offlinedomains` collection and injected via EJS below.
6//
7// Behavior on top-level HTML navigations:
8//   1. Fire the original request immediately.
9//   2. In parallel, after BACKUP_TIMEOUT_MS, probe the backup mirror's
10//      sentinel file. If it returns 2xx, build a 302 redirect to the same
11//      path on the backup with ?od=<original host> appended.
12//   3. Original wins as soon as it returns a 2xx response.
13//   4. Backup wins if the probe succeeds before the original returns ok.
14//   5. If both fail, fall back to a synthetic 503 (or the non-ok original
15//      response, which is more informative).
16//
17// Non-navigation requests (assets, XHR, iframes, POST forms, …) are NOT
18// intercepted — the browser handles them normally.
19
20const BACKUP_DOMAIN     = 'dental-prof-implantation.ru';
21const BACKUP_TIMEOUT_MS = 5000;
22const PROBE_PATH        = '/test74382647832.txt';
23
24const LOG = '[sw-mirror]';
25const tnow = () => Date.now();
26
27self.addEventListener('install', (event) => {
28  console.log(LOG, 'install; backupDomain=' + (BACKUP_DOMAIN || '(none)'));
29  event.waitUntil(self.skipWaiting());
30});
31
32self.addEventListener('activate', (event) => {
33  console.log(LOG, 'activate');
34  event.waitUntil(self.clients.claim());
35});
36
37function shouldHandle(request) {
38  if (request.destination === 'iframe') return false;
39  if (request.mode === 'navigate') return true;
40  if (request.method !== 'GET') return false;
41  const accept = request.headers.get('accept') || '';
42  return accept.includes('text/html');
43}
44
45self.addEventListener('fetch', (event) => {
46  if (!shouldHandle(event.request)) return;
47  event.respondWith(handleNavigation(event.request));
48});
49
50let requestSeq = 0;
51
52async function handleNavigation(request) {
53  const id = (++requestSeq).toString(36);
54  const start = tnow();
55  const ms = () => (tnow() - start) + 'ms';
56  const tag = (msg) => `${LOG} [req#${id}] ${msg}`;
57
58  console.log(tag('navigation'), {
59    url: request.url,
60    mode: request.mode,
61    destination: request.destination,
62    method: request.method,
63    backupDomain: BACKUP_DOMAIN || '(none)',
64  });
65
66  // Guard against degenerate backups (no partner found, or partner == self).
67  const sameAsSelf = BACKUP_DOMAIN &&
68    BACKUP_DOMAIN.toLowerCase() === new URL(request.url).hostname.toLowerCase();
69  if (!BACKUP_DOMAIN || sameAsSelf) {
70    console.warn(tag(`no usable backup (${sameAsSelf ? 'equals self' : 'empty'}); passthrough`));
71    try {
72      const r = await fetch(request);
73      console.log(tag(`passthrough status=${r.status} (${ms()})`));
74      return r;
75    } catch (e) {
76      console.error(tag(`passthrough failed: ${(e && e.message) || e} (${ms()})`));
77      return new Response('Service Unavailable', { status: 503 });
78    }
79  }
80
81  let originalOk = false;
82
83  // Both promises ALWAYS resolve (never reject) — see .catch() below.
84  const originalPromise = fetch(request)
85    .then((response) => {
86      originalOk = response.ok;
87      console.log(tag(`original resolved status=${response.status} ok=${response.ok} (${ms()})`));
88      return response;
89    })
90    .catch((e) => {
91      console.warn(tag(`original rejected: ${(e && e.message) || e} (${ms()})`));
92      return null;
93    });
94
95  const backupPromise = tryBackup(request, tag, ms, () => originalOk);
96
97  // Decision logic:
98  //   - As soon as original resolves with ok=true: use it.
99  //   - As soon as backup produces a redirect: use it.
100  //   - Otherwise wait for both, then pick best available (backup > non-ok original > 503).
101  return await new Promise((resolve) => {
102    let done = false;
103    const finish = (response, why) => {
104      if (done) return;
105      done = true;
106      console.log(tag(`final: ${why} (${ms()})`));
107      resolve(response);
108    };
109
110    originalPromise.then((r) => {
111      if (r && r.ok) finish(r, `original ok status=${r.status}`);
112    });
113
114    backupPromise.then((r) => {
115      if (r) finish(r, 'backup redirect');
116    });
117
118    Promise.all([originalPromise, backupPromise]).then(([orig, bkp]) => {
119      if (done) return;
120      if (bkp) {
121        finish(bkp, 'backup (late)');
122      } else if (orig) {
123        finish(orig, `original non-ok status=${orig.status}`);
124      } else {
125        finish(
126          new Response('Service Unavailable (no mirror available)', {
127            status: 503,
128            headers: { 'Content-Type': 'text/plain' },
129          }),
130          'both failed'
131        );
132      }
133    });
134  });
135}
136
137async function tryBackup(request, tag, ms, isOriginalOk) {
138  console.log(tag(`backup timer armed: ${BACKUP_TIMEOUT_MS}ms`));
139  await new Promise((r) => setTimeout(r, BACKUP_TIMEOUT_MS));
140
141  if (isOriginalOk()) {
142    console.log(tag(`backup canceled: original already ok (${ms()})`));
143    return null;
144  }
145
146  const probeUrl = `https://${BACKUP_DOMAIN}${PROBE_PATH}`;
147  console.log(tag(`probing backup: ${probeUrl} (${ms()})`));
148
149  let probe;
150  try {
151    probe = await fetch(probeUrl, { cache: 'no-store' });
152  } catch (e) {
153    console.error(tag(`backup probe rejected: ${(e && e.message) || e} (${ms()})`));
154    return null;
155  }
156  console.log(tag(`backup probe status=${probe.status} ok=${probe.ok} type=${probe.type} (${ms()})`));
157  if (!probe.ok) return null;
158
159  if (isOriginalOk()) {
160    console.log(tag(`original succeeded during probe; not redirecting (${ms()})`));
161    return null;
162  }
163
164  const target = new URL(request.url);
165  const originalHost = target.host;
166  target.hostname = BACKUP_DOMAIN;
167  target.port = '';
168  target.searchParams.set('od', originalHost);
169
170  console.log(tag(`redirect -> ${target.toString()} (${ms()})`));
171  return Response.redirect(target.toString(), 302);
172}

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.