PageSourceSearch

https://www.m-translate.org.ua/sw.js

js m-translate.org.ua collected 2026-10-06 04:33:44 UTC 5,826 bytes, 246 lines download raw bytes

1const CACHE_PREFIX = "mtranslate-pwa";
2const STATIC_CACHE = `${CACHE_PREFIX}-static-v1`;
3const PAGE_CACHE = `${CACHE_PREFIX}-pages-v1`;
4const RUNTIME_CACHE = `${CACHE_PREFIX}-runtime-v1`;
5const VERSION_CACHE = `${CACHE_PREFIX}-version`;
6const VERSION_KEY = "/__mtranslate_pwa_version__";
7
8const APP_SHELL_URLS = [
9  "/pwa_app",
10  "/manifest.json",
11  "/images/pwa_icons/icon_96.png",
12  "/images/pwa_icons/icon_128.png",
13  "/images/pwa_icons/icon_144.png",
14  "/images/pwa_icons/icon_152.png",
15  "/images/pwa_icons/icon_192.png",
16  "/images/pwa_icons/icon_256.png",
17  "/images/pwa_icons/icon_512.png",
18];
19
20const BYPASS_PREFIXES = [
21  "/api/2/",
22  "/api/trpc",
23  "/admin",
24  "/cpanel",
25  "/login",
26  "/logout",
27  "/review",
28  "/translate",
29  "/word-detail",
30  "/word-alternative",
31  "/alternative",
32  "/204check",
33  "/geo.js",
34  "/index.js",
35];
36
37function isSameOrigin(url) {
38  return url.origin === self.location.origin;
39}
40
41function shouldBypass(request) {
42  const url = new URL(request.url);
43
44  if (request.method !== "GET" || !isSameOrigin(url)) {
45    return true;
46  }
47
48  return BYPASS_PREFIXES.some((prefix) => url.pathname.startsWith(prefix));
49}
50
51function isStaticAsset(url) {
52  return (
53    url.pathname.startsWith("/_next/static/") ||
54    url.pathname.startsWith("/images/pwa_icons/") ||
55    /\.(?:css|js|mjs|png|jpg|jpeg|gif|svg|webp|ico|woff2?)$/i.test(url.pathname)
56  );
57}
58
59async function readStoredVersion() {
60  const cache = await caches.open(VERSION_CACHE);
61  const response = await cache.match(VERSION_KEY);
62
63  if (!response) {
64    return null;
65  }
66
67  try {
68    const payload = await response.json();
69    return typeof payload.version === "string" ? payload.version : null;
70  } catch {
71    return null;
72  }
73}
74
75async function writeStoredVersion(version) {
76  const cache = await caches.open(VERSION_CACHE);
77  await cache.put(
78    VERSION_KEY,
79    new Response(JSON.stringify({ version }), {
80      headers: { "content-type": "application/json" },
81    }),
82  );
83}
84
85async function fetchServerVersion() {
86  const response = await fetch(`/api/pwa-version?t=${Date.now()}`, {
87    cache: "no-store",
88    credentials: "same-origin",
89  });
90
91  if (!response.ok) {
92    return null;
93  }
94
95  const payload = await response.json();
96  return typeof payload.version === "string" ? payload.version : null;
97}
98
99async function clearAppCaches() {
100  const keys = await caches.keys();
101  await Promise.all(
102    keys
103      .filter((key) => key.startsWith(CACHE_PREFIX) && key !== VERSION_CACHE)
104      .map((key) => caches.delete(key)),
105  );
106}
107
108async function notifyClients(message) {
109  const clients = await self.clients.matchAll({ includeUncontrolled: true });
110  clients.forEach((client) => client.postMessage(message));
111}
112
113async function syncServerVersion({ notify = false } = {}) {
114  const latestVersion = await fetchServerVersion();
115
116  if (!latestVersion) {
117    return false;
118  }
119
120  const storedVersion = await readStoredVersion();
121
122  if (!storedVersion) {
123    await writeStoredVersion(latestVersion);
124    return false;
125  }
126
127  if (storedVersion === latestVersion) {
128    return false;
129  }
130
131  await clearAppCaches();
132  await writeStoredVersion(latestVersion);
133
134  if (notify) {
135    await notifyClients({ type: "PWA_VERSION_CHANGED", version: latestVersion });
136  }
137
138  return true;
139}
140
141async function cacheAppShell() {
142  const cache = await caches.open(STATIC_CACHE);
143
144  await Promise.all(
145    APP_SHELL_URLS.map(async (url) => {
146      try {
147        await cache.add(new Request(url, { cache: "reload" }));
148      } catch {
149        // A failed optional asset must not abort service worker installation.
150      }
151    }),
152  );
153}
154
155async function networkFirstNavigation(request) {
156  const cache = await caches.open(PAGE_CACHE);
157
158  try {
159    const response = await fetch(request);
160
161    if (response.ok && isSameOrigin(new URL(response.url))) {
162      const url = new URL(request.url);
163      if (APP_SHELL_URLS.includes(url.pathname)) {
164        await cache.put(request, response.clone());
165      }
166    }
167
168    return response;
169  } catch {
170    const cachedResponse = await caches.match(request);
171    const shellResponse = await caches.match("/pwa_app");
172
173    return (
174      cachedResponse ||
175      shellResponse ||
176      new Response("M-Translate is offline. Reconnect and try again.", {
177        headers: { "content-type": "text/plain;charset=utf-8" },
178        status: 503,
179      })
180    );
181  }
182}
183
184async function staleWhileRevalidate(request) {
185  const cache = await caches.open(RUNTIME_CACHE);
186  const cachedResponse = await cache.match(request);
187  const fetchPromise = fetch(request)
188    .then((response) => {
189      if (response.ok && isSameOrigin(new URL(response.url))) {
190        cache.put(request, response.clone());
191      }
192      return response;
193    })
194    .catch(() => cachedResponse);
195
196  return cachedResponse || fetchPromise;
197}
198
199self.addEventListener("install", (event) => {
200  event.waitUntil(cacheAppShell());
201});
202
203self.addEventListener("activate", (event) => {
204  event.waitUntil(
205    Promise.all([
206      self.clients.claim(),
207      syncServerVersion({ notify: true }),
208    ]),
209  );
210});
211
212self.addEventListener("message", (event) => {
213  if (event.data?.type === "SKIP_WAITING") {
214    self.skipWaiting();
215    return;
216  }
217
218  if (event.data?.type === "CHECK_VERSION") {
219    event.waitUntil(syncServerVersion({ notify: true }));
220    return;
221  }
222
223  if (event.data?.type === "CLEAR_CACHES") {
224    event.waitUntil(clearAppCaches());
225  }
226});
227
228self.addEventListener("fetch", (event) => {
229  const { request } = event;
230
231  if (shouldBypass(request)) {
232    return;
233  }
234
235  const url = new URL(request.url);
236
237  if (request.mode === "navigate") {
238    event.waitUntil(syncServerVersion({ notify: true }));
239    event.respondWith(networkFirstNavigation(request));
240    return;
241  }
242
243  if (isStaticAsset(url)) {
244    event.respondWith(staleWhileRevalidate(request));
245  }
246});

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.